// Basics: drilling to rubble, the vacuum and blowing, walls, the ingot mold and bars, belts, blueprints, save/load. const { load, suite, flatWorld, rect, ticks, count } = require('./harness'); const { run } = load(); const { check, done } = suite(); flatWorld(run, 240, 160, 100); run('player.tankTier = 3; player.flask = false;'); // 1. drill breaks stone loose; nothing goes into the bag run('bot.x = 116; bot.y = 92; for (let k=0;k<80;k++) toolDig(120, 104, 3)'); check('drill leaves bag empty', run('bagTotal') === 0, run('bagTotal')); const rub = count(run, run('RSTONE')); check('drill turns stone into loose rubble', rub > 20, rub); ticks(run, 60); // 2. vacuum with a filter: only rubble rect(run, 100, 96, 104, 99, run('COAL')); ticks(run, 30); const pickup = run('toolVacuum(120, 104, 6, m => m === RSTONE)'); const got = pickup.got; check('vacuum reports the first material picked up', pickup.m === run('RSTONE')); check('vacuum picks only the filtered material', run('bag[RSTONE]') > 0 && run('bag[COAL]') === 0, `${run('bag[RSTONE]')} rubble, ${run('bag[COAL]')} coal`); run('toolVacuum(102, 98, 4, m => true)'); check('empty vacuum reports no first material', run('toolVacuum(10, 10, 1, () => true).m') === 0); check('unfiltered vacuum picks coal', run('bag[COAL]') > 0, run('bag[COAL]')); // 3. drop: grains fall const c0 = run('bag[COAL]'); run('bot.x = 100; bot.y = 60;'); run('for (let k=0;k<5;k++) { toolBlow(80, 70, 1, COAL); simTick(); }'); check('blown coal flies out of the bot', run('flyers.length') > 0, run('flyers.length')); ticks(run, 120); check('blown coal leaves the bag and lands on the ground', run('bag[COAL]') < c0 && run('flyers.length') === 0 && count(run, run('COAL'), 60, 80, 100, 99) > 0, `${c0}->${run('bag[COAL]')} landed ${count(run, run('COAL'), 60, 80, 100, 99)}`); check('no selection explains how to select', run('toolBlow(80, 70, 1, 0).status') === 'Nothing selected: click or Q/E in the bag.'); check('unavailable blow mix explains none are in the bag', run('toolBlow(80, 70, 1, COAL, [CUORE, BARCU]).status') === 'None of the selected materials are in the bag.'); // bot flies through built walls but not through natural rock run('bag[RSTONE] += 64; bagTotal += 64; placeBlocks(BP_WALL, RSTONE, blockRect(30, 14, 30, 17))'); check('bot can overlap a built wall', !run('botOverlapSolid(121, 58)')); check('bot cannot overlap natural rock', run('botOverlapSolid(120, 98)')); // trees: water passes in front of a background trunk; cutting the base drops the tree run('for (let y = 80; y < 100; y++) for (let x = 140; x < 142; x++) bgm[y*W+x] = WOOD; for (let y = 72; y < 80; y++) for (let x = 136; x < 146; x++) bgm[y*W+x] = LEAVES;'); rect(run, 130, 96, 138, 99, run('WATER')); ticks(run, 120); check('water flows past a tree trunk', count(run, run('WATER'), 141, 90, 160, 99) > 0, count(run, run('WATER'), 141, 90, 160, 99)); run('bot.x = 134; bot.y = 88; for (let k=0;k<120;k++) toolDig(140, 99, 1)'); ticks(run, 5); check('cutting the trunk base brings the tree down as wood chunks', run('bgm[85*W+140]') === 0 && count(run, run('LWOOD')) > 20, count(run, run('LWOOD'))); // 4. wall blocks cost rubble and snap to the grid run('bag[RSTONE] = 200; bagTotal = 0; for (let k=0;k e.m === BARCU && e.iy >= molds[0].y + molds[0].h)')); ticks(run, 200); check('bars fall and come to rest', run('ents.every(e => Math.abs(e.vy) < 0.01)'), run('JSON.stringify(ents.map(e=>[e.ix,e.iy]))')); check('bars count as built-up copper', run('stats.bars') === 3); // 6. vacuum bars const bx = run('ents[0].ix + 2'), by = run('ents[0].iy + 1'); run(`toolVacuum(${bx}, ${by}, 4, m => m === BARCU)`); check('vacuum picks up bars', run('bag[BARCU]') >= 1 && run('ents.length') < 3, run('bag[BARCU]')); // 7. belt carries a bar: belt from block (40,24) to (46,24), powered by direct supply run('bot.x = 165; bot.y = 70; bag[BARCU] = 20; bagTotal = 0; for (let k=0;k x0 + 10, `${x0} -> ${x1}`); // 8. bar melts when heated run('const e = ents[ents.length-1]; for (let dy=-2; dy<4; dy++) for (let dx=-2; dx<6; dx++) temp[(e.iy+dy)*W + e.ix+dx] = 1300; e.T = 1100;'); ticks(run, 3); check('hot bar melts into molten copper', count(run, run('MCU')) > 0, count(run, run('MCU'))); // 9. machines snap to blocks run('bag[LWOOD] = 100; bagTotal = 0; for (let k=0;k m && m.type !== "dock").length, blocks: blockP.reduce((a,b)=>a+(b>0),0)})'); run('globalThis.__sv = serializeWorld(); deserializeWorld(__sv.meta, __sv.buffers);'); const after = run('JSON.stringify({bag: bagTotal, ents: ents.length, molds: molds.length, mach: machines.filter(m => m && m.type !== "dock").length, blocks: blockP.reduce((a,b)=>a+(b>0),0)})'); check('save/load keeps bag, bars, molds, machines and blocks', before === after, before + ' vs ' + after); done();