From fb08d7895b9c6ec970ad54a22ff9b056bb1bd02c Mon Sep 17 00:00:00 2001 From: type-two Date: Sat, 25 Jul 2026 22:19:14 +1000 Subject: [PATCH] [lane F] Chain the campaign: CONTINUE now loads the next segment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cards.js has been raising `ui:continue {from,to}` since Lane E round 1, with the comment "INTEGRATOR: nothing consumes ui:continue yet." Boot consumes it now, so crossing a gate and pressing CONTINUE loads C's `next` level instead of ending the run. Three levels in a folder become a campaign. Deliberately a full page load: the alternative is rebuilding world + player + combat + UI in place and re-wiring every bus listener to save ~1s on a transition that happens 4 times a run, and the reload guarantees no leaked pools or stale event pumps. Flags ride along (?dbg=1, ?seed= survive the hop). A `to` that isn't in CAMPAIGN — or is null — ends the campaign on the medal card rather than dumping the player into the stub tube. Verified: L2 -> CONTINUE -> ?lvl=L3_stomach with flags intact; unknown "L4_small_intestine" and null both correctly no-op. Co-Authored-By: Claude Opus 4.8 --- web/js/boot.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/web/js/boot.js b/web/js/boot.js index 27680ce..aeb8c32 100644 --- a/web/js/boot.js +++ b/web/js/boot.js @@ -133,6 +133,22 @@ bus.on('level:event', (ev) => { }); } }); +// CONTINUE on the medal card -> load the next segment. cards.js raises ui:continue with C's +// `next` id; consuming it here is what makes the campaign a campaign rather than three levels +// in a folder. A full page load is deliberate (ponytail: the alternative is rebuilding world + +// player + combat + UI in place and re-wiring every bus listener, to save ~1s on a transition +// that happens 4 times a run) — it also guarantees no leaked pools or stale event pumps. +// Flags ride along, so ?dbg=1 / ?seed= survive the hop. Unknown/absent `to` = campaign over: +// stay on the card rather than dumping the player into the stub tube. +bus.on('ui:continue', async (e) => { + if (!e?.to) return; + const mod = await import('./levels/index.js').catch(() => null); + if (!mod?.CAMPAIGN?.includes(e.to)) { console.info(`[boot] no level "${e.to}" yet — campaign ends here`); return; } + const q = new URLSearchParams(location.search); + q.set('lvl', e.to); + location.search = q.toString(); +}); + bus.on('player:death', () => { run.deaths++; run.respawnT = 2.0; }); // 2s of feed-drop, then back function updateRun(dt) { if (!run.done) run.t += dt;