Files
deepgrain-foundry/test/t_dev_console.js
T

74 lines
6.3 KiB
JavaScript

// Dev command validation, discovery, build costs, runtime controls and save metadata.
const { load, suite, flatWorld } = require('./harness');
const { run } = load();
const { check, done } = suite();
flatWorld(run, 240, 160, 110);
const command = text => run(`devCommand(${JSON.stringify(text)})`);
check('commands cannot change an unmarked world', command('money 123')[0].includes('warning'));
run('enableDevConsole(); money = 100;');
command('MoNeY -25'); command('money set 1234.5');
check('money adds signed values and sets amounts, ignoring case', run('money === 1234.5'));
command('money -34.5');
check('negative money amount subtracts', run('money === 1200'));
command('debt 500');
check('debt sets claim amount', run('debt === 500'));
command('debt clear');
check('debt clear pays off claim', run('debt === 0'));
run('seen.fill(0); EV.length = 0;');
command('give cUoRe 7'); command('give Copper Bar 3'); command('give charc'); command('give sand 30000');
check('give matches internal names, display names and unique prefixes', run('bag[CUORE] === 7 && bag[BARCU] === 3 && bag[CHARCOAL] === 1000'));
check('give exceeds bag capacity, counts totals and discovers like a pickup', run("bag[SAND] === 30000 && bagTotal === 31010 && seen[SAND] && EV.some(e => e.t === 'discover' && e.m === SAND && e.how === 'pickup')"));
check('ambiguous prefix explains candidates', command('give cop')[0].startsWith('Ambiguous material:'));
check('unknown material is explained', command('give nonexistent')[0].startsWith('Unknown material'));
check('all internal aliases resolve exactly', run('Object.keys(DEV_MATERIALS).length === NMAT && Object.entries(DEV_MATERIALS).every(([k, v]) => devMaterial(k) === v)'));
command('give sand 2147483647');
check('give rejects typed-array overflow', run('bag[SAND] === 30000'));
command('bit 1e100'); command('bag -5');
check('tier setters clamp to valid range', run('player.bitTier === BIT_TIERS.length - 1 && player.tankTier === 0'));
check('tier queries list every tier', command('bit').length === run('BIT_TIERS.length') && command('bag').length === run('TANK_TIERS.length'));
run('player.lens = player.flask = false;'); command('unlock');
check('unlock learns materials, rewards and milestones', run('seen.every(Boolean) && player.lens && player.flask && MILESTONES.every(m => ledger[m.id])'));
command('tp -500 9999');
check('teleport clamps the entire bot inside the world', run('bot.x === 0 && bot.y === H - bot.h && bot.vx === 0 && bot.vy === 0'));
run('devCommand("tp", {x: 70, y: 45})');
check('teleport can use the cursor', run('bot.x === 70 && bot.y === 45'));
command('speed 1e100'); check('speed clamps high', run('devSpeed === 8'));
command('speed -1'); check('speed clamps low', run('devSpeed === 0.1'));
command('step'); check('step requires dev pause', run('devSteps === 0'));
command('pause'); command('step 5');
check('paused steps are queued for the frame loop', run('devPaused && devSteps === 5'));
command('pause'); check('resuming discards queued steps', run('!devPaused && devSteps === 0'));
const beforeBad = run('JSON.stringify([money, debt, bagTotal, player, devLog, devSpeed, devSteps])');
for (const text of ['money', 'money set', 'money Infinity', 'money 1e309', 'money 1 2', 'debt -1', 'debt nope', 'give sand -1', 'give sand 1.5', 'bit 1.5', 'bag nope', 'unlock extra', 'tp 1', 'tp NaN 4', 'free maybe', 'speed NaN', 'step 0', 'step 10001', 'pause extra', 'weather snow', 'nonsense']) {
const output = command(text);
check('bad arguments produce a helpful error: ' + text, output.length > 0 && !output[0].startsWith('>'));
}
check('invalid commands do not mutate or log', beforeBad === run('JSON.stringify([money, debt, bagTotal, player, devLog, devSpeed, devSteps])'));
check('help lists every command', command('help').length === 13);
run('money = 17; bag.fill(0); bagTotal = 0; bagAdd(RSTONE, 32, true); bagAdd(LCLAY, 100, true);');
check('normal build costs are checked', run('!canAfford([[CASH, 100]], 1)'));
command('free on');
const buildBag = run('JSON.stringify(Array.from(bag))');
check('free bypasses affordability', run('canAfford([[CASH, 100000], [BARCU, 99]], 1)'));
check('free places a wall without taking existing rubble', run("placeBlock(BP_WALL, RSTONE, 10, 10, false) === '' && bag[RSTONE] === 32"));
check('free places a machine without charging money', run("placeMachineAt('fan', 0, 24, 10) === '' && money === 17"));
check('free never charges, including blueprint ghosts built later', run('(() => { const m = money; payCost([[CASH, 500]], 3); return money === m; })()'));
check('free places a mold without taking clay', run("typeof addMold('ingot', 35, 10, LCLAY, false) !== 'string' && bag[LCLAY] === 100"));
run("ghostBlocks.set(12 * BW + 17, [BP_BELT, 1]); ghostMachines.push({type: 'fan', dir: 0, bx: 20, by: 17}); ghostMolds.push({kind: 'ingot', bx: 9, by: 16, wall: LCLAY}); ghostTick(); ghostTick();");
check('ghost blocks, machines and molds build free too', run('!ghostBlocks.size && !ghostMachines.length && !ghostMolds.length && money === 17 && ghostMissing === 0'));
check('all free paths preserve bag contents and totals', buildBag === run('JSON.stringify(Array.from(bag))') && run('bagTotal === 132'));
command('free off'); run('payCost([[CASH, 2], [RSTONE, 4]], 1)');
check('normal payment resumes after free off', run('money === 15 && bag[RSTONE] === 28 && bagTotal === 128'));
command('weather rain');
check('weather rain starts a shower near the bot', run('weather.shower && rainAt(bot.x) > 0 && weather.shower.until === tick + 3600'));
command('weather clear');
check('weather clear stops current shower', run('weather.shower === null && weather.raining.every(v => !v)'));
run('for (let i = 0; i < 210; i++) devCommand("money 1");');
check('dev log keeps the last 200 commands', run('devLog.length === 200 && devLog.every(e => e[0] === tick && e[1] === "money 1")'));
run('devCommand("speed 4"); devCommand("pause"); devCommand("free on"); const devSaved = serializeWorld(); const devLogCopy = JSON.stringify(devLog); deserializeWorld(devSaved.meta, devSaved.buffers);');
check('dev mark and command log survive save round trip', run('devWorld && devSaved.meta.dev === true && JSON.stringify(devLog) === devLogCopy'));
check('load resets speed, free building, pause and pending steps', run('devSpeed === 1 && !devFree && !devPaused && devSteps === 0'));
run('alloc(240,160)');
check('a new world starts unmarked without a dev log', run('!devWorld && devLog.length === 0'));
done();