Skip to content

Weekly tech debt audit: windowstead - 2026-07-08 #269

Description

@itsmiso-ai

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 = 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:

  • The audit-decompose cron created duplicate child issues for Weekly tech debt audit: windowstead - 2026-07-01 #221 across two runs — a process bug, not a code bug.
  • recruit_worker() name cycling will collide once a colony exceeds 10 workers (now reachable with 5+ huts).
  • Sidebar popup positioning uses hard-coded offsets and can render off-screen on small/secondary monitors.
  • README.md ships with a stray test fix: trailer from a previous patch.
  • labels.yaml is 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

  1. 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.

  2. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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().

  5. 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.

  6. 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.

  7. Focus Mode / Zoom widgets constructed inline in _ready(). scripts/main.gd:230-251 creates CheckButton, Label, HSlider via new() and connects anonymous lambdas to toggled/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.

  8. 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().

  9. 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

  1. 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.

  2. 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.

  3. render_worker_overlay() runs every frame via _process(delta). Worker TextureRect positions 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.

  4. 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.

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:

Recommended issue breakdown

  1. P1 — Audit-decompose cron idempotency — stop creating duplicate child issues for the same umbrella; respect the <!-- /audit-decompose:vN --> marker.
  2. P1 — recruit_worker() name pool collision beyond 10 workers — extend pool, switch to unique-seed, or assert unique-on-add.
  3. P2 — worker_collision_offset hard-coded 6 offsets — derive offset count from collision count, or document the cap.
  4. P2 — Avoid double apply_loaded_dock_anchor() on startup — single source-of-truth in load_or_boot.
  5. P2 — Validate priority_order save schema — accept only [build, haul, gather] permutations.
  6. P2 — Sidebar popup positioning relative to screen_get_usable_rect() — not hardcoded 16px offsets.
  7. P2 — Strip stray test fix: trailer from README.md.
  8. 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.
  9. P2 — Move Focus Mode / Zoom widget construction out of _ready() lambdas — define in scene like the rest of the settings UI.
  10. P2 — Cache styleboxes in apply_theme() — don't .duplicate() on every call.
  11. P2 — Add apply_anchor_layout() orchestration test — pure-math is tested, orchestration isn't.
  12. P3 — Guard _on_tick() persist() contract — call only when known dirty, instead of relying on the inner short-circuit.
  13. P3 — status_label.text overflow handling — wrap or truncate long status strings.
  14. P3 — render_worker_overlay() per-frame cost — skip when no tick advance (or batch by tick).
  15. P3 — do_gather() reservation drift guard — clamp release_resource so counter never goes negative on edge paths.

Not worth doing yet

Carried forward from #221 — still valid:

  • Full pixel-art asset pipeline (runtime pixel-push is part of the aesthetic)
  • Web export / network integration (no multiplayer use case; JavaScriptBridge surface is minimal)
  • Save encryption (single-player desktop companion)
  • Worker FSM (current task→step→complete pattern works)
  • MVC UI toolkit abstraction (Godot scene tree IS the view)

## Decomposed into
<!-- audit-decompose:v1 -->
- #273 — Not worth doing yet
<!-- /audit-decompose:v1 -->

Metadata

Metadata

Assignees

No one assigned

    Labels

    auditAudit, review, or investigation work.enhancementNew feature or improvement.priority/p1High priority.status/doneWork is complete.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions