Fix blocky WebGPU sky: view inputs refresh when the camera moves (t91)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 17:46:52 -04:00
co-authored by Claude Opus 5.5
parent fbc3b07be2
commit f7ec8af791
12 changed files with 108 additions and 9 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 115 KiB

+55
View File
@@ -57,6 +57,61 @@ with launch(viewport={'width':960,'height':600}) as (_,pg,url):
}""")
assert all(r['max']==0 for r in parity),parity
print('PASS dirty and forced full uploads render identically in',len(parity),'look/light/lens/fog configurations')
# Adjacent noise cells must use the same hash for their shared lattice corners.
noise=pg.evaluate("""async()=>{
const code=GPU_SMOOTH.slice(GPU_SMOOTH.indexOf('fn h21'),GPU_SMOOTH.indexOf('fn lin'))+`
@vertex fn vs(@builtin(vertex_index) i:u32)->@builtin(position) vec4f {
let p=array<vec2f,3>(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3));return vec4f(p[i],0,1);
}
@fragment fn fs(@builtin(position) p:vec4f)->@location(0) vec4f {
let edge=floor(p.x/2.);let along=floor(p.y)*0.1;
let epsilon=select(-0.0001,0.0001,u32(p.x)%2u==1u);
return vec4f(vn(vec2f(edge+epsilon,along)),vn(vec2f(along,edge+epsilon)),0,1);
}`;
const module=GPU.device.createShaderModule({code});
const pipeline=await GPU.device.createRenderPipelineAsync({layout:'auto',
vertex:{module,entryPoint:'vs'},fragment:{module,entryPoint:'fs',targets:[{format:'rgba8unorm'}]}});
const texture=GPU.device.createTexture({size:[960,100],format:'rgba8unorm',
usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.COPY_SRC});
const buffer=GPU.device.createBuffer({size:960*100*4,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ});
const encoder=GPU.device.createCommandEncoder();
const pass=encoder.beginRenderPass({colorAttachments:[{
view:texture.createView(),loadOp:'clear',storeOp:'store'}]});
pass.setPipeline(pipeline);pass.draw(3);pass.end();
encoder.copyTextureToBuffer({texture},{buffer,bytesPerRow:960*4},[960,100]);
GPU.device.queue.submit([encoder.finish()]);await buffer.mapAsync(GPUMapMode.READ);
const pixels=new Uint8Array(buffer.getMappedRange());let max=0;
for(let y=0;y<100;y++)for(let x=0;x<960;x+=2)for(let c=0;c<2;c++) {
const i=(y*960+x)*4+c;max=Math.max(max,Math.abs(pixels[i]-pixels[i+4]));
}
buffer.unmap();buffer.destroy();texture.destroy();return max;
}""")
assert noise<=1,noise
print('PASS smooth noise is continuous across horizontal and vertical lattice boundaries',noise)
motion=pg.evaluate("""async()=>{
const results=[],now=performance.now.bind(performance);performance.now=()=>5000;
try {
SET.fog=false;SET.glow='low';SET.lightEvery=1;R.lens=false;
for(const look of ['pixel','smooth'])for(const light of ['full','fast','off']) {
SET.look=look;SET.lighting=light;
for(let n=0;n<12;n++) {
SET.zoom=6-n*0.37;resizeRender();R.camX=80.25+n*7.3;R.camY=5.5+n*0.43;
R.rain=0;R.frame++;drawWorld();await GPU.device.queue.onSubmittedWorkDone();
}
const a=worldPixelContext().getImageData(0,0,960,600).data;
const cleanCells=GPU.uploadCells;
// Discard all view textures/light history as well as the resident-world cache.
GPU.resident.fill(0);GPU.viewKey=null;GPU.lightFrame=undefined;
R.rain=0;drawWorld();await GPU.device.queue.onSubmittedWorkDone();
const b=worldPixelContext().getImageData(0,0,960,600).data;
let max=0;for(let i=0;i<a.length;i++)max=Math.max(max,Math.abs(a[i]-b[i]));
results.push({look,light,max,cleanCells});
}
SET.lighting='full';return results;
} finally {performance.now=now;}
}""")
assert all(r['max']==0 for r in motion),motion
print('PASS pan/zoom matches fresh full redraw in both looks and all light modes',motion)
cadence=pg.evaluate("""()=>{
SET.lightEvery=3;R.frame=1002;drawWorld();const old=GPU.lightFrame,origin=R.lx0;
R.camX+=4;R.frame=1003;drawWorld();const held=GPU.lightFrame===old && R.lx0===origin;
+1 -1
View File
@@ -1,4 +1,4 @@
"""Frozen legacy references, captured before retirement; compare the WebGPU world only."""
"""Frozen world references; seven smooth crops corrected for continuous noise in t91."""
import base64, gzip, json, pathlib, sys, subprocess
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / 'tools'))
from use_venv import ensure_playwright; ensure_playwright()