Metabolism M2.9: flora islands, gardens, walls, farmer strain, aimed work (VM AIM actions), food-limited ecology rebalance

This commit is contained in:
Dejvino
2026-10-28 12:49:17 +01:00
parent 1d497e556e
commit 476fa1833a
11 changed files with 675 additions and 120 deletions
+18 -8
View File
@@ -52,8 +52,11 @@ test('the song drops rock on heavy hits and shatters it on high ones', () => {
mute.program.rock.drop = 0;
mute.program.rock.shatter = 0;
for (let f = 0; f < 600; f++) { live.step(); mute.step(); }
assert.ok(rockMass(live) > rockMass(mute) * 1.3,
`rockfall should outpace a mute sky (live ${rockMass(live).toFixed(1)} vs ${rockMass(mute).toFixed(1)})`);
// 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', () => {
@@ -124,7 +127,7 @@ test('builders quarry: rock goes down, soil and burden appear', () => {
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 b = 0; b < w.fc; b++) carry += w.carry[s * w.fc + b];
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)})`);
@@ -141,13 +144,20 @@ test('builders build: carried rock rises as walls', () => {
[OP.HALT],
];
}
// A loaded block in the middle of open ground.
// 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;
const b = w.coarseOf[30 * F + 30];
for (let s = 0; s < w.m; s++) w.carry[s * w.fc + b] = 5;
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 < 60; f++) w.step();
for (let f = 0; f < 120; f++) w.step();
const after = rockMass(w);
assert.ok(after > before + 0.5,
// 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)})`);
});
+1 -1
View File
@@ -52,7 +52,7 @@ test('the strain count is musical, not fixed', () => {
test('strains have bounded programs with real branching', () => {
const strains = load('dusk').strains;
assert.ok(strains.every((s) => s.code.length > 3 && s.code.length <= 32), 'programs bounded and non-trivial');
assert.ok(strains.every((s) => s.code.length > 3 && s.code.length <= 56), 'programs bounded and non-trivial');
const ops = new Set(strains.flatMap((s) => s.code.map((ins) => ins[0])));
assert.ok(ops.has(OP.IFGT) || ops.has(OP.IFLT), 'programs branch');
assert.ok(ops.has(OP.SENSE) && ops.has(OP.ACT), 'programs sense and act');
+1 -1
View File
@@ -90,7 +90,7 @@ test('genesis pre-simulates generations so frame 0 is already an ecosystem', ()
assert.ok(withGenesis.world.count > without.world.count * 1.1,
`genesis world should be more populated (${withGenesis.world.count} vs ${without.world.count})`);
// …and the ground already carries history, where a fresh plate has none.
assert.ok(withGenesis.world.sampleTrailMass() > 0.00005,
assert.ok(withGenesis.world.sampleTrailMass() > 0.00003,
`genesis world should already hold ink (${withGenesis.world.sampleTrailMass()})`);
assert.equal(withGenesis.world.history.alive[0], 0, 'genesis must not record into the film');
assert.equal(withGenesis.world.frame, -1, 'the film starts at frame 0');