Bullet Heaven — Housekeeping Backlog
Bullet Heaven — Housekeeping Backlog
Section titled “Bullet Heaven — Housekeeping Backlog”Standing backlog for infra/tooling debt discovered mid-session that’s worth doing but shouldn’t block whatever the current task is. Check this before starting significant new work — pick off anything cheap/important first. Not for gameplay/content tasks (those live in the roadmap memory + git log); this is specifically for “we noticed this is broken/awkward, fix it sometime.”
Convention per entry: date discovered, one-line description, why it matters, rough effort.
Mark DONE (commit X) in place rather than deleting, until the next prune.
-
2026-07-09/10 — playtest batch 2 cleanup. Batch A (dash-knockback-while-lunging, enemy-sound on-screen gating, upgrade-screen death-vfx, hazards-don’t-pause-on-boss) is merged to
main.- ✅ 2026-07-10: all Batch A fixes live-confirmed by Toby — dash knockback and enemy sound
fixes confirmed working; boss-fight hazards confirmed persisting correctly. The upgrade-screen
corpse fix needed a THIRD pass: Toby caught the Elite Orbiter’s own sprite + its elite name/HP
bar still frozen-visible behind the panel even after the death_dissolve/gpu_burst/fx_layer VFX
suppression (previous 2 passes) — root cause was different:
enemy_renderer.sync()/elite_bars.update_elites()sat AFTER the game_over/_warping/_paused_for_levelup early returns inmain.gd, so they stopped running the instant the level-up pause began and froze on the last PRE-death frame (the sim had already swept the kill, the renderer just never got told). Procedural shapes masked this behind their death VFX; a detailed sprite (Orbiter) had nothing covering it. Fixed by moving both calls above the early returns, same spot as the existing render_fx_events drain. GUT green (223/223 → 220/220 after the merge dedup below), confirmed live. Commit9cfd2b6. - ✅ 2026-07-10:
balance/damage-tuning-2026-07-09merged tomain. Reconciled against the “crystals ruleset renamed to survival + reactions-mode archived” refactor that had landed onmainwhile this branch was in flight, plus a further P2-upgrade-pipeline batch that landed on top of that — one real conflict, intests/test_main.gd(a duplicate/half-renamed copy oftest_fx_layer_spark_for_the_levelup_kill_is_also_suppressed+test_death_vfx_plays_normally_ outside_a_levelupfrom the two branches’ independent fixes to the same test); resolved by keeping one de-duplicated"survival"-mode copy of each. Full suite green post-merge (220/220 scripts, 0 failing); determinism baseline held with no re-pin needed (damage-value tuning doesn’t touch the RNG draw sequence). - Still open — answer the 4 open design questions in
docs/superpowers/specs/2026-07-09- dimension-choice-popup-design.md(the portal-system-replacement design, written but not planned/implemented) so it can be turned into an executable task plan: exclusion scope across a multi-boss run, panel visual treatment, whether Home counts in the offer pool, and first-boss-at-Home behavior. That spec file is currently UNTRACKED (never committed anywhere) — commit it tomaindirectly once reviewed (it’s docs-only, no code risk) rather than leaving it only in the working tree. - Minor process note, not a bug: branch-hopping between
mainand the balance branch mid- session (to get the fx_layer fix ontomainindependently) transiently desynced Godot’s.godot/global class cache from disk (CrystalsLevelUpPanel not declaredeven though the file was correct) — same documented class of issue asdocs/godot-gotchas.md’s stale-cache entry, fixed withgodot --headless --path . --import. If tests look wrong for no reason right after switching branches, try that before assuming a real regression.
- ✅ 2026-07-10: all Batch A fixes live-confirmed by Toby — dash knockback and enemy sound
fixes confirmed working; boss-fight hazards confirmed persisting correctly. The upgrade-screen
corpse fix needed a THIRD pass: Toby caught the Elite Orbiter’s own sprite + its elite name/HP
bar still frozen-visible behind the panel even after the death_dissolve/gpu_burst/fx_layer VFX
suppression (previous 2 passes) — root cause was different:
-
2026-07-08 — overnight
/loopreview doc has unactioned findings (docs/overnight-review-2026-07-08.md, 16 cycles, 146 findings). Cheap/verified items already fixed 2026-07-09 (rendererqueue_redraw()waste, deadboss_rotation.maybe_spawn_survival_boss(), Centurion bestiary gap, 2 skill drifts, a HUD boss-label clear, Boss2’s missing enrage visual telegraph, the “crit” upgrade implemented for real — seesim/player_crit.gd). Still open, real gameplay/tooling work — not infra debt, but flagging the pointer here since NEXT_TASKS is the standing “check this first” list:- ✅ 2026-07-09: co-op P1-only damage/hazard hit-checks fixed (boss2/boss_warden/eye/graviton/ funzo/elemental_system/enemy_attacks + 2 render telegraphs) — P2 can now actually take damage from bosses/hazards instead of being functionally invulnerable to them. Ship bonuses also fixed — P2 now mirrors P1’s selected hull on co-op join. See git log 2026-07-09.
- Still open, and each genuinely bigger than “thread a param” (confirmed 2026-07-09, not just
the overnight review’s original estimate):
- ✅ Kill-XP/upgrade routing — DONE (2026-07-10, commit pending in this session).
_award_xp()banks shared party XP to every alive pilot (2026-07-09 commit104b24c), and the full player-visible pipeline now follows:upgrade_system.gd’s ~29 functions all take an explicitpilot: PlayerStateparam (Task 1), the crystal-threshold auto-upgrade path applies per-pilot (Task 2), andmain.gd’s level-up trigger now scans ALL pilots and chains panels sequentially — whichever pilot leveled up first (P1 before P2 on a simultaneous level-up) gets the panel, sim stays paused for the other pilot throughout (Task 3).LevelUpPanelis pilot-aware: shows the correct pilot’s own build/stats, gates controller input to that pilot’s own device in co-op, and labels which pilot the panel belongs to. Full brainstorm→spec→plan→ SDD cycle as recommended below; design:docs/superpowers/specs/2026-07-10-p2-upgrade-pipeline-design.md. ⚠️ Still owed: a real 2-controller co-op playtest — confirm panels chain correctly on a real level-up, P2’s controller can’t be hijacked by P1 mashing buttons, and the P1/P2 label reads clearly during actual gameplay (not automatable in CI). - ✅ Drone/decoy system — DONE (2026-07-11,
83f1e98..0a6ddf4).DroneStategained anowner: PlayerStatefield;drone_chargemoved ontoPlayerState(per-pilot, Sim keeps a P1-only forwarding accessor).sim.dronesstayed ONE shared flat pool (the original “how does enemy anti-drone targeting read multiple pilots’ pools” question resolved to “don’t change it” — enemies already treated it that way, zero changes needed there).Sim.tick()now loops every pilot for drone recharge/deploy, mirroring the weapon-firing loop’s_pilot_inputspattern. Also removed the decoy-proximity damage-synergy mechanic entirely (Chris’s call — reactions-mode-era leftover, not needed now that drones carry their own value). Full brainstorm→spec→plan→SDD cycle, 3 tasks, one fix round (2 new regression tests initially passed on both correct and broken code — caught + fixed + independently re-verified via a reverted-worktree run), clean final whole-branch review (Opus). Design:docs/superpowers/specs/2026-07-11-p2-drone-decoy-design.md. ⚠️ Still owed: a real 2-controller co-op playtest (not automatable in CI) — confirm P2’s drone deploys/recharges correctly on real hardware.
- ✅ Kill-XP/upgrade routing — DONE (2026-07-10, commit pending in this session).
- Co-op P1-only hardcoding is far more widespread than CLAUDE.md’s “two gaps” framing — the
doc has a full ~34-call-site catalog across
/sim,render/,main.gd,ui/. Ready-made checklist for whoever picks up P2 progression. - Obsidian cruiser is close to a strict upgrade over all 6 frigate hulls — quantified numbers in the doc, useful input for the frigate/cruiser balance pass already on CLAUDE.md’s list.
- 4 skill ideas surfaced (
bh-add-renderer,bh-add-sound-event,bh-determinism-repin,bh-write-test) — see the doc’s “Skill ideas” section. - Owed on-device/playtest: the new player weapon crit (
PLAYER_CRIT_MULT = 2.0, 15% per “Focus” pick) hasn’t been balance-tested — tune the multiplier once it’s been played.
-
2026-07-05 — one-off test flake seen on the tvOS project’s FIRST suite run immediately after a fresh
--import(platform/tvos, pre-deploy verification for Elemental Dimensions v2): 1580/1583 passing (1 failing, intest_boss_gate.gdarea per the run’s error output) on the very first run, then 6 consecutive re-runs all came back byte-identical clean (1581/1583 passing, 22977 asserts every time). Never recurred once the resource cache was warm. Plausibly a first-run-after-reimport cache-warming race, not a real regression — determinism held throughout every clean run. Not chased further since it didn’t reproduce; if this ever recurs on a genuinely fresh run (not just after--import), that’s the signal to investigate for real. -
✅ DONE (2026-07-04) —
scripts/check-test-count.shcan’t complete on this machine: godot’s headless run SIGABRTs (exit 134) at teardown even when the suite is fully green. Cause is RID leaks at exit (“N RIDs of type CanvasItem were leaked” / “RID allocations … leaked at exit”), not a test failure — the suite prints1518 passing / 0 failingfirst, then godot aborts on the way out. The guard’sset -euo pipefailcatches that 134 on theout=$(godot …)line and aborts BEFORE printing its Scripts-count check, so a fully-passing run looks like the guard itself broke. (Thebh-dev-chunkskill documentsset -eaborting on a failing test; this fires on a CLEAN exit too — a distinct trigger.) Workaround used this session: verified the count manually —ls tests/test_*.gd | wc -lvs the suite’s^Scriptsline (204/204). Fix (~15 min): make the guard tolerant of godot’s exit code — captureoutwith|| true(orset +earound just that one call) and decide from the parsedScripts/Failinglines, not godot’s exit status. Why it matters: the count guard is the anti-silent-drop safety net; if it can’t run, that protection is effectively off. Fixed 2026-07-04: the guard no longer wraps the godot run inset -e— it captures the output with|| trueand judges purely by parsing GUT’s summary (Scripts count + a derivedTests − Passing − Riskyfailure count, since GUT omits a “Failing” line when zero fail). So godot’s exit-134 teardown can’t abort it, and it still fails loud on a count mismatch OR a real test failure. Verified: exits 0 with204/204 test scripts ran, 0 failingon the green suite. -
2026-07-05 — Godot’s exit-134 SIGABRT at headless/editor shutdown is a known, OPEN upstream engine bug, not ours: godotengine/godot#119279 (“Crashes on exit, when
GDScriptresources outliveGDScriptLanguage”), still reproducible on 4.7 beta as of the issue’s own report. A community fix (PR #119809) was validated by its own author (exit 134→0 on both 4.6.3-stable and master) but was CLOSED, not merged — per Chris, rejected because it was AI-authored, not for a technical reason. A local checkout with that patch applied already exists at~/Claude/godot-patched-4.7(built: macOS editor target only, arm64) — NOT yet built out to the export templates this project’s pipeline actually needs (macOS export, iOS/tvOS, HTML5/Web), and not yet re-validated against GUT/the determinism baseline/the tvOS export pipeline. Immediate relief applied 2026-07-05:defaults write com.apple.CrashReporter DialogType none— suppresses the macOS crash-reporter POPUP system-wide (reversible viadefaults delete com.apple.CrashReporter DialogType); the crash itself was already harmless to test correctness (the GUT suite always prints its full pass/fail summary before the abort). Deferred, Chris’s call: finishing the patched-4.7 export-template build + full toolchain re-validation is real, separate effort — do this as its own deliberate piece of work, not mid-flight during an unrelated feature branch. Revisit if the popup suppression stops being enough, or once an official (maintainer-merged) fix lands upstream.
Done (kept for reference until next prune)
Section titled “Done (kept for reference until next prune)”-
2026-07-04 — DONE: folded the tvOS Godot project into this repo at
platform/tvos/, via SYMLINKS (the old separate~/Claude/bullet-heaven-tvosrepo is archived aside at~/Claude/_archive/bullet-heaven-tvos-old/). The open symlink-vs-copy question was resolved empirically: an isolated probe proved Godot imports + boots + runs the full 203-script suite cleanly through symlinks, so symlinks won (zero duplication, and the concurrent-clobber problem is eliminated not just made recoverable — there’s only one copy).platform/tvos/holds realproject.godot(Metal),export_presets.cfg(now TRACKED — force-included past the global ignore, fixing the old “gitignored, re-add if missing” landmine),icon.png, and the gitignoredbuild/+build-ios/Xcode shells; the 3 hand-edited non-regenerable files (dummy.mm+ two Info.plist) are tracked inplatform/tvos/patches/.bh-deploy/bh-dev-chunk/bh-live-debug-deploy/bh-appstore-releaseskills all re-pathed + the sync step removed. ⚠️ Import/boot/full-suite are green through the symlinks, but the actual on-device export→xcodebuild→devicectl was NOT re-run this session (needs the Godot fork + Xcode-beta) — the next real deploy is the final proof; the Xcode build shells were moved intact so it should behave identically. -
2026-07-05 — minor: Godot’s editor auto-generates stray
.importcache files fordocs-site/’s binary/vector assets (e.g.docs-site/public/favicon.svg.import,docs-site/src/assets/houston.webp.import) whenever the project is opened in the editor, since Godot scans the whole repo tree by default anddocs-site/isn’t excluded. These are harmless (regeneratable, don’t affect the Astro build) but show up as untracked noise ingit status. A targeteddocs-site/**/*.importgitignore rule would fix it — NOT a blanket*.importrule, since ~97.importfiles (audio assets etc.) are intentionally tracked elsewhere in the repo.