Hot loads: visible melt damage, rebuild-over repair, warning glow (t69)
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// t69: melt feedback, full-cost repair without disturbing the load, and repair undo.
|
||||
const {load, suite, flatWorld} = require('./harness');
|
||||
const {run} = load(), {check, done} = suite();
|
||||
flatWorld(run, 160, 120, 100);
|
||||
run(`bot.x=40; bot.y=40; money=500; ledger={}; EV.length=0;
|
||||
placeBlock(BP_SCREW, 4, 10, 10, true);
|
||||
part[40*W+40]=0; markPow(40*W+40,false);
|
||||
setCell(40*W+40,RBAS,950,0);`);
|
||||
check('same screw builds over damage at full price', run(`undoRecord('repair',()=>placeBlocks(BP_SCREW,4,[[10,10]],null,false)).n===1 && money===475 && blockIntegrity(10*BW+10)===16`));
|
||||
check('repair keeps the hot load in place', run('mat[40*W+40]===RBAS && temp[40*W+40]===950'));
|
||||
run('undoLast()');
|
||||
check('undo restores the hole and refunds repair cost', run('part[40*W+40]===0 && !isPow[40*W+40] && money===500 && blockIntegrity(10*BW+10)===15'));
|
||||
check('G also repairs without a removal refund', run(`undoRecord('repair',()=>placeBlocks(BP_SCREW,4,[[10,10]],null,true)).n===1 && money===475`));
|
||||
check('G over an intact screw costs nothing', run('placeBlocks(BP_SCREW,4,[[10,10]],null,true).n===0 && money===475'));
|
||||
run('part[40*W+40]=0; money=0');
|
||||
check('unaffordable repair leaves the damage alone', run('!!placeBlock(BP_SCREW,4,10,10,false) && part[40*W+40]===0 && money===0'));
|
||||
run(`money=500; placeBlock(BP_SCREW,4,10,10,false);
|
||||
tick=2; tickStamp=2;
|
||||
for(const i of blockCellList(10,10)) {setCell(i,RBAS,1000,0);}
|
||||
rebuildPower(); for(let k=0;k<500;k++) { for(const i of blockCellList(10,10)) {mat[i]=RBAS; stamp[i]=0;} machinesTick(); }`);
|
||||
check('1000 degrees does not melt screws', run('blockIntegrity(10*BW+10)===16'));
|
||||
run(`for(const i of blockCellList(10,10)) temp[i]=1100;
|
||||
for(let k=0;k<500;k++) { for(const i of blockCellList(10,10)) {mat[i]=RBAS; stamp[i]=0;} machinesTick(); }`);
|
||||
check('hot loads still melt cell by cell', run('blockIntegrity(10*BW+10)<16'));
|
||||
check('first melt explains heat limit and repair once', run(`EV.filter(e=>e.t==='screw_melt').length===1 && ledger.screwMelt===true`));
|
||||
run('const heatSave=serializeWorld(); deserializeWorld(heatSave.meta,heatSave.buffers)');
|
||||
check('existing save data retains damage and first-message state', run('blockIntegrity(10*BW+10)<16 && ledger.screwMelt===true'));
|
||||
run(`placeBlock(BP_SCREW,4,10,10,true); for(const i of blockCellList(10,10)) {mat[i]=EMPTY; temp[i]=1100;}
|
||||
setCell(41*W+41,RBAS,899,0);`);
|
||||
check('empty hot cells do not count as a hot load', run('screwLoadHeat(10*BW+10)===899'));
|
||||
run('temp[41*W+41]=900');
|
||||
check('warning can start before the melt limit', run('screwLoadHeat(10*BW+10)===900 && blockIntegrity(10*BW+10)===16'));
|
||||
run('part[40*W+40]=0; ghostBlocks.set(10*BW+10,[BP_SCREW,4,null,1]); money=500; ghostTick()');
|
||||
check('a queued G repair restores damage without refunding', run('blockIntegrity(10*BW+10)===16 && money===475 && !ghostBlocks.size'));
|
||||
run(`part[40*W+40]=0; money=500;
|
||||
undoRecord('queued repair',()=>{ghostBlocks.set(10*BW+10,[BP_SCREW,4,null,1]);undoNote({t:'paste',blocks:[[10,10,BP_SCREW,1]],machines:[],molds:[]});});
|
||||
ghostTick();undoLast();`);
|
||||
check('undo of a completed queued repair preserves the original damaged screw',run('blockP[10*BW+10]===BP_SCREW && blockIntegrity(10*BW+10)===15 && money===500'));
|
||||
for(let v=0;v<8;v++) {
|
||||
run(`removeBlock(10,10);placeBlock(BP_SCREW,${v},10,10,true); part[40*W+40]=0; markPow(40*W+40,false); placeBlock(BP_SCREW,${v},10,10,true)`);
|
||||
check(`repair restores direction ${v}`,run(`part[40*W+40]===SCREW_PARTS[${v}] && blockIntegrity(10*BW+10)===16`));
|
||||
}
|
||||
done();
|
||||
@@ -0,0 +1,79 @@
|
||||
"""t69 only: both looks, persistent damage, hover, first message and real click/G repair."""
|
||||
import pathlib
|
||||
import sys
|
||||
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / 'tools'))
|
||||
from use_venv import ensure_playwright; ensure_playwright()
|
||||
from browser import launch, consume_gpu_option
|
||||
consume_gpu_option()
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
with launch(viewport={'width': 1280, 'height': 760}) as (_, page, url):
|
||||
errors = []
|
||||
page.on('pageerror', lambda e: errors.append(str(e)))
|
||||
page.goto(url)
|
||||
page.evaluate("""() => {
|
||||
const enter=enterGame; enterGame=()=>{enter();GAME.paused=true;};
|
||||
SET.look='pixel';SET.workerCap=0;SET.cameraLead=false;SET.autosave=0;
|
||||
startNewWorld(7,768,600);
|
||||
}""")
|
||||
page.wait_for_function('GAME.running', timeout=60000)
|
||||
page.evaluate("""() => {
|
||||
mat.fill(EMPTY);part.fill(0);blockP.fill(0);blockV.fill(0);isPow.fill(0);
|
||||
flags.fill(0);temp.fill(20);bgm.fill(0);fog.fill(255);
|
||||
machines=[null];molds=[];ents=[];flyers=[];treeBases=[];saplings=[];
|
||||
pcells.clear(); weather.next=1e15;weather.shower=null;surfY.fill(300);updateRoof();
|
||||
bot.x=310;bot.y=210;bot.vx=bot.vy=0; money=500; ledger={};
|
||||
for(let bx=72;bx<=84;bx++) placeBlock(BP_SCREW,1,bx,52,true);
|
||||
for(let x=288;x<340;x++)for(let y=209;y<212;y++)setCell(y*W+x,RBAS,20,0);
|
||||
for(let x=304;x<316;x++)for(let y=209;y<212;y++)temp[y*W+x]=950;
|
||||
// Melt the right section with the real machine tick, keeping the scene paused.
|
||||
for(let k=0;k<400;k++) {
|
||||
for(let x=328;x<340;x++)for(let y=208;y<212;y++) {const i=y*W+x;mat[i]=RBAS;temp[i]=1100;stamp[i]=0;}
|
||||
tick=2;tickStamp=2;machinesTick();
|
||||
}
|
||||
for(let x=288;x<340;x++)for(let y=208;y<212;y++)temp[y*W+x]=x>=304&&x<316?950:20;
|
||||
document.querySelectorAll('.card-pop,.toast').forEach(e=>e.remove());
|
||||
SET.cardSeconds=-1;drainEvents();R.floaters=[];
|
||||
SET.fog=false;SET.zoom=12;SET.lighting='off';SET.glow='off';SET.minimap=false;
|
||||
SET.cursorLabel='all';GAME.infoOverlay=false;GAME.hidePreviews=true;
|
||||
resizeRender();snapCamera();renderDirty.fill(1);GPU.world=null;SM.surf=-1;
|
||||
}""")
|
||||
assert page.get_by_text('Screw conveyor melted', exact=True).is_visible()
|
||||
assert page.evaluate("hoverInfo(330,210).t2.join(' ').includes('hotter than 1000°C')")
|
||||
assert page.evaluate("hoverInfo(310,210).warn==='near heat limit'")
|
||||
assert page.evaluate("!hoverInfo(290,210).warn")
|
||||
for look in ['pixel', 'smooth']:
|
||||
page.evaluate(f"SET.look='{look}'")
|
||||
page.wait_for_function(f"R.smoothOn === {'true' if look == 'smooth' else 'false'}")
|
||||
page.wait_for_timeout(150)
|
||||
# The new overlay must alter pixels in both looks, and hide behind unexplored fog.
|
||||
assert page.evaluate("""() => {
|
||||
const sample=()=>{const p=worldToScreenPx(330,210);return Array.from(R.ctx.getImageData(p.x,p.y,1,1).data);};
|
||||
R.ctx.clearRect(0,0,R.canvas.width,R.canvas.height); drawScrewHeat();
|
||||
const shown=sample()[3];SET.fog=true;fog.fill(0);
|
||||
R.ctx.clearRect(0,0,R.canvas.width,R.canvas.height);drawScrewHeat();
|
||||
const hidden=sample()[3]; SET.fog=false;fog.fill(255);
|
||||
return shown>0 && hidden===0;
|
||||
}""")
|
||||
page.wait_for_timeout(100)
|
||||
page.screenshot(path=str(ROOT / f'shots/t69-{look}.png'))
|
||||
print(f'PASS {look}: cold / warning / cooled melt damage, hover, fog and screenshot')
|
||||
# Exercise the actual build input, then undo, then G + click on the same cooled damage.
|
||||
page.evaluate("GAME.paused=false;devPaused=true;GAME.hidePreviews=false;GAME.tool=2;GAME.pieceIdx=PIECES.findIndex(p=>p.id==='screw');GAME.pieceDir.screw=1")
|
||||
target = page.evaluate("worldToScreenPx(330,210)")
|
||||
for replace in [False, True]:
|
||||
if replace:
|
||||
page.keyboard.down('g')
|
||||
before = page.evaluate('money')
|
||||
page.mouse.move(target['x'], target['y'])
|
||||
page.wait_for_timeout(60)
|
||||
page.mouse.click(target['x'], target['y'])
|
||||
page.wait_for_timeout(100)
|
||||
assert page.evaluate('blockIntegrity(52*BW+82)===16')
|
||||
assert page.evaluate('money') == before - 25
|
||||
if replace:
|
||||
page.keyboard.up('g')
|
||||
page.keyboard.press('Control+z')
|
||||
assert page.evaluate('blockIntegrity(52*BW+82)<16')
|
||||
assert page.evaluate('money') == before
|
||||
assert not errors, errors
|
||||
print('PASS first melt message; real click and G repair at full cost; keyboard undo; no page errors')
|
||||
Reference in New Issue
Block a user