mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 14:24:31 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9180a8182 | ||
|
|
7fa60e5ef6 |
+29
-17
@@ -522,27 +522,39 @@ def _current_venue():
|
||||
return best
|
||||
|
||||
|
||||
def _unplayed_genre_songs(gkey, exclude, limit):
|
||||
"""Library songs of a genre with no stats yet — a young passport's gig
|
||||
still gets a full set (playing them is how stubs start).
|
||||
ponytail: full stat-less scan + python-side genre match (a few ms at 7k
|
||||
songs, single-user); push the match into SQL if propose ever feels slow."""
|
||||
def _fill_genre_songs(gkey, exclude, limit):
|
||||
"""Library songs of a genre to round out a gig — ANY song of the genre the
|
||||
set hasn't already picked.
|
||||
|
||||
Was `_unplayed_genre_songs`, restricted to `filename NOT IN song_stats`.
|
||||
That restriction created a hole: a song you'd played on a DIFFERENT
|
||||
instrument's arrangement has a stats row, so it was excluded here — and it
|
||||
lives in the played bucket for THAT instrument, not this passport's, so it
|
||||
was excluded there too. It could never be gigged. A player with 137 metalcore
|
||||
songs, all played on another instrument, got a 404 (reproduced). The player's
|
||||
library is the pool; whether a song has stats on some other instrument has no
|
||||
bearing on whether it can be in THIS gig.
|
||||
|
||||
Shuffled, so re-roll actually changes the set. The old version returned the
|
||||
library's first N in table order every time, so re-roll was a no-op for any
|
||||
set drawn from the filler (reproduced).
|
||||
|
||||
ponytail: full genre scan + python-side match + shuffle (a few ms at 7k
|
||||
songs, single-user); push into SQL if propose ever feels slow.
|
||||
"""
|
||||
db = _state["meta_db"]
|
||||
if db is None:
|
||||
return []
|
||||
rows = db.conn.execute(
|
||||
f"SELECT filename, title, artist, {_genre_expr(db)} AS g FROM songs "
|
||||
"WHERE filename NOT IN (SELECT filename FROM song_stats)"
|
||||
f"SELECT filename, title, artist, {_genre_expr(db)} AS g FROM songs"
|
||||
).fetchall()
|
||||
out = []
|
||||
for filename, title, artist, genre in rows:
|
||||
if _genre_key(genre) != gkey or filename in exclude:
|
||||
continue
|
||||
out.append({"filename": filename, "title": title or filename,
|
||||
"artist": artist or ""})
|
||||
if len(out) >= limit:
|
||||
break
|
||||
return out
|
||||
pool = [
|
||||
{"filename": filename, "title": title or filename, "artist": artist or ""}
|
||||
for filename, title, artist, genre in rows
|
||||
if _genre_key(genre) == gkey and filename not in exclude
|
||||
]
|
||||
random.shuffle(pool) # re-roll must vary; free per call
|
||||
return pool[:limit]
|
||||
|
||||
|
||||
def _validate_pack_dir(pack_dir: Path):
|
||||
@@ -836,7 +848,7 @@ def setup(app, context):
|
||||
picks.append(s)
|
||||
if len(picks) < size:
|
||||
exclude = {s["filename"] for s in picks}
|
||||
picks.extend(_unplayed_genre_songs(gkey, exclude, size - len(picks)))
|
||||
picks.extend(_fill_genre_songs(gkey, exclude, size - len(picks)))
|
||||
if not picks:
|
||||
raise HTTPException(404, "No songs of this genre in the library.")
|
||||
venue = _current_venue()
|
||||
|
||||
@@ -1192,7 +1192,21 @@
|
||||
if (typeof window.setViz === 'function') window.setViz('venue');
|
||||
} catch (_) { /* viz optional — restore stays intact */ }
|
||||
}
|
||||
// Push the gig's venue pack to the crowd layer NOW.
|
||||
//
|
||||
// crowd.setManifest(venue) is reached only through pushCrowdManifest,
|
||||
// and pushCrowdManifest is called only from refresh() — the career
|
||||
// tab's own reload. A gig navigates AWAY from the career tab to the
|
||||
// player, so refresh() never runs during it, and setting the override
|
||||
// above does nothing on its own. The result the testers saw: the venue
|
||||
// visualization turns on (3D highway) but its crowd/stage pack never
|
||||
// loads, so the song plays over the bare highway backdrop ("standard
|
||||
// particles"), or over whatever venue a previous refresh() happened to
|
||||
// leave applied. We just changed the override to this gig's venue, so
|
||||
// re-push for it. _state is the career state the booking screen already
|
||||
// fetched; guard for the rare null.
|
||||
_appliedManifestVenue = null;
|
||||
if (_state) pushCrowdManifest(_state);
|
||||
_ppGigRun = {
|
||||
songs: prop.songs,
|
||||
venue_id: prop.venue_id,
|
||||
|
||||
@@ -116,3 +116,30 @@ test('career screen pushes the crowd manifest with a base URL', () => {
|
||||
// Degrades without the crowd layer (PR1 not merged / older desktop).
|
||||
assert.match(src, /typeof crowd\.setManifest !== 'function'\) return/);
|
||||
});
|
||||
|
||||
// feedBack#… (tester): "Venue doesn't load when starting song from passport.
|
||||
// Loads standard particles." crowd.setManifest(venue) is reached ONLY through
|
||||
// pushCrowdManifest, and pushCrowdManifest is called ONLY from refresh() (the
|
||||
// career tab's own reload). A gig navigates away from that tab, so refresh()
|
||||
// never runs during it — the venue viz turns on but its crowd/stage pack never
|
||||
// loads. startGig must push the manifest itself after setting the override.
|
||||
test('startGig pushes the crowd manifest for the gig venue', () => {
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const src = fs.readFileSync(
|
||||
path.join(__dirname, '..', '..', 'plugins', 'career', 'screen.js'), 'utf8');
|
||||
const start = src.indexOf('async function startGig(');
|
||||
assert.ok(start !== -1, 'startGig not found');
|
||||
const open = src.indexOf('{', src.indexOf(')', start));
|
||||
let depth = 1, i = open + 1;
|
||||
while (i < src.length && depth > 0) { const ch = src[i]; if (ch === '{') depth++; else if (ch === '}') depth--; i++; }
|
||||
const fn = src.slice(start, i);
|
||||
// The override is set, then the manifest must be (re)pushed for it.
|
||||
const overrideIdx = fn.search(/VENUE_OVERRIDE_KEY,\s*prop\.venue_id/);
|
||||
const pushIdx = fn.search(/pushCrowdManifest\s*\(/);
|
||||
assert.ok(overrideIdx !== -1, 'startGig must set the venue override');
|
||||
assert.ok(pushIdx !== -1,
|
||||
'startGig must push the crowd manifest — refresh() (its only other caller) ' +
|
||||
'never runs during a gig, so the venue pack would never load');
|
||||
assert.ok(overrideIdx < pushIdx, 'the manifest must be pushed AFTER the override is set to the gig venue');
|
||||
});
|
||||
|
||||
@@ -445,3 +445,30 @@ def test_gold_intake_rejects_junk(client, meta_db):
|
||||
res = client.post("/api/plugins/career/drill-state",
|
||||
json={"byNode": {}, "goldImprov": blob})
|
||||
assert res.status_code == 413
|
||||
|
||||
|
||||
def test_gig_includes_songs_played_on_another_instrument(client, meta_db):
|
||||
# feedBack#… (tester): "Metalcore says 137 songs, only shows 1 in the gig list".
|
||||
# A song played on a DIFFERENT instrument's arrangement has a stats row, so it
|
||||
# was excluded from the unplayed filler — and its played bucket is that other
|
||||
# instrument's, not this passport's — so it fell into a gap and could never be
|
||||
# gigged. A guitar passport with a library of bass-played metalcore got a 404.
|
||||
for i in range(137):
|
||||
meta_db.add(f"mc{i}.feedpak", 0, 0.80, genre="Metalcore", arrangements=BASS)
|
||||
res = client.post("/api/plugins/career/gigs/propose",
|
||||
json={"instrument": "guitar", "genre": "Metalcore", "size": 4})
|
||||
assert res.status_code == 200, "a full library of the genre must never 404"
|
||||
assert len(res.json()["songs"]) == 4, "the gig must fill from the library, not the gap"
|
||||
|
||||
|
||||
def test_gig_reroll_changes_the_set(client, meta_db):
|
||||
# feedBack#… (tester): "Passport re-roll does not change songs". A set drawn
|
||||
# from the filler used to be the library's first N in table order, every time.
|
||||
for i in range(40):
|
||||
meta_db.add_song_only(f"un{i}.feedpak", genre="Metalcore")
|
||||
sets = set()
|
||||
for _ in range(5):
|
||||
r = client.post("/api/plugins/career/gigs/propose",
|
||||
json={"instrument": "guitar", "genre": "Metalcore", "size": 4})
|
||||
sets.add(tuple(sorted(s["filename"] for s in r.json()["songs"])))
|
||||
assert len(sets) > 1, "re-roll must be able to produce a different set"
|
||||
|
||||
Reference in New Issue
Block a user