Development log · 2026-06-08

The Combat Outpost, made real

The COP was geometrically correct and dramatically inert — a cluster of b-huts on a tan patch, defended by eight identical nubs, garrisoned by men who loitered, with a fire plan no soldier would recognise and a wire attack you could only watch as a cutscene. This pass rebuilt every aspect of it toward one bar: a skeptical infantryman looks at it and says "holy shit — that's a COP." It was driven by a six-specialist design survey, a synthesis judge, and seven atomic commits, each verified.

3 → 1blind heuristics replaced by one terrain spine
0 → 1Mk19 on the wall (sited on 94%-dead ground)
dead → livefob.hesco: now written & read
cutscene → the watcha wire assault now demands a decision
0 / 9copaudit sealed pockets — connectivity untouched

The shape of the problem

The outpost data model — CopLayout in terrain.ts — was clean and the movement audit (copaudit) was already 100% green across nine seeds: egress unblocked, 97% perimeter ring, zero sealed pockets, zero unreachable posts. So this was never a bug hunt. It was the harder job: taking a system that works and making it convincing — defensible, lived-in, alive.

A six-agent survey (one specialist each on defensive doctrine, visual fidelity, garrison life, facilities, gameplay, and atmosphere), grounded in the real code and in ATP 3-21.8, fed a synthesis judge. Its verdict was a Law-6 unification — three robust mechanisms instead of a pile of features:

One terrain-geometry spine feeds the render silhouette, the mortar pit, the crew-served siting, and the final protective fire. One render env seam makes the base the warm, lit, human place in a dark valley. One reused fire-request loop turns a wire attack into the commander's watch.

1 · The fire plan, sited by the terrain

Before, the perimeter was eight identical positions {id,cx,cy,facing,tower}; crew-served guns were handed out by raw array index (m240→fps[0]), the tower flag was fp % 3, the "mortar pit" was the flagpole, and there was no Mk19 at all. A range card a soldier would laugh at.

Now a deterministic line-of-sight sweep runs over the baked elevation inside buildCop(). For every position it marches its outward sector cell-by-cell, tracking the rising horizon, and measures two things: the avenue score — how far down the sector the gun can graze before terrain masks the ground — and the dead-space fraction. Then it sites the weapons the way a platoon would:

Bar chart of each fighting position's avenue reach and dead space, coloured by assigned weapon
The data that drives the siting (seed valley-2533). The .50 lands on the 922 m avenue with the lowest dead space; the Mk19 lands on the 195 m position that is 94% dead ground — the defilade nothing else can touch. No hand-placement: pure terrain geometry, identical on every replay.
Top-down tactical diagram of the COP showing each crew-served sector of fire as a wedge
The same fire plan as a tactical overlay. Wedge length is scaled to avenue reach — you can see the .50's long arc to the south-west, the M240 covering south, and the Mk19's short, steep sector to the north registered onto the FPF (the most dangerous, most-concealed avenue). The mortar pit sits in rear defilade.
Why it's honest: the dead-space numbers are high (58–94%). That's not a bug — it's the Korengal. A COP on a knob in that terrain has brutal dead ground close in; it is literally why Restrepo and Keating were so dangerous. The sweep found the real masks, and the Mk19 was placed to answer them.

2 · A strongpoint you can see

The wire used to be a flat tan tint in the baked terrain. The art to fix it already existed in the sprite manifest — hesco-straight, antenna-array, jersey-barrier, ico-mortar — and was never drawn. drawCop() now paints the fortified silhouette: a real HESCO bastion wall of gabion segments, a comms mast on the skyline, the mortar pit, a serpentine ECP of staggered T-walls, drifting burn-pit smoke, a flag that streams downwind, and the crew-served sectors of fire colour-coded by weapon.

The COP before the overhaul — a cluster of buildings on a tan patch
Before. Functional, but the perimeter doesn't read as a wall and the fire plan is invisible.
The COP after — a recognisable fortified outpost with a HESCO wall and sectors
After. The bastion ring reads as a wall; sectors, mortar pit, antenna, ECP and motor pool all legible — a recognisable outpost.

3 · The one warm place in a dark valley

At night the COP used to go dark exactly like empty hillside. The fix is the second unifying idea: the drawCop signature was widened to take a render-only env struct ({night, windX, windY, tNow}, mirroring the weather view-struct and never re-entering the sim). An additive lighter pass then emits warm window glow (the TOC, aid station and chow hall brightest — always manned), cold floodlight pools over the gate and LZ, and slow-blinking red tower beacons — all gated on darkness so a daytime frame pays nothing.

The COP at 02:00 — warm lit windows and floodlights against a dark valley
02:00. The outpost is the one warm, human place in a cold valley — exactly the feeling of returning to the wire from a night patrol. Render-only; the sim clock is untouched.

4 · The watch — the hardest part of command

The design's soul is "you watch." Yet a complex attack on the COP itself was a passive cutscene: the garrison has no maneuver task, so the call-for-fire system never fired for it. Now tickCopDefense() watches the wire. When fighters mass against it while the garrison stands to, the TOC raises a Final Protective Fire request — and the clock auto-pauses until you CLEAR HOT or DENY.

The COP under assault: stand-to in red, and a Final Protective Fire request awaiting approval
The watch, live. Left panel flips to ⚠ STAND-TO — manning the wire; the centre demands a decision: "6 fighters in the wire — FPF, danger close. Requesting 60mm on grid 177-361." The mortar fires from the pit, onto the assault as it forms. The FPF is danger-close by doctrine — and only the commander can accept that risk.

It reuses the squad fire-request loop verbatim — one pending request, a 45-second cooldown, and it never auto-fires — so it obeys the ROE law: combat is 100% AI, but the human clears the fires. Verified headlessly on valley-2533: a quiet COP raises nothing; six fighters at the wire raise the FPF; approving it fires the mortar from the pit, expends four rounds (110→106), and kills an attacker.

5 · A garrison that lives

The COP was manned but lifeless — off-duty men loitered, sentries stared one way, stand-to only ever reacted to contact, and fob.hesco (a 0–100 fortification stat) was written at birth and read nowhere. Three additions, all deterministic, wired into tickGarrison:

The squad readout showing the garrison activity and COP integrity bar
The garrison is now legible. At a glance: 9/9 effective · on guard — watching their sectors · COP integrity 60%. Sample a midday COP and you find 7 on guard, 2 MG crews, 11 men on work detail, the medic in the aid station, and the leaders in the TOC — each shown for what it is.

Verification

Every commit kept the standing checks green and was proved before it landed.

CheckResult
copaudit (9 seeds)clean — 0 sealed pockets, 0 unreachable posts, 97% ring, 0 wire-pins (connectivity untouched by the mortar pit / new positions)
smoke (serialize round-trip)SMOKE OK — no NaN, save/load identical; cop is seed-rebuilt, so zero migration
balance (stall check)8/8 deployments saw a firefight, 0 stranded, no stalls (KIA 0.13)
determinismbit-identical — the LOS sweep and the sentry sweep reproduce exactly across two same-seed runs
tsc / buildclean / green across all seven commits
the watch (live + headless)fires — request raised, auto-pause, approve → mortar from the pit, attacker down

How it was built

This was an orchestration, not a solo sprint. A workflow fanned out six independent specialists — each pinned to a senior persona, the real file:line facts, and ATP 3-21.8 — to survey one axis of the COP and return scored, doctrine-cited proposals. A seventh judge deduped them into a partitioned, dependency-ordered build plan (data spine first; render, world, and garrison behind it, one writer per hot file). Then seven atomic commits implemented it, each with its own headless probe and live capture, on a main shared with other agents committing in parallel.

survey ─┬─ defense ┐ ├─ visual │ ├─ garrison ├─► judge ─► partitioned plan ─► 7 atomic commits ─► verify ─► this report ├─ facilities│ ├─ gameplay │ └─ atmosphere┘

What we did not do (restraint, logged)

Engineering record: docs/progress/2026-06-08-cop-overhaul/ (baselines, after-numbers, live + diagram assets). Probes & tools: scripts/{cop-shot,cop-diagram}.{mjs,ts}, scripts/copaudit.ts. Touched: terrain.ts (the spine), world.ts + helpers.ts (mortar origin + crew siting + the watch), draw.ts + WorldView.tsx (the silhouette + atmosphere), garrison.ts (the living garrison), DeployScreen.tsx (legibility). Built and verified by an autonomous agent; numbers are quoted as measured.