feat(dungeon): hidden post-credit stinger 'The Vanishing' (demo-only) - #33
Merged
Conversation
Demo-only hidden post-credit cinematic for the dungeon game, triggerable in one click via a permanent-decorative-but-demo-armed rune sigil. Built as the closing flourish for the 2026-06-03 "Slay the Cert" talk. - Treatment A "The Vanishing": walk-in -> golden scroll -> white-flash teleport -> "Our hero will return..." + rune + tiny 4th-wall wink - Sticky demoMode flag (registry + localStorage + ?demo=1) distinct from per-run demoRun; gates the sigil's interactivity only, never progression - Build BOTH Hub + Boss-HUD mounts, A/B locally, prune to one (research predicts Hub: combat-HUD clutter vs. menu as canonical easter-egg home) - Zero new art; teleport sold by flash masking alpha/scale->0 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task-by-task plan for "The Vanishing": demoMode flag, PostCreditScene cinematic, demo-armed decorative sigil, dual Hub+Boss mounts with local A/B prune. Full runnable code + Vitest tests per step; choreography and A/B verified manually (no committed Playwright harness). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cene Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o-black works Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ts, typecheck - transitions.fadeToScene: guard unknown scene key (restore from black + error) instead of silently stranding on a black screen — protects the live-talk return-home path and every other caller. - PostCreditScene.leave(): removeAllEvents() so the 20s failsafe can't outlive the dismiss decision (defensive against future refactors). - demoMode: comment the read-side localStorage catch; add 4 tests covering the localStorage-throws fallback (private mode) and cross-source composition. - decorativeSigil.test: fix makeFakeText typing so strict tsc --noEmit is clean again (was a regression on the previously-green baseline). - BossFightScene: reword "A/B mount" comment (no bucketing logic exists). 126/126 tests, tsc clean, build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Integrate the post-credit stinger with main's redesigned demo system (scripted ?demo run on the real bank + boss-fight HUD bar). Resolutions: - HubScene: dropped the obsolete async beginDemoCampaign (fake demo bank; demo-questions.json was deleted on main) in favor of main's beginScriptedDemo; injected enableDemoMode there so the stinger sigil stays armed. The sigil also arms directly via the shared ?demo param. - BossFightScene: dropped the deleted demoBadge import; kept the mountDecorativeSigil mount (now beside main's new mountBossHud bar). - Merged imports (DEMO_SEED/DEMO_BOSS_ORDER + isScriptedDemo/seed-override from main; enableDemoMode + decorativeSigil from this branch). 144/144 tests, tsc --noEmit clean, vite build green.
Per design feedback, the boss-fight sigil read as stray chrome out by the audio icons. Anchor it just past the "Floor N/M · domain" run label on the left of the HUD bar instead, so it belongs to the run/domain cluster. mountBossHud now returns its run-label Text so the sigil can be placed off the label's true right edge — keeping a consistent gap for any domain-name length (MCP … Claude Code) rather than a fixed x that over/under-shoots. 144/144 tests, tsc clean, build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per design feedback, restructure the HUD run label into three pieces — floor info, the decorative rune as the separator (the old "·" dot is dropped), domain info — so the bar reads "Floor N/M ◈ domain". The rune is now genuine permanent chrome (a separator) for normal players and the demo-armed stinger trigger. mountBossHud lays out floor → reserved separator slot → domain and returns the slot centre; BossFightScene mounts the sigil there. formatRunLabel (dot-joined) becomes formatFloorLabel; tests updated. 144/144 tests, tsc clean, build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Hub auto-relaunches the scripted demo whenever ?demo is in the URL (HubScene.create), so clicking the in-fight door during a demo went to the Hub and immediately boomeranged back into the fight — the door appeared dead. exitToHub now strips the ?demo param (which also carries the seed) before fading to the Hub, so an explicit "leave" actually lands there. 144/144 tests, tsc clean, build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ting
Make the scripted ?demo a navigable journey and fix the post-credit sigil's
demo gating and placement.
Journey model (stored on the run save as journeyMode: 'normal' | 'demo'):
- Always land on the Hub in demo mode; the demo is started from the begin
button (startNewRun routes to beginScriptedDemo when ?demo, else a real run),
which also fixes "abandon demo -> New Game" starting a new demo.
- The in-fight door persists the demo run and returns to the Hub; the existing
Continue button resumes it (resumeActiveRun re-arms demoRun for demo saves).
- Journey resets (clearActiveRun) when the saved run's journeyMode disagrees
with the current mode (switch in / reload out) or after the stinger plays
(PostCreditScene sets postCreditPlayed; the Hub consumes it).
- writeSave now persists demo runs tagged demo; the Hub leak-guards them so a
demo save never surfaces as a Continue offer in normal play.
Sigil:
- Arm strictly on isScriptedDemo() (the ?demo URL) — the single source of truth
for demo mode. Removes the buggy localStorage-persisted demo flag that left
the sigil clickable in normal play; deletes the now-redundant demoMode module.
- Becomes the Floor/domain separator in the boss HUD ("Floor N/M ◈ domain"),
14px to match the labels, nudged to centre in its slot.
131 tests, tsc --noEmit clean, build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…the diamond The Hub had a separate decorative ◈ icon, but the agreed trigger is the castle-gate title itself. Remove the standalone sigil from the Hub and arm the "🏰 Gates of the Archive" title as the post-credit launcher in demo mode. Extracted armStingerTriggerIfDemo so the boss-HUD sigil and the Hub title share one arming path (interactive + pointerdown → stinger, only under ?demo, no hand-cursor). The boss-HUD ◈ separator is unchanged. 133 tests, tsc clean, build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cannot desync The scripted ?demo run keys its question picks on campaign.seed via demoRngForFloor. The seed was never persisted in RunSave and was dropped when resumeActiveRun rebuilt the campaign, so a between-boss demo resume re-picked with demoRngForFloor(undefined, floor) -> NaN collapses to seed 1 -- silently diverging from the committed cheat-sheet key on every floor past 0. The "fail loud" guard only caught a missing campaign, not a lost seed. - RunSave.campaign gains optional `seed`; isRunSave requires it for demo saves (corruption tripwire) while staying back-compat for legacy/normal saves so live players' in-progress runs still load. - New pure campaignFromSave() reconstructs a full Campaign incl. seed; resumeActiveRun routes through it instead of an inline seed-dropping literal. - writeSave persists campaign.seed. - Tests (written first, watched fail): seed round-trip, legacy back-compat, demo-without-seed rejection, campaignFromSave unit coverage, and an end-to-end resume re-pick guard pinning floor 1's golden key. Also track the thermo-nuclear-code-quality-review skill (un-ignore .claude/skills/), the review pass that surfaced this bug. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Biome check (CI lint gate) flagged organizeImports in dungeon.test.ts and formatting in runSave.test.ts on the previous commit. Pure formatting / import-sorting — no behavior change. Tests 140/140, tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A hidden, demo-only post-credit stinger for the dungeon game — the "…and one more thing" closing flourish for the 2026-06-03 Slay the Cert talk. The warlock walks in, reads the golden parchment, teleports out in a white flash, and the screen falls to black with "Our hero will return…" beneath a pulsing arcane rune + a tiny 4th-wall wink.
Launched by a camouflaged trigger: a permanent decorative rune sigil in the HUD that is inert chrome for every normal player and only becomes a one-click launch in demo mode.
Design & plan
docs/superpowers/specs/2026-05-31-post-credit-stinger-design.mddocs/superpowers/plans/2026-05-31-post-credit-stinger.mdHow it works
demoModeflag (src/game/demoMode.ts) — sticky session flag via registry +?demo=1URL param + localStorage. Deliberately separate from the per-rundemoRunflag (which is cleared on Hub return), so the trigger stays armed. Never touches bank/scoring/saves/NG+.decorativeSigil.ts— mounts the rune for all players; only attaches a click handler (no hand-cursor) whenisDemoMode. Click →PostCreditScene.PostCreditScene.ts— the cinematic. Pure tween/flash/text choreography over the existing staticheroimage (no new art); the "teleport" is a white flash maskingalpha/scale→0. Always returns to the Hub on input (after the text lands) or via a 20s failsafe.enableDemoModecalled on demo-campaign launch.Pre-arm for the talk
Open
localhost:5173/?demo=1and the sigil is live from the first screen — no campaign needed.Testing
tsc --noEmitclean,vite buildsucceeds.Reviewed
Ran the pr-review-toolkit (code / tests / silent-failures / comments). No Critical issues. Applied fixes in
19aa1cf: hardenedfadeToSceneagainst unknown-scene-key black-screens, added demoMode resilience tests, fixed a strict-typecheck regression,removeAllEvents()on leave, comment cleanups.For the reviewer to do locally (deferred Task 5)
cd public/dungeon; npm run dev→ openhttp://localhost:5173/?demo=1.?demo=1; full cinematic plays + returns to Hub; legible at projector res (~1600×900).🤖 Generated with Claude Code