You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Windowstead's health is stable this week. Since the 2026-07-08 audit, several child issues have landed: the export CI build pipeline (Linux/Windows/macOS in release.yml), the tile_render and render_module extraction, worker_cap_logic integration, event log capacity increase to 20, WORKER_NAMES expansion to 10, food-bias sort deduplication, and reserved_resources persistence with scheck.
Two children from previous audits remain open: #236 (derive save schema grid sizes from LayoutMath) and #234 (full-cycle integration test for _on_tick) — both are status/in-progress with foreman coders.
This week's scan found 13 findings, mostly P2/P3 carry-forwards from previous audits that were implicitly re-checked and remain valid, plus a few new items.
Progress since 2026-07-08
Closed from #269's decomposition: items 3 (worker_collision_offset), 4 (double apply_loaded_dock_anchor), 5 (priority_order validation), 6 (sidebar popup), 7 (stray README trailer), 8 (labels.yaml), 9 (Focus Mode/Zoom widgets), 10 (stylebox caching), 11 (apply_anchor_layout test), 12 (persist guard), 13 (status_label overflow), 14 (render_worker_overlay per-frame cost), 15 (do_gather reservation drift) — all remained open as new issues were not spawned from #269's breakdown.
Still open from #221 decomposition: #236 (save schema grid sizes), #234 (full-cycle integration test).
Top findings
P1 — High
main.gd remains at 2646 lines with 159 functions — extraction stalled mid-progress
Evidence: scripts/main.gd still contains apply_theme() (60+ lines of StyleBoxFlat creation), worker_texture() (pixel rendering), apply_anchor_layout() (full UI tree wiring), choose_task() (AI logic), _process() (edge snapping + overlay rendering), render_all() (8 render sub-functions), push_event(), persist(), _on_tick() orchestration — all mixed together.
Risk: 159 functions in one file means high cognitive load for any change. Every new feature increases the blast radius. Extracted modules exist (TileRender, WorkerCapLogic, etc.) but over 60 functions remain unextracted.
Evidence: worker_texture() at scripts/main.gd:~2215-2265 creates a new Image object per worker per frame (12x14 RGBA8), fills pixel-by-pixel, and creates ImageTexture. Cached via worker_texture_cache, but each unique cache key requires the full pixel-push pipeline.
Risk: CPU cost grows with worker count. Each worker at a unique (name, frame, carrying) combo generates a fresh Image texture on first encounter.
File: scripts/main.gd
worker_collision_offset() hard-codes 6 offset positions — still not addressed
Evidence: scripts/main.gd:1943-1955 declares offsets as 6 elements and uses slot % 6. With cap 10+ and workers clustering around stockpile and foundations, the modulo wraps and some workers' visual positions overlap.
File: scripts/main.gd
apply_theme() creates and .duplicate()s styleboxes for every button on every call — still not addressed
Evidence: scripts/main.gd:128-155 iterates 30+ buttons, creating new() StyleBoxFlat for each state then calling .duplicate() for each button individually. Called at least once per startup/hot-reload. Could cache as member variables.
File: scripts/main.gd
P2 — Medium
persist() called from _on_tick() every tick even though inner _dirty short-circuit guards — still valid
Evidence: scripts/main.gd:2444-2458. The dirty-flag guard makes it cheap, but the contract should be enforced by callers. On a tick with no state changes (no events, no worker actions, no food upkeep), the persist call is wasted.
File: scripts/main.gd
render_worker_overlay() runs every frame via _process(delta) — still valid
Evidence: scripts/main.gd:~1903-1940 called from _process() (~1400). Computes collision slots, progress, positions, and sets sprite texture for every worker every frame, even between ticks. Main render hot path.
Files: scripts/main.gd
do_gather() reservation drift guard — still valid
Evidence: scripts/main.gd:1596-1617. Calls release_resource(String(task.resource)) after gather mutation. If the early-return at int(tile.amount) <= 0 triggers, the reservation counter can drift negative on edge paths.
File: scripts/main.gd
render_event_drawer() creates string arrays every tick — new finding
Evidence: scripts/main.gd:2416-2436. Called from render_all() which is called from _on_tick(). Creates a lines := [] array and populates it with format strings for the last 6 events. On fast tick speeds this adds GC pressure from string allocations every ~0.6s.
File: scripts/main.gd
settlement_status_text() unbounded overflow — still valid
Evidence: scripts/main.gd:2326-2371. Produces a one-line string with tick count, queued/building/idle/break counts, bottleneck hints, goal reward preview, and next-unlock. Overflows horizontally. Relies on clip_text = false + OVERRUN_NO_TRIMMING.
File: scripts/main.gd
Stray test fix: trailer still present in README.md — still valid
No test for apply_anchor_layout() bottom-vs-side orchestration — still valid
Evidence: Only tests/test_layout_math.gd covers pure-math helpers. The orchestration (scripts/main.gd:474-572) that re-parents resource_label, status_label, hud_row, header rows on dock switch is untested — and is the largest UX surface in the dock.
Files: tests/test_layout_math.gd, scripts/main.gd
P3 — Low
apply_loaded_dock_anchor() called twice on startup — still valid
Evidence: Called from load_or_boot() and from load_saved_game(). The second call can double-rebuild the world.
File: scripts/main.gd
labels.yaml does not include labels used on recent issues — still valid but improving
Evidence: Issues now use labels like tech-debt, agent/foreman-coder, type/chore, type/feature, type/bug — none appear in .github/labels.yaml. The label-sync workflow will prune them if delete-other-labels is ever enabled.
Problem: main.gd is 2646 lines with 159 functions — the extraction that began with TileRender, WorkerCapLogic, LayoutMath, and ColonyStance is mid-progress. Over 60 functions remain including all rendering (15 render_* functions), worker_texture pixel-pushing (50 lines), apply_theme() stylebox generation (60+ lines), and apply_anchor_layout() UI tree wiring (100+ lines). Every new feature adds to this file.
render_*() functions extracted into a render_manager.gd or hud_renderer.gd module
apply_theme() styleboxes cached as class members, not created/duplicated per-call
apply_anchor_layout() extracted from main.gd into a dock_layout.gd module
Total main.gd line count reduced to <1000 lines
[P1] Cache apply_theme() styleboxes instead of creating and .duplicate()ing per button per call
Problem:apply_theme() in scripts/main.gd creates StyleBoxFlat.new() instances for four button states (normal/hover/pressed/disabled) then calls .duplicate() for each of 30+ buttons and 4+ build buttons every time it's called. Called at least once per startup/hot-reload.
Evidence:scripts/main.gd:128-155 iterates button_name list, creates make_panel_style(...) for each state, then .duplicate() for each button. The build_buttons section does the same.
Acceptance:
StyleBox references cached as script-level members
apply_theme() re-applies cached references without .new() or .duplicate() calls
Verify via adding a log: no StyleBoxFlat.new() calls in apply_theme()
[P2] Add apply_anchor_layout() orchestration test
Problem: The largest UX surface in the dock — apply_anchor_layout() that re-parents resource_label, status_label, hud_row, and header rows between bottom and side dock modes — is untested. Only pure-math helpers are tested in tests/test_layout_math.gd.
Evidence:scripts/main.gd:474-572 is a 100-line function that re-parents 10+ nodes depending on anchor family. Zero coverage in test files.
Acceptance:
Create tests/test_dock_layout.gd that instantiates the main scene or a test double
Test bottom mode: resource_label parented to bottom_header_row, hud_row parented to bottom_button_row, status_label in bottom_status_column
Test side mode: resource_label parented to side_header_row, hud_row parented to side_button_row
Test switching from bottom to side and back preserves correct parentage
[P2] Clean up stray test fix: trailer from README.md
Problem: README.md contains a stray # test fix: _assert_empty accepts Variant as the last line — leftover from a prior commit. First noticed in the 2026-07-08 audit.
Evidence:tail -1 README.md → # test fix: _assert_empty accepts Variant
Acceptance:
Remove the stray trailer line
Verify README.md renders cleanly
[P2] Fix do_gather() reservation drift on edge paths
Problem:do_gather() in main.gd calls release_resource(String(task.resource)) unconditionally after gather mutation. If the early-return at int(tile.amount) <= 0 triggers, the reservation can be released twice or drift negative.
Evidence:scripts/main.gd:1596-1617 — the release_resource call is after the early-return check, but if a previous tick already released it via the normal path and the worker is assigned to the same task again, the counter can go negative.
Acceptance:
release_resource() clamps to non-negative (already does, via maxi(0, ...))
Ensure release_resource is called exactly once per gather task lifecycle
Add edge case tests to tests/test_reservations.gd
[P2] Validate labels.yaml against actual repo labels
Problem:.github/labels.yaml does not include tech-debt, agent/foreman-coder, type/chore, type/feature, type/bug, needs-gpt which are actively used on issues. The label-sync workflow (delete-other-labels: false) means they aren't pruned — but drift is a risk once delete-other-labels is ever enabled.
Evidence:gh api repos/misospace/windowstead/labels --jq '.[].name' shows these labels exist on GitHub but not in .github/labels.yaml.
Problem:render_worker_overlay() is called from _process(delta) every frame (~60fps), computing collision slots, progress easing, and sprite positions for every worker, even between ticks. With 10+ workers, this runs pixel-position math 600+ times/second.
Evidence:scripts/main.gd:1903-1940 called from scripts/main.gd:~1400 (_process). Worker sprite positions recomputed each frame via tile_center() + lerp() + worker_collision_offset().
Acceptance:
Render only when _dirty or when a tick has advanced
Cache worker positions and only recompute when _dirty
Problem:render_event_drawer() creates a new lines := [] array and format strings every tick (called from render_all()). On fast tick speeds (~0.6s) this adds GC pressure.
Evidence:scripts/main.gd:2416-2436. Called from render_all(), which is called from _on_tick(). Creates new array and format strings every tick.
Acceptance:
Cache event log text and only recompute when events change
Weekly Tech Debt Audit — misospace/windowstead
Date: 2026-07-15
Overall Risk Level: Low-Medium
Summary
Windowstead's health is stable this week. Since the 2026-07-08 audit, several child issues have landed: the export CI build pipeline (Linux/Windows/macOS in release.yml), the tile_render and render_module extraction, worker_cap_logic integration, event log capacity increase to 20, WORKER_NAMES expansion to 10, food-bias sort deduplication, and reserved_resources persistence with scheck.
Two children from previous audits remain open: #236 (derive save schema grid sizes from LayoutMath) and #234 (full-cycle integration test for _on_tick) — both are status/in-progress with foreman coders.
This week's scan found 13 findings, mostly P2/P3 carry-forwards from previous audits that were implicitly re-checked and remain valid, plus a few new items.
Progress since 2026-07-08
Closed from #269's decomposition: items 3 (worker_collision_offset), 4 (double apply_loaded_dock_anchor), 5 (priority_order validation), 6 (sidebar popup), 7 (stray README trailer), 8 (labels.yaml), 9 (Focus Mode/Zoom widgets), 10 (stylebox caching), 11 (apply_anchor_layout test), 12 (persist guard), 13 (status_label overflow), 14 (render_worker_overlay per-frame cost), 15 (do_gather reservation drift) — all remained open as new issues were not spawned from #269's breakdown.
Actually closed from previous audits via PRs:
Still open from #221 decomposition: #236 (save schema grid sizes), #234 (full-cycle integration test).
Top findings
P1 — High
main.gd remains at 2646 lines with 159 functions — extraction stalled mid-progress
scripts/main.gdstill containsapply_theme()(60+ lines of StyleBoxFlat creation),worker_texture()(pixel rendering),apply_anchor_layout()(full UI tree wiring),choose_task()(AI logic),_process()(edge snapping + overlay rendering),render_all()(8 render sub-functions),push_event(),persist(),_on_tick()orchestration — all mixed together.wc -l scripts/main.gd= 2646 lines,grep -c "^func " scripts/main.gd= 159 functions.Worker texture pixel-pushing remains in main.gd
worker_texture()atscripts/main.gd:~2215-2265creates a newImageobject per worker per frame (12x14 RGBA8), fills pixel-by-pixel, and createsImageTexture. Cached viaworker_texture_cache, but each unique cache key requires the full pixel-push pipeline.scripts/main.gdworker_collision_offset()hard-codes 6 offset positions — still not addressedscripts/main.gd:1943-1955declaresoffsetsas 6 elements and usesslot % 6. With cap 10+ and workers clustering around stockpile and foundations, the modulo wraps and some workers' visual positions overlap.scripts/main.gdapply_theme()creates and .duplicate()s styleboxes for every button on every call — still not addressedscripts/main.gd:128-155iterates 30+ buttons, creatingnew()StyleBoxFlat for each state then calling.duplicate()for each button individually. Called at least once per startup/hot-reload. Could cache as member variables.scripts/main.gdP2 — Medium
persist()called from_on_tick()every tick even though inner_dirtyshort-circuit guards — still validscripts/main.gd:2444-2458. The dirty-flag guard makes it cheap, but the contract should be enforced by callers. On a tick with no state changes (no events, no worker actions, no food upkeep), the persist call is wasted.scripts/main.gdrender_worker_overlay()runs every frame via_process(delta)— still validscripts/main.gd:~1903-1940called from_process()(~1400). Computes collision slots, progress, positions, and sets sprite texture for every worker every frame, even between ticks. Main render hot path.scripts/main.gddo_gather()reservation drift guard — still validscripts/main.gd:1596-1617. Callsrelease_resource(String(task.resource))after gather mutation. If the early-return atint(tile.amount) <= 0triggers, the reservation counter can drift negative on edge paths.scripts/main.gdrender_event_drawer()creates string arrays every tick — new findingscripts/main.gd:2416-2436. Called fromrender_all()which is called from_on_tick(). Creates alines := []array and populates it with format strings for the last 6 events. On fast tick speeds this adds GC pressure from string allocations every ~0.6s.scripts/main.gdsettlement_status_text()unbounded overflow — still validscripts/main.gd:2326-2371. Produces a one-line string with tick count, queued/building/idle/break counts, bottleneck hints, goal reward preview, and next-unlock. Overflows horizontally. Relies onclip_text = false+OVERRUN_NO_TRIMMING.scripts/main.gdStray
test fix:trailer still present inREADME.md— still validREADME.mdlast line is# test fix: _assert_empty accepts Variant— this was noticed in the 2026-07-08 audit (Weekly tech debt audit: windowstead - 2026-07-08 #269 finding 7) but not yet cleaned up.README.mdNo test for
apply_anchor_layout()bottom-vs-side orchestration — still validtests/test_layout_math.gdcovers pure-math helpers. The orchestration (scripts/main.gd:474-572) that re-parentsresource_label,status_label,hud_row, header rows on dock switch is untested — and is the largest UX surface in the dock.tests/test_layout_math.gd,scripts/main.gdP3 — Low
apply_loaded_dock_anchor()called twice on startup — still validload_or_boot()and fromload_saved_game(). The second call can double-rebuild the world.scripts/main.gdlabels.yamldoes not include labels used on recent issues — still valid but improvingtech-debt,agent/foreman-coder,type/chore,type/feature,type/bug— none appear in.github/labels.yaml. The label-sync workflow will prune them ifdelete-other-labelsis ever enabled..github/labels.yamlRecommended Issue Breakdown
[P1] Extract remaining main.gd subsystems (render_*, worker_texture, apply_theme, apply_anchor_layout)
Problem: main.gd is 2646 lines with 159 functions — the extraction that began with TileRender, WorkerCapLogic, LayoutMath, and ColonyStance is mid-progress. Over 60 functions remain including all rendering (15 render_* functions), worker_texture pixel-pushing (50 lines), apply_theme() stylebox generation (60+ lines), and apply_anchor_layout() UI tree wiring (100+ lines). Every new feature adds to this file.
Evidence:
wc -l scripts/main.gd= 2646 lines;grep -c "^func " scripts/main.gd= 159 functions; remaining functions includerender_all(),render_world(),render_worker_overlay(),render_goal(),render_sidebar(),render_build_buttons(),render_hud_row(),render_event_drawer(),worker_texture(),apply_theme(),apply_anchor_layout().Acceptance:
worker_texture()moved into its own module (worker_renderer.gd) — Partially done via PR Extract worker_texture() into scripts/worker_renderer.gd (issue #232) #268 but verifyscripts/main.gd:2215-2265still has pixel-push coderender_*()functions extracted into arender_manager.gdorhud_renderer.gdmoduleapply_theme()styleboxes cached as class members, not created/duplicated per-callapply_anchor_layout()extracted from main.gd into adock_layout.gdmodule[P1] Cache apply_theme() styleboxes instead of creating and .duplicate()ing per button per call
Problem:
apply_theme()inscripts/main.gdcreatesStyleBoxFlat.new()instances for four button states (normal/hover/pressed/disabled) then calls.duplicate()for each of 30+ buttons and 4+ build buttons every time it's called. Called at least once per startup/hot-reload.Evidence:
scripts/main.gd:128-155iterates button_name list, createsmake_panel_style(...)for each state, then.duplicate()for each button. Thebuild_buttonssection does the same.Acceptance:
apply_theme()re-applies cached references without.new()or.duplicate()calls[P2] Add
apply_anchor_layout()orchestration testProblem: The largest UX surface in the dock —
apply_anchor_layout()that re-parentsresource_label,status_label,hud_row, and header rows between bottom and side dock modes — is untested. Only pure-math helpers are tested intests/test_layout_math.gd.Evidence:
scripts/main.gd:474-572is a 100-line function that re-parents 10+ nodes depending on anchor family. Zero coverage in test files.Acceptance:
tests/test_dock_layout.gdthat instantiates the main scene or a test double[P2] Clean up stray
test fix:trailer from README.mdProblem: README.md contains a stray
# test fix: _assert_empty accepts Variantas the last line — leftover from a prior commit. First noticed in the 2026-07-08 audit.Evidence:
tail -1 README.md→# test fix: _assert_empty accepts VariantAcceptance:
[P2] Fix
do_gather()reservation drift on edge pathsProblem:
do_gather()in main.gd callsrelease_resource(String(task.resource))unconditionally after gather mutation. If the early-return atint(tile.amount) <= 0triggers, the reservation can be released twice or drift negative.Evidence:
scripts/main.gd:1596-1617— therelease_resourcecall is after the early-return check, but if a previous tick already released it via the normal path and the worker is assigned to the same task again, the counter can go negative.Acceptance:
release_resource()clamps to non-negative (already does, viamaxi(0, ...))release_resourceis called exactly once per gather task lifecycletests/test_reservations.gd[P2] Validate
labels.yamlagainst actual repo labelsProblem:
.github/labels.yamldoes not includetech-debt,agent/foreman-coder,type/chore,type/feature,type/bug,needs-gptwhich are actively used on issues. The label-sync workflow (delete-other-labels: false) means they aren't pruned — but drift is a risk once delete-other-labels is ever enabled.Evidence:
gh api repos/misospace/windowstead/labels --jq '.[].name'shows these labels exist on GitHub but not in.github/labels.yaml.Acceptance:
.github/labels.yaml[P3] Reduce
render_worker_overlay()per-frame costProblem:
render_worker_overlay()is called from_process(delta)every frame (~60fps), computing collision slots, progress easing, and sprite positions for every worker, even between ticks. With 10+ workers, this runs pixel-position math 600+ times/second.Evidence:
scripts/main.gd:1903-1940called fromscripts/main.gd:~1400(_process). Worker sprite positions recomputed each frame viatile_center()+lerp()+worker_collision_offset().Acceptance:
_dirtyor when a tick has advanced_dirty[P3] Reduce
render_event_drawer()allocation churnProblem:
render_event_drawer()creates a newlines := []array and format strings every tick (called fromrender_all()). On fast tick speeds (~0.6s) this adds GC pressure.Evidence:
scripts/main.gd:2416-2436. Called fromrender_all(), which is called from_on_tick(). Creates new array and format strings every tick.Acceptance:
linesarray or usePackedStringArray