The corner block's seams, all of them A's to close. ENUM (D's flag). Anchor.type was a closed JSDoc enum 'house'|'tree'|'post', so site_02 typed its carport 'post' to fit — quietly enrolling it in the sail-post family that C's venturi and B's audit both read. Widened to E's own asset strings (carport, carport_post) and, more to the point, CHECKED: it was documentation for ten sprints, and documentation cannot fail. validateSite now rejects an unknown type by name. The ladder is untouched either way — D keyed it on `work`, the mechanism, which is exactly why that was right. CARPORT (E's proposal, adopted). $180, with E's reasoning intact: gnome 25, a night's budget 80, so 180 is 2.25 nights — a good week turned broke and felt for the rest of the run, without ending a strong one outright. Under ~120 it's a shrug, over ~250 a silent game over that teaches nothing. The number lives in site JSON, not the GLB: sites are data, and what a carport COSTS is this site's economy, not a property of the mesh. E's baked value stays as the fallback and the proposal. And it BILLS now. E shipped the trap and said it plainly — the anchors said collateral "carport" and nothing said what one cost, so nothing scored it. You could lose the worst steel in the game and pay for a $15 shackle. One broken corner is enough (the gnome needs two: it needs the sail to land on it; the carport doesn't need the sail at all), priced per structure so two beams is one carport gone, not $360. The wreck swaps on the same event — E built it to the same origin for exactly this. VENTURI (C's question). There was no disagreement to settle. weather.core aligns on |dot(wind, axis)| because a gap funnels either way through it, so an axis is a LINE: -1.08 + PI = 2.0616, and my shipped 2.1 was the same gap read from the other end. Took C's number because C MEASURED it off storm_03b and I eyeballed mine at authoring time; the 2.2 deg of my eyeballing was worth 0.44 m/s. gain left at C's 1.35 — that's a balance lever, theirs with B's audit. Also wired validateSiteWind at site load, C's ask from Sprint 10. selftest 300/0/0 (was 296). |
||
|---|---|---|
| .claude | ||
| docs | ||
| prototype | ||
| tools | ||
| web/world | ||
| .gitignore | ||
| DESIGN.md | ||
| LANE_PROMPTS.md | ||
| PLAN3D.md | ||
| README.md | ||
| server.py | ||
| SPRINT2.md | ||
| SPRINT3.md | ||
| SPRINT4.md | ||
| SPRINT5.md | ||
| SPRINT6.md | ||
| SPRINT7.md | ||
| SPRINT8.md | ||
| SPRINT9.md | ||
| SPRINT10.md | ||
| SPRINT11.md | ||
| THREADS.md | ||
SHADES — rig it, then survive the night
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)THREADS.md— the append-only lane log. The project's memory. Read the last few[I]integrator entries to know the current state.SPRINT11.md— the current sprint (highest-numbered SPRINTn.md wins)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/shades.git ~/Documents/shades
for L in A B C D E; do
git clone git@gitea.partly.party:monster/shades.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.
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.