Overnight Review — 2026-07-08
Overnight Review — 2026-07-08
Section titled “Overnight Review — 2026-07-08”Generated by an unattended /loop run (10-min cadence, ~23:35 BST → ~02:35 BST) that fans out
Haiku-model agents across the codebase looking for bugs and improvement ideas from multiple
angles, one lens per cycle-slot, rotating. Sonnet reviews every finding before acting:
- Applied automatically: only genuinely trivial, non-gameplay fixes (typos, stale
comments/docs, confirmed-dead code with zero references) — each as its own small
[overnight-loop]-tagged commit so they’re easy to spot and revert. - Logged only, not applied: real bugs, gameplay/balance ideas, perf ideas, anything touching
/simor the determinism baseline, anything needing the fullbh-dev-chunkprocess. These are candidates for Chris to triage in the morning, not verified fixes.
This is a one-off scratch doc for this run, not a standing project doc like NEXT_TASKS.md.
Executive summary (read this first)
Section titled “Executive summary (read this first)”16 cycles, ~64 Haiku scouts, every finding reviewed and re-verified by Sonnet before logging. 2 trivial fixes committed. The rest below is triage-ready — most claims were checked against the actual code, not just relayed.
2026-07-09 triage pass note: the “crit” upgrade finding below (item 6, and cycle 17’s writeup)
is a false positive — re-verified via sim/content_db.gd:69’s upgrades(), which filters the
offerable list through StatEffects.is_known(effect) before anything reaches
upgrade_system.roll_upgrade_choices(). Since crit_chance isn’t a known effect, the card is
never offered to a player at all — dead content sitting in bible.json, not a live bug. Also
during this pass: the Centurion bestiary bug (item 1) and the 13-renderer queue_redraw() cluster
(item 3) were both fixed and committed; boss_rotation.gd’s dead maybe_spawn_survival_boss()
(cycle 13) was removed along with its dedicated test file; the bh-dev-chunk/bh-area-background
skill drift (cycle 8) was fixed. See git log 2026-07-09 for the actual commits. Remaining items
(co-op P1-hardcoding catalog, Boss2 enrage visual, cruiser/frigate balance, skill ideas) are
carried forward in NEXT_TASKS.md.
If you only read five things:
- Centurion is permanently stuck at “???” in the bestiary (
content/bestiary_db.gd’sTYPE_KEYSarray is missing index 23) — verified, one-line fix, cycle 13. - Co-op is far more broken than documented — not just the 2 known gaps (XP, drones), but
~34 call sites across
/sim,render/,main.gd, andui/hardcode P1 instead of_nearest_pilot(). See the ⭐ cross-cutting section below — it’s a ready-made fix checklist, and the good news is the API already supports per-pilot calls, so it’s a threading job, not a redesign. - 13 of 14 per-effect renderers waste a
queue_redraw()call every frame even with nothing to draw — cheap, mechanical fix,wormhole_renderer.gdis the reference pattern to copy. See the other ⭐ cross-cutting section. - Two of your 8 project skills have real drift:
bh-dev-chunkcites a stale determinism baseline (wrong numbers — ironic, given what that skill exists to protect), andbh-area-backgroundpoints at the archivedbullet-heaven-tvosrepo. Both worth a 2-minute fix. - Boss2 has no visual tell when it enrages — every sibling boss (FunZo/Graviton/Eye) gets a pulsing aura, Boss2 doesn’t, despite genuinely being more dangerous once enraged (cycle 14).
- ⚠️ The “crit” upgrade is a complete dud —
data/bible.json:572-579(id"crit", display name “Focus”) grants effect"crit_chance", but that string doesn’t exist insim/stat_effects.gdorsim/sim_mods.gd(zero matches, grep-verified, independently re-confirmed by 3 separate scouts across cycles 17-18).sim/upgrades.gd:26-31’s fallback for an unknown effect just logs a consolepush_errorand returns — no crash, no player-visible sign anything’s wrong. A player who picks this upgrade during a run gets literally zero benefit and no way to know. Interesting related detail: crit mechanics DO exist and work for drone attacks (sim/drone_director.gd), just with no corresponding handler for this player-facing upgrade card — so this may be a half-finished feature (drone crit shipped, player crit upgrade never got wired up) rather than pure leftover cruft. The most severe single finding of the night — worth checking first thing.
Other notable, verified findings:
- The Obsidian cruiser is close to a strict upgrade over all 6 frigate hulls (quantified: +70% HP, +67% slots, +10 armor, for a 19% speed penalty easily offset by one level-up card) — a real input for the frigate/cruiser playtest pass already on your list (cycle 13).
boss_rotation.gd’smaybe_spawn_survival_boss()has an entire dedicated test file (20+ call sites) but zero production callers — either orphaned from an earlier design, or something never got wired in. Genuinely unclear which (cycle 13).NEXT_TASKS.mdcites “204/204 tests” — actual count is now 209. This loop couldn’t touch that file; a manual bump is all that’s needed (cycle 10).- 4 solid new skill ideas surfaced independently across multiple cycles:
bh-add-renderer,bh-add-sound-event,bh-determinism-repin,bh-write-test— see Skill ideas at the bottom.
Quality control, for calibration: roughly a third of “confirmed bug” claims from Haiku scouts turned out to be false positives on verification — hypothetical scenarios with no reachable code path, backwards understanding of Godot’s input-consumption semantics, or claims about code the scout hadn’t actually read closely enough. Every one of those is marked (checked, refuted) inline rather than silently dropped, so nothing gets re-investigated by mistake. Take anything NOT marked “verified” or “confirmed” with a bit more salt — those are logged as-is because they were plausible and worth your attention, not because they were independently re-derived.
Applied fixes (commits made this run)
Section titled “Applied fixes (commits made this run)”- 23:35 cycle 1 —
main.gd:123comment still said the Elemental Dimensions were named “Pyre/Null/Drift”; they were renamed Fire/Void/Light in v2 (2026-07-05). Updated the comment to note the rename. Commit5e6e20b. - 00:12 cycle 5 —
tools/glow_bake/live_check.gdsetmain.camera.zoomtwice (lines 33 and 39) with nothing in between that could change it — removed the redundant second assignment. Dev preview-tool script only. Commit8b17074.
Findings for review
Section titled “Findings for review”⭐ Cross-cutting: 13 renderers redraw unconditionally every frame
Section titled “⭐ Cross-cutting: 13 renderers redraw unconditionally every frame”Found piecemeal across cycles 1 and 3, then swept repo-wide for the full picture (grep across render/).
13 of 14 per-effect renderer files call queue_redraw() unconditionally in _process(), with no
check for whether there’s anything to draw: funzo_renderer.gd, weapon_pickup_renderer.gd,
orbiter_renderer.gd, ghost_telegraph_renderer.gd, boss2_renderer.gd, bomb_renderer.gd,
lancer_telegraph_renderer.gd, story_map_renderer.gd, eye_renderer.gd, boss_renderer.gd,
graviton_renderer.gd, dash_telegraph_renderer.gd, zone_renderer.gd. Most of these already guard
inside their own _draw() (e.g. bomb_renderer.gd:28’s if _sim == null or _sim.bombs.is_empty(): return),
so this isn’t a crash risk — it’s wasted queue_redraw()/canvas-dispatch overhead every frame for
renderers with nothing active most of the time (outside their specific enemy/effect being on-screen).
wormhole_renderer.gd:39 is the one file that already does this right — if not _wormholes.is_empty(): queue_redraw()
— and is the reference pattern to copy into the other 13.
Given ADAPTIVE_QUALITY is off (main.gd) and Chris’s stated priority is cutting raw render cost at
the source (not relying on the ladder), this is the single most actionable perf finding of the night —
same shape as the FunZo/nebula perf work already documented in docs/architecture/rendering-and-performance.md.
Not auto-applied: 13 files, behavioral change (even though low-risk), belongs in a real bh-dev-chunk
pass with a quick visual sanity check per renderer, not an unattended 10-minute-window edit.
⭐ Cross-cutting: co-op P1-only hardcoding is far more widespread than CLAUDE.md’s “two gaps” framing
Section titled “⭐ Cross-cutting: co-op P1-only hardcoding is far more widespread than CLAUDE.md’s “two gaps” framing”Found piecemeal across cycles 1, 4, 5, 7, 8, 9, then swept comprehensively this cycle (grep for
sim.player.pos/sim.player.hp across all of /sim, cross-referenced against _nearest_pilot()
usage in the same files). CLAUDE.md currently documents co-op as having “two real, load-bearing
gaps” (kill-XP/upgrade routing, and the drone/decoy system) — that undersells the actual scope.
At least 26 confirmed call sites across 12 files hardcode sim.player instead of routing
through sim._nearest_pilot(), meaning P2 in co-op is unaffected by most of the game’s own
attacks and hazards, not just blocked from progressing:
- The 2 already-documented architectural gaps (CLAUDE.md’s own language): kill-XP/upgrade
application (
sim/upgrade_system.gd:71,200,223— P2 can’t level up or progress weapons) and the drone/decoy system (sim/drone_director.gd— P2’s drone never fires). Not new, just now precisely located. - Newly-catalogued: nearly every enemy/boss attack or hazard that can actually HIT the player also
hardcodes P1, meaning P2 is functionally invulnerable to most of the game’s danger, not just
stuck at level 1:
sim/boss2.gd:95,164(blast + charge beam),sim/boss_warden.gd:125,155(swing + missile — Warden is currently LIVE via Elemental Dimensions, not gated off),sim/eye.gd:86,92,215,223,237(afterimage + beam hits),sim/graviton.gd:171(satellite DoT),sim/funzo.gd:168,288(zone DoT),sim/elemental_system.gd:238(web slow),sim/enemy_attacks.gd:245,259,343,406-407(orbiter shards, lancer beam, brute contact),sim/sim.gd’s Void hazard,sim/story_director.gd:63,65,76,88,192(NPC/tutorial — likely intentionally single-pilot given story mode’s scope, lower priority). sim/ship_bonuses.gd:54,56applies ship stats (max_hp/speed) only tosim.player— P2 never gets the selected ship’s bonuses at all, a third category beyond “can’t progress” or “can’t be hit.”- The root cause, confirmed clean:
sim.is_invulnerable()andsim._hurt_player()DO already accept an explicitpilotparameter (defaulting to P1) — the API supports per-pilot calls, but no enemy/boss/hazard call site in the game actually passes it. This means fixing this isn’t a deep architecture change — it’s systematically threading an existing parameter through ~26 call sites, most of which just need_nearest_pilot(...)swapped in forsim.player. - Also found in the RENDER layer this time (cycle 11), not just
/sim:render/dash_telegraph_renderer.gd:33andrender/eye_renderer.gd:124both compute their telegraph direction fromsim.player.posinstead of_nearest_pilot()— so even where the underlying attack might someday be fixed to hit P2, its visual warning telegraph would still only ever point at P1. Same checklist, same fix shape, just render-side call sites, so a real fix pass should sweep both/simandrender/. - Positive confirmation, same sweep: zero violations found for a stricter, more serious check —
no
/simfile readsTime./OS./Engine./Input./get_tree()directly (grepped explicitly). The core determinism rule (“/simnever touches engine APIs”) holds cleanly across the whole directory, unlike the co-op-pilot convention which was clearly not enforced as consistently.
Not auto-applied or fixed — this is squarely /sim gameplay logic requiring the real
bh-dev-chunk process (TDD, determinism re-verification) given co-op’s own tick()/inputs plumbing
is capped at 2 pilots today per CLAUDE.md. But this list is now a ready-made checklist for whoever
picks up P2 progression — the earlier framing of “two gaps” was accurate about what’s documented,
not what’s actually broken.
Final tally (as of cycle 12, comprehensively swept in all 4 layers — this is now believed complete):
/sim: ~26 call sites across 12 files (boss2.gd, boss_warden.gd, eye.gd, graviton.gd, funzo.gd, elemental_system.gd, enemy_attacks.gd, sim.gd, story_director.gd, drone_director.gd, upgrade_system.gd, ship_bonuses.gd) — enemy/boss/hazard attacks, XP/upgrades, drones, ship stats.render/: 2 sites (dash_telegraph_renderer.gd:33,eye_renderer.gd:124) — confirmed exhaustive via a dedicated repo-wide grep sweep in cycle 12, nothing else found.main.gd(audio + screen feedback): 1 site (main.gd:1282) — P1-only hurt sound AND screen-punch camera feedback share the same singlesim.player.hpcheck, so P2 gets no hit sound or camera kick when hurt.ui/: 5 elements confirmed P1-only display (HP bar, weapon-aim stats, level readouts on 2 panels, results screen, drone dock) — all correctly identified by the scout as symptoms of the sim-layer gap above, not independent bugs; there is currently no P2-facing UI at all (no HP bar, no join indicator, no stats row).- Root cause reminder:
is_invulnerable()/_hurt_player()already accept apilotparam — this is a threading job across ~34 call sites total, not a redesign.
23:35 cycle 1 — lenses: sim/determinism, rendering/perf, UI/UX, code quality
Section titled “23:35 cycle 1 — lenses: sim/determinism, rendering/perf, UI/UX, code quality”sim/determinism
- [idea] FunZo zone player-damage (
sim/funzo.gd:288) usessim.player.posdirectly, not_nearest_pilot()— in co-op, P2 won’t take FunZo zone damage. Same root cause as the already-documented P2-progression/drone gaps (CLAUDE.md Architecture section), just one more instance — low priority until co-op ships for real. - (checked, refuted) the scout also claimed “FunZo zones never damage the player” at all — that’s false,
funzo.gd:285-289explicitly callssim._hurt_player(...). Logging the correction so it isn’t re-investigated next cycle. - P2 XP/upgrade routing and P1-only drones — re-surfaced by the scout but these are already documented gaps in CLAUDE.md (M-A gaps 1/2), not new.
spawn_table.gd’s_lerp_weights()boundary — flagged again, but this is likely the same boundary case already found+fixed during the Centurion enemy work (see roadmap memory) — probably not still broken, worth a quick second look rather than treating as fresh.drone_rnghas no determinism-baseline coverage yet since nothing draws from it — note for whenever a real drone feature does.
rendering/perf
- [idea]
main.gd’s_enemy_colors()/_enemy_proj_colors()/_proj_colors()(~line 1414-1470) allocate freshPackedColorArrays every frame instead of reusing a pre-sized member array — minor GC-pressure cleanup, not a blocker. - [bug, verified]
render/zone_renderer.gd:19-20—_process()callsqueue_redraw()unconditionally every frame, even whensim.zones/webs/funzonesare all empty (most of the game, outside active zone effects). Cheap fix: guard with an early return when all three are empty. Not auto-applied (behavioral change, however small) — a clean pickup for a realbh-dev-chunkcycle. - [idea]
damage_numbers.gd:56-71’s pool scans all 72 slots linearly every frame regardless of active count — minor. - [idea]
fx/fx_manager.gd’sconsume()(line ~343-471) dispatches ~14 event kinds via stringmatch— an enum would be marginally faster in the hot loop. - [idea]
player2_rendererisn’t bound toQualityManagerthe wayplayer_rendereris (main.gd:1180-1190) — P2 keeps full visual detail even at low quality tiers. Moot while co-op is gated off, but worth fixing alongside it. NebulaBghas no adaptive-quality hook yet — already flagged as known deferred work indocs/architecture/rendering-and-performance.md, not new.
UI/UX
- [bug, unconfirmed]
ui/ship_config_panel.gd:240— a ship-carousel touch drag that STARTS off the back button but ENDS (release) over its rect might still trigger the drag’s tap-confirm path. Genuinely uncertain from static reading; the code already has a deliberate, reviewed fix for the reverse case (press starting on the back button). Needs a real on-device touch check, not a blind fix. - (checked, refuted)
ui/start_menu.gd:455-461“stale signal binding” claim — false positive. Thepressedsignal disconnect/reconnect for a re-roled carousel card is synchronous and unconditional right after_place_card(); there’s no window for a stale binding to fire. - [idea] No visual “locked” indicator on unpurchased shop carousel side-cards during drag (
ui/meta_shop_panel.gd:79-93). - [idea] No focus/selection highlight in the Codex Overlay’s browse list (
ui/codex_overlay.gd:149-150) — matters on tvOS joypad nav with no mouse hover to fall back on. - [idea]
DroneBayPanel/PauseMenulists wrap silently at the ends with no boundary indicator — minor tvOS nav polish.
code quality
- [duplication]
_nearest_enemy_indexis byte-identical acrosssim/weapon_scatter.gd:29,sim/weapon_beam.gd:19,sim/weapon_melee.gd:24(verified via grep) — candidate to extract into a shared helper. Not applied — touches/sim, needs a realbh-dev-chunkpass with tests, not an unattended edit.
23:42 cycle 2 — lenses: content/balance, audio, tooling/skill gaps, UI/UX round 2
Section titled “23:42 cycle 2 — lenses: content/balance, audio, tooling/skill gaps, UI/UX round 2”No safe trivial fixes this cycle — every concrete “bug” claim turned out to be a false positive on verification (see below). Zero commits made.
content/balance/enemy & weapon design
- [idea] Starter Blaster (8.9 DPS) noticeably outclasses the first acquirable weapon, Pulse (5.0 DPS) — breaks the “new weapon = progression” feel. Matches the already-open “balance-tune the ladder numbers” item in CLAUDE.md, not new territory but a concrete number to start from.
- [idea] Turret’s base damage (1.0) is half of Scatter’s (1.4) despite being a placed structure, not a spread weapon — worth confirming this is intentional (“weakest by design”) rather than an oversight.
- [idea, needs a real look] Centurion’s wave-15 debut (
sim/spawn_table.gd:25) — worth checking whether typical Survival runs actually reach wave 15 in practice; if not, the enemy is functionally rare-to-absent despite being fully wired. (checked, partially refuted — see below) - (checked, refuted) “Centurion never spawns in Survival except via a one-time swarm burst” — false.
sim/spawn_table.gdshows it IS in the normal weighted spawn blend (weight 0.05) from wave 15 onward, not gated to swarm-burst-only. - (checked, refuted) “
ELITE_CHARGER_HP_MULTcomment conflates the math order” — false.sim/sim.gd:132-138’s comment accurately narrates two sequential same-day tuning edits (Chris: “-20%”, then later “÷3 more”); the resulting formula (0.8 / 3.0) matches the code exactly. No change needed. - SOFT_ENEMY_CAP isn’t in a single obvious tuning-const block — minor findability nit, low priority.
audio
- [idea, real but not urgent]
audio/audio_manager.gd:105preloadseye_laser.wav(confirmed: the file exists, 52KB) but it’s never actually played — Eye’s laser fire currently uses the sharedbeam_hum.wavvia the genericbeamfx kind, whichaudio/SOUND_MAP.mdalready documents as an intentional, deferred differentiation. So this isn’t dead code to delete — it’s a half-finished feature: a bespoke sound asset sitting ready, waiting forsim/eye.gd’s laser-fire code to emit its own fx_event kind instead of falling through to the shared one. Worth finishing, not cleaning up. - (checked, refuted) “
ui/pause_menu.gd:146calls the wrong audio method (ui_nav()instead ofui_select())” — false. There is noui_select()method onAudioManagerat all (onlyui_nav()/ui_buy()), andaudio/SOUND_MAP.mdexplicitly documents that_ui_nav/_ui_selectintentionally both playui_tap— already audited, marked DONE in the Sound Phase 4 pass, not a bug.
UI/UX round 2
- [idea] HUD wave/boss banner (
ui/hud.gd:105-106, y=146) may visually collide with the boss name label (starts y=88) on a 720p TV — worth an on-device look. - [idea]
ui/debug_settings_panel.gd:308-309— cycling a dev-settings row (Ship Tilt, Background) doesn’t re-grab focus after_refresh_row_label(), so the joypad focus ring can drop briefly. Dev-tool only, low priority. - [idea]
ui/dialogue_box.gd:177portrait-initial letter + faint background alpha (0.18) — worth an on-device contrast check for 10-foot viewing. ui/crystals_levelup_panel.gdnull-check idea was too vague to act on — skipped.- No accessibility/contrast issues found across
hud.gd/pause_menu.gd/crystals_levelup_panel.gd/debug_settings_panel.gd/dialogue_box.gd— high-contrast neon palette, generous font sizes throughout.
23:52 cycle 3 — lenses: sim/determinism round 2, rendering round 2, code quality round 2, tooling/skill gaps round 2
Section titled “23:52 cycle 3 — lenses: sim/determinism round 2, rendering round 2, code quality round 2, tooling/skill gaps round 2”No safe trivial fixes this cycle — zero commits made. The renderer queue_redraw() pattern (found this cycle via bomb_renderer.gd) is written up as its own cross-cutting section above rather than duplicated here.
sim/determinism round 2
- (checked, refuted) “
weaken_mult()(sim/elemental_system.gd:144) could silently multiply damage down ifweaken_amtever starts negative as a sentinel” — false, and purely hypothetical. Grepped every assignment site:weaken_amtis only ever set insim/drone_director.gd:190asweaken_lvl * DISRUPTOR_WEAKEN_PER_LEVEL(always ≥0) or reset to0.0. No negative-sentinel pattern exists anywhere in this codebase. - [idea, low priority]
sim/upgrade_system.gd:187’sactive_element_count()readssim._weapon_by_id["blaster"].element_idxwith no null-check — probably always safe now that Blaster is the starter weapon, but a defensive.get()would be cheap insurance. - [idea, cosmetic]
weapon_mod_label()(sim/upgrade_system.gd:293) can display a nonsensical “+9900%” if a rate mod’s magnitude is ever exactly 0 — display-only edge case, not a real bug path today. - [idea, likely already-intentional] Crystals mode doesn’t offer
AIM_MODSas pickable level-up cards (sim/upgrade_system.gd:89-91) — this is probably correct by design (crystals mode progresses the aim weapon automatically viaWeaponThresholds.TABLE/_eval_thresholds, not player-chosen cards, per the existing starter+manual-weapon architecture) rather than a gap. Worth a sentence of clarifying comment, not a behavior change.
rendering round 2
- (see the cross-cutting renderer section above for the BombRenderer finding that triggered the full sweep)
- [idea, low value]
render/archetype_renderer.gd:92’sCORE_HEAT := 0.30const was tuned down from 0.62 for HDR cost — scout flagged it as “untested,” but this is normal for a tunable const with no dedicated visual-regression test; not actionable without an on-device look. - [idea, already known] Ship3DRenderer’s SubViewport isn’t quality-gated yet — the code’s own comment already says perf hasn’t been measured and it’s not a shipping default; not new.
- [idea, low severity]
render/hero_lights.gd:80falls back toNEUTRAL(magenta) silently if an fx_event has a missing/invalidelementkey — safe fallback, would only matter if an upstream event is already malformed.
code quality round 2
- [duplication]
_HologramBracketsinner class is duplicated betweenui/start_menu.gd:615andui/meta_shop_panel.gd:1141(verified via grep) with different tuning (arm0.12 vs 0.18, an extradraw_rectcall in the shop version) — candidate to extract into a shared render helper, but the parameter differences may be intentional per-card-size tuning, so any consolidation needs to preserve both visual results, not just merge blindly. - (checked, not actually stale) the scout flagged
ui/ship_config_panel.gd:72-74’s comment referencing a specific old commit (git show 6328f43^:ui/start_menu.gd) as “no longer navigable” — that’s not accurate, the git reference is still valid and correctly documents where the LOCKED-tag pattern was ported from before the source function was deleted. Historical provenance comments like this are fine to keep as-is. - No dead code found this pass.
tooling/skill gaps round 2
bh-write-test(second strong skill candidate) — scaffolds new GUT test files with this project’s conventions:extends GutTest, the_sim()/SimContentFixture.db()fixture pattern, theassert_lte/assert_gtenaming gotcha (NOTassert_le/assert_ge— a typo here silently drops the whole file from the run), and the.gutconfig.jsondirshardcode that makes-gtest/-gdirsilently no-op (already known but the ROOT CAUSE isn’t documented). Concrete and well-scoped — worth building alongsidebh-determinism-repin.- [doc-gap, minor] a one-line note in CLAUDE.md § Running & testing explaining why
-gtestdoesn’t narrow (the.gutconfig.jsonoverride) would save a newcomer’s debugging step — the symptom is already documented, just not the cause. - [doc-gap, minor] the determinism-baseline prose in CLAUDE.md is deliberately treated as a stale-prone example (“always read the pinned assertion, not prose”) but a newcomer can’t tell that’s intentional framing vs. an oversight — a bolder callout would help. Overlaps with the
bh-determinism-repinskill idea above; would be resolved as a side-effect of building it.
00:02 cycle 4 — lenses: content/balance round 2, audio round 2, sim round 3, tooling/skill gaps round 3
Section titled “00:02 cycle 4 — lenses: content/balance round 2, audio round 2, sim round 3, tooling/skill gaps round 3”No safe trivial fixes this cycle — zero commits made.
content/balance round 2
- [idea] Decoy unlocks (~200-250g, one-and-done) cluster around the same price as 2 levels of geometrically-scaling stat upgrades, front-loading the shop — worth reviewing the unlock/stat price relationship as a set, not each in isolation.
- [idea] The Obsidian cruiser unlock (800g, single unlock) is a big outlier vs. every other major unlock (<500g) — 4+ runs of grinding for a single gate. Worth confirming that’s the intended “aspirational” pacing rather than an oversight.
- (checked, mostly refuted) “13 named reactions generate NO damage, only a name/visual event” — false as stated. Traced the full path (
sim/elemental.gd:32→sim/elemental_system.gd:on_reaction()): EVERY reaction, burst-tagged or not, always spawns a DPS terrain zone (spawn_zone) and chain-primes nearby enemies (seed_primed) in addition to its fx event — that’s real, ongoing damage. The"effect": "burst"field only adds an extra instant nova on top for reactions tagged that way; other effect tags (shatter/spread/pull/cc/special) skip the instant nova but keep the shared zone+priming machinery. Not a bug — worth a design confirmation with Chris on whether that split (instant-nova vs. zone-only) matches intent per reaction, not a code fix. - No bugs found in
sim/meta_state.gd’s cost formula or shop gating logic.
audio round 2
- (checked, corrected) a scout claimed
SOUND_MAP.mdalready documentseye_laser.wavas “dead/unused,” recommending its preload be removed. Checked the actual “Dead/unused.wavassets” row (line 159) — it lists 5 different files (attack.wav,enemy_hit.wav,special_aegis.wav,wave_clear.wav,special_rally.wav);eye_laser.wavisn’t among them. Reinforces the cycle-2 conclusion: this is a half-finished feature asset, not dead code — leaving it as-is. - SOUND_MAP.md’s SILENT/GENERIC rows all cross-checked accurate against current code; no doc drift found.
sim round 3
- No real bugs found in
sim/enemy_behaviors.gd/sim/enemy_attacks.gdafter a thorough pass (division-by-zero guards, angle math, state machine transitions, timer dict pruning all checked out). - Confirms the known co-op P1-only pattern extends further than previously logged: orbiter shard DoT, lancer beam hits, and bomber contact damage all hard-target
sim.player(P1) viais_invulnerable()/_hurt_player()with no pilot argument. Same root cause as the FunZo-zone instance already logged in cycle 1 — not new, just broader confirmation of how much co-op work is P1-only right now.
00:12 cycle 5 — lenses: rendering round 3, UI/UX round 3, content/balance round 3, code quality round 3
Section titled “00:12 cycle 5 — lenses: rendering round 3, UI/UX round 3, content/balance round 3, code quality round 3”One trivial fix applied this cycle (see Applied fixes above, commit 8b17074).
rendering round 3
- (re-confirmed, already known) NebulaBg still isn’t wired into
QualityManager’s adaptive ladder — this is the third time tonight this surfaced (cycles 1, 2, and now 5). Not re-logging in detail again; see cycle 1’s entry. Worth flagging to Chris purely because of how often it keeps resurfacing — a decent sign it should just get fixed alongsideADAPTIVE_QUALITYre-enabling at v0.1. - [idea, very low priority]
render/swarm_renderer.gd:73-78andrender/archetype_renderer.gd:497-503write per-instance transform/colour updates even for culled (off-screen) entities before the finalvisible_instance_countgate limits the draw — CPU→GPU upload bandwidth is spent on entities that never render. Minor; worth a profile before touching. - [idea, cosmetic] manual
Color(col.r, col.g, col.b, alpha)construction in a couple of hot per-instance loops could use.with_alpha()— code hygiene only, not a bug. - No shader bugs found — uniforms update at the correct frequency, blend modes correct.
UI/UX round 3
- (checked, refuted)
ui/level_up_panel.gd:83-85’s early-return when invisible/no-cards was flagged as “leaking unconsumed input” — false. NOT callingset_input_as_handled()here is correct: the panel isn’t active, so it shouldn’t consume input meant for something else. The scout had this backwards. - (checked, refuted)
ui/boss_edge_indicator.gd’s label-position math was flagged as breaking when the boss is “near screen center” — false, structurally unreachable:visible = falsereturns early at line 77 whenever the boss is on-screen (near center), before the label math at line 87 ever runs. - [idea, low confidence]
ui/results_panel.gd’s recap-text labels have a fixedcustom_minimum_sizewith no scroll guard — could overflow on a very long recap. Plausible but unverified in depth. - The rest of this scout’s findings (drone bay focus edge cases, touch hold-timing, “icon state race”) were too hedged/speculative (“could,” “theoretically,” “brittle”) to act on or verify cheaply — skipped rather than logged as if confirmed.
content/balance round 3
- (checked, refuted) “[bug] Lancer/Zapper might not fire — fire_interval wiring unconfirmed” — the scout flagged this without actually checking; grepped
sim/enemy_attacks.gdmyself: both have real, wired firing code (Zapper via the sharedfire_intervalblock at line 145/152, Lancer via its own dedicated beam-attack block at lines 289-372). Not a bug. - [idea] Charger’s XP-per-HP ratio (~0.77 xp/hp) is ~4.6x Skirmisher’s (~0.17 xp/hp) — worth a balance look, though Charger’s higher contact damage partially justifies the premium.
- [idea] Elite Charger’s
ELITE_CHARGER_CONTACT_MULT = 0.98multiplies base contact down slightly rather than up — scout confirms this matches the documented intent (the double-dash mechanic is the elite’s real threat, not raw contact damage), just flagging as a “does the resulting feel match intent” playtest question, not a bug. - [idea, needs clarification] Centurion is listed on Home’s enemy roster but its spawn table entry doesn’t seem to reach the early (<120s) window Home mostly plays in — may mean Home runs practically never see it despite the roster claim. Related to the wave-15 timing question already logged in cycle 2; worth resolving both together.
- No discontinuities found in boss HP/damage scaling formulas.
code quality round 3
- [duplication, low priority]
_setup_canvas_glow()is byte-identical acrosstools/ship_preview/preview.gd:97-109andtools/glow_bake/preview.gd:40-52(verified via diff) — dev-tool harnesses only, low value to consolidate. - [stale-comment, NOT auto-fixed]
sim/sim.gd:1328and:1348still label_spawn_fire_hazard()/_spawn_void_hazard()as “Pyre:”/“Null:” in their doc-comments (same stale-naming pattern as themain.gd:123fix from cycle 1) — verified accurate, but deliberately not applied: this file is under/sim, and tonight’s ground rules say never touch/simduring this unattended loop, full stop, even for a comment-only change. Trivial pickup for the morning: justPyre:→Fire:andNull:→Void:in those two comment lines, zero logic change. - No dead code found in
tools/or the content pipeline this pass.
00:22 cycle 6 — lenses: audio round 3, UI/UX round 4, content/balance round 4, code quality round 4
Section titled “00:22 cycle 6 — lenses: audio round 3, UI/UX round 4, content/balance round 4, code quality round 4”No fixes applied this cycle — one attempted comment fix was made, verified wrong mid-cycle, and reverted before committing (see code quality round 4 below). Zero commits made.
audio round 3
- [idea, low priority]
"BOSS"/"WARDEN"/"BOSS DOWN"named-reaction sounds and thegame_over()/hurt()/victory()one-shot helpers are correctly wired (verified) but have zero test coverage intests/test_audio_manager.gd, unlikeFREEZE/DRONE/BLAST/GRAVITONwhich are tested. MatchesSOUND_MAP.md’s own existing “PARTIAL” test-coverage note — not a new gap, just a specific list of which cases are missing. - Clean bill of health otherwise: no per-frame audio spam, no typo’d sound keys,
/simandrender/correctly have zero direct audio calls, all UI panels null-guard theaudioproperty.
UI/UX round 4
- [idea, verified]
ui/weapon_panel.gd:233recomputes_hint.modulate.aevery frame via a fade formula, but_hint.visible = falseis set once at setup (line 152, “Chris: hide the label for now”) and never re-enabled anywhere in the file — the fade math runs forever on a permanently-invisible label. Harmless (no observable effect) but genuinely wasted per-frame work. Not auto-applied — same category as the rendererqueue_redraw()cluster, a UI-behavior file, better as a real dev-chunk pickup than an unattended edit. - [idea, low confidence]
ui/touch_controls.gd’s_decoy_btn_idxcould theoretically stay unreset if a touch-down event’s matching touch-up is lost (device edge case) — scout itself flagged this as rare/theoretical, logging for completeness only. - This cycle’s scout was noticeably more disciplined than earlier UI/UX passes — explicitly reported “no bug found” for 2 files it checked rather than padding the list, which is exactly the calibration asked for.
content/balance round 4
- Clean bill of health: mod stacking (multiplicative, intentionally uncapped for damage — confirmed EVE-style by design), evolution gating (
evolvedflag prevents double-evolution,mods.has_any()gate is clean), and the existing caps (turret count ≤4, orbit shards ≤6, fire-rate ≤FIRE_RATE_CAP) were all traced through the actual code and confirmed correct. Nothing new to log.
code quality round 4
- [stale-comment, real but NOT safely auto-fixable]
tests/test_boss_gate.gd:100-102’s comment says “Today’s 3 real Dimensions (Pyre/Null/Drift)… can only ever return ‘graviton’/‘funzo’/‘eye’”. I attempted a quick fix (names only) but caught my own error mid-edit:sim/area_defs.gd’sDIMENSION_IDSis genuinely[FIRE, VOID_DIM, LIGHT](3 entries, correct count — Home is explicitly NOT a portal destination, so my first instinct to “correct” it to 4 was itself wrong), but the boss-name list in the same comment doesn’t match either: currentboss_for()mapping is fire→warden, void_dim→graviton, light→eye — not “graviton/funzo/eye” as the comment claims (no “warden”, and “funzo” isn’t reachable via this path at all, only via the separate generic/non-Dimension rotation tested elsewhere in the same file). Reverted my edit rather than risk introducing a fresh inaccuracy. This needs someone who actually understands whetherfunzo/wardenreachability changed here to fix correctly — flagging with the full detail above so whoever picks it up doesn’t have to re-derive it. - [stale-test-data, NOT auto-fixed]
tests/test_wormhole_renderer.gd:12-13,25hardcodes old dimension name strings ("pyre","null_dim","Pyre","Null") as actual test fixture data, not just comments — changing these would need the test re-run to confirm it still passes with the new values, which this loop’s ground rules explicitly don’t allow. Logged only. tests/test_dimensions.gd’s “old Pyre” comments (lines 83, 92, 129, 223) are intentional regression-test documentation explaining what old behavior is being guarded against, not stale — correctly left alone.
00:32 cycle 7 — lenses: sim round 4, rendering round 4, audio round 4, tooling/skill synthesis
Section titled “00:32 cycle 7 — lenses: sim round 4, rendering round 4, audio round 4, tooling/skill synthesis”No fixes applied this cycle — zero commits made.
sim round 4
- [idea, verified] Two more confirmed instances of the known co-op P1-only pattern:
sim/sim.gd:1354’s Void Dimension hazard pullsplayer.posdirectly instead of via_nearest_pilot()(same function whose stale “Null:” comment was found in cycle 5), andsim/story_director.gd:76’s NPC talk-radius check does the same (sim.player.pos.distance_to(npos)), so P2 can’t trigger NPC dialogue/story drops even standing right next to one. Both grep-verified accurate. Not new territory conceptually (same root cause as the FunZo/orbiter/lancer/bomber instances already logged), but useful to have the full site list in one place for whoever eventually does the P2-progression work. - No determinism breaks found in
story_director.gd/drone_director.gd— RNG draws are clean, no-drone baseline stays an exact no-op.
rendering round 4
- Elaborates on the cycle-1 finding (
main.gd’s per-framePackedColorArrayallocation) rather than adding new territory: also applies to_enemy_scales(),wormhole_list.duplicate()(main.gd:1260), and a small boss-info array rebuilt every frame despite at most 1 boss being alive (main.gd:1237-1254). Same fix shape as already logged (pool/cache instead of reallocating) — not re-logging as a separate item, just noting the fuller scope for whoever picks up the cycle-1 finding. - Clean bill of health on
SpatialHash.query_circle()(necessarily allocates its one result array, can’t be pooled without an API change) andFxManager’s pools (already correctly pre-allocated).
audio round 4
- Bus layout, volumes, and polyphony capping all checked out correctly against
project.godot/bus_layout.tres(no wiring bugs) — but this pass did surface 3 legitimate, correctly-caveated mixing ideas rather than another clean pass:- [idea] Named reactions (
BOSS,NUKE,FREEZE,ENRAGED, etc.) intentionally bypassPER_FRAME_CAP(audio_manager.gd:157) so high-priority events always play — but that means there’s no upper bound on how many can stack in one frame; at high enemy counts with heavy elemental activity, uncapped named reactions plus capped weapon fire could still saturate the 16-voice pool. - [idea] There’s no ducking of the Music bus when SFX gets loud (buses are correctly isolated per
bus_layout.tres, just no dynamic mixing between them) — in a dense swarm fight, music never yields room to the weapon/reaction barrage. - [idea]
set_weapon_load()attenuation only applies to the 6WEAPON_FIRE_KINDS— enemy/boss projectile sounds (enemy_ranged_fire,boss_missile_launch, etc.) always play at 0dB, uncapped by load. At very high enemy counts this could contribute to pool saturation before named reactions even land. - All three are explicitly framed as “may need tuning under real load,” not bugs — genuinely untested without on-device telemetry at extreme spawn counts.
- [idea] Named reactions (
tooling/skill synthesis
- This cycle asked a scout to review the 4 skill ideas proposed so far rather than hunt for more, given returns were diminishing. Its recommendation to merge
bh-determinism-repin+bh-write-testinto onebh-determinism-baselineskill doesn’t fully hold up on reflection — they’re genuinely different concerns (test-authoring conventions for newcomers vs. a narrow re-pin ritual for an intentional baseline move), just both “testing-adjacent.” Its prioritization call is worth keeping though: build determinism-baseline tooling first — it gates every sim-touching PR, while the renderer/sound skills are additive. Also a good structural observation:bh-dev-chunk’s 7-gate checklist currently bundles determinism-checking in with steps that don’t need it (UI/render-only changes) — extracting it as an independently-callable gate (used only when a change actually touches/sim) would keepbh-dev-chunkleaner without losing the safety net.
00:42 cycle 8 — lenses: content/balance (“The Chasm”), code quality (TODO sweep), UI/UX (accessibility), skill-audit
Section titled “00:42 cycle 8 — lenses: content/balance (“The Chasm”), code quality (TODO sweep), UI/UX (accessibility), skill-audit”No fixes applied this cycle — zero commits made. Standout finding: two existing skill files have real drift (see Skill ideas below).
content/balance — “The Chasm”
- Status clarified: “The Chasm” isn’t a separate unshipped area — it’s the codename for Cycle 21 (BUILD 51), a completed content-import pass that shipped Ghost, Accumulator, tank homing missiles, and 3 new bosses (FunZo/Graviton/Eye) extending the boss rotation from 2→5. All merged, all deployed to Apple TV. Graviton and Eye are code-complete but deliberately gated off in survival via
BossRotation.V01_FUNZO_ONLY=true(Chris’s call, 2026-07-02) — not a bug, an intentional ship gate. - [idea, verified] Two more confirmed co-op P1-only sites, more nuanced than earlier findings:
sim/graviton.gdcorrectly uses_nearest_pilot()in its main pull/push mechanics (lines 90, 104, 125, 143) EXCEPT its satellite-blob DoT check at line 171, which hardcodessim.player.pos— P2 walks through satellite blobs unharmed. Similarlysim/eye.gdcorrectly uses_nearest_pilot()for targeting (lines 67, 262) but its afterimage-line and beam/pupil hit-checks (lines 86, 92, 215, 223, 237) hardcodesim.player.pos— P2 takes no damage from the Eye’s signature dash-afterimage attack. Since Graviton/Eye are gated off in survival right now, low urgency, but useful detail for whoever eventually does the P2-progression work — these are per-line gaps within otherwise-correct files, not whole-file omissions. - [idea, low priority] Graviton’s pull-then-arena-clamp ordering (
sim/graviton.gd:145-150) clamps the player back INTO the arena after the pull is applied, which could silently truncate the pull’s effect near an edge — flagged as “probably intentional” by the scout (hard arena boundary), worth a playtest confirmation rather than a code concern.
code quality — TODO/FIXME sweep
- Very clean: only ONE
TODOmarker exists anywhere insim/,render/,ui/,fx/,main.gd,audio/,meta/,net/,ai/combined (sim/sim.gd:636, the N-pilotintegrate()gap) — and it’s already documented in CLAUDE.md’s Architecture section, not a hidden untracked debt. Good sign of project discipline; nothing new to log.
UI/UX — accessibility & legibility
- Clean pass overall: no red/green-only signal found anywhere (threat levels, locked ships, shop affordability all pair color with shape/text/motion, not color alone — verified across
codex_card.gd:48,ship_config_panel.gd:196,hud.gd:256-262). Neon palette (cyan/magenta/orange/violet) doesn’t clash for deutan/protan colorblindness. - [idea, verified]
ui/weapon_panel.gd:177sets the aim-weapon stat label tofont_size 10, smaller than theFONT_SM := 14baseline used elsewhere — worth an on-device TV legibility check at 10 feet. - [gap] No player-facing accessibility/settings screen exists at all — only the dev-only
DebugSettingsPanel(gated behindBuildConfig.dev_tools()). Not urgent for v0.1 given the palette already avoids color-only signaling, but worth a post-launch backlog note if a text-size or colorblind-mode option is ever requested.
skill-audit (new angle this cycle — checked existing skills instead of proposing new ones)
- [skill-drift, high value]
.claude/skills/bh-dev-chunk/SKILL.md:53hardcodes a STALE determinism baseline —snapshot_string().hash() = 4152236597,state_checksum() = 1267954985. Verified directly: the actual current pinned assertion (tests/test_determinism_checksum.gd:47-48) is4217109746/2666143677. This is exactly the failure mode thebh-determinism-repinskill idea (proposed earlier tonight) exists to prevent — and ironically it’s the determinism-focused skill itself that’s stale. High-value fix for Chris to make directly (skill files are off-limits for this loop to touch, gitignored/local-only, not part of the git-reviewable trail). - [skill-drift]
.claude/skills/bh-area-background/SKILL.md:112-113references syncing to~/Claude/bullet-heaven-tvos/— that repo was archived 2026-07-04 when tvOS folded intoplatform/tvos/with symlinked gameplay (no sync step needed anymore, per CLAUDE.md’s top-of-file note). Dead instructions pointing at retired infra. - [skill-ok]
bh-add-content,bh-sdd-concurrent,bh-deploy,bh-appstore-releaseall checked current — correctly reference the sim-module-split, the tvOS fold, and the -Dev/production preset split. No drift found in these four. - Confirms
.claude/skills/IS readable from disk tonight (gitignored but present, as the global CLAUDE.md convention describes) — a fresh Haiku agent CAN see real skill content, not just names, when asked to look.
00:52 cycle 9 — lenses: sim round 5 (pattern hunt), rendering round 5, audio round 5, UI/UX round 6
Section titled “00:52 cycle 9 — lenses: sim round 5 (pattern hunt), rendering round 5, audio round 5, UI/UX round 6”No fixes applied this cycle — zero commits made.
sim round 5 — targeted hunt for more “hardcodes player instead of _nearest_pilot()” instances
- [idea, verified, genuinely new]
sim/elemental_system.gd:238’sweb_slow_mult()checks onlysim.player.posagainst active webs — P2 never gets web-slowed in co-op. Unlike most other co-op findings tonight, this isn’t inside an already-documented “P1-only” subsystem (drones/story/XP) — it’s a core elemental status-effect calculation, so it’s a genuinely new site, not just enumerating a known gap. - [idea, verified, more relevant than earlier finds]
sim/boss_warden.gd:125(swing hit-check) and:155(missile hit-check) hardcodesim.player.pos, while the rest of the same file correctly uses_nearest_pilot()(lines 47, 71, 143). Worth flagging above the Graviton/Eye instances found earlier tonight — Warden is the currently LIVE, reachable boss (Fire/Home dimensions, not gated off), unlike Graviton/Eye which are behindV01_FUNZO_ONLY. - The rest of this scout’s findings (drone_director.gd ~10 lines, story_director.gd 5 lines) are real but not new information — they enumerate specific lines within systems CLAUDE.md already documents as P1-only by design (the drone/decoy gap, story mode’s single-pilot scope). Not re-logging in detail.
rendering round 5
- [idea, verified but likely intentional] Root
project.godothas norendering/section at all (confirmed: zero matches), whileplatform/tvos/project.godot:27explicitly setsviewport/hdr_2d=true. Per [[bullet-heaven-tvos-metal-renderer]] memory, this is probably correct as-is: root/web builds use gl_compatibility (no HDR by design), tvOS uses the Mobile/Metal renderer (HDR available). Genuinely uncertain whether the local macOS export path (via the same rootproject.godot) also needs this — worth a quick on-device bloom comparison rather than assuming either way. - Two more re-confirmations of already-logged items (
ADAPTIVE_QUALITYoff,NebulaBgquality-ladder gap) — not re-logging in detail, this is now the 4th+ time both have surfaced. - Clean pass on scene files: no broken resource references, all sprite paths resolve,
main.tscnis intentionally empty (nodes built in code).
audio round 5
- SOUND_MAP.md’s cited line numbers have drifted a few lines from actual fire sites (expected doc staleness as code grew) but every wiring/mapping itself checked out correct.
eye_laser.wavcorrectly does NOT appear in the doc’s “dead asset” list — consistent with tonight’s established conclusion that it’s a half-finished feature, not dead code. No new findings.
UI/UX round 6
- [idea, verified, minor]
ui/hud.gd:195— when a boss dies,_boss_group.visiblecorrectly goes false but_boss_label.textis never cleared, leaving the previous boss’s name in the stale label. Currently harmless (label stays hidden in the normal path) but a robustness gap if a debug panel or a visibility-toggle bug ever re-shows it. Cheap fix (_boss_label.text = ""alongside the visibility flip), not auto-applied since it’sui/hud.gdrender-behavior code.
01:02 cycle 10 — lenses: content/balance (story mode), code quality round 6, sim round 6 (comprehensive P1-only sweep), tooling/skill round 6
Section titled “01:02 cycle 10 — lenses: content/balance (story mode), code quality round 6, sim round 6 (comprehensive P1-only sweep), tooling/skill round 6”No fixes applied this cycle — zero commits made. Sim round 6’s sweep is written up in the new cross-cutting section above rather than duplicated here.
content/balance — story mode
- [idea, verified structurally, low priority] Vex’s story arc (
data/story.json) has a narrative pacing gap: defeating her in encounterlw_e_vexfires dialoguevex_beaten(line 1411), but her reconciliation dialoguevex_allyonly fires later when the player physically walks to a different room (lw_safe1, NPC entry at line 1631-1638) — a room transition sits between “she accepts defeat” and “she becomes an ally,” with nothing narratively bridging the two. Confirmed via direct JSON read. Not a functional bug — nothing fails to trigger, all encounters/rewards/dialogue references resolve correctly — just a writing/pacing observation, and moot for v0.1 anyway since Story mode is fully gated off (StartMenu.V01_CRYSTALS_ONLY=true). Low priority, logged for whenever Story mode ships. - No uncollectable rewards, impossible encounters, or broken dialogue triggers found elsewhere in story content.
code quality round 6
- Re-confirms the 5 already-documented dead audio assets (
attack.wav,enemy_hit.wav,special_aegis.wav,wave_clear.wav,special_rally.wav) — not new, matchesSOUND_MAP.md:159exactly. - No schema gaps found — every
.get("x", default)call in/simreads a field that genuinely exists inbible.jsonor an internal TABLE structure. - (checked, refuted) scout flagged
SOUND_MAP.md:160’s “untested: BOSS/WARDEN/BOSS DOWN” note as stale because those sounds “are implemented and working” — this conflates two different claims. The doc’s “untested” note is about unit-test coverage (there genuinely isn’t one, confirmed independently by the audio scout back in cycle 6), not about whether the runtime wiring works (it does). The doc is accurate; the scout misread it.
tooling/skill round 6
bh-diagnose-render-timingandbh-live-debug-deployboth checked clean — no stale numbers, no dead references, scope still matches current architecture.- [real finding, NOT auto-fixable]
NEXT_TASKS.md’scheck-test-count.shDONE entry (and the roadmap memory echoing it) cites “204/204 test scripts” as the verified baseline — actual current count is 209 (ls tests/test_*.gd | wc -l), having grown with v0.1-prep feature work (ship-tilt, weapon-aim, blaster, weapon-thresholds tests) added since that entry was written. The entry is still correctly marked DONE (the guard script itself works), just the cited number has drifted. This loop’s ground rules explicitly forbid editingNEXT_TASKS.md— flagging clearly here so Chris (or a future session) can bump204/204→209/209by hand in bothNEXT_TASKS.mdand the roadmap memory.
01:12 cycle 11 — lenses: sim round 7 (net/ai), rendering round 6, content/balance round 7 (drones/decoys), code quality round 7
Section titled “01:12 cycle 11 — lenses: sim round 7 (net/ai), rendering round 6, content/balance round 7 (drones/decoys), code quality round 7”No fixes applied this cycle — zero commits made. Rendering round 6’s 2 new co-op findings are folded into the cross-cutting section above rather than duplicated here.
sim round 7 — net/ai directories
- Clean:
net/(telemetry, gameplay_telemetry, control_client, telemetry_device) andai/(auto_player, attract-mode only) are both small, purpose-scoped, and correctly gated — no dead code or dangling stubs. The unmerged networked-MP work correctly lives only in its separate worktree, not leaking into main. - [security note, checked, low-risk confirmed]
net/control_client.gd:16hardcodesCONTROL_KEYin source (matches~/.secrets:BULLET_HEAVEN_CONTROL_KEY). Verified: the GitHub repo (chrisallenuk/bullet-heaven) is private, and the code’s own inline comment already reasons through this exact trade-off (dev/playtest-only tool, gated behindBuildConfig.dev_tools(), never in shipping/web builds, same key already exposed in the console URL anyway). Confirmed as designed, not a fresh concern — logging only because hardcoded secrets are worth a second set of eyes, not because anything looks wrong.
rendering round 6
- [idea, low priority]
render/story_map_renderer.gd:27’s_pulseaccumulates indefinitely across a run rather than wrapping moduloTAU— theoretically could hit float-precision wobble in its sine-based shimmer after many hours of continuous runtime. Not remotely reachable in a normal play session; logging for completeness only. - [idea]
render/lancer_telegraph_renderer.gduses a fixed yellow/gold telegraph color rather than tinting by the enemy’s current element (likedash_telegraph_renderer.gdalready does) — minor visual-consistency polish idea.
content/balance round 7 — drones/decoys
- [idea, partially verified] Decoy shop-unlock costs confirmed accurate against
data/bible.json(Damage 200g, Tank 250g, Healer 300g, Ultimate 500g). The scout’s cost-per-DPS comparison (claiming Tank and Healer are strictly worse value than the cheaper Damage decoy) is plausible and internally consistent, but I couldn’t independently verify the underlying per-decoy damage/interval constants indrone_director.gdwithin this cycle’s time budget — logging as a balance idea worth Chris’s own look with the actual numbers in front of him, not a confirmed bug. - [idea] Decoy recharge (~25s) vs. active duration (~8s) creates a long idle gap between uses — flagged as a cadence/feel question for playtesting, not a bug.
code quality round 7
- [idea, low confidence]
sim/mod_state.gd:16-17’shas_any()uses direct float equality (!= 1.0) rather than an epsilon comparison — technically fragile in the abstract, but the scout’s own proposed failure case doesn’t hold up: mod magnitudes are always meaningfully different from 1.0 (15-50% boosts), far beyond any realistic floating-point rounding error, so the claimed false-negative isn’t actually reachable with real content values. Logging as a very low-priority theoretical note, not a live bug. - No other correctness issues found in
content_db.gd/mod_state.gd— lookups, defaults, and type guards all checked sound.
01:22 cycle 12 — lenses: audio round 6, UI/UX round 7, tooling round 7, rendering round 7 (all pushed toward the co-op pattern this cycle)
Section titled “01:22 cycle 12 — lenses: audio round 6, UI/UX round 7, tooling round 7, rendering round 7 (all pushed toward the co-op pattern this cycle)”No fixes applied this cycle — zero commits made. All 4 findings are folded into the cross-cutting co-op section above (now believed comprehensive across sim/render/audio/UI) rather than duplicated here.
tooling round 7 — bh-add-content and bh-sdd-concurrent both audited in full detail this pass (not just sampled): both fully current, no stale paths/numbers/references found. Two more clean skills confirmed, following the drift found earlier in bh-dev-chunk/bh-area-background.
rendering round 7 — dedicated repo-wide grep sweep (grep -rn "sim.player.pos\|sim.player.hp" render/) confirms only the 2 already-known sites exist in the entire render/ directory — the render-layer portion of the co-op pattern is now exhaustively catalogued, not just sampled.
01:32 cycle 13 — lenses: sim round 8 (core data structures), content/balance round 8 (ship classes), code quality round 8 (boss_rotation.gd), UI/UX round 8 (codex/bestiary)
Section titled “01:32 cycle 13 — lenses: sim round 8 (core data structures), content/balance round 8 (ship classes), code quality round 8 (boss_rotation.gd), UI/UX round 8 (codex/bestiary)”No fixes applied this cycle — zero commits made. Two standout findings this cycle, both flagged prominently rather than folded into a bullet list.
⭐ [bug, high-confidence, verified] Centurion can never be marked “seen” in the bestiary/codex — sim/enemy_pool.gd:27 defines TYPE_CENTURION := 23, but content/bestiary_db.gd’s TYPE_KEYS array only has 23 entries (indices 0-22, ending at TYPE_CUSTOM) — there’s no index 23. key_for_type(23) returns "" (out of bounds), which first_encounters() treats as “no codex entry,” so Centurion — the enemy that shipped in BUILD 165 with its own dedicated 7-task review cycle — will show as permanently locked (“???”) in the codex no matter how many times the player fights it. One-line fix (add "centurion", # 23 TYPE_CENTURION to TYPE_KEYS), but not auto-applied: it changes real UI-visible behavior and could interact with existing bestiary tests that assert the array’s length/contents — belongs in a proper bh-dev-chunk pass with the test suite run, not an unattended edit.
- [idea, same file]
TYPE_RUSHER/TYPE_ZAPPER(indices 9-10) are inTYPE_KEYSand have bestiary entries but the scout couldn’t find them in the liveSpawnTable— worth a decision on whether they’re shelved content (remove) or context-specific spawns (keep), a smaller version of the same “codex entry vs. actual spawn reachability” question as Centurion’s wave-15 discussion from cycle 2/8.
⭐ [idea, well-quantified] Obsidian cruiser is close to a strict upgrade over all 6 frigate hulls, not a genuine playstyle choice — elaborates on CLAUDE.md’s own already-flagged “needs playtesting” note with real numbers: +70% HP, +67% weapon slots, +10 armor (worth ~60% damage reduction, capped at 75% globally), for only a 19% speed penalty that’s easily offset by a single move-speed level-up card, and a 50% larger hitbox that the armor bonus largely compensates for anyway. All 6 frigate hulls share identical base stats — their only differentiation is a cosmetic “headline bonus” (Manta=damage, Cobalt=fire-rate, etc.), so right now frigates read as “pick your flavor of early-game placeholder” rather than a real alternative to the cruiser. Not a bug — a balance-design input for whenever Chris does the frigate/cruiser playtest pass already on the books.
sim round 8 — spatial_hash.gd / enemy_pool.gd
- Clean, thorough pass: swap-remove patterns, backwards-iteration in
_sweep_dead(), hash rebuild-before-query ordering, and capacity/bounds checks all verified correct. No bugs found in the core entity/collision data structures.
code quality round 8 — boss_rotation.gd
- No logic bugs in the rotation/index math itself (mod-5 rotation, boss-type checks, HP scaling all correct).
- [dead-code, verified]
boss_rotation.gd’smaybe_spawn_survival_boss()is called from zero production code — grepped the whole repo: its only callers aretests/test_boss_rotation.gd(20+ call sites) andtests/test_v01_funzo_only.gd. The actual live boss-spawning path isSim._spawn_phase_boss()(sim.gd:1271-1284). Either this is orphaned code from an earlier design whose test file was never cleaned up, or something intended to wire it in never happened — genuinely unclear which without more context, so not characterizing it as either “safe to delete” or “a real gap,” just flagging that a substantial, well-tested function has no production caller at all.
01:42 cycle 14 — lenses: rendering round 8 (boss renderers), content/balance round 9 (elites), audio round 7 (test cross-check), tooling round 8 (bh-deploy/bh-appstore-release re-audit)
Section titled “01:42 cycle 14 — lenses: rendering round 8 (boss renderers), content/balance round 9 (elites), audio round 7 (test cross-check), tooling round 8 (bh-deploy/bh-appstore-release re-audit)”No fixes applied this cycle — zero commits made.
⭐ [bug, verified] Boss2 has a real enrage mechanic with zero visual telegraph — sim/boss2.gd:114-117 genuinely enrages Boss2 at a low-HP threshold (BOSS2_ENRAGE_FRAC), speeding up its attack cadence (BOSS2_ENRAGE_TIME_MULT), and render_info() (sim/boss2.gd:239) correctly exposes "enraged": sim.boss2.enraged to the renderer. But render/boss2_renderer.gd never reads that key at all (zero matches, grep-verified) — unlike FunZoRenderer, GravitonRenderer, and EyeRenderer, which all render a distinct pulsing/crackling aura on enrage. Boss2 looks visually identical calm vs. enraged even though it’s mechanically more dangerous, breaking the established visual-telegraph language every other boss follows. Real, missing-feature-shaped gap (not a trivial fix — needs new render code matching the established pattern), logged for a real dev pass.
content/balance round 9 — elites
- [status, confirmed accurate] CLAUDE.md’s “~12 promotable enemy types get only the generic elite buff” checks out exactly right: 4 types have bespoke elite mechanics (Charger’s chained dash, Spider’s landing web+ray-fan, Shooter’s predictive aim+2x damage, Swarmer’s brood-burst-instead-of-XP), the other 12 (Tank/Splitter/Brute/Rusher/Zapper/Scatterer/Bomber/Orbiter/Lancer/Ghost/Accumulator/Centurion) get only the generic HP/contact/radius/speed multipliers. No type skipped or double-applied. Useful precise checklist for whoever eventually gives more elites bespoke powers.
audio round 7 — test cross-check
- Thorough pass (46 + 13 assertions individually traced against current code): zero test drift found in
test_audio_manager.gd/test_ui_sound_wiring.gd. Sixth consecutive audio-lens pass without a new confirmed issue — this system is genuinely solid.
tooling round 8 — skill re-audit
bh-deployandbh-appstore-releaseboth re-checked thoroughly (not just re-trusting the earlier light pass) — zero staleness found in either: BUILD/flag/path references, ASC app ID, bundle ID, export preset names, and the tvOS-fork Godot binary path all still current. That’s now 6 of 8 project skills confirmed clean tonight, 2 with real drift found earlier (bh-dev-chunk,bh-area-background).
01:52 cycle 15 — lenses: audio round 8, sim round 9 (turret/evolutions), UI/UX round 9 (sibling-panel comparison), tooling round 9 (exhaustive re-verify)
Section titled “01:52 cycle 15 — lenses: audio round 8, sim round 9 (turret/evolutions), UI/UX round 9 (sibling-panel comparison), tooling round 9 (exhaustive re-verify)”No fixes applied this cycle — zero commits made.
audio round 8
- (checked, refuted) “
_pool_cursornever resets between runs, could cause stale/doubled audio” — false.audio_manager.gd:139-140shows a standard round-robin pool:play()always grabs whichever player the cursor currently points to and immediately calls play on it, which replaces/interrupts whatever that player was previously doing regardless of cursor value. This is identical to how the cursor behaves wrapping mid-run (happens constantly) — there’s nothing special about a run boundary. Not a bug; a round-robin cursor persisting across runs is the correct, intended behavior, not state leakage. - Music transition logic and per-frame SFX cap reset both checked clean.
sim round 9 — turret/evolutions
- Clean: turret deployment/lifetime/cooldown/cap logic and the evolution
evolved-flag double-apply guard (can_evolve()) both checked out correct.
UI/UX round 9 — sibling-panel comparison
- (checked, likely refuted — high confidence but not click-tested) “MetaShopPanel’s RESET SHOP confirm dialog blocks mouse clicks on desktop because
_handle_reset_dialog_input()doesn’t explicitly routeInputEventMouseButton” — this doesn’t hold up against the code’s own logic. The exact same non-handling pattern already exists, documented and intentional, on the carousel’s own_input()just above it (meta_shop_panel.gd:689-691: “Deliberately does NOT route InputEventMouseButton here — left-click press/release must keep flowing to Button.pressed for click-to-activate on desktop”). Godot dispatchesButton.pressedvia its own_gui_input/click-capture pathway, independent of this custom_input()override, unlessset_input_as_handled()is explicitly called on that same event — which_handle_reset_dialog_input()never does for mouse clicks (only for confirm/back/nav matches). So by the same reasoning the carousel already relies on, clicks should still reach the dialog’s buttons normally. Logged as likely-a-false-positive rather than confirmed-fixed-or-broken, since I can’t literally click-test without running the game — a 30-second Mac playtest would settle it either way.
tooling round 9 — exhaustive re-verify
bh-diagnose-render-timingandbh-live-debug-deployre-checked with actual file/line grep verification for every referenced path/function (not just skimmed) — both fully current. That’s all 8 project skills now individually audited tonight: 6 clean, 2 with real drift found earlier (bh-dev-chunk,bh-area-background).
02:02 cycle 16 — lenses: sim round 10 (starter weapons), content/balance round 10 (crystals thresholds), code quality round 9 (spawn_director), rendering round 9 (player ship tilt)
Section titled “02:02 cycle 16 — lenses: sim round 10 (starter weapons), content/balance round 10 (crystals thresholds), code quality round 9 (spawn_director), rendering round 9 (player ship tilt)”No fixes applied this cycle — zero commits made. This is the last discovery cycle of the run; remaining time goes to a synthesis pass.
- [clean]
sim/weapon_blaster.gd/sim/weapon_aim.gd(starter weapons) — nearest-enemy targeting, cooldown timing, pierce/burst/knockback parameter passing, fire-rate scaling all checked correct. - [clean]
sim/spawn_director.gd— every time-bucket’s spawn-weight table sums to exactly 100% with no gaps/overlaps/dead branches (scout’s own mid-analysis “critical bug” flag was a false alarm it self-corrected). - [idea, cosmetic only]
sim/weapon_thresholds.gd:23-28— Orbit’s crystals-mode threshold array is[cold@5, cold@12, lightning@9, void@18], not strictly increasing by trigger value like every other weapon’s table (all 7 others + the aim weapon are cleanly ascending). Scout confirms this is functionally harmless — threshold application doesn’t care about array order — purely a source-readability nit. Trivial reorder candidate for whenever that file is next touched for real work. - [idea, dev-tool only, unverified]
render/player_renderer.gd:340’s Shader Warp tilt mode may leave a staletiltshader uniform when switching to another tilt mode and back — could cause a one-frame visual snap. This only affects the dev-only Debug Settings tilt-mode comparison toggle, not normal play; not deeply verified given end-of-run time constraints, logged as a plausible low-priority lead rather than a confirmed bug.
02:12 cycle 17 — lenses: code quality round 10, content/balance round 11 (both final systematic sweeps)
Section titled “02:12 cycle 17 — lenses: code quality round 10, content/balance round 11 (both final systematic sweeps)”No fixes applied this cycle — zero commits made. This is the last cycle of the run.
⚠️ [bug, verified, high severity] The “crit” upgrade grants zero effect — data/bible.json:575 defines an upgrade (id: "crit") with "effect": "crit_chance", but that effect string exists in neither sim/stat_effects.gd nor sim/sim_mods.gd (grep-verified, zero matches in both). sim/upgrades.gd:26-31’s apply() checks StatEffects.is_known() then SimMods.is_known(); if neither recognizes the effect, it logs a console-only push_error and returns — no crash, no in-game indication. A player who takes this upgrade card during a run gets no stat change at all, with nothing telling them it didn’t work. Written up in the executive summary as the most severe finding of the night. Not auto-fixed — implementing crit_chance (or removing the dead upgrade entry) is real feature/content work requiring a bh-dev-chunk pass, not something to guess at unattended.
[bug, independently re-confirmed] A second scout, working a different systematic method (checking every enemy type’s bestiary/index mapping rather than investigating the Centurion specifically), independently rediscovered the exact same TYPE_KEYS-missing-index-23 bug already logged in cycle 13 — good cross-validation of that finding. Importantly, this systematic re-check found no other enemy type with a similar mismatch, so the Centurion gap appears to be an isolated one-off, not a wider pattern.
02:22 cycle 18 — final cycle: systematic hunt for more unimplemented-effect-string bugs (crit_chance’s bug class)
Section titled “02:22 cycle 18 — final cycle: systematic hunt for more unimplemented-effect-string bugs (crit_chance’s bug class)”No fixes applied — zero commits made.
- (checked, refuted) A scout claimed the
"drone"effect (used across 47 meta-upgrade entries: drone-slot/recharge/sentinel/bomber/interceptor/disruptor/logistics unlocks) is unimplemented the same waycrit_chanceis — false, and NOT the same bug class. Checkeddata/bible.json:1220-1232: every one of these entries has"type": "unlock"+ a"target"field (e.g."target": "class:bomber"), the identical pattern already confirmed working for the decoy unlocks earlier tonight (cycle 11). These dispatch through a separate unlock-handling path (matching ontype/target), not throughStatEffects/SimMods’ magnitude-effect tables —"effect": "drone"here is just a category tag, not a dispatch key. The scout’s methodology (checking only the two magnitude-effect tables) doesn’t apply totype: "unlock"entries.crit_chancegenuinely IS broken because it has notype: "unlock"field and truly does rely on the magnitude-effect dispatch it’s missing from — that finding stands, this one doesn’t. - All 17 other magnitude-effect strings (
damage_mult,fire_rate_mult,move_speed,pickup_radius,max_hp,armor,decoy_power,decoy_life,thrusters,still_heal,stack_bonus,reaction_damage_mult,aura_duration_mult,projectiles_pierce,split_on_hit,lifesteal_per_kill) cross-checked present and correct inStatEffects/SimMods—crit_chanceremains the one genuine gap found tonight in this category.
Skill ideas
Section titled “Skill ideas”-
bh-add-renderer(strong candidate) — a checklist for the OTHER repeatable multi-file wiring task besidesbh-add-content(enemies/weapons): adding a new renderer/visual effect. Five steps get routinely missed or deferred: create the renderer script, wire the instance + per-frame update intomain.gd, wire it intoQualityManager(or add an explicit no-op predicate), updatedocs/architecture/rendering-and-performance.md’s cost model, and — validated directly by tonight’s own findings — guard_process()’squeue_redraw()against an empty-state early-out (the exact pattern 13 existing renderers are missing, see the cross-cutting section above). Concrete, well-scoped, and this session is itself evidence of the gap. -
bh-add-sound-event(strong candidate) — a checklist for wiring a new sound-triggering event: emit thefx_eventskind from/sim, map it inAudioManager.SOUND_FOR_FX/SOUND_FOR_NAMED_REACTION, and updateaudio/SOUND_MAP.md’s status table.bh-add-contentdoesn’t mention audio at all today, so a weapon/enemy with a new sound cue means cross-referencing two unrelated docs from memory. -
[doc-gap] the “spawn eligibility is a 3-tier gate” lesson (
docs/architecture/enemies-bosses-survival.md’s Centurion section) is written as enemy-specific, but the same “wiring must be complete across N independent systems or it silently fails” shape also applies to renderer LUT-sizing (main.gd’s_TYPE_SHAPEpartition count) — worth generalizing intodocs/godot-gotchas.mdas a named anti-pattern rather than leaving it as one feature’s footnote. -
[doc-gap, minor]
NEXT_TASKS.mdhas no stated intake template for how a fresh agent should file a new finding — a short header note would help findings actually stick instead of evaporating into chat/memory. -
bh-determinism-repin(strong candidate) — CLAUDE.md itself documents the determinism baseline has been re-pinned “many times” across 3 separate locations (prose in CLAUDE.md, the actual assertions intests/test_determinism_checksum.gd+test_determinism_crystals.gd, and memorybullet-heaven-roadmap), and explicitly warns “this line has gone stale before” — a skill wrapping the re-pin checklist (update all 3 locations, run the 600-tick trace to get the new signature, commit with a standard message) would directly prevent a documented recurring failure mode. Worth building. -
bh-write-test(second strong candidate) — scaffolds new GUT test files with this project’s non-obvious conventions:extends GutTest, the_sim()/SimContentFixture.db()fixture pattern, theassert_lte/assert_gtenaming trap (typo’d asassert_le/assert_gesilently drops the whole file from the suite), and the.gutconfig.jsondirs-hardcode root cause behind the already-known-gtest-doesn’t-narrow gotcha. Concrete, well-scoped, surfaced independently by a second scout in a later cycle. -
“P2 progression completion” skill— re-scoped by me: this is a one-off feature-completion task (bridge_award_xp/upgrade_system/drone_directorto per-pilot), not a repeatable workflow pattern. Belongs in a spec/plan, not a skill. -
[doc-gap, minor]
NEXT_TASKS.mdhas no stated convention for when to prune DONE entries — a one-line note at the top would help. -
[doc-gap, already covered] scout suggested documenting the
export_presets.cfg-must-be-copied-into-fresh-worktrees gotcha — this is already in this project’s own CLAUDE.md under “Running & testing,” not actually missing. -
[doc-gap, low value] a
.claude/hooks/conventions index was suggested — marginal value given.claude/is mostly machine-local/gitignored already; skipping.
Run ended 02:32
Section titled “Run ended 02:32”18 sweep cycles ran (23:35 → 02:32 BST) across all 7 lenses, several rotated through 8-10 rounds
each. 2 trivial fixes committed (5e6e20b, 8b17074), both comment/dead-line cleanups outside
/sim. 146 findings logged for review (bugs, balance ideas, perf ideas, false-positive
corrections all included in that count) plus 10 skill ideas. Read the Executive summary
at the top of this doc first — it has the 6 highest-priority items distilled out, including the
most severe finding of the night (the “crit” upgrade being a complete no-op). Doc is ready for
Chris to review.