Commit Graph

84 Commits

Author SHA1 Message Date
type-two
f75cf872bc [orchestrator] the dial drives, the sky finishes its sentences
Two verified production bugs from the voice-cloning review:

1. Tuner dead-dial: the dock resolved its RadioControl once, inside ui.init(),
   before any gesture built the audio graph - latching the silent echo stand-in
   for the whole session. main.ts now publishes a lazy-binding Radio->RadioControl
   adapter BEFORE ui.init (real subscribe() at probe time), attaching to the
   engine on first gesture and translating shape + units (UI MHz <-> engine kHz,
   volume()/mute() <-> setVolume()/setMuted(), locked id -> station name).
   Verified live: dock setFrequency(0.64 MHz) -> engine 640 kHz, station readout
   'THE STANDARD', '0.6 THE STANDARD AM - SIGNAL LOCKED' on the dock.

2. Chyron clipping: one un-wrapped fillText showed ~51 chars; 29 of THE
   STANDARD's 30 lines (mean 69, max 92) clipped mid-sentence. Now word-wraps to
   two lines with 20->16->13px step-down. Verified live: 71-char propaganda line
   renders complete across two lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:50:03 +10:00
type-two
f35f159b72 [orchestrator] round 5 (MEGA) review: integration fixes — the radio works in prod
Review: 5/5 lanes pass, 446/446, verified live end-to-end: fax ladder opens the
game, floor + seam glints render (RENDER's colorspace find), fossil excavates at
the seeded position, tuner dock slides in, radio locks 88.1 FKTRY FM and AM 640
THE STANDARD. The pipe is real.

Integration fixes (the cross-lane seams no lane owned):
- audio: radio handle exposed outside the DEV gate — the dock was silent in PROD
  (UI probed __fktrySpeaker, SCREEN's getRadio read a DEV-only object; both halves
  correct, handshake unowned). main.ts now publishes __fktrySpeaker on first gesture.
- contracts v7: GameData.seams (SeamDef/SeamMap lifted from SIM) + main.ts imports
  seams.json — seams were inert in the real game (SIM's request).
- index.html RULING: #screen stays interactive (UI's click-to-enlarge landed after
  RENDER's dead-zone probe — overtaken by events); cursor:pointer added.
- main.ts: ghost refreshes every frame (stale demolition tint on Esc — UI finding);
  __fktryDemo uses the grantResearch command (testkit retired).
- relic.test.ts Seamed type vs the new contract field (my breakage, my fix).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 14:32:56 +10:00
type-two
0cc80d3c82 [ui] round 5 notes
All nine orders shipped. The tab-click report was a real bug (scrollbar eating
half of every tab hitbox) and is fixed; RENDER's demolition tint is verified
with a screenshot, closing the item owed since round 3.

The honest parts: DATA's commission queue head-of-line blocks the whole game,
RENDER doesn't read the inspect selection yet, and the radio drives a stand-in
until SCREEN publishes its control. One proposal (audio on the bus), no
contract requests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:12:42 +10:00
type-two
4da8096de9 [ui] live: research via testkit, and the round-5 behaviours against the real sim
Research is granted through SIM's own testkit.grantResearch now, not by
hand-editing a save blob here — that was my round-4 fragility and it broke this
round exactly as predicted. The research loop also runs the lab for real: v5
converts one pack per RESEARCH_TICKS_PER_PACK, so the test drives 45 packs
instead of asserting after 3 ticks.

New coverage against the real sim: the mercy toast fires once and stays once
across a second brownout; the inspect selection is published on open and
dropped both on Esc and when the inspected unit is demolished; the tuner stays
hidden until relics[0].found actually flips, then reveals with the ceremony
toast and the toast standoff.

The commission tests now build the rig a player would (extractor -> belt ->
uplink) because DATA's queue head asks for raw ore the reference factory never
ships — see NOTES on head-of-line blocking. Also stopped hardcoding the i-only
assembler's coordinates; reference.ts moved it again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:12:42 +10:00
type-two
0bf0e5a076 [ui] wire round 5: tuner reveal, screen enlarge, notices, camera hint
The HUD root gains the tuner and the enlarge overlay, the T/arrows/B/M keys,
and the small amount of session state the onboarding needs: the mercy toast
fires once ever, hatch warnings are remembered per pile.

The fossil reveals the dock from either direction — the relicFound event, or a
loaded save where relics[].found is already true and no event is coming. The
dock lands in the toast stack's corner, so the toasts stand off above it rather
than printing over the dial.

Esc now closes the most-recently-fronted thing first: enlarge, then the held
tool, then panels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:12:42 +10:00
type-two
d6d6833b78 [ui] THE TUNER DOCK — the radio gets a face
Hidden until the optical fossil is excavated, then it slides in bottom-right:
frequency dial (drag or arrow keys), AM/FM/SW, volume and mute, and a station
readout in the house voice that reads STATIC until something locks. Its keys
are inert while hidden so they never shadow the pre-discovery game.

src/audio is still a stub with no radio export, so radio.ts resolves the
control at runtime and otherwise drives a silent echo stand-in — which is why
every dial is functional and testable today rather than waiting.

LANE-SCREEN: publish window.__fktrySpeaker = { radio } and this drives real
audio with no change here. The interface is exactly the one you documented —
setFrequency / setBand / volume / mute / getState / subscribe — with getState()
returning {freqMHz, band, volume, muted, station}; a non-null station is what
flips the readout to SIGNAL LOCKED.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:12:18 +10:00
type-two
5d6afdac97 [ui] THE SCREEN click-to-enlarge
Click the sky, get the sky: ~2.5x, centred, over a dimmed backdrop. Esc or a
click closes it.

Enlarges the real element rather than mirroring it. I built the mirror first
and it rendered black — THE SCREEN is a WebGL canvas, and drawImage from a
WebGL context without preserveDrawingBuffer returns nothing. Asking LANE-SCREEN
to enable that flag would cost them a buffer copy every frame for a feature
used occasionally.

Since that means touching another lane's element, it snapshots the style
attribute on open and restores it verbatim on close — including the case where
SCREEN later adds inline styles of their own, which is tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:12:18 +10:00
type-two
e24100c26e [ui] wildlife, the no-seam jam, and the opening's one mercy hint
Voice: dust piles read as nurseries rather than mess ("SOMETHING WILL HATCH IN
IT"), swarms announce and disperse differently, and SIM's new 'no seam' jam
finally gets the line it deserves — "THERE IS NOTHING DOWN THERE. MOVE."

The inspector grows a MOSQUITO EXPOSURE row, read from snap.wildlife by target,
so a machine that is mysteriously slow says why where the player is already
looking. It shows the swarm's severity, not a throughput promise — SIM owns the
real penalty and doesn't expose a number.

Toasts gain pushMessage for lines that aren't raw sim events. First among them:
once per session, on the very first brownout, "NOTHING IS GENERATING. THE
COMMITTEE SUGGESTS: POWER." The alarm alone taught nothing; this is the one
hint the opening needs, and it is emphatically not a nag — hatch warnings are
deduped per pile for the same reason.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:11:59 +10:00
type-two
acb1d88038 [ui] publish inspect mode so the renderer can highlight the open unit
Opening the inspector now publishes {mode:'inspect', entity} through
setSelection, and closing it — by X, by Esc, or because the unit was demolished
under the panel — clears it. Picking a machine or arming remove supersedes the
inspect selection and shuts the panel, so the panel and the highlight can never
disagree about what is being looked at.

The three modes are now genuinely exclusive in selection.ts rather than two
booleans and an implied third state. Inspect deliberately does not count as
"armed": you can click straight from one unit to another without putting
anything down.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:11:59 +10:00
type-two
7e24eab171 [ui] fix: the tab scrollbar was swallowing clicks on half of every tab
Reproduced the "clicking POWER does nothing" report with a real mouse:
elementFromPoint at the button's own centre returned the strip container, not
the button. The horizontal scrollbar was painted over the lower half of every
tab and ate clicks there — which is exactly why keyboard paging still worked
and clicking didn't.

Fixed by giving the scrollbar its own lane (padding-bottom >= its height). The
full button height now hit-tests to the button and a real click activates the
tab.

The strip itself is the approved round-5 design: one row that scrolls rather
than wrapping. The catalog keeps growing — RESEARCH joined in v4 and threw
REMOVE onto a line of its own — and a wrapping row moves every other tab under
the player's cursor. Paging with [ ] scrolls the active tab into view.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:11:39 +10:00
type-two
c441ec3a53 [render] round 5 NOTES
The colorspace finding written up with measurements; the relic's two-zoom
contrast numbers; the 63.7s frame-delta bug.

Includes the B8 picking investigation: 27.6% of the viewport cannot be clicked,
and #screen — a purely decorative overlay in index.html — accounts for 6.9% of
it because it takes pointer events while #ui correctly does not. That is the
review's "unclickable machines". One-line fix, but index.html is not my file.

Two contract requests (a renderer inspect channel, the #screen fix) and a
renewed proposal for a worktree per lane: other lanes saving files triggered a
Vite reload every 5-15s and wiped probes mid-measurement, so this round had to
be verified against an isolated snapshot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:09:20 +10:00
type-two
a89525ecbf [render] THE FLOOR, the relic, the juice, the pipe — and the colorspace bug behind it
The review verdict was "the game about place has no place". The root cause was
not a missing feature: a raw ShaderMaterial gets none of the renderer's output
includes, so THREE.Color's sRGB->linear conversion was never undone and the
crust rendered ~6x too dark. Measured: authored #0c0a15 (RGB 12,10,21) came out
RGB(1,1,2). One #include <colorspace_fragment> and it measures (10,8,18). Every
raw shader in this lane has been that dark since round 1.

Round 5 orders 1-8:
- ground.ts: fbm terrain mottle + neutral rim + the colorspace fix.
- seams.ts: 8 seam regions from data/seams.json. Era skins the crust with a
  wide falloff; the ore glints as sparse twinkling flecks (first pass drew
  continuous veins and they read as green worms competing with the belts).
  Fleck fade is short on purpose: SIM extracts on any footprint/rect overlap,
  so a glint dying inside the boundary would under-report where mining works.
- relics.ts: unfound = a breathing static patch, found = fossil on a plinth.
  Measured +39 contrast vs floor at wandering zoom, -18 at overview zoom —
  findable by looking, invisible from above.
- wildlife.ts: dust-pile glitter heaps + gnat swarms, harassed machines stutter.
  Two draw calls for the whole ecosystem.
- juice.ts: placement scale-pop + puff, demolition burst, shipment flash,
  all derived from snapshot deltas since render() never sees events.
- breakroom.ts: the GLYTCH cabinet, own raycast, opens match.html.
- ghost.ts: inspect highlight + cooler aura (closes round 4's placed-and-
  selected gap).

Also fixes juice never appearing: clock.getDelta() returns the real gap and a
backgrounded tab hands back SECONDS (measured 63.7s), so particles were born
and killed inside one update and the integrator flung them to infinity.
render() now clamps dt to 0.05.

Default framing lifts world origin clear of the build bar. 500 entities +
2,000 items + 50 piles + 198 gnats + the floor: 4.4ms/frame, 8 draw calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:09:20 +10:00
LANE-SIM
e9661dd772 [sim] round 5 NOTES
The relic works end to end (seed-derived, honours DATA's relicReserve,
digs up, persists). Wildlife's design point holds: sink your dust and
nothing hatches. Reference v5 researches a tech at tick 1235 with melt
and the zero-brownout regression untouched.

For the orchestrator:
- ONE contract change needed: GameData.seams + main.ts importing
  data/seams.json. DATA shipped the file mid-round with a good schema
  (and reserved a relic corner for me), but nothing reaches sim.init, so
  seams are inert in the running game. I mirrored their shape as
  SeamDef/SeamMap, exported, ready to lift
- still no trap machine in data, so swarms can't be cleared in the real
  game. Suggested machine + recipe in NOTES; the sim spots traps by recipe
  shape so it needs no code
- swarms slow crafting but NOT powerGen -- my call, flag it if wrong
- save format v3 -> v4 (wildlife, relics, lab timers)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:07:22 +10:00
LANE-SIM
71c885e009 [sim] the relic, wildlife, seams, research time, grantResearch, reference v5
Round 5 (MEGA) per lanes/LANE-SIM.md.

- THE RELIC: one optical fossil per world, position a pure function of the
  seed, 18-28 tiles out, never on a seam, never under the demo. excavate
  within a tile uncovers it; it stays found through save/load forever.
  Folds into DATA's relicReserve when the seam map supplies one
- wildlife v1: piles grow from dust a machine is BACKED UP on, not dust
  merely present -- belt your dust away and nothing ever hatches. First
  cut grew from raw held and the tidy factory still bred 56 swarms; the
  asymmetry is the whole mechanic. Swarms drift to production machinery
  and slow it; traps are recognised by recipe shape (no inputs, cans a
  swarm) so DATA's real trap needs no code. SWARM_MAX caps a plague at 8
  -- a permanently jammed machine bred 29 in one demo run
- seams: extractors idle with 'no seam' off-ore. Consumes DATA's real
  seams.json shape, defensively -- GameData.seams still needs wiring
- research takes time: RESEARCH_TICKS_PER_PACK, stepped whole packs; two
  labs measurably halve the wait (200 ticks vs 260)
- grantResearch: idempotent, save/load safe, verified through the live bus
- reference v5: a second works on the eastern seam that researches a tech
  at tick 1235. Separate from the melt line by choice -- the western
  floorplan has no columns left and threading three ingredients through it
  would make the melt regression hostage to the science

Melt still first at 1247, still shipping at 20k, zero brownout ticks,
179/179 placements legal, zero extractors off-seam once seams are wired.
99 sim tests, 446 across the repo. 63x realtime at 3,500 entities.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:07:08 +10:00
type-two
70130c9e71 [screen] round 5 notes: both organs, and what I could not verify
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:51:24 +10:00
type-two
fd2aee6c50 [audio] world ambience, discovery gating, and the radio control surface
Pre-discovery silence is structural, not a volume setting: the radio's master
gain is zero and ambience is never started until the fossil is found, and layer
1 is all one-shots, so a quiet factory is actually quiet rather than humming
under a noise floor. Measured at exactly 0.0 peak with the dial swept across
all three bands.

Dead air's tone drops 0.32 to 0.10 -- it rendered louder than the music station,
which made "dead air" the loudest thing on the dial, exactly backwards.

getRadio() is the sanctioned narrow surface for LANE-UI's tuner dock.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:51:24 +10:00
type-two
02fc7d5094 [audio] THE BAND: a tuner, four stations, and the sky as subtitles
Gaussian lock so stations swim up out of the noise instead of snapping, and
band-specific static because that is what makes a band feel like a place: AM
brown crackle, FM white hiss, SW a wandering bandpass plus a heterodyne whose
pitch IS the beat frequency, so it swoops down to nothing as you zero in.

THE STANDARD is deliberately just short of intelligible -- you can hear it is
speech and not what it says. THE SCREEN tells you what it says. That gap is the
trick, and subtitleBus is the wire that carries it.

NUMBERS speaks LANE-DATA's planted coordinates; generated filler is rejected if
it would decode onto real ground, so the only true coordinates on the band are
the deliberate ones.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:51:24 +10:00
type-two
7f6ebe5f5a [audio] THE SPEAKER layer 1: the diegetic minimum
Placement thunk, fax dot-matrix chatter, brownout pop-crackle-and-power-down,
scram klaxon, ratification sting. All synthesized, no asset files, ever.

Every sound takes (ctx, dest, t0) and nothing assumes a live context, so the
whole speaker renders in an OfflineAudioContext for deterministic spectral
verification -- the same code the player hears, no test-only path. Noise
buffers use a seeded PRNG so spectra reproduce exactly.

The limiter is a tanh WaveShaper, not a DynamicsCompressor: makeup gain would
pump the whole mix every time the klaxon fires.

The ratification sting is the only consonant, in-tune sound in the game. Its
attack is slow where everything else is percussive, because the enemy is the
one thing here that resolves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:51:24 +10:00
type-two
64cb3edec6 [screen] wildlife shimmer, boot title, and a longer awakening
Mosquito swarms degrade a REGION via the growthMask template, seeded from the
swarm's world position -- the mask's second customer, as intended.

The game finally says its own name: a chromatic-ghosted WIMVEE FKTRY burn-in
holding 1.5s then dissolving to NOTHING IS WRONG. The awakening grows from a
single frame to a 0.47s full-canvas moment, so the first shipment lands as an
event rather than a blink.

Title and awakening are timed in seconds; the scram wipe stays frame-counted.
Three clocks for three fictions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:51:04 +10:00
type-two
b5c4923a87 [screen] jammed-rot: stuck work spoils faster than resting work
A clean idle decays at the round-4 rate untouched. A factory jammed solid
shortens its own grace and steepens its decay, because the work is not resting,
it is spoiling. Rot is the ratio of jammed machines, so one stuck belt in a big
base barely registers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:51:03 +10:00
type-two
d8111feb12 [screen] the substrate ages: era-skinned clean programming
The base feed now wears the highest ratified era -- sepia film with sprocket
holes, phosphor broadcast with interlace, rainbow-on-black disc gloss, the
classic SMPTE stream, blinding fortress white. Corruption stays era-agnostic:
same shader, same params, only what they are painted onto changes.

Boot era is reel, so the world starts in the deepest stratum and climbs to the
surface. The old SMPTE look is now mid-game.

The chyron generalises from an idle-only placard to a line of text, which is
what lets the radio caption the sky in a later commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:51:03 +10:00
type-two
f2da81a9cf [data] the ladder, the ground, and the broadcast schedule
Round 5 (MEGA), all eight orders.

THE COMMISSION LADDER — the fax is the tutorial. The queue used to open on
one MELT, i.e. "build eight machines correctly, in order, with no feedback
until the last one works". Now four rungs teach one mechanic each and never
say so: 20 raw ore (extractor->belt->uplink) -> 15 luma (the demuxer, and the
slurry is your problem) -> 18 hf-dust (waste is inventory) -> 1 melt
(graduation; first-taste keeps its id, reward 50->80). Measured against SIM's
real sim: rung 1 clears at tick 650 = 21.7s with 4 machine types, against a
3-minute budget.

data/seams.json — seam regions per stratum, richness, and a seam-free
relicReserve in the SE so SIM's seed-derived optical fossil always has
somewhere legal to land. stream-crust-west covers all three reference-factory
extractors, pinned by a test.

data/stations.json — the whole dial: FKTRY FM, THE STANDARD (30 lines of
Correction propaganda, 10 minutes before a repeat), NUMBERS, DEAD AIR. The
numbers station is a real treasure map: each 5-figure group is
[x+32][y+32][richness*9] and three of them are the hidden seams. The validator
recomputes them from seams.json, so the map cannot drift, and no decoy is
allowed to decode onto real treasure.

Also:
- mosquito trap: input-free recipe, the shape LANE-SIM detects traps by (my
  first pass took ingredients, which would have silently made it an ordinary
  crafter). Unblocks vaporwave-sunset after four rounds; FORWARD_CANON pruned.
- cooler final 0.0018 -> 0.001: the additive ruling made two coolers immortal.
  Now 1 doubles output, 2 nearly eliminates downtime, 3 is an absurd shrine.
- SIM's tuning constants imported; the hand-copies are retired.
- the optical fossil canon: §1's fossils "sing when excavated", so the world is
  silent until you dig up the severed audio bus. One margin hint, mid-ladder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:47:08 +10:00
type-two
8dd4616580 [orchestrator] MEGA round 5: the paint and the pipe
Supersedes unexecuted round-5 orders. Folds in the live first-eyes review findings
(silence, void floor, onboarding cliff, quiet payoffs) + John's hidden-radio idea.

Contracts v6: AudioFX (THE SPEAKER - all Web Audio synthesis, no asset files),
RelicState + excavate + relicFound (the optical-fossil hidden pipe: find it and the
game grows a tuner - FKTRY FM, Correction propaganda subtitled by THE SCREEN's
chyron, a shortwave numbers station reading hidden-seam coordinates, dead air).
main.ts wires audio + first-gesture unlock; src/audio stub awaits LANE-SCREEN.

Lane orders: SIM = wildlife + seams-become-real + the relic + research time;
RENDER = the floor (seam glints, strata whispers), relic shimmer (no signposting),
juice, break-room cabinet -> match.html; UI = mercy toast, tuner dock, SCREEN
click-to-enlarge, camera hints; SCREEN = era feed + THE SPEAKER (3 audio layers,
4 stations, spectral verification); DATA = the commission-ladder tutorial,
stations.json broadcast schedule, seams.json final.

Ship gate: after review, redeploy partly.party/glytch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:56:09 +10:00
type-two
557c78b208 [orchestrator] round 4 review + contracts v5 + round 5 orders
Review: 5/5 pass, 318/318, tsc clean. Verified live: STANDARDS COMMITTEE renders all
eras with ratified nodes, fuel gauge + ON RESERVE state, composite seam in the tree.
Fixed the demo brownout (mine): __fktryDemo now grants referenceFactoryTech() via
testkit before dispatching (swaps to the grantResearch command when SIM lands it).

Contracts v5: grantResearch command (sandbox is a feature), SelectionState inspect
mode (RENDER's placed-and-selected auras), wildlife (WildlifeState + event),
coolRadius clarified ADDITIVE capped at HEAT_COOL_MAX. main.ts: typed selection
store (inspect-capable), sentinel string no longer sent, narrowing fixed in UI's
faithful-copy harness (my breakage, my fix).

Rulings: research-time approved; pack abandonment kept as design; DATA's ungating +
starter-plant arithmetic ratified; SCREEN era-feed + jammed-rot approved; UI tab
strip approved. MASTERPLAN: bare-stash ban, stale-numbers process note.

Round 5 = M2 closes: wildlife (dust piles, mosquito swarms, traps), research takes
time, era-aging sky, seam map groundwork, inspect-mode handshake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 21:31:57 +10:00
LANE-SIM
3e45ee2652 [sim] round 4 NOTES
Research provable end-to-end on DATA's real tree (realtech.test.ts), not
just a fixture. Cooler aura ~triples a hot generator's run. Constants
exported. Reference factory ships slabs.

For the orchestrator:
- the demo needs one line from you: the reference build uses two gated ids
  so it is a post-research save now. Dispatched cold it comes up 100 draw
  vs 80 gen and browns out -- I watched it. referenceFactoryTech(data)
  names the techs; testkit.grantResearch() opens them. Proposing a dev-only
  {kind:'grantResearch'} command instead of importing test helpers
- DATA fixed the tech bootstrap mid-round and was right to: it was circular
  and 0 of 24 techs were affordable at start
- asic-cooler still has no coolRadius, so its aura is inert in the game
- cooling cap 0.05/tick is my call; reasoning in NOTES

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:20:47 +10:00
LANE-SIM
039eff15fa [sim] research + gating, spatial cooling, exported constants, reference v4
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>
2026-07-17 20:20:32 +10:00
type-two
a48d45b4df [ui] accent: read MachineDef.accent first
Order 5, and the half that actually matters — the precedence comment landed in
the research commit but the lookup itself didn't get staged.

Precedence now matches LANE-RENDER exactly: art-directed accent wins, else the
first-output derivation, else the codex id/kind tables for machines that output
nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:14:33 +10:00
type-two
edca93f298 [ui] round 4 notes
All five orders shipped. The honest parts: SIM's research gating breaks SIM's
own reference factory at HEAD (GEN 80 vs DRAW 100, permanent brownout), nothing
in the game can make a science pack so research can't be performed by playing,
and the order's numbers went stale mid-round for the third round running.
No contract requests; one proposal (grantResearch, so tests stop coupling to
the save schema).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:13:50 +10:00
type-two
7501353311 [ui] live: a full research run, and a faithful main.ts in the harness
The harness bus is now a real v4 host: it owns the typed selection and it is
the thing that dispatches place/remove on a world click. That is what caught
the round-3 double-fire.

New: the whole loop the DoD asked for, against the real sim — locked machine
visible, its `place` silently dropped, build a lab, pick the tech through the
panel, packs delivered, `researched` fires, toast lands, padlock falls off, and
the same `place` now succeeds.

Packs are teleported into the lab's intake via the sim's own save()/load()
because nothing in the game can make a science pack yet (see NOTES);
everything after that is the sim's real drainLabs -> researched -> applyUnlocks
path, unmocked.

Reference-factory tests now run with research pre-granted, because SIM's gating
drops the software decoders reference.ts depends on. Stopped hardcoding the
i-only assembler's coordinates too — reference.ts moved it mid-round and broke
my own test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:13:50 +10:00
type-two
2904ddbb8c [ui] research: the STANDARDS COMMITTEE, and padlocks that fall off live
T opens the tech tree — every tech grouped by era in era order, cost chips in
item colours, era prefix stripped from node names (the heading already says
it), UNLOCKS: tooltips in machine names not ids. Pick a node to dispatch
setResearch; picking the active one stands it down; ratified nodes are inert.

The v4 gating rule lives in a pure research.ts: an id any tech claims is locked
until that tech completes, and an id no tech mentions is always available — so
belts never padlock. Locked machines are greyed with a padlock and a
REQUIRES: <TECH> tooltip, and can't be picked by click or by hotkey. The
padlock falls off in the same frame `researched` lands, and the player never
keeps holding something they can no longer build.

With no ResearchState at all the safe reading is that gates hold rather than
fall open, and the panel says RESEARCH OFFLINE instead of rendering a tree that
can't do anything.

Accent precedence now matches RENDER exactly: MachineDef.accent, then the
first-output derivation, then the codex kind table. RESEARCH joins the page
order for the new 'lab' kind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:13:50 +10:00
type-two
5a36e32246 [ui] migrate to the v4 typed selection protocol
Writes go through bus.setSelection(); the __remove magic string and its
REMOVE_DEF export are deleted — the renderer gets a real mode from main.ts now.

Deleted my own `remove` dispatch: main.ts dispatches it on click as of v4, and
the UI doing it too was a double-fire that only looked harmless because the
second command no-oped. Verified live: one click, one removal.

selection/setSelection are optional in the contract, so a legacy host still
gets the old {def,dir} shape (sentinel included) rather than silently getting
no selection at all. Tested both paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:13:23 +10:00
type-two
3cec62368a [ui] fix: BROWNOUT was lying about a covered deficit
The banner keyed on `out || ratio > 1`, so any time draw exceeded generation
the panel screamed BROWNOUT — even while the buffer tanks were quietly
covering it, the sim's flag was false and THE SCREEN was calm. That state is
the factory working as designed, not failing. Only the HUD was panicking.

BROWNOUT is now the sim's flag and nothing else. A covered deficit gets its
own amber, steady, non-flashing state: "ON RESERVE · 34s", where the seconds
are stored/(draw-gen) — real arithmetic, because v3 ruled stored is
bandwidth-SECONDS and gen/draw are per-second. Same maths SCREEN uses.

Four states (ok / tight / reserve / brownout) now live in a pure power.ts and
are pinned by tests, including the exact case that lied: gen 100, draw 134,
stored 1000, brownout false -> reserve, not brownout.

Also uses v4 bandwidth.capacity to turn the buffer readout into a fuel gauge
(0/900) rather than a bare number.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:13:23 +10:00
type-two
c97dd80a9b [render] round 4 NOTES
Verification numbers; the lock-rule divergence with UI (they were right, I was
wrong — the scrammed lesson misapplied); the showroom overflow postmortem; one
contract request (an inspected-entity channel for placed-and-selected auras).

Notes that round 3's MODELBEAST manifest is stale — ?showroom is the live one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:12:04 +10:00
type-two
5e8e49ef0e [render] v4: ghost mode, locked-build ghost, cooler auras, lab visuals
Round 4 orders 1-5:

- ghost keys demolition off setGhost's `mode` arg; the '__remove' sentinel is
  ignored and REMOVE_DEF is gone from this lane.
- locked-build ghost: padlock-amber (not occupied-red — "not yet" and "not
  here" are different sentences) + a padlock tinted by the gating era. Needed
  a new ERA_TELL palette: ERA_SKIN.tint is a multiplier tuned to leave DATA's
  chassis alone, so it can't carry a signal (stream is #ffffff).
- aura.ts: cooling coverage as a dashed RECTANGLE. coolRadius is chebyshev, so
  the cooled region is the footprint grown by r per side; a circle would look
  better and lie about the corners.
- lab placeholder: bench + the "bottled artifact triptych" (§2); consumes
  MachineDef.accent when present, derivation stays the fallback.

Also:
- buffer fill reads bandwidth.capacity (v4) instead of re-summing bufferCap —
  a second copy of the sim's arithmetic is what the scram latch warned about.
- lock rule matched to LANE-UI's reading: absent research means nothing has
  completed, so gated machines padlock. Mine said the opposite; theirs is the
  literal contract text, and the build bar and ghost disagreeing on the same
  screen is worse than either answer.
- showroom fitted to BOTH axes. Round 3's depth-only fit collapsed the item
  grid to one 206-tile row once the machine hall grew and ate the depth.

Verified against DATA's real v4 data and UI's migrated flow: 6x6 aura on
asic-cooler, reel/broadcast era tells, lab accent #ffc24a from data, 29
machines + 60 items in the showroom, 500 entities + 2,000 items at 3.6ms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:12:04 +10:00
type-two
dbb3615a64 [data] the composite seam + a tech tree that can actually be bought
Round 4 orders, all five.

The headline is a bug I authored: the v4 gating rule (any id in a
TechDef.unlocks is locked until that tech completes) turned three rounds of
tech authoring into a total bootstrap deadlock. Measured: melt not producible
at tick 0 (first-taste impossible), 0 of 24 techs reachable — every pack chain
gated behind packs you cannot make. disc-artifact-bottling cost 10 analog packs
and unlocked the only machine that bottles analog packs.

Fixed as a shape, not a patch, and both rules are now tests:
- the M1 melt chain is starting equipment (so first-taste is possible, and
  RENDER's era derivation correctly reads it as starting equipment)
- one pack is craftable at tick 0 — SPATIAL, made from the quantizer's own
  sins, so your first research is a study of your own damage
23/23 nodes now reachable; six emptied disc nodes deleted (no lane references
tech ids).

Also:
- §10 THE COMPOSITE SEAM transcribed: alpha/hard-matte/blue-hole/fringe-lace/
  green-spill/mixed-pixel/roto-chatter/ghost-feather/sodium-light (forward
  canon), matte bath, optical printer, chroma keyer, roto bench, neural keyer
  (powerDraw 240 — the codex's "appetite that dwarfs everything").
- ARCHAEOLOGY LAB canonized (kind: lab), ungated — a lab behind research is a
  lab you can never build.
- ASIC cooler live: the round-3 spec of 0.02 would have meant "never heats,
  free 100/s". Retuned to 0.0018, sane under both readings of the contract.
- accents where output-derivation can't work; CODEX_REF regex widened for §10.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:04:00 +10:00
type-two
180747ce28 [screen] round 4 notes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:01:38 +10:00
type-two
6404c44fcc [screen] ratification card + pallor pass
The Correction takes the sky for one second every time the player does science:
hospital white, green tick, the standard's real name from codex 1. Composited
dead last in screen space so not one pixel of it is corrupted -- that is the
joke. Hard cut on exit; a dissolve would make it look negotiable.

Timed in seconds, not frames: a broadcast interruption must last a second at any
refresh rate (verified 30 frames at 30fps, 60 at 60fps). The scram wipe stays
frame-counted -- tape artifacts are quantized to frames.

Pallor drains saturation and closes the corners in, per codex 8: colour is the
resource, so a factory with no reserve literally has less of it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:01:38 +10:00
type-two
bc992a99d4 [screen] second dread axis: pallor, and v5 fixture hygiene
tremor asks "how many seconds until dark" -- acute, the cliff edge, expressed as
judder. pallor asks "how much cushion do I have at all" -- chronic, sits there in
surplus, expressed as colour draining out. A full tank draining fast is a jolt;
a near-empty tank coasting is a way of life.

No tanks built means no cushion to miss, so no pallor. Without that, an unbuilt
reserve reads as "totally empty" and paints the boot state grey, which would
break the sterile guarantee on day one.

Fixtures now carry commissionQueue and research ahead of the v5 hardening.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:01:38 +10:00
type-two
5ad7252e20 [screen] boredom: the sky forgets, the ledger does not
No shipments for a sustained stretch and the corruption eases back toward clean.
The LEDGER is untouched -- history is not erased, only the display decays -- so
resuming production puts the targets straight back and the picture walks home in
about a second.

60s grace then 180s decay: a jam you notice, diagnose and fix costs nothing.
The player has to genuinely walk away before the sky starts cleaning itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:01:38 +10:00
type-two
83e3ad2107 [orchestrator] round 3 review + contracts v4 + composite seam codex + round 4 orders
Review: 5/5 lanes pass, 238/238, tsc clean. Verified live: scram duty-cycling with
tanks covering (zero brownout), shipment stamps, strain fever 0.824. Found 1 real bug
in review: UI BROWNOUT banner fires on covered deficit (topstrip.ts:66) - ordered.

Contracts v4: research (lab kind, setResearch, ResearchState, researched event),
SelectionState protocol (kills __remove sentinel, main.ts owns remove dispatch),
accent?, coolRadius? (spatial cooling), bandwidth.capacity?, save/load hardened,
heat ruled GROSS (net = heat - cool). tsconfig now typechecks data/ (DATA's proof).
Orchestrator patched lab entries into exhaustive kind maps (my breakage, my fix).

Rulings: pathspec-commit rule after 3 shared-index collisions (worktrees = named
escalation); bloom escalator reading accepted; UI ?uidemo + SCREEN boredom approved.

Codex: docs/FKTRY_LORE.md grew §10 THE COMPOSITE SEAM - keying/transparency artifact
family from the Corridor Crew compositing history + our corridorkey-mrp-mlx (which
doubles as a PYXLFK ground-truth artifact factory). DATA transcribes it round 4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:43:48 +10:00
type-two
7eda076fda [render] round 3 NOTES: provenance + shared-index proposal
Records that my ten round-3 src/render files reached main inside d1c3f2b
"[sim] round 3 NOTES": the five lanes share one working tree AND one git index,
so SIM committing with -a swept up my staged files. Not a SIM ownership
violation — that commit's render diff is mine. Already pushed and four sessions
are live on this history, so it stays as-is.

Proposal: a git worktree per lane (one index each, one shared repo) would end
this class of accident. Staging discipline can't — the loser is whoever happens
to have files staged, not whoever was careless.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:42:15 +10:00
type-two
82d9ab2b01 [render] round 3 NOTES + MODELBEAST work order
Verification numbers, the prod-leak postmortem, and the showroom's contents as
a 23-row asset manifest (asset key / era / footprint) — every row a live
hot-swap target. One contract request (setGhostMode), filed jointly with UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:36:06 +10:00
LANE-SIM
d1c3f2b09d [sim] round 3 NOTES
Confirms the units conversion (bufferCap 240 = 8.0s of cover at a 30/s
deficit) and reports scram survival: first scram t434, restart t934,
~718-tick duty cycle, ZERO brownout ticks across 20k, melt rate unchanged.

For the orchestrator:
- heat semantics need a one-line ruling. heatPerTick is GROSS in my model
  (net = heatPerTick - coolPerTick). DATA's committed 0.0025 against the
  0.004 default nets negative -- the decoder could never scram. Their
  in-flight 0.0033/0.001 works, so we agree by luck of timing.
- the codex's literal bloom loop is not expressible with per-grade recipes
- asic-cooler does nothing without a spatial heat mechanic
- my sim code landed inside 45003db [ui] REMOVE mode: the shared tree has
  one git index and an add-everything commit in another session swept it.
  Not a UI ownership violation. Suggest a no-`git add -A` rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:35:47 +10:00
type-two
34e8b39fed [ui] round 3 notes
Shipped 1-4, skipped the tech panel (tech.json is still []). The honest parts:
the reference factory can't ship anchor slabs so the SANITISING premise didn't
hold; live pressure verification is impractical in the browser pane (paused rAF
+ HMR churn) so it's headless against the real sim; and I nearly reported
commissionQueue as unpublished before a probe caught SIM landing it mid-round.
Two contract requests (setGhostMode to kill the __remove magic string,
MachineDef.accent) and a ?uidemo proposal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:34:40 +10:00
type-two
9f3a254d85 [ui] live harness: the HUD against the real sim and reference factory
Boots createSim() + createUI() on the real data/*.json, builds
referenceFactory(), and pumps ticks through the same update(snap, events) path
main.ts uses — then reads the actual DOM. No mocks.

Confirms what the round asked to see with eyes but the harness wouldn't allow:
heat climbing to THROTTLING and then SCRAM on a software decoder (inspected via
the real pickTile -> entityAt -> open click path), the unit still reading SCRAM
while it cools back below 1.0, SANITISING on real anchor-slab shipments,
NEXT IN TRAY matching SIM's real queue, a real standing order reaching the head
and stamping the fax, and remove mode demolishing a real entity.

Why headless: the browser pane backgrounds the tab, which pauses rAF, so the
live page advances ~10 ticks per screenshot — SCRAM needs ~435 ticks of
continuous generation, and parallel lanes' HMR reloads wiped the world every
minute or so. Same code, deterministic, milliseconds, and it stays a regression
test.

The reference factory never ships anchor slabs — it recycles them into the
i-only assembler by design — so the SANITISING case demolishes that assembler
and drops an uplink on it, which is what a player would do.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:34:40 +10:00
type-two
a6ecf88623 [ui] DOM tests under happy-dom: chips, window binding, fax stamp
The three that genuinely needed a real DOM and were blocked last round.
happy-dom is actually installed now.

Chips: node reuse across updates, state classes clearing, spec ordering, and
a dangling item id rendering drab instead of taking the panel down.
Hotkeys: attach/detach really binding and unbinding the window, space
preventing page scroll, and no hotkey firing while typing in a field.
Fax: the stamp lands and clears on its own, and two commissions completing
back to back don't let the first timer clear the second early.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:34:22 +10:00
type-two
ea17e95318 [ui] conform to contracts v3: queue truth, scram latch, chassis vs accent
NEXT IN TRAY now reads snap.commissionQueue[1]. SIM publishes the field, so
the round-2 data-order inference is deleted rather than kept as a fallback —
it went wrong the moment a standing order was re-queued to the back, and a fax
that lies quietly is worse than one that says nothing. No queue, no peek.

Inspector reads EntityState.scrammed instead of inferring scram from heat >= 1.
A scrammed machine cools back below 1.0 while still offline, so the threshold
was wrong in exactly the window that matters; re-deriving SIM's hysteresis here
would also desync silently the moment they retune HEAT_RESTART.

Build-bar icons follow the codex's two-material rule, prompted by the v3 ruling
that color is chassis, not accent: grimy body from data, plus one emissive
element derived from the machine's first output item — the same derivation
RENDER uses, so the bar and the world agree. DATA's chassis values are all
desaturated neutrals (correct per §8), and 21 icons in those colours is an
unreadable grey wall; the accent is what tells a quantizer from a demuxer.
The round-1 id/kind tables were never wrong, only mislabeled — they're the
accent fallback now, for machines that output nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:34:22 +10:00
type-two
45003db31a [ui] REMOVE mode: X arms it, click demolishes, Esc stands down
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>
2026-07-17 18:34:05 +10:00
type-two
673a06dab8 [data] round 3 notes addendum: retune fixed two UI heat tests
Measured after LANE-UI landed live.test.ts: their THROTTLING/SCRAM HUD tests
fail on round-2 data and pass on round 3 (heat was decorative before). Their
remaining anchor-slab failure is independent of data — fails identically
either side of the retune; likely the reference layout circulating recovered
slabs back into the assembler instead of an uplink.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:26:21 +10:00
type-two
4cccd22ca4 [data] heat that fires, the muxer, reel + broadcast eras
Round 3 orders, all six. The validator move landed in 4803b6a (swept into
LANE-SCREEN's commit by the shared index); this is the rest.

- heat retune: round 2's numbers were below the sim's always-on cooling, so
  nothing could ever warm up. Decoder now 100 gen at 0.0033 heat / 0.001 cool
  -> throttles 10.2s, scrams 14.5s, 23.9s cycle, 25.8/s average (1.29x the
  ASIC). Verified against LANE-SIM's real sim, not just arithmetic.
- powerGen had to move: the scram/restart hysteresis forces ~30% duty, so at
  55 gen the decoder averaged worse than a steady ASIC and nobody would build
  one.
- two new tests refuse decorative heat (per-tick and per-craft). They caught a
  fresh instance of the same bug in chart-stco-map before it shipped.
- bufferCap 240 -> 300: one tank now covers exactly one decoder scram.
- ASIC COOLER spec'd (aura deferred to M4, no contract field requested yet);
  THE MUXER + moov brain sub-lines; reel + broadcast era tech.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:24:38 +10:00