From 6ac5f463aa928456953a749f0b8a299bb74b7e7a Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Wed, 28 Oct 2026 19:15:41 -0400 Subject: [PATCH] Drill: keep pass-through; restore the original loose-grain nudge, drop dust and push (t92) Co-Authored-By: Claude Opus 5.5 --- docs/DESIGN.md | 8 ++++ docs/PROJECT_MAP.md | 4 +- docs/agent-log.md | 34 +++++++++++++ src/render/overlays.js | 18 +------ src/sim/drill.js | 51 +++++++------------- src/ui/events.js | 2 +- test/t_drill.js | 106 ++++++++++------------------------------- test/t_gpu.py | 13 ----- 8 files changed, 88 insertions(+), 148 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index fae6a33..eaf361f 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -561,3 +561,11 @@ above, including: - **Upgrade ideas for the drill**: an amplifier, a tuning fork, harmonics. Also a Grav pick upgrade tool and mining charges. See `docs/systems/dig-tools.md`. - The owner's ideas for later are tasks tagged `idea` in the Backlog of the task board. + +- **Restore the original loose-grain nudge (t92 final owner feedback, 2026-10-28).** After + playing, the owner rejected dust (never visible in game), the strong surface-only push + toward the bot, and fluidising kicks. These supersede the two t92 decisions above. Keep + 3% loose-material loss per grain and all damping per distance, with rock bores retuned to + the old feel. Restore the original advancing-front nudge: 30% times ray energy per + half-grain sample, straight back toward the bot, then down or up if blocked. Scale that + chance with sampling distance; do not keep shaking the travelled path. diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index 074f519..3519537 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -327,7 +327,7 @@ Tool helpers: reach, brushes, and plain digging. - functions: setBuildRange, inBuildRange, inReach, forBrush, drillReach, inDrillReach, toolDig - variables: BUILD_MAX, buildBox -### src/sim/drill.js (202 lines) +### src/sim/drill.js (183 lines) The resonance drill: tuned pulses that shatter one material. - functions: drillOrigin, pulseRay, builtCell, toolPulse, crumbleAround, crumbly, shatterCell, pulseTick, pingRadius, resonancePing - variables: PULSE_W, PULSE_EVERY, PULSE_SPEED, PULSE_K, drillPulses, lastPulseTick, drillWidth, THIN_BONUS, CRUMBLE_MAX, THIN_BONUS_OFF, PING_VEINS @@ -482,7 +482,7 @@ Scale colour without changing the smooth-look class. Machine overlays: fans, engines, silo and the rest, drawn on top of the world. - functions: cellRect, drawHazardRims, drawMachines, drawProcBlocks, drawSilo, drawSiloPixel, drawSiloWindow, drawRackBlocks -### src/render/overlays.js (384 lines) +### src/render/overlays.js (370 lines) Other overlays: floating text, rain and snow, flow arrows, filter tags, molds, the bot, particles. - functions: spawnFloater, drawRain, drawFlowArrows, drawJoinMark, drawJoinMarks, flowLoaded, drawFlowRates, pingImage, drawPing, drawHoveredVein, drawPingRecharge, drawRunOutline, drawFilterTags, drawFloaters, moldLabel, drawMolds, drawBot, spawnDebris, spawnSpark, spawnSuck, drawParticles - variables: PING_SPEED, PING_HOLD, PING_FADE, PING_BANDS, PING_GLINTS, GLINT_MS diff --git a/docs/agent-log.md b/docs/agent-log.md index 7ca2224..ea5162f 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -5,6 +5,40 @@ note or request it was for, what changed, the files touched, the tests run, and open or not verified. Keep each entry short; the git history has the details. +## 2026-10-28: t92 keep pass-through, restore original loose-grain behaviour (Codex) + +- Read the 7293787 then dadf62a t92 history and original drill in 90f2d3f. Restored the + original advancing-front nudge and escape order verbatim, except loose damping stays + 0.97 per grain and chance is `1 - (1 - 0.3 * e) ** (2 * step)`, preserving the original + 30% times energy at the default half-grain sample. Removed travelled-path pushing, + fluidising kicks, dust events and dust-specific event/render plumbing. Normal debris stays. +- Removed dust/channel/fluidising tests. Original ore-draining check was already present + and retained. Added back/down/up/fully-blocked escape checks and a front-only check; + three escape checks failed before restoration, all pass after. Pass-through still opens + 78 basalt cells behind 35 rubble grains; 115 blocks breaking and 118 blocks all damage. +- Restoring the nudge initially failed narrow opened-area and follow-face checks. Retuned + narrow/medium damping 0.76/0.69 -> 0.75/0.67; wide stays 0.6084. Unchanged baseline + bore assertions pass: narrow 24/198 versus original 22/181, medium 13/197 versus 13/186, + wide 6/134 versus 6/134 (depth/opened grains; all within 10%). Updated the attenuation + constant expectation for the intentional medium retune; no bore expectations weakened. +- Focused drill suite passes (61 checks). Initial `python3 test/run_all.py --browser` + passed all 33 headless suites, but seven browser suites failed with unavailable WebGPU + in the sandbox (startup timeouts/undefined GPU device). Same full command rerunning + with host GPU access passed all 33 headless and eight browser suites (361 gameplay, + 28 smooth, 18 console, 7 caps, 14 GPU, 28 goldens, 71 light, 2 Firefox). Evidence: + `/tmp/t92-restore-browser.log`, `/tmp/t92-restore-browser-host.log`. +- Fixed-powder microbenchmark (median of five after warmup, 20,000 rays): previous + travelled-path code 11.88 ms, restored front-only code 9.82 ms. Fixture stamps powder + to isolate traversal, not moving heaps; not a whole-game profile. Script: `/tmp/t92-restore-bench.js`. +- Captured and inspected `shots/t92-restore-pixel.png` and `shots/t92-restore-smooth.png`: + rubble heap beside a basalt face with a drilled notch, no dust puffs. Scene: + `/tmp/t92-restore-scene.js`, 120 held-medium ticks, zoom 8, lighting off, frozen frame. +- Files: `src/sim/drill.js`, `src/render/overlays.js`, `src/ui/events.js`, `test/t_drill.js`, + `test/t_gpu.py`, generated `docs/PROJECT_MAP.md`, `docs/DESIGN.md`, t92 task and this log. + Recorded the superseding owner decision and final checked feedback subtask; status Review. + No commit. Owner playtesting and whole-game performance remain unverified. + + ## 2026-10-28: t93 loose faces slump without sticky-step stalls (Codex) - Inspected the owner's screenshot and traced `powder`, `enterFree`, `move`, `moveChunk`, diff --git a/src/render/overlays.js b/src/render/overlays.js index 96a8e8a..7a1df2b 100644 --- a/src/render/overlays.js +++ b/src/render/overlays.js @@ -331,13 +331,8 @@ function drawBot() { ctx.beginPath(); ctx.moveTo(cx - bot.face * zp, sy + zp * 0.6 + bob); ctx.lineTo(cx - bot.face * zp * 1.6, sy - zp * 0.6 + bob); ctx.stroke(); ctx.restore(); } -function spawnDebris(x, y, m, dust = false) { +function spawnDebris(x, y, m) { if (!SET.particles || R.particles.length > 400) return; - if (dust) { - R.particles.push({ x: x + 0.5, y: y + 0.5, vx: (Math.random() - 0.5) * 0.12, - vy: -0.06 - Math.random() * 0.06, life: 36, r: 205, g: 190, b: 165, dust: true }); - return; - } for (let k = 0; k < 2; k++) R.particles.push({ x: x + Math.random(), y: y + Math.random(), vx: (Math.random() - 0.5) * 0.5, vy: -Math.random() * 0.5, life: 20 + Math.random() * 20, r: COLR[m], g: COLG[m], b: COLB[m] }); } function spawnSpark(x, y) { @@ -363,20 +358,11 @@ function drawParticles() { p.x += dx / d * sp; p.y += dy / d * sp; p.life--; if (p.life <= 0) { ps.splice(k, 1); continue; } } else { - p.x += p.vx; p.y += p.vy; p.vy += p.dust ? 0 : 0.04; p.life--; + p.x += p.vx; p.y += p.vy; p.vy += 0.04; p.life--; if (p.life <= 0) { ps.splice(k, 1); continue; } } if (SET.fog && fogSeen(p.x, p.y) < 64) continue; const s = worldToScreenPx(p.x, p.y); - if (p.dust) { - const radius = zp * (1 + (36 - p.life) * 0.035); - const puff = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, radius); - puff.addColorStop(0, `rgba(${p.r},${p.g},${p.b},${0.32 * p.life / 36})`); - puff.addColorStop(1, `rgba(${p.r},${p.g},${p.b},0)`); - ctx.fillStyle = puff; - ctx.fillRect(s.x - radius, s.y - radius, radius * 2, radius * 2); - continue; - } ctx.fillStyle = `rgba(${p.r | 0},${p.g | 0},${p.b | 0},${Math.min(1, p.life / 10)})`; const sz = p.spark ? Math.max(1, zp * 0.5) : p.suck ? zp : Math.max(1, zp * 0.7); ctx.fillRect(s.x, s.y, sz, sz); diff --git a/src/sim/drill.js b/src/sim/drill.js index 8e8d789..5fa9f84 100644 --- a/src/sim/drill.js +++ b/src/sim/drill.js @@ -8,14 +8,14 @@ // medium: a cone, the all-rounder // wide: a broad fan that clears a big face close to the bot, but reaches only a short way // Tuned to one material, a pulse passes through everything else and shatters only that, and -// loose grains it passes are shaken into short hops, so piles flow and pockets drain. +// loose grains it passes are jostled toward the bot, so a shattered vein pours out of the wall. // Built blocks, machines and molds soak pulses up unharmed. // half: half the spread angle; mouth: half the width of the fan where it leaves the bot; // en: energy per ray; damp: energy kept per grain (higher bores deeper); reach: x bit reach // Narrow/medium compensate for rubble now passing pulses; wide keeps its old squared loss. const PULSE_W = [ - { name: 'narrow', half: 0.03, mouth: 3.2, rays: 13, en: 1.75, damp: 0.76, reach: 1.45 }, - { name: 'medium', half: 0.24, mouth: 3, rays: 23, en: 1.4, damp: 0.69, reach: 1 }, + { name: 'narrow', half: 0.03, mouth: 3.2, rays: 13, en: 1.75, damp: 0.75, reach: 1.45 }, + { name: 'medium', half: 0.24, mouth: 3, rays: 23, en: 1.4, damp: 0.67, reach: 1 }, { name: 'wide', half: 0.62, mouth: 3.4, rays: 41, en: 1.32, damp: 0.6084, reach: 0.7 }, ]; const PULSE_EVERY = 8, PULSE_SPEED = 2.2, PULSE_K = 240; // ticks between pulses, cells per tick, shake per unit of energy @@ -105,7 +105,6 @@ function pulseTick(step = 0.5) { const loose = 0.97 ** step, liquid = 0.970225 ** step, built = 0.0625 ** step; const skip = 0.986049 ** step, hard = 0.1225 ** step, tuned = 0.9216 ** step; const wood = 0.85 ** step, leaves = 0.97 ** step; - let dust = 0; // cells inside the bot's body are not touched (it flies in its own little space) const bx0 = Math.floor(bot.x), by0 = Math.floor(bot.y), bx1 = Math.ceil(bot.x + bot.w) - 1, by1 = Math.ceil(bot.y + bot.h) - 1; for (const p of drillPulses) { @@ -115,37 +114,6 @@ function pulseTick(step = 0.5) { let e = p.en[k]; if (e < 0.04) continue; const ray = pulseRay(p.ox, p.oy, p.ang, Wd, -1 + 2 * (k + p.jit) / p.n), ux = ray.ux, uy = ray.uy; - // Keep shaking the travelled path while the wave advances, including the pile interior. - let shake = Wd.en, kicks = 0; - for (let d = 0.5; d < lim && shake >= 0.04 && kicks < 3; d++) { - const x = Math.floor(ray.x + ux * d), y = Math.floor(ray.y + uy * d); - if (x < 0 || y < 0 || x >= W || y >= H) break; - if (x >= bx0 && x <= bx1 && y >= by0 && y <= by1) continue; - const i = y * W + x, m = mat[i], ph = PH[m]; - if (ph === PH_SOLID) break; - if (ph === PH_LIQUID) { shake *= 0.970225; continue; } - if (ph !== PH_POWDER) continue; - shake *= 0.97; - if (stamp[i] !== tickStamp && !occ[i] && !part[i] && rnd() < 0.55 * shake) { - // Lead with the reverse ray; blocked grains can lift or spill to either side. - const backX = Math.abs(ux) > 0.38 ? -Math.sign(ux) : 0; - const backY = Math.abs(uy) > 0.38 ? -Math.sign(uy) : 0; - const side = rnd() < 0.5 ? -1 : 1; - for (let hop = 0; hop < 5; hop++) { - const mx = hop < 2 ? backX : hop < 4 ? side : -side; - const my = hop === 0 ? backY : hop === 1 ? -1 : -(hop & 1); - const nx = x + mx, ny = y + my; - if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue; - const j = ny * W + nx; - if (mat[j] !== EMPTY && PH[mat[j]] !== PH_GAS) continue; - if (occ[j] || part[j] || stamp[j] === tickStamp || !enter(m, ph, j, mx, my)) continue; - if (my && CORNER_SEAL && PH[mat[i + mx]] === PH_SOLID && PH[mat[i + my * W]] === PH_SOLID) continue; - move(i, j, x, y, nx, ny); kicks++; - if (dust < 4) { emit({ t: 'debris', x, y, m, dust: true }); dust++; } - break; - } - } - } let sd = p.s[k]; while (sd < lim && e >= 0.04) { sd += step; @@ -160,7 +128,20 @@ function pulseTick(step = 0.5) { } if (ph === PH_LIQUID) { e *= liquid; continue; } if (ph === PH_POWDER) { + // loose grains are jostled back toward the bot, so a shattered pocket drains out of the + // wall and a bore clears itself: straight back if there is room, else down or up a step e *= loose; + // Preserve the old half-grain chance when the sampling distance changes. + if (stamp[i] !== tickStamp && rnd() < 1 - (1 - 0.3 * e) ** (2 * step)) { + const sx = Math.abs(ux) > 0.3 ? -Math.sign(ux) : 0, sy = Math.abs(uy) > 0.3 ? -Math.sign(uy) : 0; + for (const [mx, my] of [[sx, sy], [sx, 1], [sx, -1], [0, sy]]) { + if (!mx && !my) continue; + const nx = x + mx, ny = y + my; + if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue; + const j = ny * W + nx; + if ((mat[j] === EMPTY || PH[mat[j]] === PH_GAS) && !occ[j] && !part[j]) { move(i, j, x, y, nx, ny); break; } + } + } continue; } if (builtCell(i, x, y)) { e *= built; continue; } diff --git a/src/ui/events.js b/src/ui/events.js index d106fa6..d8baee2 100644 --- a/src/ui/events.js +++ b/src/ui/events.js @@ -17,7 +17,7 @@ function drainEvents() { showCard({ kind: e.kind === 'bit' ? 'Drill bit upgraded' : 'Bag upgraded', title: e.kind === 'bit' ? BIT_TIERS[e.tier].name : TANK_TIERS[e.tier].name, accent: 'milestone', body: [e.kind === 'bit' ? `Digs hardness ${BIT_TIERS[e.tier].hard} and below.` : `Holds ${TANK_TIERS[e.tier].cap.toLocaleString('en-US')} grains.`] }); hudDirty = true; - } else if (e.t === 'debris') spawnDebris(e.x, e.y, e.m, e.dust); + } else if (e.t === 'debris') spawnDebris(e.x, e.y, e.m); else if (e.t === 'suck') spawnSuck(e.x, e.y, e.m); else if (e.t === 'warn') spawnFloater(e.x, e.y, e.text, '#ff8a72'); else if (e.t === 'sold') spawnFloater(e.x, e.y, '+' + fmtMoney(e.v)); diff --git a/test/t_drill.js b/test/t_drill.js index 811b6ac..de636b7 100644 --- a/test/t_drill.js +++ b/test/t_drill.js @@ -178,7 +178,7 @@ check('a mold and what sets inside it stay safe from the drill', run(`(() => { for (const i of moldCellsAt(mo.kind, mo.x, mo.y).walls) if (crumbly(i)) return false; return true; })()`)); -// No gravity or kicks here: measure absorption through a fixed thickness, beyond normal reach +// No gravity or nudges here: measure absorption through a fixed thickness, beyond normal reach // for the 115-grain case. All real medium rays still cross the powder before reaching basalt. function rubbleShield(thickness) { flatWorld(run, 240, 160, 150); @@ -206,36 +206,7 @@ check('115 grains of rubble prevent basalt breaking during sustained drilling', const through118 = rubbleShield(118); check('118 grains extinguish the ray before it can even shake basalt', through118 === 0 && !run('digp.some(d => d > 0)'), `${through118} basalt opened`); -// A 35-grain-deep pile against basalt: compare with gravity alone, tracking only rubble. -function channelPile(fire) { - flatWorld(run, 240, 160, 110); - return run(`(() => { - RS=12345; tick=0; lastPulseTick=-99; drillPulses=[]; player.bitTier=3; - bot.x=45; bot.y=88; EV.length=0; - for(let y=85;y<110;y++) { - for(let x=55;x<90;x++) setCell(y*W+x,RSTONE,20,0); - for(let x=90;x<150;x++) setCell(y*W+x,BASALT,20,F_NAT); - } - const measure=()=>{let n=0,sx=0,empty=0;for(let i=0;ie.t==='debris' && e.dust).length; EV.length=0; - } - return {before,after:measure(),dust}; - })()`); -} -const channelRest=channelPile(false), channel=channelPile(true); -check('medium pulses open most of the centre ray through a 35-grain pile within 480 ticks', - channel.after.empty>=28 && channel.after.empty>channelRest.after.empty+15,JSON.stringify(channel)); -check('the pile streams back toward the bot rather than just bouncing on its surface', - channel.after.cx${channel.after.n}`); -check('shaking loose grains emits dust puffs',channel.dust>0,`${channel.dust} puffs`); - -// Absorption over a fixed distance, isolated from gravity, kicks and cell breakage. +// Absorption over a fixed distance, isolated from gravity, nudges and cell breakage. function transmission(material, distance, step, tune = 0) { flatWorld(run, 240, 160, 150); return run(`(() => { @@ -249,7 +220,7 @@ function transmission(material, distance, step, tune = 0) { for (const [m, distance, factor, tune] of [ ['RSTONE',35,0.97,0], ['SAND',35,0.97,0], ['FEORE',35,0.97,0], ['COAL',35,0.97,0], ['WATER',10,0.985**2,0], ['GRANITE',1,0.35**2,0], ['STONE',10,0.993**2,'CUORE'], - ['STONE',1,0.69,0], ['STONE',1,0.96**2,'RSTONE'], ['BELTL',1,0.25**2,0], + ['STONE',1,0.67,0], ['STONE',1,0.96**2,'RSTONE'], ['BELTL',1,0.25**2,0], ]) { const energies = [0.25,0.5,1].map(step => transmission(m,distance,step,tune)); const expected = 1.4 * factor ** distance; @@ -257,57 +228,30 @@ for (const [m, distance, factor, tune] of [ energies.every(e => Math.abs(e-expected) < 0.00001), energies.map(e=>e.toFixed(5)).join(' / ')); } -// A settled heap must slump further than an undrilled control, conserving all its grains. -function shakenPile(pulse) { - flatWorld(run,240,160,110); - run(`RS=12345; tick=0; lastPulseTick=-99; drillPulses=[]; player.bitTier=3; bot.x=40; bot.y=88; - for(let x=65;x<116;x++) for(let y=85+Math.abs(x-90);y<110;y++) setCell(y*W+x,RSTONE,20,0); - for(let t=0;t<240;t++) simTick();`); - const height = () => run(`(() => {let top=110;for(let i=0;i<110*W;i++) if(mat[i]===RSTONE) top=Math.min(top,(i/W)|0);return 110-top;})()`); - const before=height(), grains=count(run,run('RSTONE')); - run(`for(let t=0;t<480;t++) {${pulse?'toolPulse(90,94,1,0);':''}simTick();}`); - return {before,after:height(),grains,left:count(run,run('RSTONE'))}; +// The original nudge visits only the wave front: back first, then down, then up. +for (const [blocked, expectedY] of [[0,80], [1,81], [2,79], [3,80]]) { + flatWorld(run,240,160,150); + const got=run(`(() => { + bot.x=0;bot.y=0;tickStamp=1;RS=12345; + setCell(80*W+25,RSTONE,20,0); + if (${blocked}>=1) setCell(80*W+24,STONE,20,F_NAT); + if (${blocked}>=2) setCell(81*W+24,STONE,20,F_NAT); + if (${blocked}>=3) setCell(79*W+24,STONE,20,F_NAT); + const p={ox:24.5,oy:80.5,ang:0,w:1,jit:0.5,n:1,damp:PULSE_W[1].damp, + en:new Float32Array([1.4]),s:new Float32Array(1),r:0,range:1,tune:0}; + drillPulses=[p];pulseTick(); + return mat[${expectedY}*W+${blocked===3?25:24}]===RSTONE; + })()`); + check(`original loose-grain nudge with ${blocked} blocked exits`,got); } -const restingPile=shakenPile(false), fluidPile=shakenPile(true); -check('pulses fluidise a settled rubble pile so its height drops', - fluidPile.after <= fluidPile.before-3 && fluidPile.after < restingPile.after-3, - `resting ${restingPile.before}->${restingPile.after}, shaken ${fluidPile.before}->${fluidPile.after}`); -check('shaking conserves the pile material',fluidPile.grains===fluidPile.left,`${fluidPile.grains}->${fluidPile.left}`); -check('repeating a seeded pile gives the same result',JSON.stringify(shakenPile(true))===JSON.stringify(fluidPile)); - -// A wave must still shake loose grains far behind its advancing front, throughout the path. flatWorld(run,240,160,150); -const travelled=run(`(() => { - RS=12345; bot.x=0;bot.y=0;tickStamp=1;EV.length=0; - for(const x of [25,45,65]) setCell(80*W+x,RSTONE,20,0); - const p={ox:19.5,oy:80.5,ang:0,w:1,jit:0.5,n:1,damp:PULSE_W[1].damp, - en:new Float32Array([1.4]),s:new Float32Array([60]),r:60,range:100,tune:0}; - drillPulses=[p];pulseTick(); - return [25,45,65].filter(x=>mat[80*W+x-1]===RSTONE).length; -})()`); -check('the travelled ray path pushes interior grains one cell toward the bot',travelled>=2,`${travelled}/3 botward hops (probabilistic kicks)`); - -// One artificially long ray crosses many exposed grains in a single tick, exercising the caps. -flatWorld(run,240,160,150); -const kicks = run(`(() => { - RS=12345; bot.x=0;bot.y=0;tickStamp=1; EV.length=0; - for(let x=20;x<120;x+=2) setCell(80*W+x,RSTONE,20,0); - // Neither a covered grain nor a grain already moved this tick may be kicked. - occ[80*W+20]=1; part[80*W+22]=P_PIPE; stamp[80*W+24]=tickStamp; - // An open but owned destination and one stamped this tick are also off limits. - part[80*W+25]=P_PIPE; occ[80*W+27]=1; stamp[80*W+29]=tickStamp; - const p={ox:19.5,oy:80.5,ang:0,w:1,jit:0.5,n:8,damp:PULSE_W[1].damp, - en:new Float32Array(8).fill(1.4),s:new Float32Array(8),r:200,range:100,tune:0}; - // Zero spread gives eight overlapping rays, testing the per-tick dust cap as well. - const half=PULSE_W[1].half,mouth=PULSE_W[1].mouth; PULSE_W[1].half=0;PULSE_W[1].mouth=0; - drillPulses=[p]; pulseTick(); PULSE_W[1].half=half;PULSE_W[1].mouth=mouth; - let moved=0; for(let i=0;ie.t==='debris').length, - protected:mat[80*W+20]===RSTONE && mat[80*W+22]===RSTONE && mat[80*W+24]===RSTONE, - blocked:mat[80*W+25]===EMPTY && mat[80*W+27]===EMPTY && mat[80*W+29]===EMPTY}; -})()`); -check('kicks are bounded to three per ray and dust to four puffs per tick',kicks.moved>0 && kicks.moved<=24 && kicks.dust>0 && kicks.dust<=4,JSON.stringify(kicks)); -check('shaking respects occupied, owned and stamped cells',kicks.protected && kicks.blocked,JSON.stringify(kicks)); +check('a pulse leaves grains behind its advancing front alone',run(`(() => { + bot.x=0;bot.y=0;tickStamp=1;RS=12345; + setCell(80*W+25,RSTONE,20,0); + drillPulses=[{ox:19.5,oy:80.5,ang:0,w:1,jit:0.5,n:1,damp:PULSE_W[1].damp, + en:new Float32Array([1.4]),s:new Float32Array([60]),r:60,range:100,tune:0}]; + pulseTick();return mat[80*W+25]===RSTONE; +})()`)); // Resonance ping (t50): finds natural veins within reach of the bit, under fog too, and // nothing else. diff --git a/test/t_gpu.py b/test/t_gpu.py index f6ba2d6..e84022e 100644 --- a/test/t_gpu.py +++ b/test/t_gpu.py @@ -22,19 +22,6 @@ with launch(viewport={'width':960,'height':600}) as (_,pg,url): }""") assert startup['warm']>=0 print('PASS both looks submit behind loading screen; first visible frame ms',startup) - dust=pg.evaluate("""()=>{ - const particles=SET.particles,fogOn=SET.fog;SET.particles=true;SET.fog=false; - R.particles=[];EV.length=0;emit({t:'debris',x:bot.x,y:bot.y,m:RSTONE,dust:true});drainEvents(); - const spawned=R.particles.length===1 && R.particles[0].dust===true; - R.ctx.clearRect(0,0,R.canvas.width,R.canvas.height);drawParticles(); - const data=R.ctx.getImageData(0,0,R.canvas.width,R.canvas.height).data; - let visible=0,soft=0;for(let i=3;i4 and dust['soft']==dust['visible'] and dust['expired'] and dust['disabled'],dust - print('PASS pulse dust events draw a visible soft puff, expire and respect particles setting',dust) pg.add_script_tag(path=str(root/'tools/perf-scenes.js')) pg.evaluate("async()=>{perfScene('factory',1024,640);await startThreads();simPool.force=true;SET.look='pixel';SET.zoom=2;resizeRender();snapCamera();drawWorld();}") result=pg.evaluate('''async () => {