Files
deepgrain-foundry/test/light-cases.js
T

136 lines
7.6 KiB
JavaScript

// Lighting behaviour checks executed by the GPU browser harness.
await flatWorld(run, 320, 200, 160);
await 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_SCREW, 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();
window.simRoof = Array.from(roofY);
`);
for (const mode of ['full', 'fast']) {
await run(`SET.lighting = '${mode}'; computeLight();`);
for (const channel of ['lr', 'lg']) {
const [inside, open, rock, ground, openGround] = await 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], ['screw', 56, 120], ['machine', 72, 120], ['mold', 96, 120]]) {
const a = await run(`lightAt(R.latt, ${x}, ${y})`);
check(`${mode}: ${name} transmits like air`, Math.abs(a - Math.pow(0.978, mode === 'fast' ? 4 : 2)) < 1e-3, a);
}
}
check('lighting leaves the simulation roof unchanged', await run('simRoof.every((y, x) => roofY[x] === y) && roofY[80] === 80'));
for (const material of ['STONE', 'SAND', 'WATER']) {
await run(`setCell(96 * W + 80, ${material}, AMB[96], F_NAT); setCell(97 * W + 80, STONE, AMB[97], F_NAT); updateRoof(); computeLight();`);
check(`${material} above the view still blocks daylight`, await run('lightAt(R.lr, 80, 144) < 0.925'));
await run('setCell(96 * W + 80, EMPTY, AMB[96], 0); setCell(97 * W + 80, EMPTY, AMB[97], 0);');
}
await run('updateRoof(); computeLight();');
check('removing an obstruction restores daylight', await run('lightAt(R.lr, 80, 144) === 1'));
for (const field of ['blockP[24 * BW + 20]', 'mach[96 * W + 80]', 'part[96 * W + 80]']) {
await run(`setCell(96 * W + 80, STONE, AMB[96], F_NAT); ${field} = 1; updateRoof(); computeLight();`);
check(`${field}: built contents do not shade or absorb light`, await run('lightAt(R.lr, 80, 144) === 1 && Math.abs(lightAt(R.latt, 80, 96) - Math.pow(0.978, 4)) < 1e-3'));
await run(`${field} = 0; setCell(96 * W + 80, EMPTY, AMB[96], 0);`);
}
await run('updateRoof();');
await run(`SET.daylight = 0; temp[140 * W + 80] = 1500; computeLight();`);
check('hot material inside the base lights its surroundings', await run('lightAt(R.lr, 88, 140) > 0.2'));
await 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', await run('lightAt(R.lr, 80, 140) > 0.2'));
await flatWorld(run, 320, 200, 160);
await run(`
SET.daylight = 1; bot.lamp = false;
for (let bx = 8; bx < 30; bx++) placeBlock(BP_BELT, 1, bx, 20, true);
for (let x = 32; x < 120; x++) for (let y = 77; y < 80; y++) setCell(y * W + x, SAND, AMB[y], 0);
for (let x = 180; x < 280; x++) {
setCell(80 * W + x, STONE, AMB[80], F_NAT);
for (let y = 77; y < 80; y++) setCell(y * W + x, SAND, AMB[y], 0);
}
for (let y = 40; y <= 76; y += 2) setCell(y * W + 152, SAND, AMB[y], 0);
updateRoof();
`);
for (const mode of ['full', 'fast']) {
await run(`SET.lighting = '${mode}'; computeLight();`);
for (const channel of ['lr', 'lg', 'lb']) {
const [belt, open, stone] = await run(`[lightAt(R.${channel}, 80, 84), lightAt(R.${channel}, 160, 84), lightAt(R.${channel}, 232, 84)]`);
check(`${mode} ${channel}: a loaded belt leaves daylight below it`, Math.abs(belt - open) < 1e-3, `${belt} / ${open}`);
check(`${mode} ${channel}: sand on natural stone still shades below`, stone < open * 0.95, `${stone} / ${open}`);
const [streamGround, openGround] = await run(`[lightAt(R.${channel}, 152, 160), lightAt(R.${channel}, 160, 160)]`);
check(`${mode} ${channel}: a falling stream leaves ground as bright as open sky`, Math.abs(streamGround - openGround) < 1e-3, `${streamGround} / ${openGround}`);
}
await run('setCell(76 * W + 80, WATER, AMB[76], 0); updateRoof(); computeLight();');
const a = await run('lightAt(R.latt, 80, 76)');
check(`${mode}: a wet belt load transmits like air`, Math.abs(a - Math.pow(0.978, mode === 'fast' ? 4 : 2)) < 1e-3, a);
await run('setCell(76 * W + 80, EMPTY, AMB[76], 0); updateRoof();');
}
await run('R.camY=0;');
for (const [name, support, depth, gap, expected] of [
['left belt', 'mat[i] = BELTL', 12, '', true],
['right belt', 'mat[i] = BELTR', 12, '', true],
['filter', 'part[i] = P_FILTER', 12, '', true],
['deep heap', 'mat[i] = BELTR', 13, '', false],
['air gap', 'mat[i] = BELTR', 3, 'EMPTY', true],
['gas gap', 'mat[i] = BELTR', 3, 'STEAM', true],
['12 falling grains', '', 12, '', true],
['13 falling grains', '', 13, '', false],
['natural ledge', 'mat[i] = STONE; flags[i] = F_NAT', 3, '', false],
['deep natural heap', 'mat[i] = SAND; flags[i] = F_NAT', 13, '', false],
['wall floor', 'mat[i] = STONE', 3, '', true],
['screw', 'part[i] = P_SCREW_R', 3, '', true],
['loaded screw', 'mat[i] = SAND; part[i] = P_SCREW_R', 3, '', true],
]) {
await run(`
for (let y = 40; y <= 53; y++) { const i = y * W + 300; mat[i] = SAND; part[i] = 0; flags[i] = 0; }
{ const i = (40 + ${depth}) * W + 300; mat[i] = EMPTY; mat[i + W] = BELTR; ${support}; }
${gap ? `mat[41 * W + 300] = ${gap};` : ''}
`);
check(`${name}: loose cell detection`, await run('lightAt(R.latt,300,40) > 0.9') === expected);
}
await run('mat[40 * W + 300] = SAND; mat[41 * W + 300] = EMPTY;');
check('a single grain in mid-air is see-through', await run('lightAt(R.latt,300,40) > 0.9'));
await run('R.camY=120;mat[(H - 1) * W + 300] = SAND;');
check('loose cell detection stops at the world bottom', await run('lightAt(R.latt,300,H-1) < 0.92'));
await flatWorld(run, 320, 200, 160);
await run('R.camY=100;');
await run(`
bot.lamp = false; stats.refined = 0;
placeBlock(BP_GRIND, 0, 20, 20, true);
for (let bx = 8; bx < 30; bx++) placeBlock(BP_BELT, 1, bx, 36, true);
setCell(79 * W + 80, SAND, AMB[79], 0);
`);
check('sand resting on grinder housing is see-through', await run('lightAt(R.latt,80,79) > 0.9'));
for (const mode of ['full', 'fast']) {
const result = await run(`(() => {
SET.lighting = '${mode}';
let mismatches = 0, first = '';
for (let t = 0; t < 180; t++) {
if (t % 4 === 0) setCell(48 * W + 80, SAND, AMB[48], 0);
simTick();
computeLight();
for (const a of [R.lr, R.lg, R.lb]) for (let x = 76; x < 88; x += 2) {
const below = lightAt(a, x, 160), open = lightAt(a, 152, 160);
if (Math.abs(below - open) > 1e-6) {
mismatches++;
if (!first) first = 'tick ' + tick + ', x ' + x + ': ' + below + ' / ' + open;
}
}
}
return { mismatches, first };
})()`);
check(`${mode}: streaming grinder leaves ground in daylight for 180 consecutive ticks`, result.mismatches === 0, result.first || 'no shadow');
}
check('the streaming grinder actually processes its input', await run('stats.refined > 0'), await run('stats.refined'));
done();