AoE2 · LLM Arena

Run Review: logs/2026_07_11_1 (2026-07-11, 07:55–08:03)

Full findings from the first post-refactor VM run, with an actionable TODO list. Written so a future session can pick up any item without re-deriving the analysis.

Run metadata

Commanduv run --package gameplay-agent aoe2-agent --overlay --iterations 30 (plain run — no just experiment, so no results.tsv row)
VMWindows ARM64, 3024×1672 capture, remote detection → Mac 192.168.0.106:8420
ModelsRemote: v9 (Mac). Local fallback on VM: aoe2_yolo_v5.onnx (see F-5)
OutcomeDark Age only, peak pop 14, survival 457.7 s, total food gathered 200, score 386 (last vs AI 437–482), cost ≈ $0.25
Effective turns16 of 30 — iterations 19–30 burned on window-focus failure (F-1)
Evidencelogs/2026_07_11_1/logs.txt (line refs below), goals.log (UTF-16, byte-swapped — decode before reading), images/*.jpg (17 frames)

Narrative

The opening was competent: recovered from housed-at-5/5 by turn 3, grew 4→14 villagers, pop cap 5→30. Then the economy starved: food crashed 200→10 by minute 3 and never recovered, wood ended at 4. The final frame (images/20260711_080310_00018.jpg) shows 8 idle villagers, 7 food / 5 wood. In the first phase (min 1–4) the executor’s only builds were 5 houses (iterations 2, 3, 5, 6, 8); the first food building (mill) came at minute 7 (iteration 16), and no farm was ever built — both farm attempts (iterations 15, 17) were silent no-ops (F-2). At 08:03:34 the game window could no longer be focused and the remaining 12 iterations did nothing (F-1).


Findings (ranked by impact)

F-1: Window focus loss consumed 40% of the run silently

F-2: Farm builds are silent no-ops — effect-blind action success

F-3: House-only first phase; mill four minutes too late

F-4: idle_count OCR misreads as 1 — and the misread throttled dispatch

F-5: VM local detection fallback is doubly broken (v5 + decode failure)

F-6: OCR latency is the APM ceiling (10–40 s per tick, 26 s first tick)

F-7: Executor returned zero actions on 6 of 16 turns

F-8: Reactive tier is need-blind during a food crisis

F-9: Metrics bugs

F-10 (minor)


TODO — grouped, prioritized

Priorities: P0 = do before the next baseline run; P1 = next; P2 = nice.

A. Harness / run lifecycle

B. Perception — OCR speed (fixes F-6, the APM ceiling)

C. Perception — idle-count trust (fixes F-4)

D. Detection / model ops (fixes F-5)

E. Decision-making / economy (fixes F-2, F-3, F-7, F-8)

F. Metrics (fixes F-9)

Suggested sequencing

  1. Quick wins, no VM: T-301 (idle trust gate), T-503 (house gate), T-204 + T-201 (startup), T-402 (substitution warning), T-102 (end reason).
  2. VM one-timers: T-401 (copy v9), then T-101 (focus handling — needs VM to verify).
  3. The economy fix that changes game outcomes: T-501 + T-502 (prereq + effect verification), then T-504.
  4. Throughput: T-202/T-203 (template backend + shared reading) — triples decision rate; re-run the baseline after this lands and compare tick times.
  5. Re-run just experiment-baseline 3 (T-103) only after 1–3, so the P0.1 baseline isn’t polluted by known harness bugs.

Follow-up: Run 2 review (logs/2026_07_11_2, 13:19–13:30 UTC)

First run with the P0 fix batch (commits 5cbab20 + 89e9521) live on the VM. Peak pop 18 (was 14), survival 569 s (all 30 iterations — no focus loss this time), cost $0.49, game_end_reason=iterations_exhausted. Still Dark Age. User notes: lots of misclassification remains; farms finally got built; two mills were built (one redundant); after the mill there were turns with idle villagers but no farm building; villagers were sent to misclassified “farms” (bare ground).

Fixes validated in the wild

New findings

F-11: Placement verification has heavy FALSE NEGATIVES — and caused the duplicate mill

F-12: Misclassification is now the dominant error source (user notes + screenshots)

F-13: idle_count still pinned at 1 (71/71 readings)

The trust gate contained the damage, but the underlying misread is unfixed — T-302 (save badge crop + NCC score per tick on the VM, fixture the failure) remains the path to actually reading 7/8-idle correctly.

F-14 (minor)

New TODOs (append to the categories above)

E. Decision-making / economy

D. Detection / model ops


Follow-up: Run 3 review (logs/2026_07_11_3, 14:02–14:11 UTC)

First run with T-507/T-508 (commit d3e15d2) live. Peak pop 21 (14 → 18 → 21 across the three runs), all 30 iterations, iterations_exhausted, still Dark Age. User notes: farming visibly better — but Feudal not reached in 30 rounds.

Fixes validated in the wild

Why Feudal wasn’t reached (F-16) — the new #1 issue

Feudal costs 500 food (plus two Dark Age buildings, which the agent has) and one action: select TC (h) → Research Age Up (z — documented in prompts/hotkeys.md line 25). Neither precondition path exists in the agent:

  1. All food goes into villagers, forever. The reactive tier pressed “Queue villager” on 28 of 28 turns (~1400 food attempted at 50 each). The food trajectory shows the consequence: 200 → 10 → 4 → …, never above 81 after the opening. Banking 500 is arithmetically impossible while the queue runs unconditionally.
  2. The only existing brake never engaged: _POP_CAP_BY_AGE["Dark Age"]=22 exists exactly to bank food, but pop only hit 21 at iteration ~28.
  3. Nothing ever presses the button. The strategist keeps an “Advance to Feudal Age” goal, but no tier — reactive, executor toolset, or fallback — contains an age-up action. Grep confirms: zero hz sequences in any run.

Other findings

F-17 (corrected): Duplicate mill ATTEMPT — only one mill was actually built

Two mill placements were attempted 24 s apart (14:05:49, 14:06:13), but the user confirms (and the wood math agrees) only one mill existed: attempt #2 no-oped in-game because the wood was already spent. No wood was wasted this run — the game’s own affordability check did what our stale-snapshot cost gate couldn’t (both attempts saw the same pre-purchase wood_before=160). Two real issues remain:

  1. The LLM still tried to build a second mill — it has no persistent “buildings I own” memory, and mill #1’s settlement is inherently one snapshot late (T-512 stands).
  2. The ledger over-confirmed: one 160→8 wood drop settled BOTH pending entries (build_purchase_confirmed building=mill twice, identical readings). Per-entry settlement lets multiple pendings share one piece of evidence. Harmless today (the confirmed-set is idempotent), but it would corrupt any future count-based context line (“mill×2”) → see T-514.

F-18: Farm placement reliability ≈ 50%

4 farm placements build_purchase_missing with wood UNCHANGED or rising — those placements genuinely no-oped (vs 4 confirmed). Likely cause: the placement ring anchors on the TC and the near-TC ring is increasingly occupied by houses/farms as the base grows; clicks land on blocked tiles/fog and all 6 retry offsets miss.

F-19 (persistent, from earlier runs)

New TODOs

E. Decision-making / economy


Follow-up: Run 4 review (logs/2026_07_11_4, 16:15–16:25 UTC)

First run with the Feudal path (T-510/T-511) + honest metrics + clean-code pass live. Peak pop 18, all 30 iterations, no focus loss, cost $0.50, iterations_exhausted. Still Dark Age — but for a NEW reason (F-21). User notes: (1) scout explored early ✓ but the first villager action still came noticeably late; (2) 3 villagers died hunting boars; (3) food was over-prioritized → wood hit 0 → no farms → berries dried → food income collapsed.

Fixes validated in the wild

New findings

F-20: Boar hunts killed 3 villagers (user note 2)

9 idle dispatches targeted boar (food)boar sits in the food gather classes, and a lone villager right-clicked onto a boar ATTACKS it; the boar wins. Population drops 8→7 and 12→10 confirm 3 deaths (~17% of peak workforce). Real AoE2 boar-hunting needs 3+ villagers and TC luring — far beyond the reactive tier. Compounding risk: at F1 0.67 a “deer” label isn’t trustworthy either, so confidence-gating on species is not safe (F-12).

F-21: The food-crisis override starved wood — a REGRESSION from T-510

The famine rule (food < 60 → every idle slot to food) held for most of the game (food was under 60 from ~turn 3 to ~turn 20), so idle routing sent NOTHING to wood. Wood: 200 → 50 → 0 for the final third. Consequences cascade: farms unbuildable (60 wood) → mill/lumber_camp also rejected → berries dried with no farm replacement → food income collapsed → the crisis persisted → the override kept starving wood. A positive feedback loop the original rotation’s wood slots existed to prevent. Banking (working correctly!) then had no income to bank: food peaked at 154, never 500, and the age-up rule (0 hz presses) correctly never fired.

F-22: Opening latency — scout fast, economy slow (user note 1)

Scout at ~11 s ✓. But: first villager queue at 52 s, and the first idle dispatch (16:16:01) was off-map (x=34) and wasted — the first effective villager action landed in iteration 2, ~85 s in. Two mechanical causes:

  1. The first OCR pass still took ~19 s even with the engine pre-warmed — per-field rapidocr cost (T-202’s territory).
  2. A 10-second stall between the h and q presses (16:15:47 → 16:15:57), coinciding exactly with the strategist’s concurrent OCR pass — onnxruntime holds the GIL in its worker thread, starving the event loop between actions. T-203 (single shared HUD read) removes that second pass and the contention window with it.

New TODOs

E. Decision-making / economy

V-8 matrix additions


Follow-up: Run 5 review (logs/2026_07_11_5, ~19:2x UTC)

The first ledger-recorded run (T-103 finally executed, after clearing the VM’s old-layout husk directories that were shadowing gameplay_agent and detection as namespace packages): exp_0014, composite 0.3153 (survival 0.42, population 0.40, economy 0.063, age 0.0, action_success 0.80), 27 turns / 505 s, iterations_exhausted. First run with T-515/T-516 live. UPDATE: the VM’s ledger copy (snapshotted at logs/2026_07_11_5/results.tsv) carried a surprise: 13 historical rows (exp_0001–exp_0013, Mar–Apr) preserved from the old mislocated ledger path, including exp_0013 (2026-04-25) which reached Feudal (age 0.33) under a time-budget run. The ledger itself is deliberately NOT committed (machine-local; the VM is its source of truth — gate runs there). Caveat for comparisons: those runs used --time-budget (survival component = 1.0 on timeout), while exp_0014 used --max-iterations 30 (survival 0.42 on iterations_exhausted) — composite scores are only comparable within the same budget mode. exp_0014 is the first row of the post-improvement-plan era and the reference for the current code line.

Fixes validated in the wild

Why still no Feudal — the gap is now throughput, not correctness

Food ended at ~170, climbing ~15/turn late — 500 was ~20 turns away when the 30-iteration budget ran out. No rule misfired (0 age-up presses is CORRECT at food < 500). Two compounding limiters:

F-23: Wood plateaus JUST under a farm’s cost — farms locked out by a hair

Six farm attempts rejected at wood 48, 55×4, 59 — each failing by 1–12 wood; only ONE farm got built all game. Houses (4×25) + the mill (100) drained the stock, and the wood-dispatch share (crisis 2:1 + normal pattern) sustains ~50–55 wood but not 60+. The T-516 floor prevents collapse but its target equals the farm cost exactly, so the economy hovers at the boundary where every farm attempt loses the race against the next house purchase.

F-24: Ledger false-MISSING on the mill — the ±20 slack is too tight

build_purchase_missing building=mill wood_before=130 wood_now=60: 70 net drop vs the required ≥80 (100 − 20 slack) — the mill WAS real (the strategist later saw it and farm prereqs passed via detection evidence), but ~30 wood of gathering income during the settle window masked the spend. Harmless this run (visual evidence covered it) but the slack needs to scale with lumberjack count / settle-window length, or the false-missing rate grows with the economy.

F-25 (persistent)

idle_count pinned at 1 (71/71 — T-302 unmoved through five runs); 2× “tree (food)” intent labels (T-602).

New TODOs

E. Decision-making / economy

Process note for the next Feudal attempt

30 iterations ≈ 8.5 min may be structurally short for a 500-food bank even with a healthy farm economy. Until T-202/T-203 raise the decisions-per-minute, run Feudal attempts with --max-iterations 45 (or --time-budget 900) so strategy, not runway, is what’s being measured.


Follow-up: Run 6 review (logs/2026_07_11_6, exp_0015)

First 50-iteration run (per the run-5 process note). Composite 0.4579 — best of the iterations-mode era (+45% over exp_0014) — 47 turns / 893 s, peak pop 23, total_food_gathered=1053, zero villager deaths again. The economy now WORKS: food banked past 500 by ~turn 33 and kept climbing to 767. Still Dark Age, for one final, precise reason (F-26). User notes: (1) 3 towers were built — towers must not be a Dark Age priority; (2) Feudal was blocked because only ONE qualifying Dark Age building existed (the mill) — a lumber camp was needed.

Fixes validated

New findings

F-26: Feudal blocked by the TWO-Dark-Age-buildings requirement (user note 2)

AoE2 requires two qualifying Dark Age buildings to advance — and houses don’t qualify. The agent’s only qualifying building all game was the mill, so all 14 age-up presses were no-ops against a greyed button while 500–767 food sat banked for ~15 turns. The painful irony: the simulator already encodes this (world_sim.FEUDAL_PREREQ_BUILDINGS = {mill, lumber_camp}) — a V-1 closed-loop run would have caught it before the VM did, since the sim would never have advanced either. The agent has no concept of the requirement in either the reactive tier or the executor gates.

F-27: Three phantom towers — placed by leaked input state, not by decisions (user note 1)

The log contains zero tower/outpost intents — no LLM action, no reactive action, no build-menu press for them. Yet three tower-like buildings exist. Stone only moved 170→165 all game, so they’re cheap outposts (watch towers would have cost ~75 stone). Hypothesis: some of the 14 age-up sequences (h then z) landed with a villager + open econ build menu as the active UI context instead of the TC — and in DE’s economic build menu, Z is the Outpost slot; a later placement click then dropped the ghost. (Also worth verifying: hotkeys.md documents “Z: Research Age Up” at the TC — confirm against the live DE default profile.) Note: no anti-tower prompt rule currently exists (hotkeys.md line 48 even documents the tower key for the LLM); user note 1’s remembered rule predates this prompt set.

F-28 (minor)

New TODOs

E. Decision-making / economy


Follow-up: Run 7 review (aborted by user at ~turn 10 — 3 towers again)

User stopped the run after seeing three towers. The log caught the full causal chain, and the user identified the game mechanic that explains the towers in BOTH runs 6 and 7.

F-29: A single-frame phantom mill poisoned the build gates BOTH ways

Timeline from the log: 22:44:29 farm correctly rejected (“requires a completed mill and none has been seen yet”) → 22:44:43, fourteen seconds later, a farm build PASSES the gate with no mill in the game — a one-frame misdetected “mill” (F-12 class) had entered buildings_confirmed — → 22:45:49 + 22:46:08 the LLM’s REAL mill builds are rejected with “mill already built”. One phantom frame simultaneously unlocked impossible farm builds and locked out the fix. Fixed (T-522): detection evidence is now thresholded — _BUILDING_CONFIRM_SIGHTINGS = 3 distinct frames before a class counts; ledger (wood-delta) and verified-placement confirmations remain instant.

F-30: The tower mechanism, solved (user-identified) — corrects F-27

In the DE econ build menu WITHOUT a mill, the A slot is the OUTPOST. Pressing A doesn’t no-op — it selects a tower, and the placement click builds it. Every phantom-unlocked farm attempt (., q, a, click) built an outpost: 3 farm attempts pre-mill = 3 towers, in both runs. (Run 6’s F-27 hypothesis blamed the age-up z presses; the real culprit was the same farm sequences — run 7 reproduced the towers with zero z presses.) The farm prerequisite gate is therefore a SAFETY gate, and the phantom evidence that bypassed it was the direct cause. Fixed (T-522 above + T-523): build_menu_steps now ends with an escape press (a leaked menu can never re-map later keystrokes), and core.md/hotkeys.md document the A-without-Mill=Outpost hazard for the LLM.

F-31 (minor): interrupted run logged game_end_reason= (empty)

The Ctrl-C abort produced a metrics line with an empty end reason — the KeyboardInterrupt path (which sets “interrupted”) apparently didn’t fire under the Windows asyncio runner. Low priority; recorded runs aren’t normally aborted.


Follow-up: Run 8 review (logs/2026_07_12_1, 17:37–17:44, “Feudal attempt 4”)

First run with the 2026-07-12 batch (T-202/203/512/514/518) live. 48 turns / ~420 s, composite 0.2765 (exp_0001 of the VM’s restarted ledger), iterations_exhausted, action_success 0.94 — still Dark Age. User notes: (1) the game menu opened multiple times; (2) villagers sent to random places, coordinates seemed off; (3) the mill was built very far from the TC; (4) no lumber camp was built.

Fixes validated in the wild

New findings

F-32: the trailing escape OPENS the game menu (user note 1)

Frames 3/12/27/43 show the Main Menu dialog open — each captured ~1 s after an escape press (17:38:05, 17:39:50 = the mill composite’s tail, 17:41:17, 17:43:0x). Mechanism: after a SUCCESSFUL placement click the build menu has already closed itself, so T-523’s trailing escape lands on “nothing to cancel” — and in DE that opens the game menu, which also PAUSES a single-player game. T-521’s age-up escape prefix has the same hazard. The safety escape is correct for a leaked menu and wrong on the common success path; ≥ 4 paused episodes this run, each feeding dimmed garbage frames to detection while open. → T-526.

F-33: literal click coordinates go stale when . jumps the camera (notes 2 + 3)

The LLM’s send_villager/build composites press . (select idle villager — the camera re-centers on that villager), THEN click at literal x/y computed from the PRE-jump frame (right_click “to sheep” at (1987,586) with target_id=None; the mill placement (1459,1325) “near berry bush”). After the jump the literal lands on arbitrary terrain: villagers walk to random places (note 2) and the mill rose wherever the idle villager happened to stand — far from the TC and the berry bushes (note 3). The post-. rescan refreshes the entity cache but cannot fix coordinates already baked into the action; the reactive tier is mostly immune because it re-resolves target_class after the rescan. The stale-coords failure class again, now in the composite seam. → T-525.

F-34: lumber camp cost-locked ALL game — the wood ceiling sits below 100 (note 4)

19 lumber-camp builds were emitted (LLM + Feudal prep, every turn from pop 12) and every one was rejected “costs 100 wood, you have 37–79”. Wood never reached 100: T-518 banks toward farm cost + margin (80), and houses (4×25) consume the rest, so the second Feudal prerequisite is arithmetically unreachable at ANY iteration count — F-16’s shape, one level up. The wood target must be goal-driven, not hardcoded to the farm. → T-527.

F-35 (minor)

New TODOs

E. Decision-making / economy

G. Input / composite correctness (new category)

B. Perception


Follow-up: Run 9 review (logs/2026_07_12_2, exp_0002, “Feudal attempt 5”)

First run with the T-525/526/527 batch (commit a712247). Composite 0.3259 (was 0.2765), 47 turns / 546 s, peak pop 17, action_success 0.93, iterations_exhaustedstill Dark Age, and this time the entire failure reduces to ONE root cause (F-36).

Fixes validated in the wild

The run-killer

F-36: a persistent phantom mill defeated the sighting threshold — 14 outposts, zero farms

No mill was ever purchased this game (the wood ledger has no 100-drop for one, and the LLM’s single real mill attempt was REJECTED “mill already built”), yet “mill” entered buildings_confirmed by ~minute 2.5 — a misdetection that appeared in ≥ 3 frames, defeating T-522’s threshold, whose “phantoms flicker” assumption persistent misdetections simply don’t honor (F-12’s F1 0.67). From there the F-29/F-30 chain re-ran at scale: farm builds passed the prereq gate, each a press in the MILL-LESS econ menu selected the OUTPOST, and the auto-placement click faithfully built it. 32 farm placements settled build_purchase_missing; ~14 became outposts (stone bled 200→130 in 5-stone steps — 25w+5s each; the final frame shows the base ringed by towers and the “—Outpost Built—” notification). With zero farms, food pinned ≤ 67 (ended at 2), villager production stalled at pop 17, banking never started, and the age-up correctly never fired. Conclusion: no sighting count fixes this — detection evidence must not gate builds at all. → T-529.

F-37: no circuit breaker on repeated failures

The farm build was re-emitted ~every turn for 30+ turns; each attempt burned 25 wood + 5 stone + a turn slot, and 32 consecutive build_purchase_missing results never escalated beyond a log line. → T-530.

New TODOs

E. Decision-making / economy


Follow-up: Runs 10 + 11 review (logs/2026_07_12_3 exp_0003 50-iter, logs/2026_07_12_4 exp_0004 100-iter)

First runs with T-529/T-530 (commit 7c1743b). Run 10: composite 0.3210, peak pop 21, 47 turns. Run 11: composite 0.5603 — best ever (+22% over run 6’s record), peak pop 40, 96 turns / 950 s, 1148 food gathered — and STILL Dark Age. User notes: the agent made far too many villagers (40+); it doesn’t need 42 in the Dark Age — max ~30 — after which the priority should be no idle villagers + banking the Feudal resources (once the two buildings stand).

Fixes validated in the wild

Why still no Feudal

F-38: the villager brake fires on DELIVERED population — the TC queue is invisible

Every one of run 11’s 36 reactive queue presses happened at OCR population ≤ 15 (verified press-by-press): the pop-16 banking brake held PERFECTLY at press time. But a villager takes ~25 s to produce and the loop presses q every ~10 s turn, so by the last press the TC queue held ~20 undelivered villagers — population kept climbing to 40 for minutes after the brake “engaged” (36 presses + 4 starting villagers = the observed 40 exactly). Those ~20 surplus villagers cost ~1000 food that WAS the Feudal bank. The brake must count what the agent ORDERED, not what the HUD has delivered — the same self-generated-evidence principle as the build ledger (T-529). LLM queue_villager composites (4 in run 11) bypass the brake entirely — same gate, executor-side. → T-531.

F-39 (minor): executor self-reports “Feudal Age” (5×) — already fenced

claude_response age='Feudal Age' while all 20 OCR age reads said Dark Age. Harmless today: update_from_observations deliberately ignores executor age (the exp_0011 fence) — logged here as evidence that fence is load-bearing.

New TODOs

E. Decision-making / economy


Follow-up: Run 12 review (logs/2026_07_13_1, exp_0005, “Feudal attempt 7”)

First run with T-531 (commit 58effc0). 100 iterations / 95 turns / 796 s, composite 0.4646 (down from run 11’s 0.5603), peak pop 29, 874 food gathered, action_success 0.944, iterations_exhausted — still Dark Age. But the composite comparison is meaningless this time: the executor LLM was down for 85 of 95 turns (F-40). The game was effectively played by the reactive tier alone, which makes this an accidental — and very informative — ablation study of what the reactive tier can and cannot do without the LLM.

Fixes validated in the wild

The run-killer

F-40: executor down 85/95 turns — API 400 “compiled grammar too large”, caused by T-531’s schema addition

F-41: the reactive tier cannot build the food engine — mill is an LLM single point of failure

F-40’s ablation exposed a structural gap independent of the API bug: Feudal prep (T-520) emits the lumber camp but not the mill, even though BOTH are Feudal prerequisites (world_sim.FEUDAL_PREREQ_BUILDINGS) and the mill additionally unlocks farms — the entire late-Dark-Age food engine. With the executor down, the agent had a deterministic path to a lumber camp and no path whatsoever to a mill. (It also mirrors run 8’s F-34 shape: whichever prerequisite the reactive tier doesn’t know about becomes unreachable.)

Other findings

F-42: 43 off-map right-clicks — food dispatch resolving to edge-of-screen trees

right_click_off_map fired 43 times (~7 in prior runs), mostly late-game: with zero food entities on screen, the food-kind dispatch resolved to a tree at (24, 800) — off-map, rejected, villager stays idle — and repeated the same resolution every turn. Two compounding bugs: the kind→class fallback silently substitutes wood targets for food requests (T-602’s mislabel is the visible symptom), and target resolution doesn’t filter candidates by click-safe screen bounds before selecting one (F-22’s off-map first dispatch was the same class). In a famine with an executor outage, this wasted most idle dispatches for the last third of the game.

F-43: strategist end-game fantasy goals

The final strategist turns (goals.log tail) invented an alternate reality: “currently in Feudal Age” (every OCR age read all game said Dark Age), Castle Age advancement goals (“need 800 food + 200 gold”), and COMPLETED markers for buildings that were never attempted (“Build Barracks (175 wood) to produce spearmen”, “Build Blacksmith and Market with surplus wood (1177 wood available)”) — no barracks/blacksmith/market/spearman appears anywhere in the action log or detections. Likely driver: dozens of turns of zero progress (reward +0.000, same readings) with an ever-growing goal history — the model eventually confabulated progress. T-505 (goal hygiene) gains a harder requirement: goals must be validated against ground truth (age from OCR, buildings from the confirmed ledger), not just expired.

F-44 (minor)

New TODOs

H. LLM transport / schema (new category)

E. Decision-making / economy

A. Harness / run lifecycle


Consolidated open TODOs (authoritative, updated after run 13 / logs/2026_07_17)

The per-run lists above are historical record; this section is the single list to work from. Duplicates merged, conflicts resolved, priorities re-graded on the run evidence. Done and retired: T-102, T-201, T-202, T-203, T-204, T-301, T-401, T-402, T-501, T-502 (failure semantics superseded by T-508), T-503, T-507, T-508, T-512, T-514, T-518 (band target generalized by T-527); T-504 merged into T-510; T-519 superseded by T-537.

P0 — after run 13 (first Feudal run; see the run-13 review at the end)

  1. T-537 — Income-aware build settlement DONE (F-45, supersedes T-519): settlement now deducts an EMA income estimate (clean windows only, scaled by elapsed snapshots) from the observed wood delta before judging, and record_confirmed_buildings clears the T-530 streak on ANY proof (wood-delta or visual) — details in the run-13 New TODOs entry.
  2. T-538 — Feudal-age reactive program DONE (F-46, economy-only): age-keyed executor villager gate + observe_age sync, reactive mining-camp prep, house un-stall rule, mining-camp wood bank, Feudal gold bias — details in the run-13 New TODOs entry. The Castle age-up press itself is split out as T-544 (P1): military/V-menu wiring + two-Feudal-building prep + the gated h,z press.

P0 — after run 12

  1. T-532 — Single-shot schema under the grammar limit DONE (F-40): the culprit was 22 bounded-integer Field(ge=,le=) constraints, not QueueVillagerAction per se — each numeric range is a heavyweight constrained-decoding automaton and the union was already saturated. Coordinate/duration ranges are now enforced by field_validators (same validation, zero schema minimum/maximum → small grammar); full vocabulary kept; regression guard added. VM confirms the live compiled grammar (not measurable offline) — de-risked by T-533’s fallback.
  2. T-533 — LLM-outage resilience + alarm DONE (F-40): a 400 on single-shot retries the turn via the tool loop (smaller schema surface, worked all run 12); an executor_outage alarm fires after 3 consecutive failures and llm_error_rate now lands in game_metrics_final AND results.tsv, so a dead-executor run can’t pass as valid (exp_0005 would have shown 0.95).
  3. T-534 — Reactive mill rule DONE (F-41): _feudal_prep_actions emits the mill FIRST when neither prereq stands (it’s the farm unlock), then the camp; _wood_bank_target banks mill→camp→farm. A closed-loop world_sim test proves the reactive tier reaches mill + camp with NO LLM.
  4. T-302 — idle-count fix (seven runs now; run 13 adds a SECOND failure mode — pinned at 41 in Feudal with idle_present=False and pop 32): with the executor able to go down (F-40) and 30 villagers to keep busy, the reactive tier’s dispatch sizing needs the true badge count more than ever.

P0 — after run 11 (DONE)

  1. T-531 — Villager-order ledger + Dark Age target 30 DONE (F-38, user-directed): villager queueing is now a first-class queue_villager action funneled through one executor handler — an order ledger (starting 4
    • each successful order) gates ALL paths (reactive, LLM composite, fallback) on villagers ORDERED with the Dark Age target at 30 and a 50-food gate (a no-op press is never counted). The reactive tier’s _VILLAGER_TARGET_BY_AGE replaces both the pop-16 banking brake and the pop-22 cap; drift tests pin the target to the executor gate and the starting count to memory.INITIAL_POPULATION. Raw h+q queueing no longer exists anywhere. Validated in run 12 (peak pop 29 vs run 11’s 40; all paths gated) — but the schema addition caused F-40, see T-532.

P0 — after run 9 (DONE)

  1. T-529 — Purchase-grade evidence for build gates DONE (F-36): detection sightings no longer graduate into buildings_confirmed — only the wood-delta ledger and verified placements write it, so a phantom mill can neither unlock outposts nor block the real mill. Persistent sightings surface in the context line as “(unverified sightings, NOT owned: mill)”.
  2. T-530 — Repeated-missing circuit breaker DONE (F-37): 3 consecutive build_purchase_missing settlements for one class suppress that build for 5 HUD snapshots with a teaching rejection (“placements vanished without the wood being spent”); a confirmed purchase resets the streak.

P0 — the batch after run 8 (DONE, pending VM hotkey checks)

  1. T-526 — Never open the game menu DONE (F-32, user-directed prevention over detection): every blind escape is gone — build composites end with h (select TC clears a leaked menu/ghost by switching selection), the age-up sequence is just h, z, and PressAction validation REJECTS escape/F10/F3 with a teaching message, so the LLM can’t press them either (hotkeys.md documents the rule). VM VERIFY before the next run: open the econ build menu with a villager, press H — the TC must get selected and the menu close (alongside the pending “verify Z” check).
  2. T-525 — Stale-coordinate clicks after camera jumps DONE (F-33): build placements now resolve AT CLICK TIME (auto_placement — the . select rescans first, and default_build_placement runs against the fresh cache); send composites require target_class (schema + runtime refusal with a teaching detail); execute_actions refuses any raw-x/y click that follows a camera-moving press in the same batch; the housed fallback now reuses build_steps instead of its own stale-coordinate copy.
  3. T-527 — Goal-driven wood bank target DONE (F-34): _wood_bank_target derives the target from the binding goal — lumber camp missing (Feudal prep) → 120, else mill standing → 80 (farm band), else no bias; the near-miss floor is gone (run 8 proved the plain rotation never reaches the target unaided). Drift test pins _LUMBER_CAMP_WOOD_COST to the executor table.

P0 — the 2026-07-11 batch (DONE except baseline runs)

  1. T-510 — Dark Age economy schedule DONE: crisis override (food < 60 → every idle slot to food) + banking phase (Dark Age, pop >= 16 → villager queue stops so income banks toward the 500-food Feudal cost).
  2. T-511 — Age-up action DONE: reactive rule — Dark Age + food >= 500press h, press z, emitted BEFORE the queue so the bank buys the age. Harmless no-op if the button is unavailable; doesn’t spam (research spends the food).
  3. T-506 — Zero-action executor turns DONE: single-shot responses with zero actions get ONE nudged retry (“EXECUTE your stated plan…”); still-empty falls to the game loop’s hardcoded fallback as before.
  4. T-601 — Honest metrics DONE: action_success_rate = successes / EXECUTED actions (new dedicated denominator; ≤ 1.0 by construction); total_food_gathered = sum of positive deltas between consecutive OCR readings (undercounts, never overcounts; 300-cap drops OCR glitches; LLM-echoed observations excluded). New snapshot key executed_actions.
  5. T-101 — Focus-loss handling DONE (verify on VM): focus failures no longer consume iterations (retried under the same number), attempt ≥ 2 uses a minimize/restore cycle (beats the Windows foreground lock), and 15 consecutive failures (~30 s) abort with game_end_reason="lost_focus".
  6. T-103 — Record baselines DONE in run 5: exp_0014 composite 0.3153 is the first ledger row (ledger stays machine-local on the VM by decision; snapshot at logs/2026_07_11_5/results.tsv). Baseline runs (experiment-baseline 3) still pending.
  7. T-515 — Safe huntables DONE: boar AND deer removed from GATHER_CLASSES_BY_KIND["food"] (job inference keeps them); a visible boar now triggers the farm-build path, never a dispatch.
  8. T-516 — Wood floor during food crisis DONE: famine + wood < 60 → the override routes 2:1 food:wood instead of all-food; drift test pins the duplicated farm cost to the executor table (V-4 seed).

P1

  1. T-202 + T-203 — OCR throughput pair DONE (2026-07-12 batch): templates/3024x1672/ harvested from run-3 frame 27 (all 10 digits + slash in one frame; validated over all 187 logged frames — field agreement ≥ 96% with template strictly MORE accurate than rapidocr on every eyeballed disagreement; rapidocr drops trailing digits and misreads lone digits). Age stays rapidocr via new read_age, sampled every 5 ticks (_AGE_OCR_INTERVAL). One HUD read per iteration: generate_goals accepts readings=, threaded loop → strategist (the strategist’s own OCR pass and its GIL-contention window are gone; strategist_eval keeps the self-OCR path via readings=None). VM: set AOE2_OCR_BACKEND=template (runbook updated); the config default stays rapidocr — the template backend hard-fails without per-resolution assets. Root-cause bonus: load_templates loaded MOST templates inverted. Otsu’s minority-foreground auto-invert flips a tight crop whose glyph fills most of its own image (8/11 digits at 1672 read as ‘3’/‘7’ garbage; 6/11 at 1964 — the lone-digit template fallback was silently degraded there all along). Fixed with fixed-polarity loading, plus a _FIELD_MIN_NCC=0.4 floor so merged touching-digit blobs read as “unreadable” (keeps last-known) instead of a wrong number.
  2. T-302 — Idle-count instrumentation (VM). Save badge crop + NCC score per tick; fixture the pinned-at-1 failure (3 runs × ~70 readings, 100% misread). The trust gate contains it; this fixes it. PROGRESS (2026-07-12): the failure is now pinned in the test suite — real_1672_dark_midgame (badge shows 2, read_idle_count returns 1) is the first live-resolution vision fixture, xfail-marked in test_read_idle_count_real_fixtures; the xfail clears itself when T-302’s geometry fix lands.
  3. T-512 + T-514 — Known-buildings context, bundled DONE (2026-07-12 batch): settlement deducts confirmed spend per shared wood baseline (one drop confirms at most one pending of a cost — run 3’s double-confirmation is now a red test); known_buildings_line (“Known buildings: farm=3 mill=1 (pending: farm=1)”, confirmed-evidence-only so single-frame phantoms can’t appear as owned) feeds BOTH the executor context and the strategist prompt from one formatter, backed by the new pending_placement_counts() accessor.
  4. T-513 — Placement anchor spreading. ~50% of run-3 farm placements no-oped (crowded TC ring). Anchor farms on the confirmed mill, skip fog candidates, widen radii with building count.
  5. T-505 — Strategist goal hygiene (hardened by run 12’s F-43): expire/ down-rank satisfied goals, AND validate goals against ground truth (OCR age, confirmed-buildings ledger) — run 12’s strategist ended the game planning Castle Age from the Dark Age with hallucinated COMPLETED buildings.
  6. T-517 — Opening effectiveness (run 4, F-22): verify first effective villager dispatch < 30 s once T-202/T-203 land; wasted off-map first dispatch. Mostly subsumed by the OCR throughput pair.
  7. T-518 — Farm-affordability wood bias DONE (2026-07-12 batch): the famine wood floor now banks _FARM_WOOD_COST + _FARM_WOOD_MARGIN (80), and outside a famine a mill + wood in the 40–79 near-miss band prepends a wood slot to the idle rotation (_farm_wood_near_miss). Run 8: mechanism verified, but the fixed farm target starved the 100-wood lumber camp (F-34) — superseded by T-527’s goal-driven target.
  8. T-519 — Settlement slack scaling SUPERSEDED by T-537 (run 13 proved the failure at scale: 21 false-missing settlements, five building classes suppressed, Castle gold economy blocked). 14a. T-539 (P1) — Per-field OCR dropout handling (run 13, F-47): gold missing on 26/39 readings for 11 min → strategist prompt showed 0 → Dark Age gold panic. Surface last-known-good with staleness; fixture the gold crops. 14b. T-540 (P1) — Executor target grounding (run 13, F-49, pairs with T-535): list visible gather classes in the executor context; missing food class resolves to nearest food source or farm-build fallback, not a no-op (21 target_class_not_found this run). 14c. T-541 (P1) — Single-shot grammar headroom (run 13, F-50): 37/37 single-shot calls 400’d post-T-532; verify the VM checkout, then shrink further or flag-gate the single-shot attempt and run tool-loop-first.
  9. T-520 — Lumber camp in the Feudal plan DONE: reactive Feudal prep emits the lumber-camp build from pop 12 until evidence shows one; executor unique-building gate (confirmed OR pending) stops re-emits and closes the duplicate-mill window; GameState.buildings_seen synced per turn; drift test pins the prereq set to world_sim’s.
  10. T-521 — Defensive age-up DONE: escape-prefixed sequence, press gated on mill+lumber camp visibly standing (one press instead of 14 no-ops), no-towers prompt rule added. STILL TODO on VM: verify Z is the DE age-up hotkey (hover the button).
  11. T-509 — Detection sanity filters + hard negatives. Age-impossible class filter (stable/knight_line in Dark Age), fog-region rejection (phantom 99% TC), harvest run-2/3 frames as hard negatives for the v10 retrain. Coordinates with IMPROVEMENT-PLAN P0.2 (eval set → 200 frames).
  12. T-535 — Click-safe target resolution (run 12, F-42): skip off-screen candidates during idle-dispatch resolution (43 wasted off-map clicks); food request resolving to a non-food class should prefer the farm-build fallback over silent wood substitution.

P2

  1. T-403 — v5 ONNX decode fence (demoted from P1: with v9 on the VM the v5 path is dormant; still a silent-blindness trap on fresh checkouts).
  2. T-104 — goals.log as UTF-8.
  3. T-602 — Intent label uses resolved class, not requested kind.
  4. T-524 — Resolution-independent resource-digit bank (deferred from the T-202 batch): extend the hud_digits multi-sample-bank approach (_classify_bank/_normalize_glyph) to resource digits so per-resolution template harvesting is never needed again. Needs a slash-glyph naming convention (bank keys are single chars via p.stem[0]).
  5. T-528 — Template-OCR blip harvest (run 8, F-35): wood=0 / gold=108 single-frame misreads; fixture the crops, then plausibility gate or more template samples.
  6. T-536 — Memory file encoding DONE (run 13 batch): tolerant _read_memory_file (utf-8, errors=“replace”) at all four read sites in memory_chain.py + the root cause fixed (_save_memory wrote with the platform-default encoding — cp1252 on the VM — which is where 0x97 came from). VM still owes a one-time cleanup of the corrupt file.
  7. T-542 — action_success_rate > 1.0 again (run 13, F-51: 138/78 = 1.77 despite T-601): composite-step successes aren’t matched by executed counts; add the V-5 invariant test.
  8. T-543 — game_end_reason on manual stop DONE (run 13 batch): the finally block defaults an unset reason to “interrupted” before game_metrics_final — covers CancelledError-style exits that bypass both except clauses.

Virtual testing environment: catching these issue classes before the VM

Reviewing the TODO list against the test infrastructure that already exists. The striking observation: most of the pieces exist, but they don’t overlap the failures. We have an economy simulator (packages/evaluation/src/world_sim.py — villager queue with cooldown, building costs, Feudal timer + prereqs), a synthetic game loop (apps/agent/src/synth_game_loop.py — drives the executor LLM with no screenshots/pyautogui), a scenario runner (apps/agent/src/scenario_runner.py — YAML fixtures through the REAL ClaudeProvider, incl. age_up_gate_fires.yaml), 7 real vision fixtures, and a log→scenario converter (log_to_scenario.py). Yet F-2, F-11, F-16, F-17 all shipped — because each failure lived in a seam none of these exercise. The proposals below close those seams, ordered by expected catch-rate.

V-1 (P0): Closed-loop reactive-tier simulation — “does 30 turns reach Feudal?”

Would have caught: F-16 (no Feudal), F-3 (mill too late), F-8 (need-blind routing); guards T-510/T-511 forever. The single biggest gap: synth_game_loop drives the LLM executor, and unit tests validate each reactive rule in isolation — but nothing runs the reactive tier + build gates + memory as one policy over many turns. Every rule passed its test while the composition couldn’t bank 500 food, and only a real VM run revealed it. Build: a reactive_sim harness that loops reactive.decide(entities, state, alarm=False) → applies actions to a WorldState (world_sim.apply_actions + tick) → renders state back into GameState/entities → repeats. Fully deterministic, no LLM, milliseconds per game. Assert milestones as tests: mill exists by turn N, farms ≥ K by turn M, Feudal reached by turn 30, zero villagers idle > 3 consecutive turns. Any future policy change that breaks the opening becomes a red test, not a wasted VM run.

V-2 (P0): Noisy-sensor layer over the simulator

Would have caught: F-11/F-17 (verification false negatives → duplicate-mill attempt), F-12’s gameplay impact (villagers sent to phantom farms), F-4/F-13 (pinned idle_count); guards T-507/T-508/T-301. The run-2/3 bugs lived in the perception→state seam, which today’s tests bypass by feeding perfect entity dicts. Add a configurable corruption layer between WorldState and what the policy sees:

V-3 (P0): Frame corpus harvested from every VM run

Directly implements T-302 and T-509’s harvest half; guards T-202. The 7 vision fixtures caught nothing about the live badge misread because they don’t include the live failure. Make harvesting automatic: a debug flag (or post-run script) that extracts from each run’s images/ + logs — resource bar crops, idle-badge crops with the NCC score the agent computed, and frames where detection disagreed with the wood ledger (placement “unconfirmed” but purchase confirmed = a frame YOLO got wrong). Feed them into tests/test_resource_ocr.py’s fixture tables and the hard-negative pool for the v10 retrain. Every VM run then grows the regression corpus for free — runs become test authors.

V-4 (P1): Sim↔gate constant drift guard

Prevents a whole class of silent divergence; the seeds are already visible. world_sim.py has its own BUILDING_COSTS/FEUDAL_PREREQ_BUILDINGS, the executor has _BUILD_WOOD_COST/_BUILD_PREREQ_CLASS, and aoe2.db has the authoritative tables — three copies of game truth that nothing cross-checks (the same shape as the v5/v9 model drift, F-5, in data form). One unit test asserting executor tables == sim tables (== aoe2.db rows where present) makes drift a test failure instead of a subtle sim-says-pass/game-says-fail bug.

V-5 (P1): Synthetic end-to-end metrics game

Would have caught: F-9/F-19 (action_success_rate 2.38/1.54, total_food_gathered=200); guards T-601 and the results.tsv contract (T-103). No test ever ran a whole game and looked at the final metrics — each metric had a unit test, but the aggregation across paths (fallback actions, composite steps, pipelined heads) is what broke. Run the V-1 closed loop (or synth_game_loop with a stubbed provider) to completion and assert snapshot invariants: action_success_rate <= 1.0, executed_actions >= successful_actions, total_food_gathered ≈ the sim’s known gathered total, game_end_reason != "". Cheap, and it pins the MetricsSnapshot contract end-to-end rather than field-by-field.

V-6 (P1): Record/replay for LLM scenarios

Makes the existing scenario suite continuously useful. scenario_runner.py exercises the real ClaudeProvider — high fidelity, but it needs an API key and money, so it isn’t in CI and quietly stales. Add a cassette layer (record messages.parse/create responses per scenario to JSON; replay in CI at zero cost; re-record with a just recipe when prompts change). The zero-action executor turns (F-7, 6-7 per game) are exactly the kind of drift a replayed scenario suite would have flagged when the prompt or model changed, instead of a live run.

V-7 (P2): Harness fault-injection catalogue

Extends what the focus-loss tests started (T-101’s test style). The loop-seam tests now cover focus loss and pending warm-up. Catalogue the remaining faults as parametrized loop tests with the same _patch_loop_seams machinery: remote detection outage mid-game (fallback path — F-5’s blind iterations), OCR returning ({}, None) for N consecutive turns (stale-state behavior), screenshot capture raising, strategist task exceptions. Each is a few lines now that the seams exist, and each is a VM-only surprise today.

V-8: Calibrated failure reproduction — TODO acceptance metrics

The design principle that makes V-1/V-2 measurement instruments rather than happy-path validators: the sim’s failure models are parameterized from the measured run data, so TODAY’S code reproduces the live pathologies inside the sim — and each TODO’s completion flips a metric from red to green. Fidelity becomes checkable in both directions: if current code does NOT fail in the sim the way it failed live, the sim is wrong, not the code.

Reproduction matrix (open TODOs that reproduce deterministically):

TODOSim feature requiredMetricBefore fix (expected)After fix
T-302 idle countOCR noise: idle_count pinned at 1 while true idle grows (calibrated: 71/71 readings, all 3 runs)avg/max idle villagers; total idle-timetrust gate floors dispatch at 3/turn, backlog still grows past 3dispatch matches true count; backlog ≈ 0
T-514 settlementfoundation blindness + wood ledger (V-2)ledger confirmed-count vs sim ground truthfails today: replaying run 3’s sequence (2 placements, 1 purchase, shared baseline) confirms 2 mills for 1confirms exactly 1
T-513 placement2D occupancy — buildings claim tiles as the base grows (world_sim has grid positions/sizes); clicks succeed only on free tiles incl. retry offsetsplacement success rate per game~50% farm no-ops once the TC ring fills (run 3)success ↑, farms/game ↑
T-509 filtersnoise layer emitting the MEASURED FP classes (bare-ground farm, fog-edge town_center, Dark Age stable/knight_line)phantom-poisoned prereq unlocks; false alarms; villager trips-to-nothingphantom mill FP unlocks farms early; possible false combat alarmscounts → ~0

LLM-behavioral items (NOT deterministically reproducible — use scenario_runner + V-6 cassettes with N-run repetition instead):

Deliberately measured elsewhere: T-202/T-203 (OCR latency) — a sim time model (each OCR call costs sim-seconds → fewer decisions by minute 30) would show latency’s strategic cost, but the fix itself is better measured by a plain perf benchmark over the vision fixtures.

Build order within V-8: T-514’s and T-302’s reproductions first — both are near-pure replays of already-observed sequences (run 3’s double-confirmation is deterministic; the pinned count is a one-line noise rule), so they validate the sim’s fidelity immediately. T-513/T-509 need V-1’s renderer + V-2’s noise layer first.

Sequencing note

V-1 and V-2 share the state→entities renderer, so build V-1 first and add V-2’s corruption layer on top of it. V-3 requires only a small VM-side script plus fixture-table entries. V-4/V-5 are pure test files. The payoff logic: the VM run cadence is the bottleneck (~10 min + human attention per experiment); every issue class moved into the virtual loop converts a VM run from “discover bugs” into “confirm improvements.”

Cross-references


Follow-up: Run 13 review (logs/2026_07_17, “Feudal attempt 8”) — FEUDAL REACHED

Run metadata

Why Feudal was reached this time (the user’s question)

Every prior run (attempts 4–7, exp_0001–0005, and all of the 07-11 series) died in the Dark Age. This run reached the age-up press at 21:56:46 — 16 min 17 s into the run — and OCR confirmed Feudal at 22:00:14 (~20 min). The cause is not one fix but the Dark Age pipeline composing end-to-end for the first time; each stage was individually broken in a previous run:

  1. T-531 villager ledger + Dark Age target 30 banked the food. 42 villager_queue_rejected events show the gate holding on every path (reactive, composite, fallback) as the ledger climbed 6→9→12→22→30. Target hit ~21:54:55; food then banked 262→520 in under two minutes and the reactive age-up fired on the next tick it could. Previous runs spent that food on villager 31, 32, 33… forever.
  2. T-534/T-520 reactive Feudal prep built both prerequisites. Mill confirmed by 21:48:36 (“mill already built — one is enough”), lumber camp by 21:54:25 — goal-driven, mill-first, despite circuit-breaker interference (F-45).
  3. T-511/T-521 reactive age-up needed zero LLM cooperation. The h,z sequence fired once (Research Feudal Age (reactive)), the food bank paid (520→20), no button-mash, no menu escape.
  4. T-533 outage fallback kept the executor alive. The single-shot path 400’d on EVERY call (37×, F-50) — the exact error class that made run 12 a dead-executor run (would-be error rate 0.95) — but each turn fell back to the tool loop and produced real actions: llm_error_rate=0.0.
  5. No game-menu incidents, no focus spiral (T-526/T-101 holding; only 2 benign focus_window_error warnings).

So: “relatively early” is really “at all, and promptly” — the banking gate plus the reactive prereq/age-up chain removed the LLM from the critical path of the age-up, and the transport fallback kept the rest of the loop useful.

Fixes validated in the wild

New findings

F-45: Build circuit breaker false-positives are now the #1 issue

21 build_purchase_missing warnings; house, mill, lumber camp, farm AND mining camp all got suppressed at some point — yet the “already built” rejections at 21:48:36 (mill) and 21:54:25 (lumber camp) prove placements WERE succeeding. The settlement check compares raw wood before/after, and ~30 villagers’ gather income swamps the spend: e.g. 21:56:41 house settle wood_before=95 wood_now=235 — +140 income masks a −25 purchase (T-519’s slack problem, now at ruinous scale). Consequences this run:

F-46: No Feudal-age program — the economy idled at the top of the run

After Feudal (~22:00) the reactive tier kept running its Dark Age script: villager target still 30 (rejection message still says “bank food for the Feudal Age” while IN Feudal), no mining camp (suppressed, F-45), no farms, no Feudal buildings, no wood sink. Wood ballooned 231→1833 in five minutes; food 985; gold flat. The LLM burned its turns shift-dispatching “41 idle villagers” (a misread, F-48) to gold with no camp to drop at. The Dark Age pipeline now works; there is nothing equivalent for Feudal→Castle.

F-47: Gold OCR dropped out for 11 minutes; strategist assumed 0

Gold appeared in only 13 of 39 ocr_readings — present (100) until 21:50:22, then missing until 22:02:57 (70). The strategist prompt evidently rendered the gap as Gold=0 (“need 200 gold… currently at 0” at 21:57:11, while gold was actually ~100), driving a premature panic gold-rush in the Dark Age. Also confused: that goal said 200 gold “for Feudal Age” (Feudal costs 500 food, zero gold).

F-48: idle_count now misreads pinned-41 (was pinned-1)

All Dark Age readings: idle_count=1 (the known T-302 failure, 7th run). From the first Feudal reading onward: idle_count=41 with idle_present=False and pop only 32 — internally contradictory twice over. The LLM trusted it (“41 idle villagers!”) for its last ~8 turns.

F-49: Target-class grounding gaps wasted ~35 actions

21 target_class_not_found (LLM requested berry_bush/deer right-clicks when no such detection existed this frame) → right_click_no_coords no-ops; plus 20 right_click_off_map rejections (T-535’s geometry, still open).

F-50: Single-shot grammar STILL over the limit — 37/37 calls 400’d

Every single-shot attempt failed with “compiled grammar is too large” and fell back to the tool loop. T-532 (bounds → validators) was supposed to fix this; either the VM checkout predates it or the fix is insufficient. Cost is latency + tokens per turn (double round-trip), not correctness — T-533 absorbed it — but the fallback is now the de-facto primary path.

F-51 (minor)

New TODOs

E. Decision-making / economy

B/C. Perception

H. LLM transport / schema

F. Metrics

A. Harness / run lifecycle