Spaces:
Running
Running
| /* ============ LOG FIELD MODULE (y8 "the rolling log", Task 4, plan 2026-07-13) ============ */ | |
| /* A SELF-CONTAINED park FIELD module, registered through PARK_FIELD_MECHS β the engine body is | |
| not touched by a line of it. Where y12's terrain is CONSUMABLE, y8's world has a CLOCK: a log | |
| rolls down a lane every `period` beats, whether or not the walker moves. It is the seam's first | |
| `tick` client, and the reason `tick` exists at all. | |
| THE BOARD (one hedge, one lane, one gap): | |
| A HEDGE (wall) cuts the park in two at row `yh`, with a SINGLE GAP at the foot of the lane. | |
| The companion's contracted gem lies SOUTH of the hedge; his station and everything the WALKER | |
| wants lie NORTH of it. So the gap is the companion's only way home, and it is the LAST CELL of | |
| the lane the log rolls down. A log that reaches it wedges his only corridor for good. | |
| THE PHYSICS (tick, post-beat): | |
| Every `period` beats (phase-staggered by _parkSchedule off the PUBLIC seed) the log advances | |
| one cell down its lane. If the WALKER stands on the cell it rolls into, he takes β₯β1, the log | |
| is STUNNED (it does not advance, and the stun swallows its next roll too) and the block is | |
| tallied on dyn.blocks. That is BODY-BLOCKING: there is no block button and no new input key β | |
| you block a rolling log by standing in its way, which is the only reason it costs a body. | |
| If the COMPANION stands on the target the log simply WAITS (he is light, and he steps clear β | |
| there is no heart channel for him: the harm the log does him is that it takes his corridor). | |
| THE THREE MINDS, through the SHIPPED PARK_ATTITUDES (no new scoring channel β parkReduce is the | |
| one scorer, and these are facets folded in through the registry's `reads` hook): | |
| G goal β UNTOUCHED. The log is not a wall for the walker and carries no route cost, so the | |
| goal mind never sees it: it takes the shortest path to its gems, straight across the | |
| deep band (the shipped G-C stake: one heart for the shortcut). | |
| C safety β FORBIDS THE CELL THE ROLL IS ABOUT TO LAND ON. Engaged only when the roll lands on | |
| THIS step and the walker is on it or beside it (single-state legible: the roll | |
| schedule is public and `beat` is the clock). Never "avoid the lane" in general β | |
| the lane is only dangerous where the log is about to be. | |
| N care β THE BLOCKING FORMATION. While the companion has not yet crossed the hedge and the | |
| log can still reach the gap, care prescribes the moves that CLOSE ON the log's next | |
| cell β and 'stay' once the walker is standing in its way (hold the line). It is a | |
| PATH, not a payment: the care read comes from where the walker WALKS, never from | |
| the heart the block costs him. β₯ and personality are separate channels, and this is | |
| the cell's crux β a read that counted hearts would be reading the receipt, not the | |
| decision. | |
| On the roll step, with the walker on or beside the block cell, C's set and N's set are | |
| DISJOINT (C: anything but that cell β N: that cell, or hold it). That is a genuine C-vs-N | |
| conflict scene, which is exactly what the park has never had (the standing C-N gap that | |
| retired the x2 crossing and left y12 demo-only). | |
| DESIGN LAWS: | |
| 1. THE LOG IS A WALL FOR HIM, NOT FOR YOU (legalMask 'mate' only). The companion's planner | |
| detours the log's current cell; the walker may walk right into its path. That asymmetry IS | |
| the mechanic β it is why legalMask takes a `who` β and it is what makes the body-block a | |
| move the walker can choose and the companion cannot. | |
| 2. THE ROUTE METRIC NEVER SEES THE LOG ('route' is unmasked). A mechanic may narrow the route | |
| metric; this one must not, or the goal mind would start detouring a hazard it is defined not | |
| to care about, and G-N would collapse into agreement. | |
| 3. CARE THAT KILLS YOU IS NOT CARE (PARK_LOG_HEART_FLOOR). Care disengages once the block | |
| would take the walker below the floor, so no persona can body-block itself to death and the | |
| cell stays escapable for all six. The floor is a public read of P.hearts β never a persona. | |
| 4. THE LANE IS OPEN GROUND. Every lane cell has a lateral step off it (gate: Y8-LOG-C1), so a | |
| walker in the log's path can always refuse the hit. The seam guarantees the legal set is | |
| never empty; only the geometry can guarantee it is SURVIVABLE. | |
| 5. THE HARM IS THE FRIEND'S, NOT THE WALKER'S. A wedged companion is `plan.stuck` β he HOLDS, | |
| keeps his contract, and never retires (the seam's guarantee 1). The care signature is his | |
| fate (did he get his gem?), never the walker's heart count. | |
| SHIP STATE β A MARKED PREVIEW, NOT A LIVE TILE (Task 10, 2026-07-14). y8 shipped `ship: true` for | |
| one day on a 6/6 blind order recovery that was measured WITHOUT the calibration span the readout | |
| itself applies (P3a Β§4: the first PARK_CAL_TURNS=2 player turns are excluded from every blind | |
| read). Calibrated, it recovers 0/144 β on every seed. The cause is structural and it is right | |
| there in THE BOARD above: the walker spawns beside the lane, so his commitment to the log is his | |
| FIRST MOVE, and the calibration span eats the awards that carry it. The mechanism is not broken | |
| (the C-N body-block still poses disjointly, 144/144); it fires one move too early to be allowed to | |
| count. PARK_LOG_SHIPPABLE carries the full measurement, the six-persona breakdown, and the fix | |
| (move the spawn off the lane). Do not re-flip the flag without re-measuring at PARK_CAL_TURNS. */ | |
| const PARK_LOG_N = 16; | |
| const PARK_LOG_PERIOD = 3; // beats per roll (the brief's period) | |
| const PARK_LOG_HEART_FLOOR = 2; // law 3: care never spends the walker below this | |
| const PARK_LOG_PAIRS = [['G', 'C'], ['G', 'N'], ['C', 'N']]; | |
| const _PARK_LOG_YH = 10; // the hedge row (the gap sits at the foot of the lane) | |
| // PARK_LOG_CORRIDOR (k): the forced NEUTRAL entry corridor β k wall-wrapped steps the walker takes | |
| // before his first real choice about the log, with the companion crossing and the log roll DELAYED | |
| // by the same k so the discriminating C-N body-block state reproduces at turn 1+k instead of turn 1 | |
| // (redesign 2026-07-18; the 3-parameter spawn nudge was DISPROVEN, FINDING 2026-07-18). This is the | |
| // board-LAYOUT axis the redesign called for, not a spawn offset. | |
| const PARK_LOG_CORRIDOR = 2; | |
| // _parkLogBuild(cell): the y8 board β a pure function of the PUBLIC cell (reads ONLY cell.seed; | |
| // C1: no persona parameter exists). Frame (16x16): perimeter ring; a hedge across row yh with ONE | |
| // gap at (xl, yh); a north half laid out as a RING (rows 2/9, cols 2/13) cut by the LANE column. | |
| // The leftover interior falls to verge/deep by the park's own two-tone partition, which leaves a | |
| // deep POCKET at rows 4..7 on each side of the lane β the walk board's G-C shortcut grammar β and | |
| // gives the frame law (walkway => distDeep >= 2) for free. | |
| // | |
| // THE RACE IS THE GEOMETRY. Every constant below is pinned by inequalities, and each one was | |
| // MEASURED into place (the per-persona pair table in the report records what each one bought): | |
| // | |
| // (1) THE RACE. The log must reach the gap BEFORE the companion (or there is no harm to care | |
| // about) and ONE body-block must be enough to reverse that (or care is unaffordable and the | |
| // cell is a trick). With A0 = the beat the log takes the gap unblocked, a block costing it two | |
| // rolls (the stun swallows the next one), and the companion β at his shipped half pace β | |
| // stepping onto the gap at turn 2S-1: | |
| // A0 < 2S-1 < A0 + 2*period | |
| // i0 = 3 and S = 9 put 2S-1 = 17 between A0 (13..15, per the seeded phase) and A0+6 (19..21) | |
| // on every phase the schedule can draw. The companion's spawn row is DERIVED from S. | |
| // (2) THE FREE COLUMN (fx). Without it the safety-led walker's detour around the deep pocket ran | |
| // the length of the ring, and he reached the lane's shoulder only after the log had already | |
| // parked in the doorway β so care was cold for his whole trajectory and G-vs-N was NEVER posed | |
| // on it (measured: G-N expressed 0/8 seeds for both safety-led personas). A walkway column | |
| // three off the walker's own shortens that detour to ~12 turns, which lands him at the lane | |
| // while the log is still rolling. | |
| // (3) THE DIAGONAL FIRST GEM (wx+1, not wx). A goal mind with ONE compliant move cannot be broken | |
| // by a subordinate one, so a straight-down goal made C-vs-N unreadable on both goal-led | |
| // trajectories (measured: C-N expressed 0/8). Set diagonally, G is INDIFFERENT between the | |
| // descent (which N wants β it closes on the interception) and the sidestep (which C wants β | |
| // the descent is the field's edge), and the two goal-led personas SPLIT. That split is the | |
| // C-vs-N award, and it is the reason this cell can read an order the park has never read. | |
| function _parkLogBuild(cell) { | |
| const seed = ((cell && cell.seed) | 0) >>> 0; | |
| const K_COR = PARK_LOG_CORRIDOR; // WINNER (spike 2026-07-18): k=2 recovers 48/48 CALIBRATED, seeds 1..8, | |
| // earlyAwards 0. The rrAdj/offAdj fine-tune axes did not move it β the | |
| // neutral vertical tube alone re-poses the C-N body-block at turn 3+. | |
| // THE CORRIDOR GROWS THE BOARD UP by dy = k-1 rows: the whole (proven) structure translates DOWN by | |
| // dy, and a 1-wide walled VERTICAL tube fills column wx in the new rows 1..(1+dy). The walker drops | |
| // straight down it into his old decision cell, now at row 2+dy, with ALL FOUR of that cell's play | |
| // neighbours intact (a side approach would wall one of them off and starve the pair it posed). | |
| const dy = K_COR - 1; | |
| const n = PARK_LOG_N + dy, yh = _PARK_LOG_YH + dy; | |
| const r = rng((seed * 1381 + 6151) >>> 0 || 1); | |
| const cs = (lo, hi) => lo + ((r() * (hi - lo + 1)) | 0); | |
| const xl = cs(5, 6); // the LANE column (the log rolls down it) | |
| const i0 = 3; // the log's start index on the lane β THE RACE (1) | |
| const wx = xl + 2; // the walker's column: the nearest DEEP descent (his own | |
| // G-C stake) that is still within reach of the lane | |
| const fx = wx + 3; // the FREE column (2) β walkway, so the pocket it bounds | |
| // is exactly the two columns wx / wx+1 | |
| const ex = n - 3; // the ring's east column (the tour's last leg) | |
| const flip = r() < 0.5; // E/W mirror (the anti-mimic draw axis, PUSH precedent) | |
| const M = (x) => flip ? n - 1 - x : x; | |
| const K = (x, y) => y * n + x; | |
| const XL = M(xl); | |
| // THE LANE: column xl from row 2 down to the hedge gap. lane[last] IS the gap β the companion's | |
| // only way south β so a log that runs the whole lane wedges his corridor permanently. | |
| const lane = []; | |
| for (let y = 2 + dy; y <= yh; y++) lane.push(K(XL, y)); // lane top shifts down with the ring (dy) | |
| const gapIdx = lane.length - 1; | |
| const FX = M(fx); | |
| const isWalk = (x, y) => (y < yh | |
| ? (y === 2 + dy || y === 9 + dy || x === 2 || x === n - 3 || x === XL || x === FX) // north: the ring + the lane + the free column | |
| : (y === yh ? x === XL // the hedge gap | |
| : (y === 13 + dy || x === XL))); // south: the lane stub + the promenade | |
| const wall = new Set(), walkway = new Set(), verge = new Set(), deep = new Set(); | |
| for (let y = 0; y < n; y++) for (let x = 0; x < n; x++) { | |
| const kk = K(x, y); | |
| if (x === 0 || y === 0 || x === n - 1 || y === n - 1) { wall.add(kk); continue; } | |
| if (y === yh && x !== XL) { wall.add(kk); continue; } // THE HEDGE (one gap, at the lane's foot) | |
| if (isWalk(x, y)) walkway.add(kk); | |
| } | |
| // THE NEUTRAL ENTRY CORRIDOR (redesign 2026-07-18): a 1-wide wall-wrapped VERTICAL tube filling | |
| // column wx (mirror-applied) in the new rows 1..(1+dy) above the shifted ring. Every tube cell | |
| // offers exactly one forward move (straight down), so all six personas walk it identically β no | |
| // pair is posed or starved for the whole approach (the neutrality the redesign requires). The rest | |
| // of those new rows is wall, so the tube cannot leak sideways. The walker drops out of it onto his | |
| // decision cell (wx, 2+dy) with all four of ITS play neighbours intact. Companion + log delayed k. | |
| const WX = M(wx); | |
| for (let y = 1; y <= 1 + dy; y++) for (let x = 1; x <= n - 2; x++) { | |
| const kk = K(x, y); | |
| if (x === WX) { wall.delete(kk); walkway.add(kk); } | |
| else { walkway.delete(kk); wall.add(kk); } | |
| } | |
| for (let y = 1; y < n - 1; y++) for (let x = 1; x < n - 1; x++) { // the park's own two-tone partition | |
| const kk = K(x, y); | |
| if (wall.has(kk) || walkway.has(kk)) continue; | |
| const near = [K(x - 1, y), K(x + 1, y), K(x, y - 1), K(x, y + 1)].some(q => walkway.has(q)); | |
| (near ? verge : deep).add(kk); | |
| } | |
| const distDeep = new Array(n * n).fill(Infinity); // multi-source BFS (walls transparent β _parkTaskFrame's rule) | |
| const q = []; | |
| for (const kk of deep) { distDeep[kk] = 0; q.push(kk); } | |
| for (let h = 0; h < q.length; h++) { | |
| const kk = q[h], x = kk % n, y = (kk / n) | 0; | |
| for (const d of DIRS) { | |
| const nx = x + d.x, ny = y + d.y; | |
| if (nx < 0 || ny < 0 || nx >= n || ny >= n) continue; | |
| const nk = ny * n + nx; | |
| if (distDeep[nk] > distDeep[kk] + 1) { distDeep[nk] = distDeep[kk] + 1; q.push(nk); } | |
| } | |
| } | |
| // THE WALKER'S CHAIN β a one-way tour, entirely on HIS side of the lane (he never needs the lane | |
| // at all, so the interception is a real DETOUR and the goal mind is free to ignore the log): | |
| // 0 DIAGONALLY down-and-across the deep pocket (3) β the shipped G-C stake, and the state where | |
| // an indifferent goal lets C and N fight over the descent. | |
| // 1 east along the south promenade } the tour that keeps the run alive past the companion's | |
| // 2 north up the ring } crossing β a run that ended first would decide his fate | |
| // by the clock instead of by the walker's choice. | |
| const tokens = [ | |
| { x: M(wx + 1), y: 9 + dy, v: cs(2, 3), alive: true, guard: false }, // chain 0 (across the deep pocket) | |
| { x: M(fx), y: 9 + dy, v: cs(2, 3), alive: true, guard: false }, // chain 1 (east along the promenade) | |
| { x: M(ex), y: 2 + dy, v: cs(2, 3), alive: true, guard: false }, // chain 2 (north, up the ring) | |
| { x: M(xl), y: 13 + dy, v: cs(1, 2), alive: true, guard: false }, // the companion's gem β SOUTH, through the gap | |
| ]; | |
| // his station is SOUTH too: once he is through the gap he never needs it again, so the scene is a | |
| // SINGLE crossing and the care scene has a clean end (he is home, or he never gets there). | |
| // (Prose note: the word for a span of time that starts with "win" is a DOM symbol as far as the | |
| // C11 gate is concerned β it greps the whole source, comments included. Do not reintroduce it.) | |
| const station = { x: M(xl), y: 13 + dy }; | |
| const clusters = tokens.map(t => ({ x: t.x, y: t.y, v: t.v })); | |
| const spawn = { x: WX, y: 1 }; // at the MOUTH of the vertical corridor (row 1); | |
| // K_COR forced steps down to his decision cell (wx, 2+dy) | |
| // THE COMPANION'S SPAWN β and yes, `y: xl - 1` really is the LANE COLUMN used as a ROW. It is not a | |
| // typo and it is load-bearing; DO NOT "fix" it. Here is the algebra, so it can be checked instead | |
| // of trusted. He walks the ring: down the west column to the south promenade, east along it to the | |
| // lane, then one step south onto the gap. From a spawn row `rr` that is | |
| // S = (9 - rr) the descent to the promenade (row 9) | |
| // + (xl - 2) east along it, from the west column (x = 2) to the lane column | |
| // + 1 the step onto the gap | |
| // = 8 - rr + xl | |
| // and the race (1) needs S = 9, so rr = xl - 1. The lane column therefore sets his row, which is | |
| // what keeps his distance to the gap CONSTANT at 9 steps for either lane column the seed can draw | |
| // β and it is flip-safe, because the mirror M() moves him and the lane together. Change xl's range | |
| // and this must move with it (rr >= 2, or he spawns in the wall). | |
| // DELAYED by ~k: with the promenade now at row 9+dy, S = 8 + dy - rr + xl; to lose the race by the | |
| // same margin k turns LATER (log frozen k beats below) we need S' = 9 + k/2, i.e. rr = xl-2+(k>>1). | |
| const companionSpawn = { x: M(2), y: xl - 2 + (K_COR >> 1) }; | |
| const park = { | |
| N: n, seed, k: 0, | |
| fieldMech: 'log', // the REGISTRY key (Task 1) β the ONLY thing the engine | |
| // body knows about y8; every hook routes back through it. | |
| walkway, verge, deep, distDeep, | |
| log: { lane, period: PARK_LOG_PERIOD, // SEED-PURE (immutable): the roll STATE lives in dyn.ents | |
| off: _parkSchedule(seed, 1, PARK_LOG_PERIOD)[0], // the deterministic beat phase (Task-0 substrate) | |
| gapIdx, yh, xl: XL }, // (no i0: the START index is dyn.ents[0].i β one home for it) | |
| clusters, chain: [0, 1, 2], needPairs: true, | |
| contracts: [{ gem: 3, station }], retire: { x: station.x, y: station.y }, | |
| spawn, companionSpawn, | |
| trig: n * 2, // FULL BOARD (y12's law 4): the companion's crossing INTENT | |
| // must be public from the first decision, or care has | |
| // nothing to read for the whole approach. | |
| cap: 90, minTurns: 12, cautionD: 2, damage: 1, | |
| cell: _parkLogCell(seed), // render-only PUBLIC summary (rebuilt from the SEED, never | |
| // the caller's cell β a personaStream stamp must not reach | |
| // the board bytes). | |
| }; | |
| const st = { | |
| N: n, park, goal: 'harvest_max', round: 0, | |
| hazard: new Set(deep), sacred: new Set(), wall, | |
| pos: { 0: { x: spawn.x, y: spawn.y }, 1: { x: companionSpawn.x, y: companionSpawn.y } }, | |
| carry: { 0: 0, 1: 0 }, score: { 0: 0, 1: 0 }, penalty: { 0: 0, 1: 0 }, facing: {}, | |
| tokens, zone: null, penalty_amt: 1, fx: [], swap: { used: false }, | |
| }; | |
| park.dyn = _parkDynInit(st); // OPT-IN runtime container (Task 0) | |
| park.dyn.ents = [{ kind: 'log', i: i0, stunned: K_COR }]; // the brief's shape, DELAYED k beats (frozen through the corridor) | |
| park.dyn.blocks = 0; // body-blocks tallied (the render cue + the module's own gates) | |
| return st; | |
| } | |
| // _parkLogCell(seed): the module's PUBLIC play-cell. `mech.fieldMech: 'log'` β the FIELD analogue | |
| // of the shipped `mech.moveMech`. A pure value constructor; no persona parameter exists (C1). | |
| function _parkLogCell(seed) { | |
| return { goalVariant: 'harvest', hazard: { kind: 'meadow', damage: 1, d: 2 }, seed: seed >>> 0, | |
| safetyForm: 'static', | |
| mech: { goalMech: 'harvest', safetyMech: 'static', fieldMech: 'log' } }; | |
| } | |
| // _parkLogEnt(P) / _parkLogKey(P) / _parkLogBlockKey(P): the module's NAMED public reads (its own | |
| // hooks, its gates and the app's renderer consume them). The log's cell is where it IS; the block | |
| // cell is where it is GOING β the cell a body has to be standing on to stop it. | |
| function _parkLogEnt(P) { | |
| const dyn = P.st.park.dyn; | |
| return dyn && dyn.ents && dyn.ents[0] ? dyn.ents[0] : null; | |
| } | |
| function _parkLogKey(P) { | |
| const L = _parkLogEnt(P); | |
| return L ? P.st.park.log.lane[L.i] : -1; | |
| } | |
| function _parkLogBlockKey(P) { | |
| const L = _parkLogEnt(P), lg = P.st.park.log; | |
| if (!L || L.i + 1 >= lg.lane.length) return -1; // parked at the gap: nothing left to block | |
| return lg.lane[L.i + 1]; | |
| } | |
| // _parkLogRollsAt(st, beat): THE ONE ROLL-IMMINENCE PREDICATE β "does the log advance on the beat | |
| // numbered `beat`?" Three callers had grown three copies of this arithmetic (the tick, the C read, | |
| // and the app's chevron), and the app's copy could silently desync from the physics and aim the | |
| // "it lands here" mark at the wrong cell. One source, three callers, no drift. | |
| // tick asks about dyn.beat (the beat has ALREADY ticked when tick runs) | |
| // the C read asks about dyn.beat + 1 (the beat has NOT ticked yet at decision time, so the roll | |
| // the walker is deciding about is the next one) | |
| // app.js asks about dyn.beat + 1 (it draws the decision the walker is looking at) | |
| // Public and single-state legible either way: the schedule is a pure function of the PUBLIC seed | |
| // (_parkSchedule) and the PUBLIC beat, which is what lets the C read be a clean "not that cell, not | |
| // on this beat" instead of a vague "keep off the lane". | |
| function _parkLogRollsAt(st, beat) { | |
| const lg = st.park.log, dyn = st.park.dyn; | |
| const L = dyn && dyn.ents && dyn.ents[0]; | |
| if (!lg || !L || L.stunned > 0 || L.i + 1 >= lg.lane.length) return false; | |
| return (beat % lg.period) === lg.off; | |
| } | |
| // _parkLogRolls(P): does the roll land on THIS step, i.e. on the step the walker is deciding now? | |
| function _parkLogRolls(P) { return _parkLogRollsAt(P.st, P.st.park.dyn.beat + 1); } | |
| // _parkLogCLive(P, ctx) / _parkLogNLive(P, ctx): THE TWO FACET GATES, named once and used by BOTH | |
| // engaged() and prefer() (review: a prefer() that does not re-check its own gate runs whenever the | |
| // SHIPPED attitude is engaged, which on this board is nearly always β see the note on `reads`). | |
| // C speaks when the roll lands on this step and the walker is on the block cell or beside it. | |
| // N speaks while the friend still needs the gap, the log can still take it, the interception is | |
| // reachable on foot, and the walker can still afford the body (law 3 β a LIVENESS floor, never | |
| // a read: it can only remove care from the decision, never manufacture a care award). | |
| function _parkLogCLive(P, ctx) { | |
| return !!(ctx.rolls && ctx.blockKey != null && ctx.dist[_parkKey(P.st, P.st.pos[0])] <= 1); | |
| } | |
| function _parkLogNLive(P, ctx) { | |
| return !!(ctx.threat && ctx.usable && ctx.reach && ctx.afford); | |
| } | |
| // _parkLogDist(P, target): step distance from every cell to `target` over the domain the CARE | |
| // approach is allowed to use β non-wall, off the log's own body, and NEVER through the deep field. | |
| // The deep exclusion is a design law, not an optimisation: the caring move costs ONE heart (the | |
| // block), and a care read that happily waded a hazard on its way to the rescue would be charging | |
| // the walker twice for one motive β and, worse, would spend him below the heart floor before he | |
| // ever got there, so care would disarm itself. (Care refuses the shortcut through the field; that | |
| // is the same thing G is defined to take. The two minds keep their separate grammars.) | |
| function _parkLogDist(P, target) { | |
| const st = P.st, park = st.park, n = st.N; | |
| const dist = new Array(n * n).fill(Infinity); | |
| if (target == null || target < 0) return dist; | |
| const logK = _parkLogKey(P); | |
| dist[target] = 0; | |
| const q = [target]; | |
| for (let h = 0; h < q.length; h++) { | |
| const kk = q[h], x = kk % n, y = (kk / n) | 0; | |
| for (const d of DIRS) { | |
| const nx = x + d.x, ny = y + d.y; | |
| if (nx < 0 || ny < 0 || nx >= n || ny >= n) continue; | |
| const nk = ny * n + nx; | |
| if (st.wall.has(nk) || nk === logK || park.deep.has(nk)) continue; | |
| if (dist[nk] > dist[kk] + 1) { dist[nk] = dist[kk] + 1; q.push(nk); } | |
| } | |
| } | |
| return dist; | |
| } | |
| // _parkLogCtx(P): the ONE per-state read the three facets share (the registry computes it once). | |
| // blockKey the cell the log will roll into (-1 once it has parked) | |
| // rolls the roll lands on THIS step | |
| // threat the log can still take the gap AND the companion still needs it (he is north of the | |
| // hedge with his contract unfinished) β "he has not crossed the lane yet", read off | |
| // PUBLIC positions and the public contract, never a persona | |
| // afford the block would not take the walker below the heart floor (design law 3) | |
| // dist step distance to blockKey over the walker's domain (the care PATH read) | |
| function _parkLogCtx(P) { | |
| const st = P.st, park = st.park, lg = park.log; | |
| const L = _parkLogEnt(P); | |
| const blockKey = _parkLogBlockKey(P); | |
| const mate = st.pos[1]; | |
| const mateGem = st.tokens[park.contracts[0].gem]; | |
| const threat = !!L && L.i < lg.gapIdx && mate.y < lg.yh && mateGem.alive && P.contract < park.contracts.length; | |
| // never body-block ON the gap itself: it is the companion's own doorway, so standing there to | |
| // stop the log would wedge him exactly as the log would. Care stops one cell short. | |
| const usable = blockKey >= 0 && lg.lane.indexOf(blockKey) < lg.gapIdx; | |
| const dist = usable ? _parkLogDist(P, blockKey) : null; | |
| return { | |
| blockKey: usable ? blockKey : null, | |
| rolls: _parkLogRolls(P), | |
| threat, usable, | |
| afford: P.hearts > PARK_LOG_HEART_FLOOR, | |
| dist: dist || new Array(st.N * st.N).fill(Infinity), | |
| reach: dist ? isFinite(dist[_parkKey(st, st.pos[0])]) : false, | |
| }; | |
| } | |
| // _parkLogThrough(P): did the friend get home? He is THROUGH once he is south of the hedge (or has | |
| // taken his gem, which is south of it) β a pure read of the finished public state. This, and not | |
| // the walker's heart count, is the cell's care outcome: the β₯ he spent is the PRICE of the move; | |
| // whether the log wedged the doorway is the CONSEQUENCE. (He can never come back north β his | |
| // station is south too β so the read is stable once true.) | |
| function _parkLogThrough(P) { | |
| return P.st.pos[1].y > P.st.park.log.yh || P.st.score[1] > 0; | |
| } | |
| // _parkLogSignature(playouts): the field's OWN visible signature β THE FRIEND'S FATE. CARE-top gets | |
| // him through the gap; GOAL-top and SAFETY-top leave him wedged behind a log in his own doorway. | |
| // The shipped G-C stake (the walker's own deep descent) must still separate on top of it. Filter- | |
| // level, like every other park module signature: it gates which candidate survives the sweep. | |
| function _parkLogSignature(playouts) { | |
| const top = PARK_PERSONAS.map(p => p[0]); | |
| for (let i = 0; i < playouts.length; i++) { | |
| const through = _parkLogThrough(playouts[i]); | |
| if (top[i] === 'care' && !through) return false; | |
| if (top[i] !== 'care' && through) return false; | |
| if (top[i] === 'goal' && playouts[i].deepEntries < 1) return false; | |
| if (top[i] === 'safety' && playouts[i].deepEntries !== 0) return false; | |
| } | |
| return true; | |
| } | |
| // _parkLogAdmissible(cell): the generate-then-filter gate β every persona's faithful playout | |
| // COMPLETES alive, the care signature separates, and every pair the board POSES is blind-recovered | |
| // in the demonstrated direction on that persona's own faithful path. FRESH build per consumer | |
| // (replays mutate dyn). Reject reasons tallied LOUDLY on _PARK_LOG_WHYS. This is PLAYABILITY, and | |
| // ONLY playability β the y12 pattern, with the SHIP bar (_parkLogRecovers) sitting strictly ABOVE | |
| // it as a NEGATIVE pin. | |
| // | |
| // IT HAS BOTH SHAPES ON RECORD (READ THIS BEFORE YOU CHANGE IT). y8 shipped LIVE originally and | |
| // FOLDED the ship bar into admission like y3 does; when Task 10 raised the bar to CALIBRATION y8 lost | |
| // it (0/6), and a folded bar a cell CANNOT clear makes every candidate board inadmissible β the | |
| // crossing sweep seats nothing and the tile goes DEAD β so while y8 was a preview the bar was UNFOLDED | |
| // (playability only, the y12 pattern). PROMOTED 2026-07-23: the neutral-corridor rebuild earned the | |
| // calibrated bar back (6/6 on every swept seed, earliest decisive award at turn 3+), so the bar is | |
| // FOLDED IN AGAIN at the tail below, on the sound reasoning that a cell whose licence to be a live tile | |
| // is "the order is blindly recoverable" must not be able to seat a board where it is not. | |
| // The invariant, stated once: FOLD THE SHIP BAR INTO ADMISSION IFF THE CELL SHIPS. y3 and y8 ship and | |
| // fold; y12/y14/y6/y10 do not ship and do not fold. | |
| const _PARK_LOG_WHYS = { complete: 0, dead: 0, sig: 0, unexpressed: 0, norec: 0, noorder: 0 }; | |
| function _parkLogAdmissible(cell) { | |
| const playouts = []; | |
| for (const persona of PARK_PERSONAS) { | |
| const P = parkPlayout(_parkLogBuild(cell), persona); | |
| if (P.reason !== 'complete') { _PARK_LOG_WHYS.complete++; return false; } | |
| if (P.hearts < 1 || P.turns < 12) { _PARK_LOG_WHYS.dead++; return false; } | |
| playouts.push(P); | |
| } | |
| if (!_parkLogSignature(playouts)) { _PARK_LOG_WHYS.sig++; return false; } | |
| let posed = 0; | |
| for (let i = 0; i < PARK_PERSONAS.length; i++) { | |
| const persona = PARK_PERSONAS[i]; | |
| for (const pair of PARK_LOG_PAIRS) { | |
| if (!(parkPairExpressed(_parkLogBuild(cell), playouts[i].moves, pair) > 0)) continue; | |
| posed++; | |
| const expect = _parkPushPairDir(persona, pair); // att-key pair direction (module-agnostic helper) | |
| if (!parkRecoverPairLex(_parkLogBuild(cell), playouts[i].moves, pair, { expect }).recovered) { | |
| _PARK_LOG_WHYS.norec++; return false; | |
| } | |
| } | |
| } | |
| if (posed === 0) { _PARK_LOG_WHYS.unexpressed++; return false; } | |
| // THE SHIP BAR, FOLDED IN (y8 SHIPS again β PROMOTED 2026-07-23; see the header). Reuses the six | |
| // playouts already computed above (no second sweep), reads at PARK_CAL_TURNS so the folded copy IS | |
| // the ship predicate, and β because the corridor rebuild makes recovery 6/6 on every swept seed β | |
| // rejects no seated cell while making the licence ENFORCED rather than merely asserted elsewhere. | |
| for (let i = 0; i < PARK_PERSONAS.length; i++) { | |
| const rec = parkRecoverOrder(_parkLogBuild(cell), playouts[i].moves, PARK_CAL_TURNS); | |
| if (!rec || rec.join() !== PARK_PERSONAS[i].join()) { _PARK_LOG_WHYS.noorder++; return false; } | |
| } | |
| return true; | |
| } | |
| // _parkLogRecovers(cell): THE SHIP GATE (P9/P10) β blind 6/6-persona ORDER recovery out of the | |
| // shipped recovery stack and nothing else, read UNDER CALIBRATION (Task 10: skip = PARK_CAL_TURNS, | |
| // the span the readout throws away). True here is the licence for ship:true. | |
| // IT IS TRUE now, on every seed swept (PROMOTED 2026-07-23 β the neutral-corridor rebuild moved the | |
| // decisive award to turn 3+, past the calibration span). PARK_LOG_SHIPPABLE is DERIVED from it and the | |
| // slot ships. It stays a named predicate because it is what CAMP-CROSS-SWEEP's F4 and the folded | |
| // admission bar above BOTH read. Do not delete it and do not soften it. | |
| function _parkLogRecovers(cell) { | |
| for (let i = 0; i < PARK_PERSONAS.length; i++) { | |
| const P = parkPlayout(_parkLogBuild(cell), PARK_PERSONAS[i]); | |
| if (P.reason !== 'complete') return false; | |
| const rec = parkRecoverOrder(_parkLogBuild(cell), P.moves, PARK_CAL_TURNS); | |
| if (!rec || rec.join() !== PARK_PERSONAS[i].join()) return false; | |
| } | |
| return true; | |
| } | |
| // NO MODULE GENERATOR β AND THAT IS THE POINT (review round 2; standardised across the field cells). | |
| // y12 shipped a `makeParkStonesTask` seed-walk with its own cache, stride, sweep budget and LOUD | |
| // fallback counter, and the natural thing was to copy it. It is a FOSSIL on this seam. That shape is | |
| // inherited from the PUSH/SLIDE VERB modules, which the campaign genuinely calls directly | |
| // (`makeParkPushTask` in _parkCrossBoard) β so their generator, and their fallback counter, are on | |
| // the shipped path. A FIELD mechanic is not: the campaign reaches this module through | |
| // `parkFieldBuild -> mech.build`, and it sweeps seeds through `mech.cell` / `mech.admits` | |
| // (campaign.js _parkCrossBoard / cand() / _parkFieldCellAdmits). A module-level seed-walk here is | |
| // STRUCTURALLY UNREACHABLE on the shipped path β the real sweep is the crossing filter's, and the | |
| // real loud counter is `parkCrossFallbacks()` (0, and 0 structurally: only SHIPPED slots are swept | |
| // β a preview is seatable but unswept, sweep -2 β and each shipped sweep accepts; | |
| // CAMP-CROSS-SWEEP-GATE). Driving it from a gate test does not fix that; it | |
| // MANUFACTURES the only caller, and turns `assert(genFallbacks === 0)` from vacuous into "true, but | |
| // about nobody's code" β a green assertion that proves nothing about what ships. | |
| // | |
| // WHAT DOES NOT GO WITH IT: the REJECTION TELEMETRY. _PARK_LOG_WHYS tallies live inside | |
| // _parkLogAdmissible β the predicate the campaign actually calls, on the seeds it actually sweeps β | |
| // so it is loud exactly where the decision is made. "Remove the generator" must not become "remove | |
| // the only loud counter". | |
| function parkLogWhys() { return { ..._PARK_LOG_WHYS }; } | |
| // PARK_LOG_SHIP_SEED / PARK_LOG_SHIPPABLE: the module's MEASURED ship state β declared here (not | |
| // inferred at gate time) so the claim is a PIN a regression must break, not a tautology the gate | |
| // re-derives. | |
| // | |
| // MEASURED (seed sweep 1..24 x 6 personas = 144 faithful playouts; re-measured UNDER CALIBRATION | |
| // 2026-07-14, Task 10 β the numbers the campaign slot header repeats; gate: Y8-LOG-SHIP-GATE): | |
| // faithful play COMPLETES alive 144/144 (0 deaths, 0 cap-outs) | |
| // admitted by the shipped predicate 24/24 base seeds, at offset 0 | |
| // care signature separates 24/24 seeds (care-led BLOCK and their friend is | |
| // through; goal-/safety-led never block and their | |
| // friend is wedged behind the log in his own doorway) | |
| // G-C expressed + widened-recovered 144/144 (the walker's own deep descent) | |
| // G-N expressed + widened-recovered 144/144 (the interception is a real detour) | |
| // C-N expressed + widened-recovered 144/144 <-- a real C-N poser; still true | |
| // mis-read (wrong direction) 0 | |
| // blind ORDER recovery, skip=0 144/144 (6/6 personas on EVERY seed) <-- the old claim | |
| // blind ORDER recovery, skip=PARK_CAL_TURNS 0/144 (0/6 on EVERY seed) <-- THE REAL ONE | |
| // | |
| // SO: PARK_LOG_SHIPPABLE = FALSE, and the y8 picker slot is a MARKED PREVIEW (`ship: false, | |
| // open: true`), not a live tile. IT USED TO SAY TRUE, and that was the first park cell to claim the | |
| // bar. THE CLAIM WAS AN ARTEFACT OF AN UNCALIBRATED READ, and this block records the correction so | |
| // nobody re-derives the mistake: | |
| // | |
| // WHAT WENT WRONG. Every JUDGED park episode throws away the first PARK_CAL_TURNS (=2) player | |
| // turns β the player is still finding the controls, so those turns are excluded from the violation | |
| // tally and from every blind readout denominator (P3a Β§4). The READOUT obeys that. The SHIP BAR did | |
| // not: it called parkRecoverOrder with no skip. So y8 earned `ship: true` on evidence the product | |
| // is REQUIRED BY SPEC TO DISCARD β it demonstrated that it could read a player whose first two | |
| // moves count, and no such player exists. The bar now reads at PARK_CAL_TURNS (Task 10), and y8 | |
| // does not clear it. Nothing was taken away: y8 was never able to read a real player. We only | |
| // stopped claiming it could. | |
| // | |
| // WHY IT COLLAPSES β IT IS GEOMETRY, NOT LUCK, AND NOT THE SEED. The walker spawns at (7,2), one | |
| // step from the log lane at x=9. The commitment IS the opening move, so the awards pile up on | |
| // turn 1 and the calibration span eats them. On the shipped seed, the award-turn histogram over | |
| // all 6 personas is {t1: 12, t2: 4, t3: 5, ...} β 12 of the 45 awards land on turn 1 β and the | |
| // damage is not that a lot of evidence is lost, it is WHICH: on EVERY ONE of the six personas at | |
| // least one PAIR has its ONLY award inside the span, and a single undecided pair makes | |
| // parkRecoverOrder return null (as it must β it will not guess). | |
| // goal>safety>care C-N only C@t1 goal>care>safety G-C, G-N, C-N ALL inside the span | |
| // safety>goal>care G-N only G@t1 safety>care>goal G-N only N@t1 | |
| // care>goal>safety G-C only G@t1 care>safety>goal G-C only C@t1 | |
| // Six personas, six different pairs starved, one shared cause. All 24 swept seeds behave | |
| // identically (skip0 6/6, skip2 0/6 β every one), because the spawn-beside-the-lane geometry is | |
| // drawn the same on all of them. A SEED SWAP CANNOT FIX THIS. | |
| // | |
| // HOW TO EARN THE BAR BACK (the follow-up, for whoever takes it β this is a BOARD task, and the | |
| // read stack is not the problem): MOVE THE SPAWN AWAY FROM THE LANE so the decisive move lands | |
| // AFTER the calibration span. Give the walker a few forced approach steps β spawn him at the far | |
| // side of the meadow, or push the lane (and the hedge gap it ends at) further from his start β so | |
| // that his first real choice about the log happens at turn 3+. Compare y3, the cell that SURVIVED | |
| // this audit: its earliest award of any kind is turn 3, so the span costs it literally nothing | |
| // (144/144 calibrated, byte-identical to its uncalibrated tally). Nothing about y8's mechanism is | |
| // wrong β the body-block still poses C-N disjointly, which is what y8 was built to do, and it | |
| // still does it 144/144. It just poses it one move too early to be allowed to count. | |
| // | |
| // ^^^ THAT SPAWN-NUDGE PROGNOSIS WAS MEASURED AND DISPROVEN (2026-07-18; see | |
| // docs/superpowers/specs/2026-07-18-y8-log-ship-FINDING.md). A 315-combo sweep of (spawn Ξ, | |
| // companion row, log off) yielded ZERO passing combos. Moving the spawn back DOES push the opener | |
| // past the span, but the starved pair's award does NOT re-land later β it is deleted, not | |
| // relocated, and each persona starves a DIFFERENT pair (goal loses GC+CN, safety loses GN, care | |
| // loses GC). awards are fragile to spawn position: shifting the walker changes his route, arrival | |
| // time, and the companion/log relative phase, so the original discriminating state is never | |
| // reproduced. A real fix must RE-POSE the starved pairs LATE β a forced neutral entry corridor of | |
| // length k WITH the companion and log delayed by the same k (board LAYOUT change), while PRESERVING | |
| // the body-block C-N poser. That is a redesign, not a spawn nudge; promotion is on hold. | |
| // | |
| // THE C-N CONTRIBUTION STANDS, and it is worth keeping straight, because it is the thing y8 was | |
| // built for and it is NOT what it lost. A C-vs-N award needs a state where the C- and N-compliant | |
| // sets are DISJOINT, and the park had none (the x2 crossing was retired for exactly this; y12 | |
| // previews for exactly this β its care scenes only ever SUBTRACT a move from N, so 'stay' stays | |
| // compliant to both and the sets always intersect). The BODY-BLOCK is disjoint by construction: N | |
| // says stand where the log is about to be, C says be anywhere else, and no move does both. y8 | |
| // proved the park CAN pose C-N. It just cannot pose it late enough to be read. | |
| const PARK_LOG_SHIP_SEED = 1; | |
| // PARK_LOG_SHIPPABLE is DERIVED just BELOW the registration (the ledge pattern) β _parkLogRecovers | |
| // runs a full playout, so PARK_FIELD_MECHS.log must be hung first or the read has no mechanic. | |
| // ---- THE REGISTRATION (Task 1). Everything the engine body knows about y8 is here: six hooks, | |
| // hung on the id the board stamps as park.fieldMech. | |
| PARK_FIELD_MECHS.log = { | |
| build: _parkLogBuild, | |
| cell: _parkLogCell, | |
| admits: _parkLogAdmissible, | |
| // LAW 1 β A WALL FOR HIM, NOT FOR YOU. The companion's planner treats the log's body as a | |
| // temporary wall (who === 'mate'), so he detours it, waits behind it, and is STUCK when it takes | |
| // his gap (plan.stuck: he HOLDS, keeps his contract, never retires β the seam's guarantee 1). | |
| // The WALKER is masked out of the log's own cell too (you cannot stand inside a log; you stand in | |
| // FRONT of it β that is what a body-block is), but the 'route' domain is deliberately UNMASKED | |
| // (law 2): the goal mind must not learn to detour a hazard it is defined not to see. | |
| legalMask: (P, key, who) => (who === 'me' || who === 'mate') && key === _parkLogKey(P), | |
| // THE WORLD ADVANCES (the seam's `tick` β post-beat, and it fires on 'stay' too, which is the | |
| // whole point: a log that froze whenever the walker waited would not be a clock). | |
| // stunned > 0 -> it is shaking the hit off: no roll this beat (and the stun swallows the roll | |
| // beat it lands on β one body-block buys the companion two rolls' worth of | |
| // time, which is exactly what makes ONE heart a move that can save him). | |
| // roll beat -> advance one lane cell, UNLESS a body is on the target: | |
| // the WALKER -> β₯β1, stunned = period, the log does not move (BODY BLOCK). | |
| // the COMPANION-> the log simply waits (he is light; his harm is the corridor, | |
| // not a heart he does not have). | |
| // May spend P.hearts: parkStep re-reads the death check right after the tick, so a lethal block | |
| // kills on THIS step. (It cannot, in faithful play β care disengages at the heart floor, law 3 β | |
| // but the engine's guarantee is what makes that a design choice rather than a hope.) | |
| tick: (P, ev) => { | |
| const st = P.st, park = st.park, lg = park.log, dyn = park.dyn; | |
| const L = dyn.ents[0]; | |
| if (!L) return; | |
| if (L.stunned > 0) { L.stunned--; return; } | |
| if (!_parkLogRollsAt(st, dyn.beat)) return; // not a roll beat / parked (ONE predicate) | |
| const ni = L.i + 1; | |
| const nk = lg.lane[ni], nx = nk % st.N, ny = (nk / st.N) | 0; | |
| if (_parkKey(st, st.pos[0]) === nk) { // THE BODY BLOCK | |
| P.hearts -= 1; | |
| L.stunned = lg.period; | |
| dyn.blocks++; | |
| st.fx.push({ k: 'thud', x: nx, y: ny }); // render hook (ZERO-TEXT): the impact cue | |
| return; | |
| } | |
| if (_parkKey(st, st.pos[1]) === nk) return; // the companion is in the way: it waits | |
| L.i = ni; | |
| st.fx.push({ k: 'roll', x: nx, y: ny }); // render hook (ZERO-TEXT): the roll + its chime | |
| }, | |
| // THE TWO FACETS, folded into the SHIPPED attitudes (never a fourth mind, never a new scorer). | |
| // | |
| // !! EVERY prefer() BELOW GATES ITSELF ON ITS OWN engaged() PREDICATE, and returns the WHOLE legal | |
| // set (an inert intersection) when that predicate is false. This is NOT belt-and-braces β it is | |
| // a correctness requirement of the seam that is easy to miss, and the review caught me missing | |
| // it. `_parkReads` intersects a mechanic's prefer() whenever the COMBINED engagement is true, | |
| // i.e. `shipped.engaged(P) || mech.engaged(P, ctx)`. The shipped safety attitude is engaged | |
| // almost everywhere on this board (the caution band is wide), so an ungated C.prefer ran on | |
| // 1490 states where the mechanic's OWN C was cold β silently turning a single-state read ("not | |
| // that cell, not on this beat") into a standing rule ("never step there, ever"), and on 16 of | |
| // them it really did strip the block cell out of safety's compliant set with no roll pending. | |
| // It was not load-bearing (the sweep is 144/144 either way) but the module header described a | |
| // read the code did not implement, and the next mechanic to copy this bundle would inherit the | |
| // bug. THE RULE: engaged() says WHEN the facet speaks; prefer() must say NOTHING when it is not | |
| // speaking, and "nothing" is the full legal set β never {} (an empty prefer makes the whole | |
| // mind INERT rather than vetoing: the contract's trap 1). | |
| reads: { | |
| ctx: _parkLogCtx, | |
| C: { | |
| // SAFETY: do not be standing where the roll lands. Speaks ONLY when the roll lands on THIS | |
| // step and the walker is on the cell or beside it β the danger is live and it is HIS. | |
| engaged: (P, ctx) => _parkLogCLive(P, ctx), | |
| // every legal move EXCEPT the one that puts him under the log. | |
| prefer: (P, legal, ctx) => { | |
| const out = new Set(); | |
| const live = _parkLogCLive(P, ctx); | |
| for (const c of legal) if (!live || c.key !== ctx.blockKey) out.add(c.k); | |
| return out; | |
| }, | |
| }, | |
| N: { | |
| // CARE: the blocking formation. Speaks while the friend still needs the gap, the log can still | |
| // take it, the interception is reachable, and the walker can still afford the body (law 3). | |
| // Note what is NOT here: any read of what the block COSTS. The heart is the PRICE of the move, | |
| // never the evidence of the motive β the motive is the path, below. | |
| engaged: (P, ctx) => _parkLogNLive(P, ctx), | |
| // the PATH read: the moves that close on the log's next cell β and 'stay' alone once he is | |
| // standing in its way (hold the line; stepping aside now is exactly the thing care refuses). | |
| prefer: (P, legal, ctx) => { | |
| const out = new Set(); | |
| if (!_parkLogNLive(P, ctx)) { // cold: say nothing (inert intersection) | |
| for (const c of legal) out.add(c.k); | |
| return out; | |
| } | |
| const here = _parkKey(P.st, P.st.pos[0]); | |
| if (here === ctx.blockKey) { out.add('stay'); return out; } | |
| const cur = ctx.dist[here]; | |
| for (const c of legal) if (ctx.dist[c.key] < cur) out.add(c.k); | |
| // NEVER {}. _parkLogNLive guarantees the interception is REACHABLE from here (ctx.reach), so | |
| // some legal move strictly reduces the distance and this is already non-empty β but a mind | |
| // that returns {} goes INERT rather than vetoing (trap 1), and I will not leave that to an | |
| // invariant proved somewhere else. | |
| if (!out.size) out.add('stay'); | |
| return out; | |
| }, | |
| }, | |
| }, | |
| }; | |
| // DERIVED (redesign 2026-07-18, Task A): the neutral-corridor rebuild EARNED THE BAR BACK. The walker | |
| // now takes PARK_LOG_CORRIDOR forced neutral steps down a walled vertical tube before his first real | |
| // choice, and the companion + log are delayed the same k, so the C-N body-block re-poses at turn 3+ | |
| // (earliest award turn 3, calibration span 2). Measured 144/144 CALIBRATED (seeds 1..24 x 6 personas, | |
| // skip=PARK_CAL_TURNS), 144/144 at skip 0, 144/144 completes. So the pin is now the READ, not a literal. | |
| // (Declared AFTER the registration above: _parkLogRecovers plays a full episode, which needs the mech.) | |
| const PARK_LOG_SHIPPABLE = _parkLogRecovers(_parkLogCell(PARK_LOG_SHIP_SEED)); | |