CONTRACTS v9 notes: 'sealed' jam reason documented; SIM's firewalls-catch-only- what-walks departure RATIFIED (UI measured the alternative as rung 3 not existing); unitCleared queued as round 8's first contract item. main.ts DEV block now exposes window.__fktrySim so testkit verbs can drive an in-browser review the way lane tests drive headless ones - used today to verify the full Phase 1 loop live: band 4, three simultaneous notices, gunship tether + real mirrored theft (sky placard + ledger + ticker + toast), auditor SECTOR LOCK with hash countdown, THE SCREEN held at HOLD with zero feed-time cost. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
96 lines
4.8 KiB
Markdown
96 lines
4.8 KiB
Markdown
# CONTRACTS — how the lanes fit together (ORCHESTRATOR-OWNED)
|
||
|
||
The binding type definitions live in [`src/contracts.ts`](src/contracts.ts) — that file
|
||
is the law. This doc explains the shape of the machine in prose.
|
||
|
||
## Data flow
|
||
|
||
```
|
||
data/*.json ──▶ main.ts loads GameData
|
||
│
|
||
┌───────────┼─────────────────────────┐
|
||
▼ ▼ ▼
|
||
sim.init() renderer.init() ui.init() / screen.init()
|
||
│
|
||
30 tps fixed timestep: sim.tick()
|
||
│
|
||
├─▶ sim.snapshot() ──▶ renderer.render(snap, alpha) (every rAF)
|
||
│ ──▶ ui.update(snap, events)
|
||
└─▶ sim.drainEvents() ─▶ screen.onEvents(events)
|
||
|
||
user input: UI builds Commands ─▶ bus.dispatch ─▶ sim.enqueue
|
||
renderer supplies pickTile + ghost preview
|
||
```
|
||
|
||
## Ownership map
|
||
|
||
| Path | Owner | Everyone else |
|
||
|---|---|---|
|
||
| `src/contracts.ts`, `src/main.ts`, root config, this doc, `MASTERPLAN.md` | orchestrator | read-only |
|
||
| `src/sim/**` | LANE-SIM | read-only |
|
||
| `src/render/**`, `public/assets/**` (consumption) | LANE-RENDER | read-only |
|
||
| `src/ui/**` | LANE-UI | read-only |
|
||
| `src/screen/**` | LANE-SCREEN | read-only |
|
||
| `data/**` | LANE-DATA | read-only |
|
||
| `lanes/LANE-X.md` NOTES section | lane X | orchestrator writes ORDERS section |
|
||
| `../docs/FKTRY_LORE.md` | canon | read-only for all lanes |
|
||
|
||
## Key invariants
|
||
|
||
- **Snapshot is read-only.** Renderer/UI/Screen never mutate it. Sim may reuse
|
||
internal buffers between snapshots; consumers must not hold references across frames.
|
||
- **All mutation goes through Commands.** No lane reaches into sim internals.
|
||
- **Events are drained once per frame by main.ts** and fanned out; consumers get the
|
||
same array — treat it as immutable.
|
||
- **IDs are data-driven.** Renderer/UI/Screen never hard-code item/machine ids; they
|
||
key off `GameData` and the `asset` field. New content should light up with zero code.
|
||
- **`alpha`** in `renderer.render(snap, alpha)` is the 0..1 fraction between the last
|
||
sim tick and the next — use it to interpolate belt item positions for smooth motion.
|
||
- **Belt visual speed** derives from `MachineDef.beltSpeed` and `BeltItem.t`; sim owns
|
||
the truth, renderer owns the smoothness.
|
||
- **Coordinates**: tile grid, origin at world center, +x east, +y south. `Dir` 0..3 =
|
||
N,E,S,W clockwise. Machine `pos` = origin corner (min x, min y of footprint).
|
||
|
||
## Contract-change requests
|
||
|
||
Written in your lane NOTES as `CONTRACT REQUEST: <what> — <why>`. The orchestrator
|
||
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.
|
||
|
||
## v6 notes
|
||
|
||
- **THE SPEAKER**: `AudioFX` (src/audio/**, LANE-SCREEN) — all sound is Web Audio
|
||
synthesis, no asset files. main.ts wires init/unlock/onEvents/frame. Pre-discovery
|
||
only the diegetic minimum plays; the band unlocks on `relicFound`.
|
||
- **Sanctioned narrow imports** (read-only, documented at the export site):
|
||
`src/sim/constants.ts` (tuning truth) and `src/audio`'s `radio` control (UI's
|
||
tuner dock). Everything else stays contract-mediated.
|
||
- **Relics are secrets.** `snapshot.relics` is always present, but nothing may
|
||
signpost an unfound relic: RENDER draws the subtle shimmer, UI shows nothing until
|
||
`found`. Discoverability budget: wandering + curiosity. That's the design.
|
||
- New jam reason string: `'no seam'` (extractor off-seam).
|
||
|
||
## v9 notes
|
||
|
||
- Jam reason `'sealed'` joins `'no seam'` in the sim's vocabulary: a warden-sealed
|
||
machine stalls with it until the `pry` command clears the seal. UI translates it
|
||
as a document, not a fault.
|
||
- RULING (round 7, ratifying SIM's departure): firewalls catch only what WALKS.
|
||
Gunships are airborne and immune — UI measured rung 3 being deleted entirely
|
||
otherwise. Pinned by SIM's regression test (a gunship must reach tether and emit
|
||
`mirrored` in the shipped demo).
|
||
- Round-8 backlog, first item: a `unitCleared` event — the firewall's victory
|
||
currently fires nine times in the demo and is silent to SCREEN/audio/RENDER.
|