From b74dfd80db80edc45dd892b321e8570cfeaf3fdd Mon Sep 17 00:00:00 2001 From: m3ultra Date: Sat, 18 Jul 2026 19:03:36 +1000 Subject: [PATCH] =?UTF-8?q?rigging:=20the=20fabric=20bet=20is=20reachable?= =?UTF-8?q?=20=E2=80=94=20F=20cycles=20cloth/membrane=20in=20prep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FABRIC and setFabric() shipped in SPRINT6 and the sim honoured porosity all along, but no key, click or HUD line ever reached them — every rig ever played was DEFAULT_FABRIC. F toggles the two entries, the panel shows the fabric, the splash teaches it, the blurb announces the switch. Session-level assert pins the cycle, the $0 price, and the refusal of unknown ids. Co-Authored-By: Claude Fable 5 --- web/world/js/hud.js | 1 + web/world/js/rigging.js | 13 +++++++++++-- web/world/js/rigging.selftest.js | 17 ++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/web/world/js/hud.js b/web/world/js/hud.js index 0b947ee..3f46295 100644 --- a/web/world/js/hud.js +++ b/web/world/js/hud.js @@ -516,6 +516,7 @@ export function createHud(d) { ['click', 'pick an anchor · again to unpick'], ['[ ]', 'tension the rig — tighter holds shape, and breaks harder'], ['S', 'buy a spare shackle'], + ['F', 'choose the fabric — cloth breathes, membrane stops the rain'], ['E', 'repair a blown corner (ladder if it is up high)'], ['C', 'brace against the wind'], ['Enter', 'commit the rig and start the night'], diff --git a/web/world/js/rigging.js b/web/world/js/rigging.js index 4ed283c..add616c 100644 --- a/web/world/js/rigging.js +++ b/web/world/js/rigging.js @@ -514,6 +514,15 @@ export async function createRiggingUI({ if (ev.key === '[') session.setTension(session.tension - 0.05); else if (ev.key === ']') session.setTension(session.tension + 0.05); else if (ev.key.toLowerCase() === 's') say(session.setSpares(session.spares ? 0 : 1)); + else if (ev.key.toLowerCase() === 'f') { + // The forecast bet, finally reachable. FABRIC and setFabric() shipped in + // SPRINT6 and the sim honoured porosity all along — but no key, click or + // HUD line ever reached them, so every rig ever played was DEFAULT_FABRIC. + // Two entries, so F is a toggle; announce the blurb because the panel + // line alone doesn't say what the bet IS. + const next = FABRIC[(FABRIC.indexOf(session.fabric) + 1) % FABRIC.length]; + if (say(session.setFabric(next.id)).ok) onMessage(`${next.name} — ${next.blurb}`); + } else return; ev.preventDefault(); refresh(); @@ -585,14 +594,14 @@ export async function createRiggingUI({ : null; el.textContent = [ `PREP — rig four corners $${s.budget} left`, - `tension ${s.tension.toFixed(2)} spare x${s.spares}${area ? ` sail ${area.toFixed(0)} m2` : ''}`, + `tension ${s.tension.toFixed(2)} spare x${s.spares} ${s.fabric.name}${area ? ` sail ${area.toFixed(0)} m2` : ''}`, '', ...rows, ...(standingLine ? ['', standingLine] : []), '', s.canStart ? 'ENTER to start the storm' : `pick ${MAX_CORNERS - session.picks.length} more corner(s)`, 'click anchor: rig / cycle hw shift-click: remove', - '[ ] tension S spare RMB orbit', + '[ ] tension S spare F fabric RMB orbit', ].join('\n'); } diff --git a/web/world/js/rigging.selftest.js b/web/world/js/rigging.selftest.js index f67d9a5..018c5a9 100644 --- a/web/world/js/rigging.selftest.js +++ b/web/world/js/rigging.selftest.js @@ -6,7 +6,7 @@ * js/tests/b.test.js) and node. */ -import { RiggingSession } from './rigging.js'; +import { RiggingSession, FABRIC } from './rigging.js'; import { SailRig, TENSION_MIN, TENSION_MAX } from './sail.js'; import { HARDWARE, START_BUDGET, SPARE_COST } from './contracts.js'; @@ -344,6 +344,21 @@ test('reset() returns a used session to a fresh prep phase', () => { return 'budget, picks, tension, spares all fresh; rig-able again'; }); +test('fabric is a bet the F key can actually place (SPRINT15 — the dead mechanic)', () => { + const s = session(); + assert(s.fabric.id === 'cloth', `default fabric should be cloth, got ${s.fabric.id}`); + // the exact call the F key makes: cycle to the other entry by id + const next = FABRIC[(FABRIC.indexOf(s.fabric) + 1) % FABRIC.length]; + assert(s.setFabric(next.id).ok, 'cycling to membrane refused'); + assert(s.fabric.id === 'membrane' && s.fabric.porosity === 0, 'membrane not applied'); + assert(s.budget === START_BUDGET, 'fabric is a bet, not a purchase — budget must not move'); + assert(s.summary.fabric.id === 'membrane', 'the panel reads fabric off summary and must see the change'); + // wrap-around brings it home, and garbage is refused loudly + assert(s.setFabric(FABRIC[(FABRIC.indexOf(s.fabric) + 1) % FABRIC.length].id).ok && s.fabric.id === 'cloth', 'cycle should wrap back to cloth'); + assert(s.setFabric('hessian').ok === false, 'unknown fabric must refuse, not default'); + return 'cloth -> membrane -> cloth, $0 both ways, hessian refused'; +}); + export const RIGGING_TESTS = TESTS; export function runRiggingSelftest() {