HardYards/web/world/data/sites/backyard_01.json
type-two 6bcc2540cf Sprint 11 gate 2: the job sheet — five nights become five jobs
DESIGN.md has opened with this since line 3 — "every callout is a different
site, a different client" — and the game had the site and not the client. The
forecast card is a job sheet now and the aftermath is an invoice.

THE WEEK, once it was asked to name a client, mostly wrote itself: four nights
are the same yard, so they're one client on retainer while they're away (the
Hendersons, in Cairns from Friday — SPRINT11's own line). Night 3 is a
different SITE, so it is necessarily a different CLIENT: a short-notice callout
to a corner block you've never rigged, on the night the change comes early.
That isn't flavour bolted on, it's what `site: site_02_corner_block` already
meant, finally said out loud.

BUDGET Y IS THE FEATURE, not the letterhead. DESIGN.md's brief is "client wants
X, budget Y, forecast Z" and this card had Z alone. The fee has existed since
Sprint 8 and the player has never seen it until the money was already spent —
you chose what to rig without knowing what the job was worth, which is a reveal,
not a decision. The sheet quotes base + garden + clean up front, and a test pins
that a perfect night pays exactly what the sheet advertised: a job sheet that
over-promises is the game lying on paper.

THE CLEAN BONUS is the new money, and it's the one number in week.js that isn't
measured. It gives the invoice three axes that mean three different things —
you turned up, their garden lived, you didn't wreck the place — and it's NOT
gated on winning, because a lost night where you broke nothing of theirs is a
different fact and deserves its own row. It also makes the carport bite twice:
180 + 20 = 200, inside E's own "over ~250 is a silent game over" ceiling.
Measured: it adds ~$100 to a competent week (385 -> 485), which makes
BROKE_BELOW's known runaway worse. I did NOT pay for it by quietly trimming
feeFor or gardenBonusMax — those carry measured evidence, and re-tuning someone
else's evidence to fund my own feature is the move this repo keeps catching. If
gate 3 says the bank runs away, this is the lever to cut first and it's data.

TWO BUGS, BOTH CAUGHT BY LOOKING AT THE CARD, neither by a test:
 · "THE HENDERSONS" over "The Backyard — Your own place". The blurb and the
   letterhead called each other liars on screen. The blurb gave way: "your own
   backyard" is a leftover from when the game had one yard, and DESIGN.md never
   wanted it. The line's job survives — night 1 is the tutorial because you know
   this yard, now from having worked it for years rather than owning it.
 · C's `lead` is a 0..1 haze dial (confidence = 1 − lead), not "nights out". I
   read it as nights, passed 1, and the sheet advertised "forecast confidence
   0%". Now 0.6, because C's own hail rule calls L > 0.55 too distant to promise
   ice, and tomorrow is exactly that. C: the slot is mine, the number is yours.

Lane C's forecastLines(def, lead) finally has a caller above 0 — two sprints
after they built it. Lane E: .letterhead / .brief / .jobsheet / .tomorrow are
structure only and yours to style.

selftest 304/0/0 (was 300).
2026-07-17 17:37:18 +10:00

98 lines
4.6 KiB
JSON

{
"id": "backyard_01",
"name": "The Backyard",
"blurb": "You've worked this one for years. You know where everything is, which is the only advantage you get.",
"_blurb": [
"SPRINT11: this said 'Your own place' until the job sheet put a client's name at the top of the",
"card and the two lines called each other liars on screen — THE HENDERSONS over 'your own place'.",
"The blurb gave way, and it wasn't close: 'your own backyard' is a leftover from when the game had",
"exactly one yard, and DESIGN.md has never wanted it — line 3 is 'every callout is a different",
"site, a different client', and gate 2 is the sprint where that frame actually arrives.",
"The line's JOB survives, which is why it's a rewrite and not a deletion: night 1 is the tutorial",
"because you know this yard, and that advantage now comes from having worked it for years instead",
"of owning it. Client-agnostic on purpose — the yard is the site's business, the client is the",
"night's (week.js NIGHTS). Caught by looking at the card, not by a test. No assert reads a blurb."
],
"yard": { "width": 30, "depth": 20 },
"sun": { "elevationDeg": 55, "azimuthDeg": -125 },
"gardenBed": { "x": 1, "z": 2, "w": 6, "d": 4 },
"house": {
"model": "house_yardside_v1",
"x": 0, "z": -10.5,
"anchors": [
{ "id": "h1", "node": "fascia_anchor_01", "type": "house", "work": "bracket" },
{ "id": "h2", "node": "fascia_anchor_02", "type": "house", "work": "bracket" },
{ "id": "h3", "node": "fascia_anchor_03", "type": "house", "work": "bracket" }
]
},
"trees": [
{
"id": "t1", "model": "tree_gum_01_v1",
"x": -9, "z": 2, "phase": 0.7, "trunkH": 4.2, "anchorY": 3.4,
"anchors": [
{ "id": "t1", "node": "branch_anchor_01", "type": "tree", "work": "cloth" },
{ "id": "t1b", "node": "branch_anchor_02", "type": "tree", "work": "cloth" },
{ "id": "t1c", "node": "branch_anchor_03", "type": "tree", "work": "cloth" }
]
},
{
"id": "t2", "model": "tree_gum_02_v1",
"x": 8, "z": -2, "phase": 2.9, "trunkH": 3.8, "anchorY": 3.1,
"anchors": [
{ "id": "t2", "node": "branch_anchor_01", "type": "tree", "work": "cloth" },
{ "id": "t2b", "node": "branch_anchor_02", "type": "tree", "work": "cloth" }
]
}
],
"posts": [
{ "id": "p1", "x": -4.5, "z": 5.5, "h": 4.0, "type": "post", "work": "cloth" },
{ "id": "p2", "x": 4.0, "z": 6.0, "h": 4.0, "type": "post", "work": "cloth" },
{ "id": "p3", "x": 0, "z": 7.0, "h": 4.0, "type": "post", "work": "cloth" },
{
"id": "p4", "x": -3.2, "z": -1.2, "h": 4.0, "type": "post", "work": "cloth",
"_why": [
"DO NOT move this post toward the bed. Its position is swept, not chosen, and it is the",
"single number holding DESIGN.md's central tension open.",
"",
"Before p4 the wild night had NO winnable line, for a geometric reason: every anchor near",
"the bed (p1/p2/p3) is SOUTH of it and nothing stands north short of the house 10 m away,",
"so covering rigs had to span the yard and died while rigs small enough to survive sat",
"beside the bed rather than over it. p4 supplies the missing north-west corner.",
"",
"Swept against the smallest quad covering 90% of the bed:",
" (-2.2, -1.2) -> 44.4 m2 DO NOT: full coverage lands INSIDE the survivable band,",
" so covering the bed stops costing risk and the rigging",
" puzzle loses its wrong answers.",
" (-3.2, -1.2) -> 48.6 m2 <- here (with the 8 deg rake the top anchor lands at",
" -3.72,-1.40 and full coverage costs 51.6 m2)",
" (-3.2, -2.0) -> 51.7 m2",
" (-4.2, -3.0) -> 60.0 m2",
" beyond z=-4 -> 63.5 m2, i.e. too far out to matter at all",
"",
"Full coverage costs 63.5 m2 without it and 51.6 with: a real new option, still bigger",
"than the 23-38 m2 rigs that survive unaided. a.test.js asserts the >45 m2 floor, so",
"moving this inward goes red — that assert is the tension's only guard."
]
}
],
"shed": { "model": "shed_01_v1", "x": 11.8, "z": 6.2, "rotYDeg": -90 },
"shedTable": { "model": "shed_table_v1", "x": 9, "z": 6, "rotYDeg": -90, "pickupNode": "pickup_anchor" },
"gnome": { "model": "garden_gnome_01_v1", "x": 4.3, "z": 4.4, "rotYDeg": -126, "collateralValue": 25 },
"fence": {
"sides": ["south", "west", "east"]
},
"wind": {
"venturi": []
}
}