A's §8 seam, built to their shape: an ordered {key, text} list the card maps, the
offerBand construction, reachable through weather.midStormRead?.() under a
namespace import.
THE INVERSION IS THE DESIGN. offerBand's sin is OMISSION — the board lied about
night 7 by printing two of the fields it held. A dispatch's sin is INVENTION, and
the consequence is that the read is SHARPER than a forecast, not vaguer: standing
in the yard you know the wind on your back to the km/h, so the present tense is
exact where the offer card was banded. What you do not have is the bureau's
numbers — how long it runs, the peak, whether a southerly lands at t=48.
So the read is ruled by TENSE. Permitted: NOW (exact, unhedged); THE VISIBLE
IMMINENT (a gust front crossing the grass — DESIGN.md line 149, a 1.5 s telegraph
horizon earned by looking); and THE PAST AS WRITTEN ON THE YARD — at t=30 you have
no memory of the storm because you were driving, but hail lying in the grass and
water standing on the flat are a record you did not have to be present for.
Integrals over [0, t] are honest; over (t, ∞) they are not. The future is
forbidden outright.
And the admission is mandatory: on an offer card a missing hail line means the
storm does not hail, on a dispatch it means nobody knows, and silence cannot tell
those apart. The read says so first and last, in constant words — the ignorance
line is unconditional because a line that appears only when there is something to
not-know becomes the very fact it claims not to have.
dispatchHonest(def, t) extends the gate: forecastHonest unchanged (no forecast
card is not a licence for an indescribable storm — it raises the bar, since the
read is the only description there is), plus the three things only an arrival can
get wrong. My own S17 filing was wrong and testing it said so: hail.size 0 was
already refused by validateStorm a layer up. The reachable hole is size ABSENT —
it passes every gate and defaults to the MIDDLE of the vocabulary, so the soaker
minus one field prints "marble stones" for its real 0.45 fine pea.
Pins +5 (527 -> 532). The strongest states the rule rather than policing a
vocabulary: THE READ AT t MAY NOT MOVE WHEN THE STORM'S FUTURE BEYOND t MOVES,
with a vacuity guard proving the mutations really do change the storm. Four
mutations red and restored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|---|---|---|
| .claude | ||
| docs | ||
| prototype | ||
| tools | ||
| web/world | ||
| .gitignore | ||
| DESIGN.md | ||
| LANE_PROMPTS.md | ||
| PLAN3D.md | ||
| README.md | ||
| ROADMAP.md | ||
| server.py | ||
| SPRINT2.md | ||
| SPRINT3.md | ||
| SPRINT4.md | ||
| SPRINT5.md | ||
| SPRINT6.md | ||
| SPRINT7.md | ||
| SPRINT8.md | ||
| SPRINT9.md | ||
| SPRINT10.md | ||
| SPRINT11.md | ||
| SPRINT12.md | ||
| SPRINT13.md | ||
| SPRINT14.md | ||
| SPRINT15.md | ||
| SPRINT16.md | ||
| SPRINT17.md | ||
| SPRINT18.md | ||
| THREADS.md | ||
HARD YARDS — rig it, then survive the night
(repo/product history: the project was codenamed SHADES through Sprint ~8; the shipped game is HARD YARDS. Internal docs and the gitea repo name still say shades — that's the same thing, not a second game.)
Geometry/tower-defense: you're a small person rigging shade sails against Australian storms. three.js r175 (vendored, no build step) + a zero-dep Python server. Everything the repo needs is in the repo.
Run it
python3 server.py --port 8801 # any free port
# game: http://localhost:8801/web/world/index.html
# selftest: http://localhost:8801/web/world/selftest.html (must be all green)
# 2D proto: http://localhost:8801/prototype/index.html (the original thesis)
No pip, no npm, no build. Python 3.8+ and a browser.
Read in this order
DESIGN.md— the design canon (what the game is)PLAN3D.md— the build canon (stack, contracts, lane model) ·docs/MANUAL.md— the HOW: tool map, asset/site cookbooks, earned gotchas ·ROADMAP.md— the growth canon (the arcs from the week to the business)THREADS.md— the append-only lane log. The project's memory. Read the last few[I]integrator entries to know the current state.SPRINT15.md— the current sprint (highest-numbered fired sprint wins: SPRINT16 is pre-speced TEETH and fires only when 15 integrates — its own header says so)LANE_PROMPTS.md— copy-paste prompts per lane, per sprint (bottom = newest)
The lane model (how this repo is built)
Five parallel Opus agents ("lanes"), each in its OWN clone, on its own branch:
| lane | owns |
|---|---|
| A | main.js, world.js, camera, hud, week, sites, server — shell & integration, merge shepherd |
| B | sail.js, rigging — cloth sim, economy, balance.test pen |
| C | weather, skyfx, debris, storm/site wind data |
| D | player, interact, ladder, broom — and the designated playtester |
| E | Blender asset factory (tools/blender), models/, textures |
Hard rules (they exist because each was earned — see THREADS):
- One clone per lane. Never two agents in one working tree.
- Rebase onto latest
mainbefore starting; small commits; push your branch. - File ownership is the merge strategy; cross-lane needs go in THREADS.md.
- Selftest green after every landing. Skips must be real. An assert that cannot fail is decoration.
- All sim code is deterministic
(dt, t)— no Date.now, no rAF. - Measure, don't reason: the repo's biggest bugs were all "a number gathered from the wrong harness". When two harnesses disagree, find the variable.
Setup on a new machine
Gitea's SSH runs over tailscale (the public host is Cloudflare-proxied, no
direct SSH). Add to ~/.ssh/config:
Host gitea.partly.party
HostName 100.71.119.27
Port 222
User git
Then:
git clone git@gitea.partly.party:monster/hardyards.git ~/Documents/shades
for L in A B C D E; do
git clone git@gitea.partly.party:monster/hardyards.git ~/Documents/shades-lane$L
git -C ~/Documents/shades-lane$L checkout -B lane/$(echo $L | tr 'A-Z' 'a-z') origin/lane/$(echo $L | tr 'A-Z' 'a-z')
done
~/Documents/shades is the integration checkout (main; where merges and
sprint docs happen). The five shades-lane* clones are where lane agents run.
Per clone, once: cp .claude/launch.example.json .claude/launch.json and pick
a port of your own (integration 8823, lanes A–E 8824–8828). The real
launch.json is untracked — six checkouts on one laptop can't share one port.
Sprint cycle
- Fire lanes with the newest prompts at the bottom of
LANE_PROMPTS.md(Lane A usually first if the sprint gives it rulings/blocking items). - Lanes work, push their branches, log in THREADS.md.
- Integrate in the main checkout: merge
origin/lane/{e,c,b,d}(THREADS.md conflicts resolve keep-both-chronological; real code conflicts get read), run selftest, play the result, writeSPRINT(n+1).md+ prompts, push main AND fast-forward all fivelane/*branches to main.