Downhill sloped belts draw as plain belts, ghosts and previews too
Downhill runs have no cleats, so they no longer draw the sidewall or ribs either: placed, as ghosts, or in the drag preview, which now knows the belt's direction. Uphill runs keep the wall and ribs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+4
-3
@@ -462,9 +462,10 @@ above, including:
|
||||
Measured: about 80-86 grains/s uphill under a heavy feed (flat 90); downhill about 38, since
|
||||
the top only feeds a layer two grains deep onto the face.
|
||||
- **No cleats on downhill belts (t42 feedback, 2026-10-26).** The owner asked why a downhill
|
||||
belt has cleats: on 45° the load slides anyway. A downhill sloped belt is now a plain belt with
|
||||
a sidewall that carries at belt speed like a flat one (90 grains/s); only uphill runs have
|
||||
ribs. A whole run also shares one cleat phase, so pieces added later line up with it.
|
||||
belt has cleats: on 45° the load slides anyway. A downhill sloped belt is now a plain belt, with
|
||||
no ribs and no sidewall, that carries at belt speed like a flat one (90 grains/s); only uphill
|
||||
runs are sidewall belts. A whole run also shares one cleat phase, so pieces added later line up
|
||||
with it.
|
||||
|
||||
## Ideas not built yet
|
||||
|
||||
|
||||
@@ -4,6 +4,19 @@ What agents have done, newest first. Add an entry for every piece of work: the d
|
||||
note or request it was for, what changed, the files touched, the tests run, and anything left
|
||||
open or not verified. Keep each entry short; the git history has the details.
|
||||
|
||||
## 2026-10-27: t42 feedback: downhill belts drop the sidewall too; new tasks (Claude Code)
|
||||
|
||||
- Downhill sloped belts now draw as plain belts: no far wall and no ribs, placed or as ghosts or
|
||||
drag previews (`slopeWallAt`, ghost drawing in `render/world.js`, `previewBlock` now gets the
|
||||
belt direction from `previews.js` and `preview-selection.js`). `slopeUphill` takes the
|
||||
direction bits instead of a block.
|
||||
- Test: `t_slope_belt_art.js` checks a downhill run draws no wall or ribs. Drag preview checked
|
||||
headlessly with a counting canvas (downhill draws the band only); placed belts and ghosts
|
||||
checked in screenshots. `python3 test/run_all.py --browser`: ALL PASSED. Rebuilt dist.
|
||||
- Filed t59 (world longevity audit), t60 (renewable resources, discussion), t61 (sloped walls
|
||||
sticky after removing a piece mid-flow: not reproduced headless, see its report), t62 (screw
|
||||
conveyor horizontal/diagonal joins), t63 (vacuum falling grains).
|
||||
|
||||
## 2026-10-26: t42 feedback: uphill stall at a seam, no cleats downhill, slope pick (Claude Code)
|
||||
|
||||
- The owner's long uphill run stalled with a few grains, with a flashing seam between pieces 9
|
||||
|
||||
@@ -149,7 +149,7 @@ function slopeWallAt(x, y, smooth = false) {
|
||||
for (let by = Math.max(0, (y >> 2) - 1); by <= Math.min(BH - 1, (y >> 2) + 3); by++)
|
||||
for (let bx = Math.max(0, (x >> 2) - 2); bx <= Math.min(BW - 1, (x >> 2) + 2); bx++) {
|
||||
const b = by * BW + bx;
|
||||
if (!isBelt(blockP[b]) || !isSlopeBelt(blockP[b])) continue;
|
||||
if (!isBelt(blockP[b]) || !isSlopeBelt(blockP[b]) || !slopeUphill(blockP[b], blockV[b])) continue; // downhill: a plain belt
|
||||
const sd = slopeWallShape(x + 0.5, y + 0.5, b);
|
||||
if (sd <= best) { best = sd; slopeWallOwner = b; }
|
||||
}
|
||||
|
||||
+2
-2
@@ -413,8 +413,8 @@ function drawWorld() {
|
||||
const i = y * W + x, n = (y >> 2) * BW + (x >> 2);
|
||||
if (n !== b && (mat[i] !== EMPTY || part[i] || mach[i] || occ[i] || blockP[n])) continue;
|
||||
sd = slopeBeltShape(x + 0.5, y + 0.5, b, joins, code);
|
||||
const rib = !isFilter(code) && slopePreviewCleat(x, y, b, code);
|
||||
if (sd > 0 && !rib && (isFilter(code) || slopeWallShape(x + 0.5, y + 0.5, b, code) > 0)) continue;
|
||||
const up = !isFilter(code) && slopeUphill(code, v), rib = up && slopePreviewCleat(x, y, b, code);
|
||||
if (sd > 0 && !rib && (!up || slopeWallShape(x + 0.5, y + 0.5, b, code) > 0)) continue;
|
||||
if (rib) sd = -0.1;
|
||||
}
|
||||
if (isSlope(code) && !slopeCell(code, dx, dy)) continue;
|
||||
|
||||
@@ -107,7 +107,7 @@ function carryBeltsAndFilters(belts, filters, d, bm, carrying) {
|
||||
if (carrying) {
|
||||
if (PH[above] === PH_LIQUID) beltDrain(b);
|
||||
// uphill, the cleats do the carrying
|
||||
if (!isSlopeBelt(code) || !slopeUphill(code, (y >> 2) * BW + (x >> 2))) carryColumn(b - W, d, null);
|
||||
if (!isSlopeBelt(code) || !slopeUphill(code, blockV[(y >> 2) * BW + (x >> 2)])) carryColumn(b - W, d, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ function ribCells(x, y, g) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function slopeUphill(c, b) { return beltGrade(c) * (blockV[b] & 1 ? 1 : -1) < 0; }
|
||||
function slopeUphill(c, v) { return beltGrade(c) * (v & 1 ? 1 : -1) < 0; } // v: the belt's direction
|
||||
// Is (x, y) a face cell of a sloped belt with grade g?
|
||||
function slopeFaceAt(x, y, g) {
|
||||
if (x < 0 || x >= W || y < 0 || y >= H) return false;
|
||||
@@ -143,7 +143,7 @@ function slopeCleatsTick(advance = true) {
|
||||
const runs = new Map();
|
||||
for (const list of [beltR, beltL]) for (const i of list) {
|
||||
const x = i % W, y = (i / W) | 0, b = (y >> 2) * BW + (x >> 2), c = blockP[b];
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || !slopeUphill(c, b) || beltFaceY(c, x >> 2, y >> 2, x) !== y || netSat(i) <= 0) continue;
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || !slopeUphill(c, blockV[b]) || beltFaceY(c, x >> 2, y >> 2, x) !== y || netSat(i) <= 0) continue;
|
||||
// lift the run's ribs off; each is put back where it moves to, and a rib leaving the top is gone
|
||||
if (cleatFoot(x, b)) for (const j of ribCells(x, y, beltGrade(c))) if (j >= 0) cleatMask[j] = 0;
|
||||
if (!cleatFoot(x, b, (slopePhase[b] + 1) % CLEAT_SPACING)) continue;
|
||||
@@ -172,7 +172,7 @@ function slopeCleatsTick(advance = true) {
|
||||
// they slide back down between steps, which on 45° is faster than the belt runs
|
||||
for (const list of [beltR, beltL]) for (const i of list) {
|
||||
const x = i % W, y = (i / W) | 0, b = (y >> 2) * BW + (x >> 2), c = blockP[b], a = i - W;
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || !slopeUphill(c, b) || beltFaceY(c, x >> 2, y >> 2, x) !== y || netSat(i) <= 0) continue;
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || !slopeUphill(c, blockV[b]) || beltFaceY(c, x >> 2, y >> 2, x) !== y || netSat(i) <= 0) continue;
|
||||
if (a < 0 || PH[mat[a]] !== PH_POWDER || occ[a] || cleatMask[a] || stamp[a] === tickStamp) continue;
|
||||
const d = blockV[b] & 1 ? 1 : -1, st = conveyorStep(x, y, d), j = a + d + st * W;
|
||||
if (x + d < 0 || x + d >= W || j < 0 || cleatMask[j] || !passable(mat[a], PH_POWDER, j, d, st)) continue;
|
||||
@@ -190,7 +190,7 @@ function rebuildCleatMask() {
|
||||
cleatCells.length = 0;
|
||||
for (const list of [beltR, beltL]) for (const i of list) {
|
||||
const x = i % W, y = (i / W) | 0, b = (y >> 2) * BW + (x >> 2), c = blockP[b];
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || !slopeUphill(c, b) || beltFaceY(c, x >> 2, y >> 2, x) !== y || !cleatFoot(x, b)) continue;
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || !slopeUphill(c, blockV[b]) || beltFaceY(c, x >> 2, y >> 2, x) !== y || !cleatFoot(x, b)) continue;
|
||||
for (const j of ribCells(x, y, beltGrade(c))) if (j >= 0) cleatCells.push(j);
|
||||
}
|
||||
for (const i of cleatCells) {
|
||||
|
||||
@@ -73,7 +73,7 @@ function drawPastePreview() {
|
||||
ctx.strokeStyle = 'rgba(92,186,166,0.85)'; ctx.setLineDash([zp, zp]); ctx.lineWidth = Math.max(1, zp * 0.3);
|
||||
ctx.strokeRect(x, y, w, h); ctx.setLineDash([]);
|
||||
const planned = new Map(bp.blocks.map(([dx, dy, code, v]) => [(oy + dy) * BW + ox + dx, [code, v]]));
|
||||
for (const [dx, dy, code, v] of bp.blocks) { const [bx2, by2, bw2, bh2] = blockRectPx(ox + dx, oy + dy, 1, 1); ctx.fillStyle = `rgba(${previewColor(code, v)},0.5)`; previewBlock(code, bx2, by2, bw2, bh2, false, code === BP_SCREW ? v : -1, ox + dx, oy + dy, planned); }
|
||||
for (const [dx, dy, code, v] of bp.blocks) { const [bx2, by2, bw2, bh2] = blockRectPx(ox + dx, oy + dy, 1, 1); ctx.fillStyle = `rgba(${previewColor(code, v)},0.5)`; previewBlock(code, bx2, by2, bw2, bh2, false, code === BP_SCREW || isBelt(code) ? v : -1, ox + dx, oy + dy, planned); }
|
||||
for (const [type, dx, dy] of bp.machines) { const P = PIECE_BY_ID[type]; const [mx, my, mw, mh] = blockRectPx(ox + dx, oy + dy, P.bw, P.bh); ctx.strokeStyle = 'rgba(255,210,122,0.8)'; ctx.strokeRect(mx, my, mw, mh); }
|
||||
for (const [k, dx, dy] of bp.molds) { const md = MOLDS[k]; const [mx, my, mw, mh] = blockRectPx(ox + dx, oy + dy, md.bw, md.bh); ctx.strokeStyle = 'rgba(255,190,90,0.8)'; ctx.strokeRect(mx, my, mw, mh); }
|
||||
ctx.restore();
|
||||
|
||||
+3
-3
@@ -118,10 +118,10 @@ function drawPreviews() {
|
||||
const ok = (!err || swap) && !ghost && afford;
|
||||
const [x, y, w, h] = blockRectPx(bx, by, 1, 1);
|
||||
ctx.fillStyle = ghost ? 'rgba(90,190,230,0.22)' : ok ? (swap ? 'rgba(255,190,90,0.5)' : `rgba(${col},0.55)`) : 'rgba(239,106,85,0.35)';
|
||||
previewBlock(code, x, y, w, h, false, P.id === 'screw' ? dIdx : -1, bx, by, planned);
|
||||
previewBlock(code, x, y, w, h, false, P.id === 'screw' || P.id === 'belt' ? dIdx : -1, bx, by, planned);
|
||||
ctx.strokeStyle = ghost ? 'rgba(90,190,230,0.8)' : ok ? 'rgba(236,226,210,0.8)' : 'rgba(239,106,85,0.9)'; ctx.lineWidth = Math.max(1, zp * 0.25);
|
||||
if (ghost) ctx.setLineDash([zp, zp * 0.7]);
|
||||
previewBlock(code, x, y, w, h, true, P.id === 'screw' ? dIdx : -1, bx, by, planned); ctx.setLineDash([]);
|
||||
previewBlock(code, x, y, w, h, true, P.id === 'screw' || P.id === 'belt' ? dIdx : -1, bx, by, planned); ctx.setLineDash([]);
|
||||
if (P.dirs && (P.id === 'belt' || P.id === 'screw' || P.filter || P.id === 'pump' || P.id === 'outlet')) {
|
||||
// a chevron on every block, pointing the way it will carry (or pump, or pour)
|
||||
const [dx, dy] = isSlopeBelt(code) ? [dIdx ? 1 : -1, beltGrade(code) * (dIdx ? 1 : -1)] : P.id === 'screw' ? SCREW_DIRS[dIdx] : P.id === 'pump' ? PUMP_V[dIdx] : P.id === 'outlet' ? OUTLET_V[dIdx] : [[-1, 0], [1, 0]][dIdx];
|
||||
@@ -222,7 +222,7 @@ function previewBlock(code, x, y, w, h, stroke = false, direction = -1, bx = -1,
|
||||
if (stroke) { const fill = ctx.fillStyle; ctx.fillStyle = ctx.strokeStyle; ctx.fill(); ctx.fillStyle = fill; }
|
||||
else ctx.fill();
|
||||
// Use the same filled wall profile as placed slopes; ribs stay on the physics feet.
|
||||
if (!isFilter(code)) {
|
||||
if (!isFilter(code) && (direction < 0 || slopeUphill(code, direction))) { // downhill: a plain belt
|
||||
ctx.beginPath();
|
||||
for (let yy = -9; yy < 8; yy += step) for (let xx = -5; xx < 9; xx += step)
|
||||
if (slopeWallShape(ox + xx + step / 2, oy + yy + step / 2, b, code) <= 0)
|
||||
|
||||
@@ -137,6 +137,9 @@ check('far wall is filled down to the belt and grains cover it',run(`(()=>{
|
||||
}
|
||||
return wall>20;
|
||||
})()`));
|
||||
flatWorld(run,160,120,112);
|
||||
run('for(let k=0;k<6;k++)placeBlock(BP_BELT_UP,0,12+k,20-k,true);rebuildPower();slopeCleatsTick(false);prepareSlopeBelts(0,0,W,H);');
|
||||
check('a downhill belt draws no wall and no ribs',run('(()=>{let n=0;for(let i=0;i<N;i++)if(slopeDetailAt(i,i%W,(i/W)|0))n++;return n===0 && cleatCells.length===0;})()'));
|
||||
flatWorld(run,320,240,232);
|
||||
run('for(let k=0;k<8;k++)placeBlock(BP_BELT_UP,1,12+k,30-k,true);for(let t=0;t<120;t++)simTick();');
|
||||
check('idle empty slope lets every moving area sleep',run('awake.every(v=>v===0)'),run('awake.reduce((n,v)=>n+(v>0),0)'));
|
||||
|
||||
Reference in New Issue
Block a user