Skip to content

1.0.1 — work queue

CLOSED 2026-10-14: iOS + tvOS 1.0.1 (store 255) approved and live. The next store version is 1.1; its queue is docs/RELEASE-1.1.md. Move items from here to there, do not tick them here.

1.0 (store build 37 = Sim_Const.BUILD 206, commit 56e1d13) went to App Store review on 2026-09-28. v0.1 became 1.0, so docs/RELEASE-0.1.md is closed as a work queue; this file is the queue for the next store version. Anything anyone finds goes here first; tick items ✅ DONE (commit) in place.

  • Version string is 1.0.1, not 1.01 — Apple compares dot-separated integers, so 1.01 == 1.1.
  • Build numbering (Chris, 2026-09-28): one number everywhere. The next store upload uses store build = Sim_Const.BUILD (so 207, not 38); scripts/release/release.sh does this now.
  • Dev / beta / store pipeline — ✅ DONE (build-channels branch; Worker deploy + D1 migration
    • device install pending Chris) — spec docs/superpowers/specs/2026-09-28-build-channels-pipeline-design.md (separate Dev app, unified numbering, store-N tags, channel field, dev flags out of main.gd). ⚠️ Deploy order: apply the 10 ALTER TABLEs (telemetry/schema.sql, 2026-09-28 block) to live D1 first, verify with PRAGMA table_info(<t>) on all 5 tables (samples, runs, encounters, feedback, hitches), THEN wrangler deploy. Worker-before-migration makes every INSERT fail (no such column) and drops live 1.0 telemetry.

Status: SUBMITTED 2026-10-04 (store build 255, iOS + tvOS, WAITING_FOR_REVIEW)

Section titled “Status: SUBMITTED 2026-10-04 (store build 255, iOS + tvOS, WAITING_FOR_REVIEW)”

Carry into 1.0.2 (the iOS listing went in half-updated, Chris chose not to pull it again):

  • Upload marketing/appstore/ipad_v2/ (8 screenshots) to the iOS iPad set; iOS 1.0.1 still shows the 6 old 24-Sep shots.
  • Upload marketing/appstore/previews_v2/iphone/ + previews_v2/ipad/ (gitignored; rebuild commands in marketing/appstore/SUBMIT.md). iOS 1.0.1 has no previews. tvOS 1.0.1 has all 3.
  • Write the iOS What’s New (1.0.1 was the first iOS release, so it had none).
  • ✅ DONE (fb/stats-sheet-fit; adaptive crystal row pitch with a legibility floor, right-stick scroll + d-pad follow, preview-changed rows sort first, rung labels clear of counts, stale Pickup stat removed; fit test over 5 loadouts x 3 devices x both hosts; needs Chris’s ATV check with a pad) Stats sheet on tvOS: text clipped at the box edges, scroll columns don’t scroll, text too big for its boxes (Chris voice feedback, D1, 2026-10-11, BUILD 297 ATV). This is the TV-legibility verdict owed on the stats-sheet-nav merge (4a4a3cd3), and it fails. ui/stats_sheet.gd. Fix + a fit test driven at the TV (1280) AND iPad (985) / iPhone (1204) ShopLayoutFixture profiles, asserting text rects stay inside their boxes and the columns scroll by focus/d-pad.

  • ✅ DONE (fb/knockback-clamp; knockback can no longer carry an enemy past ±ARENA_HALF in survival, story excluded because its rooms exceed the arena; still unclamped: enemies idling/kiting 0-250 px past the edge before the leash) Rift Bolt knockback pushes enemies outside the arena (Chris voice feedback, D1, 2026-10-11, BUILD 297 ATV). sim/weapon_aim.gd DEF_KNOCKBACK 260 + the knockback mod. Clamp knocked-back enemies to the arena bounds (determinism-sensitive: sim change).

  • BLOCKS THE NEXT STORE UPLOAD: controller telemetry is a new KIND of data; privacy page + ASC label not updated (2026-10-07 perf/controller workflow). PadLiveness now sends pad_conn / input_gap / input_resume (rough pad type, slot, counts; never the Bluetooth name) and marketing/appstore/compliance.md widened “Other Diagnostic Data” to all platforms. Still to do: the public privacy page in ~/Claude/bullet-heaven-site/privacy/ (separate repo, deploy via its scripts/deploy-site.sh) and re-tick the ASC label for tvOS/macOS. (2026-10-11: the ASC App Privacy label is per APP, not per platform, and “Other Diagnostic Data” was already ticked for MetricKit, so the label likely needs no change, only Chris confirming the tick in ASC > App Privacy; ✅ privacy page DONE 2026-10-11: “Game controllers” bullet live, site commit bcea2eb, verified on darkcosmos.lumara.digital/privacy/.) 📍 Only left: Chris confirms the Other Diagnostic Data tick in ASC > App Privacy. Merge is fine; the store upload is not until both are done.

  • Verify on device: the forced-tutorial boot prewarm + 6 new PrewarmPass families (merged 2026-10-07). Fresh Dev install with the tutorial unfinished on the ATV and iPhone, then SELECT build,dev,frame_ms,pipelines,run_time FROM hitches WHERE pipelines>0 AND run_time<1 AND build>=N must return zero rows (it was 2.0-3.3 s / 12-13 pipelines on 268/271). Also eyeball the boot cover (“DARK COSMOS” on a NeonBackdrop + GlassPanel) and the start-menu stutter the extra families may add (ScreenFeedback 65k set_pixel vignette + full ArchetypeRenderer now build as the menu appears, unmeasured). Then skill bh-fresh-install-stall-check: fresh-install stall seconds were 7.9 s on 260/262/264 and the metallib bake changed nothing, cause not yet found. Findings: docs/research/2026-10-07-perf-and-controller-workflow-audit.md.

  • BLOCKS THE NEXT STORE UPLOAD containing the HDR/SDR colour-profile work: on-device checks not yet run (2026-10-09, hdr-colour-spec final review I1). (a) iPhone: HDR engages and the active colour profile follows it (plan Task 8, docs/superpowers/plans/2026-10-09-hdr-sdr-colour-systems-step1.md). (b) Fresh-install shader-stall check (skill bh-fresh-install-stall-check) on Apple TV AND iPhone: the new border_halo global shader uniform (declared in [shader_globals]) was never verified through the export shader baker.

  • Controller “stops responding”: repro + read the new telemetry (2026-10-07). Two 30-min Nimbus sessions on the ATV on the next Dev build, noting wall-clock times of each dead moment, then join against events types pad_conn / input_gap / background and hitches (skill bh-telemetry). Already shipped: auto-pause when the app resigns active mid-fight (D1 showed 2-25 s frozen background/foreground pairs in live play, one 24 s on 273, then the player dropped straight back in). Still open from the audit: Survival runs start in attract autopilot and ignore RB/LB until the stick moves (round 2 in flight); co-op pilot<->pad mapping is connection order so a reconnect swaps/orphans pilots; GCSupportsControllerUserInteraction=true in the tvOS plist as a possible overlay trigger (unverified).

  • Webs (Scar Tissue v2) are a measured fps cost on the ATV at equal enemy counts (BUILD 273). Auto session 9874f3: webs 0 -> 59.6 fps, 1-49 -> 56.7, >=50 (avg 149) -> 48.2; human 142ad2 webs >=50 (avg 66) -> 53.8. Isolate per web with tools/perf_probe/look_bench.gd on device, then cull off-screen webs / merge into the web MultiMesh layer / cheaper tier at webs>=50. Gate: an ATV session with webs>=50 holds >=58 fps. ✅ DONE in code (fix2/webs-render-cost merged 2026-10-08, 815d4d29/01d7150a): at 50+ webs on screen the web look drops to end rings + ring-weight side lines (pixel-identical below 50; M5 GPU 0.76x at 150 webs, 0.53x at 400), A/B flag webs_lite in Debug Settings + console, threshold/hysteresis tunables (401 = never), PrewarmPass family 26. Still to verify on the ATV: D1 samples WHERE build >= <first build with it> AND webs >= 50, expect avg fps >= 58; and Chris eyeballs the no-fill corridor look on TV.

  • Mid-run 0.9-1.3 s stalls on the ATV are NOT script time (proc_ms 6-90 ms vs frame_ms ~1000; tight 940-1050 ms cluster at ~200 enemies on 262/264, ~1 per hour since 268; looks like a Metal nextDrawable timeout, not shader compile). Needs drawable-acquire / GPU time in the fork’s hitch payload to split GPU-bound from present-blocked. The GRAVITON / BOSS DOWN ~1 s rows are NOT explained by the unwarmed screen passes (skeptic-refuted); probe pipeline counts before adding families.

  • ✅ DONE (verified 2026-10-11: deploy.sh --check-only green, Worker deployed 2026-10-09 09:03 UTC after the last telemetry/src commit 96c139e3, live samples rows carry frozen_reason gameover/levelup/pause/…) Frozen-state telemetry needs its 5 D1 ALTERs applied, THEN telemetry/deploy.sh (Chris’s OK; production). Migration telemetry/migrations/2026-10-07-frozen-state.sql. Worker-before-migration makes every sample INSERT fail. Until deployed, the new frozen/reason fields are dropped by the old Worker (harmless).

  • ✅ DONE (lan-jitter) LAN co-op client stalls often over Wi-Fi: the jitter buffer in the spec was never built (2026-10-07, Dev 271, Mac client → ATV host). Chris: “worked ok but quite a lot of stalls”. A headless Mac client over the same Wi-Fi stalled 108 of 600 frames (~18%); the Mac → ATV ping is ~17 ms, typical Wi-Fi clumping. Cause: LockstepSession.step() (client) ticks the moment ONE row is queued and only catches up (2 ticks/frame) above CATCHUP_QUEUE 3, so every late packet is a visible freeze. Spec docs/superpowers/specs/2026-10-07-networked-coop-design.md §4 “Client tick pacing” says the client keeps a 1–2 row jitter buffer. Fix: prefill ~2-3 rows before the first tick, aim to hold that cushion (catch up above it, wait only below it), test it with LoopbackTransport injected jitter (stall count must drop), and report stalls in telemetry so device runs can be compared. No netcode library: netfox / godot-rollback-netcode were evaluated in that spec §6 and rejected (they assume Node-property state, not our PackedArray sim).

  • iPhone JOIN list stays empty while the Mac lists the ATV (2026-10-07, Dev 271). The ATV registered fine (code VGE3Y, net key = this Wi-Fi’s public IPv4, no IPv6 on the network). The Mac also hears the UDP beacon, so its row does not prove the registry path; iOS only gets the registry. Check: is the iPhone on the same Wi-Fi (not cellular)? Does typing the code join? If yes, debug /mp/nearby from iOS (LanRegistry.start_nearby).

  • ✅ DONE (20782220) LAN join on iOS fails as a bare “Could not join: host_timeout” when Local Network access is off (2026-10-07, Dev 271). iPhone → ATV host timed out on every path; the host was registered (code ESBEW, 192.168.68.123 reachable from the Mac, a headless Mac --mp-join connected) and the join worked as soon as Chris enabled Settings → Privacy & Security → Local Network → Dark Cosmos Dev. iOS silently drops the UDP, so the player gets a timeout with no hint. Fix: on an iOS client host_timeout (all IPs tried), show “Allow Local Network for Dark Cosmos in Settings → Privacy & Security” in the lobby status, and consider a pre-join local-network probe to trigger the system prompt. Also seen: the ATV advertises a dead second IP (10.198.61.241), which costs one extra timeout before the real one when order_ips ranks it first.

  • ✅ DONE (521fdee2) Button prompts show Xbox colours on non-Xbox pads (Chris, 2026-10-04, SteelSeries Nimbus: “the colours dont match the controller”). ButtonPrompt.style_for_name() only recognises PlayStation names; every other pad falls through to "xbox" (green A / red B / blue X / yellow Y discs). Fix: match Xbox by name, and give every other pad (Nimbus/MFi, 8BitDo, Switch Pro, unknown) a neutral style, with the same letters on a plain disc in UI tokens. Device check needed: the exact Input.get_joy_name() string for the Nimbus on tvOS/iOS/macOS. Fixed: Xbox matched by “xbox”/“microsoft”/“xinput”, every other pad gets the new "neutral" style (token disc, accent rim, L1/R1 bumpers). Pad name NOT confirmed on device: the engine fork’s SDL MFi driver names an Apple pad by GCController.productCategory, which should be “MFi” for a Nimbus (the SDL DB names are “SteelSeries Nimbus”/“SteelSeries Nimbus Plus”); all of these fall to neutral. Still pending: Chris confirms on the Nimbus.

  • iPhone (iPhone16,2) Dev BUILD 251: Button labels blank AND player hull invisible (2026-10-03). ✅ DONE (72c55003, branch ios-baker-saved-materials): fix = no runtime-created CanvasItemMaterials. All 41 CanvasItemMaterial.new() + BLEND_MODE_ADD sites (render/, fx/, ui/, main.gd) now preload the shared saved res://fx/additive_mat.tres; tests/test_baked_shader_materials.gd bans new ones. Baked (baker ON) iPhone Dev BUILD 254: hull draws in Lesson 1 (screenshot) and Button labels draw (Chris confirmed). The 23 runtime .duplicate()s of saved ShaderMaterials did NOT need changing. Store iOS 1.0 (build 37) still has the bug on A17+ iPhones; 1.0.1 carries the fix. ROOT CAUSE FOUND (BUILD 253 experiment): the iOS shader baker. An iOS-Dev export with shader_baker/enabled=false draws the hull AND the Button labels on the iPhone (screenshot + Chris confirmed). Matches upstream godotengine/godot#124088 (4.7.2/4.8.dev, iPhone 17: “argument canvas_data[0] has space for 160 bytes, but argument has length(272)” Metal validation error, failed draw calls, UI text missing; workaround: no runtime-created materials; no fix PR yet). iPad M1 unaffected. The store iOS 1.0 (build 37) and every baked iOS build likely have it on A17+-class iPhones. Fix options: (a) baker off for iOS presets (accept first-use stalls); (b) replace every runtime CanvasItemMaterial.new() / ShaderMaterial.new() with saved .tres (the hull glow sprites still build theirs in code); (c) fork runtime + templates for iOS like tvOS. Chris to decide. Screenshot (devicectl device capture screenshot, works over USB): pause menu plates draw, every Button label is blank; Labels (PAUSED, TIME, “Echo”) draw, same Orbitron font as the buttons. Ship: nothing drawn at all. Chris: “i think this happened before” on iPhone. ATV (fork runtime) last seen fine; Mac with the SAME stock 4.7.1 + --rendering-method mobile + platform/tvos project renders both fine, so it’s iPhone/stock-iOS-runtime specific. Ruled out: missing assets (ship3d_*.ctex and fonts are in build-ios/BulletHeaven.pck), pack/engine mismatch (both 4.7.1.stable.official). Toggling Debug Settings “Liquid-glass UI” + “Nova Look” off did NOT fix it (pause still showed glass plates after, so confirm the toggle actually applied). Godot print output does NOT reach idevicesyslog on this build — add file logging or an on-screen error overlay to get evidence. Next: check the iPad (same stock pipeline) to split device-GPU vs. iOS-pipeline; check whether a fresh full --export-release "iOS-Dev" (new xcframework, last full export 2026-09-29) changes it. Update BUILD 252: Dev builds now write Documents/logs/godot.log (pull with devicectl device copy from --source Documents/logs/godot.log); iPhone + iPad logs show NO render/load errors. iPad (M1, tutorial already done) start menu renders all text fine; iPhone (A17 Pro, fresh save → Tutorial) still shows no hull. Tutorial on the iPad shows the ship (Chris, 2026-10-03), so it is NOT the Tutorial path: it’s iPhone-specific. Remaining differences: GPU (A17 Pro / Apple9 vs M1 / Apple7), content scale (3x vs 2x), aspect (19.5:9). Next: does the hull show in Survival on the iPhone; try a Mac run at the iPhone’s resolution/scale (--resolution 2796x1290); check Godot 4.7.1 issues for Apple9/A17 canvas regressions.

  • PSO prewarm was culled whole: Warden v2 first-link stalls on BUILD 248 ATV (2026-10-02). ✅ DONE (7246ebbf, branch prewarm-cull-fix). Six 0.5-1.1 s stalls at run_time 214-237 s were the Warden v2 looks (swing, lane, bolt, charge, drone, burst, missile) first-linking mid-fight. Root cause: render/prewarm_pass.gd hid itself with modulate alpha 0.001, but Godot’s canvas cull skips any item whose combined alpha is < 0.007 (renderer_canvas_cull.cpp), so nothing outside SubViewports was ever submitted; the TouchControls prewarm pad (alpha 0.001) likewise. Fix: the pass draws at full alpha under the start menu’s opaque backdrop and hides + frees the instant the menu hides or leaves the tree. Desktop probe (mobile renderer): menu prewarm 9 → 38 pipelines; Warden fight 10 links (all 7 fx/boss/ shaders) → 1-5, none Warden; menu pixel diff with/without the pass = 0 (mobile + GL compat). HitchTracker also keeps fresh new: tags over repeat-tag spam. Guard tests/test_prewarm_cull_alpha.gd. Open: ATV fresh-process Warden fight should show no new:-tagged boss stalls; recheck the iPad run-start rows (the BUILD 220 “pad families not missing” verdict was taken while the pad was culled); the probe still shows ~10-14 links during ordinary play after the menu (e.g. enemy_smear), a separate prewarm-coverage gap.

  • BUILD 243 Apple TV GPU page fault with an evolved Wildfire / Firestorm chain (2026-10-01). AppleTV11,1 (A12, native Metal fork) faulted (“BIF0 page fault”, is_read, fragment work) ~4 s after the chain pushed weapon zones past 20. Prime suspect (by elimination vs BUILD 240, not reproduced on Mac Metal with validation): the perf_firestorm_cluster per-zone neighbour lists from 9d1c3c92, a dynamically indexed uniform vec4 nbrs[256] in render/weapon_zone_field.gdshader, live above 20 zones. ✅ FIX LANDED (BUILD 244, branch fx-v2-promote-railgun): nbrs, WZ_NBR_THRESHOLD/WZ_NBR_K and weapon_zone_neighbours removed; only the lossless early stop remains (early_stop uniform bool), and PerfCuts.firestorm_cluster now defaults OFF (row kept for an on-device A/B). Open: Chris to confirm on the Apple TV that an evolved Wildfire chain past 20 zones no longer faults with the row OFF, then again with it ON.

  • Orbit shards / blaster bolts randomly vanish mid-run (Chris, 2026-10-01). ✅ DONE (7fce5123). Engine-level, not the shaders: MultiMesh.buffer = ... never emits changed (unlike set_instance_transform_2d), so a buffer-fed MultiMeshInstance2D never redraws and canvas culling tests the rect cached at its LAST redraw. Once the player moved away from that spot the whole look was culled, popping back on the next visibility toggle. Hit every weapon FX v2 look (persistent orbit/bolts worst, since they never toggle) plus ZoneRenderer’s web + zone layers. Fix: MultiMeshCulling.disable() (all-covering custom AABB) on all four builders; repro’d and verified on mobile + gl_compatibility; tests/test_batched_layer_culling.gd also guards any future .buffer = layer. Needs an on-device eyeball (ATV: orbit + blaster across a long run).

  • Halo Chain enemies pay 0 XP. ✅ DONE (2026-09-30). A full audit of every enemies.add() call site found THREE truncation bugs, not one: Halo Chain packs (halo_chain_director.gd — 11 args), Splitter children (sim.gd _sweep_dead — 10 args) and Boss2’s summon adds (boss2.gd fire() — 10 args), all leaving biomass at its 0 default while the bible values landed in the never-read xp_val column. Same trap as the 2026-08-19 story-mode bug. All three now pass the real biomass (Splitter children and Boss2 summons pay the swarmer’s bible biomass, mirroring _spawn_elite_swarmer_brood’s unit-biomass pattern; the summon spawn was extracted into Boss2.spawn_summons so the real path is testable). Bosses (flat 400 fallback), tank missiles and FunZo jesters are intentionally 0-biomass — documented in tests/test_spawn_xp.gd. XP asserted through the real spawn → damage_enemy → _sweep_dead path (tests/test_spawn_xp.gd). No determinism re-pin: all three baselines held (halo packs spawn past the 10s window, and no splitter died in it).

  • Weapon evolve rungs are unreachable in human runs (Chris, 2026-09-30: “why don’t the weapons ever seem to become fully evolved”). In Survival, evolutions fire ONLY from WeaponThresholds rungs (level-up evolve: cards are crystals-era residue, never offered there) — and the rungs need 15-30 crystals of ONE element (mostly 18; aim 30). Crystals only arrive via the one grant card per level-up, 1-3 of an element each. Live D1 data (40 recent crystal-picking runs, 2026-09-30): best single-element total in any HUMAN run = 12 (a 7.5-min run; most top out at 3-10); only 6-20-minute attract/soak runs (void 20-31) ever cross a rung. The values are marked STARTER VALUES — balance via telemetry in sim/weapon_thresholds.gd, and that balance pass never ran. PARKED (Chris, 2026-09-30) for a dedicated balance pass — no spot-fixing the TABLE without it. (Compounding factor: on today’s main an evolved weapon barely LOOKS different — Blade Tempest still draws a ~97° crescent (fixed by the FX v2 batch 1 merge in §3b) and Beam Lance ignores width. Beam Lance’s width is fixed by FX v2 batch 2 (branch fx-v2-batch2-beam-railgun), so the payoff will at least be visible once those land.)

  • Remote config offers boss stats nothing reads. remote-config/manifest/build-206.json lists enemies.funzo/warden/graviton.{hp,armor,speed,contact_damage}, but those bosses spawn from code consts (FUNZO_HP …); only the Eye reads the bible. A patch would publish and do nothing. Either exclude them from the manifest or make the bosses read the bible. ✅ DONE (c142f22f): all four fields now reach the real pooled spawn path. Warden’s bible survival defaults now match its pre-existing SV_* values, while its Story/Tutorial constants remain unchanged. Pinned by test_boss_remote_config.gd.

  • Expose Supernova’s post-230 balance levers through remote config. Evolution area, cadence, burst damage, remnant radius, and remnant DPS are code literals today, so tuning its new big/slow/remnant identity would otherwise need another App Store build. Keep remnant lifetime shared with Wildfire’s ElementalSystem.ZONE_DURATION unless a future pass deliberately splits it. ✅ DONE (c142f22f): five keys are in remote-config/manifest/build-230.json, with a live evolved-pulse test covering all of them.

  • Remote-config batch 3: elites tunable + a mechanical liveness gate (2026-09-30). The elite promotion/stat family and the whole EliteKit attack roster are now patchable without a store build (67 new keys; 84 tunables total in remote-config/manifest/build-230.json), with structural bounds: elite contact mult capped 1.47 (keeps an elite Swarmer’s hit under the Warden’s 44 swing), radius mults capped so promoted enemies stay inside MAX_ENEMY_RADIUS (80). ELITE_XP_MULT is DEAD (multiplies only the never-read xp_val column) — stays const, removal logged in NEXT_TASKS.md. ✅ DONE (1ce0dbd7): plus tools/remote_config/liveness_probe.gd — the per-batch hand-trace is now a ~5-minute automated sweep (one deterministic run per key, digest-diffed vs baseline; bible fields included, so the “manifest offers stats nothing reads” class is caught before a patch ships), and the BIOMASS_TRIGGER <= TARGET cross-key rule is coded into Tunables.apply + publish-config.mjs (no more eye-check). ATV frame-time check for the BUILD 230 keys (Supernova/boss + elite batches; batch 1’s passed 2026-09-28 on BUILD 205) still pending with Chris — don’t publish any pacing/elite patch before it runs.

  • Beacon flank-reduction buff never wired. BEACON_FLANK_MULT is referenced nowhere though its header says _move_enemies reads it. Wire it or delete it (Chris’s call on intent).

  • Drone shop copy + dead Disruptor: Power (Chris, 2026-09-29: “the decoy seems to have some upgrades which don’t make sense” + “put an explanation against each drone type”). ✅ DONE (e0bc848e, 2026-09-29). Three fixes: (1) the legacy decoy-power/decoy-duration pair scales Decoy/Bomber/Interceptor damage and EVERY class’s lifespan but read as duplicates of “Decoy: Power”/“Decoy: Endurance” in the Decoy tab — renamed “Drone Power”/“Drone Lifespan” (per the 2026-09-27 plan, ids unchanged), routed to the General drone section, drones icon; (2) “Disruptor: Power” was a dead purchase (the class dealt no damage at all) — the slow field now chips DISRUPTOR_DMG per 0.5s pulse, scaled by cfg.dmg, and the misleading “+25% damage / repair” descs were corrected per class (only Logistics repairs); (3) every Drones shop section now carries a one-line what-it-does/what-it’s-good-for blurb (ShopCategories.DRONE_SECTION_INFO). Suite 343 scripts / 3445 tests green.

  • Decoy TYPE cards removed — the decoy is one basic low-damage taunter (Chris, 2026-09-30: “the different types of decoy don’t really make sense, decoy should be a low dmg taunter and thats it”). ✅ DONE (2026-09-30). The four unlock-decoy-* cards (Striker/Bulwark/Mender/Swarm) left bible.json (66 → 62 shop items), and with them the whole type machinery: DECOY_TYPES, Sim.decoy_type, MetaState.selected_decoy/owns_decoy(), the shop’s equip-on-retap path, the DecoyState.extra companion system, the decoy shop icon, the runs-row decoy telemetry field, and the economy sim’s priciest-decoy equip heuristic. What flies is the basic cfg (sentinel_cfg()), levelled only through the drone-sentinel-* stat cards. Save-safe: old saves with a selected type / bought cards load fine (orphaned levels sit inert; the gold still refunds via the full-shop Reset — the gold_spent ledger never keyed on ids). Suite 343/3441 green, determinism baselines held; catalogue-cost pin 22951 → 21701.

  • Fresh-install ~1 s shader stalls on every Apple device: the bake never contained .metallib (2026-10-06). Xcode 27 ships the Metal Toolchain as a separate download and this Mac lacked it, so every export logged Metal shader baking limited to SPIR-V and shipped MSL source; the device compiled each shader on first use (~1 s on the ATV), hidden by the per-app OS shader cache until a fresh install. Every first launch in telemetry stalls (store 255 iPad 4.1 s, iPhone 1.5 s; ATV 260 reinstall ten ~1 s stalls). Toolchain installed; exports now fail on that warning (91cc7c6b). BUILD 262 Dev = first metallib-baked ATV build, installed onto a deleted app. Remaining: (1) verify 262’s fresh launch has no ~1 s pipelines > 0 rows; (2) the next store build is the real fix for players; (3) hitch tracking only runs mid-run, so menu-time stalls are invisible.

  • ~1 s first-use shader stalls on Apple TV/iPhone: our custom shaders are never baked. The shader baker (editor/export/shader_baker_export_plugin.cpp _customize_resource) only bakes Material resources found in exported files/scenes. All 21 of our ShaderMaterials are built in code (ShaderMaterial.new(), 15 of them from inline Shader.new() code strings, e.g. render/screen_punch.gd, render/shockwave_layer.gd, render/gravity_lens.gd), and there are zero saved ShaderMaterial .tres files, so the baker only covers Godot’s built-in canvas shaders. Telemetry: BUILD 208 (baker actually running) still had 0.96-1.3 s stalls with 1 new pipeline each (a boss-down moment and one mid-play); iOS store BUILD 206 had 3 up to 3 s. Fix: move each shader to a .gdshader + saved ShaderMaterial .tres, preload it (duplicate per instance where uniforms differ), and check the baker’s step count rises above 202. Fix merged d82c6260 (baker 202 → 328 steps, guard tests/test_baked_shader_materials.gd), on the ATV as Dev BUILD 209 — tick only after its telemetry shows the stalls gone. D1 hitch check 2026-09-30 — fix works, but NOT tickable yet: stalls remain. BUILD 211 on the ATV (post-1080p, ~2.4 h of samples) is CLEAN: 103 hitches, none with pipelines > 0, max 120 ms. But 209 still had ONE 1.26 s stall in a modal at run_time 602 s (3 new pipelines; its other 484 hitches are 4K-render-era noise, pre-1080p), and BUILD 212 REGRESSED in 17 min of play: 1.13 s at run start (recent = new:pipelines, 4 pipelines compiled, run_time 0) + 1.06 s at run_time 664 s. 212’s only functional delta is 84cc802c, which edits two ALREADY-SAVED shaders (star_field.gdshader, nebula_bg.gdshader, both carry .tres) — suspect: EDITING an existing shader dodges the fork’s bake cache (unconfirmed); the other suspects are the unbaked engine-material classes below. BUILD 213/214 have zero telemetry samples (never run on any device), so the verdict needs the next ATV dev session: watch pipelines on any hitch ≥500 ms. BUILD 215’s first session (2026-09-30, ~4 min): 3 hitches, ZERO with pipelines > 0, max 85 ms — no 212-class run-start stall. 215 carries 212’s same two edited shaders baked cleanly, which supports the stale-bake-cache theory for 212, but the item stays open until a longer session (boss-down moment, an area change) confirms it. Follow-ups (reviewer): ~45 CanvasItemMaterial.new() + 3 ParticleProcessMaterial.new() (fx/gpu_burst.gd, fx/thruster_trail.gd, render/funzo_confetti_mist.gd) are still unbaked (engine registers their shaders non-embedded); the guard test only scans render/ ui/ fx/ main.gd. Also still open: BUILD 208’s game-over hitch was 503 ms of script time (proc_ms). 2026-09-30 root cause + fix (BUILD 217): the reviewer’s “unbaked” split was half wrong. The 45 CanvasItemMaterial.new() sites were NEVER missing from the bake — they all share one baked default CanvasShaderRD version (27 variants in the export cache), which is exactly why BUILD 211/215 logged zero pipelines > 0 hitches despite them. The real gap was the 3 ParticleProcessMaterial.new() sites: each distinct code-built config composes its own particle shader version at RUNTIME (damping / curve / ramp feature toggles), none in the bake cache → full source compile on first use. That was BUILD 216’s 494 ms 5-pipeline run-start stall (new:state:play,new:pipelines, fresh process) and BUILD 212’s 1.13 s equivalent. Fix: the 3 process materials saved as exact-config .tres (render/shaders/{thruster_trail,gpu_burst, funzo_confetti}_process_mat.tres) + preloaded at their sites — verified empirically that the fork’s baker DOES bake saved ParticleProcessMaterial .tres (bake 202 → 204 steps, 2 new ParticlesShaderRD cache entries); tests/test_baked_shader_materials.gd now bans ParticleProcessMaterial.new() in shipped dirs. CanvasItemMaterial sites stay as-is (shared baked version; residual per-PSO creation cost only, small). Untick this only once BUILD 217+ ATV telemetry shows a fresh-process first run with NO run-start pipelines > 0 hitch. Same day, part 2 — the un-bakeable half is PSO linking. BUILD 217’s fresh-process first run STILL hitched at run start (762 ms, 4 pipelines, proc_ms 9 = pure render thread) — but with ZERO runtime shader-cache writes on the ATV and the driver’s com.apple.metal library cache not touched during the run, i.e. no source compiles remained anywhere. What’s left is Metal pipeline-state-object LINKING: per-process, on each pipeline’s first submit, not serializable, not bakeable. Fix part 2: render/prewarm_pass.gd, wired in main._show_start_menu — draws one invisible instance of every run-start pipeline family (multimesh ADD quads, both particle compute+draw passes, additive textured quads, ADD primitives, ArenaBackground’s star multimesh, NebulaBg’s offscreen pass) for 24 frames behind the start menu, so the menu period pays the PSO links where a stalled frame is invisible. PSOs then exist before any first run. Measured, prewarm v1 (BUILD 218, fresh-process attract run): run-start hitch 762 → 126 ms (4 small PSOs left, ~30 ms each) + 105 ms at first blaster_fire (3 PSOs) — the heavy links are gone, but the ≥60 ms bar is not met yet. Prewarm v2 (BUILD 219) adds the four families the menu never draws — HeroLights’ PointLight2Ds (zero 2D lights exist at the menu), ArenaBorder’s shader strips, Polygon2D (ship core / turret hexes), outlined dmgnum labels. ✅ VERIFIED ON THE ATV (2026-09-30, BUILD 219 fresh-process attract run): ZERO hitches — not even a sub-60 ms one, 200 enemies on screen 74 s in. Run-start stall history: 494 ms (216) → 762 ms (217, post-bake, PSO-only) → 126 ms (218, prewarm v1) → 0 (219, prewarm v2). The ATV draws nothing cold at run start any more. ⚠️ One narrow residue, iPad only: the same BUILD 219 iPad attract session still hitched 129 ms + 107 ms with the 4+3-pipeline shape. Since the ATV is fully clean, those 7 must be iOS-only first-draw families the ATV never renders — prime suspect TouchControls (main._new_run adds it on iOS only; its joystick/buttons first-draw at play entry; the menu never shows it). Follow-up: prewarm TouchControls (and any other iOS-only run-start node) the same way, then verify a fresh iPad run. Status 2026-09-30, after verifying BUILDs 220-222 on the iPad — three findings: (1) TouchControls prewarm landed (family 11, 5faaa362) and is verified NOT the fix. BUILD 220’s fresh iPad run still hitched 131 ms / 4 PSOs at run start + 98 ms / 3 at first blaster — the same shape as 219, so the pad’s families were never the missing ones. The prewarm stays (correct coverage, zero cost, tvOS no-op). ATV on the same BUILD 220: CLEAN (two benign sub-80 ms rows, zero pipelines > 0) — the ATV half of this item is closed. (2) Root cause: the stock Godot 4.7.1 iOS export baker is thin. The iOS-Dev export bakes ~16 distinct RD cache entries (~1 CanvasShaderRD variant) vs the fork’s ~137 (19 CanvasShaderRD variants). The tvOS app (fork runtime + fork bake) links those variants invisibly during the menu prewarm; the iPad (stock runtime + stock bake) pays a ~100 ms source compile+link per missing variant at its first submit — exactly the 131/98 ms rows. The STORE iOS path has the same weakness (build-ios.sh exports with stock godot — store BUILD 206’s 3 s stalls). (3) A fork-baked pack inside the stock runtime REGRESSES (tried + reverted same day). BUILD 221 (fork --export-pack): 1243 ms run start, 705 ms first level-up, a cascade of 300-700 ms rows (8 total) — the stock runtime can’t read the fork’s bake cache and compiles more from source than stock’s own 16 entries covered. install-dev.sh reverted to stock with a warning comment; BUILD 222 redeployed to restore the 220 state. (222’s first observed session came back CLEANER than 220’s baseline — no run-start or first-blaster row, one 305 ms first-new:nova stall at 24.7 s; plausibly the 221 session’s runtime compiles persisting in the app’s on-disk cache across the reinstall, so don’t read that as the bake gap being gone — a true first install should still show the 131/98 shape.) The real fix is the fork runtime + templates inside the iOS app (the tvOS model) so fork-baked packs are first-class — a release-pipeline project that also changes the store build path. Chris’s call on scope/timing vs 1.0.1. Tick THIS item only once BOTH devices’ fresh first runs are clean; the iPad half now waits on that decision (standing residue: ~131/98 ms).

  • Debug Settings panel traps you on tvOS (Chris, 2026-09-30, mid audio A/B: “I wasn’t able to get out of the debug menu, the game carried on in the bg”). ✅ DONE (same day). The panel’s _input handled only up/down/A, so the Siri remote’s Back did NOTHING — the only exit was swiping down ~25 rows to the bottom “Close” button while the sim kept running behind the panel (opening it from the pause menu hides the menu and pauses nothing; main.gd only restores the pause menu when closed fires). Back (JOY_BUTTON_B), Esc and ui_cancel now emit closed, exactly like the Close button — the pause menu returns via the existing await. Dev-builds-only surface. Pinned in tests/test_debug_settings_panel.gd (back closes + hides; Esc closes; back ignored while hidden) — 60/60 panel tests green.

  • ✅ DONE (2026-10-01, 2bbad3ee) Firestorm zone + Thunder Wake puddle now drawn. Both persistent DoTs lived on the weapon instances and had no renderer; ZoneRenderer.weapon_zone_discs now draws them (FX v2 batch 2). The Railgun mark lands with the batch-2 Railgun shootout winner.

  • ✅ DONE (2026-10-01, 4ebccfcc) Railgun mechanics batch (FX v2 batch 2, Chris-approved A1/A2=B1/A3/C). The lance fx event carries target_dist; a marked enemy that gets hit flashes (hit-confirm, EnemyPool.last_hit_time); a charge tell latches the aim 0.1 s before the shot and the shot follows it; an expiring mark or a marked death detonates for 0.25 x lance damage within 40 px (queued, resolved at the next Railgun update, never mid-sweep). All three knobs are remote-config tunables (WeaponRailgun.TELL_S / MARK_POP_FRAC / MARK_POP_RADIUS), 0 = the old behaviour, so Chris can A/B them; all render consumers are FxManager-owned, so they show on v1 and under every candidate look. Branch fx-v2-batch2-beam-railgun; still needs Chris’s on-device feel check.

  • ✅ DONE (2026-10-01, 7da4f5e6) Pulse, Beam and Railgun all read the same yellow (Chris, Apple TV). bible light (#fff3a0, S 0.37: Beam + Railgun) and lightning (#ffe34d, S 0.70: Pulse) both hit ElementPalette.vivid()’s S >= 0.92 floor and landed ~2° apart, (1, 0.88, 0.08) vs (1, 0.84, 0.00); all layers are blend_add, so nothing else separated them. ElementPalette.weapon_color() keeps light a pale gold for player weapon FX only: own hue, S WEAPON_LIGHT_SAT 0.48, V floored = (1.00, 0.94, 0.52); lightning and every other element unchanged. FxManager uses it for every fx_event with a wid (enemy beams keep the old colour) plus the Railgun mark, tell and mark pop. A/B: Debug Settings “Weapon Tints (A/B)” (default ON, also the console’s graphics toggle); remote: ElementPalette.WEAPON_TINTS kill switch + WEAPON_LIGHT_SAT (0.3-1.0) so the gold can be tuned on device without a build. Branch fx-v2-promote-railgun. Needs Chris’s eye on the TV: whether 0.48 reads gold or cream once additive overlap pushes it bright (raise the tunable toward 0.55-0.6 if cream).

  • ✅ DONE (2026-10-01, 445ce1ec) Evolved Beam swamps the screen (“all of them become a bit too much once evolved”). New Beam board letter E = letter D toned down (archive rule: D untouched): halo quad 1.6 half-widths max and capped at 14 px, held beam soft-capped at ~0.9 after BASE_DIM (no bloom), a continuing shot in a stream drawn at 50% with no flash, Lance rod/braid 0.6x. Compare D vs E in Debug Settings > Beam Look. Note the beam’s footprint is still the real hit width (the sheath IS the hit edge), so an evolved wide Lance stays wide by design.

  • Kinetic and aether render as pure blue (logged, not fixed). bible kinetic #c9d1d9 (S 0.07) and aether #e0e0ff (S 0.12) are near-grey, but vivid()’s saturation floor turns them into (0.07, 0.49, 0.90) and (0.08, 0.08, 1.00). Hits Blade, Turret, Momentum Core (kinetic), Blaster (aether) and every kinetic/aether enemy. Same root cause as the light/lightning collapse; needs a design call on what kinetic/aether should look like before touching vivid(). Other weapon pairs that also land close under vivid(): cold Orbit (0.02, 0.63, 1.00) vs time Chronowell (0.08, 0.70, 1.00), ~3° apart (Chronowell’s own FX use a hard-coded pale cyan, so it shows mostly in shared paths); fire (Wildfire, Nova) vs blood (Scar Tissue, Scatter), 10° apart.

  • Seven player-facing mechanics have no renderer at all (audited 2026-09-30 with the new roster board, tools/vfx_library/library.tscn). Ranked by gameplay impact: enemy elemental aura + stack count (core to the elemental engine — damage changes, nothing draws), the Firestorm zone and the Thunder Wake puddle (both persistent DoTs that already have sim state, so a ZoneRenderer consumer is the cheap win), the Railgun mark (damage amplification with no visible tell), the Disruptor field radius, the Scar Tissue stack meter, and the Glass Wisp phase state (untargetable looks identical to solid). Evidence: the red MISSING cards on that board, with each item’s sim source path in tools/vfx_library/library_manifest.gd. Fix these alongside FX v2 batch 2 (Beam + Railgun).

  • Late-run fps collapse on the Apple TV (BUILD 233 telemetry, 2026-10-01). ✅ DONE (a6361428, dev-installed on the ATV as BUILD 234). A 14-min attract run held 50 fps (the TV runs at 50 Hz) until webs, zones and swarms piled up, then sat at 16-27 fps for ~90 s. Pooled ATV samples (builds 228-233, ~6,100) pinned three costs: (1) ElementalSystem.drop_webs scanned every web (up to WEB_CAP 400) for every spider every tick -> now a per-call coarse grid, ~10x cheaper (3.7 -> 0.38 ms/tick on the Mac at 40 spiders / 400 webs), output proven identical to the full scan incl. cap eviction (tests/test_web_drop_grid.gd); (2) Sim.zones (Wildfire scorch mod + Supernova remnant, new at 64-cap from BUILD 232) drew 3 immediate-mode calls each -> one MultiMesh + render/zone_field.gdshader (48 zones: 254 -> 15 draws; web path pixel-identical, Metal within AA-edge noise after an HDR sRGB->linear fix that instance colours need), prewarmed as PrewarmPass family 13; (3) Wildfire rebuilt the spatial hash per detonation -> once per fuse batch. Determinism baselines held, late-game invuln soak clean. Verify on the ATV: a long run with spiders + Wildfire should hold fps windows near 50 where BUILD 233 dropped to 16-27. Not addressed: evolved Nova costs ~5 fps even with few enemies (cause unknown; ATV reports no gpu_ms). ATV verdict (2026-10-01, TV now 60 Hz): a 15-min BUILD 234 run averaged 57.2/60, worst window 41 (233 on the same 60 Hz: 46.2 avg, worst 16). Fix confirmed.

  • Game-over screen stutters on the ATV (BUILD 235 telemetry, 2026-10-01). ~145 frames of 78-93 ms over ~60 s on the results screen in one run (fps 40-46); pipelines = 0, proc ~15 ms, ~270 draws, so GPU-side, not a compile. Also logged on 230/231/233. Suspect the frosted GlassPanel card (ui/results_panel.gd; already uses a one-shot snapshot) or the focused button’s glow quad. Reproduces on every death, so profile it there first.

  • Once-per-run ~1-1.3 s first-use pipeline stall on the ATV (BUILDs 232-234). One pipeline, low proc_ms, at 0:46 / 1:54 / 2:04 in three runs; not reproduced in 3 runs on 235. Until 234 its recent context was always empty: the look-back was measured from the stall’s END (fixed ee09dc90, which also tags new:node:/new:shader: first sightings). Next occurrence on 235+ names the cause — query hitches WHERE pipelines > 0.

  • Evolved weapons cost ~3-5 fps on the ATV with few enemies (BUILD 234: flat 60 before evolutions, 55-58 after, sim_ms flat). Suspect the Supernova remnant zone’s fill (zones=1 for the rest of the run). Measure with perf_probe --zones=1 at remnant radius. BUILD 235 run b72fe12d (autopilot, 2026-10-01) — worse than 3-5 fps, and it’s per cast: 60 fps with 150-200 enemies before Nova evolved at 3:46; after it, windows of 30-42 fps (worst with 100-175 enemies + 165-391 webs + a zone) and max_frame_ms 42-64 in almost every window, even clean-60 ones with <10 enemies (before: 24-38). All 3 hitches (61-64 ms, pipelines 0) have nova in recent. render_cpu_ms 0.5-2 vs render_ms 28-40 = GPU. Pooled 60 Hz ATV samples (233+): zones 0 → 57 fps, 1 → 54, 2 → 46, 3-10 → 38-49 with <30 enemies. Lead suspect: the Supernova shockwave fill per cast, then the crater. Nova* dealt 2.3% of run damage, Orbit* 0.05% — the costliest look is near-useless (balance call for Chris). A/B in flight: dev-channel patch b235-dev-001 (REMNANT_FRAC 0.1, remote-config/build-235.json); revert with a {} patch to build-235@dev after the run (it otherwise serves dev 236+). Clean on-device A/B tool: next item.

  • On-device perf scenario in the Remote Playtest Console (Chris, 2026-10-01). One button: clear + invuln + spawns paused, grant/evolve a fixed loadout via the real crystal path, a ring of stationary targets, optional held webs/zones, then ABBA-alternate a RENDER-ONLY hide toggle (Supernova shockwave / zone layer / web layer — flippable mid-run, determinism-safe, like perf_probe --hide-v2) in ~10 s segments, running the schedule on the device. Each segment pushes one EventQueue event (p50/p95/max frame ms, frames >33 ms, fx before the worst frame); the panel shows an A-vs-B table. Needed because remote-config tunables apply only at run start and the hitch log ignores the <60 ms stalls. Ships with BUILD 236 (+ RC-4’s LIFE_EVOLVED).

    • code done (bbb0e922 + panel 86f60420); control Worker deployed 2026-10-01 (version 45bfd495); pending: ATV acceptance run
  • Web layer overdraw on the Apple TV (Chris, 2026-10-01: “slows down a lot when webs are on screen”). Webs are already ONE batched draw (render/zone_renderer.gd web layer, since BUILD 191), but each web is its own additive quad, so up to WEB_CAP 400 overlapping webs (radius 38, spacing 34; Elite Spider capsules radius 100) shade the same pixels many times on a fill-bound GPU. Weapon zones got a union shader for exactly this (one fill per pixel, zone_renderer.gd weapon-zone section, WZ_MAX 64); webs did not. Measure first with the perf scenario (webs=400, arm webs), then consider a union/low-res approach — 400 webs exceed the zone shader’s 64-slot union.

  • ✅ DONE (branch crystal-card-names; numbers were consistent, “6 more” = still needed AFTER the grant, so the body now starts “Then N more X:”; every player-facing element label reads the bible name via ContentDB.element_name) Level-up crystal card: title and body disagree, and the title uses the old element name (found 2026-10-06 in the Mac App Store screenshot run). The card read “+3 Lightning, +4 Void” while its body said “6 more Void and Beam reaches 25% further” (+4 vs 6, and the body never mentions the Lightning/Electricity crystals) (seen in a 2880x1800 capture via shoot.gd, level 26). Two problems: the title says “Lightning” where data/bible.json calls the element Electricity, and the title/body counts may come from different sources. Find where the card builds its title vs body (ui/upgrade_copy.gd / the level-up panel), make both read one value, and use the bible’s element display name. The Mac listing shipped without this shot until it is fixed.

  • The first warp in a tinted Dimension wipes the backdrop tint (found 2026-10-09 by the HDR step-2 final review). ArenaBackground.set_tint() stores the Dimension tint as the backdrop’s own modulate (render/arena_background.gd:270), and main._drive_warp writes arena_bg.modulate = Color(dim, dim, dim) every warp frame and leaves it WHITE at warp end. So after the player’s first warp in Void / Poison / Fire, the arena backdrop loses its tint until the next area change (main.gd area switch) or a live HOOK_TINT. Fix: keep tint and warp dim separate and multiply them (e.g. ArenaBackground holds _tint and _warp_dim). This interacts with colour_space_fix (branch hdr-colour-space): there the star-layer compensation must also take the tint into account, so fix it on or after that branch.

  • Un-remapped alpha layers on the linear canvas (audit 2026-10-12, docs/research/2026-10-12-unremapped-alpha-audit.md). Glass glow 5.8-6.4x, enemy Nova glows 2.6-4x, far stars 5-9x brighter than 8-bit on Metal L0 SDR / HDR. Player hull glow ✅ DONE (e8e482e9) and UI P3 in HDR ✅ DONE (7772c7b8), both approved by Chris on BUILD 312. 📍 Execute plan docs/superpowers/plans/2026-10-12-unremapped-alpha-fix-batch.md.

  • ✅ DONE (fix/hp-clamp: _hurt_player floors hp at 0, tests/test_hp_floor.gd; determinism baselines unchanged; the seed-1085003922 repro now scores 104.0 s with no invariant error) Pilot HP can go below 0 on a lethal hit (found 2026-10-13 by the persona benchmark, persona-offline-bench). Repro: the damage:strong elite of the epoch-2 prod archive (genome 986ed32f08954dce), seed 1085003922, skill strong, learner conditions (EvolveBatch.soak_persona): SoakRun invariant tick 6241: pilot hp out of bounds: -0.011623/100. Pre-existing, not from that branch. Likely cause (reviewer, [Likely]): Sim._hurt_player does pilot.hp -= taken with no clamp (sim.gd ~3324); the only clamp to 0 is inside _check_pilot_hit (sim.gd ~3412, under if pilot.hp > 0.0 and not is_invulnerable(pilot)), so a lethal hit from any of the ~20 other _hurt_player callers (burn elite_kit.gd:355, leech enemy_behaviors.gd:705, graviton.gd:178, funzo.gd:317, hazards, boss attacks) leaves hp negative. SoakRun’s tolerance is -0.01 (soak_run.gd:279). Side effect: the soak learner and benchmark silently drop every run that dies this way (no sample), a small survivorship bias against genomes that die to DoTs. Fix: clamp in _hurt_player. May touch determinism: check whether hp feeds state_checksum(); if a pinned baseline moves, re-pin via skill bh-determinism-repin.

  • ✅ DONE (2026-09-30) scripts/gate.sh:32 silently discards intentional project.godot edits (git checkout -- project.godot, meant to undo Godot’s own rewrites). Bit us 2026-09-28: a real rename was reverted before commit. Same line in scripts/economy-careers.sh:23. Fixed with the snapshot-and-restore option: both scripts snapshot project.godot at start and restore it at the end (only if the run rewrote it, with a loud warning + the snapshot path) — gate/career-induced Godot rewrites are still reverted, but a developer’s intentional uncommitted edit now survives. Validated by a full gate run (PASS) and a real mini career run.

  • marketing/template/make_press.py SHOTS points at renamed screenshots (02_drones.png etc.; current set is 01_horde … 06_stats). Chris picks the shots, then regenerate marketing/appstore/press/one-sheet.png (footer URL already fixed in 66a2f2b).

  • ~/Claude/bullet-heaven-site has no git remote — its commits exist only on this Mac.

  • .claude/skills/ is globally gitignored, so Toby doesn’t get the 19 project skills (bh-deploy, bh-appstore-release …). Decide how to share them.

  • Audio perf pass (docs/research/2026-09-29-audio-performance-audit.md, all 8 items): PCM SFX imports, idle-first voices + boss reserve + UI pool, per-play churn, music dedupe/preload/loop imports, SFX_COOLDOWN_TICKS, HardLimiter, DevFlags.audio_sfx_off, aud_* sample telemetry. Merge ✅ DONE — fe29d3d8 (2026-09-29) already carried the whole branch perf-audio/2026-09-29; this line was stale. Live D1 + Worker ✅ DONE (2026-09-30): the 7 aud_* ALTERs applied one command each (telemetry/migrations/2026-09-29-audio-telemetry.sql), deploy.sh --check-only green, Worker 3231abe6 deployed, smoke test passed (aud values land; old-style no-aud samples still insert with NULLs; dedup intact; smoketest rows deleted). Dev builds ≥ BUILD 211 already send aud_* and it is now stored, so the ATV A/B needs no new build: flip audio_sfx_off in Debug Settings (live flag) and compare aud_us / sim_ms per window. Still open: Chris by ear on ATV/iPhone (cooldown table, limiter at loud pile-ups, boss stings in busy fights); web first-boss-spawn trace (needs the web demo rebuilt + deployed from main first). Dashboard note: aud_* is ingest-only — the dash tables don’t render audio columns yet; query D1 directly for the A/B. ATV session verdict 2026-09-30 (Dev BUILD 215, 237 s autopilot survival run, 586 kills, lvl 16): audio data side ✅ DONE. aud_us p50 2.9 ms / p95 5.2 ms per 5 s window = 10-17 µs per tick at 35-152 enemies ≈ 0.3% of the ~3.7 ms/frame sim_ms — PCM did its job; audio is no longer worth chasing on ATV. aud_mix_rate 48000 on-device (closes the audit’s [Likely] HDMI-48 kHz question); audio_sfx_off verified live (aud_us → ~14 µs, timer residue). The pool hit 16/16 in the dense fight with 21 steals over 57 windows (the #3 boss-sting steal risk is real — the ear test decides if it’s audible); aud_capped 275 of ~2 900 plays (~10% dropped by cap/cooldowns — the cooldown table is biting). The planned on/off sim_ms comparison is moot: the off windows were the run’s death tail (0-1 enemies — Chris got trapped in the debug panel, bug below), and aud_us measures the audio share directly anyway.

  • The docs site has been silently FROZEN since 2026-07-14 — found in the 2026-09-30 save housekeeping. Every push since the first frontmatter-less docs/superpowers/{specs,plans} file landed (07-14) failed its CF Pages build (Starlight requires a fenced title: on every content file; 28 specs/plans had none, and the 09-26 mobile-touch survey had an unfenced title: line); CF kept serving the pre-07-14 snapshot while everyone believed pushes deployed. ✅ DONE (6a4edf67, 2026-09-30): frontmatter on all 29 files + gate-run guard scripts/test/docsite-frontmatter.test.mjs (lists EVERY offender — the CF build only reports the first). Live site verified current again (survey, audio audit, specs pages all 200). Lesson recorded: a 200 from the site proves nothing — check wrangler pages deployment list --project-name=dark-cosmos-docs.

  • Remote Console v2 (Chris, 2026-10-01: “a toggle to turn upgrades off, reset everything, remove crystals, bigger more graphical panel, better organised, graphical tooltips”). No XP dev toggle (console + Debug Settings), Reset everything (build + enemies + gems), crystal removal that rolls back rungs (mod log + weapon rebuild), and a full-width neon mission-control panel with hover/long-press tooltips. Spec docs/superpowers/specs/2026-10-01-remote-console-v2-design.md, plan docs/superpowers/plans/2026-10-01-remote-console-v2.md. ✅ DONE (branch console-v2; merged dd208755)

  • Remote Console: Looks as one-tap buttons + a What’s new tab (Chris, 2026-10-02). Every Looks row (Background, Ship tilt, weapon looks, Web look) is a wrapping button group that sends on tap and lights the live option (game status now reports bg + tilt). New What’s new tab lists player-visible changes per BUILD with where to test them (control/src/whats_new.js); whoever merges a player-visible change adds a build: null item, and scripts/bump-build.sh stamps it on main/release/*. ✅ DONE (branch console-looks-whatsnew). Needs a Worker deploy to go live.

  • Walk the still-open items in docs/RELEASE-0.1.md (§1 gates like ADAPTIVE_QUALITY, §4 fix-if-cheap, §5 parked) and move what belongs in 1.0.1 here; leave the rest parked.

  • Code cleanup audit (PARTLY DONE: zero-risk deletions + doc drift = PR #19 2c0b83c, teaser overlay 843c3d5; the rest needs Chris’s calls or is structural): docs/research/2026-09-28-code-cleanup-audit.md (dead code, A/B look switches awaiting Chris’s pick, dev-only consts in main.gd, sim.gd/main.gd extraction plan, stale worktrees with 3 unmerged commits, doc drift). Pick what goes in 1.0.1 vs later.

    • ✅ DONE 2026-09-29 (branch cleanup/dead-code-2026-09-29: e0b662b, 9b67605, 1faf1e6, cba291f, 1984acb, cff72dd + the doc-drift commit): the zero-risk deletions — §1 dead sim symbols / consts / functions, primed/react_cd, the teaser-wormhole machinery, _archive/, verify-sim-refactor.sh, the 4 risky tests, §8 doc drift. Determinism unchanged, no re-pin. Still open: everything that needs Chris’s call (power-ups, Boss2, Warden elite, Beacon flank, A/B switches, ADAPTIVE_QUALITY, music dup), the Halo Chain/splitter biomass bug, xp_val + add() redesign, Elemental.apply()/ContentDB.reaction(), and the structural extractions.
  • Chris: audio/menu_theme.mp3 and audio/survival_loop.mp3 are byte-identical (3.7 MB each, both shipped). Intended, or was the survival loop meant to be different music? (Branch perf-audio/2026-09-29 makes survival_loop reuse menu_theme’s stream at runtime, so web decodes it once; the file itself is still shipped until this is decided.)

  • AFTER HDR P3: GPU fill-rate pass + 4.8 sim-CPU profile (Chris, 2026-10-11). The ATV GPU-bound scene is fill-rate bound (perf_fx_half_res on = 16.7 ms); 4.8 beat 4.7 by +21% frames (ABA). Start with an Xcode GPU frame capture on the iPhone (cable) for per-pass cost. Findings + ideas: docs/research/2026-10-11-godot-48-gpu-ab-and-fill-rate.md.

  • HDR: widen the UI to Display P3 too (Chris 2026-10-12, “increase saturation of all UI elements in HDR mode”). Reverses the 2026-10-11 “UI stays sRGB” rule; HDR-only, SDR bit-exact. Branch hdr-ui-p3 (one path ColorGamut.ui_out = the LiveTokens output filter, own dial ColorProfileHdr.GAMUT_UI_WIDEN seed 1.0; arch doc docs/architecture/ui-tokens.md § HDR Display P3). Needs review + merge, then Chris’s ATV/iPhone eyeball (dial 0 / 0.5 / 1).

  • ✅ DONE (hdr-colour-space: code through 6902d940, BUILD 310 6bbf8e68; arch doc docs/architecture/rendering-and-performance.md § Display P3 output) Display P3 wide-gamut output with HDR (plan docs/superpowers/plans/2026-10-11-hdr-p3-palette.md). Patched 4.8 fork Viewport.output_gamut (iOS + tvOS), ColorGamut widens gameplay colours to the P3 edge only while HDR output is engaged; UI stays sRGB. Baked BUILD 310 on iPhone 15 Pro Max + ATV, A17 baker check passed, ABBA fps shows no visible cost (iPhone 70.1 vs 70.6, ATV 59.78 vs 59.94 at the 60 cap, both inconclusive; the <= 0.1 ms render_cpu_ms budget was NOT measured, samples don’t tag the gamut state). Chris (ATV): “everything on looks best”, seeds stay GAMUT_P3 true / GAMUT_WIDEN 1.0. Still owed before main: merge review (plan Task 10) and the fresh-install stall check. Follow-up: glows a bit soft, especially on the player.

  • ✅ DONE (fb/health-bar-always-visible; pause-menu “HP bar Always/Auto” half-row beside Ship stats, Debug Settings row + console touch_flag, saved; needs Chris’s ATV eyeball) Settings toggle: hull/health bar always visible (Chris voice feedback, D1, 2026-10-11, BUILD 297 ATV). Settings row + Debug Settings/console parity row; persisted in the save.

  • ✅ DONE (fb/player-death-sequence; render-only, skippable, master switch + every value a live tunable, vfx_library entry; flash 0.35; needs Chris’s ATV/iPhone eyeball, ring size and whether players get a flash/shake off-switch are his calls) Player ship death sequence: a big wide exciting explosion before the results screen (Chris voice feedback, D1, 2026-10-07, BUILD 267 ATV). No player-death FX exists today (main.gd sim.game_over branch goes straight to _show_results_when_drawn; fx/death_dissolve.gd is enemies only). Design call with Chris first; needs a vfx_library entry + score.

  • Supernova “covers the screen” (TestFlight #27, 2026-09-27, iPhone11,2). The fps half is tracked under the evolved-weapons perf item; the screen-coverage half is not. 445ce1ec toned down evolved Beam, not Supernova. Chris’s call on size.

  • Supernova iridescent look + x4 damage / half rate + shockwave (Chris, 2026-10-12). Branch fb/supernova-iridescent (stacked on fb/supernova-visual-size). Look: thin-film hue cycle + thin white-hot filaments (NovaBurstLook.EVO_IRIDESCENCE / EVO_HOT_THREADS, live; 0 = old look). Balance: EVOLVE_DMG_MULT 7 -> 28, EVOLVE_COOLDOWN_MULT 2.5 -> 5 (burst DPS x2), crater absolute dps kept (REMNANT_DPS_FRAC 0.25 -> 0.0625). Shockwave: enemies thrown out (bosses + kinetic-immune never, elites x0.5), enemy bullets turned outward (WeaponNova.SHOCK_*). Needs Chris’s eye: SDR + HDR, several Supernovas overlapping.

  • Re-judge “too hard, elite Glass Wisp has too much health” (Chris voice feedback, D1, 2026-10-07, BUILD 273 ATV) on a build with 50f4ef18/8463532c (the Orbiter/Wisp HP cut below).

  • Elite Orbiter + Glass Wisp HP cut (Toby playtest via Chris, 2026-10-07: “some enemies, especially elite forms, have too much health, notably the Orbiter and the Glass Wisp”). ✅ DONE (branch elite-hp-orbiter-wisp): new tunables Sim.ELITE_ORBITER_HP_MULT 12 (was the generic 27x: 16 → 432 HP, now 192) and Sim.ELITE_GLASS_WISP_HP_MULT 6; Glass Wisp bible HP 12 → 8 (plain wisp 8, elite 324 → 48 nominal; the 50% phase-out per hit roughly doubles effective HP, so ~650 → ~100). All per-type elite HP overrides now come from one helper, Sim._elite_hp_mult(tid), on both _spawn_one and spawn_story_enemy. Biomass/XP scales with the mult, so elite wisps/orbiters pay proportionally less XP. Test tests/test_elite_hp_orbiter_wisp.gd. Pending: Toby/Chris feel-check on device; tune live via the console if still off.

  • Ship stats: weapon navigation + every crystal rung (Chris, 2026-10-07: “navigate into the full ship stats and select between the weapons … we need to see all thresholds”). ✅ DONE (branch stats-sheet-nav): up/down (d-pad / sticks / arrows) and row taps pick a weapon in both sheets (shoulders kept; a tap OFF the glass still closes), the always-on Rift Bolt gets its own row, and each weapon carries a per-SLOT glyph (ui/weapon_glyph.gd: circle/triangle/square/…; crosshair = Aim). The Crystals panel is one line per element (name | rung bar | count) with one glyph per rung of every owned weapon and the rung number under it where it fits; filled = reached, GOOD = the previewed card crosses it; the selected weapon’s glyphs are lit, the rest dimmed, and its elements sort to the top. Rows select on release (a drag scrolls the column instead). Aim’s path reads its real AIM_MODS effects (+50% damage, +1 pierce, …). Elements an owned weapon needs but you hold none of show as 0 rows. Tests: test_weapon_glyph.gd, test_stats_sheet_rows/view, pause + level-up nav. Needs Chris’s eye on device (iPhone tap targets, TV legibility of the glyphs).

  • Nebula visual polish (Chris, 2026-10-07, autopilot preview). Organic look + 12 clouds, 0.24–0.42 opacity and 0.6 organic scale applied LIVE to ATV BUILD 272 and verified in device status (no problems/pending keys). Local implementation and refreshed ui-sweep also pass the gate. Await Chris’s visual verdict, then an authorized persistent patch for supported values; investigate organic-selector persistence separately. No rebuild/install or persistent patch publication; local code/baselines remain uncommitted.

  • Event Horizon: 7 shards + space-time warp (Chris, 2026-10-07) — ✅ DONE (branch event-horizon-warp). Evolve ADDS +3 shards (cap 7; pre-evolve cap 4 so “+3” is always true); evolved ring bends enemy bullets and nudges enemies along the spin (lens stronger near shards; bosses immune); red lens + eddy VFX (VisualLook.orbit_warp A/B), blood-red tint, deeper hum. All strengths remote tunables, 0 = off.

  • Event Horizon: Apple TV perf + feel check. M5 only so far: GPU +0.025 ms per evolved ring, sim 0.167 -> 0.253 ms/update at 500 bullets + 140 enemies. Escape hatches: WARP_* = 0, WARP_GAIN 0, orbit_warp off. Also Chris’s eye on the VFX (library rating held at 4).

  • Autopilot upgrade picks: always take the best card (Chris, 2026-10-04: “the autopilot needs to always make the best decision on what upgrades to choose”). ✅ DONE (2026-10-04, this commit): SkilledAutoPlayer.pick_upgrade_for(sim, pilot, ids) ranks the offered cards with the SAME pure upgrade_effects score the level-up panel badges BEST (rung-crossing crystal grant 20/evolve 100 > new weapon 15 > long-term crystal 9+8×closeness > plain stat 8 > dead 0), ties keep offer order, and a tutorial lesson’s forced card always wins its own step (pick_crystal completes only when its card is applied). Callers routed: main attract/capture, soak, autopilot_bench, profiles; ExperimentalUpgradePlayer.PRIORITY keeps the OLD prefix heuristic as the A/B control arm, and story traces keep the context-free path. Paired-seed soak (24 seeds, skilled AI, only the picker differs): mean death 6961→7574 ticks (+8.8%), 17/24 identical, 5 better / 1 worse, best run 6958→21441 (lvl 11→22); death-cause mix and build diversity unchanged. Follow-up (economy sim): profiles inherit the smarter picks, so SkillProfile calibration vs human targets needs a re-run of scripts/economy-careers.sh (the old “strong is capped by SkilledAutoPlayer” note in ai/skill_profile.gd).

  • Weapon FX v2 — foundation + batch 1 (Chris, 2026-09-28) — ✅ DONE (2026-09-30: merge 10cfb863 + requirements commit): merged as reviewed per Chris’s T16 sign-off; PrewarmPass family 12 covers the six look pipelines + the legacy plasma quad (switch-gated); VisualLook.weapon_fx_v2 default ON; every look material saved as fx/weapons/<look>_mat.tres (the branch’s code-built materials would have compiled ~1 s/shader on device — caught by the baker guard at the gate); bolt_impact has its own BOLT_IMPACT_CAP; windowed shader compile-checks clean under both renderers; ui-sweep baselines re-blessed (the 2026-09-24 ones predated Plasma Nova + the legacy Blade arc fix — drift verified deterministic + flag-independent before blessing; menu/shop captures unchanged). Spec docs/superpowers/specs/2026-09-28-weapon-fx-v2-design.md. Per-weapon shader looks that grow with level and transform on evolution; batch 1 = Blaster (all bullets), Orbit Shards, Blade, Scythe, Nova. Fixes Blade/Scythe 360° evolutions drawing a ~97° crescent. Later batches (Beam width bug, invisible Firestorm / Thunder Wake zones, Chalice element colour) ship in later updates. Plan docs/superpowers/plans/2026-09-29-weapon-fx-v2-foundation-batch1.md, implementation merged from branch weapon-fx-v2-batch1 (2026-09-30). T16 sign-off (Chris, 2026-09-30): MERGE AS REVIEWED — orbit perf accepted, void accent kept; bolt_impact gets its OWN frame budget (BOLT_IMPACT_CAP 16; Chris’s call after the shared-vs-separate explanation). Merge requirements, same day: (1) the branch predates PrewarmPass — its run-start draw families (v2 PlayerBoltRenderer + the fx/weapons/ looks) go into render/prewarm_pass.gd alongside this merge, or they PSO-link on device at first bullet/slash; (2) windowed shader compile-checks under BOTH rendering methods (the headless gate never compiles shaders — bit this branch’s own Task 13); (3) grep all tests relying on the VisualLook.weapon_fx_v2 default before flipping it, and re-bless affected ui-sweep baselines. 223 device feedback → 224 (dim): Chris on 223: the looks overload the iPad and “make the screen very white”. Data: iPad render_ms 14.7→24.4 with v2 on; ~57 hitch rows in one attract session, mostly pipelines: 0 + proc_ms 84–195 ms (main-thread spikes, incl. level-up frames), 3 rows actual PSO links (142/101/101 ms); ATV 50 fps but 3 first-use stalls (1747/652/602 ms) despite family 12 prewarm — consistent with the standing iPad bake-coverage gap (thin stock bake compiling the six NEW shader families from source on first run; per the 221→222 precedent the on-disk runtime cache should settle this over the next launch — VERIFY on the next session) plus real steady-state additive fill (render_ms). Fix shipped: BASE_DIM 0.75 — one knob on WeaponFxLayer multiplying every v2 look’s final rgb (alpha untouched; player_bolt + legacy plasma already dimmed), warp dim scales by it. Commit dec86688, deployed as BUILD 224 both devices. VERIFIED on iPad same day (D1 attract runs, enemies 40-200): 223 642 hitches avg frame 25.2 ms → 224 session 1 exactly 2 (one-time PSO links: 371 ms new:blaster_fire ×4 pipelines at t=2s, 241 ms new:nova at t=33s — both low proc_ms, the classic link signature, NOT the 223 proc storm) → 224 fresh session 2 zero hitches, 120 fps, avg frame 8.4 ms (better than the pre-v2 222 attract baseline of 14.3). The 223 storm is confirmed a first-session-after-install source-compile artifact (thin stock bake meeting six new shader families); it settled into the on-disk runtime cache, exactly the 221→222 precedent. Still open: Chris’s eyeball on the toned-down look (0.75 first guess, one constant), ATV first-use stalls re-check on 224 (needs a real run — its deploy only reached the menu), and a later-pass look at why family-12 prewarm didn’t absorb the two session-1 links (lit-run vs unlit-menu pipeline variant, or warm-window race — cheap to fix but session 2 shows the warmed cache makes it near-invisible; the sweep later VERIFIED family 12 warms under the same lit state as in-run, so the links were plain first-use). 225 (nova white-hot trim): Chris on 224: “the white hot nova is still a little too white hot when it has so many together” — the stacked additive white terms (ember white dots, core ×1.6, evolved shock ring ×2.0) trimmed ~25% (78cd1816). Eyeball verdict pending. ATV deep-run status (2026-09-30): the 223 ATV attract ran 287 s clean and DIED one wave short of the deep endgame; with the TV left on, 224+ attract runs reach 300-630 s (Accumulator elite + BOMB cascades + all six evolved v2 looks) and melt to fps 7-16, render 75-109 ms at 3-8 enemies — GPU fill-bound (the same frame is ~8.5 ms on the iPad; M5 fork-binary probe shows no engine pathology). ADAPTIVE_QUALITY := false since 2026-06-28 means no ladder catches it (Chris: enable the ladder only after no-tradeoff levers are exhausted, or make the ladder subtler). Shipped no-tradeoff batch (BUILD 226, 39ab812d + c7265389, catalog perf-data/2026-09-30/analysis/no-tradeoff-sweep.md): Toby’s web capsules (elite landing 33 webs → 4; collision one segment test; circles byte-identical, baselines intact), batched look rebuilds (was up to 17 rewrites/frame on the evolved blaster build), frozen-frame enemy-sync skip (the 84-138 ms proc frames’ waste), change-gated ScreenFeedback pushes, dead StoryMapRenderer redraw. These remove the CPU half; the fill half needs a taste call (ranked levers: nova corona quad extent, bolt glow trail extent, orbit evolved arcs, dmgnum live cap — or the ladder). 226 regression → 227 fix: 226 melted the iPad too (fps 6-17, render 80-163 ms at t≈295, where 224/225 both ran 103-119 at the same window) — a real regression, not seed variance. Root cause: web_instance built capsule quads as Transform2D(rot, size, rot, pos) and the 4-arg form’s THIRD arg is skew, so every capsule web was sheared by its own angle (a 45° web = a giant additive parallelogram; circle webs at angle 0 were unaffected, hiding it from the circle-pinned tests and the eye). Webs-present render: 16.4 ms (224, 299 webs) → 81.1 ms (226, 104 webs). Fix b3b43832 (BUILD 227): explicit basis construction (_web_transform), tests now pin the RAW basis axes — get_rotation()/get_scale() decompose the basis and would still pass a sheared transform — and the hot loop is actually inlined (226’s commit claimed it). 227 verified on iPad: deep window 93-120 fps (where 226 hit 7-17), 400 webs at 13-16 ms render (vs 226’s 81 ms with a quarter as many). ATV 227: avg 44 fps / render 30.6 / sim 5 — CPU clean, fill wall remains (the taste call above). 228: evolved-nova trim round 2 (Chris, ATV Supernova “adjust that weapon some more”): ramp top off pure white, embers ×0.85 evolved, shock ring 1.6→1.3, corona 1.4→1.05 later linger, LIFE_EVOLVED 0.70→0.60. Base nova untouched. + per-sample loadout telemetry: samples.weapons (“nova*,orbit”, * = evolved) on every 5 s row, so perf dips correlate with the build mid-run (runs only land at end; the ATV attract picks nova nearly every run, the iPad almost never — the fingerprint ends that ambiguity). D1 ALTER + Worker 94ab833c deployed before the build, per the schema deploy order. The fingerprint’s first catch (the real ATV/iPad fill wall): the SAME iPad run went 11.7 ms render → 91.5 ms (fps 112 → 30) the moment nova evolved, at 10-50 enemies, webs 0, draws low, render_cpu_ms 0.4-1.0, sim normal — GPU back-pressure from the evolved Supernova alone. It was hidden until now: 226’s melt was web shear AND this (a nova* run), and 227’s clean verification run rolled a no-nova build. Evolved quad = 2 × 1.35 × (180 × 1.8 × radius mods) px ≈ full-screen on the iPad panel, while its content ends at ~1.14 — ~42% of the quad paid full fbm + 21-ember + 6-accent-motif cost for invisible pixels. 229 (e22fc541): evolved early-out at d>1.16 before all heavy work (softened by an evolved fade; base form untouched), ember loop gated to ember reach (lossless — the exp terms are e^-500+ out there), evolved ember budget 14→17 (was 21). Probe fixes: level-up deadlock unstick (frame_probe’s tick-N+1 fix) + dead-look rejection now only over ACTIVE weapons. Attract pauses Chris saw (2026-10-01): by design — each level-up freezes the sim and the AI picks after a 0.8 s beat (_auto_pick_timer), deep runs chain 2-3 picks (2-3 s pauses); 226’s frozen-frame sync skip made the world stop fully during them (previously it kept subtly animating). One-time PSO compile hitches ride the first evolved-nova draw (976 ms ATV, “new:nova”). 230 (111106b6) — Supernova rework (Chris: “increase its dmg, add dmg over time, halve or more its rate of fire”): evolved nova = big/slow/remnant — cooldown ×2.5 (was ×0.6, it previously fired FASTER), damage ×7 (was ×1.8; burst DPS ≈ par with the old ratio), and each evolved pulse drops a burning remnant zone at the pilot position (existing terrain-zone infra + ZoneRenderer element tint; radius 0.55×area, dps 25% of the burst per second, default 2.4 s). The slower cadence is ALSO the evolved fill-wall fix — on-screen Supernova overlap scales 1:1 with cadence. spawn_zone gained optional radius/duration/dps params (Wildfire unchanged, byte-identical); zones are NOT in state_checksum and no baseline evolves nova → determinism verified unchanged. New sim tests tests/test_weapon_nova.gd.

  • Telemetry v2 (Chris, 2026-09-28/29) — code ✅ DONE (Tasks 1-11 merged, last db326aa); the Chris-gated steps G1-G13 below are open — spec docs/superpowers/specs/2026-09-29-telemetry-v2-design.md, plan docs/superpowers/plans/2026-09-29-telemetry-v2.md (task merges in git log --grep "telemetry v2"). ⚠️ Nothing v2 goes to testers before the plan’s Chris-gated steps G1-G13: D1 migration → bash telemetry/deploy.sh --check-only → deploy → smoke; App Store privacy label from marketing/appstore/compliance.md + site privacy notice (G5); on-device MetricKit (G6), render_cpu_ms (G10) and ATV Dev boot with the Sentry addon fetched (G11). Sentry DSN (G7, remove the .ios call-stack override) and G13 only if Chris wants Sentry live. First Dev iPhone install needs a full iOS export (see dev-tools.md § Telemetry v2). Status 2026-09-29: ✅ G1-G3 (migration, Worker b6a45439, smoke), ✅ G5 (12-month retention cron 31c7de5 + privacy page live), ✅ G10 + G11 (ATV Dev: clean boot, render_cpu_ms ~1.5 ms), ✅ G7/G13 decided: Sentry stays OFF for 1.0.1 (Chris; revisit only if MetricKit crash data proves insufficient). Open: G4 (Chris, ASC label), G8 (Chris runs git add -f .claude/skills/; auto-mode blocked it), dashboard header eyeball. G6 check 2026-09-30 (not yet passable, blocked on the iPhone): zero metrickit rows in D1 events (expected, not a failure). Verified from here: the sink IS in the current iOS Dev export (DerivedData-dev binary built 29 Sep 21:17 links MetricKit weak + carries BHMetricKitSink), and OS.get_cache_dir() == NSCachesDirectory on iOS is CONFIRMED — the iPad Dev container’s Library/Caches holds our canary.txt (the plan’s [Likely], closed). But the iPad’s BUILD 210 install (launched 29 Sep 20:16) predates the 21:17 sink build, so that install can never produce rows; the sink-carrying install went to the iPhone (no launch event from iOS/iPhone16,2 since BUILD 206, i.e. it hasn’t been launched since install, and the device was unavailable to devicectl at check time). Remaining steps when the iPhone is next available: launch the Dev app, background, relaunch the next day, confirm mk-*.json appears then vanishes in Library/Caches and a metrickit row lands in events (devicectl files cmd in plan G6). Low follow-ups from the Task 6 review (non-blocking):

    • render_cpu_ms skips UPDATE_WHEN_VISIBLE/WHEN_PARENT_VISIBLE SubViewports (threat radar ui/threat_radar.gd:204, codex card) — count them when visible. net/telemetry.gd _sync_measuring.
    • One stale sample per run: a persistent SubViewport keeps its last measurement after measuring is switched off; a new mid-run SubViewport reads 0 once. Optionally skip the first read after enabling.
    • Real GPU time on Apple devices needs Metal timestamp support in the ~/Claude/godot-tvos fork (MTLCounterSampleBuffer); gpu_ms stays null on Metal/WebGL until then. Optional.
  • Warden rebuild (Nova look, FX, smarter Survival AI) (Chris, 2026-10-01) — ✅ DONE (75074990..45606e8a)

  • Warden: tutorial swing that misses draws no slash under warden_v2 ✅ DONE (6e8b1232) (final review M2, 2026-10-02). WardenFx spawns the crescent on boss_swing_start (Survival-only) or boss_swing_hit (hit-only), so a dodged tutorial swing shows the ring then nothing. Render-only fix: latch swing_active rising edge in WardenFx.update_boss when no start event arrived.

  • Warden: far-band lunge barely reaches (M5). ✅ DONE (6e8b1232): LUNGE_LENGTH 620 -> 760 (registry bounds stay derived 76..7600; no CROSS_RULES). Far PREFERRED_RANGE 700 vs LUNGE_LENGTH 620 (reach ~700). Tuning call: LUNGE_LENGTH ~760 or far pref ~600 (both remote-tunable / static).

  • Warden: device feel-check of the Survival AI (M7). Movement/aggression are headless-verified only and NOT behind warden_v2 (it gates looks only). Needs an ATV/iPhone playtest before a store build; also eyeball bolt size (Task 6 note), swing crescent thickness, enraged telegraphs now >= 0.45 s.

  • Warden: spec deviations to acknowledge (M4): approach dash kept (gated beyond target range) instead of replaced by the lunge; reposition happens in APPROACH not REST; deck weights + PREFERRED_RANGE are not remote-tunable (static Dictionaries); enraged pupil-narrowing not implemented. Classic look (warden_v2=false) misdraws SLAM/FAN/LUNGE telegraphs (M3) — ✅ M3 fixed (6e8b1232): BossRenderer.classic_overlay(). (range-aware attack deck, 3 new attacks, strafing + phase 2, per-attack damage telemetry, Nova body, projectile + telegraph looks behind VisualLook.warden_v2; spec docs/superpowers/specs/2026-10-01-warden-rebuild-design.md). Open: Chris eyeballs the captures, scores the warden_* roster entries and feel-checks aggression (all weights/speeds are remote tunables).

  • Chris’s BUILD 248 ATV feedback (2026-10-02) — Warden: “overall massive improvement”. New items:

    • Orbit Shards VFX stays on screen after the weapon is removed via the Remote Console (bug). ✅ DONE (f2e2e239, branch orbit-fx-fixes): dev_remove_weapon only erases the id; the arsenal keeps the WeaponOrbit + frozen positions, and both orbit renderers gated on the instance, never on active_weapon_ids. Same pattern in other persistent weapons: see the next item.
    • Orbit Shards: one FX layer drifts over time / looks stretched when evolved ✅ DONE (branch orbit-fx-fixes): not the phase/extent – shader TIME (grows all session) fed fx_noise’s hash21, whose float32 precision collapsed (~4 hash values at 30 min): the lightning crackle stopped and the swirls went blocky/streaky. fx_noise now wraps inputs into NOISE_PERIOD (all v2 + Warden shaders). Repro: tools/weapon_vfx/orbit_drift.tscn. Needs Chris’s on-device confirm after a long session.
    • Evolved Orbit (Event Horizon) look rework: less of a solid ring, more distinct orbs with long tails that read as solid (bh-vfx-iteration, toggleable A/B). ✅ DONE (branch orbit-fx-fixes): comet orbs + flat-topped tapering tails, lens reduced to faint broken arcs; VisualLook.orbit_comets (default ON) + Debug Settings / Remote Console row “Orbit Comets (A/B)”. Awaiting Chris’s verdict.
    • Orbit Shards knock back enemies and enemy projectiles, scaling up with weapon level/upgrades (gameplay change; touches determinism if baseline runs field orbit). ✅ DONE (branch orbit-knockback): radial-outward shove through the shared EnemyPool.knockback impulse (bosses immune, elites ×0.5, brute/wyrm/mid-dash/Halo immune as for the manual weapon) + speed-preserving outward deflect on enemy bullets; scales with WeaponVfx.invested and evolution. 6 WeaponOrbit.* remote tunables. No baseline fields orbit — hashes unchanged. Needs an on-device feel-check of the numbers.
    • Every console-removed weapon leaves its deployed state behind (bug, follow-on to the Orbit item): turret bodies, Thunder Wake puddles and Firestorm zones stayed drawn; Chronowell bubbles stayed drawn AND kept slowing enemy bullets. ✅ DONE (branch weapon-removal-cleanup): Sim.dev_remove_weapon (and dev_reset_upgrades) now call PilotArsenal.clear_deployed (per-weapon audit in its comment) and drop shared Wildfire fuses / Railgun mark pops once no pilot wields them; zone + turret renderers only draw equipped weapons. Dev path only, baselines unchanged. Tests: tests/test_dev_remove_weapon_cleanup.gd. Review round 1: also gives back an evolved Momentum Core’s Overdrive +10% pilot.speed (only reverted inside its update()); no other weapon holds a pilot stat between ticks.
    • Co-op (gated off): two Chronowell pilots decay every bubble twice as fast – each pilot’s WeaponChronowell.update ticks ALL of sim.chronowell_zones, not just its own (found in the weapon-removal-cleanup review). Fix before co-op ships: tick only z.pilot == pilot zones.
    • Co-op (gated off): turret bodies are only drawn for pilot 0 – main.gd syncs turret_bodies from sim.turret (P1’s arsenal); a P2 turret fires from an invisible unit.
    • Tutorial walls should look like the Survival arena walls (story-map walls vs ArenaBorder). ✅ DONE (4dd6c33a, branch tutorial-walls) — walls/sealed gates/false walls drawn by the shared border_look.gdshaderinc (union SDF, one MultiMesh); Debug Settings “Tutorial Walls (A/B)”; captures docs/captures/tutorial-walls-2026-10-02/. Needs Chris’s eye + ATV frame-time check.
    • Chalice pools feel too small; make them bigger and look better (Chris, 2026-10-07). ✅ DONE (branch chalice-pools, not merged) — bible area 90 -> 130 (a real DPS buff, ~2.1x area, not compensated) + live WeaponChalice.RADIUS_MULT; ChalicePoolLook liquid pool behind Debug Settings “Chalice Look (A/B)”; half-res, crowd/QM-L2 lite, optional PerfCuts.chalice_lite (off). M5: +0.5-0.7 ms for 8 pools vs the hexagon’s ~0.1 (rendering-and-performance.md “Chalice”). Needs Chris’s eye in motion (stacked evolved pools are bright) + ATV frame-time check. Also fixed there: each pool poisons with its own cast element, not P1’s (co-op).
  • Rift Bolt detonation (2026-10-12) ✅ DONE (merge rift-detonation): every weapon hit adds an element stack (cap 6); a Rift Bolt hit detonates them (bolt dmg × 0.25 × stacks, half splash within 70 px, bosses half). Rift Bolt base damage 6.0 → 5.25 (calibration: docs/research/2026-10-12-rift-bolt-detonation-calibration.md). Spec docs/superpowers/specs/2026-10-12-rift-bolt-detonation-design.md.

    • Chris: on-device feel-tune (DETONATE_, RINGS_MAX 3 vs 6, RIFT_DETONATE_SFX_).
    • Chris’s call: Chalice pools + Orbit add a stack every tick (cap in ~0.1 s) — keep, or add a per-source stack interval?
    • Measure 6 aura rings × full swarm on Apple TV (frame time); RINGS_MAX 3 is the live fallback.
    • Chris’s call: tut_riftbolt detonation line plays while other guns are off (no detonation seen there).
    • Start a new learning-autopilot persona epoch (AUTOPILOT_VERSION 1 → 2) — needs Chris’s OK.
    • Detonation SFX has no cooldown (pierce through a stacked crowd fills PER_FRAME_CAP).

3c · Remote-control surface — the no-resubmit program (Chris, 2026-10-01)

Section titled “3c · Remote-control surface — the no-resubmit program (Chris, 2026-10-01)”

Goal: the next submission (1.0.1) should carry everything that lets us change the game WITHOUT another App Store review. The 1.0 build (BUILD 206) carries only remote-config batch 1 (12 tunables + the bible overlay, remote-config/manifest/build-206.json) — everything below applies from 1.0.1 (BUILD 249, remote-config/manifest/build-249.json, 494 tunables) on. Corrected 2026-10-02. Governing constraint: Apple 2.5.2 — download DATA only, never executable code (no GDScript, no shaders). Reference: docs/architecture/remote-config.md.

Already built, ships with 1.0.1, zero extra work:

  • 84 sim tunables (remote-config/manifest/build-230.json) + every numeric bible field via BibleOverlay (weapon/enemy/boss stats, upgrade/mod magnitudes, shop prices + cost growth).
  • Rollback (publish {}), per-channel patches (--channel testflight), the 30 s crash-guard blacklist, live-event windows and A/B cohorts are Worker-only changes (client already sends install_id and polls /config at boot/menu/resume/run-start).

Work queue for this program (each its own chunk, in order):

  • RC-1: Boolean feature flags / kill switches — ✅ DONE (34873ecc). "type": "bool" registry entries ride the same fetch/reset/apply/config_hash pipeline (no bounds); first flags: Sim.DIMENSIONS_ENABLED (boss-death dimension offers) and DamageNumbers.ENABLED (render-side — entries may now point outside sim/). publish-config validates true/false only, the manifest types them "bool", the liveness probe flips the bundled default. A new flag is now a one-line registry entry + a guard at the consumer.
  • RC-2: Maintenance / min-build banner — ✅ DONE (f1c94c6e; Worker deployed 2026-10-01, version 179643e1). The Worker merges a global notice (KV key notice) into every /config response — including builds with no patch; the start menu shows it as a glass banner (cyan info / amber warn), filtered per build via min_build/max_build. Publish: node scripts/publish-notice.mjs (--clear removes). Comms, not config: excluded from config_id/crash-guard/config_hash; a notice failure never breaks a patch.
  • RC-3: Remote strings — ✅ DONE (this commit). Display-copy bible strings are overlayable via BibleOverlay.STRING_FIELDS (weapons.name, enemies.name, mods.name, meta_upgrades.name, meta_upgrades.desc) — non-empty, single-line, ≤200 chars, stamped into config_hash. Machine-key strings (mods.effect, weapons.element, …) stay unpatched on purpose. Manifest regen: 636 bible fields. UI chrome strings (menu labels etc.) remain hardcoded — add to the whitelist only when a real rename need appears.
  • RC-4: Render-side tunables — ✅ DONE (this commit). QualityManager.ENABLED (bool flag — a patch can now switch the adaptive ladder on remotely; replaces main.gd’s ADAPTIVE_QUALITY const) plus the ladder thresholds/timings (DOWN_FPS, HARD_DROP_FPS, DOWN_DWELL, PROBE_DWELL, PROBE_BACKOFF, SETTLE, WARMUP, EMA_ALPHA) and NovaBurstLook.LIFE_EVOLVED. New CROSS_RULES pair HARD_DROP_FPS <= DOWN_FPS, mirrored in publish-config.mjs. Liveness via tests/test_render_remote_config.gd (the probe can’t see render-side values). Glow/bloom and particle budgets deliberately deferred — no incident history, and the ladder flag already covers the “strip fidelity remotely” case. (docs + tests in this chunk)
  • RC-5: Remaining sim tunable batches ✅ DONE 2026-10-01 (skill bh-tunable-batch): enemy behaviours/attacks, drone director, rest of sim.gd — all three batches shipped. Batch 6 (rest of sim/*.gd) ✅ DONE 2026-10-01 (1df3b4f5, merged f68ce4f0, manifest build-236 = 463 tunables): 186 keys shipped across Sim/SpawnDirector/bosses/DimensionHazards etc., 19 reverted dead (4 BossWarden tutorial/story-only paths, FUNZO_ZONE_DURATION + LASER_ACTIVE_S visuals-only, the rest listed in the remote-config doc), 180 probe-LIVE + 6 wiring-pinned, 10 new CROSS_RULES. Everything remotely changeable now is. Batch 4 (enemy behaviours/attacks) ✅ DONE 2026-10-01 (05b9a025, merged 5d523a05, manifest build-236 = 195 tunables): 92 keys shipped across EnemyBehaviors/EnemyAttacks/ EnemyChipFire, 9 reverted dead (CUSTOM_* ×8 dev-console-only, ELITE_ZAPPER_SPREAD rosterless), 7 new CROSS_RULES, 30 probe-LIVE + 62 wiring-pinned (probe scenario-blindness documented — a roster/wave-pace probe override is the clear next tooling investment). Batch 5 (drone director) ✅ DONE 2026-10-01 (7ed3930e, merged, manifest build-236 = 235 tunables): all 40 drone keys shipped, 0 reverted; probe gained --meta-levels/--drone-loadout flags so runs field all four drone classes (38/40 probe-LIVE, 2 overcharge-heal keys wiring-pinned).
  • Balance bug (found by the batch-5 liveness work): a bought Logistics drone overcharge never fires without the endurance upgrade — its 8 s cooldown equals the drone’s 8 s life. Cooldown and life are now both remote tunables, so this can be patched without a build once Chris picks the intended values.
  • RC-6: Remote URLs — ✅ DONE (this commit), rescoped on a finding: the game binary has NO hardcoded user-facing links to un-hardcode (the only hardcoded URLs are the Worker endpoints themselves, which can’t be remote-served — bootstrap problem; store-listing links live in ASC metadata, changeable without a build). What shipped instead: the notice banner’s url field (validated by publish-notice.mjs since RC-2 but dropped client-side) is now carried end-to-end — notice_for_build keeps https-only urls and the start menu renders a tappable CTA line (Platform.can_open_links(); hidden on tvOS, which has no browser). Discord/support/privacy CTAs are now publishable with any notice. A general links map was deliberately not built (no consumer = dead knob; rebuild on this channel if an about/links UI ever lands).
  • RC-7 (deferred, big): R2 asset pipeline — download+cache+fallback for textures/audio (data, so 2.5.2-legal): seasonal reskins, new music, texture-swap VFX variants without a build. Design sketch (2026-10-01, for whoever picks it up):
    • Manifest: a KV-served JSON (same Worker, e.g. assets key merged like notice) listing optional packs: {id, version, sha256, bytes, target} per file. Versioned like config_id.
    • Client: after the config fetch, diff the manifest against a user://assets/ cache; download missing/outdated files over HTTPS from R2, verify sha256 BEFORE use, and only then swap. Any failure (offline, bad hash, partial) silently keeps the bundled asset — the game must ship complete and never depend on a download.
    • Consumers opt in per call site: a texture slot (arena background variant, VFX sprite) or music track asks AssetCache.get(id) and gets the remote file if verified, else the bundled fallback. No manifest entry = bundled, always.
    • Hard rules: data files only (no .gd/.gdshader/.tres with script refs — 2.5.2); a size budget + cache eviction (iOS cellular); never block boot or a run start on a download; downloads reported via EventQueue.push (a new data kind → privacy page + ASC label update).
    • Why deferred: no consumer needs it yet (no seasonal content, no alternate soundtrack), so it would ship unexercised — build it when the first real asset swap is wanted, not before.
  • RC-8: Remote background theming ✅ DONE (ac83ebd2) (Chris, 2026-10-01: “can backgrounds be changed this way” — scope: FULL theming). All backgrounds are procedural, so no RC-7 downloads needed: (a) numeric knobs (parallax, densities, counts, scales, alphas, speeds) via the RC-4 render-tunable pattern; (b) variant choice — already data-driven (set_variant_by_name) and live-switched by Debug Settings — a remote int key (-1 = AreaDefs default, 0-4 = force Grid/Starfield/Nebula/Aurora/Galaxy); (c) a NEW "type": "color" in the tunables pipeline (hex string in the JSON patch → Color.html client-side, publish-config validation, manifest typing) covering: ArenaBackground globals (grid lines, border, starfield, galaxy core/outer), NebulaBg col_lo/col_hi + intensity (mind the HDR pow(1/2.2) pre-compensation trap, nebula_bg.gd:78-85), per-area Dimension tints (AreaDefs.tint overrides), aurora band hues. Never remote-able: the shader math itself (FBM/twinkle/aurora-wave code — 2.5.2 + needs a build). Full inventory: this session’s explorer report (per-system knobs with file:line).

Hard limits — no submission can buy these: new content needing code wiring (new weapons/enemies/bosses/mechanics, adding/removing bible entries); downloaded GDScript/Lua/shaders (2.5.2 violation); structural fields (enemies.*.radius, meta_upgrades.*.max_level, bundled-0 fields, const-folded/match-gating consts — full table in remote-config.md); beyond the 0.1×–10× tunable bounds; privacy-significant collection changes (ride a submission + the ASC label).

Outside the binary — App Store Connect, no new build ever needed: promotional text (anytime, no review), price/territories/phased release, In-App Events, custom product pages. Screenshots / description / keywords ride a version review.

  • Bump application/short_version to 1.0.1 in the tvOS + iOS presets (only after Apple approves 1.0 — a rejected version is reused, not bumped).
  • Tag the shipped 1.0 commit once approved.