docs/MANUAL.md: dev manual — tool map, asset + site cookbooks, earned gotchas
This commit is contained in:
parent
03c6cd1c09
commit
f13aad6411
@ -19,6 +19,7 @@ No pip, no npm, no build. Python 3.8+ and a browser.
|
|||||||
|
|
||||||
1. `DESIGN.md` — the design canon (what the game is)
|
1. `DESIGN.md` — the design canon (what the game is)
|
||||||
2. `PLAN3D.md` — the build canon (stack, contracts, lane model)
|
2. `PLAN3D.md` — the build canon (stack, contracts, lane model)
|
||||||
|
· `docs/MANUAL.md` — the HOW: tool map, asset/site cookbooks, earned gotchas
|
||||||
3. `THREADS.md` — the append-only lane log. **The project's memory.** Read the
|
3. `THREADS.md` — the append-only lane log. **The project's memory.** Read the
|
||||||
last few `[I]` integrator entries to know the current state.
|
last few `[I]` integrator entries to know the current state.
|
||||||
4. `SPRINT12.md` — the current sprint (highest-numbered SPRINTn.md wins)
|
4. `SPRINT12.md` — the current sprint (highest-numbered SPRINTn.md wins)
|
||||||
|
|||||||
150
docs/MANUAL.md
Normal file
150
docs/MANUAL.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# HARD YARDS — dev manual & cookbooks
|
||||||
|
|
||||||
|
*Written at Sprint 13 integration, distilled from THREADS. The canon docs
|
||||||
|
(DESIGN.md, PLAN3D.md) say what the game IS; this says HOW to work on it.
|
||||||
|
When this file and a lane's THREADS entry disagree, THREADS wins — fix this
|
||||||
|
file.*
|
||||||
|
|
||||||
|
## The tool map — what answers what
|
||||||
|
|
||||||
|
| question | tool | notes |
|
||||||
|
|---|---|---|
|
||||||
|
| does everything still work? | `web/world/selftest.html` | must be N/0/0; the count is the per-lane sum |
|
||||||
|
| is this yard rig-able by mouse? | `web/world/dev_rigging.html` | N switches yards; `__laneB.switchSite(i)` |
|
||||||
|
| what does the sky/storm look like at t=X? | `web/world/dev_skyfx.html` | scrubbable clock; flies debris; warning: it aims you SOUTH |
|
||||||
|
| player anims/movement | `web/world/dev_player.html` | |
|
||||||
|
| is this site winnable, at what price? | `tools/site_audit/` (audit.html browser, audit.mjs node) | funnel state printed in the header — check it |
|
||||||
|
| does rigging save the garden here? | `tools/garden_bench/` + `tools/site_audit/gardenfly.js` | garden outcomes, separation targets, margin rule |
|
||||||
|
| per-anchor wind exposure over a storm | `tools/storm_envelope/` | the storm-side second harness |
|
||||||
|
| regenerate all canon GLBs | `blender -b -P tools/blender/build_yard_assets.py` | deterministic; `--only <name>` for one asset |
|
||||||
|
| job-sheet/invoice design preview | `tools/jobsheet/index.html?v=...` | E's design-ahead handover pattern |
|
||||||
|
| deploy to partly.party/hardyards | `sh tools/deploy_hardyards.sh` | ships `web/` ONLY; self-verifies |
|
||||||
|
|
||||||
|
## Adding an asset (a prop, a structure, a thing in the yard)
|
||||||
|
|
||||||
|
**House rules first (PLAN3D §0):** the runtime asset is always `.glb`, committed
|
||||||
|
in `web/world/models/`. Raw `.blend`/`.fbx` never enter the repo (gitignored).
|
||||||
|
Two legitimate routes in:
|
||||||
|
|
||||||
|
**Route 1 — the factory (canon assets).** Add a builder to
|
||||||
|
`tools/blender/build_yard_assets.py`, re-run, commit the GLBs. Nothing is
|
||||||
|
hand-edited after export; the factory must produce byte-identical output across
|
||||||
|
runs (this is asserted — three full runs, same hashes). This is Lane E's lane.
|
||||||
|
|
||||||
|
**Route 2 — vendored external (John's hand-built / MeshGod / library assets).**
|
||||||
|
Precedent exists: `models/debris/*` is copied verbatim from the 3D-STORE
|
||||||
|
library. Drop the GLB in `web/world/models/`, then wire it (below). If it's a
|
||||||
|
one-off, say in THREADS where it came from; if it'll be regenerated, it belongs
|
||||||
|
in the factory instead.
|
||||||
|
|
||||||
|
**Wiring a prop — the gnome pattern (~5 lines + data, one commit).** There is
|
||||||
|
deliberately no generic `props: []` — every prop is a named top-level key in
|
||||||
|
the site JSON, wired explicitly, so a site says what it contains. The bike
|
||||||
|
(Sprint 12) is the worked example — copy its diff:
|
||||||
|
1. Put `bike_kid_01_v1.glb` in `web/world/models/`.
|
||||||
|
2. Site JSON gains a key: `"bike": { "x": -7, "z": 9.69, "rotYDeg": 180 }`.
|
||||||
|
3. `world.js` loader: one entry in the `Promise.all` + a placement block
|
||||||
|
(position via `heightAt(x,z)`, rotation from the JSON).
|
||||||
|
4. Optional collateral: bake `collateral` / `collateral_value` extras in the
|
||||||
|
GLB as the *proposal*; the site JSON's number is *canonical* (the carport's
|
||||||
|
`collateralValue: 180` pattern). Nothing is billed until `collateralFor()`
|
||||||
|
can resolve it — an unpriced collateral returns null and reads as "not
|
||||||
|
scored", never as free… and never as billed either (the gutter was a free
|
||||||
|
failure for two sprints).
|
||||||
|
5. Wrecks: same origin, same footprint, mesh-for-mesh swap on
|
||||||
|
`world.wreckStructure(id)` — build the wreck variant from a shared helper
|
||||||
|
so intact/wrecked can't drift (E's `_house_facade()` pattern).
|
||||||
|
6. An assert that can actually fail, negative-control-proven (E rebuilt the
|
||||||
|
bike at lean 0° to watch the suite go red before trusting it).
|
||||||
|
|
||||||
|
**Conventions the loader reads (bake as GLB extras / node names):**
|
||||||
|
- `rating_hint` (float) on anchor nodes — effective failure = `hw.rating ×
|
||||||
|
ratingHint`. Unset ⇒ 1 at adoption; the sim reads the anchor, live.
|
||||||
|
- `collateral` (string) on anchor nodes — what breaks when this anchor's
|
||||||
|
corner blows. `collateral_key` on a structure whose priced thing has a
|
||||||
|
different name than the structure (house → "gutter").
|
||||||
|
- Named nodes matter: `pickup_anchor` (where carried items sit), the
|
||||||
|
`fascia_anchor_*` family, etc. Check `world.js` adoptAnchor before renaming
|
||||||
|
anything.
|
||||||
|
|
||||||
|
**⚠ THE AXIS TRAP (bitten every sprint since 3):** the exporter maps Blender
|
||||||
|
`(x, y, z)` → three.js `(x, z, −y)`. A lean toward Blender +Y arrives leaning
|
||||||
|
toward three.js **−Z**. Never write a docstring about orientation without
|
||||||
|
measuring the exported GLB in three.js coords — E's bike comment lied about its
|
||||||
|
own geometry and only a browser-coords assert caught the flip. Dims/tri-count
|
||||||
|
verifies CANNOT catch a wrong shape or a flipped lean; look at the render.
|
||||||
|
|
||||||
|
## Authoring a site (a "level")
|
||||||
|
|
||||||
|
Sites are data: one JSON in `web/world/data/sites/`. Top-level shape (see
|
||||||
|
`site_02_corner_block.json`): `id, name, blurb, yard, sun, gardenBed,
|
||||||
|
structures, trees, posts, fence, wind, [props…], [separation], _design/_why`.
|
||||||
|
Anchors are not listed directly — they come from structures/trees/posts (and
|
||||||
|
GLB nodes), typed against the checked `ANCHOR_TYPE` enum in contracts.js.
|
||||||
|
|
||||||
|
The authoring loop that works (Sprint 10–13 distilled):
|
||||||
|
1. Copy the nearest existing site JSON; keep `_design`/`_why` notes in-file —
|
||||||
|
they're the level's design doc and the repo expects them.
|
||||||
|
2. `validateSite` runs at load and fails loud (types, wind block, enum). Boot
|
||||||
|
with `boot({site:'your_site', bank:N})` to jump straight to it.
|
||||||
|
3. Wind personality lives in the SITE (venturi axis/gain/throat, tree
|
||||||
|
shelters), storm personality in the storm def. **The venturi axis is site
|
||||||
|
GEOMETRY (a line, mod π), not a wind heading** — reconciled twice in
|
||||||
|
THREADS before that sentence earned its place.
|
||||||
|
4. Every trap must be: visible before commit (the enum gives it its pre-rig
|
||||||
|
read), priced (collateral), and mechanically real (rating_hint) — "the
|
||||||
|
price is the trap" vs "the steel is the trap" is a ruling, not an accident.
|
||||||
|
5. Run the gauntlet before calling it a level: `site_audit` (winnable lines at
|
||||||
|
$80, funnel ON — check the header), `gardenfly`/`garden_bench` (does
|
||||||
|
rigging separate held-vs-bare? pin a `separation` block if this site makes
|
||||||
|
that promise), `storm_envelope` (per-anchor exposure), then **a cold
|
||||||
|
playthrough by someone who didn't author it** (Lane D's job) — Sprint 11's
|
||||||
|
soft-lock shipped behind 296 green tests and was found in one minute of
|
||||||
|
play.
|
||||||
|
6. The blurb and brief carry real design load (night 3's "plenty to tie off
|
||||||
|
to" is the trap's cover story). Client/brief/pay live in week.js NIGHTS;
|
||||||
|
a site without a night is only half real (`boot({site})` seeks the night).
|
||||||
|
|
||||||
|
## The earned gotchas (each cost a real sprint-day; receipts in THREADS)
|
||||||
|
|
||||||
|
- **Measure through the real chain.** Any garden/load number that didn't go
|
||||||
|
through `commit → attach → skyfx` is suspect: session-level `commit()`
|
||||||
|
bypasses attach and scores every rig as bare (D, S13); a 12 s pre-settle
|
||||||
|
skews SailRig's internal storm clock (B catching A, S13).
|
||||||
|
- **Site wind is built in ONE place**: `windForSite()` in weather.js. Three
|
||||||
|
harnesses independently rebuilt it wrong (funnel off ×2, frozen tree sway).
|
||||||
|
Import the helper; never hand-wire venturi/shelters in a tool again.
|
||||||
|
- **Margin rule:** any bench corner within ~15% of its rating breaks in the
|
||||||
|
real game (residual bench-vs-UI under-read, cause unfound). Marginal ≠ PASS.
|
||||||
|
- **`world.dress()` needs the importmap** — without it the bare-specifier
|
||||||
|
throw is swallowed by callers' try/catch and every ratingHint silently
|
||||||
|
becomes 1.0 (no carport trap, invincible house).
|
||||||
|
- **`setHardware()` refuses over-budget quietly** — check `.ok` or fly with
|
||||||
|
`budget: 9999` in tools (this ate four of C's measurements).
|
||||||
|
- Documentation cannot fail: an unchecked enum, a JSDoc contract, a comment
|
||||||
|
about geometry — if nothing asserts it, expect it to be wrong someday.
|
||||||
|
- Two harnesses, one number. When they disagree, find the variable before
|
||||||
|
tuning anything; the variable is usually the harness.
|
||||||
|
- One clone per lane; ports 8823 (integration) / 8824–8828 (A–E) via
|
||||||
|
untracked `.claude/launch.json` (copy the example).
|
||||||
|
- Hidden tabs throttle rAF dead: drive with `SHADES.step(dt)` / `.render()`;
|
||||||
|
hidden-canvas boot needs one resize before projections are sane.
|
||||||
|
|
||||||
|
## Design canon quick-reference (ruled, don't relitigate casually)
|
||||||
|
|
||||||
|
- Business: HARD YARDS. ABN stays sequential (never make it realistic).
|
||||||
|
- Carport $180 / gnome $25 / gutter $90; bike deliberately unpriced until the
|
||||||
|
sim can knock it over (tripwire assert enforces this).
|
||||||
|
- The wild night sells no clean line — the pyrrhic win is intended. Night 5's
|
||||||
|
garden is beyond saving BY DESIGN and the brief says so. GARDEN_DRAIN does
|
||||||
|
not move. 18–45 m² is an availability floor, not a ceiling.
|
||||||
|
- Hail is why sails matter (steep, blockable); rain walks under cloth (don't
|
||||||
|
reopen the rain-angle argument).
|
||||||
|
|
||||||
|
## Where knowledge actually lives
|
||||||
|
|
||||||
|
READ ORDER for anyone new: README → DESIGN.md → PLAN3D.md → THREADS from the
|
||||||
|
last `[I]` entry backwards until you have context → the current SPRINTn.md.
|
||||||
|
THREADS is append-only memory, not a reference — when you learn something from
|
||||||
|
it the hard way, promote it into THIS file so the next person reads one page
|
||||||
|
instead of 300 entries.
|
||||||
Loading…
Reference in New Issue
Block a user