Same-row belt feed into an uphill low end draws as two capped ends (t67)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 14:30:02 -04:00
co-authored by Claude Opus 5.5
parent 45538cc18e
commit 02e338a46f
7 changed files with 45 additions and 44 deletions
+6
View File
@@ -500,6 +500,12 @@ above, including:
belt one block below an uphill belt's low end also feeds it. The owner first approved a rule belt one block below an uphill belt's low end also feeds it. The owner first approved a rule
based only on shape, then found in testing that horizontal → uphill and vertical → diagonal based only on shape, then found in testing that horizontal → uphill and vertical → diagonal
didn't join the way the other pieces did. didn't join the way the other pieces did.
- **Warehouses replace the trade dock, eventually (t68, 2026-10-27).** The dock only exists to
get selling working for testing; the owner finds it unengaging. The plan: a warehouse at each
edge of the map, on indestructible ground, served by a road and trucks. Warehouses accept
loose grain as well as packaged goods (pallets, barrels or sacks from packaging machines).
Trucks matter to play, not just looks: what's waiting is collected when a truck comes, on its
schedule. How the player learns where the warehouses are is still open.
## Ideas not built yet ## Ideas not built yet
+12 -3
View File
@@ -4,6 +4,16 @@ 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 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. open or not verified. Keep each entry short; the git history has the details.
## 2026-10-27: t67 belt drop drawn as two capped ends (Claude Code)
- A flat belt feeding an uphill belt's low end on the same row (and two uphill slopes side by
side) stays joined, but grains really drop about 3 grains onto the face there, so it now
draws as two capped ends instead of the bridge phase 2b drew (it overlapped and stepped
badly). `beltDrop` in `src/render/slope-belts.js` decides it for `slopeBeltConnections` and
`flatBeltCaps`; the shader and `world.js` packing went back to their pre-2b versions.
- Tests: `t_joins.js` and `t_smooth.py` drop checks updated; the existing sand test confirms
the same-row feed carries sand up. `python3 test/run_all.py --browser` all passed.
## 2026-10-27: t67 phase 2b, joins follow the travel direction (Claude Code + Codex) ## 2026-10-27: t67 phase 2b, joins follow the travel direction (Claude Code + Codex)
- The owner tested phase 2: horizontal → uphill and vertical-up → diagonal didn't join like the - The owner tested phase 2: horizontal → uphill and vertical-up → diagonal didn't join like the
@@ -19,9 +29,8 @@ open or not verified. Keep each entry short; the git history has the details.
`src/render/world.js`, `src/ui/previews.js`, `tools/scenes/joins.js`, tests `t_joins.js`, `src/render/world.js`, `src/ui/previews.js`, `tools/scenes/joins.js`, tests `t_joins.js`,
`t_diag_screws.js`, `t_slope_belt_art.js`, `t_smooth.py`, `docs/PROJECT_MAP.md`. `t_diag_screws.js`, `t_slope_belt_art.js`, `t_smooth.py`, `docs/PROJECT_MAP.md`.
- Tests: `python3 test/run_all.py --browser` all passed. - Tests: `python3 test/run_all.py --browser` all passed.
- Open: Codex hit its usage limit before finishing. A flat belt feeding an uphill belt on the - Open: Codex hit its usage limit before finishing; the belt drop drawing was finished in the
same row is joined but draws badly in the smooth look (overlapping casing, stepped edges; next entry. Not playtested.
`shots/joins-belt-drop-smooth.png`). Not playtested; determinism diff not re-run by hand.
## 2026-10-27: t67 phase 2, screw conveyors follow the join rule (Claude Code + Codex) ## 2026-10-27: t67 phase 2, screw conveyors follow the join rule (Claude Code + Codex)
+4 -19
View File
@@ -23,27 +23,13 @@ vec3 slopeShape(vec2 p, vec2 worldOrigin, float grade, int joins) {
vec2 axis = vec2(1.0, grade) * 0.70710678; vec2 axis = vec2(1.0, grade) * 0.70710678;
vec3 best = slopeStroke(p, a, b, dot(worldOrigin + a, axis)); vec3 best = slopeStroke(p, a, b, dot(worldOrigin + a, axis));
if ((joins & 1) != 0) { if ((joins & 1) != 0) {
vec3 h; vec3 h = slopeStroke(p, vec2(-2.0, a.y), a, worldOrigin.x - 2.0);
if ((joins & 128) != 0 && grade < 0.0) {
h = slopeStroke(p, vec2(-2, 2), vec2(0, 2), worldOrigin.x - 2.0);
if (h.x < best.x) best = h;
h = slopeStroke(p, vec2(0, 2), vec2(0, a.y), worldOrigin.x);
if (h.x < best.x) best = h;
h = slopeStroke(p, vec2(0, a.y), a, worldOrigin.x);
} else h = slopeStroke(p, vec2(-2.0, a.y), a, worldOrigin.x - 2.0);
if (h.x < best.x) best = h; if (h.x < best.x) best = h;
h = slopeCorner(p, a, worldOrigin.x + a.x); h = slopeCorner(p, a, worldOrigin.x + a.x);
if (h.x < best.x) best = h; if (h.x < best.x) best = h;
} }
if ((joins & 2) != 0) { if ((joins & 2) != 0) {
vec3 h; vec3 h = slopeStroke(p, b, vec2(6, b.y), worldOrigin.x + b.x);
if ((joins & 128) != 0 && grade > 0.0) {
h = slopeStroke(p, b, vec2(4, b.y), worldOrigin.x + b.x);
if (h.x < best.x) best = h;
h = slopeStroke(p, vec2(4, b.y), vec2(4, 2), worldOrigin.x + 4.0);
if (h.x < best.x) best = h;
h = slopeStroke(p, vec2(4, 2), vec2(6, 2), worldOrigin.x + 4.0);
} else h = slopeStroke(p, b, vec2(6, b.y), worldOrigin.x + b.x);
if (h.x < best.x) best = h; if (h.x < best.x) best = h;
h = slopeCorner(p, b, worldOrigin.x + b.x); h = slopeCorner(p, b, worldOrigin.x + b.x);
if (h.x < best.x) best = h; if (h.x < best.x) best = h;
@@ -91,9 +77,8 @@ const SM_SLOPE_DRAW = `
float grade = (shape == 0 || shape == 2) ? -1.0 : 1.0; float grade = (shape == 0 || shape == 2) ? -1.0 : 1.0;
float dir = ((slopeClass - 23) % 2) == 1 ? 1.0 : -1.0; float dir = ((slopeClass - 23) % 2) == 1 ? 1.0 : -1.0;
vec4 tag = cellAt(tTree, c); vec4 tag = cellAt(tTree, c);
int packedX = int(tag.g * 255.0 + 0.5); int joins = int(tag.a * 255.0 + 0.5);
int joins = (int(tag.a * 255.0 + 0.5) & 127) | (packedX & 128); vec2 local = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w);
vec2 local = vec2(float(packedX & 127), floor(tag.b * 255.0 + 0.5)) - 8.0 + fract(w);
vec3 shapePos = slopeShape(local, w - local, grade, joins); vec3 shapePos = slopeShape(local, w - local, grade, joins);
float sd = shapePos.x, axis = shapePos.y, depth = shapePos.z; float sd = shapePos.x, axis = shapePos.y, depth = shapePos.z;
float time = !mesh && (joins & 16) != 0 ? uTime : 0.0; float time = !mesh && (joins & 16) != 0 ? uTime : 0.0;
+17 -15
View File
@@ -7,16 +7,26 @@ function slopeBeltConnections(bx, by, code, planned = null) {
const join = endJoin(bx, by, end, planned); const join = endJoin(bx, by, end, planned);
if (join < 0) continue; if (join < 0) continue;
const nb = join >> 1, next = planned && planned.get(nb); const nb = join >> 1, next = planned && planned.get(nb);
const slope = isSlopeBelt(next ? next[0] : blockP[nb]) && const pcode = next ? next[0] : blockP[nb];
((nb / BW) | 0) - by === beltGrade(code) * (nb % BW - bx); const slope = isSlopeBelt(pcode) && ((nb / BW) | 0) - by === beltGrade(code) * (nb % BW - bx);
// A same-row feed into a low end drops onto its face: drawn as two capped ends, still joined.
if (!slope && beltDrop(nb, by, beltGrade(code) * (end ? 1 : -1) > 0, pcode, join & 1)) continue;
joins |= (slope ? 4 : 1) << end; joins |= (slope ? 4 : 1) << end;
if (!slope && beltGrade(code) * (end ? 1 : -1) > 0 &&
((nb / BW) | 0) === by) joins |= 128; // low-end feed drops from the same-row flat
} }
return joins; return joins;
} }
// Does a same-row join between a piece in row `by` and block nb meet a slope's low end?
function beltDrop(nb, by, ownLow, pcode, pend) {
return ((nb / BW) | 0) === by && (ownLow || beltGrade(pcode) * (pend ? 1 : -1) > 0);
}
function flatBeltCaps(b, planned = null) { function flatBeltCaps(b, planned = null) {
return 3 ^ pieceJoins(b % BW, (b / BW) | 0, planned); const bx = b % BW, by = (b / BW) | 0;
let caps = 0;
for (let end = 0; end < 2; end++) {
const join = endJoin(bx, by, end, planned), nb = join >> 1, next = planned && planned.get(nb);
if (join < 0 || beltDrop(nb, by, false, next ? next[0] : blockP[nb], join & 1)) caps |= 1 << end;
}
return caps;
} }
function prepareSlopeBelts(x0, y0, x1, y1) { function prepareSlopeBelts(x0, y0, x1, y1) {
if (slopeBeltJoins.length !== NB) { slopeBeltJoins = new Uint8Array(NB); slopeBeltHalo = new Uint8Array(NB); beltCaps = new Uint8Array(NB); } if (slopeBeltJoins.length !== NB) { slopeBeltJoins = new Uint8Array(NB); slopeBeltHalo = new Uint8Array(NB); beltCaps = new Uint8Array(NB); }
@@ -60,19 +70,11 @@ function slopeBeltShape(x, y, b, joins = slopeBeltJoins[b], code = blockP[b]) {
const origin = (ox + grade * oy) * Math.SQRT1_2; const origin = (ox + grade * oy) * Math.SQRT1_2;
slopeBeltSegment(x, y, ax, ay, bx, by, origin + (ax + grade * ay) * Math.SQRT1_2); slopeBeltSegment(x, y, ax, ay, bx, by, origin + (ax + grade * ay) * Math.SQRT1_2);
if (joins & 1) { if (joins & 1) {
if ((joins & 128) && grade < 0) { slopeBeltSegment(x, y, -2, ay, ax, ay, ox - 2);
slopeBeltSegment(x, y, -2, 2, 0, 2, ox - 2);
slopeBeltSegment(x, y, 0, 2, 0, ay, ox);
slopeBeltSegment(x, y, 0, ay, ax, ay, ox);
} else slopeBeltSegment(x, y, -2, ay, ax, ay, ox - 2);
slopeBeltCorner(x, y, ax, ay, ox + ax); slopeBeltCorner(x, y, ax, ay, ox + ax);
} }
if (joins & 2) { if (joins & 2) {
if ((joins & 128) && grade > 0) { slopeBeltSegment(x, y, bx, by, 6, by, ox + bx);
slopeBeltSegment(x, y, bx, by, 4, by, ox + bx);
slopeBeltSegment(x, y, 4, by, 4, 2, ox + 4);
slopeBeltSegment(x, y, 4, 2, 6, 2, ox + 4);
} else slopeBeltSegment(x, y, bx, by, 6, by, ox + bx);
slopeBeltCorner(x, y, bx, by, ox + bx); slopeBeltCorner(x, y, bx, by, ox + bx);
} }
return slopeDistance; return slopeDistance;
+1 -2
View File
@@ -279,9 +279,8 @@ function drawWorld() {
const slopeClass = 23 + (code - BP_BELT_UP) * 2 + dir; const slopeClass = 23 + (code - BP_BELT_UP) * 2 + dir;
if (slopeArt) cl = slopeClass; if (slopeArt) cl = slopeClass;
const f = isFilter(code) && filterCfg.get(sb), fm = f && f.m.length ? f.m[(Math.floor((x + beltGrade(code) * y) / 2) & 65535) % f.m.length] : 0; const f = isFilter(code) && filterCfg.get(sb), fm = f && f.m.length ? f.m[(Math.floor((x + beltGrade(code) * y) / 2) & 65535) % f.m.length] : 0;
// Alpha bit 128 marks slope tags; the spare high bit of local X carries the drop.
tpx[row + bx] = ((slopeBeltJoins[sb] | 128) << 24) | slopeClass | tpx[row + bx] = ((slopeBeltJoins[sb] | 128) << 24) | slopeClass |
((y - ((sb / BW) | 0) * BS + 8) << 16) | (((x - (sb % BW) * BS + 8) | (slopeBeltJoins[sb] & 128)) << 8); ((y - ((sb / BW) | 0) * BS + 8) << 16) | ((x - (sb % BW) * BS + 8) << 8);
if (slopeArt) { r = fm ? COLR[fm] : 0; g = fm ? COLG[fm] : 0; b = fm ? COLB[fm] : 0; } if (slopeArt) { r = fm ? COLR[fm] : 0; g = fm ? COLG[fm] : 0; b = fm ? COLB[fm] : 0; }
} }
if (slopeDetail && slopeWallOwner >= 0) { if (slopeDetail && slopeWallOwner >= 0) {
+4 -4
View File
@@ -123,15 +123,15 @@ check('a flat belt picks the same-row uphill inlet before the below-low-end exce
for(const n of [b,b+1,b-BW+1]){blockP[n]=n===b?BP_BELT:BP_BELT_UP;blockV[n]=1;} for(const n of [b,b+1,b-BW+1]){blockP[n]=n===b?BP_BELT:BP_BELT_UP;blockV[n]=1;}
return endJoin(10,10,1)===(b+1)*2&&endJoin(11,9,0)===-1; return endJoin(10,10,1)===(b+1)*2&&endJoin(11,9,0)===-1;
})()`)); })()`));
check('same-row uphill low end draws the drop in placed and planned geometry',run(`(()=>{ check('same-row uphill low end draws as two capped ends in placed and planned geometry',run(`(()=>{
blockP.fill(0);blockV.fill(0);const b=10*BW+10; blockP.fill(0);blockV.fill(0);const b=10*BW+10;
const p=new Map([[b,[BP_BELT_UP,1]],[b-1,[BP_BELT,1]]]); const p=new Map([[b,[BP_BELT_UP,1]],[b-1,[BP_BELT,1]]]);
const j=slopeBeltConnections(10,10,BP_BELT_UP,p); const j=slopeBeltConnections(10,10,BP_BELT_UP,p);
return j===129&&slopeBeltShape(40.5,43,b,j,BP_BELT_UP)<0&&flatBeltCaps(b-1,p)===1; return j===0&&endJoin(10,10,0,p)>=0&&flatBeltCaps(b-1,p)===3;
})()`)); })()`));
check('uphill same-row slope joins draw a high elbow and a low drop',run(`(()=>{ check('uphill slopes side by side on one row are joined but drawn as two capped ends',run(`(()=>{
blockP.fill(0);blockV.fill(0);const b=10*BW+10,p=new Map([[b,[BP_BELT_UP,1]],[b+1,[BP_BELT_UP,1]]]); blockP.fill(0);blockV.fill(0);const b=10*BW+10,p=new Map([[b,[BP_BELT_UP,1]],[b+1,[BP_BELT_UP,1]]]);
return slopeBeltConnections(10,10,BP_BELT_UP,p)===2&&slopeBeltConnections(11,10,BP_BELT_UP,p)===129; return endJoin(10,10,1,p)>=0&&slopeBeltConnections(10,10,BP_BELT_UP,p)===0&&slopeBeltConnections(11,10,BP_BELT_UP,p)===0;
})()`)); })()`));
// Downhill face-to-face transfers (screens always travel downhill when placed). // Downhill face-to-face transfers (screens always travel downhill when placed).
for(const grade of [-1,1])for(const filter of [false,true])for(const outgoing of [false,true])for(const joined of [false,true]) { for(const grade of [-1,1])for(const filter of [false,true])for(const outgoing of [false,true])for(const joined of [false,true]) {
+1 -1
View File
@@ -212,7 +212,7 @@ with sync_playwright() as p:
} finally {slopeDetailAt=detail;performance.now=now;} } finally {slopeDetailAt=detail;performance.now=now;}
}""") }""")
for look, (joined, free) in drops.items(): for look, (joined, free) in drops.items():
check(f'{look}: the same-row uphill inlet draws its drop above the low face', joined != free and joined[2] < free[2], [joined, free]) check(f'{look}: the same-row uphill inlet is a drop, drawn like a free low end', joined == free, [joined, free])
filter_plans = ev("""() => { filter_plans = ev("""() => {
const saved={...GAME},original=slopeBeltConnections,seen=[]; const saved={...GAME},original=slopeBeltConnections,seen=[];
try { try {