Round 4 per lanes/LANE-SIM.md.
- research v1: kind:'lab', setResearch, snapshot.research, 'researched'.
Gating: any id a tech unlocks is refused until that tech lands; ids no
tech names are free forever. Labs take only what the active tech still
needs; several labs feed one total; research survives save/load as a
RULE, so a restored save can still build what it unlocked
- realtech.test.ts: a cold-start research run on DATA's own tree -- ore ->
packs -> archaeology lab -> tech -> a refused recipe now accepted. Also
the tripwire if the tech bootstrap ever closes over again; it was
circular and unenterable at the start of this round
- spatial cooling: coolRadius, chebyshev from the footprint, auras sum,
total capped at HEAT_COOL_MAX (0.05 -- past that a stack buys nothing
but still costs bandwidth and floor). Proven by measuring shed rate
- src/sim/constants.ts: 12 constants so DATA imports truth instead of
hand-copying numbers that go stale in silence
- reference v4: anchor-slab overflow tap, so the demo ships slabs (18 per
20k). Needed TWO melt lanes: with one, melt spills onto the slab lane
and the splitter phase-locks with the reactor's push order -- slab to
the assembler, melt to the uplink, forever, and no slab ever sold. It
passed 20k ticks looking perfect. referenceFactoryTech() derives the
techs the layout needs, from the commands
- also wired: bandwidth.capacity, EntityState.scrammed, commissionQueue
65 sim tests, 318 across the repo. Melt still tick 1248, zero brownouts
over 20k.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A misplaced machine is finally undoable by mouse. The REMOVE button sits
with the tabs rather than on a page, because it's a mode, not a machine, and
it has to be reachable from any page. It stays armed between clicks —
demolition is usually plural — and clicking open ground does nothing, so a
stray click never reads as a broken tool. Toast: "UNIT RECLAIMED. THE FLOOR
REMEMBERS."
LANE-RENDER: the hover hook is the __remove ghost sentinel. The UI has no
Renderer reference and the only channel to the ghost is selectedBuild(),
which main.ts feeds to setGhost — so while remove is armed, bus._sel reads
{def: '__remove', dir: 0}. Key the demolition tint off that def id. It's safe
because sim's place handler ignores unknown defs, so main.ts's click-to-place
fires a place for __remove and nothing happens; the UI dispatches the real
remove itself. Exported as REMOVE_DEF and pinned by a test.
A magic string shared between two lanes via a NOTES file is not a design;
contract request filed for UIBus.setGhostMode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>