Fix bounty-hunt tracker so READY repaints when complete flips after all
- Include the quest's complete bit in the tracker row fingerprint so a later frame that only flips `complete` (with no check change) still triggers a repaint to READY. - Update bounty-hunt and quest-tracker tests to pin the ordering: all legs checked while contract is still out, then COMPLETE landing on a subsequent frame with checks unchanged. - Add station surface music tracks for spacestation variants 0 and 1 in data/music.json.
This commit is contained in:
parent
5cf2152359
commit
93a844f07b
|
|
@ -33,5 +33,8 @@
|
|||
"18": "assets/music/capital-neutral-01.mp3"
|
||||
},
|
||||
"_stationFrames_comment": "stationFrames = the loop that plays on a deep-space station's surface — keyed by the spacestations.png VARIANT frame (the same key its landing/takeoff clips use, data/landing.json → stationVideos), so a station hums the track matching the art it is drawn with. Empty for now (a station surface is silent) until the station tracks are authored — add one line per variant when they land.",
|
||||
"stationFrames": {}
|
||||
"stationFrames": {
|
||||
"0": "assets/music/spacestation-01.mp3",
|
||||
"1": "assets/music/spacestation-02.mp3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ const SQ = configData.sidequests; // data/sidequests.json (categories / bounty /
|
|||
const S = await import(pathToFileURL(join(root, 'js/quests/SideQuests.js')).href);
|
||||
const { SideQuestState } = S;
|
||||
const { GameScene } = await import(pathToFileURL(join(root, 'js/scenes/GameScene.js')).href);
|
||||
const { QuestState } = await import(pathToFileURL(join(root, 'js/quests/QuestState.js')).href);
|
||||
|
||||
let failures = 0;
|
||||
const check = (label, cond, extra = '') => {
|
||||
|
|
@ -356,6 +357,7 @@ section('THE FLYING — accept → legs → mark → kill → claim (the real sc
|
|||
s.planet = null;
|
||||
s.tetherField = { tethers: [{ x: 0, y: 0, radius: 12000 }, { x: -400, y: 0, radius: 9000 }, { x: 400, y: 0, radius: 9000 }] };
|
||||
s.sideQuestState = new SideQuestState();
|
||||
s.questState = new QuestState(); // no story grants — the tracker's side row stands alone
|
||||
s.sfx = [];
|
||||
s.playSfx = (n) => { s.sfx.push(n); };
|
||||
s.toasts = [];
|
||||
|
|
@ -499,9 +501,25 @@ section('THE FLYING — accept → legs → mark → kill → claim (the real sc
|
|||
at(plan.bountyAt); // stand in the lane so the field is read here
|
||||
s.updateSideQuestRoute();
|
||||
check('survivors keep it OUT of complete (the escort must stop shooting)', s.sideQuestState.isCompleted(id) === false);
|
||||
// The tracker's view of the same frames (the upper-left quest summary):
|
||||
// the kill has ALREADY checked every leg off — 100% painted — while the
|
||||
// contract is still out, and the COMPLETE mark lands a frame LATER with
|
||||
// no check change (the tracker's fingerprint must carry the bit —
|
||||
// dev/quest-tracker.test.mjs pins the repaint; this pins the ORDER).
|
||||
const snapRow = () => s.questSnapshot().quests.find((q) => q.id === id);
|
||||
const rowOut = snapRow();
|
||||
check('the summary shows every leg checked while the contract is still out',
|
||||
rowOut.checks.length > 0 && rowOut.checks.every((c) => c.done) && rowOut.complete === false && rowOut.claimed === false,
|
||||
JSON.stringify(rowOut));
|
||||
for (const e of escort) e.dead = true;
|
||||
s.combat.tickWaves(); // the last survivor drops — the run reads 'done'
|
||||
s.updateSideQuestRoute();
|
||||
const rowReady = snapRow();
|
||||
check('the COMPLETE mark lands with the legs STILL checked (no check change)',
|
||||
rowReady.complete === true && rowReady.claimed === false &&
|
||||
JSON.stringify(rowReady.checks.map((c) => c.done)) === JSON.stringify(rowOut.checks.map((c) => c.done)) &&
|
||||
rowReady.checks.every((c) => c.done),
|
||||
JSON.stringify(rowReady));
|
||||
check('with the last escort down the contract is COMPLETE', s.sideQuestState.isCompleted(id) === true, JSON.stringify(s.toasts.at(-1) ?? {}));
|
||||
const completeToast = s.toasts.filter((t) => /COMPLETE/.test(t.text)).length;
|
||||
check('the completion toasts once', completeToast === 1);
|
||||
|
|
|
|||
|
|
@ -905,11 +905,24 @@ const T0 = 100000;
|
|||
!scene.add.texts.some((t) => t.text === 'READY') &&
|
||||
rowTints() === 0 && trk._shimmers.length === 0);
|
||||
|
||||
// The quest COMPLETES (a check flips) — the same-frame repaint goes READY.
|
||||
snap.quests[0].checks[0].done = true;
|
||||
snap.quests[0].complete = true;
|
||||
snap.quests[0].progress = '1/1';
|
||||
// The BOUNTY HUNT's ORDER (the tracker's one repaint trap): a quest's
|
||||
// checks can all stand DONE before its COMPLETE mark lands — the hunt's
|
||||
// kill logs every leg at once (100% painted, still open), and the
|
||||
// field-clear completes it on a LATER frame with NO check flipping.
|
||||
// The first stage must paint plain (all checks done ≠ complete) …
|
||||
tweens.length = 0; // (the constructor's first poll is not the moment)
|
||||
snap.quests[0].checks[0].done = true;
|
||||
snap.quests[0].progress = '1/1';
|
||||
trk.update(scene.time.now);
|
||||
check('all checks done but NOT complete: the row stays plain (100%, no READY, no rim)',
|
||||
scene.add.texts.some((t) => t.text === '100%') &&
|
||||
!scene.add.texts.some((t) => t.text === 'READY') &&
|
||||
rowTints() === 0 && trk._shimmers.length === 0);
|
||||
|
||||
// … and NOW ONLY the complete bit flips (the checks already painted) —
|
||||
// the fingerprint carries the bit, so the same frame still repaints READY.
|
||||
tweens.length = 0;
|
||||
snap.quests[0].complete = true;
|
||||
trk.update(scene.time.now);
|
||||
check('READY row: the % readout flips to READY (amber, bold)',
|
||||
scene.add.texts.some((t) => t.text === 'READY' && t.color === '#ffc94d' && t.style?.fontStyle === 'bold'));
|
||||
|
|
|
|||
|
|
@ -32,7 +32,10 @@
|
|||
* scene's toast + 'discovery' chime carry it when the tracker is slid
|
||||
* off), and the row's popup gains a CLAIM REWARD button (the console's
|
||||
* claim-plate palette) that claims in place (GameScene.claimQuest — the
|
||||
* console's own path).
|
||||
* console's own path). The fingerprint carries each quest's COMPLETE bit
|
||||
* as well as its checks: a BOUNTY HUNT's mark lands on a frame AFTER its
|
||||
* last check has painted (the kill logs every leg at once, the field-
|
||||
* clear completes it) — that flip must still repaint the row READY.
|
||||
*
|
||||
* Lifecycle (driven by GameScene — see GameScene.updateHud /
|
||||
* startExpand): the tracker is VISIBLE ⇔ the dossier is FOLDED, armed by
|
||||
|
|
@ -318,7 +321,15 @@ export class QuestTrackerHud {
|
|||
a: ids,
|
||||
c: active.map((q) => {
|
||||
const checks = q.checks ?? [];
|
||||
// The quest's OWN complete bit LEADS its entry — a BOUNTY HUNT's
|
||||
// ledger mark lands AFTER its last check flips (the kill logs every
|
||||
// leg at once — 100% painted, still open — and the field-clear
|
||||
// completes it with NO check change behind it). Without the bit
|
||||
// here that frame repaints nothing and the READY state — the
|
||||
// CLAIM's door — never lands (QuestWindow._fpOf carries it; the
|
||||
// tracker must match).
|
||||
return (
|
||||
(q.complete === true ? 1 : 0) +
|
||||
checks.map((c) => (c?.done ? 1 : 0)).join('') +
|
||||
'|' +
|
||||
checks.map((c) => String(c?.detail ?? '')).join(',').slice(0, 40)
|
||||
|
|
|
|||
Loading…
Reference in New Issue