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 is in a healthy mid-extraction state. Since the 2026-07-01 audit (#221), three child issues landed on main: WORKER_NAMES expansion to 10 + badge colors (#227 → PR #242), Linux export-validation in CI (#233 → PR #240), and the use_local_storage = false test workaround removal with GDScript and short-circuit fix (#238 → PR #258). The eight remaining P1/P2 children of #221 (#228, #243, #244, #245, #246, #248, #249, #250) and the P2 children (#251, #252) are still in flight or status/ready.
This week's audit found 15 new findings, mostly P2/P3. The most material are:
recruit_worker() name pool collision beyond 10 workers.scripts/main.gd:1115 uses next_index = current % len(WORKER_NAMES). With WORKER_NAMES.size() == 10 and worker_cap = BASE_WORKER_CAP + 2*huts = 2 + 2*huts, five huts allow cap = 12. The 11th recruit reuses "Jun". Evidence: scripts/main.gd:1113-1119, scripts/constants.gd:7 (WORKER_NAMES), scripts/constants.gd:74 (WORKER_CAP_BONUSES).
P2 — Medium
worker_collision_offset has hard-coded 6 offsets.scripts/main.gd:1948-1955 declares offsets as a 6-element array and uses slot % offsets.size(). With cap > 6 and clustered workers (around the stockpile, foundations), more than 6 workers can share a tile and the offset array overflows via modulo, losing some workers' visual placement.
apply_loaded_dock_anchor() called twice on startup.scripts/main.gd:351-360 (called from load_or_boot) and scripts/main.gd:879-887 (called from load_saved_game). Burns DisplayServer calls and may double-rebuild the world on a normal boot path.
validate_save_schema() does not validate priority_order contents. Only checks the field is an array. A corrupted save with priority_order: ["weird", "things"] will silently drop invalid kinds via apply_priority_order() (scripts/main.gd:1245-1265). Stricter validation would prevent bad migrations. Evidence: scripts/game_state.gd:213-216.
Sidebar popup uses hard-coded 16px offsets.scripts/main.gd:585-590 (position_popup_panel) and scripts/layout_math.gd:106-114 (popup_position_for_anchor) use fixed 16px offsets. On small/secondary monitors the sidebar can render partially off-screen. Should be relative to DisplayServer.screen_get_usable_rect().
Stray test fix: trailer in README.md. Last line of README.md is test fix: _assert_empty accepts Variant — looks like a commit-message fragment that accidentally landed in main and was never reverted. Cosmetic, but visible to contributors.
labels.yaml drift. Issues use tech-debt, agent/foreman-coder, area/product, type/chore, type/feature, type/bug, needs-gpt — none appear in .github/labels.yaml. Either add them to the file or document the label-sync workflow as the source of truth.
apply_theme() recreates and .duplicate()s styleboxes for every button on every entry.scripts/main.gd:117-155. Styleboxes could be cached as members and re-applied without .duplicate().
No test for apply_anchor_layout() bottom-vs-side orchestration. Only tests/test_layout_math.gd covers pure-math helpers. The orchestration (scripts/main.gd:473-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.
P3 — Low
persist() called every tick in _on_tick() even when _dirty is false. The dirty-flag short-circuit (scripts/main.gd:2438) makes the call cheap, but the contract should be enforced by callers rather than rely on the inner guard. Evidence: scripts/main.gd:1410-1418, scripts/main.gd:2438-2448.
status_label.text from settlement_status_text() is unbounded. Includes reward preview + next-unlock + bottleneck hints on one line. With many queued builds and an active reward, it overflows horizontally. Relies on clip_text = false + OVERRUN_NO_TRIMMING to render. Evidence: scripts/main.gd:2335-2371.
do_gather() reservation drift guard.scripts/main.gd:1604-1617 calls release_resource(String(task.resource)) after the gather mutation; if the early-return at int(tile.amount) <= 0 triggers, the reservation counter can drift negative on edge paths. Minor invariant issue.
P1 — Audit-decompose cron idempotency — stop creating duplicate child issues for the same umbrella; respect the <!-- /audit-decompose:vN --> marker.
P1 — recruit_worker() name pool collision beyond 10 workers — extend pool, switch to unique-seed, or assert unique-on-add.
P2 — worker_collision_offset hard-coded 6 offsets — derive offset count from collision count, or document the cap.
P2 — Avoid double apply_loaded_dock_anchor() on startup — single source-of-truth in load_or_boot.
P2 — Validate priority_order save schema — accept only [build, haul, gather] permutations.
P2 — Sidebar popup positioning relative to screen_get_usable_rect() — not hardcoded 16px offsets.
P2 — Strip stray test fix: trailer from README.md.
P2 — Reconcile labels.yaml with actual repo labels — add tech-debt, agent/foreman-coder, type/*, area/product, needs-gpt, or document the label-sync workflow.
P2 — Move Focus Mode / Zoom widget construction out of _ready() lambdas — define in scene like the rest of the settings UI.
P2 — Cache styleboxes in apply_theme() — don't .duplicate() on every call.
P2 — Add apply_anchor_layout() orchestration test — pure-math is tested, orchestration isn't.
P3 — Guard _on_tick()persist() contract — call only when known dirty, instead of relying on the inner short-circuit.
P3 — status_label.text overflow handling — wrap or truncate long status strings.
P3 — render_worker_overlay() per-frame cost — skip when no tick advance (or batch by tick).
P3 — do_gather() reservation drift guard — clamp release_resource so counter never goes negative on edge paths.
Weekly Tech Debt Audit — misospace/windowstead
Date: 2026-07-08
Overall Risk Level: Low-Medium
Summary
Windowstead is in a healthy mid-extraction state. Since the 2026-07-01 audit (#221), three child issues landed on main: WORKER_NAMES expansion to 10 + badge colors (#227 → PR #242), Linux export-validation in CI (#233 → PR #240), and the
use_local_storage = falsetest workaround removal with GDScriptandshort-circuit fix (#238 → PR #258). The eight remaining P1/P2 children of #221 (#228, #243, #244, #245, #246, #248, #249, #250) and the P2 children (#251, #252) are still in flight or status/ready.This week's audit found 15 new findings, mostly P2/P3. The most material are:
recruit_worker()name cycling will collide once a colony exceeds 10 workers (now reachable with 5+ huts).README.mdships with a straytest fix:trailer from a previous patch.labels.yamlis out of sync with the labels actually used on issues.No P0 regressions found this week.
Progress since 2026-07-01
Closed: #227 (PR #242), #233 (PR #240), #238 (PR #258).
Still open from #221: #228, #243, #244, #245, #246, #248, #249, #250, #251, #252.
Top findings
P1 — High
Decompose cron creates duplicate child issues for the same umbrella. Examples: Extract remaining main.gd subsystems into dedicated modules #232 vs Extract remaining main.gd subsystems into modules (choosetask AI, render, workertexture, process edge snap) #243, Deduplicate worker cap calculation — main.gd should delegate to worker_cap_logic.gd #229 vs Deduplicate worker cap logic: make main.gd:getworkercap() delegate to workercaplogic.gd #244, Deduplicate food-bias sort logic in choose_task #230 vs Deduplicate food-bias sort logic: unify gather/gatherfood sort paths in choosetask #245, Persist reserved_resources in save data #231 vs Persist reservedresources in save data to prevent double-booking across reloads #246, Increase event log capacity from 8 to 20+ #239 vs Increase event log capacity (8→20+) #248, Add full-cycle integration test for _on_tick #234 vs Add full-cycle integration test for ontick with real timer mock #249, Wire MilestoneManager into main.gd or remove as dead code #237 vs Wire MilestoneManager into main.gd bootstrapstate() or remove dead code #250, Move tile_accent / tile_style rendering helpers into a render module #235 vs Move tileaccent / tilestyle rendering helpers into a rendermodule.gd #251, Derive save schema grid sizes from LayoutMath instead of hardcoded array #236 vs Derive expected tile grid sizes from LayoutMath in save schema validation instead of hard-coded list #252. The cron should detect
<!-- /audit-decompose:vN -->marker and the existence of children before re-decomposing. Evidence:gh issue list --repo misospace/windowstead --search "Weekly tech debt" --state all.recruit_worker()name pool collision beyond 10 workers.scripts/main.gd:1115usesnext_index = current % len(WORKER_NAMES). WithWORKER_NAMES.size() == 10andworker_cap = BASE_WORKER_CAP + 2*huts = 2 + 2*huts, five huts allow cap = 12. The 11th recruit reuses "Jun". Evidence:scripts/main.gd:1113-1119,scripts/constants.gd:7(WORKER_NAMES),scripts/constants.gd:74(WORKER_CAP_BONUSES).P2 — Medium
worker_collision_offsethas hard-coded 6 offsets.scripts/main.gd:1948-1955declaresoffsetsas a 6-element array and usesslot % offsets.size(). With cap > 6 and clustered workers (around the stockpile, foundations), more than 6 workers can share a tile and the offset array overflows via modulo, losing some workers' visual placement.apply_loaded_dock_anchor()called twice on startup.scripts/main.gd:351-360(called fromload_or_boot) andscripts/main.gd:879-887(called fromload_saved_game). Burns DisplayServer calls and may double-rebuild the world on a normal boot path.validate_save_schema()does not validatepriority_ordercontents. Only checks the field is an array. A corrupted save withpriority_order: ["weird", "things"]will silently drop invalid kinds viaapply_priority_order()(scripts/main.gd:1245-1265). Stricter validation would prevent bad migrations. Evidence:scripts/game_state.gd:213-216.Sidebar popup uses hard-coded 16px offsets.
scripts/main.gd:585-590(position_popup_panel) andscripts/layout_math.gd:106-114(popup_position_for_anchor) use fixed 16px offsets. On small/secondary monitors the sidebar can render partially off-screen. Should be relative toDisplayServer.screen_get_usable_rect().Stray
test fix:trailer inREADME.md. Last line ofREADME.mdistest fix: _assert_empty accepts Variant— looks like a commit-message fragment that accidentally landed in main and was never reverted. Cosmetic, but visible to contributors.labels.yamldrift. Issues usetech-debt,agent/foreman-coder,area/product,type/chore,type/feature,type/bug,needs-gpt— none appear in.github/labels.yaml. Either add them to the file or document the label-sync workflow as the source of truth.Focus Mode / Zoom widgets constructed inline in
_ready().scripts/main.gd:230-251createsCheckButton,Label,HSlidervianew()and connects anonymous lambdas totoggled/value_changed. Inconsistent with scene-defined settings UI and untestable without a full Control scene. Closely related to Extract remaining main.gd subsystems into modules (choosetask AI, render, workertexture, process edge snap) #243.apply_theme()recreates and.duplicate()s styleboxes for every button on every entry.scripts/main.gd:117-155. Styleboxes could be cached as members and re-applied without.duplicate().No test for
apply_anchor_layout()bottom-vs-side orchestration. Onlytests/test_layout_math.gdcovers pure-math helpers. The orchestration (scripts/main.gd:473-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.P3 — Low
persist()called every tick in_on_tick()even when_dirtyis false. The dirty-flag short-circuit (scripts/main.gd:2438) makes the call cheap, but the contract should be enforced by callers rather than rely on the inner guard. Evidence:scripts/main.gd:1410-1418,scripts/main.gd:2438-2448.status_label.textfromsettlement_status_text()is unbounded. Includes reward preview + next-unlock + bottleneck hints on one line. With many queued builds and an active reward, it overflows horizontally. Relies onclip_text = false+OVERRUN_NO_TRIMMINGto render. Evidence:scripts/main.gd:2335-2371.render_worker_overlay()runs every frame via_process(delta). WorkerTextureRectpositions are recomputed each frame even when no tick has advanced. Combined with Weekly tech debt audit: windowstead - 2026-07-01 #221's P0 enhancement: prototype desktop-overlay colony sim #2 (per-worker pixel-push textures), this is the main render hot path.do_gather()reservation drift guard.scripts/main.gd:1604-1617callsrelease_resource(String(task.resource))after the gather mutation; if the early-return atint(tile.amount) <= 0triggers, the reservation counter can drift negative on edge paths. Minor invariant issue.Pre-existing items from #221 still valid
These are tracked by open decomposed children and are listed here for completeness only — no need to re-open them:
_on_tickRecommended issue breakdown
<!-- /audit-decompose:vN -->marker.recruit_worker()name pool collision beyond 10 workers — extend pool, switch to unique-seed, or assert unique-on-add.worker_collision_offsethard-coded 6 offsets — derive offset count from collision count, or document the cap.apply_loaded_dock_anchor()on startup — single source-of-truth inload_or_boot.priority_ordersave schema — accept only[build, haul, gather]permutations.screen_get_usable_rect()— not hardcoded 16px offsets.test fix:trailer fromREADME.md.labels.yamlwith actual repo labels — addtech-debt,agent/foreman-coder,type/*,area/product,needs-gpt, or document the label-sync workflow._ready()lambdas — define in scene like the rest of the settings UI.apply_theme()— don't.duplicate()on every call.apply_anchor_layout()orchestration test — pure-math is tested, orchestration isn't._on_tick()persist()contract — call only when known dirty, instead of relying on the inner short-circuit.status_label.textoverflow handling — wrap or truncate long status strings.render_worker_overlay()per-frame cost — skip when no tick advance (or batch by tick).do_gather()reservation drift guard — clamprelease_resourceso counter never goes negative on edge paths.Not worth doing yet
Carried forward from #221 — still valid: