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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
growthMask() is the reusable spatial-artifact template: an fbm-ragged growth
front creeping outward from a deterministic seed. The raggedness is load-bearing
-- a clean circle reads as a spotlight, not as spreading crystal.
Moire separates area from intensity: intensity saturates early while the mask
keeps growing with tally, so a big shipment reads as "the maze has spread"
rather than "everything got slightly shimmery". Seeded from the tick the crystal
first landed.
Tremor is ~1/4 of the brownout's judder and fever is a smooth haze against
tremor's random jitter, so dread, fever and seizure stay distinguishable.
Also clamps dt at both ends: a negative dt (clock rewind) turned ease()'s
1-exp(-dt/tau) into a huge negative multiplier and threw eased values into orbit.
Unreachable from main.ts's monotonic clock, but not a landmine worth keeping.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per the v3 units ruling, stored is bandwidth-seconds and draw-gen is bandwidth
per second, so stored/(draw-gen) is literally how long until the lights go out.
Tremor ramps over the last 8 seconds. That reads correct at every factory size
for free: 1000/s with 4000 banked feels exactly as doomed as 10/s with 40.
A factory in surplus is always calm, which is also what keeps the boot state
(gen=draw=stored=0) sterile by construction rather than by special case.
Fever averages only the machines that run hot, so one glowing decoder is not
drowned by a sea of cold belts.
Pure and snapshot-shaped so the sterile guarantee is a test, not a promise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SIM landed heat this round, so I read their implementation: `scrammed` lives
on their internal Ent, never on EntityState; they deliberately clear `jammed`
on scram ("reported by its own event, not as a flow jam"); and Renderer only
ever receives snapshots, never events. So the latched-off state is not
snapshot-visible at all.
A plain `heat >= 1` test (what I shipped earlier today) therefore un-scrams the
moment a machine starts cooling. With HEAT_RESTART=0.5 and dissipation
0.004/tick, the sim holds it dead for ~125 ticks (~4s) while heat falls 1.0 ->
0.5 — the renderer showed it alive, glowing and animating, that whole window.
Now latches at 1.0 and releases below SCRAM_RELEASE=0.5, mirroring them.
Verified: 1.0 dead -> 0.7 STAYS dead -> 0.3 releases and recovers.
This duplicates SIM's constant, which is the wrong place for it to live —
EntityState.scrammed is filed as a contract request with the evidence.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Round 2 orders 1-6:
- topology.ts: belt neighbour analysis -> straight/cornerA/cornerB/merge,
one InstancedMesh per (def x shape), flow field baked per shape. Corners
sweep a quarter-arc; merges get inlet arrows.
- cargo follows the corner arc: `t` is progress along the tile, so walking
items straight through the centre made them cut corners and pop in at the
wrong edge. They now enter at the feeding edge (verified 0.089-0.248 off
the centreline where straight pins to 0.000).
- heat.ts + entities.ts: dull-red body ramp, rising shimmer column, throttle
(idle AND GLB clips slow together), scram (dead-dark + amber, frozen) and
a restart path that restores what scram zeroed.
- buffer tanks show fill from their share of bandwidth.stored / bufferCap.
- AnimationMixer plays clip 0 looped; rigged GLBs clone via SkeletonUtils so
instances don't share a skeleton.
- exact cargo interpolation via BeltItem.id, order-matching kept as fallback.
- multi-mesh belt GLBs merge with groups (2 materials, 1 draw call) instead
of rendering only the first mesh.
MachineDef.color is wired to the BODY, not the accent, against the letter of
the orders: all 21 values DATA authored are industrial chassis neutrals per
codex/§8, and wiring them to the emissive makes every machine glow muddy and
kills the two-material rule. Flagged in NOTES for the orchestrator to settle.
500 entities + 2,000 items still at 3.6ms/frame (4.6x headroom), 5 draw calls.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Round 2 per lanes/LANE-SIM.md.
- splitters: round-robin across outward-facing belts, blocked outputs
skipped not waited on; rank now traces THROUGH splitters and takes the
best outcome reachable across every terminal a lane reaches
- buffer tanks: charge on surplus, discharge to cover deficit, brownout
only once dry; bandwidth.stored is the pool
- heat v1: heatPerTick while active + RecipeDef.heat per craft, ambient
cooling, linear throttle 1.0@0.7 -> 0.5@1.0, scram at 1.0, restart
under 0.5, both edges emit 'scram'. Throttle applies to powerGen too,
so a hot decoder sags before it drops off the bus
- commission queue: data-order activation, repeat re-enters at the back
- jammed:'starved' (edge-triggered) and BeltItem.id (stable through belt
transfers and splitters alike)
15 new fixture tests for the M2 mechanics -- data/ carries none of the v2
fields yet, so heat and tanks are tested against a fixture this lane owns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Order asked for a minimum of ten under happy-dom. happy-dom is not actually
installed (not in package.json, not in node_modules) and root config is not
mine to edit, so these run in the default node environment instead — which is
why the logic was factored into pure modules first.
Covers everything the order named except the two things that genuinely need a
DOM: chip rendering (the arithmetic is covered) and attach()'s window binding
(the routing is covered).
Pins the invariants worth pinning: no build page ever exceeds nine slots at
any catalog size, syncRoster copies values rather than holding sim buffers
across frames, footprint hit-testing respects rotation, and an unknown jam
reason or machine kind degrades instead of throwing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Inspector: setRecipe finally has a UI — a process picker listing each
machine's recipes as readable lines ("1 LUMA BARS -> 1 COEFFICIENT PACK +
1 HF DUST"). Heat gets a bar with THROTTLING and SCRAM states rather than a
number. Jam reasons route through jamText() so 'starved' and 'output full'
read as different problems, and an unknown reason passes through uppercased
instead of being swallowed.
Fax: NEXT IN TRAY peek, a STANDING ORDER stamp for repeat commissions, and a
SANITISING chip once anchor-slabs ship (hardcoded id sanctioned this round).
voice.ts/palette.ts now prefer MachineDef.flavor/.color from data and keep the
round-1 hand-transcribed tables as fallback. chipspec.ts lifts the chip
arithmetic out of the DOM so it can be tested.
Caveats in NOTES: the sim pins the active commission to commissions[0] and
never advances, so STANDING ORDER is unreachable; NEXT IN TRAY infers "next"
from data order because the snapshot has no queue.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kind tabs alone don't deliver "every machine hotkey-reachable at any catalog
size": REFINE holds 14 crafters and 1-9 reaches nine. So a page caps at nine
and an oversized kind spills into numbered pages (REFINE 1/2, REFINE 2/2).
The 21-machine catalog lands as 1+3+9+5+2+1 across six pages, every slot
hotkeyed. Pagination is pure (pages.ts) so the invariant can be tested rather
than eyeballed.
Selecting a machine from anywhere pulls the bar to the page that holds it, so
the hot button is always the visible one.
hotkeys.ts splits into a pure KeyRouter (map + dispatch, no window) and a thin
window binding, which lets the keymap be tested in node.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contract request 1 landed, so the inspector opens the way it always should
have: click a machine. pick.ts loses the window.__fktryRenderer probe and
its apology; index.ts loses the TAB manifest cycle that stood in for it.
?uifixture and fixture.ts are deleted too. The sim is real now — a brownout
is something you cause by building an extractor with no generator, which is
a better demo than the fake one ever was.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
One stamp per shipped event (not per unit), 0.4s, sized by count, in the item's
chip colour. Composited in screen space so a melting feed never drags the placard.
Scram events get the one-frame TRACKING LOST bar, positioned from entity+tick so
a run always wipes the same way.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
block: 8x8 quantization on a lattice locked to screen space, so the grid stays
rigid while melt drags the picture underneath it. ring: Gibbs halos, gradient
sampled out to 7px so they ring OUT from edges rather than clinging to them,
oscillating parallel to the edge. moire: two near-identical lattices beating,
with rainbow sheen.
PARAMS 8 -> 11; the ledger, easing, wobble and stats all iterate PARAMS, so the
new classes inherit the existing machine. Wires bricks/coeff-packs to block,
halos/gibbs-wraiths to ring, crystal to moire.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Round 2 orders, all seven.
- data/CANON-DELTA.md: ledger of every invention beyond the codex, with the
rationale and the arithmetic behind the numbers.
- colour + flavor on all 21 machines (kills UI's hardcoded lookup and RENDER's
black-on-black); desaturated industrial per style guide §8.
- heat: software decoder 45->55 gen at 0.0025/tick (throttles 9.3s, scrams
13.3s) so it's a burst generator, not a better ASIC; a pinch on the splicer.
- bufferCap 240 = 10s of the reference chain's one-decoder-down deficit.
- commission queue reordered into a ramp + 3 standing orders, incl. THE
CORRECTION BUYBACK (the sellout loop: anchors clean THE SCREEN).
- container line (moov-shard/offset-ribbon/sync-key) unblocks the hex splicer;
bloom grades 1-5. bloom-concentrate kept — LANE-SCREEN hardcodes it.
- validator 28 -> 36 tests, covering every v2 field; FORWARD_CANON pruned.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adding happy-dom hoisted @types/node, changing global setTimeout's return type;
src/ui/fax.ts stampTimer is a DOM timer id, so use window.setTimeout. One-line
cross-lane touch by the orchestrator to unbreak the build it broke.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the render stub with the full M1 surface:
- camera: ortho iso rig, wheel zoom, middle/right-drag + WASD pan, resize
- ground: shader tile grid w/ world-edge dissolve + screen-space vignette
- registry: procedural placeholders (two-material rule) + MODELBEAST GLB
hot-swap, probed on init and every 10s in dev. GLBs auto-normalise to
the machine footprint, so any export scale lands correctly.
- belts: InstancedMesh per belt def, chevron scroll injected into a
MeshStandardMaterial so belts keep lighting/fog/brownout
- cargo: InstancedMesh per item type, interpolated across ticks via alpha
- ghost + pickTile with footprint occupancy (cyan valid / red blocked)
- feedback: jam klaxon blink, brownout dims the scene via tone-mapping
exposure so emissives dim too
Machine accents are derived from data (recipes -> outputs -> ItemDef.color),
with a legibility floor: near-black products like MDAT ORE would otherwise
render the extractor's CRT head as a black panel and its ore as invisible.
Verified in the dev build: 500 entities + 2,000 belt items at 3.2ms/frame
(5 instanced draw calls); a GLB dropped in mid-session swaps without reload.
Dev scaffold is DEV-gated and tree-shaken from the prod bundle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the stub with the real sim per lanes/LANE-SIM.md round 1.
- grid + placement/remove/rotate/setRecipe, footprint rotation, bounds
- belts: 2 items/tile, 0.45 spacing enforced across the tile seam,
back-pressure, merges, belt<->machine handoff
- crafting: recipe buffers (2x in, stall at 4x out), edge-triggered jams
- bandwidth v1: gen vs draw, compression generates, brownout = gen/draw
scaling both craft progress and belt movement
- shipping + commission tracking
- output routing ranks belts by tracing the chain to its terminal machine,
so the demuxer's three outputs reach three different consumers without
splitters (see NOTES - biggest judgement call of the round)
18 tests. Measured 3,500 entities + 6,820 belt items at 0.42ms/tick (80x
realtime). Full M1 chain ships MELT at tick 1149 in both node and browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sim stub reported an empty world, so there was nothing on screen to
verify the HUD against: an empty inspector and a 0/0 meter prove nothing.
This synthesises a plausible snapshot -- entities mid-craft, a jam, heat,
shipments, a recurring brownout and commission -- so every surface can be
watched doing its job in a browser.
Runs on a wall clock rather than the sim tick: rAF stops in a backgrounded
tab, and a tick-driven fixture crawls too slowly to ever reach the brownout
window. It is a fake sim for eyeballing chrome; nothing here is or needs to
be deterministic.
Inert without the query param, dispatches no Commands, touches no other
lane. Delete once the real sim can be driven to a brownout by hand.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The five M1 surfaces, replacing the stub:
- buildbar.ts: one button per machine, hotkeys 1-9, R rotates, Esc or
right-click clears. LANE-DATA shipped 21 machines, so the bar wraps
rather than running off both edges of the viewport; slots 10-21 are
click-only for now (paging proposed in NOTES).
- topstrip.ts: bandwidth meter going amber at 85% and red + flashing
BROWNOUT over 100%, buffer, shipped ticker + per-item chips, tick or
HALTED. The brownout is legible from this panel alone, which is the
one failure the player has to read across the room.
- inspector.ts: name, codex flavor, intake/output chips with live
buffers, process bar, heat, jam reason. Holds an entity id and
re-looks-up each frame, so a removed unit closes itself.
- fax.ts: the commission card with wants-as-chips and a FAX SENT stamp
on commissionDone.
- toasts.ts: max 4, 6s TTL, colored per event kind.
Click-to-inspect is not wired: the UI cannot reach renderer.pickTile
(UIBus exposes only dispatch/selectedBuild, and ui.init never sees the
Renderer). TAB cycles the unit manifest as a stopgap so the inspector is
reachable without a console. Contract request filed in NOTES.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shared plumbing for the DOM HUD, no framework:
- voice.ts: every player-facing string, so the OSHA-poster deadpan
register lives in one place instead of being re-invented per panel.
- style.ts: injected terminal-industrial stylesheet. Panels re-enable
pointer-events; #ui itself stays transparent to the mouse.
- chips.ts: the item chip (swatch + name + count), keyed off GameData
so new items from LANE-DATA light up with no code change.
- hotkeys.ts: the whole keymap in one file, per lane orders.
- selection.ts: pending build selection, written through to bus._sel.
- pick.ts: tile -> entity hit-testing plus the flattened roster the UI
keeps instead of holding a snapshot across frames (CONTRACTS says sim
may reuse those buffers, and input handlers fire between frames).
- palette.ts: machine accent colors. Stopgap — MachineDef has no color
field; contract request filed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eased param pressure so shipments land as waves, log escalation over the
first ~100 items, cross-modulated wobble above 0.8 so a mature factory looks
alive, brownout seize, and a one-frame white awakening on the first ship ever.
Measured 0.004-0.013ms/frame CPU against a 2ms budget.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mix and total are separate axes: what you ship picks the artifact, how much
you have ever shipped picks the intensity. That separation is what makes
40 chroma slurry read as chroma specifically instead of generic damage.
Covers the full 40-item codex LANE-DATA landed; unknown ids degrade to a
generic noise bump, with a test walking data/items.json to keep it that way.
anchor-slab has negative potency and sanitizes THE SCREEN (see NOTES).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Raw WebGL fullscreen triangle, not three.js: one texture, one pass.
The 8 prototype params keep their tuning verbatim; u_drift, u_brownout
and u_flash are additions. Base feed repaints only when a visible glyph
changes rather than per frame, so the texture upload stays off the hot path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Transcribe the §2-§6 codex into machine-readable canon and add the validator
that keeps five parallel lanes from drowning in dangling ids.
- validator (28 tests): recipes point back at their machine, every item/tech/
commission reference resolves, ids unique + kebab-case, colors hex, footprints
>=1x1, plus a reachability pass proving MELT is craftable from raw ore.
- items 9->36, machines 7->21, recipes 4->35, tech 0->17, commissions 1->10.
- mosh rewired to canon (gop-crate -> melt + reclaimed anchor slab); all seed
recipe ids preserved so nothing dangles mid-round.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>