v0.1 Release Candidate — punch list
v0.1 “Dark Cosmos” Release Candidate — punch list
Section titled “v0.1 “Dark Cosmos” Release Candidate — punch list”CLOSED 2026-09-28. v0.1 shipped as store version 1.0 (build 37, in App Store review). The live queue is now
docs/RELEASE-1.0.1.md; open items below are triaged into it.
THE single source of truth for what stands between main and the v0.1 RC.
Consolidated 2026-07-14 from: CLAUDE.md ⚠️ items, NEXT_TASKS.md, docs/overnight-review-2026-07-08.md
(unactioned residue), docs/overnight-review-2026-07-13.md (open triage), and the roadmap memory.
Process spec: docs/superpowers/specs/2026-07-14-v0.1-release-candidate-process-design.md.
Standing rules until the RC ships:
- Feature freeze on
main— bug fixes, QA tooling, docs, and release tasks only. - Anything anyone finds goes on this list FIRST (add a line under the right section) — no
ad-hoc fixing mid-other-work. Mark items
✅ DONE (commit)in place. - Chris + Toby both work off this list; deploys stay serialized (multiagent-deploy memory).
0 · The v0.1 goal (Chris, 2026-09-23) — the finish line every session works toward
Section titled “0 · The v0.1 goal (Chris, 2026-09-23) — the finish line every session works toward”Goal: v0.1 uploaded to TestFlight (Friends & Family group) from a build that passes every §1 gate, carrying up to 3 approved graphics upgrades. “Improve everything” has no end; this does.
Done means ALL of:
- Every P0 below is fixed; every P1 is fixed or deferred by Chris by name.
- Gates green: GUT +
check-test-count.sh; both determinism baselines (any re-pin carries a written reason); soak 0 invariant failures;story_trace --mode=tutorialcompletes;ui-sweeprepaired so it can actually certify (it fails on unmodifiedmaintoday). - Perf:
ADAPTIVE_QUALITYon, with the costly full-screen effects actually wired onto the ladder, and Apple TV (AppleTV11,1) p50 ≥ 45 fps at 51-150 enemies, floor ≥ 37 fps at any count (Chris, 2026-09-23). Baseline from live telemetry: BUILD 48 (June) 48.4 fps / 14.7 ms render at 51-150; BUILDs 181-184 38.1 fps / 26.9 ms (138 samples) — a ~10 fps regression. Measured 2026-09-23 (PERF_BENCH, marker build 9001, Living Room ATV, 50 Hz display so 50 = cap): 51-150 enemies: tier 0 36.7 fps / 26.7 ms render -> tier 1 (bloom + HDR target + point lights off) 47.8 fps / 20.6 ms -> tier 2 50 fps (capped). 11-50: 38.0 -> 48.3 -> 50. The entire regression is tier 0’s bloom/HDR/lights; one ladder step clears the 45 fps target. Decision owed (Chris): ship bloom off on ATV (re-enable ADAPTIVE_QUALITY and it settles at L1), or make bloom cheaper. The Nova look (glow baked into the unit sprites) doesn’t depend on bloom. - Graphics: max 4 freeze exceptions (3 set by Chris 2026-09-23; raised to 4 on 2026-09-24 for the
liquid-glass UI redesign,
docs/superpowers/specs/2026-09-24-liquid-glass-ui-redesign-design.md, Chris: “do this as a 4th because I think it is a big improvement”). Each ships behind a toggle with before/after captures; Chris picks by eye; none may push ATV below the perf target. - Every player-facing claim (store copy, codex, tutorial, site) matches the sim.
- Last gate, human-only: the §3 playtest script on Mac + Apple TV + iPhone (Chris/Toby).
Guardrails: changes land sequentially (never parallel writes to one tree), each with its
gate, git push after every merge. Claude stops and asks before: any further freeze exception,
any balance-number change, a determinism re-pin, or any outward-facing step other than the
TestFlight upload itself (Claude is authorized to upload to TestFlight — Chris, 2026-09-23;
App Store review submission and site/web-demo deploys still need a yes on the day).
Decisions 2026-09-23 (Chris): perf target 45 fps; fix the Fire/Lightning status bug for 0.1; give the Blaster + Rift Bolt fire sounds.
Graphics exception candidates (built 2026-09-23, awaiting Chris’s pick — max 3):
- Enemy hit-flash —
main.ENEMY_HIT_FLASH(render/hit_flash_tracker.gd). Render-only, ~0 GPU cost. - Damage-number rolling totals —
DamageNumbers.MERGE. Replaces “6 6 66 6” smears with one ticking total per enemy. Render-only, fewer live Labels (cheaper, not dearer). - (held) Bloom-independent neon — now folded into the Nova Drift look goal
(
docs/superpowers/specs/2026-09-23-nova-drift-look-goal.md, set by Chris 2026-09-23). Ships in 0.1 only if Chris picks it before the other gates close; never delays the release. Both built ones default ON so they can be judged on-device; each is a one-line flip to reject. - Liquid-glass UI redesign (approved as the 4th exception, Chris 2026-09-24) —
VisualLook.glass_ui. Spec:docs/superpowers/specs/2026-09-24-liquid-glass-ui-redesign-design.md. Includes the dud crystal-card filter (closes the §4 “no effect” card item) and one deliberate survival re-pin if needed. Plan 1 (core) built 2026-09-24 on branchliquid-glass-ui: GlassPanel + frost/clear shaders (14c16d7); dud crystal cards re-rolled (0424234,b3726b7; no re-pin needed, both determinism baselines unchanged); StatsSheet (cbc876b,ca7c9e6,151364a); level-up bottom strip + Y ship-stats sheet, old panel kept asLegacyLevelUpPanel(f4ab612,14476b4,be0dbad); glass pause menu with read-only ship stats (9fea67e,32d8bc5); Debug Settings “Liquid-glass UI” toggle + docs (Task 8). Toggle: pause menu swaps on the next pause, level-up on the next run (main._new_run()rebuilds it). On-device frame check: pending (after merge). Still owed: Chris’s before/after pick on device; Plan 2 (the other screens + HUD docks) not started.
Gameplay/UX exceptions, 2026-09-26 (Chris asked for each directly; branch balance-pass):
- Balance pass: bosses harder (Warden/FunZo/Graviton/Eye faster cadences, Eye gaze volley, boss HP
scales with the rotation, adds start 4s in); Charger toned down; most normal enemies fire weak
“chip” shots; Leech heals on damage dealt instead of on kill; elites prowl/strafe/dive and carry
two of six player-style attacks (
sim/elite_kit.gd: aether/fire burn/blood/void shove/cold slow/ firebomb). Skilled-AI soak (30 runs, seed0 500): mean survival 91s -> 72s, charger contact deaths 16 -> 1, bullets now the main killer. That AI does not dodge bullets, so this overstates it for humans. Determinism baselines unchanged. Owed: human playtest of elites, bosses, chip fire. - Enemy variation + elite homing missiles (2026-09-26, Chris): elites fire more (Blaster 3->4 shots
at 330->380 px/s, Nova 12->16, Scatter 5->6) and gain a 7th kit weapon, a pair of homing missiles
(
EliteKit.ATK_HOMING: 250 px/s, slower than the pilot; 2.2 rad/s turn, a ~115px circle, so a sidestep beats them; 6s life; drawn brighter). Quick chippers shoot faster (EnemyChipFire. SPEED_BY_TYPE: rusher x1.45, ghost x1.35, accumulator x1.3, spider x1.25, halo chain x1.2; brute x0.8). Review caught the first cut steering missiles AWAY from the pilot (reversedangle_differenceargs) and firing 4 stacked missiles, not 2; both fixed, test-pinned. Skilled-AI soak (20 runs, cobalt, seed0 500): mean survival 72s -> 67s, 0 invariant failures. Determinism baselines unchanged (all of it is gated past the 10s window). Owed: human feel check. - Harder, busier start + two new elite weapons (2026-09-26, Chris, after build 28): enemy shots
faster (chip 210->235 base with +/-10% per-shot jitter, still below the ship’s 260; shooter
300->330; elite kit +8-10%); chip fire starts at 10.5s (was 12) at 4.5 shots/s (was 3), wider aim
wobble, every 6th chip a 3-pellet fan; elites from 22s (was 30). New elite kit weapons (pool now
9): WEB, a faint 0.6s tell line then a thin tether that slows you to 60% for 2.2s, snapped by a
warp, by range (560px) or by killing the elite; WARP, a 0.8s violet marker beside you, the elite
blinks onto it, and a pilot still within 190px has the warp scrambled for 3s (rides dash_cd, so
every warp-readiness display shows it).
render/elite_fx_renderer.gddraws both. Skilled-AI soak (20 runs, cobalt, seed0 500): mean survival 67s -> 57s, 0 invariant failures; that AI doesn’t dodge bullets. Determinism baselines unchanged. Owed: human feel check. - Build 31 (BUILD 199, 2026-09-27): iOS + tvOS uploaded, in both beta groups, IN_BETA_TESTING. Carries the 2026-09-27 feedback pass below up to 0995caf, including 12 more weapons behind Arsenal unlocks (only Blaster, Orbit Shards, Nova free; prices 100-300 are first-pass).
- TestFlight feedback pass (2026-09-27, Chris’s 16 screenshot notes, all from his own devices; he
asked to act on them). Landed on main: #06 empty glass level-up cards (the glass shaders dropped
modulate, so a stalled entry left cards invisible and untappable; shaders fixed + an 0.8s entry failsafe, 2964eb7; D1 in-game report #3 on the same iPad a minute earlier is the same event, the session ran on at 120 fps, not a crash); #16 Aurora band reads as sky, not a hazard (2964eb7); #09/#10 tutorial level-ups offer only Plating/Hull Plating/Thrusters/Magnet Field, no crystal grants, no hidden room (and its free Orbit), weapon + drone docks hidden (53417d3); #11 one-time Survival intro from the team,MetaState.seen_survival_intro(53417d3); #15 Rift Bolt fire sound throttled to ~8/s with pitch jitter and a -6dB sustained-fire floor (7c4d984); #12 codex previews draw the real enemy through ArchetypeRenderer (11f3552); #13 gold pulse on the menu SHOP row and death-screen Shop button whenever something is affordable, plus a one-time first shop tip (thenseen_shop_tip, nowshop_tip_views), and #14 a YOUR WEAPONS column on the death screen (top 4 by damage share + dps, from a write-onlySim.damage_by_weapon, not checksummed) (b1791a7). Open on #13/#14: the tip recommends Vitality and Bulwark (Chris to check the wording); the pause-menu shop has no glow; evolved weapons show their base name. #07/#08 Ship stats in three titled sections (Ship/Crystals/Weapons) with plain-English notes and armor shown as % damage cut, a darkening veil, objective panel hidden while it is open, lesson 4 pulses the STATS button gold. Merged (e186870, 5fb468d; after build 31): Drone Bay respects the drone cooldown, Drone Bay reachable from the shop and pause menu, shop advice for the first 3 visits (survival, next weapon, next drone upgrade;MetaState.shop_tip_viewsreplacedseen_shop_tip), evolved weapons shown as “Evolved (Base)”. #17 Arsenal unlocks (0995caf, in build 31). #18 every shop screen + Drone Bay on GlassPanel (18fce45, merged). Owed: ATV frame cost, re-bless ui-sweep shop.png + reset_shop_dialog.png. The Warden is harder with the tutorial upgrade list cut; story_trace still passes. Owed: on-device check of all of it; Chris hears the Rift Bolt throttle. Also 2026-09-27, Chris: frigates now drawn at 2/3 of the cruiser’s size (f856b2e, render-only). Also 2026-09-27, Chris (freeze exceptions, both his calls):- Hangar screen: pre-run drone row was hard-coded to 1 slot, so the cruiser showed 1 of its 2; now the hull’s own slots + the saved loadout. Hull drawn at its in-flight size ratio, class shown before the bonus (“FRIGATE · +20% damage”).
- Base speed cut: frigates 260 -> 230, cruiser 210 -> 185, so Cobalt’s +15% (~265) stands out. Owed: a play feel check; tank_missile (200) and fleet_kite (195) are now faster than the cruiser.
- Level-up with Ship stats open: tapping a card previews its stat change; tapping it again chooses.
- Hull / Armor naming (Chris picked): Plating -> Reinforced Hull (+25 Hull), Hull Plating -> Armor Plating, shop Vitality -> Hull Frame, Bulwark -> Armor Weave; stat label “max HP” -> “Hull”. Ids unchanged, so saves and telemetry are unaffected.
- Nova lesson (Chris): new tutorial-only Mite (3 HP, no shots) arrives in bursts of 10 up to 30 alive; Nova ~8 kills/s vs Blaster ~2/s, so the swarm piles up without Nova. 50 Nova kills.
- Nova burst restyled as fiery plasma, A/B switch
VisualLook.plasma_nova(Debug Settings “Plasma Nova (A/B)”), default on. Owed: Apple TV frame cost, especially evolved Supernova. - Shop rebuilt (Chris: “make it much easier to use”): left tab rail (Pilot / Drones / Arsenal / Utility / Ships) with affordable-count badges, card grid (2/3/4 columns by width), Drones as one tab with a section per class, DRONE BAY + RESET in the header, no BACK card. Rough edges fixed after: badges count drone sections (max 6, others cap 9+), sparse tabs centred + Ships note, toast hides for dialog/bay, scroll edge fade (the iPad “dots” were clipped text descenders), shop.png + reset_shop_dialog.png re-blessed. Owed: device pass; ui-sweep still over threshold on 7 untouched screens (dimension_choice, hud_in_run, level_up(_stats), pause(_stats), ship_config). TestFlight notes 07:25-07:42Z, iPad, arrived after the pass above; do after #18 merges):
- #19 death window too wide; the green glow behind REDEPLOY is clipped. Card capped at 820 and fitted to the safe area (iPad’s 1.3x UI zoom left it 985 wide); glass glow now fades to zero inside its own pad on EVERY GlassPanel (5aa50f1). Owed: look at the other glass screens on device.
- #20 Fuchsia’s pickup-radius bonus is dead. Chris: hide it for now; it returns with a planned feature where some enemies drop hard-to-collect materials, and Fuchsia’s bonus will be about those. Hidden from ShipBonuses.ORDER (data kept in TABLE); saves on Fuchsia fall back to Cobalt.
- #21 was the shop’s Arsenal cards, not level-up: 3-line descriptions + font fitting, no copy changed; a new test measures all 66 shop entries and all 191 level-up sentences (bd419cf). Same day, Chris: KEEP the Chronowell slow (a402c58) and Railgun rate (8ab0451) level-up cards; approved the runs.pilots/runs.level2 telemetry columns (live, e49cdf3). Build-33 TestFlight notes (iPad, 10:49-10:55Z), freeze exceptions:
- “Two upgrades at this point” (tutorial LEVEL 6): levels earned in lessons 5-7 were banked and
all opened back to back once the Nova lesson freed level-ups (the Warden kill chained three
too). Then Chris: “only one upgrade is offered per section”. Now each lesson grants at most ONE
level-up pick in total (
TutorialLessons.pick_budget/grant_pick, asked bySim._process_levelups): LEVEL UP, SHIP STATS, CRYSTALS (its crystal card is the one), NOVA and THE WARDEN get one each, every other lesson none, and never two waiting. An extra level still raises the level (lesson “level” steps complete), its pick is dropped. Before: the Nova Mites gave 14 picks and the Warden lesson 2; a full StoryAutoPlayer run is now 1/1/1/1/1. - Power-ups off in all modes for now (Chris):
Sim.POWERUPS_ENABLED := false. Skirmishers still roll the drop kind fromrng(spawn stream unchanged), nothing spawns; the system is kept. - “No dmg upgrades in the tutorial”: Overclock (fire-rate) is back on
TUTORIAL_MODS, and each owned weapon’s own damage card (wm:blaster:power,wm:nova:power) is offered. The global “damage” mod is never offered anywhere (it only reaches the Rift Bolt and drones), so not used. Reverses part of this morning’s #09 (Overclock out). Owed: the Warden may now be too easy. - “Hide the weapon and drone huds” (first Survival run after the tutorial): the hand-off no longer forces both docks on; that run starts hidden like any other (Y/V reveals). Control is still handed straight to the player, no autopilot.
- “Only offer 1 card with crystals”: at most one crystal card per level-up panel
(
CRYSTAL_SECOND_GRANT_CHANCE0.0, itsupgrade_rngdraw kept; the short-pool fill stops at one too). Reverses the 2026-07 “sometimes more than one at a time” call. - “The player can’t actually be killed during the tutorial” (Chris): hits still land in full
(hull drops, flash, sound, camera punch, damage gate, i-frames) but in the tutorial the hull
never goes below 1 (
TutorialLessons.MIN_HULL), so no game over. One floor inSim._hurt_player, which every pilot HP loss goes through (contact, enemy shots, burn, leech, lancer, bomber, brute slam, orbiter, hazards, every boss attack).sim.tutorialis null in Survival, Story and both determinism baselines, which are unchanged.
- Build 30 playtest fixes (2026-09-26, Chris’s TestFlight feedback): tutorial dialogue skips on a
tap anywhere and holds each line ~1s longer (
6535e30); the Rift Bolt lesson holds the auto weapons and its swarmers take 3 bolts (8862550); glass blurs more and sits darker (e2eed00). - Hitch telemetry (2026-09-26, QA tooling, ships in build 29): every render frame >= 60ms is
sent with its context (CPU ms, GPU pipelines compiled, screen state, the fx events in the second
before). New D1
hitchestable + dashboard section;d11fff5. - Survival Warden, charger lane, arena edge, dimension cards (2026-09-26, Chris, after build 28):
the Survival Warden (tutorial/story Warden unchanged, gated on
sim.story == null) moves 56->88, lunges 540 px/s for 0.38s at the start of an approach when the pilot is 360px+ away, contact 26->34, swing 44->56, barrage 28->36, missiles 7->9 at 18 dmg. Two new attacks: SWARM (10 drones, 14 enraged, 560 px/s, 7.5 rad/s turn, 3 dmg + a jolt slow and shove; warp i-frames are the counter) and SUMMON (a web elite + a warp elite beside it viaEliteKit.forced, max 4 alive). Charger telegraph redrawn as a lane with streaming chevrons and a closing strike reticle. Newrender/arena_border.gd: HDR glowing edge + drifting fog of war outside (strips only, no interior cost). Dimension choice shows all three rifts as glass cards with an authored danger tier (AreaDefs.DANGER, first-pass values), threat lines read from the real buffs, and the reward mult. DoT (burn, zones, leech, beams) flashes but no longer shakes the camera. Dash upgrade renamed Thrusters -> Warp (the move-speed card keeps “Thrusters”). Tank archetypes buffed: Brute HP 60->140, Summoner 13->32. - Fork (
split) fires on every hit, pierce hits included (was final hit only, so Penetrator delayed it). Level-up card tags padded and letter-spaced for legibility. - Anonymous in-game feedback: pause menu “Send feedback” -> telemetry worker
POST /feedback-> D1feedbacktable. Owed: D1 migration + worker deploy; App Privacy label may need “Other User Content”. (Migration + deploy done 2026-09-26, live in build 26.) - Tutorial opens and closes with a note from the team asking for feedback (
dev_welcome/dev_thanks). - From Chris’s own build-26 feedback: the touch top-right HUD button is now a feedback button
(
TouchControls.SYS_SLOT_IS_FEEDBACK; flip to restore the tactical-HUD toggle), and IDEA hides the quick-answer chips and gives a 170px text box where a keyboard exists (tvOS keeps the chips). - TestFlight feedback on build 26 (Chris): the pause menu’s stats sheet now closes on a tap anywhere
(its own panels were eating the tap before the dim saw it); a thumb held through a level-up or
pause steers again on its next drag (it used to need lifting); a ready DRONE button glows after
a hit and flashes at <=30% hull (
TouchControls.drone_nudge, Debug Settings “Touch: drone button nudge”, on by default, touch only). - TestFlight feedback on build 27 (Chris): the death screen is now one frosted glass card (stat tiles, killed-by + what-hurt-you beside counter-next-time, all three actions on screen at once instead of the one-card carousel); the drones panel is a clear glass card, top-left on touch (inside the safe area), bottom-right elsewhere. Owed: see it on a real iPhone.
- Overnight whole-game audit loop, 2026-09-26/27 (Chris: “report + low-risk fixes”, then “lets do
all of them” for the medium/high findings it reported): 28 low-risk fixes by the loop
(
docs/research/2026-09-26-whole-game-audit-loop.md), then the rest by hand: co-op dimension hazards hit + eject every pilot (01ded62); Lancer beam hits every pilot in line, melee weapons read the firing pilot’s element (55b0d1a); co-op threat radar centres on the camera, weapon SFX attenuation counts every pilot, dash telemetrychainset (fb63be2); shop purchases save immediately, Railgun “rate” mod now offered (8ab0451). Two new level-up cards (Chronowell slowa402c58, Railgun rate8ab0451), Chris to keep or revert. Determinism baselines unchanged. Owed: ui-sweep + 2-controller co-op check (P2 HUD/dock, radar, hazards) + iPhone safe-area check before the next device build. Not done: runspilots/level2columns (the game sends them, the worker ignores them until the D1 migration + worker insert are approved); touch-only iOS co-op affordance (design call); redundantSpatialHash.rebuild()(sim time is not the bottleneck). - Build map:
Sim_Const.BUILD194 = store builds 24-26 (indistinguishable in telemetry/feedback); 195 = store build 27; 196 = store build 28; 197 = store build 29; 198 = store build 30. BumpSim_Const.BUILDwith every store upload from here on.
Source audit: 8-dimension read-only workflow 2026-09-23 (findings folded into §2/§4 below,
tagged (audit 09-23); the release-readiness dimension failed and was re-run separately).
Confirmed OUT of 0.1 (Chris, 2026-07-14): networked MP (stays in its worktree); the dimension-choice popup’s 4 open design questions (current behaviour ships); bespoke powers for the ~12 generic-elite enemy types; normal-mapped 2D hull lighting.
1 · Release gates (Phase 4 — every one must pass)
Section titled “1 · Release gates (Phase 4 — every one must pass)”- Re-enable
ADAPTIVE_QUALITY := true(main.gd:137, off since 2026-06-28) and verify tier-shedding actually engages on Apple TV under load via telemetry. (CLAUDE.md; Chris’s call) - Flag audit ✅ AUDITED 2026-07-15 (grep-verified on main @ BUILD 166):
V01_CRYSTALS_ONLY=true✓ ·V01_LOCK_AREAS=false(Dimensions live) ✓ ·V01_LOCK_COOP=false(co-op on) ✓ ·SHIP3D_MEASURE=false✓ ·MEASURE_FULL_QUALITY=false✓. ONE flag deliberately not yet in release state:ADAPTIVE_QUALITY=false— its re-enable is the separate §1 gate above (flip + on-device verify rides the next build after the §3 playtest). - Full GUT suite +
scripts/check-test-count.sh+ determinism baselines green (incl. the new 2-pilot baseline once QA Task 4 lands). - Soak sweep clean (no invariant failures / unexplained telemetry anomalies) —
scripts/soak.sh. - UI screenshot sweep clean vs blessed baselines —
scripts/ui-sweep.sh. (Gate REPAIRED 2026-09-23 — 9/9 pass and captures are reproducible; re-run at RC time.) - Structured playtest script (§3) passed on Mac + Apple TV + iPhone.
-
bh-appstore-release→ TestFlight → Friends & Family group; several days’ soak; only showstoppers block. (Build 19 isWAITING_FOR_REVIEW— if rejected, read memorybullet-heaven-tvos-metal-rendererbefore assuming the Metal fix was wrong.) - App Store privacy label covers remote config’s
install_id— a random per-install identifier sent with every/configrequest (docs/architecture/remote-config.md§ “Open items”). Check whether the label needs “Identifiers → Device ID, Analytics, not linked” before the next submission.
2 · RC-blockers
Section titled “2 · RC-blockers”-
tvOS 1.0 build 22 REJECTED 2026-09-26 (2.1a crash on launch + 2.3.8 name mismatch) — build 23 uploaded for both platforms (this commit). Crash:
-[AGXA8CommandQueue addResidencySet:]on the review Apple TV HD (AppleTV5,3, A8), same stack as build 18. The build-19 fork fix (godot-tvos4e0000a) was never inexport_templates/4.7.dev/tvos.zip(template dated 2026-06-24; every archive since linked the pre-fix lib); template repackaged with the fixedbin/libgodot.tvos.template_release.arm64.a(old zip kept astvos.zip.2026-06-24-prefix.bak), and the gate verified in build 23’s disassembly. Even fixed, Godot’s Metal renderer refuses pre-Apple4 GPUs (A8, A10X), somin_tvos_versionis now 27.0, which drops exactly the Apple TV HD and first-gen 4K. iOS was already A12+ viaiphone-ipad-minimum-performance-a12, so its minimum stays 15.0. Name:platform/tvos/project.godotconfig/nameis now “Dark Cosmos” (iOS/tvOSuser://is the Documents dir, name-independent). ⚠️ Owed: Living Room ATV is on tvOS 26.6 and must update to 27 before any dev install. -
iOS touch UI / UX pass (Chris-directed, 2026-09-26, branch
ios-touch-ui) — Chris asked, while iOS 23 was pulled from review, for “the look, the position and the function” of the touch buttons, the overlapping shop cards, the ship + ship-stats screens, and a ~0.5 s pause early in iOS runs. Research:docs/research/2026-09-26-mobile-touch-controls-survey.md.- Touch controls (
ui/touch_controls.gd): sized in points viaPlatform.units_per_pt, so iPhone and iPad match physically (iPad was ~1.75x larger); inside the safe area; pause/HUD 44 pt targets; WARP/DRONE glass discs with drawn icons, charge arcs and a ready pulse; idle fade; haptics. Slide the aim thumb onto WARP/DRONE to fire them (tap still works), behind Debug Settings “Touch: slide to warp/drone” (default OFF since build 25, Chris’s call 2026-09-26) + “Touch: haptics”. Controls hide under every modal (main._touch_controls_blocked), not under dialogue. - Shop cards: three rows that can’t collide (name / 2-line description / level pips + a price pill sized to its text); BACK inside the safe area. Root cause of the overlap: a fixed-width 104px price label, plus Label size assigned before autowrap (Godot widens it).
- Ship screen: ring layout (radius 260 > half the phone canvas; top slot sat on the title) replaced by ship left / weapons grid + drones right, sized to the viewport; opaque backdrop (the HDR start menu read through 0.88); tappable ‹ › paging + EQUIPPED / TAP TO EQUIP status; pre-run starter shows the Blaster (it still said Blade).
- Stats sheet: level-up mode stays above the card strip on a 554-tall phone canvas;
pause mode centres; safe-area aware; darker glass (
GlassPanel.shade) so the HUD banner doesn’t read through. Pause menu scales to fit the phone’s safe height. - Hitch: iOS presets now set
shader_baker/enabled=true(Metal shaders pre-compiled into the pack; 202 steps). It only runs from a WINDOWED export with--rendering-method mobile, never--headless(bh-deploy / bh-appstore-release updated). tvOS presets not yet (pending the iOS check). Not proven to be the cause of the pause until played on device. - Gates: GUT 275/275 scripts, 0 failing; ui-sweep re-blessed for ship_config, pause_stats,
level_up_stats only (eyeballed at 1920x1080); device captures via new
tools/ui_capture/. - ⚠️ Owed: on-device feel check (iPhone + iPad): slide-to-warp vs tap, sizes, and whether the early pause is gone.
- Touch controls (
-
Tutorial onboarding (Ember Reach) — ✅ DONE (commits
11c0797..99f9997): deliberate feature-freeze exception, approved by Chris 2026-07-21: re-enables the built-but-gated Tutorial mode as a mandatory-by-default first-run flow (fresh saves launch straight into it, no menu stop), capped to the first act (Ember Reach) only — ends with an auto-launch into Survival on Ember Warden defeat, not the full 4-domain campaign. Teaches movement, auto vs. manual weapon, dash/warp (new dialogue — the only genuinely missing lesson), and the decoy (already taught). Also re-instantiatesui/control_hint.gd’sControlHintfor Tutorial-mode runs only — Chris deliberately removed it everywhere on 2026-07-05 (commitc7ef9d9), so Survival’s removal stays as-is; this is not a bug fix, just extending the existing hint to the new Tutorial entry point. Design:docs/superpowers/specs/2026-07-21-tutorial-onboarding-design.md. Full verification sweep (Task 8, 2026-07-22): GUT suite 244/244 scripts, 2279 tests, 0 failing; test-count guard OK; both determinism baselines unchanged (survival4217109746/3312602286, co-op370166760/2437492589); headless boot smoke clean (noSCRIPT ERROR). Deployed to Apple TV as part of BUILD 175 (2026-07-22, see the deploy entry below) — reachable on-device now, not just merged. ⚠️ Owed: the manual playtest (fresh-save → Tutorial → Echo → ember_intro warp/dash line → Ember Warden → portal handoff → relaunch shows 2-entry picker) — not automatable, needs a human, now doable on the actual TV. -
Tutorial stage-gating (fully progressive, gate-until-taught redesign) — ✅ DONE (commits
b08c978..13f0dfd): the 2026-07-21 Tutorial onboarding above taught its lessons but didn’t actually WITHHOLD anything — a player could dash, fire the manual weapon, or open the level-up panel before the matching dialogue beat ever fired. This redesign makes every taught system inert until its own beat: no movement HUD untilmove_intro; Blaster granted (not pre-armed) atember_intro; manual (Rift Bolt) weapon gated behindaim_intro; dash/warp gated behindpulse_learn; HP bar and threat radar narration now fire reactively the moment each would first have something to show (hp_intro/radar_intro,sim/story_director.gd’s new_check_tutorial_teaching()); level-up panel stays closed untillevelup_intro; the decoy meter stays frozen at 0 untilecho_decoy;ember_e1’s arena spawns one enemy at a time; local co-op join is blocked for the duration; the post-Warden Dimension Choice popup is suppressed in story runs (auto-handoff to Survival playstutorial_completeinstead). Gate primitive:StoryState.tutorial_locked(beat_id). Design:docs/superpowers/specs/2026-08-10-tutorial- stage-gating-design.md; plan:docs/superpowers/plans/2026-08-10-tutorial-stage-gating.md. 12 implementation tasks, every one task-reviewed clean (fix rounds only on Tasks 8/9/12, all closed). Full verification sweep (Task 13, 2026-08-10): GUT suite 246/246 scripts, 2330 tests, 2326 passing, 4 pre-existing risky/pending (unrelated null-safety tests), 0 failures, exit 0; test-count guard OK; both determinism baselines unchanged (survival4217109746/3312602286, co-op370166760/2437492589); headless boot smoke clean (noSCRIPT ERROR). Two research-closed pre-implementation-checklist items reconfirmed:ui/ship_config_panel.gdstill never renders the Aim weapon (zeroaim/Aimmatches, unaffected by this plan), andteach_decoy’s boss-fight dialogue reads as situational reinforcement (new heal-while-still info), not a duplicate ofecho_decoy’s fuller intro — no content tweak needed. ⚠️ Story Board capture smoke test (Step 5) could NOT be run end-to-end: no recorded replay exists yet for the redesigned Tutorial (generating one needs a real human playthrough withRECORD_REPLAYon — see Step 6/the Story Board tool’s own record stage) —capture.gditself was confirmed to boot correctly under real windowed Metal rendering and fail cleanly (not hang) on the missing-replay precondition. Per Chris’s 2026-08-10 decision, the NEXT real device deploy of this feature is what Eve should record + capture for Story Board — her in-flight BUILD 185 capture targeted the pre-redesign Tutorial and is superseded, not merged. ⚠️ NOT yet deployed to any device — merged tomainonly, no BUILD bump this session. Unlike the 2026-07-21 Tutorial onboarding entry above (deployed to Apple TV as BUILD 175), this redesign is still only reachable in-editor/via a local export; the next real device build is also what Eve should use for her Story Board re-capture, per the note just above. ⚠️ Owed: the manual Mac playtest (Step 6, 8-point script in the plan/task-13 brief) — not automatable, needs a human. -
Tutorial friendliness pass (adversarial audit + fixes) — ✅ DONE 2026-08-19 (commit
eb7a013+ follow-up). Chris asked for an adversarial workflow over the tutorial against four goals: one thing at a time, unlock only that thing, guidance text legible and properly spaced, and good cadence. 14-agent read-only audit (1 mapper → 6 lenses → 6 refuters → synthesis; 57 raw findings, 7 refuted, 50 survived) written up indocs/research/2026-08-19-tutorial-adversarial-audit.md, then fixed. The headline was not a pacing problem — the tutorial’s central lesson was impossible.spawn_story_enemypassed 11 args toEnemyPool.addand stopped atbehavior, sobiomass_vdefaulted to 0; the XP economy pays on biomass (_xp_worth), never on thexp_valcolumn it did pass (written, read nowhere). Every scripted enemy was worth 0 XP, solevelup_introandcrystal_introwere unreachable and the Warden was fought with a stock loadout. Proven by an end-to-end headless A/B: unmodifiedmainfinishes 272 kills at level 1, 0 level-ups; after the fix, 7 level-ups with the first at 13.3s. Passing biomass alone was not enough (Ember’s whole roster is 50 biomass vs a startingxp_to_nextof 100) →Sim.TUTORIAL_XP_TO_NEXT(22), plusspawn_story_bossnow scales the Warden’s XP to its actual strength instead of paying survival’s flatBOSS_XP_BIOMASS400 (which alone banked ~15 chained level-up panels at the climax). Also: 3 ungated second entry points closed (Drone Baydeploy_now, the damage-driven decoy recharge, shop-unlocked weapons at tick 0);pulse_learnsplit so the warp gets its own beat and_update_dashgates on the beat that teaches it;aim_intro/secret_hintmade reactive so the first arena no longer opens with 16s of unbroken multi-mechanic text; adaptive dialogue hold (the flat 3.2s truncated any line >134 chars mid-reveal — two shipped lines did exactly that, both gate-releasing, and beats are fire-once); inter-beat gaps, two-stage skip, touch skip (the box was literally unskippable on iOS), gradient dim, viewport-relative panel that no longer sits on the touch buttons it describes; per-ability touch gating (WARP/DRONE/aim pad were on screen and dead from tick 1 on iOS, defeating the whole 2026-08-10 staged design); 9 false dialogue claims corrected against the sim; platform-resolved control-name tokens; tutorial music;move_nudgeidle backstop; softer Survival hand-off. Verification: suite 247/247 scripts, 2347 passing, 0 failing (same 4 pre-existing risky/pending); test-count guard OK; BOTH determinism baselines unchanged (no re-pin — story spawns are not in the survival/co-op baselines); soak 6 runs 0 invariant failures 0 anomalies; headless boot smoke clean; newtests/test_tutorial_progression.gd(12 tests) drives the REAL call paths and was mutation-checked (reintroducing the biomass bug fails exactly the two XP assertions). ⚠️ Owed: a human playtest — the automated trace covers beat order/gating/XP but cannot judge feel, and the read-freeze means wall-clock cadence is only verifiable by playing it. Not deployed to any device (no BUILD bump). ⚠️ Theui-sweepgate could not certify this: two of its baselines already fail on unmodifiedmain, one of them non-deterministically — logged inNEXT_TASKS.md. -
Tutorial lesson track (full rebuild) — ✅ DONE 2026-09-24 (branch
tutorial-lessons, commits96d8f14..b5ebe9a+ a final docs commit; 12-task subagent-driven-development, every task reviewed clean, fix rounds on Tasks 9, 10 and 11 all closed). Replaces the beat-gated Tutorial the two entries above describe with ten lessons — one unlock, one checked objective each, in a fixed order (table + full architecture:docs/architecture/meta-and-story.md§ “Tutorial lesson track”; design/plan:docs/superpowers/specs|plans/2026-09-24-tutorial-lesson- track*). Gates open on the intro beat being read, not queued — closes the 2026-08-19 audit’s “gates open on queue” hole for good, structurally (StoryState.promote_read/read_dialogue), not by convention. Ships a clear-glass objective panel (top-centre, LESSON N OF 10, live glyph for the last-used device), drawn speaker emblems + role + side (allies left, enemies right — Ash and The Null introduce themselves on the way to the Warden), a starter-loadout tutorial run that never touches the real save, and lesson 4’s sheet-first gate (blocks a pick until Ship stats has been opened once, with a visible nudge on a refused attempt — this plan’s own Task 9 review caught the first version shipping that gate silently, with no nudge at all). The Pulse gift and its encounter heals are REMOVED from the tutorial (Chris’s call) — the Warden fight is now measurably harder without them (see the playtest note below). Also found + fixed along the way: a pre-existing sim bug where the warp could tunnel a sealed gate in story mode (now slides along the wall instead, ~4.8M-sweep probe-tested) and a real “dialogue panel rendered off-screen since a previous task” render bug, both caught by this plan’s own reviews, not by design.tools/story_auto/story_trace.gd --mode=tutorialwas rewritten to drive every real objective (not just beats) and check unlock order:lessons: ok, all 10 lessons complete in order, 153.0s sim time (13 level-ups, 107 kills, level 14 — excludes the read-freeze, not wall clock). Verification (Task 12): GUT suite 272/272 scripts, 2640 passing, 0 failing (4 pre-existing risky/pending), test-count guard OK; both determinism baselines unchanged, no re-pin (the lesson track is gated onsim.tutorial != null, tutorial-only — neither baseline enables it);soak.sh --runs=4 --ticks=7200 --ships=cobalt --coop=bothclean, 0 invariant failures, 0 anomalies;ui-sweep.shclean, all 11 baselines passed includingbestiary.png/hud_in_run.png(both previously-documented as flaky/stale — passed clean this run); headless boot smoke clean, 0SCRIPT ERROR. 22 dev-only 1280×720 captures (tools/tutorial_capture/lesson_shots.gd, gitignoredreports/tutorial-lessons/) were read frame-by-frame for every lesson — three real capture-tool bugs found and fixed in the process (none are game bugs): the dialogue typewriter reveal needs a real-time wait, not a frame count, in an unthrottled windowed run;_automust be forced false or the attract-mode auto-pick timer silently confirms a level-up card before the shot is taken; and a room-ambient encounter dialogue (Ash) can steal a frame meant for the objective panel unless it’s re-cleared right before capture. NOT yet merged tomain, no BUILD bump, not on any device. ⚠️ Owed: a human playtest — script in §3 below. A structural save-safety gap found while building the capture tool, fixed in this same commit:MetaStore.disk_io_enabled(new static guard, defaulttrue, no effect on any existing call site) now exists specifically so a windowed dev tool can flip it false beforemain.tscnis even instantiated and guaranteesave_state()is a no-op for the rest of that process —main._ready()unconditionally reloads the REALuser://meta.jsonand can synchronously auto-start a tutorial run off it before a harness’s own_ready()regains control, so “swapmain.metafor a synthetic one after boot” (the existingtools/ui_sweep/sweep.gdpattern) is not sufficient on its own to guarantee “never writes the real save” in every case.tools/ui_sweep/sweep.gddoes not yet use this guard — worth adding there too, tracked inNEXT_TASKS.md. -
Build the QA machinery — ✅ DONE 2026-07-14 (plan
docs/superpowers/plans/2026-07-14-qa-buildout.md; commits26bef91,c844f31,70a15f5,a1cb315,810480d,a45530a; 5 tasks + fix wave, all task-reviewed + clean final Opus whole-branch review). Ships:scripts/soak.sh(autopilot soak: invariants + per-weapon damage/kill telemetry + cross-run anomaly detection),scripts/ui-sweep.sh(9 blessed screenshot baselines, RMSE diff), dev-flag input-replay recorder/replayer (RECORD_REPLAYin main.gd),Sim.stats_sinkattribution plumbing. The plan’s Task 4 was superseded — see next item. -
2-pilot determinism test + drone state in checksum — ✅ DONE 2026-07-14 by the concurrent session (spec
docs/superpowers/specs/2026-07-14-coop-determinism-coverage-design.md; drones pool into checksumsb176faf, 2-pilot trace + pinned baseline8b0314a, closed outf80be19/acac305). Single-pilot baseline unchanged, no re-pin. -
Aligned BUILD 166 deploy to all three device classes ✅ DONE (2026-07-15): Apple TV installed via devicectl (TV asleep — benign launch error, lands on wake); iPhone installed AND launched live; Mac standalone export at builds/macos/BulletHeaven.app. Ships the full 07-14/15 swarm output + carousel declutter + P2 co-op sweep + mouse-aim fix + BASTION cruiser sprite + orbit 10.0. tvOS project verified pre-build (233/233 scripts, 0 failing, boot smoke clean). Web demo deliberately not deployed (public page — separate call). (was: TV 165, Mac 162, iPhone 147; carousel declutter, P2 drone/decoy, P2 upgrade pipeline, mouse-aim fix all merged but never deployed anywhere).
-
BUILD 167 deploy to Apple TV + iPhone ✅ DONE (2026-07-16): both installed AND launched live with zero errors (no benign asleep-launch noise this time). Ships Wyrm per-segment DoT fix, dimension-status doc drift fix, Boss2 hidden from play (OVERSEER/Sentinel dual-name sidestepped), and the mouse-aim redesign (permanent motion-latch + controller/platform device gating, plus the iOS/tvOS skip-the-tracking-entirely follow-up). Pre-flight: full suite 233/233 scripts 0 failing, count guard OK, boot smoke clean on BOTH the root repo and the folded tvOS project (233/233 parity confirmed) before building. Web demo + Mac export deliberately not redone (no public-facing content this round; the input fix specifically wants Chris’s own on-device feel-check before wider play).
-
BUILD 168 deploy to Apple TV + iPhone ✅ DONE (2026-07-16): Apple TV installed AND launched live with zero errors; iPhone installed successfully, launch hit the documented benign “device not, or could not be, unlocked” error (screen locked) — install itself confirmed shipped regardless. Ships the same-night polish batch: HP-vignette continuous ramp, manual-weapon gradient shader + “Railspike” evolution, Dimension-Choice/death-Results carousel conversion. Pre-flight: full suite 234/234 scripts 0 failing, count guard OK, boot smoke clean on both the root repo and the folded tvOS project (234/234 parity confirmed). Web demo + Mac export not redone (no public-facing content; app icon still not wired — awaiting Chris’s pick between the two baked candidates).
-
BUILD 169 deploy to Apple TV + iPhone ✅ DONE (2026-07-16): Apple TV installed AND launched live with zero errors; iPhone installed successfully, launch hit the documented benign “device not, or could not be, unlocked” error (screen locked) — install itself confirmed shipped regardless. Ships the 5 commits that had been sitting on
mainsince BUILD 168: app icon wired in (Gemini-generated from the real ship), player accent-orb upgrade indicator removed, thruster tier-scaling fix (starts small, only reaches its old “big” look at full upgrade), the new enemy arena leash (stray enemies recalled after 8s, survival-only), and the HP-bar HUD subtlety pass (number hidden, fades above 50% HP, more transparent/less glow). Pre-flight: full suite 235/235 scripts 0 failing (4 pre-existing risky/pending), count guard OK, boot smoke clean on both the root repo and the folded tvOS project (235/235 parity confirmed), both determinism baselines unchanged. Web demo + Mac export not redone (no public-facing content this round). -
BUILD 170 deploy to Apple TV + Mac ✅ DONE (2026-07-17): Apple TV installed AND launched live with zero errors; Mac standalone export launched locally. Ships two changes: the AI-pilot overhaul (
ThreatMapshared threat-scoring utility,SkilledAutoPlayernow actively engages/dashes offensively/spams drones/aims at the worst threat, and the new always-on screen-edgeThreatRadarglow — a real permanent player feature, not a debug overlay) plus a follow-up polish pass making the low-HP border glow’s WIDTH ramp with lost health (not just its brightness), tiny right at the 50% threshold and reaching its old fixed size at 0 HP. Pre-flight: full suite 2173/2173 passing (4 pre-existing risky/pending), count guard OK (238/238 scripts), boot smoke clean on both the root repo and the folded tvOS project, determinism baseline unchanged (feature never touches/sim). Web demo not redone (no public-facing content this round); iPhone/iPad not redeployed this round (Chris asked for Mac + Apple TV only). -
BUILD 171 deploy to Apple TV + Mac ✅ DONE (2026-07-17): Apple TV installed AND launched live with zero errors; Mac standalone export launched locally. Ships a playtest polish pass on the BUILD 170 features: threat radar reads calmer (ThreatMap SECTORS halved 16->8 so nearby threats merge into fewer, wider indicators; added a Gaussian distance falloff so far, not-yet-relevant spawns barely register instead of counting equally with real nearby danger; ThreatRadar blobs roughly halved in size with a real 6-ring Gaussian falloff replacing the old 3 hard rings) and the low-HP border glow reads mistier (darker/more saturated colour, softened falloff exponent, four slow layered shimmer octaves replacing two fast ones, slower breathing). Both verified with real captures, not just the numbers. Pre-flight: full suite 2178/2182 passing (4 pre-existing risky/pending), count guard OK (238/238 scripts), boot smoke clean on both the root repo and the folded tvOS project, determinism baseline unchanged (neither change touches
/sim). Web demo + iPhone/iPad not redeployed this round. -
BUILD 172 deploy to Apple TV + Mac, plus tvOS home-screen icon fix ✅ DONE (2026-07-17): Apple TV installed AND launched live with zero errors (twice — see below); Mac standalone export launched locally. Ships a real, MEASURED fix: Chris noticed Ace dying faster after BUILD 171’s threat-radar change and asked whether telemetry could isolate AI runs (it couldn’t — fixed separately by tagging
is_autoon gameplay reports,net/gameplay_telemetry.gd) — but a same-seed soak A/B was faster and confirmed it directly: mean survival had dropped 94.8s -> 87.6s (-7.6%, p=0.006). Root cause: the distance-discountedThreatMapalmost always picks a NEARBY target now, so the engage force and flee force were fighting over the same enemy on nearly every encounter. Fixed by only engaging targets beyondTHREAT_RADIUS(still approaching); re-verified on the same seeds at 104.6s mean, beating the ORIGINAL pre-ThreatMap- change baseline by the same margin it had just lost by. Also fixed while deploying: the real tvOS app icon (platform/tvos/icon.png, wired at BUILD 169) had never actually reached the device —scripts/fix-tvos-appstore-icons.pyrebuilds the App Icon asset catalog fromicon.png, but that script was only ever part of thebh-appstore-releaseflow, never this playtest-install one, and--export-packdoesn’t touchbuild/’s already-baked catalog. Every playtest install from BUILD 169 through 172 shipped the old generic placeholder icon despite the correct source being wired in for 3 builds. Ran the script, rebuilt, reinstalled — confirmed the real neon-triangle icon is now in the compiled catalog.bh-deployskill updated so this isn’t missed again. Pre-flight: full suite 2180/2184 passing (4 pre-existing risky/pending), count guard OK (238/238 scripts), boot smoke clean on both the root repo and the folded tvOS project, determinism baseline unchanged. Web demo + iPhone/iPad not redeployed this round. -
BUILD 173 deploy to Apple TV + Mac ✅ DONE (2026-07-17): Apple TV installed successfully (launch hit the documented benign “System is asleep — foreground app launch forbidden” error, TV was off; install itself confirmed shipped regardless); Mac standalone export launched locally. Ships the per-enemy threat radar rework (Chris, after playing the 8-sector-blob version: “make every enemy have a radar threat light and an average light effect that connects above them all… a gentle wide mist”).
ThreatMap.enemy_weight()extracted as a shared per-enemy danger formula (Ace’s own AI decisions unchanged, verified).ThreatRadarrewritten: every enemy gets its own edge-projected light instead of 8 sector blobs, rendered via twoMultiMeshInstance2Dlayers reusingSwarmRenderer‘s existing glow pattern — a sharp full-res layer (small distinct dots) plus a mist layer rendered into a low-resSubViewportand upscaled with bilinear filtering, so dense clusters genuinely blur into soft wide glow (confirmed by research: naive additive overlap alone doesn’t merge silhouettes, needs the blur pass, same techniqueNebulaBgalready proves out on this hardware). Verified with a real capture + quantitative pixel sampling (52-sample brightness profile across a 25-enemy test cluster: zero local minima, smooth single hump — not 25 separate peaks), not just visual eyeballing. Pre-flight: full suite 2213/2217 passing (4 pre-existing risky/pending), count guard OK (240/240 scripts), boot smoke clean on both the root repo and the folded tvOS project, determinism baseline unchanged (/simuntouched), soak sanity clean (0 invariant failures). Open item for Chris’s own playtest judgment (not a code issue):SCORE_NORMALIZE=8.0now normalizes a single enemy’s own weight rather than a whole sector’s sum, so ordinary swarm enemies’ sharp-layer dots may read very faint — may be exactly the intended “tiny lights” feel, may need tuning. Web demo + iPhone/iPad not redeployed this round. -
BUILD 174 deploy to Apple TV ✅ DONE (2026-07-17): Apple TV install + launch both succeeded cleanly (no benign-asleep error this time). Ships two separate pieces of work: (1) Ace loadout-aware weapon range —
ai/weapon_range.gd(WeaponRange.effective_range(), a per-weapon reach lookup verified against every realsim/weapon_*.gdfield) replacesSkilledAutoPlayer’s fixed engage-cutoff/dash-window/aim-radius constants with the median equipped weapon’s real range, so Ace positions based on his actual loadout instead of ignoring it; (2) a Chris-directed balance pass — every weapon’sbase_damage+30% and the shared “power” upgrade mod 1.25/1.20→1.5 across all weapons + the manual aim weapon, following a diagnostic soak sweep that found chargers (TYPE_ELITE) cause 60-67% of all Ace deaths across two 60-run sweeps, at roughly the same mean level as every other death cause (not a late-game- specific problem — a design implication for a future orbit/dodge AI pass, not yet built). Soak death-cause + level tracking (sim.death_causeplumbed into soak reports, 3 newfun_signalsaggregates) is what made that diagnosis possible — QA tooling, no gameplay change on its own. Pre-flight: full suite 2242/2246 passing (4 pre-existing risky/pending), count guard OK (241/241 scripts), boot smoke clean on both the root repo and the folded tvOS project, determinism baseline passed UNCHANGED (the pinned 600-tick trace never reaches a kill/level-up within its window, so damage magnitude didn’t touch RNG draw order — no re-pin needed), soak sanity clean (0 invariant failures across two post-change sweeps). Web demo + iPhone/iPad not redeployed this round. -
BUILD 175 deploy to Apple TV ✅ DONE (2026-07-22): Apple TV install + launch both succeeded cleanly (no benign-asleep error). This is a catch-up deploy — everything merged to
mainsince BUILD 174 ships at once: (1) the from-scratch threat-radar rework (top-K sharp-dot layer + analytic per-sector angular-Gaussian glow, zero SubViewport/per-enemy cost — see Current status in CLAUDE.md, not yet acceptance-tested against the 172-174 FPS baseline this round either); (2) Tutorial Onboarding (Ember Reach) — fresh saves now launch straight into a capped first-act tutorial teaching movement/weapons/dash/decoy, auto-handing off into Survival once the Ember Warden falls;docs/superpowers/specs|plans/2026-07-21-tutorial- onboarding*, full history memorybullet-heaven-roadmap; (3) player bullets thinned 50% (PLAYER_BULLET_WID/AIM_LANCE_WID5.0→2.5) and Rift Bolt (the manual/aim weapon, named this session) given a deepened near-black-violet→magenta-pink identity distinct from the auto-weapon’s element-driven colour. Pre-flight: full suite 244/244 scripts, 2275/2279 passing (4 pre-existing risky/pending) on both the root repo and the folded tvOS project; boot smoke clean on both; determinism baselines unchanged (every sim-layer Tutorial change lives behind the pre-existingstory != nullseam; the FX/colour/width tweaks are pure render). ⚠️ Nothing in this deploy has been human-verified for feel — the tutorial flow, the threat-radar rework’s actual FPS impact, and the bullet thinning/recolour all still need a real playtest. Web demo + iPhone/iPad not redeployed this round. -
BUILD 176 deploy to Apple TV — ThreatRadar idle-render perf fix ✅ DONE (2026-07-22): install succeeded; first launch attempt hit the benign “System is asleep” error (TV was asleep), retried once the TV woke and launched cleanly. Chris photographed the F2 perf overlay on BUILD 175 sitting idle (0 enemies/projectiles/gems on screen) showing only ~41fps, render 24.32ms dominating a negligible 0.80ms sim — the opposite of what the just-shipped “fixed-cost” threat-radar rework promised. Live tvOS telemetry confirmed it was a real regression, not device noise: BUILD 175 at avg_enemies=1.5 measured 34.1fps/29.75ms render, WORSE than BUILD 173’s old per-enemy-scaling mist system at the same near-zero-enemy bucket (34.3fps/28.78ms) — the exact system the rework replaced specifically for being too slow under load. Root cause:
ui/threat_radar.gd’supdate_radar()forcedvisible=trueunconditionally every frame; the aggregate-glow shader is a full-screenColorRectwith a per-pixel fragment shader (8-sector loop +exp()), so it paid full per-pixel cost across the whole screen even with nothing to show — draw-call count was genuinely fixed as designed, but fragment-shader cost scales with screen pixels, not draw count, so “fixed draws” didn’t mean “fixed or low cost.”fx/screen_feedback.gdalready has the reference pattern for this (visible = enabled # kill the full-screen shader pass), andui/boss_edge_indicator.gdalready gates its own visibility on real content — ThreatRadar claimed to extend the former but never adopted its visibility gating. Fix:visible=falsewheneverselect_top_k()returns no threats. An existing test (test_update_radar_self_repairs_visibility) asserted forced-visible even on an empty arena — that encoded a misreading of the BossEdgeIndicator pattern it claimed to mirror (BossEdgeIndicator hides on no-content too); updated to test self-repair with real content, plus a new test for the actual fix. Pre-flight: full suite 244/244 scripts, 2276/2280 passing on both the root repo and the folded tvOS project; boot smoke clean on both; determinism baselines unchanged (pure render-layer change, no/simtouch). ⚠️ Still needs a real on-device FPS re-check once the TV is awake — this fixes the confirmed root cause but hasn’t been re-measured live yet; if idle fps is still poor after this, the aggregate-glow shader’s per-pixel cost may need reducing further even when it IS showing something (not just gated off when idle). -
BUILD 177 deploy to Apple TV — adversarial performance investigation + 2 more shader-gating fixes ✅ DONE (2026-07-22): install + launch both succeeded cleanly. A full adversarial workflow (101 agents, 8 parallel finders + 3-lens skeptic verification per finding) investigated the remaining ~50fps idle / ~40fps light-load ceiling after BUILD 176’s ThreatRadar fix — 21 findings confirmed, full writeup
docs/research/2026-07-22-atv-performance-adversarial-investigation.md. Two more instances of the exact ThreatRadar bug class found and fixed same-day infx/screen_feedback.gd(both worse — neither had ANY gate before, not even the broken adaptive-quality one): the low-HP border stayedvisible=truethe entire game regardless of HP; the darkening vignette’s shader had no uniforms/TIME at all (mathematically constant output) yet was recomputed as a live per-pixel shader every frame — now baked once into a static texture, pixel-verified against the original formula. Chris independently confirmed bloom is NOT the light-load driver — he tested Debug Settings → all fx off (including bloom) on BUILD 176 and still saw the fast ~50→~40fps drop under light load, meaning the real cost is something that scales with actual content even at low quantities, not the always-on full-screen post-processing. Leading remaining suspects per the investigation: unbatched spider-web rendering (confirmed 3.2x the draw calls of a full 58-enemy swarm),HeroLights(player’s real-time light, ungated),ArenaBackground(no camera culling, unlikeSwarmRenderer), and ~20 renderer nodes thatqueue_redraw()unconditionally regardless of content. Pre-flight: full suite 244/244 scripts, 2280/2284 passing on both the root repo and the folded tvOS project; boot smoke clean on both; determinism baselines unchanged (pure render-layer changes, no/simtouch). ⚠️ Light-load drop still unresolved — these two fixes help the fully-idle case but were not expected to (and per Chris’s bloom-off test, didn’t need to) address the light-load-specific regression; next work should target the content-scaling suspects above, not more always-on-shader gating. -
BUILD 178 deploy to Apple TV — ThreatRadar aggregate-glow downres ✅ DONE (2026-07-22): install succeeded; first launch attempt hit the benign “System is asleep” error (TV was asleep), retried once awake and launched cleanly. Chris’s own on-device bloom-off test (recorded in the BUILD 177 entry above) ruled out always-on post-processing as the light-load driver; live telemetry then pinpointed the real cause precisely:
render_msjumped ~10ms the instant even ONE enemy exists (49.4fps at 0 enemies → 36fps at 1 enemy), then stayed FLAT regardless of enemy count all the way to 99 — the signature of a fixed full-screen-shader cost switching on, not per-entity scaling. That’s exactlyThreatRadar’s aggregate-glow shader: correctly gated off at true zero enemies by BUILD 176’s fix, but real gameplay almost always has at least one enemy, so the same expensive full-screen pass returns the instant there’s any action at all — BUILD 176/177’s fix helped the idle screenshot case but did nothing for “light load,” which is what was actually being asked about. Fix: downres the shader into an offscreen SubViewport at 0.35x resolution (same techniquerender/nebula_bg.gdalready uses, more aggressive since an 8-sector angular sum has far less spatial detail than FBM cloud noise), displayed via a bilinearly-upscaledTextureRectwithBLEND_MODE_ADD— required un-premultiplying the shader’s own colour output to avoid double-applying alpha at the new composite stage. Pre-flight: full suite 244/244 scripts, 2283/2287 passing on both the root repo and the folded tvOS project; boot smoke clean on both; determinism baselines unchanged (pure render-layer change). ⚠️ Reasoned through but NOT visually verified locally (shader/blend colours don’t render headless) — the blend-mode math should reproduce the original glow exactly, but needs a real on-device look to confirm the additive compositing reads correctly, alongside the actual light-load FPS re-check this whole investigation was chasing. -
BUILD 179 deploy to Apple TV — dedupe ThreatRadar weight computation, throttle F2 overlay ✅ DONE (2026-07-22): install succeeded; first launch attempt hit the benign “System is asleep” error, retried once the TV woke and launched cleanly. Chris reported a fresh wave spawning off-screen (nothing visually rendered yet) still ran slow — ruling out anything render/visibility-related and pointing at a pure CPU cost scanning the full enemy list regardless of what’s actually drawn. Found two: (1)
ui/threat_radar.gd’supdate_radar()was calling BOTHselect_top_k()andThreatMap.compute()every frame, each independently looping over every enemy and callingThreatMap.enemy_weight()— genuinely double work (400 weight computations/frame at 200 enemies); deduped via two newai/threat_map.gdfunctions (weights_for(),compute_from_weights()), computing weight once and sharing it —compute()’s public signature/behavior is unchanged (verified byte-for-byte equivalent), soSkilledAutoPlayer(the other consumer) is unaffected. (2) the F2 debug overlay itself was rebuilding its full text (an O(enemy count) aura loop + Label reshape) every single frame, unconditionally, folded directly into the exactrender_msnumber being read off every screenshot this session — throttled to 4 updates/sec. Pre-flight: full suite 244/244 scripts, 2287/2291 passing on both the root repo and the folded tvOS project; boot smoke clean on both; determinism baselines unchanged (SkilledAutoPlayer’s own 30/30 tests confirmThreatMap.compute()’s output is untouched). ⚠️ Needs an on-device re-check once the TV wakes, ideally during a scenario matching Chris’s original report (a wave spawning before anything is visually on screen) to confirm this specific cost is actually gone. -
BUILD 180 deploy to Apple TV — glow-technique rework (3 rounds) + Ghost glow removed ✅ DONE (2026-07-22): Chris flagged the neon glow reading flat/pastel vs. a lightsaber-style white-hot core + colour halo. Root cause:
render/swarm_renderer.gd’s core was a flat hard-edged quad with no internal gradient, and the halo was capped at alpha ≤1.0 so it never triggered Godot’s real HDR bloom. Round 1: shrank the flat core (55%) and added a soft radialGlowTexture“hotcore” layer (additive, HDR-overbright) — applied toSwarmRenderer(every bullet/enemy/gem) and, for procedural shapes,ArchetypeRenderer(bosses/specials; sprite shapes keep full-size cores). Round 2 (Chris caught it): the round halo/hotcore looked worse on angular shapes than on the isolated dot tests — a circle doesn’t follow a star’s points or a cross’s arms. Switched procedural shapes to a “shell” technique (6 scaled copies of the enemy’s own shape mesh, flat colour, additive, alpha descending outward — ref: a Unity forum thread on lightsaber-aura shaders) so the glow genuinely follows the silhouette. Sprite shapes (spider/ghost/orbiter/ warden) kept the round halo — their mesh is an untrimmed rectangular bounding quad, not a meaningful silhouette; shelling that rectangle produced an ugly flat box, caught and reverted same pass. Brute (radius 48, far bigger than every other type) needed 6 gentler shell steps instead of 4 to avoid banding into a solid white block at that scale. Round 3: Ghost’s glow removed entirely (Chris’s call) — it’s the only type onSHAPE_WISP, fully isolated change. Full ritual green every round: suite 244/244 scripts (2287/2291 passing, 4 pre-existing risky/pending), count guard OK, boot smoke clean, both determinism baselines unchanged (single-pilot4217109746/3312602286, 2-pilot370166760/2437492589) — every change here is render-side only. Verified visually via a newtools/glow_compare/harness (isolated dots, a full 25-type enemy gallery with/without glow, and real in-game captures) before each commit, not just reasoned about from code. Install + launch both succeeded cleanly this time, no benign “System is asleep” retry needed. -
BUILD 181 deploy to Apple TV — baked the shell-glow (perf pass on BUILD 180’s glow work) ✅ DONE (2026-07-22): the live 6-shell technique above had no per-frame variation (same shape, same relative scales/alphas, only the tint colour changes per enemy) — a pure baking candidate. Chris: “bake all the assets we can.” New
tools/glow_bake/bake_shape_glows.gdrenders each procedural shape’s shell stack once via a throwawayArchetypeRendererinstance’s_mesh_for_shape()(kept independent of the runtime glow pipeline — that pipeline now LOADS these baked textures, so depending on it here would be circular on the very first bake), folds the additive-white brightness into an alpha channel (same encodingGlowTexture.build()already uses), crops to content, and saves 16 tiny PNGs (~1-3KB each) torender/shape_glow/. Cuts every procedural shape from 7 draw layers (core + 6 shells) down to 2 (core + 1 baked, tinted- per-instance glow texture) — directly answers BUILD 180’s flagged perf concern. Two real bugs caught before shipping: (1) baking with 3x oversampling for “extra resolution” accidentally baked in visible banding — the same large-scale banding mechanism found on Brute during BUILD 180 now hit every shape, since oversampling effectively renders “this shape at 3x size” during capture; fixed by baking at native (1.0x) scale instead. (2) The bake tool’s rewrite (needed to fix the circular-dependency bug) dropped the HDR/bloom/tonemap environment setup entirely, so the overbright innermost shell just clamped to 1.0 with no real bloom pass — baked glow read visibly dimmer in real gameplay than the live version it replaced; fixed by restoring the same env config used everywhere else this session. Full ritual green: suite 244/244 scripts (2287/2291 passing) on both the root repo and the folded tvOS project, boot smoke clean on both, both determinism baselines unchanged (render-only change). Install + launch both succeeded cleanly. ⚠️ Still owed: a genuine on-device FPS comparison against BUILD 179/180 — the draw-call/overdraw reduction is real and measured in code (7→2 layers), but not yet confirmed as an actual measured FPS delta on the Apple TV. -
BUILD 182 deploy to Apple TV — reverted to live-shell glow as default (baked “hasn’t worked very well” on-device, Chris) ✅ DONE (2026-07-22): rather than overwrite the live-shell code with the baked version again,
render/archetype_renderer.gdnow keeps BOTH techniques behind oneUSE_BAKED_GLOWconst (defaultfalse= live shell) — switching or resuming work on the baked path is a one-line change, not archaeology. Found + fixed a real bug while restoring:BAKE_OVERSAMPLEwas left at3.0after the bake tool’s own oversample was changed to1.0(BUILD 181’s banding fix) — every baked glow quad was rendering at 1/3 its correct world size. Even with that fixed, a real in-game A/B (published to the colour-system artifact) shows baked still reading visibly tighter/weaker than live — open for further iteration, not yet resolved. Full ritual green: suite 244/244 scripts (2287/2291 passing), count guard OK, boot smoke clean, both determinism baselines unchanged. Install + launch succeeded cleanly. -
BUILD 183 deploy to iPhone — fixed a real “app crashes on launch, no crash log” bug caused by a stale iOS engine build ✅ DONE (2026-07-23): first iPhone deploy since the 2026-07-19 stock Godot 4.6.3→4.7.1 upgrade.
platform/tvos/build-ios/BulletHeaven.xcframework’s compiledlibgodot.awas still dated 2026-07-11 (pre-upgrade) — packing game data with the new 4.7.1godotbinary via--export-packalone writes pack format v4, which the stale 4.6.3-era compiled engine can’t read. Symptom was genuinely hard to diagnose remotely: Godot’s own boot splash shows, then the app exits cleanly (exit code 0, no signal) with zero crash report anywhere (idevicecrashreportpulled the full on-device log archive, confirmed no BulletHeaven entry at all) and zero console output overdevicectl --consoleeven wired over USB — worked perfectly on a genuinely fresh-save Mac export the whole time, which is what made this look like a code bug rather than a build-pipeline one. Root cause only surfaced viaidevicesyslog(installedlibimobiledevicevia brew) live-streamed during a real launch:core/io/file_access_pack.cpp: Pack version unsupported: 4/main.cpp: Error: Couldn't load project data ... Is the .pck file missing?. Fixed with a full--export-release "iOS-Dev"(not just--export-pack) to regenerate the xcframework at the current engine version, re-applied theNSLocalNetworkUsageDescriptionInfo.plist patch (a full export resets it), rebuilt + reinstalled + relaunched — confirmed running live and playable by Chris on his iPhone 15 Pro Max (iOS 27). Structural lesson, added tobh-deploy: after any stock Godot version upgrade, the NEXT iOS deploy needs a full--export-releasefirst, not--export-pack— tvOS was unaffected because it always builds via the pinned custom~/Claude/godot-tvosfork binary, but iOS’s stock-Godot--export-packpath silently drifts from whatever engine version is baked intobuild-ios/until forced to regenerate. -
Obsidian cruiser real sprite — still an Aurum-copy placeholder; player-facing paid unlock.
tools/ship_previewbake + shop/picker on-device check. (CLAUDE.md) ✅ sprite DONE — Chris picked BASTION (2026-07-15), wired asrender/ship_sprites/ship3d_obsidian.png(512×512 drop-in, zero code changed; verified visually in place). Still owed: the on-device shop/picker check (§3 playtest) + W4’s two wiring notes (TV legibility of the dark flanks; tier-based vs radius-based baked-sprite scale for the 1.5× cruiser size). (was: ⏳ candidates ready for Chris’s pick, overnight W4, commitc72ecbc— pick + wiring + on-device check still open):tools/ship_preview/candidates-obsidian/—obsidian_bastion.png(broad armored wedge dreadnought, deep violet),obsidian_ram.png(twin-prow assault cruiser with a glowing central ram, magenta-violet),obsidian_monolith.png(hammerhead slab, indigo), plusobsidian_lineup.png(all three next to the cobalt frigate at the in-game 1.5x radius ratio). Same procedural recipe as the shipped hull bakes (Ship3DRenderer, top-down 512px transparent) with a darker volcanic-glass treatment; rebake/tweak viatools/ship_preview/obsidian_candidates.tscn(windowed, foreground). Wiring notes: re-check the dark flanks against the in-game void background on the TV, and PlayerRenderer’s baked-sprite scale is tier-based, not radius-based — the cruiser’s 1.5x visual size needs its own scale decision. -
Balance-tune pass with soak telemetry in hand: evidence pack ready:
docs/balance-evidence-2026-07-15.md(W5) — 100 runs aggregated + 2 targeted sweeps; 6 labeled proposals await Chris (trim Aim baseline;BOSS_HP_TIME_FRAC→0.8 only-if clean-arena blessed; keepXP_LEVEL_GROWTH/FIRE_RATE_CAP; elite telemetry before touchingELITE_*; fresh-save-arc decision; Orbit ~0 damage — investigated, NOT a bug ✅ (W8, testea2cb64): attribution correct (sim.gdwraps every weapon update indamage_context = wid, the 95 dmg WAS orbit’s own bucket); weapon functional — new definitive GUT testtest_orbit_full_contact_second_removes_exactly_base_damage_hppins exactly base_damage HP removed per full-contact second; root cause is DESIGN SCALE: biblebase_damage1.2 is consumed as contact DPS (× dtsince day one,6c63944“continuous DPS”) → 1.2 DPS/enemy vs beam ~8 / blaster ~8.9, so even 100% contact for a 7-min run ≈ 500 dmg; autopilot kiting is secondary (95 dmg ≈ ~79 enemy-contact-seconds across the 7 runs that picked it — contact DID happen). Buff value = balance pass, Chris’s call — see P6 resolution in the evidence pack). ✅ TUNED (2026-07-15): Chris set base_damage 1.2 → 10.0 (top of the 8-15 peer-DPS band, paying for orbit’s get-close positioning risk); pinning test updated to match; determinism baseline unchanged (orbit not in the 600-tick trace); soak clean. The tune itself (const changes) remains open.FIRE_RATE_CAP,BOSS_HP_TIME_FRAC,XP_LEVEL_GROWTH,ELITE_HP_MULT/ELITE_CONTACT_MULT; the 2026-07-09 content batch (6 weapons, 6 enemies) never feel/balance-tested; player crit (PLAYER_CRIT_MULT=2.0, 15% “Focus”) untested. First soak data (2026-07-14, 56 runs, fresh save, all hulls, solo+co-op, 0 invariant failures/anomalies): manual AIM weapon = 60% of ALL damage under constant autopilot aim (blaster 19%, drone 9%, everything else <4% each) — first hard input for the aim-ladder tune; autopilot on a fresh save dies at 1–3 min game-time every run (consistent with meta-progression-first balance, but decide if the intended fresh-save survival window is really ~2 min); only 3 enemy types (0/2/4) died across the whole sweep and no run reached a boss/dimension — late-game coverage comes from the soak invuln mode. -
Remote config (Chris, 2026-09-27, approved freeze exception). Balance changes (bible.json weapon/enemy/shop numbers + sim tuning consts converted to
static vartunables) served per build from the telemetry Worker, applied at run start, no App Store build. Numbers only (guideline 2.5.2). The client half must be in the launch binary or it costs a build + review to add later. Infra first, then const conversion in module batches, each gated by an unchanged determinism checksum + A12 ATV frame time. Spec:docs/superpowers/specs/2026-09-27-remote-config-design.md; architecture doc (current source of truth):docs/architecture/remote-config.md.- Infra —
Tunables/TunablesRegistry, bible overlay, fetcher + cache + crash-loop guard, install id, Worker/configroute + KV, D1 stamping columns, manifest tool, publish script, re-fetch on return-to-menu (throttled). Tasks 1-9b,d0ceb26..3a9aa70(full commit-by-commit trail:.superpowers/sdd/2026-09-27-remote-config/progress.md). - Batch 1 (wave/boss/elite pacing + Fork/Penetrator split damage) — 12 live tunables
shipped, 8 dead ones reverted to
constafter a liveness check (d1a2dfd,f355bc7,5cc9180; full list + why each was dead:docs/architecture/remote-config.md§ “Batch 1”). ✅ Frame-time gate PASSED (2026-09-28, BUILD 20593eb117vs 204 on the ATV 4K): ATV autopilot frame p95 26-38 ms vs 204’s 29-40 ms (same spread). ATV sim_ms read ~1.3 ms higher over 2 sessions, so a controlled Mac A/B (seed 1234, invulnerable, 3600 ticks, 3 reps, identical checksums) settled it: 204 = 859 µs/tick, 205 = 852 µs/tick, no cost. The ATV gap is noise. - Refund ledger —
reset_shoprefunds the gold actually paid (MetaState.gold_spent), not a re-price at patched prices; pre-ledger saves migrate once from the frozen BUILD 204 price tabledata/meta_prices_legacy.json(d0019f1,cc9e1d9,docs/architecture/remote-config.md§ “Shop prices and the refund ledger”). Shop-price patches are safe to publish only for builds containing the ledger (BUILD ≥ 205, never 204). - Next batches (spec §7.2, most-likely-to-tune first, each its own conversion + liveness
check + determinism/soak/ATV gate): bosses, elites/elite_kit, enemy behaviours/attacks, the
drone director, then the rest of
sim.gd. Not required for v0.1 — ship with whatever’s landed; anything unconverted still needs an App Store build to change, same as today.
- Infra —
-
Meta-shop pricing from a simulated economy (Chris, 2026-09-27). Prices were hand-set: 22,951 gold maxes all 66 items vs ~200-400 gold per median human Survival run (~60-110 runs). Target (Chris): first meaningful buy in 1-2 runs, core Pilot + Arsenal by ~run 20, Drones the long tail. Plan: soak gains
--metashop state +--profile+run_gold; SkilledAutoPlayer gets weak/median/strong skill knobs calibrated to human telemetry p25/p50/p85; a career sim (run → bank → buy → repeat) under two buy policies (shop-tip order, value-per-gold); a per-item value sweep at fresh/~run-20/~run-60 saves. Output is a report + price proposal for Chris to approve; the tooling is QA work, the repricing is the balance change. Also answers the “fresh-save-arc decision” above. Spec:docs/superpowers/specs/2026-09-27-shop-economy-sim-design.md. Plan 1 ✅ (2026-09-27: MetaRunSetup, soak –meta/–profile, calibrated profiles, career sim, first curve). Finding: whole catalogue bought in ~30 median runs, not 60-110, because one item, Field Medic (stand-still heal), turns 1-min runs into 5-20-min runs once the pilot has some defence (ablation-confirmed). Awaiting Chris’s per-tier targets → Plan 2. Write-up:docs/research/2026-09-27-shop-economy-first-curve.md. -
Wyrm shared-HP pool takes multiplied damage when one AoE hits several segments in one tick — separate from the fixed kill-payout bug; AoE builds melt wyrms. (07-13 c2) ✅ DONE (commit
dc3e739, overnight W1) — every multi-target damage loop now brackets itself as onebegin/end_aoe_group()event;damage_enemyapplies at most one hit perwyrm_idper event (distinct events still stack). 7 new tests intests/test_wyrm.gd; full suite + count guard + boot smoke + determinism (no re-pin) + soak all clean. -
CLAUDE.md references
V01_LOCK_NETWORKED_MPwhich doesn’t exist onmain(lives only in the unmerged worktree) — doc fix so the flag audit doesn’t chase a ghost. (07-13 c1) ✅ DONE (overnight W2) — verified absent frommainby whole-tree grep; CLAUDE.md now says the const exists only in the MP worktree. -
INVESTIGATE: solo mid-run kill stall (probable boss wall). First invuln coverage sweep (soak
reports/soak-1784065064.json): 4 of 6 SOLO runs flagged SOFTLOCK (zero kills for 3+ min while alive) at tick ~23.5–27k (~6.5–7.5 min game-time); 0 of 6 co-op runs. ✅ DONE (overnight W3) — verdict: NOT a stall, NOT an autopilot-won’t-engage artifact; no AutoPlayer change warranted. Instrumented replay of the repro (seed 3000, solo cobalt, invuln): TYPE_BOSS spawns ~t13380 with ~6.6k HP as the ONLY entity alive; AutoPlayer engages fine — it kites at its flee-equilibrium ~272px (inside its 400px aim range) and the boss HP falls smoothly 6562→257 (~31 HP/s) until it dies at t26279, after which the run continues normally. The fight lasts ~12.9k ticks (~3.6 min) > the 3-min SOFTLOCK window, and kills flatline to zero for its whole duration because survival’s PHASE_BOSS empties the arena and the designed replacement — trickle adds — is DEAD CODE (next item). Co-op’s ~2× DPS keeps the same fight under 3 min → 0/6 co-op flags, as observed. The remaining “is a 3.6-min solo boss fight OK” question is balance, already covered by the §2 balance-tune item (BOSS_HP_TIME_FRAC). (2026-07-14 soak; investigated 2026-07-15) -
Survival boss fights have NO trickle adds —
Sim._spawn_boss_adds(sim.gd:1616) is orphaned dead code with zero callers onmain(found by W3’s softlock investigation). Its own comment +SpawnDirector.pick_boss_add_type’s say it “replaces the old clean-arena suppression”, and story mode’s separateStoryDirector._spawn_boss_addsIS wired — but the survival phase machine never calls the Sim one, so every survival boss fight is a silent empty-arena duel (this is also exactly why the soak SOFTLOCK heuristic fires on long solo fights). Decide: wire it into PHASE_BOSS as designed (draws fromrng→ determinism re-pin needed, gameplay-affecting → not an overnight blind fix) or bless the clean arena and delete the dead code + fix the spawn_director comment. (2026-07-15, W3) -
CLAUDE.md dimension-status doc drift: it still says Ice/Blood/Electricity/Poison are “deferred pending their own new bosses/mechanics”, but
sim/area_defs.gd+ the 07-10 dimension-choice popup now offer 3 of ALL 7 non-Home dimensions with stand-in bosses (Warden et al) — confirmed live in soak (electricity/poison/ice all visited). Update CLAUDE.md + the enemies-bosses-survival architecture doc. (2026-07-14 soak) ✅ DONE (orchestrator take-over from frozen W2, 2026-07-16) — verified live roster againstsim/area_defs.gd: all 7 non-Home dimensions defined + offered (fire/ice/void_dim/blood/electricity/light/poison), bossesvoid_dim→Graviton,poison→FunZo,light→Eye native; Warden stands in for fire/ice/blood/electricity (their bespoke bosses unbuilt). Corrected the false “deferred” claim in CLAUDE.md and added a current-roster table (source of truth) todocs/architecture/enemies-bosses-survival.md. Pure docs, no code touched. -
✅ DONE 2026-09-23 (copy rewritten against the live sim: burn/shock, 3-way dimension choice, tutorial; also dropped the “steady frame rate” claim the ATV doesn’t currently meet — restore it once the 45 fps target is hit) P0 — App Store copy advertises a mechanic that no longer exists.
marketing/appstore/copy.md:7,17,22andSUBMIT.md:29promise “elemental reactions” / “set off a reaction” — reactions mode was archived 2026-07-09 (the in-game Codex was corrected 07-22,1c00fa9, but the store copy never was). Rewrite against what the sim actually does before any upload. (audit 09-23) -
[~] WIRED 2026-09-23 (L1 sheds HeroLights with bloom, L2 sheds NebulaBg; also fixed NebulaBg’s SubViewport rendering every frame while “disabled”). Flag still OFF: with
TARGET_FPS=50and ATV at ~38 fps, flipping it today would strip the game to L5. Flip only after the perf work. P0 — the adaptive quality ladder can’t shed the effects it most needs to. Re-enablingADAPTIVE_QUALITY(§1) is not enough on its own:NebulaBgis explicitly “NOT on the adaptive ladder (yet)” (render/quality_manager.gd:52) andHeroLights(render/hero_lights.gd, real-time point lights, a named suspect in the light-load FPS drop) is never referenced by_apply(). Wire both onto the ladder, then flip the flag, then verify on-device. (audit 09-23) -
✅ DONE 2026-09-23 (mods stay a shared party pool, documented; Leech now heals every living pilot against their own cap, single-pilot arithmetic unchanged) P1 — co-op Leech heal always goes to P1.
sim/sim.gd:2489healsplayer(P1) on every kill regardless of who owns the upgrade;Upgrades.apply()writes P2’s SimMods picks into the sharedsim.mods(sim/upgrade_system.gd:252). Decide shared-party vs per-pilot mods; fix Leech either way. (audit 09-23) -
✅ DONE 2026-09-24 (branch
perf-191, not yet on a device) P1 — two draw-call blowups (BUILD 191 telemetry). (a) Spider webs: atSim.WEB_CAP(400) a Survival frame went from ~100 to ~2,200 draws, fps 17-25 (seen in builds 168/173/191, the “unbatched spider-web rendering” suspect from the BUILD 177 entry).ZoneRenderernow draws all webs as ONE MultiMesh (render/web_field.gdshader); Mac probe 2,082 → ~80 draws, isolated before/after captures match within ~1.5% mean brightness, low-detail path too. (b) Tutorial/story sat at ~450 draws, fps ~37, with 0-2 enemies:StoryMapRendererissued adraw_rectper room/wall/gate of the WHOLE map every frame (407 of 422 draws). Map geometry is now baked into meshes; tutorial 422 → 23 draws, screenshot-identical. Measured with the newtools/perf_probe/. Owed: confirm on the ATV via telemetry (drawsin the tutorial and in a web-heavy Survival run). -
✅ DONE 2026-09-27 (
4beee06) P0 — Blaster + Penetrator + Fork + Overclock was near-invincible (Toby, build 203 ATV, no shop spend: arena held 6-27 enemies from minute 3, three bosses died within ~1s of enraging). Sincea55ff97Fork split on every pierce hit at FULL damage, up to 36 full hits per shot. Children now 50% damage (25% when the forked hit was a boss), Fork offered once per run. Penetrator still stacks uncapped; cap it next if runs stay too easy. -
✅ DONE 2026-09-27 P1 — autopilot warped from sides that made no sense, died to “swarmer shot”.
SkilledAutoPlayernow plans move + warp together (_plan) and predicts danger lanes (ai/danger_lanes.gd); skilled soak mean death 55s → 117s. Same change cut exhaust/warp VFX (thruster plume + sparks dimmer, warp speed lines off, enemy warp trail dimmer). Owed: eyeball the VFX on a device.
3 · Owed human playtests — batched into ONE scripted session per device (Phase 3)
Section titled “3 · Owed human playtests — batched into ONE scripted session per device (Phase 3)”All of these ride the BUILD 166 aligned deploy; none are automatable. The playtest script will sequence them; findings come back to §2/§4.
- 10-lesson Tutorial track — full device sweep (tutorial-lesson-track plan, 2026-09-24).
Script:
- Fresh save on Apple TV, real controller (not the Siri Remote), start to finish.
- Fresh save on the Mac, keyboard only, start to finish.
- Same Mac run, plug in a controller mid-run (around lesson 5) — every prompt glyph (objective panel, dialogue skip hint, level-up hints) should switch to match within one lesson, not lag or stay stuck on the old device.
- Fresh save on iPhone, touch only, start to finish — every prompt should read as a touch word (“TAP”/“HOLD”/the on-screen button’s own name), never a keyboard or controller glyph.
- For each of the ten lessons, on at least one device: did you know what to do without re-reading the line twice? Was it obvious who was talking (name, portrait, which side)? Note the lesson number and what confused you, if anything.
- Time each lesson (dialogue read to objective complete). Flag anything that feels rushed or drags — the automated trace (§2’s “Tutorial lesson track” entry) gives a sim-time-only baseline (excludes the read-freeze), so real cadence will always run longer; the question is whether it’s longer in a way that feels wrong, not by how much.
- Open the pause menu at least once mid-Tutorial: Shop/Ship/Bestiary tabs must be absent,
C(codex)/Y/V(HUD toggle) must do nothing, and no “NEW ENEMY” card should ever pop. - Take damage near the Warden and confirm a tip only appears while genuinely low on health, not as a constant fixture.
- Balance call for Chris: is the Warden too hard now that the Pulse gift and its encounter
heals are gone? A mortal bot in the automated trace dies ~4s after
warden_introwith no heal rewards left — real difficulty, not a bug, and this playtest is the first human data point. - Check the lesson 6 (WARP) 45° wall-slide feel — the warp used to tunnel a sealed gate in story mode (fixed this plan) and now slides along the wall instead; confirm the slide distance feels right, not short or cut off.
- Start-menu carousel declutter: Mac click-through (swipe, Ship/Shop/Enemies open + return, Survival still starts). (roadmap 07-14)
- Ship Tilt: play both mechanisms (Shader Warp vs Baked Flipbook vs Off) and pick the winner — Mac first, then Apple TV. (roadmap 07-06)
- Reset Shop: dialog legibility + the real save/refresh path. (roadmap 07-05)
- Manual aim weapon: worth learning? Tune
WeaponThresholds.TABLE["aim"]ladder. (CLAUDE.md) - 2-controller co-op session: P2 level-up panels chain P1→P2, P1 genuinely can’t hijack P2’s panel (incl. mouse), P2’s drone deploys/recharges independently, P2 takes boss/hazard damage. (roadmap 07-10/11)
- Centurion on the TV: rosette legibility at swarm scale + per-dimension colour check. (roadmap 07-06)
- New-content feel pass: Momentum Core, Chronowell, Scar Tissue, Railgun, Wildfire, Thunder Wake / Beacon, Glass Wisp, Halo Chain, Leech, Coil, Fleet Kite. (CLAUDE.md 07-09)
- Elemental Dimensions v2 + dimension-choice popup flow on-device sanity. (roadmap 07-05/11)
4 · Fix-if-cheap (real but small; grab opportunistically, don’t chase)
Section titled “4 · Fix-if-cheap (real but small; grab opportunistically, don’t chase)”-
COPY — the decoy drone must always be called “Decoy”, never “Sentinel” (Chris, 2026-09-27). — ✅ DONE (
9561261) Player-facing text only: shop item names/descs indata/bible.json, the shop sub-category tab, Drone Bay + drone panel labels, codex/tutorial/dialogue copy. Internal ids (sentinel,drone-sentinel-*) stay (saves, telemetry, economy-sim data). The “Sentinel” BOSS is a different thing and keeps its name. Landed as one mapping,ShopCategories.drone_class_label(), pinned bytests/test_decoy_copy.gd. Open, Chris’s call: the Decoy shop tab now shows both “Decoy Power” (decoy-power, +15%) and “Decoy: Power” (drone-sentinel-power, +25%), two different upgrades with near-identical names. -
SHOP TIP — recommended the Bomber before a second drone slot. A frigate has 1 slot and the shop only auto-equips into an EMPTY one, so a tip follower bought a drone that stayed parked (or replaced the Decoy). Paired-seed test 2026-09-27 (autopilot, 10 seeds): flying the Bomber INSTEAD of the Decoy earned less gold on 9/10 seeds (fresh) and 8/10 (run-10 save). Tip now asks for one slot level before each new drone class (
ui/shop_advice.gdDRONE_PRIORITY). Chris agreed. ✅ DONE (this commit) -
Level-up can offer a dud “no effect” card. Seen in the ui-sweep level_up capture: “+3 Lightning, +2 Fire, +3 Kinetic — no effect” offered as the third (OK) choice — a crystal grant with nothing to feed. Wastes one of three choices. Filter no-effect grants out of
roll_upgrade_choices(or re-roll). Upgrade logic, found during the 2026-09-23 Nova UI pass; not fixed there. ✅ DONE (0424234) -
PERF/VISUAL — the shipped nebula is effectively invisible on Metal but costs a full-screen pass every frame. Its offscreen SubViewport output lands ~gamma-2.2 darker in the HDR canvas (a 0.19 blue measured 6/255), so the shipped grade (peak ~0.07) renders at ~1/255. Either make it visible (the Nova look does, with gamma compensation) or stop paying for it (shed it from L0 / disable). Measured 2026-09-23 with a layer-swap probe: nothing covers it; its own output is dark. (Nova look goal)
-
Dialogue skip-hint text is 17px (
ui/dialogue_box.gd:140) against 22-23px for the speaker/line labels — likely too small at 10-foot TV distance. Check on the TV, bump if so. (audit 09-23) -
Stat level-up cards read “0 → 3” / “Focus 0 → 0.1” with no unit or meaning — ✅ DONE (commits
60beaac,8de45a9): each card now carries one plain sentence saying what the upgrade does for you (ui/upgrade_copy.gd), and a stat card no longer shows the raw “0 → 3” line. (theupgrade_previewcontent behind the glass cards). A player can’t tell what 0.1 Focus buys. Give each stat line a unit or a plain-language effect. (liquid-glass final review 09-24) -
Scar Tissue’s “threshold” crystal rung (void 10) does nothing.
apply_modsetsdischarge_threshold = max(round(3 × 0.85), 1)= round(2.55) = 3, the base value (sim/weapon_scar_tissue.gdapply_mod), while its label says “-1 stack to discharge” (sim/upgrade_system.gdweapon_mod_label). Either make the rung really lower it (e.g. a flat -1) or relabel it. The level-up sentence already claims nothing for it. (liquid-glass Plan 1b Task 4, 09-24) -
Rift Bolt (“aim”) rung labels are wrong.
weapon_path_label/weapon_mod_labeluseweapon_mod_mag, which falls back to 1.0 for “aim” (so power reads “+0% damage”), and pierce/burst/velocity have no label case (they print “Pierce”/“Burst”/“Velocity”), while the sim appliesaim_mod_mag(Sim._apply_weapon_thresholds: ×1.5 power, +1 pierce, ×1.2 burst/velocity). Seen in card change/progress lines and the sheet’s upgrade path. The level-up sentence (UpgradeCopy) already uses the right magnitudes. (liquid-glass Plan 1b Task 4, 09-24) -
The stats sheet’s embedded weapon upgrade path ignores the card preview: with a card focused that crosses a rung, the crystal row shows “✓12” while the selected weapon’s path (
WeaponDetailView) still reads “(11/12)”. Feed the preview into the path. Liquid-glass Plan 2. (liquid-glass final review 09-24) -
✅ DONE 2026-09-23 — border was already gated (07-22); the damage-flash
_vignettequad now hides at alpha 0. Low-HP border + vignette full-screen passes never toggle.visible(fx/screen_feedback.gd) — same always-on fragment-cost class as the BUILD 175 ThreatRadar regression; apply thevisible = enabledgate. (audit 09-23, from the 07-22 ATV perf investigation) -
(refuted, audit 09-23) “NebulaBg renders ~2K on a 4K TV”: both project.godot files use
stretch/mode="canvas_items"at a 1280×720 base, sovisible_rectis ~720p logical regardless of output resolution. Don’t re-chase. -
Wyrm per-segment status DoT still multiplies the shared pool (found during the §2 AoE fix, overnight W1):
apply_status_and_decayticks burn/poison DoT per SEGMENT carrying a foreign aura, so igniting 5 segments drains the shared pool at 5× DPS. NOT the AoE-event bug (that’s fixed,dc3e739) — this is N independent statuses, and deduping needs a design call: is a burn on the worm per-creature or per-segment? Whichever way, it’s a one-bracket fix inelemental_system.gdonce decided. (2026-07-15 W1) ✅ DONE (overnight W1) — design call taken PER-CREATURE (matches the AoE fixdc3e739): a status DoT on a shared-pool creature applies ONCE perwyrm_idper tick, not per segment. Reused the exact proven mechanism — bracketed the wholeapply_status_and_decaypass inbegin_aoe_group()/end_aoe_group(), sodamage_enemy’s per-wyrm_iddedup absorbs every segment-after-the-first. 3 new TDD’d tests intests/test_wyrm.gd(failing-first proved the N× drain: a 20-seg wyrm dropped ~20× one tick, now exactly 1×): shared pool loses one burn tick not per-segment; two overlapping wyrms each burn once (per-wyrm_id); ordinary enemies still each tick (dedupe is Wyrm-pool-only). Non-wyrm (wid < 0) DoT byte-identical. DoT is deterministic (no RNG) → determinism baseline UNCHANGED (verified, no re-pin). Full ritual green: suite 2090 passing/0 failing/4 pre-existing risky, count guard 233/233, boot smoke clean, soak 8 runs 0 invariant failures/0 anomalies. -
Dash-into-solid-hazard damage bypasses
dev_invuln, so soak invuln runs can DIE and end early (found by W3’s softlock investigation):DimensionHazards.resolve_dash_into_body(dimension_hazards.gd:452) deliberately skipsis_invulnerable()(per its own comment — dash i-frames shouldn’t protect against ramming terrain, correct for real play) but that also skipsdev_invuln. AutoPlayer panic-dashes into void asteroids: in the seed-3000 repro the “invuln” pilot took four 55-dmg lumps and died at t35380 of 36000 — the soak’s invuln mode doesn’t guarantee full-window late-game coverage in solid-hazard dimensions (void/ice). Minimal fix: keep bypassing the dash i-frames but respectpilot.dev_invulnat that one call site (no rng, baseline-safe since dev_invuln is false everywhere but dev/soak). (2026-07-15, W3) ✅ DONE (overnight W6) — TDD’d exactly the proposed one-condition fix (real-play behaviour + the is_invulnerable() bypass unchanged; newtest_dash_into_ice_respects_dev_invuln, and the existing ignores-invulnerability test still passes). Ritual green: suite 232/232 scripts 0 failing, count guard, boot smoke, determinism baseline unchanged (no rng touched), default soak clean 0/0. Repro re-run (--invuln=1 --seed0=3000): the invuln pilot now survives the full 36000-tick window (previously died t35380); its only flag is the known blessed SOFTLOCK heuristic on the 3.6-min boss duel (W3 verdict / dead-trickle-adds item, pre-existing). -
PauseMenu/ShipConfigPanel don’t mark unrelated events handled at the end of
_input— residual leak paths the 07-14 main.gd modal gating didn’t cover at panel level. (07-13 c1) ✅ DONE (commitca96fe9, overnight W10) — both panels now end_inputwith a catch-allset_input_as_handled()driven by a pure staticconsumes_unmatched_event()classifier (key/joypad consumed; mouse/touch exempt so the real buttons keep their GUI-click dispatch; PauseMenu additionally exempts ESC/Start/Back — main’s stage-1 pause keys are its close path). The one REAL leak found: mid-run pause → Ship → controller Start used to reach main’s_toggle_pause(), whose_paused_for_menuclose-path resumed the sim + freed the pause menu under the still-open panel (1d8474a’s guard only covered the OPEN path) — reproduced failing-first with realpush_inputpropagation, now consumed by the panel. Full ritual green (suite 232/232 scripts 0 failing, count guard, boot smoke, determinism unchanged, ui-sweep 9/9 ok). -
DialogueBox._input has the same missing catch-all (07-13 c3 triage, not previously on this list): unmatched events fall through an open dialogue un-handled. Same fix shape as
ca96fe9(classifier + end-of-_input consume; keep touch/mouse-confirm branches intact) — but dialogue overlaps STORY mode flows, so re-check what should still pass through (main’s pause keys currently open a pause menu over story dialogue? verify intent) before copying the pattern blindly. (2026-07-15, W10) ✅ DONE (commitd8d5851, overnight W13) — ca96fe9’s shape, ShipConfigPanel’s variant (no ESC/Start/Back exemption). Story-interplay verdict: (1) pause keys do NOT open a pause menu over story dialogue and haven’t since the 07-14 gating —_toggle_pause()’s_any_content_modal_open()guard (which includesdialogue_box.is_showing()) already made ESC/Start a no-op per Decision 1 of the modal-gating spec, so consuming them at the panel changes zero behaviour (wiring test pins both halves: no pause menu AND event handled); the box never relies on main’s pause keys to close (confirm-skip + auto-advance). (2) What still passes through: mouse/touch/drag — LOAD-BEARING here, not just the Button GUI-dispatch gotcha: the main story never pauses under dialogue (main.gd’s read-freeze is tutorial-only) and iOS touch gameplay (ui/touch_controls.gd) is _input-event-driven. Key/joypad/stick gameplay is unaffected by consumption (input_router.gd POLLS the Input singleton). (3) Dialogue-advance keys verified through real root.push_input propagation, incl. a tutorial-mode test proving the read-freeze resumes after a confirm-skip; the dialogue-over-level-up overlap is safe by tree order (level_up/dimension_choice are added after dialogue_box, so they consume their own nav/confirm first). Full ritual green: suite 233/233 scripts 0 failing, count guard, boot smoke, determinism unchanged, ui-sweep 9/9 ok (rerun solo — a concurrently-killed first run correctly tripped W12’s incomplete-run guard). -
scripts/ui-sweep.shexits 0 “all ok” on a partial/emptyout/✅ DONE (W12) — the compare loop iterates only over PNGs that exist, so if the capture godot dies/is killed mid-run (seen live: an App-Napped windowed godot at ~0% CPU while the display slept) the script happily diffs whatever’s left and reports success. Add a guard: exactly the 9 expected captures present AND fresher than the run’s start time, else exit 1. (2026-07-15, W10) Fixed: requires the ‘[ui_sweep] DONE’ marker, then every blessed baseline must have a capture FRESHER than a pre-run stamp (self-maintaining from the baseline set, no hardcoded 9); missing/stale → exit 1. Both failure paths negative-tested. -
Dead
_update_joy_debug_label()+_joy_lblinmain.gd. (07-13 c1) ✅ DONE (overnight W2) — confirmed dead by whole-tree grep (_joy_lbldeclared but never assigned, so theis_instance_validguard never fired); removed; full ritual green (suite 232/232 scripts, count guard, boot smoke, determinism baseline unchanged, ui-sweep 9/9 ok). -
Co-op sequential level-up chaining (
_next_leveling_pilot) has zero test coverage. (07-13 c2) ✅ DONE (overnight W6) — 3 new headless tests intests/test_main.gd(full main.tscn boot, the file’s existing pattern):_next_leveling_pilotordering (P1-before-P2, P2-alone, none → null); the full simultaneous-level-up chain (P1 panel → pick → P2 panel with sim still frozen, per-pilot pendings spent independently, resume i-frames granted to BOTH pilots); and P1 keeping the panel across multiple pendings before P2 ever gets it. Mutation-verified (reversed pilot scan order → 14 failures). Sim-side accrual was already covered (test_coop/test_pilots_query/test_xp_levelup). The on-device half (P1 can’t hijack P2’s panel with a real 2nd controller/mouse) stays on the §3 human playtest as noted there. -
test_drone_rng_is_isolatedis tautological — never proves isolation. (07-08 c10 / 07-13 c2) ✅ DONE (overnight W6) — rewritten as a two-sim differential experiment: identically-seeded sims run the same Interceptor strikes, one critting (drone_rng draws) and one crit-0 (no draws); asserts the spawn/upgrade/player-crit streams stay draw-for-draw identical AND that drone_rng advanced by exactly one draw per crit check (exact-N resync + divergence guard). Kill verified by mutation (crit draw redirected tosim.rng→ 8 failures). Suite 232/232 0 failing + count guard green. -
CLAUDE.md “2 separate RNG streams” is stale — it’s 4 (
rng,upgrade_rng,drone_rng,player_rng). (07-13 c2) ✅ DONE (overnight W2) — verified all 4 onSim(sim/sim.gd:245-248). -
SOUND_MAP.mdmissing rows for the 7 new weapons. (07-13 c2) ✅ DONE (overnight W2) — the 7 are Chronowell/Chalice/Wildfire(×2 events)/Momentum Core/Scar Tissue(×2)/Railgun/Thunder Wake per the 07-13 review; added 12 honest rows incl. Scythe (GENERIC via Melee’sslash) + the Blaster/Aim finding below, and recomputed the snapshot line (59 DONE · 13 GENERIC · 16 SILENT). -
✅ DONE 2026-09-23 (Chris: yes) — Blaster
blaster_fire.wav(-20 LUFS, ducks with build load) + Rift Boltaim_fire.wav(-16 LUFS, never ducked); 3 ElevenLabs candidates each inaudio/candidates/, swap viatools/sound_map. ⚠️ Picked by measured attack/decay/spectrum, not by ear — Chris to audition. DECISION (Chris): give the Blaster starter + manual Aim weapon fire sounds? Neither emits any fire fx_event (weapon_blaster.gd/weapon_aim.gd), and the 07-09 batch’s fx kinds are all unmapped inSOUND_FOR_FX(unmapped = plays nothing,audio_manager.gd:195). The remaining SILENTSOUND_MAP.mdrows are a deliberate deferral (CLAUDE.md), and firing sounds for the two highest-frequency weapons are taste-sensitive (they repeat constantly) — so this is escalated as a decision, NOT auto-fixed. Why it’s escalated at all: the Aim weapon was 60% of all damage in the 07-14 soak — the game’s main damage dealers are silent. Ready path when wanted: the ElevenLabs API pipeline already used for every shipped effect (audio/SOUND_MAP.mdconventions + taxonomy suggestions already in the file). (2026-07-15, W2 while auditing SOUND_MAP; escalation phrasing per coordinator hold) -
Manual (Aim) weapon “acting weird since mouse was enabled” (Chris, 2026-07-16): root cause was the 07-14 mouse-fires-nonstop fix (
f8e2c5e) overcorrecting — its 200ms motion-recency window made mouse aim go idle ~200ms after the cursor stopped moving, i.e. the instant a player held still to keep firing at a target (the natural way to aim with a mouse), AND didn’t stop a connected controller’s incidental mouse/trackpad motion (e.g. a resting hand near a MacBook trackpad) from intermittently hijacking aim between stick bursts. ✅ DONE (2026-07-16) — replaced the time-window with (1) a permanent motion-latch (holding the cursor still now keeps firing, same as holding a stick pushed) and (2) a device-presence gate:ui/platform.gd’s newPlatform.has_mouse()(desktop OS names only —is_touch()alone can’t tell “has a mouse” from “controller-only, no mouse”, since both read false there) plusInputRouter.has_playable_joypad_connected()mean mouse aim is now NEVER consulted while any real (non-remote) controller is connected, restoring pure pre-mouse behaviour for controller players regardless of incidental cursor motion. 6 tests updated/added intests/test_input_router.gd(2 new: controller-suppresses-mouse, resumes on disconnect; 1 replaces the old window-expiry test with held-still-keeps-firing;now_msparam dropped fromset_mouse_aim_contextentirely — no longer time-based). Pure input-routing change, zero/simfiles touched —InputRouterhas no references anywhere insim//tools//scripts/, so AutoPlayer/soak never exercises this path; soak/ui-sweep skipped as genuinely N/A rather than run pro forma. Ritual: suite 233/233 scripts 0 failing, count guard OK, boot smoke clean, both determinism baselines unchanged (input-layer only, as expected). Follow-up (Chris, same day): iOS/tvOS should never even ENABLE mouse fire. Already true at consumption (Platform.has_mouse()excludes both —OS.get_name()reports plain “iOS”/ “tvOS” there, neither in the desktop-name list), butmain.gd’s_physics_processwas still computing the mouse-cursor position + vector math every frame on those platforms regardless, just to have it discarded — gated the call itself behindPlatform.has_mouse()so the work is skipped outright, not merely inert. Suite + boot smoke re-verified clean after. -
eye_laser.wavpreloaded but never played — finish or remove. (07-08 c1) ✅ DONE (commit6167268, overnight W7) — FINISHED, not removed (07-08 review already established it’s a half-built feature: bespoke asset ready, fire code falling through to the sharedbeamkind).sim/eye.gdnow emits a one-shot audio-onlyeye_laserfx kind at the telegraph→fire transition (once per volley), mapped inSOUND_FOR_FX; the per-tickbeamvisual + its hum are untouched. SOUND_MAP row GENERIC→DONE. Full ritual + soak clean. -
Boss2 + Warden missing from
bible.json(⚠️ bible re-export is UNSAFE per CLAUDE.md — hand-edit only). (07-08 c14) ✅ DONE (commita730852, overnight W7) — hand-addedwarden+sentinel(the BestiaryDB key for Boss2) with stats from the live sim consts (boss_warden.gd/boss2.gd); the codex had been falling back tobestiary.json’s stale prose stats (Warden 1500hp/armor 0 vs real 3600/6). Inert to the sim — TYPE_BOSS/TYPE_BOSS2 map to{}in_build_enemy_types; only the Eye reads bible stats at spawn. Suite + boot smoke clean. -
Bestiary lists Rusher/Zapper but SpawnTable never spawns them — wire in or shelve. (07-08 c11) ✅ DONE (overnight W3, verdict-only, no code) — investigated, split verdict; nothing shelved, nothing wired (feature freeze).
- Rusher: REACHABLE — the item is a FALSE ALARM for Rusher. SpawnTable/SpawnDirector deliberately
exclude it (SpawnTable’s own comment;
pick_type/pick_boss_add_typeprovably never return it), BUT FunZo summons 3 Rushers at its 75%-HP threshold (sim/funzo.gd:80FUNZO_THRESHOLD_SUMMONS["75"]→check_threshold_summons→funzo.gd:239_spawn_one(TYPE_RUSHER)). FunZo is a live survival boss (Home rotation slot 1,sim.gd:1909; + the Poison dimension boss,sim.gd:1934), so a real fight spawns Rushers. Rusher is genuinely in the game — do NOT remove its bestiary entry. - Zapper: UNREACHABLE by any gameplay path, but NOT a dangling/broken entry → left as-is (no shelve).
Checked every path: primary
SpawnTable(excluded,spawn_table.gd:8-9), secondarySpawnDirector(pick_typefalls through to SWARMER;pick_boss_add_type= [SWARMER,SPIDER] only), all per-dimension rosters (sim/area_defs.gd— grep: zero rusher/zapper, and_remap_to_dimensioncan only map INTO the roster, never into an off-roster type), story mode (story_director.gdADD_POOL/RANDOM_POOL+ every bible-authored encounter — none list zapper), and all boss summons (only FunZo summons, Rusher not Zapper). Zapper’s ONLY reachable path is dev tooling (_ENEMY_NAME_TO_TID→dev_spawn_custom, the Remote Console “Spawn Enemy” row). It is a fully-implemented, test-covered, dev-spawnable, codex-listed enemy deliberately held out of the auto-spawn pool (intentional 2026-07 “Crystal Endless” balance removal — pinned bytest_crystal_endless_tuning.test_zapper_and_rusher_not_spawnedandtest_new_enemies.test_zapper_no_longer_spawned, which assert it STAYS out). So there is nothing dangling to strip and nothing to mark “unimplemented” — it works, it’s just not in the random roster by design. Wiring it into spawns is a feature (frozen); no change made. Doc-only close. (2026-07-15, W3)
- Rusher: REACHABLE — the item is a FALSE ALARM for Rusher. SpawnTable/SpawnDirector deliberately
exclude it (SpawnTable’s own comment;
-
Railgun fx_event kind
"lance"collides with the pre-existinglanceshape constant. (07-13 c2) ✅ DONE (commit90c80a2, overnight W7) — renamed torailgun_lanceat the sim emit site + both FxManager consumers (ESSENTIAL_KINDS + the shared beam-flash branch) + test + SOUND_MAP row; whole-tree grep confirms no other consumer of the exact string (audio never mapped it). Pure string rename, no RNG: determinism unchanged-green, full ritual clean. -
Per-frame alloc trims:
active_weapon_views(),elite_render_info(),ArchetypeRenderer.sync()(fresh arrays + doubleshape_for()),_enemy_colors()/_proj_colors()PackedColorArray. (07-08/07-13 perf) ✅ DONE (overnight W11; code landed inside commit6949675— W10’s docs commit swept W11’s already-staged files off the shared index in a concurrent-commit race, so there is no separate W11-labelled commit; content + verification are W11’s, main confirmed green as-pushed by both) — measured all 5 sites first (headless micro-bench, full main.tscn boot, 140 enemies/8 elites/80 proj/40 eproj). Trimmed the two that matter:_enemy_colors138→~85 µs/frame andArchetypeRenderer.sync119→~74 µs (colour+sync path 256→169 µs, −34%) via reused member PackedColorArray/scratch buffers (CoW gotcha: an alias detaches — index the member directly), a precomputed_element_color_vividLUT (preserves the shipped double-vivid exactly), singleshape_for()per enemy, per-frame (not per-swarmer)AreaDefs.get_def, and zero-copy packed-array locals;_proj_colors/_enemy_proj_colorsgot the buffer reuse too (28.6→22.5 µs / 11.5→11 µs). SKIPPED as measured-negligible:active_weapon_views~4 µs andelite_render_info~13 µs per frame (<0.1% of a 20 ms ATV budget even at 3-5× A12 scaling). Behaviour-identical (3 new buffer-reuse/LUT-parity tests + a shrinking-pool sync test); ritual green: suite 233 scripts 0 failing, count guard, boot smoke, determinism unchanged (no re-pin, render-only), ui-sweep 9/9 ok FOREGROUND, soak 0/0. Technique note added todocs/architecture/rendering-and-performance.md. -
main.gd._enemy_scales()is dead code — zero callers ✅ DONE (W12) — removed along with the now-orphanedENEMY_MESH_RADIUSconst (last user);archetype_renderer.gd’s BASE_RADIUS comment updated to stand alone (verified truthful vs sync() line 516). (found by W11’s alloc-trim measurement pass): enemies moved toArchetypeRenderer, which computes scale fromradius/BASE_RADIUSinternally, so the old SwarmRenderer-era scales builder (incl. its elite-breathe throb) is orphaned. Confirm the elite breathe effect isn’t meant to return, then delete. (2026-07-15, W11) -
HUD wave/boss banner may collide with boss name label on 720p TV. (07-08 c2)
-
Polish batch (Chris, 2026-07-16): app icon, low-HP screen effect, carousel-everywhere consistency, manual weapon visuals/upgrades. ✅ DONE — 3 commits (
3b6a29b,ee180d3,1d004ed):- Low-HP screen border — was a hard on/off snap at 30% HP; now a continuous ramp from
50% HP (
ScreenFeedback.set_hp_fraction), ease-in intensity + colour desaturation, so it starts subtle and escalates toward 0 HP instead of jumping straight to ~full glow. - Manual (Aim) weapon visuals — flat single-colour bolt → tail-to-nose gradient shader,
driven by a new
WeaponAim.visual_tier()that ramps as its 4 threshold mods land; modestly thinner (5.0, was 6.0 — a full fix needs aproj_rendererper-instance scale that doesn’t fight bullet-shape orientation, documented in-code, out of scope). Plus a real evolution, “Railspike” (void≥30: ×1.5 damage, +2 pierce, +30% velocity) — aim was the only weapon with no evolve() at all. - Carousel-everywhere — Dimension Choice popup + the death Results screen’s 3 actions
converted from plain button lists to the established single-card slide+crossfade carousel
(
ship_config_panel.gd’s pattern). Scoped to genuine “pick 1 of N” pickers only (Pause Menu/Debug Settings/Drone Bay/Codex are config/browse screens, not choice pickers — Chris’s own scoping call); Victory’s single “Climb again” button stays a plain button. - App icon ✅ WIRED IN (2026-07-16):
platform/tvos/icon.pngreplaced — was a generic holographic emblem, not the ship at all.tools/ship_preview’shero3dbake mode was extended with aSHIP_TIER/ICON_ZOOMenv-var path so it renders the ACTUAL starter hull (tier-0 “cobalt” outline) for reference, then Gemini web generated the final icon from that reference (concept-art/gemini/icon-gen-1.png, cyan neon triangular ship + violet radial glow) — SynthID watermark removed via a feathered blur-inpaint (icon-gen-1-clean.png), resized to 1024×1024 and flattened to RGB with no alpha (Apple requirement). Old icon backed up atconcept-art/gemini/backup/icon-old-generic-emblem.png. Boot smoke clean on the tvOS project after the swap. - Player ship accent orbs REMOVED (2026-07-16, Chris: “remove the little glowing orbs
that appear round the ship as it upgrades, we need a different way to show upgrade”).
PlayerRenderer’s orbiting accent-orb system (0/0/2/4/6 orbs across the 5 level tiers) is gone entirely — sinceUSE_BAKED_HULLbypasses the procedural per-tier silhouette, these orbs were actually the ONLY thing that grew with tier on top of the halo/thruster recolor. Tier progression still reads via: halo size+hue (cyan→teal→azure→violet→gold), thruster recolor, and the one-shot ascension flash on tier-up — all untouched. DECISION (Chris): a replacement upgrade-tier indicator is wanted but not yet designed — see the options below. - Ritual: suite green throughout (234 scripts, 0 failing at final HEAD), count guard OK, boot
smoke clean, both determinism baselines unchanged, soak clean. ui-sweep: manually verified
both converted panels via a targeted capture (the full 9-screen sweep reliably stalls
between start_menu/ship_config in this session’s environment — confirmed unrelated to this
change, reproduced across multiple retries);
dimension_choice.png’s blessed baseline needs re-blessing once the sweep can run to completion (expected — the screen looks different now). - ⚠️ Owed: Chris’s on-device look at all of this (mouse/controller/TV) — none of it can be fully judged from static captures; his pick on the icon candidate before it’s wired in.
- Low-HP screen border — was a hard on/off snap at 30% HP; now a continuous ramp from
50% HP (
-
Thruster scaled too big from level 1; enemies could get permanently stranded outside the arena (Chris, 2026-07-16): “the main thruster is too strong to begin with it should only get this big when it is fully upgrade[d]” + “make it so no enemies ever stay alive outside of the area too long… make it so they always come to the arena after not too long.” ✅ DONE:
- Thruster tier-scaling —
PlayerRenderer’s tier multiplier was a flat0.8 + 0.12*tier(a 1.6× range top-to-bottom, so even a fresh tier-0 ship already read as “big”). Replaced withTHRUSTER_TIER_MULT := [0.40, 0.60, 0.80, 1.05, 1.28](tier4 unchanged — the “fully upgraded” look is preserved exactly — tier0 cut to 0.40, a 3.2× range). New test (test_thruster_starts_small_and_only_reaches_full_size_when_fully_upgraded) pins both ends. - Enemy arena leash — enemies had NO equivalent of the pilot’s
PlayerState.clamp_arena; whatever pushed one out (knockback, a boss field, a behavior with no pull back toward the player) could strand it beyond reach forever (“we ca[n]’t kill them”). New universal safety net inSim._recall_stray_enemies(called every tick, survival-only): any enemy sitting continuously beyondARENA_HALF + STRAY_MARGIN(250px — comfortably past the 150pxSPAWN_BORDER_MARGINso a fresh ring-spawn is never flagged) forSTRAY_RECALL_TIME(8s) gets axis-clamped straight back inside±ARENA_HALF(same technique as the pilot clamp — no rng draw, so determinism is unaffected by construction). New per-enemyEnemyPool.stray_timercolumn (swap-removed like every other status column). Gated off in story mode (room walls define its bounds, not the survival arena box). 4 new tests (tests/test_enemy_arena_recall.gd): far-stranded gets recalled after the timeout, just-past the margin is NOT recalled prematurely, inside the margin is never touched, story mode is untouched. - Ritual: suite green (235 scripts, 0 failing — 4 pre-existing risky/pending, unrelated), count
guard OK, boot smoke clean, both determinism baselines unchanged, soak (
--runs=6 --ticks=6000 --ships=cobalt,obsidian --coop=both --invuln=1) 24 runs / 0 invariant failures / 0 anomalies. - ⚠️ Owed: on-device confirmation the recall doesn’t read as a visible “pop” for a nearby player watching it happen — untested by design (no visual telegraph added; teleport-style fixes to a rare edge case were judged not worth extra VFX scope for 0.1).
- Thruster tier-scaling —
-
bible.jsonevolutions array empty while 5+ weapons declare evolution names — verify whether player-visible, then fill or strip. (07-13 c2) ✅ DONE (commitc13007d, overnight W7) — verdict: NOT player-visible, STRIPPED. ContentLoader/ContentDB never read the weapons’evolutionfield or theevolutionscategory (zero consumers in content/sim/ui/main.gd); the game’s real evolution source of truth isWeaponEvolutions.TABLE+ each weapon’sevolve(). The 5 names were a partial drifting duplicate (4 matched TABLE, blaster’s “Auto-Cannon” named an evolution TABLE lacks — new finding below). Blanked to""matching the other 11 weapons. -
Orbit crystals threshold array not strictly increasing (readability nit). (07-08 c16) ✅ DONE (commit
7ba0ee5, overnight W7) — rungs reordered to 5/9/12/18; behaviour-identical (same rule set, idempotent per-rule grants, commuting orbit mods on distinct fields, per-element order unchanged — full proof in the commit message). New guard test pins ascending amounts (array-order + per-element) for EVERY weapon in the TABLE. Full ritual + soak clean. -
data/bible.jsonfunzo/graviton stats stale vs live sim consts (found by W7’s Boss2/Warden bible work): funzo bible hp 720 / radius 55 / contact 20 vs liveFUNZO_HP4000 / 85 / 35; graviton bible hp 780 vs liveGRAVITON_HP4500 — Toby’s 07-08 4× boss-HP rebalance never reached the bible, so the codex shows those stale numbers (BestiaryLoader prefers bible stats). Codex-only wrongness: FunZo/Graviton spawn from their consts; only The Eye reads bible at spawn (its 3000 IS live). Hand-edit fix, same pattern asa730852. (2026-07-15, W7) ✅ DONE (commitb2a7deb, overnight W9) — hand-edited exactly the 4 stale values (funzo hp/radius/contact fromFUNZO_HP/FUNZO_RADIUS/FUNZO_CONTACT_DMG, graviton hp fromGRAVITON_HP; every other field already matched its const). Re-verified sim inertness (_build_enemy_typesmaps both TYPE_* to{}, sim.gd:680-681). Full ritual green. -
Boss2 has TWO player-facing names: the HUD boss bar + edge indicator call it “OVERSEER” (
main.gd:1357) while the codex/bestiary calls it “Sentinel” (bestiary.json, BestiaryDB key) — and “Sentinel” ALSO collides with the Sentinel drone class in the shop/drone-bay UI. Pick one name (Chris’s call); one-line fix either side once decided. (2026-07-15, W7) ✅ RESOLVED (2026-07-16, Chris’s call): hidden, not renamed. Rather than pick a name, Boss2 is removed from play entirely —Sim.BOSS2_ENABLED := falsegates its one live auto-spawn trigger (the Aurora/Ember Reach mid-wave elite atELITE_BOSS2_TIME; the real Home/Dimension boss rotations never included it, and Aurora/Ember Reach aren’t offered by the real dimension-choice popup — only reachable via dev tooling). HUD label swapped “OVERSEER”→neutral “BOSS2” placeholder (only ever seen via a deliberate dev-console spawn); codex key blanked (content/bestiary_db.gdTYPE_KEYS[15]) so “Sentinel” no longer collides with the shop’s Sentinel drone class. Code kept fully intact (boss2.gd,boss2_director,BossState, Remote Console “boss2” dev-spawn) for possible future reactivation — flip the flag + pick one final name to bring it back. 4 tests updated/added (2 flipped assertions intest_elites.gd/test_dimensions.gd, codex-exclusion pin intest_bestiary_loader.gd, 2 hardcoded-count fixes intest_bestiary_loader.gd/test_codex_overlay.gd). Full ritual green: suite 233/233 scripts 0 failing, count guard OK, boot smoke clean, determinism baselines UNCHANGED (single-pilot4217109746/3312602286, 2-pilot370166760/2437492589— the trigger was outside both pinned windows regardless), soak 8 runs 0 invariant failures/anomalies; ui-sweep 9/9 clean (max RMSE 0.048, threshold 0.06; the sweep’s own process hung after writing all 9 captures — likely App Nap despite running foreground, the same class of issue W12 built the DONE-marker guard for — so the automated gate correctly refused to pass; verified manually instead with the samemagick comparecommand/threshold against the on-disk captures, which were confirmed fresh from this run’s start). -
WeaponEvolutions.TABLEmissing theblasterrow —weapon_blaster.gd:73evolve()is “Auto-Cannon” (per its comment) and blaster HAS an evolve rung inWeaponThresholds, butevolved_name("blaster")falls back to capitalize → level-up/crystal previews show “EVOLVE → Blaster” instead of “Auto-Cannon”. One table row + desc. (2026-07-15, W7) ✅ DONE (commit1894b20, overnight W9) — row added with a desc truthful toevolve()(+1 shot · 30% faster fire · ×1.4 damage); regression test pins “Auto-Cannon” viaupgrade_choice_display, plus a completeness guard (every evolve threshold rung must have a TABLE row + non-empty desc, all 16 weapons) so the fallback-name bug class can’t recur. Full ritual green (suite 232/232 scripts 0 failing, count guard, boot smoke, determinism unchanged). -
Ship Tilt debug: switching modes can leave a stale shader uniform / incompatible BAKED_FLIPBOOK state (dev-only — moot once the tilt winner is picked). (07-08/07-13)
-
docs-site/**/*.importgitignore rule for editor-generated noise. (NEXT_TASKS 07-05) ✅ ALREADY DONE — landed 2026-07-05 in commitd02b41a(rule present at.gitignore:11, zero docs-site.importfiles tracked); the NEXT_TASKS entry was stale and is now marked resolved. -
Frigate-vs-cruiser balance: Obsidian is near strict-upgrade (+70% HP, +67% slots, +10 armor for −19% speed) — decide if a frigate niche buff ships in 0.1 or waits. (07-08 c13)
Adversarial code audit — 2026-07-23 (37-agent workflow, 29 candidates → 20 verified)
Section titled “Adversarial code audit — 2026-07-23 (37-agent workflow, 29 candidates → 20 verified)”Full run: 7 perspective finders (correctness/determinism/perf/architecture/dead-code/silent-fail/test-gaps) → each finding adversarially refuted before it survived. Headline result: zero determinism breaks, zero sim-purity violations, zero mid-loop pool mutations, zero mis-routed RNG streams — the load-bearing invariants hold by construction. Findings below, most-severe first.
- HIGH — Wyrm kills award ~0 XP (reward paid on a biomass-0 follower, not the head) —
sim/sim.gd:_sweep_dead._spawn_wyrm/_spawn_story_wyrmpark the whole worm’s biomass on the HEAD (biomass_v if s == 0 else 0);_sweep_deaditerates DESCENDING, so the once-per-worm reward gate fires on the first-swept (high-index) follower →_xp_worthreturns 0 →_award_xpearly-returns →paid_wyrms[wid]locks → head never pays. Every Wyrm (incl. Elite, biomass 80) yielded ~nothing. Kills/gold unaffected (flat per-kill constants), which hid it behind the kill-economy test. A regression from the 2026-07-12 “biomass too high” fix. ✅ DONE (2026-07-23) — new_wyrm_biomass: Dictionary(wid→total) recorded at both spawn sites, awarded once in the reward gate in O(1) regardless of which segment index the gate lands on, pruned on payment. 2 TDD’d tests intest_wyrm.gd(failing-first proved 0 XP; incl. a head-not-at-index-0 swap-remove-churn case). Determinism baselines UNCHANGED (no re-pin — no wyrm in the 10s window; dict is deterministic derived state, out of checksum). Full suite green 244/244, count guard OK, boot smoke clean. - MED —
damage_enemyscans the whole enemy pool per single-target Wyrm hit —sim/elemental_system.gd:~195. The shared-HP mirror loop isfor i in range(sim.enemies.count); the AoE dedup only collapses multi-target loops, so each landing projectile pays O(enemy_count). Fix: awyrm_id → segment-indexmap (see theme below), iterate only that worm’s segments. Determinism-safe (identical values, order-independent). (Part of the Wyrm-model theme — one refactor closes this + the two Low AoE gaps + gives the reward gate a canonical head lookup.) - MED —
ContentLoader.validate()ref-checks weapon elements but NOT enemy elements —content/content_loader.gd:~62. A typo ("electric"vs"electricity") passes validation, degrades to-1at runtime → enemy renders neutral, no auras/reactions, no error logged. Latent authoring trap (all 30 enemies valid today). Higher impact than it looks because per-enemycoloris dead, so element is the render colour. Fix: extendvalidate()to ref-checkenemies[].element(+ dimension overrides / reaction ids), same fail-loud path weapons get. - MED — co-op per-pilot upgrade test asserts counters, not cross-pilot isolation —
test_p2_levels_up_independently_of_p1checkspending_levelupscounts only, never that P1 applying an upgrade left P2’sPlayerState/arsenal byte-identical. The per-pilot refactor threadedpilotthrough ~17 fns — the highest-risk place for a leak. Fix: snapshot P2, apply several distinct StatEffects upgrades to P1, assert P2 unchanged (symmetric). Restrict to StatEffects —sim.modsis one sharedModState, mutates party-wide by design. - LOW (correctness) — Wyrm AoE-dedup gaps (2): manual-weapon impact burst lands a second
half-hit on a same-worm segment (net 1.5× on wyrms) —
elemental_system.gd:~107, direct hit runs beforebegin_aoe_group(); and the Sentinel reflect loop isn’t deduped —drone_director.gd:~361, N contacting segments each reflect into the shared pool. Both fix by matching the establishedbegin/end_aoe_group()bracketing. - LOW (perf, render-only) —
ThreatRadar.select_top_kallocates a lambda +sort_customper qualifying enemy —ui/threat_radar.gd:~116, every frame before the visibility gate. Fix: track weakest-index + O(k) rescan after a replacement; hoist the comparator. - LOW (perf, render-only) — per-enemy
tid in _NO_LIVE_TINT_TYPESlinear scan —main.gd:~1725, O(6) per enemy per frame. Fix: const Dictionary +.has(tid). - LOW (dead code, all CONFIRMED safe deletes):
_spawn_accum(sim/sim.gd:~495) and_swarm_burst_fired(sim/sim.gd:~592, its “3:00 crescendo guard” comment actively lies) — both declared, never read;upgrade_choice_display()(sim/upgrade_system.gd:~348) production-dead (only 3 tests call it);HOT_CORE_HEAT(render/archetype_renderer.gd:~143) orphaned copy-paste;bible.jsonper-enemycolordead for 24/25 types (enemies render by element — collapse the table to the one live Skirmisher lookup + strip the fields). (NOTE:upgrade_choice_displayis used by the W9 evolve-name regression test above — route it or keep, don’t blind-delete.) - LOW (structure) — persistent-singleton preservation list duplicated verbatim across
_new_runand_return_to_menu—main.gd:~583. Two identical 16-node guard-chains; adding a singleton and updating one pathqueue_free()s a still-referenced node. Fix: one_persistent_children()source both sweeps filter against. (CLAUDE.md rule 10 hazard.) - LOW (test-gap, defensive — code correct today): pin
_roll_dimension_choiceRNG-stream isolation + same-seed reproducibility (sim/sim.gd:~2583, mirrortest_player_crit); pinTargeting.nearest_enemy_indextie-break + empty-pool-1(sim/targeting.gd:~9).
Cross-cutting themes (structural fixes worth more than the sum of the one-offs):
- Wyrm’s shared-HP model is a recurring hazard — the XP bug (fixed), both AoE-dedup gaps, and
the O(n) mirror scan all trace to “many segments, one pool.” A single
wyrm_id → segment-indexlist maintained at spawn/despawn structurally fixes the scan AND gives dedup/reward a canonical “these are the worm’s segments (+ head)” lookup — collapses 4 findings into one refactor. - Fail-loud validation is uneven — weapons get element ref-checks; enemies/reactions/dimension
overrides don’t. Cover every
element_index()consumer (structure over “spell it right”). upgrade_rngis drawn both in-tick (_roll_dimension_choice) and render-side (roll_upgrade_choicesfrom main.gd) — harmless today (one sim instance), but a real latent snag against the netcode-ready keystone. Give the dimension roll its own in-tick stream BEFORE the networked-MP merge, not after.
5 · Post-0.1 (parked; do NOT pull into the RC)
Section titled “5 · Post-0.1 (parked; do NOT pull into the RC)”- Networked MP merge (M-B/M-C worktree) + re-add iOS
NSLocalNetworkUsageDescription. - Dimension-choice popup: the 4 open design questions in the 2026-07-09 spec.
- Bespoke elite powers for the ~12 promotable enemy types.
- Normal-mapped 2D hull lighting (
hero_lights.gddeferred path). - Boss-intro / elite-spawn screen-space announcement effect. (07-13 c2)
- Graphics ideas batch: XP-gem comet streak, per-tier level-up motion signature, per-hull thruster tint, GravityLens BackBufferCopy downres. (07-13 c2)
- NebulaBg → QualityManager ladder wiring (moot while the ladder is off; revisit WITH the ADAPTIVE_QUALITY re-enable gate). (07-08, resurfaced 4×)
- Patched Godot 4.7 export-template build (exit-134 teardown fix) — deliberate standalone effort, Chris’s call. (NEXT_TASKS 07-05)
- Old separate overnight-review docs’ remaining graphics/idea inventory (see those files).
6 · Fixed-since-review reconciliation (so nobody re-chases these)
Section titled “6 · Fixed-since-review reconciliation (so nobody re-chases these)”Items the 07-08/07-13 review docs list that are ALREADY closed on main: crit upgrade
implemented for real (sim/player_crit.gd, 07-09); Centurion bestiary ??? (07-09); Boss2
enrage telegraph (07-09); renderer queue_redraw() waste (07-09); orphaned
maybe_spawn_survival_boss (07-09); co-op P1-only damage/hazard hit-checks incl. Graviton/Eye
(07-09); Wyrm 20× kill payout (4319812); Disruptor slow inert (760b72d); Halo Chain buff
inert (5ca07d4); co-op weapon/drone element attribution (16f36a6/f869bd9); modal
input-leak family (8a95e7b/1d8474a); mouse-aim fires-nonstop (f8e2c5e); docs-site
frontmatter build break (9c1f676).