Belts carry 4 grains deep; stalled belts warn and stop animating (t45)

This commit is contained in:
2026-10-29 23:21:27 -04:00
parent 99aae0aae0
commit ac5f5a06d1
27 changed files with 335 additions and 51 deletions
+10 -2
View File
@@ -15,7 +15,8 @@ function cellPixel(x, y) {
return;
}
const useFog = SET.fog, grain = SET.grain, incand = SET.incandescence;
const anim = R.frame, lens = R.lens, tl = THERM[SET.thermalPalette] || THERM.inferno;
let anim = R.frame;
const lens = R.lens, tl = THERM[SET.thermalPalette] || THERM.inferno;
const glowOn = SET.glow !== 'off' && !lens, gpx = R.glowPx, gw = R.glowW;
const fy = y / FOG, fj = Math.floor(fy), ft = fy - fj, fr = fj * FW, fn = Math.min(FH - 1, fj + 1) * FW;
const yw = y * W;
@@ -36,6 +37,8 @@ function cellPixel(x, y) {
}
}
const i = yw + x, m = mat[i], nz = noiseB[i] - 128;
const ownBlock = (y >> 2) * BW + (x >> 2);
if (isBelt(blockP[ownBlock]) || isFilter(blockP[ownBlock]) || blockP[ownBlock] === BP_SCREW) anim = transportClock(ownBlock);
const T = temp[i];
// Also used by the thermal lens, which always draws in pixels.
let looseShade = 1;
@@ -138,6 +141,7 @@ function cellPixel(x, y) {
const db = diagonalScrewBlock(x, y, sm);
let diagonal = false;
if (db >= 0) {
anim = transportClock(db);
const v = blockV[db], joins = diagonalJoins[db];
const sd = diagonalScrewShape(x + 0.5, y + 0.5, db, v, joins);
p = sm || sd <= 0 ? SCREW_PARTS[v] : 0;
@@ -193,7 +197,7 @@ function cellPixel(x, y) {
}
const slopeArt = sb >= 0 && (m === EMPTY || PH[m] === PH_GAS || m === BELTL || m === BELTR);
if (slopeArt && !sm) {
const color = slopeBeltColor(sb, x, y, anim, br, bgc, bb);
const color = slopeBeltColor(sb, x, y, transportClock(sb), br, bgc, bb);
r = color & 255; g = color >> 8 & 255; b = color >> 16 & 255;
}
const slopeDetail = slopeDetailAt(i, x, y, sm);
@@ -272,5 +276,9 @@ function cellPixel(x, y) {
bpx[row + bx] = fogColor(c, k); tpx[row + bx] = fogColor(t, k);
}
}
if (sm && GPU.motionData) {
const owner = slopeDetail && slopeWallOwner >= 0 ? slopeWallOwner : sb >= 0 ? sb : db >= 0 ? db : ownBlock;
GPU.motionData[row + bx] = transportClock(owner) / 60;
}
px[row + bx] = top | ((b > 255 ? 255 : b) << 16) | ((g > 255 ? 255 : g) << 8) | (r > 255 ? 255 : r);
}
+2
View File
@@ -7,6 +7,8 @@ function gpuOverrides() {
if (R.smoothOn) {
if (!GPU.overrideMask || GPU.overrideMask.length !== R.px.length) GPU.overrideMask = new Uint8Array(R.px.length);
GPU.overrideMask.fill(0);
if (!GPU.motionData || GPU.motionData.length !== R.px.length) GPU.motionData = new Float32Array(R.px.length);
else GPU.motionData.fill(0);
}
if (!GPU.blocks || GPU.blocks.length !== NB) GPU.blocks = new Uint8Array(NB);
const marked = GPU.blocks; marked.fill(0);
+8 -6
View File
@@ -10,6 +10,7 @@ struct Params { view: vec4f, world: vec4f, style: vec4f, effects: vec4f, camera:
@group(0) @binding(6) var tGlow: texture_2d<f32>;
@group(0) @binding(7) var linear: sampler;
@group(0) @binding(8) var<uniform> su: vec4f;
@group(0) @binding(9) var tMotion: texture_2d<f32>;
fn sampleLevel(t: texture_2d<f32>, uv: vec2f, level: f32) -> vec4f { return textureSampleLevel(t,linear,uv,level); }
fn modulo(x: f32,y: f32) -> f32 { return x-y*floor(x/y); }
fn h21(inputP: vec2f) -> f32 { var p=inputP; p = fract(p * vec2f(123.34, 456.21)); p += dot(p, p + 45.32); return fract(p.x * p.y); }
@@ -51,9 +52,9 @@ fn beltLineSD(c: vec2i, lb: vec2f) -> f32 {
return beltSD(lb, (caps & 1) != 0, (caps & 2) != 0);
}
fn bandRidge(w: vec2f, lb: vec2f, dir: f32) -> f32 {
fn bandRidge(w: vec2f, lb: vec2f, dir: f32, time: f32) -> f32 {
var run: f32 = select( -1.0, 1.0 , lb.y < 2.0 );
return step(0.72, fract((w.x - dir * run * (su.x) * 30.0) * 0.5));
return step(0.72, fract((w.x - dir * run * time * 30.0) * 0.5));
}
fn bandColor(lb: vec2f, rg: f32) -> vec3f { return mix(vec3f(0.2, 0.21, 0.24), vec3f(0.34, 0.35, 0.39), rg) * (select( 0.72, 1.0 ,lb.y < 2.0 )); }
@@ -72,6 +73,7 @@ ${GPU_SMOOTH_SLOPE_HELPERS}
var qq: vec2f = q - 0.5; var c0: vec2i = vec2i(floor(qq)); var t: vec2f = qq - floor(qq);
var k0: vec4f = cellAt(tCol, c0); var k1: vec4f = cellAt(tCol, c0 + vec2i(1, 0)); var k2: vec4f = cellAt(tCol, c0 + vec2i(0, 1)); var k3: vec4f = cellAt(tCol, c0 + vec2i(1, 1));
var n0: i32 = cls(k0); var n1: i32 = cls(k1); var n2: i32 = cls(k2); var n3: i32 = cls(k3);
let motionTime = cellAt(tMotion, c).r;
var own: vec4f = cellAt(tCol, c); var nc: i32 = cls(own);
var s: vec2f = smoothstep(vec2f(0.1), vec2f(0.9), t);
var wq: vec4f = vec4f((1.0 - s.x) * (1.0 - s.y), s.x * (1.0 - s.y), (1.0 - s.x) * s.y, s.x * s.y);
@@ -228,14 +230,14 @@ ${GPU_SMOOTH_SLOPE_DRAW}
var edge: f32 = -sd;
var bc: vec3f;
if (edge < 0.8) {
bc = bandColor(lb, bandRidge(w, lb, dir));
bc = bandColor(lb, bandRidge(w, lb, dir, motionTime));
bc *= mix(0.55, 1.0, smoothstep(0.0, max(px * 1.4, 0.12), edge));
} else {
var rc: vec2f = vec2f(select( 3.0, 1.0 ,lb.x < 2.0 ), 2.0); var dv: vec2f = lb - rc;
var rd: f32 = length(dv);
bc = vec3f(0.15, 0.16, 0.18);
if (rd < 0.95) {
var ang: f32 = atan2(dv.y, dv.x) - dir * (su.x) * 30.0 / 0.95;
var ang: f32 = atan2(dv.y, dv.x) - dir * motionTime * 30.0 / 0.95;
var spoke: f32 = smoothstep(0.55, 0.85, cos(ang * 3.0));
bc = mix(vec3f(0.5, 0.36, 0.25), vec3f(0.72, 0.53, 0.36), 0.5 + 0.5 * dot(normalize(dv + 1e-4), vec2f(-0.6, -0.8)) * smoothstep(0.2, 0.95, rd));
bc *= mix(1.0, 0.72, spoke * smoothstep(0.25, 0.4, rd));
@@ -294,7 +296,7 @@ ${GPU_SMOOTH_SLOPE_DRAW}
tc *= mix(0.5, 1.0, smoothstep(0.0, max(px * 1.3, 0.1), min(a, 4.0 - a)));
} else {
var full: bool = own.r + own.g + own.b > 0.0;
var fl: f32 = fract((along + a * 0.9 - sp2 * (su.x) * 14.0) * 0.5);
var fl: f32 = fract((along + a * 0.9 - sp2 * motionTime * 14.0) * 0.5);
var flight: f32 = smoothstep(0.82, 0.92, fl) * (1.0 - smoothstep(0.92, 1.0, fl));
var bore: vec3f = vec3f(0.11, 0.1, 0.11) * (0.8 + 0.4 * (1.0 - abs(a - 2.0) / 1.4));
tc = select( bore, own.rgb * (0.9 + 0.1 * vn(w * 1.3)) ,full );
@@ -343,7 +345,7 @@ ${GPU_SMOOTH_SLOPE_DRAW}
var hole: f32 = 1.0 - smoothstep(0.5 - bw2 - px, 0.5 - bw2, max(cg.x, cg.y));
var fc: vec3f;
if (edge < 0.8) {
var rg: f32 = bandRidge(w, lb, dir);
var rg: f32 = bandRidge(w, lb, dir, motionTime);
var band: vec3f = bandColor(lb, rg);
if (tinted && (carry == (lb.y < 2.0))) { band = mix(band, tag.rgb * (select( 0.8, 1.0 ,lb.y < 2.0 )), 0.75 * rg); }
fc = mix(band, behind * 0.85, hole * (1.0 - rg) * 0.75);
+1 -1
View File
@@ -76,7 +76,7 @@ const GPU_SMOOTH_SLOPE_DRAW = ` if (nc == 31 || nc == 32) {
var local: vec2f = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w);
var shapePos: vec3f = slopeShape(local, w - local, grade, joins);
var sd: f32 = shapePos.x; var axis: f32 = shapePos.y; var depth: f32 = shapePos.z;
var time: f32 = select( 0.0, (su.x) , !mesh && (joins & 16) != 0 );
var time: f32 = select( 0.0, motionTime , !mesh && (joins & 16) != 0 );
var lb: vec2f = vec2f(axis, depth); var alongWorld: vec2f = vec2f(axis, depth);
var rg: f32 = step(0.72, fract((axis - dir * (select( -1.0, 1.0 ,depth < 2.0 )) * time * 30.0) * 0.5));
var belt: vec3f;
+4 -2
View File
@@ -19,16 +19,17 @@ async function gpuSmoothInit() {
function gpuSmoothTextures() {
const w=R.bw,h=R.bh;
GPU.smoothOver=Array.from({length:3},()=>gpuTexture(w,h));
GPU.motion=gpuTexture(w,h,'r32float');
GPU.smoothMask=gpuTexture(w,h,'r8uint');
GPU.backTex=gpuTexture(w,h,'rgba8unorm',true);
GPU.treeTex=gpuTexture(w,h,'rgba8unorm',true);
GPU.fieldTex=GPU.device.createTexture({size:[w,h],format:'rgba8unorm',mipLevelCount:Math.min(4,1+Math.floor(Math.log2(Math.max(w,h)))),usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.STORAGE_BINDING|GPUTextureUsage.RENDER_ATTACHMENT});
GPU.viewTextures.push(...GPU.smoothOver,GPU.smoothMask,GPU.backTex,GPU.treeTex,GPU.fieldTex);
GPU.viewTextures.push(...GPU.smoothOver,GPU.smoothMask,GPU.motion,GPU.backTex,GPU.treeTex,GPU.fieldTex);
}
function gpuSmoothBindings(group,view,buffer) {
const colourResources=[buffer(GPU.uniform),buffer(GPU.lut),buffer(GPU.land),...GPU.worldTextures.slice(0,9).map(view),view(GPU.override),view(GPU.overrideGlow),view(GPU.colourTex),null,view(GPU.backTex),view(GPU.treeTex),GPU.fieldTex.createView({baseMipLevel:0,mipLevelCount:1}),...GPU.smoothOver.map(view),view(GPU.smoothMask)];
GPU.bindColourSmooth=GPU.device.createBindGroup({layout:GPU.colourSmooth.getBindGroupLayout(0),entries:colourResources.map((resource,binding)=>({binding,resource})).filter(e=>e.resource)});
GPU.bindSmooth=group(GPU.smooth,[buffer(GPU.uniform),view(GPU.colourTex),view(GPU.backTex),view(GPU.treeTex),view(GPU.fieldTex),view(GPU.lightTex),view(GPU.glowTex),GPU.sampler,buffer(GPU.smoothUniform)]);
GPU.bindSmooth=group(GPU.smooth,[buffer(GPU.uniform),view(GPU.colourTex),view(GPU.backTex),view(GPU.treeTex),view(GPU.fieldTex),view(GPU.lightTex),view(GPU.glowTex),GPU.sampler,buffer(GPU.smoothUniform),view(GPU.motion)]);
GPU.fieldMips=[];
for(let i=1;i<GPU.fieldTex.mipLevelCount;i++) GPU.fieldMips.push({
bind:group(GPU.fieldMip,[GPU.fieldTex.createView({baseMipLevel:i-1,mipLevelCount:1}),GPU.sampler]),
@@ -36,6 +37,7 @@ function gpuSmoothBindings(group,view,buffer) {
});
}
function gpuSmoothUpload() {
gpuWrite(GPU.motion,GPU.motionData,R.bw,0,0,R.bw,R.bh);
for(const [i,a] of [R.backPx,R.treePx,R.fieldPx].entries()) gpuWrite(GPU.smoothOver[i],a,R.bw,0,0,R.bw,R.bh);
gpuWrite(GPU.smoothMask,GPU.overrideMask,R.bw,0,0,R.bw,R.bh);
GPU.device.queue.writeBuffer(GPU.smoothUniform,0,new Float32Array([(performance.now()-SM.t0)/1000,smoothSurface(),SET.glow==='off'?0:SET.glow==='high'?0.9:0.55,0]));
+18
View File
@@ -368,3 +368,21 @@ function drawParticles() {
ctx.fillRect(s.x, s.y, sz, sz);
}
}
// Always visible, in both looks: amber warning on each stopped transport block.
function drawTransportStalls() {
const ctx = R.ctx, v = viewCells(), s = BS * R.zp;
ctx.save();
for (const [b] of transportState) {
if (!transportStalled(b)) continue;
const x = b % BW * BS, y = Math.floor(b / BW) * BS;
if (x + BS < R.camX || x > R.camX + v.w || y + BS < R.camY || y > R.camY + v.h || (SET.fog && fogSeen(x + 2, y + 2) < 64)) continue;
const p = worldToScreenPx(x, y);
ctx.fillStyle = 'rgba(255,164,48,0.25)'; ctx.fillRect(p.x, p.y, s, s);
ctx.strokeStyle = '#ffbd59'; ctx.lineWidth = Math.max(1, R.zp * 0.25); ctx.strokeRect(p.x, p.y, s, s);
ctx.fillStyle = '#ffdc85';
ctx.fillRect(p.x + s * 0.45, p.y + s * 0.15, s * 0.1, s * 0.4);
ctx.fillRect(p.x + s * 0.45, p.y + s * 0.7, s * 0.1, s * 0.1);
}
ctx.restore();
}
+1 -1
View File
@@ -33,7 +33,7 @@ function prepareSlopeBelts(x0, y0, x1, y1) {
if (c === BP_BELT || c === BP_FILTER) beltCaps[b] = flatBeltCaps(b);
if (!isSlopeBelt(c)) continue;
const face = beltFaceY(c, bx, by, bx * BS) * W + bx * BS;
slopeBeltJoins[b] = slopeBeltConnections(bx, by, c) | (netSat(face) > 0 ? 16 : 0) | (filterCfg.get(b)?.inv ? 32 : 0) | (isFilter(c) && netSat(face) > 0 && screenLoaded(b) && (tick & 2) ? 64 : 0);
slopeBeltJoins[b] = slopeBeltConnections(bx, by, c) | (netSat(face) > 0 ? 16 : 0) | (filterCfg.get(b)?.inv ? 32 : 0) | (isFilter(c) && netSat(face) > 0 && screenLoaded(b) && !transportStalled(b) && (tick & 2) ? 64 : 0);
for (let yy = Math.max(0, by - 2); yy <= Math.min(BH - 1, by + 1); yy++)
for (let xx = Math.max(0, bx - 1); xx <= Math.min(BW - 1, bx + 1); xx++) slopeBeltHalo[yy * BW + xx] = 1;
}