Dev console refuses buildable piece materials; loose piece grains cleared on load (t97)

This commit is contained in:
2026-10-29 18:08:38 -04:00
parent 0483fb8da4
commit 3b427aeecc
9 changed files with 119 additions and 9 deletions
+68
View File
@@ -107,4 +107,72 @@ check('completion supports keywords and help commands', run("devComplete('free o
check('completion leaves unmatched input intact', run("devComplete('give zzzzz').text === 'give zzzzz'"));
check('history validates stored data', run("devHistory(null).items.length === 0 && devHistory(['ok', 5, '', ' ']).items.join() === 'ok'"));
check('history caps at 50 and navigates to empty after newest', run("(() => { const h = devHistory([]); for (let i=0; i<55; i++) devRemember(h, 'money ' + i); devRemember(h, ' '); const newest = devRecall(h, -1); for (let i=0;i<60;i++) devRecall(h,-1); const oldest = devRecall(h,-1); for(let i=0;i<60;i++) devRecall(h,1); return h.items.length === 50 && newest === 'money 54' && oldest === 'money 5' && devRecall(h,1) === ''; })()"));
// Construction materials are pieces, never loose inventory or nozzle grains.
flatWorld(run, 240, 160, 110);
run('enableDevConsole();');
check('construction material markers cover every dedicated piece material', run(`
[BELTL, BELTR, SHAFT, GATE, HULL, PIPE, DOCKW, GRINDER, SAWB, PRESSB, RACKB].every(m => PARTMAT[m])
`));
check('give refuses internal and display names of every construction material', run(`(() => {
const before = JSON.stringify([bagTotal, Array.from(bag), devLog]);
for (const [key, m] of Object.entries(DEV_MATERIALS)) if (PARTMAT[m]) {
for (const name of [key, MD[m].name]) {
const out = devCommand('give ' + name + ' 3');
if (!out.error || !out[0].includes('built from the build menu')) return false;
}
}
return before === JSON.stringify([bagTotal, Array.from(bag), devLog]);
})()`));
check('give wall refuses without changing inventory', command('give wall 3').error === true && run('bagTotal === 0'));
check('construction pieces stay out of lists, suggestions and completion', run(`
devMaterialList().every(([, m]) => !PARTMAT[m]) &&
devComplete('give saw').matches.length === 0 &&
!devSuggestions('sawb').includes(MD[SAWB].name)
`));
command('give sand 7'); command('give copper bar 2'); command('give stone 4');
check('grains, bars and wall ingredients still work', run('bag[SAND] === 7 && bag[BARCU] === 2 && bag[RSTONE] === 4'));
check('bag pickup and refund cannot introduce piece grains', run(`(() => {
const before = bagTotal;
const added = bagAdd(SAWB, 2, true);
giveOrDrop(GATE, 3, 40, 40);
return added === 0 && bagTotal === before && !bag[SAWB] && !bag[GATE] && mat[40 * W + 40] === EMPTY;
})()`));
run('bag[SAWB] = 4; bagTotal += 4;');
check('nozzle refuses even a directly contaminated bag', run('toolBlow(50, 40, 1, SAWB).placed === 0 && flyers.length === 0'));
check('mixed nozzle skips piece grains while blowing sand', run(`(() => {
const out = toolBlow(50, 40, 1, SAWB, [SAWB, SAND]);
return out.placed > 0 && flyers.every(f => f.m === SAND) && bag[SAWB] === 4;
})()`));
// Keep legitimate blocks, slopes and their outside seals, parts, machines and natural rock.
flatWorld(run, 240, 160, 110);
run(`
const builtCodes = [BP_WALL, BP_SAW, BP_GRIND, BP_PRESS, BP_RACK, BP_BELT, BP_SHAFT, BP_GATE, BP_PIPE, BP_PUMP, BP_OUTLET, BP_SCREW, BP_GRATE, BP_FILTER, BP_BELT_UP];
for (let k = 0; k < builtCodes.length; k++) placeBlock(builtCodes[k], k === 0 ? RSTONE : 0, 2 + k * 3, 10, true);
placeMachineAt('fan', 0, 4, 17, true);
addMold('ingot', 12, 17, LCLAY, true);
placeDock(180);
const ownedBefore = JSON.stringify([Array.from(mat.slice(40 * W, 80 * W)), Array.from(part.slice(40 * W, 80 * W)), Array.from(blockP)]);
const pieceIds = Object.values(DEV_MATERIALS).filter(m => PARTMAT[m]);
for (let k = 0; k < pieceIds.length; k++) setCell(20 * W + 20 + k, pieceIds[k], 20, 0);
// A nozzle grain in the open side of a real sloped belt is still loose.
const looseCorner = 40 * W + 176;
setCell(looseCorner, SAWB, 20, 0);
bag[SAWB] = 5; bag[HULL] = 2; bag[SAND] = 7; bagTotal = 14;
const straySave = serializeWorld(); straySave.meta.flyers.push(GATE, SAND);
deserializeWorld(straySave.meta, straySave.buffers);
`);
check('loading clears all loose construction grains', run('pieceIds.every((m, k) => mat[20 * W + 20 + k] === EMPTY) && mat[looseCorner] === EMPTY'));
check('loading preserves built cells, slope seals, parts, machines and molds', run(`
ownedBefore === JSON.stringify([Array.from(mat.slice(40 * W, 80 * W)), Array.from(part.slice(40 * W, 80 * W)), Array.from(blockP)]) && machines.some(m => m && m.type === 'fan') && molds.length === 1
`));
check('loading clears construction inventory and saved nozzle grains with correct totals', run('bag[SAWB] === 0 && bag[HULL] === 0 && bag[GATE] === 0 && bag[SAND] === 8 && bagTotal === 8'));
check('taking down a saw and a wall refunds ingredients without piece grains', run(`(() => {
removeBlock(5, 10); removeBlock(2, 10);
return bag[RSTONE] === 16 && Object.values(DEV_MATERIALS).filter(m => PARTMAT[m]).every(m => !bag[m]);
})()`));
check('ordinary natural rock survives cleanup', run('mat[120 * W + 20] === STONE'));
check('free building and unlock still work with construction materials excluded', run(`(() => {
enableDevConsole(); devCommand('unlock'); devCommand('free on');
return seen[SAWB] && placeBlock(BP_SAW, 0, 5, 5, false) === '' && mat[20 * W + 20] === SAWB;
})()`));
done();