From 02e338a46f55f3ba2528f0c0bf5acbc4e157b61d Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Tue, 27 Oct 2026 14:30:02 -0400 Subject: [PATCH] Same-row belt feed into an uphill low end draws as two capped ends (t67) Co-Authored-By: Claude Opus 5.5 --- docs/DESIGN.md | 6 ++++++ docs/agent-log.md | 15 ++++++++++++--- src/render/slope-belt-shader.js | 23 ++++------------------- src/render/slope-belts.js | 32 +++++++++++++++++--------------- src/render/world.js | 3 +-- test/t_joins.js | 8 ++++---- test/t_smooth.py | 2 +- 7 files changed, 45 insertions(+), 44 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index cd883ac..15827f7 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -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 based only on shape, then found in testing that horizontal → uphill and vertical → diagonal 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 diff --git a/docs/agent-log.md b/docs/agent-log.md index 0f68aa3..3ca1649 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -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 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) - 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`, `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. -- Open: Codex hit its usage limit before finishing. A flat belt feeding an uphill belt on the - same row is joined but draws badly in the smooth look (overlapping casing, stepped edges; - `shots/joins-belt-drop-smooth.png`). Not playtested; determinism diff not re-run by hand. +- Open: Codex hit its usage limit before finishing; the belt drop drawing was finished in the + next entry. Not playtested. ## 2026-10-27: t67 phase 2, screw conveyors follow the join rule (Claude Code + Codex) diff --git a/src/render/slope-belt-shader.js b/src/render/slope-belt-shader.js index f6e0acc..09dac11 100644 --- a/src/render/slope-belt-shader.js +++ b/src/render/slope-belt-shader.js @@ -23,27 +23,13 @@ vec3 slopeShape(vec2 p, vec2 worldOrigin, float grade, int joins) { vec2 axis = vec2(1.0, grade) * 0.70710678; vec3 best = slopeStroke(p, a, b, dot(worldOrigin + a, axis)); if ((joins & 1) != 0) { - vec3 h; - 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); + vec3 h = slopeStroke(p, vec2(-2.0, a.y), a, worldOrigin.x - 2.0); if (h.x < best.x) best = h; h = slopeCorner(p, a, worldOrigin.x + a.x); if (h.x < best.x) best = h; } if ((joins & 2) != 0) { - vec3 h; - 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); + vec3 h = slopeStroke(p, b, vec2(6, b.y), worldOrigin.x + b.x); if (h.x < best.x) best = h; h = slopeCorner(p, b, worldOrigin.x + b.x); 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 dir = ((slopeClass - 23) % 2) == 1 ? 1.0 : -1.0; vec4 tag = cellAt(tTree, c); - int packedX = int(tag.g * 255.0 + 0.5); - int joins = (int(tag.a * 255.0 + 0.5) & 127) | (packedX & 128); - vec2 local = vec2(float(packedX & 127), floor(tag.b * 255.0 + 0.5)) - 8.0 + fract(w); + int joins = int(tag.a * 255.0 + 0.5); + vec2 local = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w); vec3 shapePos = slopeShape(local, w - local, grade, joins); float sd = shapePos.x, axis = shapePos.y, depth = shapePos.z; float time = !mesh && (joins & 16) != 0 ? uTime : 0.0; diff --git a/src/render/slope-belts.js b/src/render/slope-belts.js index fb45d6b..c9880c7 100644 --- a/src/render/slope-belts.js +++ b/src/render/slope-belts.js @@ -7,16 +7,26 @@ function slopeBeltConnections(bx, by, code, planned = null) { const join = endJoin(bx, by, end, planned); if (join < 0) continue; const nb = join >> 1, next = planned && planned.get(nb); - const slope = isSlopeBelt(next ? next[0] : blockP[nb]) && - ((nb / BW) | 0) - by === beltGrade(code) * (nb % BW - bx); + const pcode = next ? next[0] : blockP[nb]; + 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; - 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; } +// 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) { - 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) { 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; slopeBeltSegment(x, y, ax, ay, bx, by, origin + (ax + grade * ay) * Math.SQRT1_2); if (joins & 1) { - if ((joins & 128) && grade < 0) { - 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); + slopeBeltSegment(x, y, -2, ay, ax, ay, ox - 2); slopeBeltCorner(x, y, ax, ay, ox + ax); } if (joins & 2) { - if ((joins & 128) && grade > 0) { - 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); + slopeBeltSegment(x, y, bx, by, 6, by, ox + bx); slopeBeltCorner(x, y, bx, by, ox + bx); } return slopeDistance; diff --git a/src/render/world.js b/src/render/world.js index c453de3..9f1ce26 100644 --- a/src/render/world.js +++ b/src/render/world.js @@ -279,9 +279,8 @@ function drawWorld() { const slopeClass = 23 + (code - BP_BELT_UP) * 2 + dir; 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; - // Alpha bit 128 marks slope tags; the spare high bit of local X carries the drop. 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 (slopeDetail && slopeWallOwner >= 0) { diff --git a/test/t_joins.js b/test/t_joins.js index 9579170..0c63e47 100644 --- a/test/t_joins.js +++ b/test/t_joins.js @@ -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;} 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; const p=new Map([[b,[BP_BELT_UP,1]],[b-1,[BP_BELT,1]]]); 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]]]); - 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). for(const grade of [-1,1])for(const filter of [false,true])for(const outgoing of [false,true])for(const joined of [false,true]) { diff --git a/test/t_smooth.py b/test/t_smooth.py index 0c22ac2..84190bf 100644 --- a/test/t_smooth.py +++ b/test/t_smooth.py @@ -212,7 +212,7 @@ with sync_playwright() as p: } finally {slopeDetailAt=detail;performance.now=now;} }""") 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("""() => { const saved={...GAME},original=slopeBeltConnections,seen=[]; try {