Skip to content

Overnight performance + code-quality audit log (2026-07-22)

Overnight performance + code-quality audit log (2026-07-22)

Section titled “Overnight performance + code-quality audit log (2026-07-22)”

Trigger: Chris asked for an unattended overnight loop — every ~10 minutes for 3.5 hours (23:15 → ~02:45 local, 2026-07-22/23) — running a lightweight adversarial workflow that hunts for new performance and code-quality issues, re-checks the still-open backlog from 2026-07-22-atv-performance-adversarial-investigation.md, adversarially verifies every candidate, and implements+commits+pushes anything confirmed low risk (small, mechanical, matches an existing pattern already used elsewhere in the codebase). Medium/high-risk findings are logged here, not implemented.

Mechanism: .claude/workflows/overnight-perf-quality-cycle.js (gitignored, local-only) run via a session cron job (*/10 * * * *), gated by .claude/overnight-loop-state.json for the iteration/time cutoff. Each cycle’s guardrails: never touches BUILD numbers/export presets/release files, never re-pins the determinism checksum, never touches sim/*.gd’s sim/render separation rule, no force-push, reverts and downgrades to report-only if tests fail.

Cycles are appended below in order, oldest first. Skim the bold FIXED lines for anything that landed; everything else is report-only (safe to action manually later, or already actioned).


5 findings (1 from backlog triage, 2 performance, 2 code-quality), all 5 survived adversarial verification (2/2 skeptics agreed on each).

  • FIXED — HUD _hp_label.text reshaped every frame despite being permanently hidden since 2026-07-16 (ui/hud.gd:195). Gated behind the same if _hp_label.visible: pattern the sibling labels already use. Commit 42f2aab.
  • FIXED — MultiMesh.instance_count reassigned unconditionally every frame in render/swarm_renderer.gd + render/archetype_renderer.gd (core/halo/hotcore + per-shape-bucket
    • per-shell), even when the count hadn’t changed — a real reallocation cost paid every tick during combat. Added a dirty-check (if X.instance_count != n: X.instance_count = n) at each site. Commit 821d1c5 — this commit also swept in an unrelated stale-comment fix from a sibling agent (see below), a bystander of a workflow-concurrency bug, not a mistake in the fix itself.
  • FIXED (documentation only, zero behavior change) — render/archetype_renderer.gd:128’s BAKE_OVERSAMPLE comment referenced a tools/glow_bake/bake_shape_glows.gd constant (OVERSAMPLE) that doesn’t exist. Rewritten to describe the real relationship. Landed inside commit 821d1c5 (see note above).
  • FIXED (documentation only) — render/swarm_renderer.gd:8’s “PROTOTYPE, not yet approved for rollout” comment on the hotcore glow layer was stale — git log confirms it shipped live in BUILD 180+ and CLAUDE.md’s own Current status confirms BUILD 182 deployed to Apple TV. Comment rewritten to say it’s live with no on/off toggle (unlike the sibling USE_BAKED_GLOW flag). Also landed inside commit 821d1c5.
  • Reported, not implemented — sim/spatial_hash.gd’s rebuild() is called redundantly up to 6-10+ times per tick (once canonically in sim.gd:827, then again defensively by weapon_nova.gd, weapon_scar_tissue.gd, weapon_thunder_wake.gd, weapon_orbit.gd, and 3 separate call sites in weapon_wildfire.gd). Real finding, rated medium risk (touches 6 call sites across weapons + sim/, needs care around the sim/render determinism rules) — left for a deliberate follow-up, not an unattended overnight fix.

⚠️ Process note, read before trusting future cycles’ “FIXED” commits at face value: this cycle’s Fix phase ran all 5 fix agents concurrently against the same non-isolated working tree (a bug in overnight-perf-quality-cycle.js’s use of pipeline() for a single-stage step, which provides no serialization). Two agents both touched render/swarm_renderer.gd at the same time; one agent (the SpatialHash one, which ultimately reported rather than fixed) left exploratory edits across sim/elemental_system.gd + 4 weapon files that another agent’s transcript mistakenly described as “a concurrent agent’s in-progress work” — it was a sibling agent from this same workflow run, not an outside process. Nothing was lost or corrupted (verified by hand afterward: 2 clean commits, one harmless duplicate no-op diff on CLAUDE.md discarded, nothing pushed until reconciled) but the loop was paused after this cycle rather than left running overnight as originally set up. The script needs a fix (serialize the Fix phase, or run it with isolation: 'worktree') before the loop resumes.

Script fixed after cycle 1 (Fix phase now runs sequentially, one agent at a time; risk gate moved to a hard first-step check). 0 backlog items left (cycle 1 cleared what qualified), 4 fresh findings (2 performance, 2 code-quality — both code-quality ones turned out to be perf-flavored too), all 4 survived adversarial verification.

  • FIXED — sim/weapon_wildfire.gd:81’s _step_fuses() rebuilt the spatial hash every tick whenever Wildfire was equipped but never queried it there (both real consumers, _detonate() and _step_burn_zones(), already rebuild before their own queries). Pure dead-code removal. Full suite + determinism baseline green, no re-pin needed. Commit 9220a39.
  • Reported, not implemented (risk correctly downgraded by the fix agent itself, not the finder) — sim/weapon_orbit.gd:31’s “defensive” hash rebuild looked like the same class of redundant rebuild as the Wildfire one above and was initially rated low-risk, but attempting the identical fix broke tests/test_weapon_orbit.gd (a test calls WeaponOrbit.update() directly without going through Sim.tick(), so it depends on Orbit’s own internal rebuild to see enemies added after Sim.new()). The fix agent caught this via the test suite, reverted cleanly (git checkout -- sim/weapon_orbit.gd, confirmed clean), and reported instead of forcing it through — exactly the safety net this loop is supposed to provide.
  • Reported, not implemented (risk=medium, hard-gated before any file was touched) — render/archetype_renderer.gd:666’s per-shape shell-glow layers issue 14 uncached RenderingServer calls per enemy per frame (7 layers × 2 calls), un-batched; same shape of issue in render/swarm_renderer.gd’s core+halo+hotcore (6 calls/instance). This is the concrete code-level cost behind the “owed: on-device FPS check of the shell technique” item already in CLAUDE.md — worth a deliberate look, not an unattended fix.
  • Reported, not implemented (risk=medium, hard-gated) — ui/threat_radar.gd:124’s select_top_k() full re-sorts a 24-element array (with a freshly-allocated comparator lambda) on every enemy that clears the inclusion bar, every frame — an overlooked hot spot in the exact file that was just reworked for this class of cost on 2026-07-22.

Process note: this cycle’s Fix phase ran fully sequentially with no working-tree collisions — the cycle-1 concurrency bug appears fixed. The hard risk-gate also worked as intended: both medium-risk findings were declined in ~3 seconds flat (single tool call, immediate report) rather than being reasoned into “actually this looks safe” the way the MultiMesh finding was in cycle 1.

6 findings (1 backlog, 3 performance, 2 code-quality). 5 of 6 survived adversarial verification — the 6th (5 boss-overlay renderers unconditionally calling queue_redraw()) was refuted by a verifier who flagged that “just mirror commit 4f53bdf’s pattern” doesn’t automatically hold given the finding below about that same pattern being reverted elsewhere; correctly dropped before ever reaching Fix.

  • FIXED — render/wormhole_renderer.gd’s update_wormholes() destroyed and rebuilt its Label child (font override, size, alignment, colour) every single frame regardless of whether the wormhole list actually changed — real Node churn + TextServer reshaping 50-60×/sec for content that’s static the overwhelming majority of the time it’s on screen. Added a dirty-check matching the codebase’s existing pattern, early-return when unchanged. Render-only, full suite green, no determinism check needed. Commit 262f15b.
  • Reported, not implemented (risk=low, but correctly declined anyway) — the elite-bar/decoy/ powerup renderers’ unconditional queue_redraw() looked like the same fix already applied to 8 sibling renderers in commit 4f53bdf (2026-07-09), but the fix agent checked git history first and found that exact “gate on emptiness” pattern was applied to zone_renderer.gd and weapon_pickup_renderer.gd in that same commit, then explicitly reverted in 734ef39 (2026-07-14) for a stale-visual bug (gating on emptiness skips the frame that should draw the last frame’s content fading out). Good catch — this is exactly the kind of “looks safe, actually isn’t” trap the fix agent is supposed to catch before committing, not after.
  • Reported, not implemented (risk=medium, hard-gated) — sim/enemy_behaviors.gd:623’s Wyrm-chain grouping does a second full enemy-array scan every tick (with a per-group sort allocating a fresh lambda) even when zero Wyrms are alive, duplicating a scan _move_enemies already does.
  • Reported, not implemented (risk=medium, hard-gated) — sim/elemental_system.gd:270’s web-drop dedup does an O(spiders × live webs) linear scan every tick with no spatial index, despite SpatialHash already existing and being used elsewhere in the sim for exactly this.
  • Reported, not implemented (risk=medium, hard-gated) — tools/glow_bake/bake_shape_glows.gd hardcodes its own copy of SHELL_SCALES/SHELL_ALPHAS/SHELL_BOOST, which must stay hand-synced with render/archetype_renderer.gd’s identically-named consts — the same discipline-not-structure failure mode that caused the real BAKE_OVERSAMPLE bug fixed earlier this session (BUILD 182). Suggested fix (reference ArchetypeRenderer’s consts directly instead of duplicating them) is good but touches the bake tool, correctly left for a deliberate pass.

4 findings (1 backlog, 2 performance, 1 code-quality — labeled code-quality but perf-flavored), all 4 survived adversarial verification. Best cycle yet: 3 of 4 fixed.

  • FIXED — render/funzo_renderer.gd/eye_renderer.gd/graviton_renderer.gd called queue_redraw() unconditionally every frame regardless of whether their boss was even alive — since only one boss type is ever active per run, up to 3 of these overlay renderers paid that cost for zero visual output almost the entire game. Gated via visible = alive in _process(), matching fx/screen_feedback.gd’s established idiom. Boss2Renderer/BossRenderer were deliberately left alone (they draw lingering rockets/missiles outside their own alive-check, so the same gate would be a real behavior change there, not a mechanical no-op). Commit 62ad88c.
  • FIXED — sim/enemy_attacks.gd’s update_orbiters/update_lancers each did a full presence-only prescan of the enemy array before a second full pass to do the real work — the only two functions in the file not using the single-pass continue-filtered pattern every sibling function already uses. Folded into one pass. Full suite + determinism baseline green. Commit faec9e1.
  • FIXED — ai/skilled_auto_player.gd called ThreatMap.compute() independently from 3 separate call sites every input-poll (6 full O(enemy) passes total for what’s the same result within one tick). Added a per-tick, per-pilot memoized cache — keyed on sim.run_time and pilot reference specifically because co-op soak runs reuse one SkilledAutoPlayer instance across two pilots in the same tick, so a tick-only cache key would’ve leaked pilot 1’s threat read into pilot 2’s decisions. AutoPlayer/SkilledAutoPlayer sit outside /sim and don’t affect determinism, confirmed via the class’s own header comment — no re-pin needed. Commit c0ae40a.
  • Reported, not implemented (risk=medium, hard-gated) — the same “redundant hash rebuild” pattern as cycle 2’s Wildfire finding, this time across 5 weapon files at once (weapon_nova.gd/weapon_orbit.gd/weapon_scar_tissue.gd/weapon_thunder_wake.gd/ weapon_wildfire.gd) rebuilding a hash sim.gd:827 already freshly built moments earlier — correctly declined given cycle 2 already showed one instance of “looks redundant” turning out to be load-bearing for a test elsewhere; a multi-file version of that same pattern deserves a deliberate look, not an unattended sweep.

6 findings (1 backlog, 2 performance, 1 code-quality; 2 of the 4 fresh ones turned out to be the same “MultiMesh sized to the wrong count” and “redundant hash rebuild” shapes already seen in earlier cycles, applied to new files). 5 of 6 survived verification.

  • FIXED — render/boss_renderer.gd (Warden) called queue_redraw() unconditionally every frame with no alive-gate — the one boss renderer missed from the 2026-07-22 batch that already fixed its 3 siblings (Graviton/FunZo/Eye). Gated on alive or missiles-lingering, mirroring the existing pattern exactly. Commit a2222ed.
  • FIXED — same fix, same reason, for render/boss2_renderer.gd (Sentinel). Commit 5aa6017.
  • FIXED — main.gd’s _sync_aim_lance() sized the aim-lance MultiMesh (and its halo) to the entire projectile pool count every frame (every weapon’s shots, not just the aim weapon’s handful), with no equality guard — breaking both halves of the convention swarm_renderer.gd/ archetype_renderer.gd already established. Fixed to size to the actual aim-lance instance count with a dirty-check. Commit a74befd.
  • FIXED — ui/boss_edge_indicator.gd’s label text/colour override was rebuilt every frame even when unchanged — the same anti-pattern this loop already fixed twice this session (ui/hud.gd cycle 1, render/wormhole_renderer.gd cycle 3). Dirty-checked. Commit 8f702f5.
  • Reported, not implemented (risk=medium, hard-gated) — ui/boss_edge_indicator.gd’s off-screen arrow indicator apparently never calls queue_redraw() at all, which sounds like it could be a real correctness bug (arrow not updating), not just a perf one — worth Chris’s own look rather than an unattended fix, since “is this actually broken in play” needs eyes-on, not just code-reading.
  • Reported, not implemented (risk=medium, hard-gated) — the redundant spatial-hash-rebuild pattern from cycles 2/4, now also confirmed in ElementalSystem.resolve_collisions() itself, on top of 5 weapon files. Same “needs a deliberate pass” call as before.

0 backlog items left, 3 fresh findings (1 performance, 1 code-quality — the 2 performance findings merged into a single spatial-hash entry with the “Verify” pass, see below). All 3 survived verification.

  • FIXED — ui/threat_radar.gd declared KERNEL_WIDTH/SECTOR_COUNT GDScript consts that looked load-bearing (one even commented “must match ThreatMap.SECTORS”) but were never actually referenced anywhere — the real values driving the shader are a separate hardcoded copy baked into the embedded shader source string, which GDScript consts can’t interpolate into. Same “silently disconnected duplicate” failure mode as the BAKE_OVERSAMPLE bug fixed in BUILD 182. Deleted the dead consts, left a comment pointing at where the real values live. Commit 21d5195.
  • Reported, not implemented (risk=medium, hard-gated) — yet another instance of the redundant spatial-hash-rebuild pattern (cycles 2/4/5’s running theme), this time also flagging the recursive case in WeaponWildfire._ignite() where a chain reaction rebuilds the hash once per newly-ignited neighbour. The backlog is building a clear case for a deliberate pass across all weapon files at once, rather than another piecemeal attempt.
  • Reported, not implemented (risk=low, but correctly declined anyway) — WeaponThunderWake’s per-tick dictionary rebuild in _track_movement() looked like a mechanical dirty-check fix, but the fix agent read the file’s own header comment first and found it’s a deliberate design choice (self-pruning dead enemies without needing a _sweep_dead hook) — the suggested “fix” would have required inventing new pruning logic, not a small mechanical change. Second time this session a fix agent caught a “looks safe” finding that wasn’t (cycle 3 had the same class of save via git history).

0 backlog items, 2 fresh performance findings + 1 code-quality finding (about a gap in one of today’s own earlier fixes). All 3 survived verification, all 3 fixed — clean sweep.

  • FIXED — the 4 boss overlay renderers (eye_renderer.gd/funzo_renderer.gd/ graviton_renderer.gd/boss2_renderer.gd) gated on visible = alive back in cycle 5, but each still called render_info() TWICE per frame while any boss is alive (once in _process() just to read alive, again in _draw() for the real data) — each call is an O(enemy) linear scan + fresh Dictionary build. render/boss_renderer.gd (Warden) already had the right pattern (cache once, read from a member). Mirrored it across all 4. Commit 23b132d.
  • FIXED — self-correcting catch: cycle 5’s aim-lance fix (a74befd, earlier tonight) stopped the MultiMesh sizing bug but did it by adding a THIRD full-pool scan alongside two that already existed, all checking the identical condition. Collapsed to one collection pass into a reused scratch buffer. Commit 4ffd4db.
  • FIXED — that same aim-lance fix had zero regression test coverage, unlike the sibling renderers its own comment claimed to match. Added a GUT test asserting the MultiMesh sizes to the aim-shot subset, not the whole pool — closing the gap so the exact bug just fixed can’t silently return. Commit ceddb37.

Notable: this cycle audited its own earlier work from the same session (the aim-lance fix) and found two real gaps in it — the loop is now checking behind itself, not just finding fresh ground.

0 backlog items, 3 fresh findings (2 performance, 1 code-quality) plus 1 refuted before reaching Fix: ui/boss_teaser_overlay.gd’s animated backdrop ticking/redrawing outside the overlay’s open window — verifiers found Godot’s CanvasLayer-hiding behavior likely already suppresses the child’s _draw() in practice, so the “real cost” premise didn’t hold up under scrutiny.

  • FIXED — render/archetype_renderer.gd’s sync() had a type_to_shape parameter that hasn’t actually been read since a 2026-07-15 perf trim switched to an inline shape_for() lookup — but main.gd was still building and passing that now-pointless array every frame. Pure dead-code removal; also updated 3 other stray callers (tests, tools/glow_compare, tools/sound_map_bake, tools/funzo_lab) found via a repo-wide grep beyond what the finding named. Commit 0ccf0e9.
  • Reported, not implemented (risk=medium, hard-gated) — 9 separate full-population enemy scans run every tick with no per-type early-out (update_shooters/update_tank_fire/update_ranged/etc. plus step_wyrm_chains), 5 of them also reallocating a fresh Dictionary every tick regardless of whether that enemy type has any live instances. Real finding, needs a per-type instance counter added to EnemyPool first — a genuine small feature, not a mechanical fix.
  • Reported, not implemented (risk=medium, hard-gated) — ArchetypeRenderer.sync()’s live-shell technique issues ~14 RenderingServer calls per procedural enemy per frame (7 layers × transform+ color), a CPU-side cost distinct from the GPU overdraw cost CLAUDE.md already flags as unmeasured — a genuinely separate lever worth profiling before assuming the eventual fix is GPU-side.

0 backlog items, 2 fresh findings (1 performance, 1 code-quality), both survived verification, both correctly hard-gated at risk=medium with zero files touched.

  • Reported, not implemented — 6 separate enemy_attacks.gd update functions (update_shooters/update_ranged/update_custom_attacks/update_tank_fire/ update_elite_brute_slams/update_fleet_kite_fire) each do a full O(enemy_count) scan AND allocate a fresh Dictionary every tick, unconditionally, even when zero enemies of their target type are alive — a sim-tick-side (CPU) analog to the render-side alloc-trim work already done in W11. Needs a per-type presence signal built once per tick first; not a mechanical fix.
  • Reported, not implemented — tools/glow_bake/bake_shape_glows.gd hand-duplicates ArchetypeRenderer’s shell-glow constants (SHELL_SCALES/SHELL_ALPHAS/SHELL_BOOST/ BASE_RADIUS) and its shape-name table, the exact “must stay hand-synced, nothing enforces it” pattern that’s already caused two real bugs this session (BAKE_OVERSAMPLE, and this cycle’s own earlier ui/threat_radar.gd dead-const removal cited as the counter-example). The bake tool already depends on ArchetypeRenderer directly, so referencing its consts instead of re-declaring them is straightforward — but touches a tool script outside the usual sim/render hot path this loop is scoped to verify quickly, so left for a deliberate pass.

Stopped early on Chris’s request (9 of a planned ~21 cycles run, from 23:15 to 02:42 — the session’s attention was split across a real iPhone crash investigation and a separate one-off legacy-elemental audit in the middle of the run, both documented elsewhere in this session).

16 fixes committed to main across cycles 1-9 (excluding the separate manual legacy-elemental-audit fixes and the BUILD 183 deploy, which aren’t part of this automated loop):

42f2aab 821d1c5 9220a39 262f15b 62ad88c faec9e1 c0ae40a a2222ed 5aa6017 a74befd 8f702f5 21d5195 23b132d 4ffd4db ceddb37 0ccf0e9

Roughly a dozen more findings were correctly reported-only (mostly risk=medium, hard-gated before any file was touched) — the recurring theme across cycles 2/4/5/6/8/9 is a redundant SpatialHash.rebuild() pattern across several weapon files, still open, worth a deliberate pass rather than another piecemeal attempt. A few findings were also correctly refuted at verify (cycle 3’s boss-overlay queue_redraw claim, cycle 8’s BossTeaserOverlay backdrop claim) or declined by the fix agent itself after reading further context that the finder missed (cycle 2’s weapon_orbit rebuild broke a test; cycle 6’s Thunder Wake dictionary rebuild was deliberate design, not an oversight) — the adversarial-verify + hard-gate + test-driven-revert design held up across all 9 cycles with zero bad commits landing.

Process note for next time: cycle 1 exposed a real concurrency bug in the workflow script’s Fix phase (fixed same session, held for the remaining 8 cycles with no repeat).