Buildings cast no shadows: daylight passes player-built pieces (t37)

The lighting finds its own roof per column, skipping walls, belts, tubes, machines and molds,
and lets light through them like air, in both looks. Natural rock still shades; the
simulation's roof (steam condensing under a built roof) is unchanged. New test/t_light.js.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 16:42:07 -04:00
co-authored by Claude Opus 5.5
parent 5ce44f842e
commit 20d0627d75
5 changed files with 94 additions and 5 deletions
+63
View File
@@ -0,0 +1,63 @@
// Run the renderer's lighting against real simulation data, with only canvas storage stubbed.
const fs = require('fs');
const path = require('path');
const { load, suite, flatWorld } = require('./harness');
const { run } = load();
const { check, done } = suite();
for (const f of ['setup.js', 'light.js']) run(fs.readFileSync(path.join(__dirname, '../src/render', f), 'utf8'));
run(`
const SET = { lighting: 'full', daylight: 1, ambient: 0.12 };
R.lightC = {};
R.lightX = { createImageData: (w, h) => ({ data: new Uint8ClampedArray(w * h * 4) }), putImageData() {} };
R.vx0 = 0; R.vy0 = 100; R.bw = 320; R.bh = 80;
function lightAt(a, x, y) { return a[(Math.floor(y / R.lres) - R.ly0) * R.lw + Math.floor(x / R.lres) - R.lx0]; }
`);
flatWorld(run, 320, 200, 160);
run(`
bot.lamp = false;
for (let bx = 8; bx < 30; bx++) placeBlock(BP_WALL, RSTONE, bx, 20, true);
for (let by = 21; by < 39; by++) {
placeBlock(BP_WALL, RSTONE, 8, by, true);
placeBlock(BP_WALL, RSTONE, 29, by, true);
}
placeBlock(BP_BELT, 1, 12, 30, true);
placeBlock(BP_TUBE, 0, 14, 30, true);
addMachine('silo', 18, 30, 0);
addMold('bit', 24, 30, RSTONE, true);
for (let y = 80; y < 88; y++) for (let x = 180; x < 280; x++) setCell(y * W + x, STONE, AMB[y], F_NAT);
updateRoof();
const simRoof = Array.from(roofY);
`);
for (const mode of ['full', 'fast']) {
run(`SET.lighting = '${mode}'; computeLight();`);
for (const channel of ['lr', 'lg']) {
const [inside, open, rock, ground, openGround] = run(`[lightAt(R.${channel}, 80, 144), lightAt(R.${channel}, 152, 144), lightAt(R.${channel}, 232, 144), lightAt(R.${channel}, 80, 160), lightAt(R.${channel}, 152, 160)]`);
check(`${mode} ${channel}: under a built roof matches open daylight`, Math.abs(inside / open - 1) < 0.03, `${inside} / ${open}`);
check(`${mode} ${channel}: ground under the base matches outside`, Math.abs(ground / openGround - 1) < 0.03, `${ground} / ${openGround}`);
check(`${mode} ${channel}: natural overhang still shades`, rock < open * 0.95, `${rock} / ${open}`);
}
// Directly check transmission as daylight can hide an opaque piece in the open.
for (const [name, x, y] of [['wall', 32, 120], ['belt', 48, 120], ['tube', 56, 120], ['machine', 72, 120], ['mold', 96, 120]]) {
const a = run(`lightAt(R.latt, ${x}, ${y})`);
check(`${mode}: ${name} transmits like air`, Math.abs(a - Math.pow(0.978, mode === 'fast' ? 4 : 2)) < 1e-6, a);
}
}
check('lighting leaves the simulation roof unchanged', run('simRoof.every((y, x) => roofY[x] === y) && roofY[80] === 80'));
for (const material of ['STONE', 'SAND', 'WATER']) {
run(`setCell(96 * W + 80, ${material}, AMB[96], F_NAT); updateRoof(); computeLight();`);
check(`${material} above the view still blocks daylight`, run('lightAt(R.lr, 80, 144) < 0.95'));
run('setCell(96 * W + 80, EMPTY, AMB[96], 0);');
}
run('updateRoof(); computeLight();');
check('removing an obstruction restores daylight', run('lightAt(R.lr, 80, 144) === 1'));
for (const field of ['blockP[24 * BW + 20]', 'mach[96 * W + 80]', 'part[96 * W + 80]']) {
run(`setCell(96 * W + 80, STONE, AMB[96], F_NAT); ${field} = 1; updateRoof(); computeLight();`);
check(`${field}: built contents do not shade or absorb light`, run('lightAt(R.lr, 80, 144) === 1 && Math.abs(lightAt(R.latt, 80, 96) - Math.pow(0.978, 4)) < 1e-6'));
run(`${field} = 0; setCell(96 * W + 80, EMPTY, AMB[96], 0);`);
}
run('updateRoof();');
run(`SET.daylight = 0; temp[140 * W + 80] = 1500; computeLight();`);
check('hot material inside the base lights its surroundings', run('lightAt(R.lr, 88, 140) > 0.2'));
run(`temp[140 * W + 80] = AMB[140]; bot.x = 72; bot.y = 130; bot.lamp = true; computeLight();`);
check('the bot lamp still lights the base', run('lightAt(R.lr, 80, 140) > 0.2'));
done();