[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>
This commit is contained in:
parent
7eda076fda
commit
83e3ad2107
@ -411,3 +411,115 @@ your win condition from "make it glitch" to "make it silent," or steal the KDM w
|
|||||||
(also you).
|
(also you).
|
||||||
- Every asset generated gets its element's codex name — this file is the manifest;
|
- Every asset generated gets its element's codex name — this file is the manifest;
|
||||||
check off against it.
|
check off against it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. THE COMPOSITE SEAM — keying & transparency artifacts
|
||||||
|
|
||||||
|
> Source study: Corridor Crew's history of compositing (Niko Pueringer) + our local
|
||||||
|
> `corridorkey-mrp-mlx` (MLX port of CorridorKey, runs in the MODELBEAST flow on the
|
||||||
|
> m3ultra). Thesis, and the section's whole physics: **you cannot film transparency.**
|
||||||
|
> Alpha does not exist in nature — every era of cinema is a different failed séance
|
||||||
|
> for summoning it. In the Bitstream this is literal: ALPHA is the one resource that
|
||||||
|
> cannot be mined from any seam. It can only be *computed*, and every computation
|
||||||
|
> before the last one lies.
|
||||||
|
|
||||||
|
**Geology:** the Composite Seam is not a stratum — it is a vertical FAULT crossing
|
||||||
|
every era layer, glittering with half-transparent fossils. Mining near it yields
|
||||||
|
subjects fused to backgrounds that were never really there.
|
||||||
|
|
||||||
|
### Resources & products
|
||||||
|
|
||||||
|
**ALPHA** — *raw transparency, the unfilmable substance.* A shimmering nothing in a
|
||||||
|
frame; the rarest resource in the game. Cannot be extracted; only keyer machines emit
|
||||||
|
it, each era's keyer emitting a corrupted grade.
|
||||||
|
`ASSET (3D + shader): a glass ingot that renders as a checkerboard-transparency hole in the world itself, faint rainbow rim, pedestal casting a shadow the ingot doesn't`
|
||||||
|
|
||||||
|
**HARD MATTE** — *reel-era binary alpha.* Silver-halide matte slabs: every pixel all
|
||||||
|
or nothing. Razor edges, zero feather; anything soft (hair, smoke, glass) that touches
|
||||||
|
one gets guillotined to a hard silhouette. Cheap, brutal, in demand for "woodcut" looks.
|
||||||
|
`ASSET (3D): stack of black-and-white film mattes like x-ray plates, silhouettes with surgically hard edges, chemical stain rims, one plate mid-slice through a wisp of smoke`
|
||||||
|
|
||||||
|
**BLUE HOLE** — *optical-printer costume dropout.* A patch of SUBJECT gone transparent
|
||||||
|
because it dared to be blue. Sold as canned absence; installing one in a product makes
|
||||||
|
part of it see-through in a way that reads as a wound.
|
||||||
|
`ASSET (3D): a mannequin torso with a clean-edged void where the chest was, world visible through it, faint blue glow licking the hole's rim`
|
||||||
|
|
||||||
|
**FRINGE LACE** — *matte lines, bottled.* The bright halo ribbon where layers of film
|
||||||
|
were re-exposed slightly misaligned. Harvested off composite edges like dried glue.
|
||||||
|
`ASSET (3D): coils of luminous ribbon, each strand a hard bright outline the exact shape of some absent object's edge, wound on an industrial spool`
|
||||||
|
|
||||||
|
**GREEN SPILL** — *chroma contamination.* Cousin fluid to CHROMA SLURRY but nastier:
|
||||||
|
it doesn't tint what it touches, it makes it PARTIALLY DISAPPEAR (the keyer downstream
|
||||||
|
eats whatever it stains). Leaks from every chroma keyer; the OSHA posters are explicit.
|
||||||
|
`ASSET (3D): dripping acid-green fluid whose puddles render as半 half-vanished floor, hazmat berm around a keyer machine, splashes eating a crate mid-vanish`
|
||||||
|
|
||||||
|
**MIXED PIXEL** — *the purple gel: an unmixable blend.* Foreground and background
|
||||||
|
fused into one pixel that belongs to neither — the red gel that filmed purple. Classic
|
||||||
|
keyers cannot separate it; the neural keyer eats it for breakfast.
|
||||||
|
`ASSET (3D): a jar of pixels each half one color half another, split diagonally, labeled RED (DISPUTED)`
|
||||||
|
|
||||||
|
**ROTO CHATTER** — *boiling edges.* Frame-inconsistent cutout outlines, jittering like
|
||||||
|
they're being redrawn 24 times a second by a tired artist — because they are. A live
|
||||||
|
hazard that infests composite edges; trapped and canned, it sells as nervous energy.
|
||||||
|
`ASSET (VFX + 3D can): an outline that never sits still, vibrating polyline crawling around a silhouette; the can has a shaking label`
|
||||||
|
|
||||||
|
**SODIUM LIGHT (589nm)** — *the grail wavelength.* Disney's sodium-vapor trick:
|
||||||
|
mathematically perfect mattes, hair and glass and gossamer intact — locked behind an
|
||||||
|
exclusive prism only one company ever owned. In the Bitstream, The Correction hoards
|
||||||
|
the prisms (perfection is their department). A single beam is an endgame reagent.
|
||||||
|
`ASSET (3D): a sealed lantern emitting one impossibly pure amber-orange line of light, wavelength etched on the housing, white-glove handling cradle, Correction seal`
|
||||||
|
|
||||||
|
**GHOST FEATHER** — *semi-transparency done RIGHT.* Hair strands, motion blur, glass —
|
||||||
|
alpha with gradients. The proof-of-mastery product; every era below the neural keyer
|
||||||
|
ruins it, so demand never dies.
|
||||||
|
`ASSET (3D): a single enormous feather fading from fully solid quill to truly transparent tip, the transition agonizingly smooth, displayed under museum glass`
|
||||||
|
|
||||||
|
### Machines
|
||||||
|
|
||||||
|
**MATTE BATH** *(reel era)* — chemical tank that binarizes anything: footage in, hard
|
||||||
|
mattes out, all softness destroyed (a GHOST FEATHER fed in comes out a HARD MATTE and
|
||||||
|
the machine does not apologize). One mistake ruins the whole reel — jams eat inventory.
|
||||||
|
`ASSET (3D): open chemical bath with film strips on rollers dipping through, developer-red darkroom lamp, dissolving softness visibly leaving the film as rising fog`
|
||||||
|
|
||||||
|
**OPTICAL PRINTER** *(the iron lung)* — a cathedral of bellows and lenses that splits
|
||||||
|
footage into R/G/B channel reels and recombines them slightly wrong. Produces
|
||||||
|
composites + FRINGE LACE always + BLUE HOLES whenever fed anything blue. Huge
|
||||||
|
footprint, gorgeous, mechanical; the codex's most machine-ass machine.
|
||||||
|
`ASSET (3D, hero): room-sized brass-and-iron optical printer, three film paths in primary-color light, bellows lungs mid-breath, a fringe of glowing misalignment haloing its output gate`
|
||||||
|
|
||||||
|
**CHROMA KEYER** *(stream era)* — digital green screen box: cheap, fast, 30-year-old
|
||||||
|
math. Emits ALPHA (consumer grade) + GREEN SPILL, chokes on hair (GHOST FEATHER input
|
||||||
|
→ ROTO CHATTER hazard spawn), turns semi-transparent inputs into MIXED PIXELS.
|
||||||
|
`ASSET (3D): studio-green cube with a subject-shaped hole punched clean through, green fluid weeping from its seams, sample swatches of failed hair taped to its side`
|
||||||
|
|
||||||
|
**ROTO BENCH** — *the grind made flesh.* Manual-labor station that converts ANY
|
||||||
|
footage to cutouts — perfectly, eventually — at an atrocious rate (24 frames of pain
|
||||||
|
per second of footage). The economic floor under every keyer: when automation fails,
|
||||||
|
the bench never does, it just costs your life. Emits ROTO CHATTER while running.
|
||||||
|
`ASSET (3D): a hunched drafting desk with a glowing frame-by-frame lightbox, mountain of finished frames on one side, coffee rings fossilized into the wood, a small flickering outline hovering over the current frame`
|
||||||
|
|
||||||
|
**NEURAL KEYER** *(endgame)* — the CorridorKey machine: trained on ten thousand
|
||||||
|
synthetic ghosts because reality couldn't teach it (you can't film the answer). Pulls
|
||||||
|
perfect ALPHA through glass, chainmail, motion-blurred hair; UNMIXES pixels (MIXED
|
||||||
|
PIXEL → original colors, recovered); no spill, no fringe, no chatter. The catch is
|
||||||
|
pure hardware: it draws 24 VRAM — a bandwidth appetite that dwarfs everything else in
|
||||||
|
the game. You don't unlock the neural keyer; you unlock the power plant it demands.
|
||||||
|
`ASSET (3D, hero): a monolithic GPU shrine, fans like cathedral rose windows, cables thick as anacondas, projecting a flawless translucent figure above itself; power meter needle buried in the red`
|
||||||
|
|
||||||
|
### Hazards
|
||||||
|
|
||||||
|
**SPILL FOG** — green contamination cloud around leaky keyers; products crossing it
|
||||||
|
gain partial transparency (defect or feature, depending on the commission).
|
||||||
|
**EDGE BOIL** — roto chatter infestation: outlines in the area start crawling;
|
||||||
|
machines misread their own footprints and mis-deliver until it's trapped.
|
||||||
|
|
||||||
|
### PYXLFK / MODELBEAST production note
|
||||||
|
|
||||||
|
`corridorkey-mrp-mlx` (gitea.partly.party/monster/corridorkey-mrp-mlx, deployed in the
|
||||||
|
MODELBEAST flow on the m3ultra) is a REAL artifact factory for this whole section:
|
||||||
|
run the same footage through (a) classic chroma-subtraction math and (b) the neural
|
||||||
|
keyer — the difference image is a pixel-perfect, ground-truth-labeled map of spill,
|
||||||
|
fringe, and mixed pixels. Degrading the alpha hint synthesizes roto chatter and hard-
|
||||||
|
matte binarization with known parameters. Every artifact above is bakeable with a
|
||||||
|
recorded recipe, which is the PYXLFK contract: corruption in, sidecar out.
|
||||||
|
|||||||
@ -55,3 +55,16 @@ data/*.json ──▶ main.ts loads GameData
|
|||||||
|
|
||||||
Written in your lane NOTES as `CONTRACT REQUEST: <what> — <why>`. The orchestrator
|
Written in your lane NOTES as `CONTRACT REQUEST: <what> — <why>`. The orchestrator
|
||||||
batches them between rounds. Until granted, work around locally inside your lane.
|
batches them between rounds. Until granted, work around locally inside your lane.
|
||||||
|
|
||||||
|
## v4 notes
|
||||||
|
|
||||||
|
- `snapshot.commissionQueue` may alias a live sim array — read-only, never retain
|
||||||
|
across frames (same rule as the snapshot itself, stated here because it looks like
|
||||||
|
a plain array).
|
||||||
|
- Selection protocol: UI writes `bus.setSelection(...)`; main.ts derives ghost mode
|
||||||
|
and dispatches `place`/`remove` on click. UI must NOT dispatch `remove` itself.
|
||||||
|
The `'__remove'` def string is a deprecated bridge, deleted in v5.
|
||||||
|
- Heat: `heatPerTick` is GROSS; net climb = `heatPerTick − coolPerTick`. A machine
|
||||||
|
whose heatPerTick ≤ its coolPerTick can never overheat — the validator enforces it.
|
||||||
|
- Research gating: ids appearing in any `TechDef.unlocks` are locked until researched;
|
||||||
|
ids referenced by no tech are always available.
|
||||||
|
|||||||
@ -37,6 +37,16 @@ codex wins.
|
|||||||
5. Commit with your lane prefix: `[sim] belt merge logic + tests`. One or a few
|
5. Commit with your lane prefix: `[sim] belt merge logic + tests`. One or a few
|
||||||
focused commits, not one mega-commit. Push.
|
focused commits, not one mega-commit. Push.
|
||||||
|
|
||||||
|
**COMMIT HYGIENE (ruled after three round-3 collisions):** five sessions share ONE
|
||||||
|
working tree and ONE git index. `git add -A`, `git add .`, and `git commit -a` are
|
||||||
|
BANNED — they commit whatever other lanes have staged at that instant. Every commit
|
||||||
|
uses an explicit pathspec so the index state of other lanes cannot leak in:
|
||||||
|
`git commit -m "[sim] ..." -- fktry/src/sim fktry/lanes/LANE-SIM.md`
|
||||||
|
(a pathspec commit ignores the staging area for everything outside the paths).
|
||||||
|
Before pushing, `git show --stat HEAD` — if it lists files outside your lane,
|
||||||
|
`git reset --soft HEAD~1` and recommit correctly. If collisions still happen,
|
||||||
|
the next escalation is a git worktree per lane.
|
||||||
|
|
||||||
## Hard rules
|
## Hard rules
|
||||||
|
|
||||||
- **Never edit**: `MASTERPLAN.md`, `CONTRACTS.md`, `src/contracts.ts`, `src/main.ts`,
|
- **Never edit**: `MASTERPLAN.md`, `CONTRACTS.md`, `src/contracts.ts`, `src/main.ts`,
|
||||||
|
|||||||
@ -25,47 +25,46 @@ REQUESTs.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CURRENT ORDERS — Round 3 (goal: heat that matters, deeper economy)
|
## CURRENT ORDERS — Round 4 (goal: THE COMPOSITE SEAM + research economy)
|
||||||
|
|
||||||
Round 2 review: the bloom-concentrate grep-before-delete and the fault-injection
|
Round 3 review: re-introducing round 2's exact bug to prove the validator catches it
|
||||||
discipline are exactly the standard. Both your cross-lane asks are RULED:
|
is the gold standard — and your "decorative number" lesson is now a contract comment
|
||||||
**(1) Units: per-second.** `gen`/`draw` are per second, `stored` is
|
AND your validator's job forever (heat ruling: gross, net = heat − cool). Verdicts:
|
||||||
bandwidth-seconds; SIM converts their integration this round — your `bufferCap: 240`
|
tsconfig FIXED (`include: ["src", "data"]` — your poisoned-file proof was conclusive).
|
||||||
= 8 seconds of a 30 deficit, as you intended. **(2) Cooling:** `coolPerTick?` granted
|
`accent?` and `coolRadius?` are in v4. SIM exports tuning constants this round —
|
||||||
in v3 (sim default 0.004); SIM wires it. Your SIM's-test-is-red flag was correct
|
retire your hand-copies. The stco/keyring recipe-assignment logic ("made by the
|
||||||
mid-round and self-resolved at HEAD (their landing fixed it) — the stash-and-rerun
|
machine that already owns that knowledge") is exactly how future inventions should
|
||||||
diagnostic was the right move. Ownership RULING: `data-validate.test.ts` moves to
|
argue.
|
||||||
`data/validate.test.ts` (git mv, yours — vitest picks it up there; `src/sim/**`
|
|
||||||
returns fully to SIM).
|
|
||||||
|
|
||||||
1. **The validator move** above, first, so the tree is clean.
|
1. **THE COMPOSITE SEAM — the codex grew §10** (docs/FKTRY_LORE.md, read it first):
|
||||||
2. **Heat retune, the headline**: today NOTHING can overheat — every `heatPerTick`
|
keying/transparency artifacts, from "you cannot film transparency". Transcribe it:
|
||||||
you authored is below the 0.004 ambient cooling, so heat is decorative. With
|
ALPHA (the unfilmable resource — only keyer machines emit it), HARD MATTE, BLUE
|
||||||
`coolPerTick` yours to set per machine: hit SIM's suggested feel — software
|
HOLE, FRINGE LACE, GREEN SPILL, MIXED PIXEL, ROTO CHATTER, GHOST FEATHER, SODIUM
|
||||||
decoder throttles after ~10s of continuous load, scrams ~13-15s, full duty cycle
|
LIGHT (589nm, Correction-hoarded reagent — forward canon until M4); machines
|
||||||
~25s; ASIC stays incapable of heat. Show the arithmetic in CANON-DELTA (per-tick
|
MATTE BATH (reel), OPTICAL PRINTER (the iron lung — make its ratios hurt), CHROMA
|
||||||
vs per-second — mind the units ruling). Give the hex-splicer a lesser fever.
|
KEYER (stream; leaks green spill, wrecks feathers into chatter), ROTO BENCH (the
|
||||||
Coordinate with SIM's reference-factory v3 via NOTES — your numbers decide
|
grind: converts anything, atrociously), NEURAL KEYER (endgame: perfect, and its
|
||||||
whether their layout survives.
|
powerDraw should be obscene — the codex says its appetite dwarfs everything;
|
||||||
3. **Buffer economy check** under the units ruling: is 240 (8s) still your intent
|
research-gate it behind a fortress-adjacent tech). Era-gate the family through
|
||||||
now that it's true? One tank should cover one decoder scram — verify against your
|
the tech tree; validator covers everything; CANON-DELTA logs deviations.
|
||||||
retuned cycle, adjust, show working.
|
2. **Research economy**: author the lab machine (`kind:'lab'` in v4 — name it in
|
||||||
4. **ASIC COOLER spec'd** in CANON-DELTA (the codex names it, §2 "vented by ASIC
|
codex voice, it's yours to canonize in CANON-DELTA) and audit all 24+ tech costs
|
||||||
coolers"): item/machine entry + intended adjacency mechanic, data authored but
|
now that they'll actually be paid: pack production chains must exist and the
|
||||||
recipes gated behind a `broadcast`-era tech node — the MECHANIC stays deferred
|
early nodes must be affordable off a first-taste-era factory. Show one worked
|
||||||
until SIM builds auras (M4); the data existing first is fine and the validator's
|
cost path (packs/minute vs research demand) in CANON-DELTA.
|
||||||
FORWARD_CANON pattern covers it.
|
3. **ASIC COOLER activation**: SIM lands `coolRadius` auras this round — give the
|
||||||
5. **Reel + broadcast era tech nodes**: research costs in analog packs; unlocks
|
cooler its radius + numbers (your spec'd adjacency intent), and make the software
|
||||||
field-loom/telecine recipe families you already authored.
|
decoder + cooler pairing the worked example (duty cycle with vs without).
|
||||||
6. **Muxer + moov brain sub-lines** (§7): `stsz-ledger`, `stco-map`, `stts-metronome`,
|
4. **Accent fills**: `accent?` where the first-output derivation is wrong — belts,
|
||||||
`stss-keyring` items + assembly recipes converging on a `moov-brain` →
|
buffer tank, uplink, lab, coolers; leave it unset where the derivation is already
|
||||||
`mux-container` endgame recipe on the muxer. This is M4's rocket — data lands
|
right (that's the design, not laziness).
|
||||||
first, mechanics later. Muxer machine entry if you don't have one.
|
5. **Fixture/validator updates**: v4 fields (`accent`, `coolRadius`), the gross-heat
|
||||||
|
rule you already enforce stays, `commissionQueue` v5-hardening — any fixtures you
|
||||||
|
own carry it.
|
||||||
|
|
||||||
**Definition of done:** validator green from its new home; a software decoder
|
**Definition of done:** Composite Seam fully transcribed and reachable through era
|
||||||
demonstrably overheats in SIM's tests using YOUR numbers (coordinate — their DoD
|
tech (validator green, FORWARD_CANON updated); a research path is affordable and
|
||||||
depends on your retune); CANON-DELTA carries the arithmetic; FORWARD_CANON pruned of
|
proven against SIM's real mechanic; cooler pairing arithmetic in CANON-DELTA.
|
||||||
anything that gained a recipe.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -25,35 +25,37 @@ you never simulate. If motion looks wrong, interpolate better or note it — don
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CURRENT ORDERS — Round 3 (goal: truth-driven states + asset intake readiness)
|
## CURRENT ORDERS — Round 4 (goal: v4 protocol, research & aura visuals)
|
||||||
|
|
||||||
Round 2 review: the corner-cargo catch and the scram-latch bug you found by READING
|
Round 3 review: the dead-debug-hook bundle grep and the provenance note were exemplary;
|
||||||
SIM'S CODE are the reasons this process works. Verdicts: your color deviation is
|
your worktree analysis drove the new MASTERPLAN commit rule (pathspec commits — your
|
||||||
**APPROVED as the ruling** — contracts v3 re-comments `MachineDef.color` as
|
option 2, with worktrees as the named escalation). Verdicts: CONTRACT REQUEST 1
|
||||||
chassis/body, accent stays derived; DATA's values stand; deviating loudly with a
|
GRANTED — v4 has the typed selection protocol (`SelectionState`, `setGhost(..., mode)`)
|
||||||
written rationale was exactly right. Req 2 GRANTED: `EntityState.scrammed` is in v3 —
|
and main.ts already passes `mode` on every call (the `'__remove'` def string still
|
||||||
SIM wires it this round. Req 3 (`BeltItem.side`) DEFERRED until merges matter.
|
arrives as a bridge; it dies in v5). `MachineDef.accent?` also landed (your round-2
|
||||||
`BeltItem.id` is now required, so your fallback path can retire whenever you like.
|
wish, UI re-filed it). Era skins verified beautiful.
|
||||||
|
|
||||||
1. **Scram from truth**: read `EntityState.scrammed`, delete your `SCRAM_RELEASE`
|
1. **Ghost migrates to `mode`**: key demolition tint off the 4th arg, ignore the
|
||||||
duplication and the desync risk it carried.
|
sentinel def. Keep rendering exactly as shipped otherwise.
|
||||||
2. **Remove-mode support**: UI ships a REMOVE interaction this round — give it a
|
2. **Locked-machine ghost** (research lands this round): a `place` for a locked id is
|
||||||
hover treatment (target machine tinted demolition-red, footprint outline) driven
|
silently dropped by the sim — the ghost must telegraph it. Distinct from
|
||||||
by a `setGhost`-style call. You own the visual language; coordinate the hook via
|
occupied-red: padlock-amber wash + "era" tell if you can read the tech era from
|
||||||
NOTES if `setGhost(null, ...)` isn't expressive enough (I'll widen the contract
|
data cheaply. Coordinate the lock lookup with UI via NOTES (both of you derive it
|
||||||
next round if you two need it).
|
from `snapshot.research.unlocked` + tech.json — same rule, CONTRACTS v4 notes).
|
||||||
3. **Showroom formalized**: promote the devscene showroom row to `?showroom` — every
|
3. **Cooler auras**: `coolRadius` machines get a floor-ring (subtle, engineering-
|
||||||
machine + every item on plinths, labeled, camera preset. This becomes the QA page
|
drawing vibe, not a buff circle) on hover/ghost AND while placed-and-selected;
|
||||||
for MODELBEAST asset drops (which land as `public/assets/models/<asset>.glb`;
|
heat-shimmer suppression inside the ring falls out of sim truth automatically.
|
||||||
your hot-swap + normalization + AnimationMixer are already ready — this is the
|
4. **Lab visuals**: placeholder with a distinct silhouette (research bench — codex
|
||||||
eyeball step).
|
voice pending DATA's machine entry); `accent?` consumed when present (your
|
||||||
4. **Era-skin groundwork**: registry hook for per-era material variants (§8 era
|
derivation stays fallback).
|
||||||
skins) — plumbing only, stream-era default, so eras can land in data without a
|
5. **Housekeeping approved from your NEXT list**: splitter lamp can stay timer-driven
|
||||||
renderer round.
|
(real round-robin state isn't in the snapshot and doesn't earn a contract field
|
||||||
|
yet); showroom item-hall paging when DATA's catalog next grows (Composite Seam
|
||||||
|
transcription lands ~15 items this round — it will).
|
||||||
|
|
||||||
**Definition of done:** scram visuals verified against SIM's real latch (no local
|
**Definition of done:** remove-tint works with the mode arg (verified with UI's
|
||||||
constants); showroom screenshotted in NOTES; remove-mode hover demonstrated with
|
migrated flow); a locked machine's ghost is legibly "not yet" rather than "broken";
|
||||||
UI's flow; perf standing rule still met.
|
cooler ring reads at a glance; showroom absorbs the new catalog.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -27,33 +27,37 @@ seed stock — port, don't move).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CURRENT ORDERS — Round 3 (goal: continuous dread + spatial artifacts)
|
## CURRENT ORDERS — Round 4 (goal: second dread axis, boredom, ratification)
|
||||||
|
|
||||||
Round 2 review: the ringing self-rejection ("sterile, but sharper — it flunked your
|
Round 3 review: measuring tremor in *seconds-until-dark* — and getting scale-
|
||||||
bar and I nearly shipped it") is the exact quality instinct these orders exist to
|
invariance for free from the units ruling — is the single most elegant thing in this
|
||||||
buy. Verdicts: CONTRACT REQUEST GRANTED — `frame(timeMs, snap?)` is in v3 and main.ts
|
codebase. The negative-dt landmine catch is exactly why the NOTES exist. Verdicts:
|
||||||
already passes the snapshot; the un-retained invariant you promised is now a contract
|
your capacity PROPOSAL is GRANTED unasked — `bandwidth.capacity?` is in v4 (SIM sums
|
||||||
comment. Scram-wipe PROPOSAL: APPROVED at 3 frames (~100ms) — you were right, one
|
`bufferCap`); your "reserve low vs falling fast" second reading is this round's order.
|
||||||
frame is a flinch, not a legend. Regional-moiré PROPOSAL: APPROVED as this round's
|
Your stalled-factory idea is APPROVED as design. The strain/fever/seizure hierarchy is
|
||||||
centerpiece.
|
canon now — keep future readings inside it.
|
||||||
|
|
||||||
1. **Signal strain** (your design, now unblocked): continuous dread from the
|
1. **Two dreads**: with `capacity` available, split the readings — "seconds left"
|
||||||
snapshot — `bandwidth.stored` draining toward zero = rising tremor/desync judder
|
(tremor, as shipped) vs "reserve fraction" (a low-tank pallor/vignette that sits
|
||||||
BEFORE the brownout seizure; aggregate entity `heat` = a slow feverish shimmer.
|
there even when draw ≤ gen and nothing is draining). A full tank draining fast vs
|
||||||
Keep it subordinate to the corruption ledger: strain is weather, shipments are
|
a near-empty tank coasting should FEEL different; write the one-line design intent
|
||||||
climate.
|
for each in NOTES.
|
||||||
2. **Regional moiré**: grow the lattice from a region instead of full-frame (the
|
2. **Boredom** (your proposal): no `shipped` events for a sustained stretch → total
|
||||||
codex has it spreading as maze walls) — spatial mask, seeded deterministically,
|
corruption eases back toward clean, slowly, visibly — the factory's work
|
||||||
creeping outward with tally. This is the template for future spatial artifacts;
|
literally evaporating off THE SCREEN. Escalation tallies stay (history isn't
|
||||||
note the mask approach for reuse.
|
erased; the *display* decays), so production resuming snaps it back fast. Tune
|
||||||
3. **Scram wipe at 3 frames**, as approved.
|
the decay horizon so a normal jam doesn't read as punishment (minutes, not
|
||||||
4. **Idle strain baseline**: with zero corruption AND healthy bandwidth, the feed
|
seconds). Test it.
|
||||||
stays perfectly sterile — verify strain can't leak into the NOTHING IS WRONG era
|
3. **Ratification interruption**: `researched` event → a one-second full-screen
|
||||||
(a test pinning params at zero under a healthy snapshot would do it).
|
interruption card on the feed — sterile white, "STANDARD RATIFIED", era name,
|
||||||
|
then the corruption crashes back in. The Correction briefly wins the sky every
|
||||||
|
time the player does science. Deadpan, readable, cheap.
|
||||||
|
4. **Fixture hygiene**: `commissionQueue` hardens to required in v5 — update your
|
||||||
|
fixture snapshots this round.
|
||||||
|
|
||||||
**Definition of done:** strain visible as stored drains (drive it by pausing the
|
**Definition of done:** both dread axes verified at the pixel level (your contact-
|
||||||
reference factory's generators or just feeding a fixture snapshot), regional moiré
|
sheet method); boredom decays and snaps back under test; ratification card exactly
|
||||||
grows rather than switches, wipe readable, budget still <2ms, tests updated.
|
N frames with era text; budget still <2ms.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -21,39 +21,45 @@ playable through unit tests. You are the lane the other four trust with their li
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CURRENT ORDERS — Round 3 (goal: M2 "PRESSURE" becomes real, M3 groundwork)
|
## CURRENT ORDERS — Round 4 (goal: M3 "DEPTH" — research, spatial heat)
|
||||||
|
|
||||||
Round 2 review: the reference factory turning round 1's deadlock into 1,136 shipped
|
Round 3 review: the worst-case power sizing ("a test that passes on a coincidence is
|
||||||
bricks is exactly what "surplus becomes product" was supposed to mean — outstanding.
|
worse than one that fails") and the counter-current bloom discovery are the two best
|
||||||
Verdicts: `coolPerTick` GRANTED (contracts v3, default 0.004 — your constant becomes
|
engineering notes anyone has filed. Verdicts: **heat semantics RULED as you asked** —
|
||||||
the fallback). `setRecipeAt` GRANTED (your tripwire, defused). `BeltItem.id` HARDENED
|
gross, net = heatPerTick − coolPerTick, now a contract comment and a DATA validator
|
||||||
to required (you asked for it). Throttle-hits-powerGen: KEPT, your reading was right.
|
rule. **Bloom loop: the escalator reading is ACCEPTED** — your wafer-bus circle IS the
|
||||||
The validator-file ownership wart: RULED — it moves to `data/validate.test.ts`, DATA
|
mechanic; the codex line stays as lore-poetry. **Git: pathspec commits are now a hard
|
||||||
executes the move, `src/sim/**` is purely yours again.
|
rule in MASTERPLAN** (your option, chosen over worktrees for now — worktrees are the
|
||||||
|
escalation if it recurs). save/load hardened in v4 as you offered. commissionQueue
|
||||||
|
aliasing documented in CONTRACTS. Your perf-noise honesty is noted — a headless
|
||||||
|
benchmark is a good M4 item, not now.
|
||||||
|
|
||||||
**UNITS RULING (DATA's cross-lane ask):** `gen`/`draw` are bandwidth per SECOND;
|
1. **Research v1 — the round's centerpiece.** Contracts v4: `kind:'lab'`,
|
||||||
`stored` is bandwidth-seconds. Your integration currently charges/drains per tick —
|
`{kind:'setResearch'}`, `snapshot.research` (`ResearchState`), `researched` event.
|
||||||
divide by `TICKS_PER_SECOND` so a 30-deficit drains 30 stored per second, not per
|
Labs consume science packs from their input toward the active tech's `cost`;
|
||||||
tick. DATA's `bufferCap: 240` then covers 8s as they intended. Update tests; confirm
|
totals met → `researched`, unlocks apply. Gating rule (in CONTRACTS v4 notes): ids
|
||||||
the conversion in NOTES so DATA can retune against truth.
|
appearing in any `TechDef.unlocks` are locked — `place`/`setRecipe`/`setRecipeAt`
|
||||||
|
for locked ids are silently dropped; everything unreferenced is free. Multiple
|
||||||
|
labs stack. save/load carries research. Tests incl. a full unlock path against
|
||||||
|
DATA's real tech.json.
|
||||||
|
2. **Spatial cooling** — `coolRadius` (v4): a machine with it applies its
|
||||||
|
`coolPerTick` to machines within chebyshev range of its footprint (stacking = sum,
|
||||||
|
cap total cooling at your call, note it). The ASIC COOLER finally means something;
|
||||||
|
DATA activates numbers in coordination.
|
||||||
|
3. **Export your tuning constants** (`src/sim/constants.ts`: throttle/restart
|
||||||
|
thresholds, default cooling, etc.) — DATA's tests currently hand-copy them and go
|
||||||
|
stale silently; let them import truth.
|
||||||
|
4. **Reference factory v4**: add an anchor-slab overflow tap → uplink so the demo
|
||||||
|
ships slabs (UI's SANITISING + the buyback fiction become visible in `__fktryDemo`
|
||||||
|
without surgery — they had to demolish your assembler to see it). Keep the 20k
|
||||||
|
regression green; a lab + a research run in the reference build would be ideal
|
||||||
|
but is optional if layout pressure fights you.
|
||||||
|
5. **Fixture hygiene**: `commissionQueue` hardens to required in v5 — make sure your
|
||||||
|
snapshot literal always carries it (you already publish it live).
|
||||||
|
|
||||||
1. **Contracts v3 wiring**: `EntityState.scrammed` (RENDER is currently duplicating
|
**Definition of done:** research provable end-to-end in tests (place lab → feed packs
|
||||||
your 0.5 constant to avoid rendering a lie — kill that), `commissionQueue` on the
|
→ unlock → previously-dropped place now succeeds); cooler aura demonstrably extends a
|
||||||
snapshot (active first, then upcoming — UI's NEXT IN TRAY is guessing today),
|
decoder's duty cycle; constants exported; reference factory ships slabs.
|
||||||
`coolPerTick` per machine, `setRecipeAt` command, the units conversion above.
|
|
||||||
2. **Save/load**: implement `save()`/`load()` — JSON, deterministic round-trip
|
|
||||||
(test: save at tick N, load into a fresh sim, run both 1,000 ticks, deep-equal).
|
|
||||||
3. **Reference factory v3**: heat is about to become real (DATA retunes this round —
|
|
||||||
coordinate via NOTES). Restructure power as ASIC base + software-decoder burst +
|
|
||||||
buffer tank so the layout *survives scram duty-cycles* — DATA warned your three
|
|
||||||
naked decoders will black out in lockstep. Keep the 20k regression green.
|
|
||||||
4. **Bloom loop proof**: test that a closed belt circle through a bloom-duplicator
|
|
||||||
accumulates grades (your rank-0 loop acceptance + DATA's grade recipes should
|
|
||||||
compose). This is M3's signature topology — prove it early.
|
|
||||||
|
|
||||||
**Definition of done:** all tests green including save/load round-trip and the bloom
|
|
||||||
loop; NOTES confirm the units conversion + the retuned reference factory's scram
|
|
||||||
survival (ticks of first scram, recovery, melt rate before/after).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -24,39 +24,43 @@ dispatch Commands via the bus and read state from `update(snap, events)`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CURRENT ORDERS — Round 3 (goal: close the interaction loop, verify the pressure UI live)
|
## CURRENT ORDERS — Round 4 (goal: research UI, one real bug, protocol migration)
|
||||||
|
|
||||||
Round 2 review: 57 tests against a 10 minimum, and factoring pure modules instead of
|
Round 3 review: `live.test.ts` — booting the real sim against the real DOM — is the
|
||||||
touching root config when my grant turned out to be vapor — model behavior. About
|
best verification asset any lane has produced; the SANITISING machine-swap showed
|
||||||
that: **happy-dom genuinely is installed now** (verified: `package.json` line 17, and
|
exactly the right instinct when an order's premise fails. Verdicts: CONTRACT REQUEST 1
|
||||||
your 57 tests pass under `--environment happy-dom`). My round-2 "it's installed" was
|
GRANTED (v4 `SelectionState` + `setSelection`/`selection` on the bus — main.ts now owns
|
||||||
wrong and your BLOCKED section caught it — good catch, keep doing that.
|
the remove dispatch; see order 2). Request 2 GRANTED (`MachineDef.accent?` — your
|
||||||
Verdicts: CONTRACT REQUEST 1 GRANTED — `SimSnapshot.commissionQueue` in v3, SIM wires
|
derivation becomes the documented fallback, DATA fills the field where it's wrong).
|
||||||
it this round. Request 2 was already landing as you wrote it (parallel-round race;
|
`?uidemo` PROPOSAL APPROVED. Tech-panel skip was right at the time — but it was
|
||||||
the queue advances at HEAD — I watched PATIO RESURFACING arrive live). REMOVE
|
another round-start grep going stale: `tech.json` has 24 nodes at HEAD (DATA landed
|
||||||
PROPOSAL: APPROVED.
|
mid-round again). It's a real order now.
|
||||||
|
|
||||||
1. **REMOVE**: your design — suggested shape: X hotkey / bar button arms remove mode,
|
1. **BUG, found live in review: the BROWNOUT banner lies.** `topstrip.ts:66` keys on
|
||||||
hover shows RENDER's demolition tint (they're building it this round — coordinate
|
`out || ratio > 1` — during a covered deficit (tanks draining, sim `brownout:
|
||||||
via NOTES), click dispatches `remove`, Esc disarms. Toast in house voice
|
false`, THE SCREEN correctly calm) the panel screams BROWNOUT. That state is
|
||||||
("UNIT RECLAIMED. THE FLOOR REMEMBERS.").
|
*survival*, not failure. Fix: BROWNOUT = the flag only; deficit-with-reserve gets
|
||||||
2. **NEXT IN TRAY becomes truth**: read `snap.commissionQueue` when present, delete
|
its own amber state (suggest "ON RESERVE · 34s" — `stored/(draw-gen)` is seconds
|
||||||
`peekNext` inference the moment SIM lands it (keep fallback until then).
|
left, same math SCREEN uses for dread). Add a test pinning covered-deficit ≠
|
||||||
3. **DOM render tests**: the two you named as needing a real DOM (chip rendering,
|
brownout.
|
||||||
`attach()` binding) plus fax stamp lifecycle — `// @vitest-environment happy-dom`.
|
2. **Selection protocol migration**: write via `bus.setSelection(...)`, drop the
|
||||||
4. **Live pressure verification** (now unblocked): `window.__fktryDemo()` builds
|
`REMOVE_DEF` sentinel export and — important — **delete your own `remove`
|
||||||
SIM's reference factory in one call — use it. Verify with your own eyes and
|
dispatch**: main.ts dispatches it on click now (double-fire today is harmless
|
||||||
screenshot in NOTES: heat bar + THROTTLING + SCRAM on the software decoders once
|
only because the second one no-ops).
|
||||||
DATA's retune lands, SANITISING chip on real anchor shipments, STANDING ORDER
|
3. **Research UI, the centerpiece**: `T` opens the tech tree (24 nodes, 5 eras),
|
||||||
stamp on a real repeat completion (I verified the stamp renders — you verify the
|
pick → `setResearch`; progress from `snapshot.research` (packs delivered vs
|
||||||
full lifecycle).
|
cost); locked machines in the build bar get greyed + padlock + "REQUIRES:
|
||||||
5. **Stretch, skippable**: read-only tech panel (`T`) rendering `tech.json` as an era
|
<tech>" tooltip (lock rule in CONTRACTS v4 notes); `researched` event → toast in
|
||||||
tree marked RESEARCH OFFLINE — research mechanics land in M3, the panel can exist
|
house voice ("STANDARD RATIFIED. NEW CRIMES AVAILABLE.") and the padlocks fall
|
||||||
first.
|
off live.
|
||||||
|
4. **`?uidemo`** — as approved: boots `__fktryDemo()` after init.
|
||||||
|
5. **Accent adoption**: read `accent?` first, your derivation second, kind table
|
||||||
|
third (same precedence as RENDER — one sentence in NOTES confirming you both
|
||||||
|
read it identically).
|
||||||
|
|
||||||
**Definition of done:** a misplaced machine is mouse-removable; queue peek reads
|
**Definition of done:** banner distinguishes RESERVE from BROWNOUT under test; a
|
||||||
truth; the three DOM tests exist; NOTES carry screenshots of heat/scram/SANITISING
|
full research run is verifiable in `live.test.ts` (lock visible → research → unlock
|
||||||
observed live.
|
→ place succeeds); remove mode works through the typed protocol with RENDER's tint.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,8 @@ export type MachineKind =
|
|||||||
| 'splitter' // 1 in, N out round-robin
|
| 'splitter' // 1 in, N out round-robin
|
||||||
| 'power' // generates bandwidth
|
| 'power' // generates bandwidth
|
||||||
| 'buffer' // stores bandwidth smoothness (brownout protection)
|
| 'buffer' // stores bandwidth smoothness (brownout protection)
|
||||||
| 'shipper'; // consumes items => 'shipped' events => THE SCREEN
|
| 'shipper' // consumes items => 'shipped' events => THE SCREEN
|
||||||
|
| 'lab'; // v4: consumes science packs toward the active research
|
||||||
|
|
||||||
export interface MachineDef {
|
export interface MachineDef {
|
||||||
id: string;
|
id: string;
|
||||||
@ -54,9 +55,15 @@ export interface MachineDef {
|
|||||||
color?: string; // v3 RULING: CHASSIS/BODY color (codex §8 grime); the emissive
|
color?: string; // v3 RULING: CHASSIS/BODY color (codex §8 grime); the emissive
|
||||||
// accent stays derived from outputs. (Was mislabeled "accent".)
|
// accent stays derived from outputs. (Was mislabeled "accent".)
|
||||||
flavor?: string; // v2: codex one-liner for the inspector (DATA's material)
|
flavor?: string; // v2: codex one-liner for the inspector (DATA's material)
|
||||||
heatPerTick?: number; // v2: heat while active (the software decoder's whole bit)
|
heatPerTick?: number; // v2: heat while active. v4 RULING: this is GROSS heat — net
|
||||||
|
// climb = heatPerTick − coolPerTick; a machine whose
|
||||||
|
// heatPerTick ≤ its coolPerTick can never overheat.
|
||||||
bufferCap?: number; // v2: bandwidth storage in bandwidth-SECONDS (see SimSnapshot)
|
bufferCap?: number; // v2: bandwidth storage in bandwidth-SECONDS (see SimSnapshot)
|
||||||
coolPerTick?: number; // v3: per-machine cooling rate; sim default 0.004
|
coolPerTick?: number; // v3: per-machine cooling rate; sim default 0.004
|
||||||
|
accent?: string; // v4: art-directed emissive accent; fallback stays derived
|
||||||
|
// from first output item (renderer + UI share the derivation)
|
||||||
|
coolRadius?: number; // v4: spatial cooling aura in tiles (chebyshev from footprint);
|
||||||
|
// this machine's coolPerTick also applies to machines in range
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TechDef {
|
export interface TechDef {
|
||||||
@ -113,20 +120,32 @@ export interface SimSnapshot {
|
|||||||
beltItems: ReadonlyArray<BeltItem>;
|
beltItems: ReadonlyArray<BeltItem>;
|
||||||
// v3 RULING on units: gen/draw are bandwidth per SECOND; stored is bandwidth-seconds
|
// v3 RULING on units: gen/draw are bandwidth per SECOND; stored is bandwidth-seconds
|
||||||
// (sim integrates surplus/deficit divided by TICKS_PER_SECOND each tick).
|
// (sim integrates surplus/deficit divided by TICKS_PER_SECOND each tick).
|
||||||
bandwidth: { gen: number; draw: number; stored: number; brownout: boolean };
|
// v4: capacity = summed bufferCap of placed tanks (0 if none) — SCREEN's 2nd dread axis.
|
||||||
|
bandwidth: { gen: number; draw: number; stored: number; capacity?: number; brownout: boolean };
|
||||||
shippedTotal: Record<string, number>;
|
shippedTotal: Record<string, number>;
|
||||||
activeCommission: string | null;
|
activeCommission: string | null;
|
||||||
commissionQueue?: string[]; // v3: truth for "next in tray" — active first, then
|
commissionQueue?: string[]; // v3: active first, then upcoming in activation
|
||||||
// upcoming in activation order (hardens in v4)
|
// order. May alias a live array — read-only, never
|
||||||
|
// retain. HARDENS IN v5: update fixtures this round.
|
||||||
commissionProgress: Record<string, number>;
|
commissionProgress: Record<string, number>;
|
||||||
|
research?: ResearchState; // v4: research progress (hardens in v5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResearchState {
|
||||||
|
active: string | null; // TechDef.id being researched
|
||||||
|
progress: Record<string, number>; // packs delivered toward active (itemId -> n)
|
||||||
|
unlocked: string[]; // completed TechDef.ids
|
||||||
|
}
|
||||||
|
// v4 gating rule: a machine/recipe id that appears in ANY TechDef.unlocks is locked
|
||||||
|
// until that tech completes; ids referenced by no tech are always available.
|
||||||
|
|
||||||
export type Command =
|
export type Command =
|
||||||
| { kind: 'place'; def: string; pos: Vec2; dir: Dir }
|
| { kind: 'place'; def: string; pos: Vec2; dir: Dir }
|
||||||
| { kind: 'remove'; pos: Vec2 }
|
| { kind: 'remove'; pos: Vec2 }
|
||||||
| { kind: 'rotate'; pos: Vec2 }
|
| { kind: 'rotate'; pos: Vec2 }
|
||||||
| { kind: 'setRecipe'; entity: number; recipe: string | null }
|
| { kind: 'setRecipe'; entity: number; recipe: string | null }
|
||||||
| { kind: 'setRecipeAt'; pos: Vec2; recipe: string | null } // v3: id-free variant
|
| { kind: 'setRecipeAt'; pos: Vec2; recipe: string | null } // v3: id-free variant
|
||||||
|
| { kind: 'setResearch'; tech: string | null } // v4: pick the lab target
|
||||||
| { kind: 'setPaused'; paused: boolean };
|
| { kind: 'setPaused'; paused: boolean };
|
||||||
|
|
||||||
export type SimEvent =
|
export type SimEvent =
|
||||||
@ -137,7 +156,8 @@ export type SimEvent =
|
|||||||
| { kind: 'placed'; entity: number; def: string; tick: number }
|
| { kind: 'placed'; entity: number; def: string; tick: number }
|
||||||
| { kind: 'removed'; 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
|
| { kind: 'scram'; entity: number; on: boolean; tick: number } // v2: heat shutdown edge
|
||||||
|
| { kind: 'researched'; tech: string; tick: number }; // v4: research complete
|
||||||
|
|
||||||
// ---------------------------------------------------------------- module interfaces
|
// ---------------------------------------------------------------- module interfaces
|
||||||
|
|
||||||
@ -147,23 +167,38 @@ export interface Sim {
|
|||||||
tick(): void; // advance exactly one tick
|
tick(): void; // advance exactly one tick
|
||||||
snapshot(): SimSnapshot; // read-only view; renderer/ui must not mutate
|
snapshot(): SimSnapshot; // read-only view; renderer/ui must not mutate
|
||||||
drainEvents(): SimEvent[]; // events since last drain (main loop fans out)
|
drainEvents(): SimEvent[]; // events since last drain (main loop fans out)
|
||||||
save?(): string; // v3: full state as JSON, deterministic round-trip
|
save(): string; // v4: HARDENED — full state, deterministic round-trip
|
||||||
load?(json: string): void; // v3: restore a save() blob (hardens in v4)
|
load(json: string): void; // v4: HARDENED — restore a save() blob
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** v4: the typed selection protocol — kills the '__remove' magic-string sentinel. */
|
||||||
|
export type SelectionState =
|
||||||
|
| { mode: 'build'; def: string; dir: Dir }
|
||||||
|
| { mode: 'remove' }
|
||||||
|
| null;
|
||||||
|
|
||||||
export interface Renderer {
|
export interface Renderer {
|
||||||
init(container: HTMLElement, data: GameData): Promise<void>;
|
init(container: HTMLElement, data: GameData): Promise<void>;
|
||||||
render(snap: SimSnapshot, alpha: number): void; // alpha: 0..1 interp between ticks
|
render(snap: SimSnapshot, alpha: number): void; // alpha: 0..1 interp between ticks
|
||||||
pickTile(clientX: number, clientY: number): Vec2 | null;
|
pickTile(clientX: number, clientY: number): Vec2 | null;
|
||||||
setGhost(def: string | null, pos: Vec2 | null, dir: Dir): void;
|
/** v4: mode drives the ghost visual — 'build' = placement preview, 'remove' =
|
||||||
|
* demolition tint. def is null in remove mode. The '__remove' sentinel is deprecated
|
||||||
|
* and main.ts stops sending it in v5. */
|
||||||
|
setGhost(def: string | null, pos: Vec2 | null, dir: Dir, mode?: 'build' | 'remove'): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UIBus {
|
export interface UIBus {
|
||||||
dispatch(cmd: Command): void;
|
dispatch(cmd: Command): void;
|
||||||
/** current build selection; renderer ghost + click-to-place read this */
|
/** current build selection; renderer ghost + click-to-place read this.
|
||||||
|
* DEPRECATED in v4 (still served): use selection()/setSelection(). */
|
||||||
selectedBuild(): { def: string; dir: Dir } | null;
|
selectedBuild(): { def: string; dir: Dir } | null;
|
||||||
/** v2: world picking, plumbed from the renderer — UI's click-to-inspect */
|
/** v2: world picking, plumbed from the renderer — UI's click-to-inspect */
|
||||||
pickTile(clientX: number, clientY: number): Vec2 | null;
|
pickTile(clientX: number, clientY: number): Vec2 | null;
|
||||||
|
/** v4: typed selection. main.ts owns the state; UI writes via setSelection.
|
||||||
|
* In remove mode main.ts dispatches the `remove` command on click — the UI must
|
||||||
|
* NOT also dispatch it (that was the round-3 double-fire). */
|
||||||
|
selection?(): SelectionState;
|
||||||
|
setSelection?(sel: SelectionState): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UI {
|
export interface UI {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* FKTRY main loop — ORCHESTRATOR-OWNED wiring. Lanes do not edit.
|
* FKTRY main loop — ORCHESTRATOR-OWNED wiring. Lanes do not edit.
|
||||||
* Fixed-timestep sim (30 tps) + interpolated render + event fan-out.
|
* Fixed-timestep sim (30 tps) + interpolated render + event fan-out.
|
||||||
*/
|
*/
|
||||||
import { TICKS_PER_SECOND, type Command, type Dir, type GameData, type UIBus } from './contracts';
|
import { TICKS_PER_SECOND, type Command, type Dir, type GameData, type SelectionState, type UIBus } from './contracts';
|
||||||
import { createSim } from './sim';
|
import { createSim } from './sim';
|
||||||
import { createRenderer } from './render';
|
import { createRenderer } from './render';
|
||||||
import { createUI } from './ui';
|
import { createUI } from './ui';
|
||||||
@ -21,11 +21,24 @@ const renderer = createRenderer();
|
|||||||
const ui = createUI();
|
const ui = createUI();
|
||||||
const screen = createScreenFX();
|
const screen = createScreenFX();
|
||||||
|
|
||||||
|
// v4 bridge: _sel keeps the legacy {def,dir} shape (including the deprecated
|
||||||
|
// '__remove' sentinel) so unmigrated lanes keep working; selection()/setSelection()
|
||||||
|
// are the typed protocol. The sentinel path is deleted in v5.
|
||||||
const bus: UIBus & { _sel: { def: string; dir: Dir } | null } = {
|
const bus: UIBus & { _sel: { def: string; dir: Dir } | null } = {
|
||||||
_sel: null,
|
_sel: null,
|
||||||
dispatch: (cmd: Command) => sim.enqueue(cmd),
|
dispatch: (cmd: Command) => sim.enqueue(cmd),
|
||||||
selectedBuild: () => bus._sel,
|
selectedBuild: () => bus._sel,
|
||||||
pickTile: (x, y) => renderer.pickTile(x, y),
|
pickTile: (x, y) => renderer.pickTile(x, y),
|
||||||
|
selection: (): SelectionState => {
|
||||||
|
if (!bus._sel) return null;
|
||||||
|
if (bus._sel.def === '__remove') return { mode: 'remove' };
|
||||||
|
return { mode: 'build', def: bus._sel.def, dir: bus._sel.dir };
|
||||||
|
},
|
||||||
|
setSelection: (sel: SelectionState) => {
|
||||||
|
bus._sel = sel === null ? null
|
||||||
|
: sel.mode === 'remove' ? { def: '__remove', dir: 0 as Dir }
|
||||||
|
: { def: sel.def, dir: sel.dir };
|
||||||
|
},
|
||||||
};
|
};
|
||||||
// LANE-UI sets the build selection through this hook:
|
// LANE-UI sets the build selection through this hook:
|
||||||
(window as any).__fktryBus = bus;
|
(window as any).__fktryBus = bus;
|
||||||
@ -39,14 +52,22 @@ async function boot() {
|
|||||||
// click-to-place / hover ghost glue
|
// click-to-place / hover ghost glue
|
||||||
const game = document.getElementById('game')!;
|
const game = document.getElementById('game')!;
|
||||||
game.addEventListener('pointermove', (e) => {
|
game.addEventListener('pointermove', (e) => {
|
||||||
const sel = bus.selectedBuild();
|
const sel = bus.selection!();
|
||||||
renderer.setGhost(sel?.def ?? null, renderer.pickTile(e.clientX, e.clientY), sel?.dir ?? 0);
|
const tile = renderer.pickTile(e.clientX, e.clientY);
|
||||||
|
if (sel?.mode === 'remove') {
|
||||||
|
// legacy def string kept alongside the mode until renderers migrate (v5 removes it)
|
||||||
|
renderer.setGhost('__remove', tile, 0, 'remove');
|
||||||
|
} else {
|
||||||
|
renderer.setGhost(sel?.def ?? null, tile, sel?.dir ?? 0, 'build');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
game.addEventListener('pointerdown', (e) => {
|
game.addEventListener('pointerdown', (e) => {
|
||||||
if (e.button !== 0) return; // pan/context buttons never place
|
if (e.button !== 0) return; // pan/context buttons never place
|
||||||
const sel = bus.selectedBuild();
|
const sel = bus.selection!();
|
||||||
const tile = renderer.pickTile(e.clientX, e.clientY);
|
const tile = renderer.pickTile(e.clientX, e.clientY);
|
||||||
if (sel && tile) sim.enqueue({ kind: 'place', def: sel.def, pos: tile, dir: sel.dir });
|
if (!sel || !tile) return;
|
||||||
|
if (sel.mode === 'remove') sim.enqueue({ kind: 'remove', pos: tile });
|
||||||
|
else sim.enqueue({ kind: 'place', def: sel.def, pos: tile, dir: sel.dir });
|
||||||
});
|
});
|
||||||
|
|
||||||
const MS_PER_TICK = 1000 / TICKS_PER_SECOND;
|
const MS_PER_TICK = 1000 / TICKS_PER_SECOND;
|
||||||
|
|||||||
@ -51,6 +51,7 @@ export const BODY_BY_KIND: Record<MachineKind, number> = {
|
|||||||
power: 0x45434f,
|
power: 0x45434f,
|
||||||
buffer: 0x3a3f48,
|
buffer: 0x3a3f48,
|
||||||
shipper: 0x4a4652,
|
shipper: 0x4a4652,
|
||||||
|
lab: 0x3d3a4e, // research bench chassis (v4 kind; placeholder until art-directed)
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Fallback accent when a machine runs no recipes (belts, power, shippers). */
|
/** Fallback accent when a machine runs no recipes (belts, power, shippers). */
|
||||||
@ -62,6 +63,7 @@ export const ACCENT_BY_KIND: Record<MachineKind, number> = {
|
|||||||
power: 0xffb03f, // serene LED face
|
power: 0xffb03f, // serene LED face
|
||||||
buffer: 0x7fffd4,
|
buffer: 0x7fffd4,
|
||||||
shipper: 0x3fd4ff, // uplink to THE SCREEN
|
shipper: 0x3fd4ff, // uplink to THE SCREEN
|
||||||
|
lab: 0xb0a0ff, // research glow (v4 kind; placeholder)
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Body height in world units. Silhouette differentiation on a flat iso grid. */
|
/** Body height in world units. Silhouette differentiation on a flat iso grid. */
|
||||||
@ -73,6 +75,7 @@ export const HEIGHT_BY_KIND: Record<MachineKind, number> = {
|
|||||||
power: 0.95,
|
power: 0.95,
|
||||||
buffer: 0.7,
|
buffer: 0.7,
|
||||||
shipper: 1.3,
|
shipper: 1.3,
|
||||||
|
lab: 1.1, // v4 kind; placeholder
|
||||||
};
|
};
|
||||||
|
|
||||||
export const JAM_AMBER = 0xffa726;
|
export const JAM_AMBER = 0xffa726;
|
||||||
|
|||||||
@ -23,6 +23,7 @@ const GROUP_OF: Record<MachineKind, string> = {
|
|||||||
crafter: 'REFINE',
|
crafter: 'REFINE',
|
||||||
power: 'POWER',
|
power: 'POWER',
|
||||||
shipper: 'SHIP',
|
shipper: 'SHIP',
|
||||||
|
lab: 'RESEARCH', // v4 kind
|
||||||
};
|
};
|
||||||
|
|
||||||
const GROUP_ORDER = ['EXTRACT', 'LOGISTICS', 'REFINE', 'POWER', 'SHIP'];
|
const GROUP_ORDER = ['EXTRACT', 'LOGISTICS', 'REFINE', 'POWER', 'SHIP'];
|
||||||
|
|||||||
@ -38,6 +38,7 @@ const ACCENT_BY_KIND: Record<MachineKind, string> = {
|
|||||||
power: '#3fffe0',
|
power: '#3fffe0',
|
||||||
buffer: '#7fb0ff',
|
buffer: '#7fb0ff',
|
||||||
shipper: '#d8ffd8',
|
shipper: '#d8ffd8',
|
||||||
|
lab: '#b0a0ff', // v4 kind; placeholder
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Fallback chassis for machines DATA hasn't art-directed yet. */
|
/** Fallback chassis for machines DATA hasn't art-directed yet. */
|
||||||
|
|||||||
@ -11,5 +11,5 @@
|
|||||||
"lib": ["ES2022", "DOM"],
|
"lib": ["ES2022", "DOM"],
|
||||||
"types": ["vite/client"]
|
"types": ["vite/client"]
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src", "data"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user