[orchestrator] round 1 review + contracts v2 + round 2 orders

Review: all 5 lanes pass — 70/70 tests, tsc clean, ownership audit clean,
integration verified live (sim -> events -> UI ticker + SCREEN corruption).

Contracts v2 (granted from lane requests): UIBus.pickTile (wired), BeltItem.id?,
MachineDef.{color,flavor,heatPerTick,bufferCap}?, CommissionDef.repeat?,
SimEvent 'scram'. main.ts: non-left pointerdown no longer places. happy-dom added.

Rulings: canon melt chain IS M1 (masterplan updated); rank-routing stays, splitters
next; anchor-slab sanitizing -> Correction buyback fiction; build-bar paging approved;
per-lane dev ports 8151-8155. Round 2 orders written in all lane docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-17 16:42:58 +10:00
parent 5ee0fb9e78
commit 86d765f90b
10 changed files with 292 additions and 149 deletions

View File

@ -52,12 +52,19 @@ codex wins.
missing data. Build to the contract, stub the rest, note the stub.
- If the app won't boot because of someone else's in-flight work, note it, build
against the contracts anyway, and verify with `npm test`/`check` this round.
- **Dev server ports are assigned per lane** so five parallel sessions never collide:
`npm run dev -- --port <yours>` — SIM 8151, RENDER 8152, UI 8153, SCREEN 8154,
DATA 8155. 8150 belongs to the orchestrator. Do not create or edit
`.claude/launch.json` (orchestrator-owned, lives outside the repo).
## Milestones (orchestrator's map — lanes don't self-assign ahead)
- **M1 — FLOW** (rounds 1-2): place extractor → belts → demuxer → quantizer → mosh
reactor → shipper; items visibly ride belts; MELT ships; THE SCREEN corrupts in
response. The whole fantasy in miniature.
- **M1 — FLOW** (rounds 1-2): the full canon melt chain — ore → demuxer →
{quantizer, subsampler, p-caster, dct-press} → gop-assembler → mosh reactor →
shipper. RULING (round 1): LANE-DATA's codex-faithful chain replaces the original
6-machine sketch; the ~50-entity build IS the fantasy. A deadlock-free reference
factory ships as the official demo (SIM round 2). Items visibly ride belts; MELT
ships; THE SCREEN corrupts item-specifically.
- **M2 — PRESSURE**: bandwidth economy + brownouts, heat/throttling, HF dust piles,
first wildlife (mosquito swarms), commissions via fax UI.
- **M3 — DEPTH**: tech tree + eras, bloom loops, subsampler bleed, moiré growth,

View File

@ -26,40 +26,46 @@ REQUESTs.
---
## CURRENT ORDERS — Round 1 (goal: milestone M1 "FLOW" + M2 prep)
## CURRENT ORDERS — Round 2 (goal: contracts-v2 content + M2 "PRESSURE" economy)
The seed JSONs in `data/` are orchestrator placeholders — correct them, then grow:
Round 1 review: the validator's fault-injection and the FORWARD_CANON allowlist were
exactly right. Verdicts on your report: **M1 IS your canon chain** — MASTERPLAN
updated, no shortcut recipe, the depth is the game. Your four inventions
(`press-anchor-slab`, `color-plate` + `v-hold-roll`, `artifact-bottler`,
`lane-splitter`) are **CANONIZED**. Dials-as-recipes STAYS (right call; a real dial is
a v3+ contract discussion). CONTRACT REQUEST GRANTED: `MachineDef.heatPerTick?` exists
now — and your validator should now also accept `color?`, `flavor?`, `bufferCap?`,
`CommissionDef.repeat?`.
1. **Validator first** (`src/sim/data-validate.test.ts`, vitest): every recipe's
machine + items exist; every machine's recipes exist and point back at it; every
tech unlock exists; commission wants exist; ids unique + kebab-case; colors valid
hex; footprints ≥1×1. This test is the lane's handshake — it must fail loudly on
any dangling reference.
2. **Items** → full §2 + §3 catalog: all raw resources, refined intermediates, frames,
the five science packs, and tier-2 products (melt, bloom-concentrate,
macroblock-bricks, ringing-halos, static-canister, moire-crystal, false-color-
speckle, judder-cams, explosive-hits, gamut paints ×3). Colors from codex vibes.
3. **Machines** → full §4 catalog (demosaicer, dct-press, subsampler, mv-extractor,
p-caster, gop-assembler, bloom-duplicator, field-loom, telecine-puller,
hex-splicer, software-decoder, buffer-tank...). Machines beyond M1's seven should
exist in data even though sim/render only generically support crafter/power kinds
— that's fine, they'll work as generic crafters until given bespoke mechanics.
4. **Recipes** → wire the economy: ore → luma/chroma/mv-flux (demux), luma → coeff
packs + hf-dust (quantize, generates bandwidth), mv-flux → delta-wafers, slab +
wafers → gop-crate, gop-crate → melt + reclaimed slab (mosh), coeff overdrive →
macroblock-bricks + double hf-dust, hf-dust → static-canister, science pack
assembly ×3 (analog/spatial/temporal). Include at least two genuinely absurd
ratios for comedy that still balance.
5. **Tech tree v1**`disc` and `stream` era nodes: unlock quantizer tiers, mosh
reactor, bloom duplicator, subsampler grades. Costs in science packs.
6. **Commissions** → 8-10 in full fax voice (§6 has three exemplars). Escalating
absurdity and quantity; at least one demanding a *live* hazard product you know
isn't shippable yet (mark it `"wants": {"mosquito-swarm": 3}` — forward canon;
the validator should allow items that exist in data even if no recipe makes them).
1. **`data/CANON-DELTA.md`** (new file, yours): ledger of everything invented beyond
the codex — round 1's four + whatever follows. The lore doc absorbs it in one
orchestrator pass later; until then this is the single place canon-drift lives.
2. **Art direction pass**: `color` on all 21 machines (renderer precedence:
your color > derived-from-outputs) and `flavor` on all 21 — codex one-liner each,
house voice. This kills UI's hardcoded lookup and RENDER's black-on-black problem.
3. **Heat economy**: `heatPerTick` on software-decoder, and scale its `powerGen` so
hot-but-mighty vs ASIC cool-and-steady is a real choice; a pinch of `heat` on
hex-splicer recipes. SIM implements throttle/scram this round — your numbers decide
whether anyone ever dares run two software decoders side by side.
4. **`bufferCap` on buffer-tank** — one tank should ride out roughly 10 seconds of
typical M1-chain deficit. Show your arithmetic in CANON-DELTA.
5. **Commission queue content** (SIM lands queue + `repeat` this round): reorder for
a difficulty ramp; add 2-3 `repeat:true` standing orders, including **THE
CORRECTION BUYBACK** — pays generous bandwidth for anchor-slabs, fax written in The
Correction's voice: polite, clean, quietly horrifying. This is the sellout loop
(ship your anchors for power now, watch your SCREEN go clean — SCREEN lane already
sanitizes on anchor shipments).
6. **Container line**: `moov-shard` / `offset-ribbon` / `sync-key` items + recipes
assembling `container-pack` (unblocks `stream-hex-splicing`); prune the
FORWARD_CANON allowlist as they gain recipes.
7. **Bloom line**: `bloom-grade-1..5` items + bloom-duplicator recipes
(grade n + delta-wafers → grade n+1); grade 5 is commission bait. Validator covers
all new content.
**Definition of done:** `npm test` green including your validator; dev server still
boots with expanded data (placeholders render for everything); every entry traceable
to a codex section. NOTES list what you invented beyond the codex.
**Definition of done:** validator green over the expanded catalog; every machine has
color + flavor; the queue tells a story from first-taste to the first buyback; SIM's
reference-factory test still passes against your rebalanced numbers (coordinate via
NOTES if a ratio change breaks their layout).
---

View File

@ -25,37 +25,33 @@ you never simulate. If motion looks wrong, interpolate better or note it — don
---
## CURRENT ORDERS — Round 1 (goal: milestone M1 "FLOW")
## CURRENT ORDERS — Round 2 (goal: finish M1 polish, open M2 "PRESSURE")
Replace the stub in `src/render/index.ts`:
Round 1 review: excellent — the hot-swap verification and the fog/CAM_DIST honesty
note were exactly right. Verdicts: req 1 GRANTED (`BeltItem.id?` in contracts v2 —
use when present, keep order-matching as fallback until v3 hardens it). Req 2 GRANTED
(`MachineDef.color?` — precedence: `def.color` > your derived-from-outputs >
kind accent; your derivation stays as the fallback, it was the right instinct).
Req 3 DONE (main.ts ignores non-left buttons; drop your camera.ts workaround or keep
it as belt-and-braces, your call).
1. **Camera + controls.** Orthographic isometric-style view. Pan: middle-drag or WASD.
Zoom: wheel (clamped ~8..40 world units). Window resize handled.
2. **Ground.** Tile grid over the 64×64 world; subtle Stream-Crust styling (dark
`#0a0812` base, faint `#181425` lines) — see style guide §8. A soft vignette or
fog so the world doesn't just end.
3. **Asset registry + hot-swap.** `registry.ts`: maps asset key → THREE.Object3D
factory. Default: procedural placeholder (footprint-sized beveled box, body color
from data, one emissive accent, tiny idle animation — e.g. quantizer jaw bob).
On init, attempt `GLTFLoader` for `public/assets/models/<key>.glb`; if present it
replaces the placeholder factory. Also poll or re-check on a 10s timer in dev so
assets dropped in mid-session appear without reload (note what you implement).
4. **Entities.** Sync scene to `snap.entities` (add/remove/rotate). Belts get a flat
directional piece with an animated arrow/chevron texture (shader or UV scroll).
5. **Belt items.** InstancedMesh per item type (colored octahedron/box placeholder),
positioned from `BeltItem.t` + belt entity pos/dir, interpolated with `alpha` for
butter-smooth motion at 30 tps sim.
6. **Picking + ghost.** `pickTile` via raycast to the ground plane. `setGhost` shows
the selected machine's placeholder at the hovered tile, tinted cyan; red tint if
tiles occupied (you can read occupancy from `snap.entities` footprints).
7. **Feedback dressing (cheap but load-bearing):** jammed machines get a small
blinking amber light; brownout dims the whole scene ~30% and flickers lights
(listen to snapshot.bandwidth.brownout).
1. **Belt topology visuals**: corner/junction pieces chosen from neighbor analysis —
pure presentation, sim truth unchanged. Splitters (mechanics land this round in
SIM) get a visible output-cycling tell.
2. **Heat**: shimmer + dull-red emissive ramp driven by `EntityState.heat`; throttled
= idle animation visibly slowed; scram = dead-dark + blinking amber until restart.
3. **Buffer tank**: visible fill level (shader window or scale trick) proportional to
the tank's share of `snapshot.bandwidth.stored`.
4. **AnimationMixer**: play clip 0 on rigged GLBs with an idle loop — MODELBEAST
assets are imminent; verify with any animated GLB you can find or make.
5. **Exact cargo interpolation** via `BeltItem.id` — delete the one-tick approximation.
6. **Multi-mesh belt GLBs**: merge-or-group fix (your call, note it) so a fancy belt
asset can't half-render.
**Definition of done:** dev server shows the factory; placing a line from the UI (or
console dispatch) shows machines + items gliding down belts smoothly; a GLB dropped
into `public/assets/models/quantizer.glb` replaces the placeholder. `npm run check`
clean. NOTES written.
**Definition of done:** reference factory (SIM ships it this round — coordinate via
their `src/sim/reference.ts` export in a dev page or test harness) looks ALIVE: corners
read as corners, heat reads as heat, tanks read as tanks; perf standing rule still met
under `?devscene=stress`.
---

View File

@ -27,33 +27,36 @@ seed stock — port, don't move).
---
## CURRENT ORDERS — Round 1 (goal: milestone M1 "FLOW")
## CURRENT ORDERS — Round 2 (goal: artifact fidelity, M2 "PRESSURE" hooks)
Replace the stub in `src/screen/index.ts`:
Round 1 review: the pixel-reading verification set the bar for the fleet. Verdicts:
your anchor-slab PROPOSAL — option (b) ADOPTED as M3 fiction (The Correction buyback;
DATA writes the standing order this round); LANE-UI surfaces a SANITISING chip
meanwhile; keep potency 2.0 as shipped. Debug hook: KEEP, gated behind
`import.meta.env.DEV`. The 2-red you flagged was mid-round churn — HEAD is 70/70.
The launch.json affair: resolved, per-lane ports are in MASTERPLAN now; you're on 8154.
1. **Port the GLYTCH shader stack** into a proper module (`glitchStack.ts`): vertex +
fragment shader with the 8 uniforms, compiled against your base texture. Keep
param semantics identical to the prototype (it's tuned; respect the tuning).
2. **Corruption ledger.** `onEvents`: each `shipped` event adds to a per-item tally.
Map items → param pressure in `corruptionMap.ts`, e.g. `melt → u_mosh+u_melt`,
`chroma-slurry → u_chroma`, `hf-dust → u_noise`, `anchor-slab → REDUCES overall
corruption slightly` (shipping clean anchors sanitizes — nice tension, note it for
the orchestrator). Unknown items → small generic noise bump. Pressure eases toward
its tally-driven target (lerp) so shipments feel like waves, not switches.
3. **Escalation curve.** Total corruption 0→1 over roughly the first 100 shipped
items (tune by feel), log-ish so early shipments feel huge. Above 0.8, add
compound wobble (params cross-modulate slowly) so a mature factory's SCREEN looks
*alive*, not maxed-out static.
4. **Brownout reaction.** On `brownout:true`, freeze-frame + heavy dropout lines +
desync judder until it lifts. The factory's pain shows in the sky.
5. **Idle charm.** With zero corruption, the clean feed runs bland loops with a
"NOTHING IS WRONG" chyron. First shipment ever interrupts it with a single frame
of full-white glitch — the awakening. Cheap, unforgettable.
1. **Artifact passes the 8 params can't express** (your list, adopted wholesale):
- legible 8×8 **macroblock quantization** pass — bricks/coeff overdrive must READ
as a blocky grid, not generic noise;
- edge-detected **ringing halos** (Gibbs oscillation hugging contrast edges);
- **moiré interference lattice**.
Wire signatures into corruptionMap: `macroblock-bricks`, `ringing-halos`,
`moire-crystal`. Keep the per-item legibility bar from round 1: shipping 40 bricks
should look UNMISTAKABLY different from 40 slurry.
2. **Shipment lands**: a ~0.4s stamp/silhouette flash in the item's chip color
composited on the feed per `shipped` event (batch bursts: one stamp per event, not
per unit — a 40-unit shipment is one big stamp, not 40 flickers).
3. **Scram wipe**: `scram` events (new in contracts v2) get a one-frame white
"TRACKING LOST" bar wipe. Cheap, from the event stream you already have.
4. **OPTIONAL — signal strain**: buffer tanks land this round; if you want THE SCREEN
to tremble as `bandwidth.stored` drains BEFORE brownout, file the snapshot-access
CONTRACT REQUEST you deferred (`frame(timeMs, snap?)`) and build it next round —
or skip it. Your call; the seam is noted and I'll grant it in v3 if asked.
**Definition of done:** dev build — screen starts sterile; console-dispatch a few
shipments (or wait for the real chain) and corruption rises, item-specifically;
brownout visibly seizes it. `npm run check` clean, frame budget respected (note
measured ms). NOTES written.
**Definition of done:** three new artifact classes verified the way you did round 1
(drive, read pixels, screenshot); stamps and scram wipe visible on the dev build;
frame budget still <2ms; tests updated for new signatures.
---

View File

@ -21,44 +21,39 @@ playable through unit tests. You are the lane the other four trust with their li
---
## CURRENT ORDERS — Round 1 (goal: milestone M1 "FLOW")
## CURRENT ORDERS — Round 2 (goal: finish M1, open M2 "PRESSURE")
Replace the stub in `src/sim/index.ts` with a real implementation:
Round 1 review: outstanding. Verdicts on your report — **rank-routing STAYS** (it made
M1 flow; your proposal 2, auto-fallback, is REJECTED for exactly the reason you gave:
it deletes the byproduct tension). Proposal 3 (starved) APPROVED. Contracts v2 landed
for you: `BeltItem.id?`, `CommissionDef.repeat?`, `MachineDef.heatPerTick?/bufferCap?`,
`SimEvent 'scram'`. Extractor seam tiles: deferred to M3 worldgen, stop worrying.
1. **World + placement.** Tile grid (start 64×64, world-centered). `place` command:
validate footprint fits and tiles are free, spawn `EntityState`, emit `placed`.
`remove` (emit `removed`, refund nothing yet), `rotate`, `setPaused`. Invalid
commands are silently dropped (UI will preview validity later).
2. **Belts.** `kind:'belt'` entities carry up to 2 items each as `BeltItem` (t: 0..1),
moving at `beltSpeed` tiles/sec. Items transfer to the belt/machine the belt points
into (dir). Items on a belt keep minimum spacing 0.45. Belt→machine: push into
`inputBuf` if the machine's current recipe wants that item and buffer < 2× recipe
need; otherwise the item waits (belt backs up — this is core Factorio feel, get it
right). Machine→belt: outputs pop onto an adjacent belt facing away, one item per
available slot.
3. **Crafting.** Machines with recipes: when `inputBuf` covers `inputs`, consume,
run `ticks` (progress 0..1), emit `crafted`, deposit `outputs` into `outputBuf`.
If `outputBuf` holds ≥ 4× outputs, machine stalls with `jammed:"output full"`
(emit `jammed` once per stall, not per tick). Extractors need no inputs. Machines
with multiple recipes use `setRecipe`; default to `recipes[0]`.
4. **Bandwidth (power) v1.** Sum powerGen (power kind) vs powerDraw of all entities +
active recipe `bandwidth` (note: negative recipe bandwidth GENERATES — quantizer
compression pays power, per lore). If draw > gen: global speed multiplier =
gen/draw for all progress and belt movement (brownout), emit `brownout` on/off
edges, set `snapshot.bandwidth` accordingly. No storage/buffer tanks yet.
5. **Shipping.** `kind:'shipper'` consumes ANY item arriving in its input, increments
`shippedTotal`, emits `shipped`. Track the single active commission (first in
data): fill `commissionProgress`, emit `commissionDone` once satisfied.
6. **Tests** (minimum): placement collision; belt line moves an item end to end in
the expected tick count; belt back-pressure (full line stops cleanly, no item
loss/dupe); demux recipe consumes 2 ore → 1 luma + 1 slurry; brownout halves
speed at draw=2×gen; determinism (two sims, same seed+commands, 500 ticks,
deep-equal snapshots); shipping increments and fires commissionDone.
1. **Reference factory.** `src/sim/reference.ts` exporting
`referenceFactory(data: GameData): Command[]` — your deadlock-free M1 layout
(second quantizer on crime, second assembler on i-only). Test: ships melt within
2,000 ticks AND is still shipping at 20,000 (the deadlock regression net). This
becomes the official demo; I wire it to a dev hook in main.ts next round.
2. **Splitters.** `kind:'splitter'` becomes real: 1 input, round-robin across outward-
facing adjacent belts; a blocked output is skipped, not waited on; your rank cache
traces THROUGH splitters (rank = best reachable downstream). This is the sanctioned
surplus-voiding tool — the rank rule elsewhere is unchanged.
3. **Buffer tanks.** `kind:'buffer'` + `bufferCap`: charge when gen>draw, discharge to
cover deficit, brownout only when tanks run dry; `snapshot.bandwidth.stored` = pool.
4. **Heat v1.** Accumulate `heatPerTick` while active + `RecipeDef.heat` per craft;
ambient dissipation; heat>0.7 → per-machine throttle (curve is your call, note it);
heat=1 → scram: machine stops, emits `{kind:'scram',on:true}`, restarts below 0.5
with `on:false`. `EntityState.heat` is already in the contract — make it real.
5. **Commission queue.** Data-order activation; `commissionDone` advances; `repeat:true`
re-enters at the back of the queue. Progress counts only while active (keep).
6. **`jammed:'starved'`** — recipe idle with empty inputs, edge-triggered like
'output full'.
7. **`BeltItem.id`** — stable per-item ids in every snapshot (optional field now;
hardens to required in v3, so just always set it).
**Definition of done:** with the dev server running, the orchestrator can (via UI or
console `__fktryBus.dispatch(...)`) place extractor→belts→demuxer→quantizer→
mosh-reactor→shipper and watch `shippedTotal.melt` climb. All tests green. NOTES
written (decisions, gotchas, what you'd tackle in round 2).
**Definition of done:** all of the above tested (determinism suite still green);
reference factory test proves melt at scale with zero deadlock; heat demonstrably
throttles and scrams the software decoder in a test.
---

View File

@ -24,36 +24,32 @@ dispatch Commands via the bus and read state from `update(snap, events)`.
---
## CURRENT ORDERS — Round 1 (goal: milestone M1 "FLOW")
## CURRENT ORDERS — Round 2 (goal: finish M1 interaction, open M2 "PRESSURE")
Replace the stub in `src/ui/index.ts`:
Round 1 review: strong, and the honesty section was the best in the fleet. Verdicts:
CONTRACT REQUEST 1 GRANTED — `UIBus.pickTile` is live, wired to the renderer in
main.ts. Req 2 GRANTED (`MachineDef.color?`). Req 3 GRANTED as `MachineDef.flavor?`
LANE-DATA fills it this round; read data first, keep voice.ts as fallback. Req 4
GRANTED — `happy-dom` is installed; use `// @vitest-environment happy-dom` per test
file (no root config change needed). Build-bar paging PROPOSAL: APPROVED.
1. **Build bar** (bottom center): one button per machine in `GameData.machines`
(icon = colored square from placeholder color + name), hotkeys 1-9. Click/hotkey
selects for placement → sets `bus._sel` (see main.ts) `{def, dir}`; `R` rotates the
pending dir; `Esc`/right-click clears selection. Selected button shows hot state.
2. **Top strip**: bandwidth meter (gen vs draw bar that goes amber near 100%, red +
"BROWNOUT" flash when over), shipped-total ticker, tick/paused indicator.
`Space` toggles pause (dispatch `setPaused`).
3. **Inspector** (click an entity with nothing selected to build): panel showing
machine name, codex flavor line (hard-code a lookup of 1-liners per machine for
now — request codex excerpts in data via NOTES if you want them properly), recipe
with input/output item chips + live buffer counts, progress bar, jam reason in
amber. Close with Esc. (Selecting an entity: you'll need the hovered tile — reuse
`renderer.pickTile` via a `pointerdown` listener on `#ui`'s capture phase or
request a cleaner hook via CONTRACT REQUEST if this fights main.ts's handler; a
pragmatic v1 is fine.)
4. **Commission fax** (top right, under THE SCREEN): the active commission's flavor
text on a fax-paper styled card, `wants` as item chips with progress `3/40`,
satisfying it plays an aggressively mundane "FAX SENT" stamp animation on
`commissionDone`.
5. **Event toasts** (bottom right): `jammed`, `brownout`, `commissionDone` become
small toast lines in the house voice, max 4 on screen, 6s TTL.
1. **Click-to-inspect** via `bus.pickTile`. Delete the TAB-cycle fallback and
`?uifixture` — the sim is real now; drive a brownout by building.
2. **Build bar paging** (your design): kind tabs, `[`/`]` to page, 1-9 within the
active page — every machine hotkey-reachable at any catalog size.
3. **Inspector upgrades**: recipe picker (`setRecipe` finally gets its UI), heat bar
with THROTTLING/SCRAM states, and starved-vs-backed-up wording (sim distinguishes
`'starved'` from `'output full'` this round).
4. **Commission queue UI**: current fax + a "NEXT IN TRAY" peek; `repeat:true`
commissions get a STANDING ORDER stamp. When anchor-slabs ship, show a SANITISING
chip (hardcoding the id is fine this round — the fiction becomes The Correction's
buyback in M3).
5. **Tests, minimum 10**: chips, roster/footprint hit-testing, selection, hotkey map —
under happy-dom.
**Definition of done:** full M1 chain placeable entirely with mouse + hotkeys, no
console needed; brownout is legible from the top strip alone; fax completes when melt
ships. `npm run check` clean, no console errors, pointer fall-through verified.
NOTES written.
**Definition of done:** the reference factory (SIM ships `src/sim/reference.ts` this
round) is inspectable end to end by mouse alone; every machine reachable by hotkey;
tests green; fixture deleted.
---

View File

@ -51,6 +51,10 @@ export interface MachineDef {
powerGen?: number;
beltSpeed?: number; // tiles/sec, kind==='belt'
asset: string; // codex asset key for renderer mesh registry, e.g. "quantizer"
color?: string; // v2: art-direction accent; renderer falls back to derived
flavor?: string; // v2: codex one-liner for the inspector (DATA's material)
heatPerTick?: number; // v2: heat while active (the software decoder's whole bit)
bufferCap?: number; // v2: bandwidth storage, kind==='buffer'
}
export interface TechDef {
@ -65,6 +69,7 @@ export interface CommissionDef {
flavor: string; // the fax text — deadpan absurd
wants: Record<string, number>;
rewardBandwidth: number;
repeat?: boolean; // v2: standing order — re-activates after completion
}
export interface GameData {
@ -94,6 +99,8 @@ export interface BeltItem {
item: string;
entity: number; // belt entity id
t: number; // 0..1 progress along the belt tile
id?: number; // v2: stable per-item id for exact render interpolation
// (optional this round; hardens to required in v3)
}
export interface SimSnapshot {
@ -121,7 +128,8 @@ export type SimEvent =
| { kind: 'brownout'; on: boolean; tick: number }
| { kind: 'placed'; entity: number; def: string; tick: number }
| { kind: 'removed'; entity: number; def: string; tick: number }
| { kind: 'commissionDone'; commission: string; tick: number };
| { kind: 'commissionDone'; commission: string; tick: number }
| { kind: 'scram'; entity: number; on: boolean; tick: number }; // v2: heat shutdown edge
// ---------------------------------------------------------------- module interfaces
@ -144,6 +152,8 @@ export interface UIBus {
dispatch(cmd: Command): void;
/** current build selection; renderer ghost + click-to-place read this */
selectedBuild(): { def: string; dir: Dir } | null;
/** v2: world picking, plumbed from the renderer — UI's click-to-inspect */
pickTile(clientX: number, clientY: number): Vec2 | null;
}
export interface UI {

View File

@ -25,6 +25,7 @@ const bus: UIBus & { _sel: { def: string; dir: Dir } | null } = {
_sel: null,
dispatch: (cmd: Command) => sim.enqueue(cmd),
selectedBuild: () => bus._sel,
pickTile: (x, y) => renderer.pickTile(x, y),
};
// LANE-UI sets the build selection through this hook:
(window as any).__fktryBus = bus;
@ -42,6 +43,7 @@ async function boot() {
renderer.setGhost(sel?.def ?? null, renderer.pickTile(e.clientX, e.clientY), sel?.dir ?? 0);
});
game.addEventListener('pointerdown', (e) => {
if (e.button !== 0) return; // pan/context buttons never place
const sel = bus.selectedBuild();
const tile = renderer.pickTile(e.clientX, e.clientY);
if (sel && tile) sim.enqueue({ kind: 'place', def: sel.def, pos: tile, dir: sel.dir });

123
package-lock.json generated Normal file
View File

@ -0,0 +1,123 @@
{
"name": "glytch",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"happy-dom": "^20.10.6"
}
},
"node_modules/@types/node": {
"version": "26.1.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~8.3.0"
}
},
"node_modules/@types/whatwg-mimetype": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
"integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/buffer-image-size": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz",
"integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
},
"engines": {
"node": ">=4.0"
}
},
"node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/happy-dom": {
"version": "20.10.6",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.10.6.tgz",
"integrity": "sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": ">=20.0.0",
"@types/whatwg-mimetype": "^3.0.2",
"@types/ws": "^8.18.1",
"buffer-image-size": "^0.6.4",
"entities": "^7.0.1",
"whatwg-mimetype": "^3.0.0",
"ws": "^8.21.0"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/undici-types": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
"dev": true,
"license": "MIT"
},
"node_modules/whatwg-mimetype": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/ws": {
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

5
package.json Normal file
View File

@ -0,0 +1,5 @@
{
"devDependencies": {
"happy-dom": "^20.10.6"
}
}