// M2.8 — rock: the inert resource. It is scattered at the world's birth, // dropped and shattered by the song, impassable to the population, and // quarried by builders — carried, walled, ground into soil. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { resolve, dirname } from 'node:path'; import { analyse } from '../src/audio/analyse.js'; import { decodeWav, mono } from '../src/audio/wav.js'; import { buildSong } from '../src/song/song.js'; import { Run } from '../src/film.js'; import { OP, S_ROCK, A_DIG, A_BUILD } from '../src/song/strains.js'; const HERE = dirname(fileURLToPath(import.meta.url)); const BANK = resolve(HERE, '../..', 'flow-state/test/songs'); function load(name, seed = 12345) { const wav = decodeWav(readFileSync(resolve(BANK, `${name}.wav`))); return buildSong(analyse(mono(wav.channels), wav.sampleRate), seed); } function run(name, seed = 12345, field = 64) { const song = load(name, seed); const r = new Run(song, { fps: 30, seed, width: 240, height: 135, field, genesis: 0 }); return r; } const rockMass = (w) => { let s = 0; for (let i = 0; i < w.n; i++) s += w.rock[i]; return s; }; const maskedCells = (w) => { let s = 0; for (let i = 0; i < w.n; i++) s += w.rockMask[i]; return s; }; test('the world is born with stone in it (deterministically)', () => { const a = run('drone').world; const b = run('drone').world; assert.ok(rockMass(a) > 5, `scatter should hold stone (${rockMass(a).toFixed(1)})`); assert.ok(maskedCells(a) > 0, 'some ground is impassable from birth'); assert.deepEqual(Array.from(a.rock), Array.from(b.rock)); }); test('the song drops rock on heavy hits and shatters it on high ones', () => { const live = run('runner').world; const mute = run('runner').world; mute.program.rock.drop = 0; mute.program.rock.shatter = 0; for (let f = 0; f < 600; f++) { live.step(); mute.step(); } // The sky's ledger: a live song drops stone, a mute one never does. assert.ok(live.rockFallen > 5, `rockfall should accumulate (${live.rockFallen.toFixed(1)})`); assert.equal(mute.rockFallen, 0, 'a mute sky drops nothing'); assert.ok(rockMass(live) > rockMass(mute), `the live world holds more stone (${rockMass(live).toFixed(1)} vs ${rockMass(mute).toFixed(1)})`); }); test('rock is a wall: the population cannot cross it', () => { const w = run('drone').world; const F = w.field; // A solid vertical wall of stone down the middle, north to south. for (let y = 0; y < F; y++) { for (let x = 30; x < 34; x++) w.rock[y * F + x] = 1.5; } w.refreshRockMask(); // A crowd planted on the west side, food on both sides so the only // reason not to spread east is the wall. for (let y = 8; y < 56; y++) { for (let x = 4; x < 26; x++) { const i = y * F + x; for (let s = 0; s < w.m; s++) w.pop[s][i] = 2; w.alleg[0][i] = 2 * w.m; w.resource[i] = 1.2; } for (let x = 40; x < 60; x++) w.resource[y * F + x] = 1.2; } w.bodies[0].cx = 0.2; w.bodies[0].cy = 0.5; w.bodies[0].alive = true; if (!w.aliveBodies.includes(0)) w.aliveBodies.push(0); w.recount(); for (let f = 0; f < 300; f++) w.step(); let east = 0; for (let y = 0; y < F; y++) { for (let x = 34; x < F; x++) east += w.density[y * F + x]; } assert.ok(east < w.count * 0.02, `the wall must hold (${east.toFixed(1)} of ${w.count.toFixed(0)} leaked east)`); }); test('builders quarry: rock goes down, soil and burden appear', () => { const w = run('dusk').world; const F = w.field; // Every strain digs where rock lies. for (const s of w.strains) { s.code = [ [OP.SENSE, 4, S_ROCK, 1], [OP.IFGT, 4, 0.05, 3], [OP.HALT], [OP.ACT, A_DIG, 4, 1], [OP.HALT], ]; } // A rich seam under the crowd. for (let y = 20; y < 40; y++) { for (let x = 20; x < 40; x++) { const i = y * F + x; w.rock[i] = 1.4; for (let s = 0; s < w.m; s++) w.pop[s][i] = 3; w.alleg[0][i] = 3 * w.m; } } w.refreshRockMask(); w.bodies[0].alive = true; w.bodies[0].cx = 0.45; w.bodies[0].cy = 0.45; if (!w.aliveBodies.includes(0)) w.aliveBodies.push(0); w.recount(); const before = rockMass(w); let foodBefore = 0; for (let i = 0; i < w.n; i++) foodBefore += w.resource[i]; for (let f = 0; f < 120; f++) w.step(); const after = rockMass(w); let foodAfter = 0; for (let i = 0; i < w.n; i++) foodAfter += w.resource[i]; let carry = 0; for (let s = 0; s < w.m; s++) for (let i = 0; i < w.n; i++) carry += w.carry[s][i]; assert.ok(after < before - 1, `quarries must open (${before.toFixed(1)} → ${after.toFixed(1)})`); assert.ok(carry > 0.2, `the burden must be carried (${carry.toFixed(2)})`); assert.ok(foodAfter > foodBefore, `grinding yields soil (${foodBefore.toFixed(1)} → ${foodAfter.toFixed(1)})`); }); test('builders build: carried rock rises as walls', () => { const w = run('dusk').world; // Every strain builds. for (const s of w.strains) { s.code = [ [OP.SET, 4, 1], [OP.ACT, A_BUILD, 4, 1], [OP.HALT], ]; } // No weathering during the test: we are measuring the masons, not the rain. for (const sec of w.program.sections) sec.operators.erode = 0; // A loaded block on cleared, open ground. const F = w.field; for (let y = 26; y < 35; y++) { for (let x = 26; x < 35; x++) w.rock[y * F + x] = 0; } w.refreshRockMask(); for (let s = 0; s < w.m; s++) w.carry[s][30 * F + 30] = 400; const before = rockMass(w); for (let f = 0; f < 120; f++) w.step(); const after = rockMass(w); // The four cells of the loaded block can hold 2.4 each; the weathering // floor eats ~2% over the run — the wall rise must dominate. assert.ok(after > before + 4, `carried rock must rise (${before.toFixed(2)} → ${after.toFixed(2)})`); });