Corner placements of screw conveyors stay separate ends (t62 follow-up)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 12:32:23 -04:00
co-authored by Claude Opus 5.5
parent ff9fd7b1b8
commit 787721ad35
11 changed files with 115 additions and 102 deletions
+5
View File
@@ -486,6 +486,11 @@ above, including:
on warns first that the save will be marked, and the mark stays with the world; any future
achievements won't unlock in a marked world. Besides money, items and upgrades it may have
game speed and other things just for fun.
- **Screw conveyors only join a diagonal on the same row (t62, 2026-10-27).** A straight screw
joins a diagonal when it sits right beside the diagonal's end block, and that join is drawn
as an elbow. When the diagonal instead starts one block lower or higher, so the two only touch
corner to corner, they stay two separate ends and nothing passes between them. The owner tried
a corner join and found it looked odd, and building it that way is unusual anyway.
## Ideas not built yet
+6 -6
View File
@@ -265,9 +265,9 @@ Pipes, pumps and outlets: each joined line holds one liquid as a count, not as g
Per-tick machine work: steam engines, fans, belts, filters, screw conveyors (machinesTick).
- functions: engineIntake, fanBlow, machinesTick
### src/sim/conveyors.js (208 lines)
### src/sim/conveyors.js (198 lines)
How belts, filters and screw conveyors move what is on or in them. What rests on a belt or filter rides along: the whole stack standing on the carrying surface steps one cell toward d together (belts run front of the line first, so the way ahead is clear). If a powder grain is blocked, the load above it stands still and the belt slips underneath. A filter's own material drops through it instead, unless it is already full.
- functions: conveyorStep, carryColumn, beltDrain, launchGrain, rebuildFilters, filterCarry, carryBeltsAndFilters, setFilter, filterRun, roomFor, screwSpread, screwIntake, straightScrewNext, diagonalScrewNext
- functions: conveyorStep, carryColumn, beltDrain, launchGrain, rebuildFilters, filterCarry, carryBeltsAndFilters, setFilter, filterRun, roomFor, screwSpread, screwIntake, diagonalScrewNext
- variables: CARRY_DEPTH, filterR, filterL, filtersDirty
### src/sim/slope-conveyors.js (230 lines)
@@ -284,7 +284,7 @@ Molds: cavities for castings; ingot troughs empty themselves below. Removal retu
- functions: moldCellsAt, canPlaceMold, newMoldRecord, addMold, castMix, cavityState, troughState, clearCavity, moldPlugCells, rakeAbove, furnaceJunkHint, unplugMold, ejectIngot, checkMolds, moldAt, tally, collectMold, removeMold
- variables: DROSS, grains, barWord
### src/sim/items.js (295 lines)
### src/sim/items.js (288 lines)
Bars and other loose objects (ents), and the bag: adding, taking, costs and refunds.
- functions: entSolidCell, entSupportCount, entFreeAt, entMark, spawnBar, removeEnt, entScrewAt, entScrewFree, entScrewMove, entScrewLane, entScrewEdge, entScrewEnter, entScrewTick, entDiagonalScrewTick, entSlopeBeltTick, entTick, entsInCircle, bagAdd, bagTake, giveOrDrop, refundAt, canAfford, payCost, fmtMoney, costText, needText
- variables: BAR_W, BAR_H, entCurM, droppedLast
@@ -367,7 +367,7 @@ Drawing only: the face stays on its simulation cells; the band hangs below it.
- functions: slopeBeltConnections, flatBeltCaps, prepareSlopeBelts, slopeBeltSegment, slopeBeltCorner, slopeBeltShape, slopeBeltAt, slopeBeltColor, screenLoaded, slopeWallShape, slopeWallAt, slopeDetailAt, slopePreviewCleat
- variables: slopeBeltJoins, slopeBeltHalo, beltCaps, slopeAcross, slopeAlong, slopeDistance, slopeWallOwner
### src/render/diagonal-screws.js (74 lines)
### src/render/diagonal-screws.js (68 lines)
Screw casing geometry shared by both looks, ghosts and placement previews. smooth-look class of a screw conveyor cell by direction: 9 up, 10 right, 11 down, 12 left
- functions: diagonalScrewJoins, diagonalScrewShape, diagonalScrewBlock
- variables: SCREW_CLASS, diagonalJoins, diagonalAcross, diagonalAlong, diagonalCap
@@ -393,7 +393,7 @@ Smooth look: WebGL setup, texture uploads, drawing and adaptive resolution.
Smooth slope shading, kept beside the shared CPU geometry in slope-belts.js.
- variables: SM_SLOPE_HELPERS, SM_SLOPE_DRAW
### src/render/smooth-shaders.js (373 lines)
### src/render/smooth-shaders.js (370 lines)
Smooth shaders: vertex and fragment programs for the rounded world look.
- variables: SM_VS, SM_FS
@@ -577,4 +577,4 @@ The frame loop and boot.
- `tools/scenes/t35-joins.js`: Run after clear-sky.js. Paused, empty pipes make casing, flights and mouths easy to inspect.
- `tools/scenes/t36-funnel.js`: A twelve-block funnel, one block open at the bottom, draining 400 grains of sand.
- `tools/scenes/t40-sloped-belt.js`: Self-contained: a flat feed climbs, crosses the top, and runs downhill; a filter sorts below.
- `tools/scenes/t62-joins.js`: Self-contained grid. Rows: UR, DR, DL, UL. Each run has both end joins. Columns: horizontal edge, horizontal corner, vertical edge, vertical corner; repeat those four with the straight travel reversed. Camera fits --zoom 6 --viewport 2100x1300. Run after clear-sky.js or by itself. Empty, paused screws expose the whole casing.
- `tools/scenes/t62-joins.js`: Self-contained grid. Rows: UR, DR, DL, UL. Each run shows both ends. Columns: horizontal edge, horizontal corner, vertical edge, vertical corner. Corner columns stay unconnected, with two separate ends. Repeat those four with the straight travel reversed. Camera fits --zoom 6 --viewport 2100x1300. Run after clear-sky.js or by itself. Empty, paused screws expose the whole casing.
+13
View File
@@ -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: t62 corner placements stay separate (Claude Code + Codex)
- Owner tried the corner join and found the jog odd; decision (recorded in DESIGN.md): only
same-row joins connect. Removed join kinds 5/6 and `straightScrewNext`; `diagonalScrewNext`
and `entDiagonalScrewTick` now continue across a corner only into a diagonal running the same
way, so a corner straight piece no longer receives grains or bars. Kept the shared geometry
file and the smooth-look elbow seam fix.
- Files: `src/render/diagonal-screws.js`, `src/render/smooth-shaders.js`, `src/sim/conveyors.js`,
`src/sim/items.js`, `src/sim/machine-tick.js`, `test/t_diag_screws.js`, `test/t_smooth.py`,
`tools/scenes/t62-joins.js`, `docs/PROJECT_MAP.md`, `docs/DESIGN.md`.
- Tests: `python3 test/run_all.py --browser` all passed; determinism diff changed only the
replaced corner checks.
## 2026-10-27: t62 screw conveyor joins (Claude Code + Codex)
- Straight screws touching a diagonal's end only at the corner now join, in both looks and in
+5 -11
View File
@@ -5,7 +5,7 @@ for (let p = P_SCREW_UR; p <= P_SCREW_UL; p++) SCREW_CLASS[p] = 19 + p - P_SCREW
// Drawing only: the four transport lanes remain in the square block.
let diagonalJoins = new Uint8Array(0);
function diagonalScrewJoins(bx, by, v, planned = null) {
// Three bits per end: 1..4 edge exits, 5/6 corner horizontal/vertical; bits 6/7 are collars.
// Three bits per end: 1..4 edge exits; bits 6/7 are collars.
const dy = v === 4 || v === 6 ? -1 : 1;
let joins = 0;
for (let end = 0; end < 2; end++) {
@@ -23,7 +23,6 @@ function diagonalScrewJoins(bx, by, v, planned = null) {
if ((code === BP_SCREW && nv < 4 && (nv & 1) === (dir & 1)) ||
((code === BP_BELT || code === BP_FILTER) && (dir & 1))) { join = dir + 1; break; }
}
if (!join && nx >= 0 && ny >= 0 && nx < BW && ny < BH && nc === BP_SCREW && nv < 4) join = nv & 1 ? 5 : 6;
joins |= join << (end * 3);
if (!join) joins |= 64 << end;
}
@@ -37,15 +36,10 @@ function diagonalScrewShape(x, y, b, v, joins) {
for (let end = 0; end < 2; end++) {
const join = (joins >> (end * 3)) & 7, side = end ? 1 : -1;
const slope = v === 4 || v === 6 ? -1 : 1;
const dx = join >= 5 ? (join === 5 ? side : 0) : join ? SCREW_DIRS[join - 1][0] : side * Math.SQRT1_2;
const dy = join >= 5 ? (join === 6 ? side * slope : 0) : join ? SCREW_DIRS[join - 1][1] : side * slope * Math.SQRT1_2;
const ox = join === 6 ? side * BS : 0, oy = join === 5 ? side * slope * BS : 0;
const along = (lx - ox) * dx + (ly - oy) * dy, across = -(lx - ox) * dy + (ly - oy) * dx;
let radius = Math.hypot(across, Math.min(0, along));
if (join >= 5) {
const t = Math.max(0, Math.min(1, (lx * ox + ly * oy) / (BS * BS)));
radius = Math.min(radius, Math.hypot(lx - ox * t, ly - oy * t));
}
const dx = join ? SCREW_DIRS[join - 1][0] : side * Math.SQRT1_2;
const dy = join ? SCREW_DIRS[join - 1][1] : side * slope * Math.SQRT1_2;
const along = lx * dx + ly * dy, across = -lx * dy + ly * dx;
const radius = Math.hypot(across, Math.min(0, along));
const cap = along - (join ? 2 : 2 * Math.SQRT2);
const sd = Math.max(radius - 2, join || (joins & (64 << end)) ? cap : -10);
if (sd < best) {
+1 -4
View File
@@ -265,12 +265,9 @@ ${SM_SLOPE_DRAW}
int join = (joins >> (end * 3)) & 7;
float side = end == 0 ? -1.0 : 1.0;
vec2 ray = join == 0 ? vec2(side, side * (nc == 19 || nc == 21 ? -1.0 : 1.0)) * 0.70710678 :
join == 5 ? vec2(side, 0) : join == 6 ? vec2(0, side * d.y) :
join == 1 ? vec2(0, -1) : join == 2 ? vec2(1, 0) : join == 3 ? vec2(0, 1) : vec2(-1, 0);
vec2 offset = join == 5 ? vec2(0, side * d.y * 4.0) : join == 6 ? vec2(side * 4.0, 0) : vec2(0);
float t = dot(local - offset, ray), cross2 = dot(local - offset, vec2(-ray.y, ray.x));
float t = dot(local, ray), cross2 = dot(local, vec2(-ray.y, ray.x));
float radius = length(vec2(cross2, min(0.0, t))), cap = t - (join == 0 ? 2.82842712 : 2.0);
if (join >= 5) radius = min(radius, length(local - offset * clamp(dot(local, offset) / 16.0, 0.0, 1.0)));
// A connected mouth meets another opaque pipe; fading its edge leaves a seam.
float sd = max(radius - 2.0, (joins & (64 << end)) != 0 ? cap : -10.0);
if (sd < screwSD) {
+6 -16
View File
@@ -176,20 +176,6 @@ function screwIntake(i, p, dx, dy) {
}
}
// A straight mouth can feed a diagonal tail one block across the corner.
function straightScrewNext(i, p) {
const dx = SCREW_DX[p], dy = SCREW_DY[p], x = i % W, y = (i / W) | 0;
const xx = x + dx, yy = y + dy, next = i + dx + dy * W;
if (xx < 0 || yy < 0 || xx >= W || yy >= H) return -1;
if (((xx >> 2) === (x >> 2) && (yy >> 2) === (y >> 2)) || part[next] || blockP[(yy >> 2) * BW + (xx >> 2)]) return next;
for (let side = -1; side <= 1; side += 2) {
const nx = xx + (dx ? 0 : side * BS), ny = yy + (dy ? 0 : side * BS);
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
const j = ny * W + nx, q = part[j];
if (SCREW_DX[q] === (dx || side) && SCREW_DY[q] === (dy || side)) return j;
}
return next;
}
// Four lanes cross a block in four beats; the casing carries every lane across the corner.
// Prefer the next diagonal block, then either straight join, then the open head.
function diagonalScrewNext(i, p) {
@@ -198,11 +184,15 @@ function diagonalScrewNext(i, p) {
if (xx < 0 || xx >= W) return -1;
if ((xx >> 2) === (x >> 2)) return i + dx;
const yy = y + dy * BS;
if (yy >= 0 && yy < H && IS_SCREW[part[yy * W + xx]]) return yy * W + xx;
if (yy >= 0 && yy < H) {
const q = part[yy * W + xx];
if (q >= P_SCREW_UR && q <= P_SCREW_UL && SCREW_DX[q] === dx && SCREW_DY[q] === dy) return yy * W + xx;
}
if (IS_SCREW[part[y * W + xx]]) return y * W + xx;
if (dy > 0 && yy < H && (mat[yy * W + xx] === BELTL || mat[yy * W + xx] === BELTR)) return ((yy & ~3) - 1) * W + xx;
const hy = by + (dy > 0 ? BS : -1), hx = bx + (y & 3);
if (hy >= 0 && hy < H && IS_SCREW[part[hy * W + hx]]) return hy * W + hx;
// An open mouth discharges along the diagonal, clear of the last block.
return yy >= 0 && yy < H ? yy * W + xx : -1;
// A corner screw is a separate end, not an open discharge cell.
return yy >= 0 && yy < H && !IS_SCREW[part[yy * W + xx]] ? yy * W + xx : -1;
}
+1 -8
View File
@@ -90,13 +90,6 @@ function entScrewTick(e, p) {
if ((tick & 1) || netSat(ci) <= 0 || rnd() > netSat(ci)) return;
const dx = SCREW_DX[p], dy = SCREW_DY[p];
if (dx && dy) { entDiagonalScrewTick(e, p); return; }
const edge = entScrewEdge(e, dx, dy, 0), behind = edge - dx - dy * W;
const corner = edge >= 0 ? straightScrewNext(behind, p) : -1;
if (corner >= 0 && corner !== edge) {
const x = (corner % W) & ~3, y = entScrewLane((corner / W) | 0, e.iy);
if (entScrewMove(e, x, y, true)) stats.moved++;
return;
}
let x = e.ix + dx, y = e.iy + dy, next = 0, ax = 0, ay = 0;
for (let k = 0; k < (dx ? BAR_H : BAR_W); k++) {
const i = entScrewEdge(e, dx, dy, k);
@@ -124,7 +117,7 @@ function entDiagonalScrewTick(e, p) {
const dx = SCREW_DX[p], dy = SCREW_DY[p], bx = e.ix & ~3, by = e.iy & ~3, lane = e.iy & 3;
for (const [ox, oy] of [[dx, dy], [dx, 0], [0, dy]]) {
const x = bx + ox * BS, y = by + oy * BS + lane, q = entScrewAt(x, y);
if (!q) continue;
if (!q || (ox && oy && (q < P_SCREW_UR || q > P_SCREW_UL || SCREW_DX[q] !== dx || SCREW_DY[q] !== dy))) continue;
if (SCREW_DX[q] * dx + SCREW_DY[q] * dy < 0) return;
if (entScrewMove(e, x, y, true)) stats.moved++;
return;
+1 -1
View File
@@ -130,7 +130,7 @@ function machinesTick() {
if (shifted) continue;
}
if (x + dx < 0 || x + dx >= W || y + dy < 0 || y + dy >= H) continue;
const dest = dx && dy ? diagonalScrewNext(i, p) : straightScrewNext(i, p);
const dest = dx && dy ? diagonalScrewNext(i, p) : i + dj;
if (dest < 0) continue;
const nx = dest % W, ny = (dest / W) | 0;
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
+29 -33
View File
@@ -48,38 +48,35 @@ for (const v of [4, 5, 6, 7]) {
const joined = drain(c,500);
check(`direction ${v}: straight to diagonal to straight`,joined.out===16 && joined.leaks===0,JSON.stringify(joined));
}
// Corner-adjacent joints: all axes, both ends, grains and queued bars.
function cornerLine(v, vertical = false) {
const s = line(v), vv = vertical ? (s.dy < 0 ? 0 : 2) : (s.dx > 0 ? 1 : 3);
const tx = s.bx-s.dx, ty = s.by-s.dy, hx = s.head+s.dx, hy = s.hy+s.dy;
run(`for(let k=0;k<3;k++) {
placeBlock(BP_SCREW,${vv},${tx}-k*${vertical?0:s.dx},${ty}-k*${vertical?s.dy:0},true);
placeBlock(BP_SCREW,${vv},${hx}+k*${vertical?0:s.dx},${hy}+k*${vertical?s.dy:0},true);
}`);
return {...s, tail:tx-2*(vertical?0:s.dx), by:ty-2*(vertical?s.dy:0),
head:hx+2*(vertical?0:s.dx), hy:hy+2*(vertical?s.dy:0)};
}
for(const v of [4,5,6,7]) for(const vertical of [false,true]) {
const s=cornerLine(v,vertical);
rect(run,s.tail*4,s.by*4,s.tail*4+3,s.by*4+3,run("SAND"));
const r=run(`(()=>{let out=0,leak=0;for(let k=0;k<700;k++){
simTick();
for(const f of flyers)if(f.m===SAND){
if(${vertical ? (s.dy<0?`f.y<${s.hy*4}`:`f.y>=${(s.hy+1)*4}`) : (s.dx<0?`f.x<${s.head*4}`:`f.x>=${(s.head+1)*4}`)})out++;else leak++;
// Corner placements are separate mouths, on both axes and in either transfer direction.
for (const v of [4,5,6,7]) for (const vertical of [false,true]) for (const fromDiagonal of [false,true]) {
fresh();
const [dx,dy]=run(`SCREW_DIRS[${v}]`), sv=vertical?(dy<0?0:2):(dx>0?1:3);
const sx=20+(fromDiagonal?0:-dx), sy=20+(fromDiagonal?0:-dy);
const tx=20+(fromDiagonal?dx:0), ty=20+(fromDiagonal?dy:0);
const source=fromDiagonal?v:sv, target=fromDiagonal?sv:v;
const label=`corner ${vertical?'vertical':'horizontal'} ${v} ${fromDiagonal?'diagonal to straight':'straight to diagonal'}`;
run(`placeBlock(BP_SCREW,${source},${sx},${sy},true);placeBlock(BP_SCREW,${target},${tx},${ty},true);rebuildPower();`);
rect(run,sx*4,sy*4,sx*4+3,sy*4+3,run('SAND'));
const grains=run(`(()=>{let crossed=0;for(let k=0;k<80;k++){
tick+=2;tickStamp++;machinesTick();
for(let i=0;i<N;i++)if(mat[i]===SAND){
const x=i%W>>2,y=(i/W|0)>>2;
if(x===${tx} && y===${ty})crossed++;
if(x!==${sx} || y!==${sy})setCell(i,EMPTY,20,0);
}flyers=[];
}return {out,leak,inside:mat.reduce((n,m)=>n+(m===SAND),0)};})()`);
check(`corner ${vertical?"vertical":"horizontal"} joins ${v} carry every grain to the far mouth`,r.out===16 && !r.leak,JSON.stringify(r));
cornerLine(v,vertical);
putBar(s.tail*4,s.by*4); putBar(s.tail*4,s.by*4+2);
const bars=run(`(()=>{let out=0,overlap=false;for(let k=0;k<700;k++){
simTick();
for(let a=0;a<ents.length;a++)for(let b=0;b<a;b++) {
const e=ents[a],f=ents[b];if(e.ix<f.ix+4 && f.ix<e.ix+4 && e.iy<f.iy+2 && f.iy<e.iy+2)overlap=true;
}return crossed;})()`);
check(`${label} does not pass grains`,grains===0,grains);
run('mat.forEach((m,i)=>{if(m===SAND)setCell(i,EMPTY,20,0);});');
for(const lane of [0,1,2]) {
putBar(sx*4,sy*4+lane);
const bar=run(`(()=>{const e=ents[0];let crossed=false;for(let k=0;k<80;k++){
tick+=8;entMark(e,0);entScrewTick(e,SCREW_PARTS[${source}]);entMark(e,1);
if((e.ix>>2)===${tx} && (e.iy>>2)===${ty})crossed=true;
if(!entScrewAt(e.ix,e.iy))break;
}removeEnt(e);return crossed;})()`);
check(`${label} does not pass a bar in lane ${lane}`,!bar);
}
if(mat.some((m,i)=>occ[i] && (PH[m]===PH_SOLID || PH[m]===PH_POWDER)))overlap=true;
for(const e of ents.slice())if(${vertical ? (s.dy<0?`e.iy+2<=${s.hy*4}`:`e.iy>=${(s.hy+1)*4}`) : (s.dx<0?`e.ix+4<=${s.head*4}`:`e.ix>=${(s.head+1)*4}`)}){out++;removeEnt(e);}
}return {out,overlap,left:ents.length};})()`);
check(`corner ${vertical?"vertical":"horizontal"} joins ${v} carry queued bars`,bars.out===2 && !bars.overlap,JSON.stringify(bars));
}
// A saturated ten-block line: 80 warmup ticks, then 600 measured ticks at 60 Hz.
const rates = [];
@@ -208,9 +205,8 @@ for (const v of [4,5,6,7]) for (const end of [-1,1]) for (let dir=0;dir<4;dir++)
fresh();
const slope=v===4 || v===6?-1:1, nx=20+end, ny=20+end*slope, shift=end>0?3:0;
run(`placeBlock(BP_SCREW,${v},20,20,true);placeBlock(BP_SCREW,${dir},${nx},${ny},true);`);
const expected=dir&1?5:6;
check(`corner ${v} end ${end} straight ${dir} selects a closed elbow`,run(`(()=>{const j=diagonalScrewJoins(20,20,${v});return (j>>${shift}&7)===${expected} && !(j&${end>0?128:64});})()`));
check(`corner ${v} end ${end} straight ${dir} meets all four mouth lanes`,run(`(()=>{const j=diagonalScrewJoins(20,20,${v});return [0.5,1.5,2.5,3.5].every(t=>diagonalScrewShape(${dir&1?82+end*1.99:"80+t+"+end*4},${dir&1?"80+t+"+end*slope*4:82+end*slope*1.99},20*BW+20,${v},j)<=0);})()`));
check(`corner ${v} end ${end} straight ${dir} keeps its free-end collar`,run(`(()=>{const j=diagonalScrewJoins(20,20,${v});return (j>>${shift}&7)===0 && !!(j&${end>0?128:64});})()`));
check(`corner ${v} end ${end} straight ${dir} leaves the free-end geometry unchanged`,run(`(()=>{const b=20*BW+20,j=diagonalScrewJoins(20,20,${v});removeBlock(${nx},${ny});const alone=diagonalScrewJoins(20,20,${v});placeBlock(BP_SCREW,${dir},${nx},${ny},true);return j===alone && [0.5,1.5,2.5,3.5].every(t=>diagonalScrewShape(82+${end}*2,80+t,b,${v},j)===diagonalScrewShape(82+${end}*2,80+t,b,${v},alone));})()`));
check(`corner ${v} end ${end} straight ${dir} planned joins match placed joins`,run(`(()=>{const j=diagonalScrewJoins(20,20,${v});removeBlock(${nx},${ny});return diagonalScrewJoins(20,20,${v},new Map([[${ny}*BW+${nx},[BP_SCREW,${dir}]]]))===j;})()`));
}
for (const v of [4,5,6,7]) for (let dir=0;dir<4;dir++) for (const code of ['BP_SCREW','BP_BELT','BP_FILTER']) {
+30 -6
View File
@@ -97,34 +97,58 @@ with sync_playwright() as p:
}''')
check('sidewall stays behind the grain colour in pixels and smooth',
wall and wall['colors'][0] == wall['colors'][1] and wall['grainClass'] == 2, wall)
# All 64 straight/diagonal mouths must have copper casing on both sides of the join.
# Edge joins keep closed elbows; corner placements match two isolated free ends.
pg.set_viewport_size({"width": 2100, "height": 1300})
ev("part.fill(0); blockP.fill(0); occ.fill(0); mach.fill(0); ents=[]; ghostBlocks.clear();")
ev("() => {" + (root / "tools/scenes/t62-joins.js").read_text() + "}")
mouths = ev('''() => {
SET.zoom=6; SET.lighting="off"; SET.glow="off"; resizeRender();
bot.x=CAM[0]-bot.w/2;bot.y=CAM[1]-bot.h/2;snapCamera();
const now=performance.now.bind(performance),frozen=now();
performance.now=()=>frozen;
try {
const out={};
const draw=()=>{SM.scale=1;drawWorld();};
for(const look of ["pixel","smooth"]) {
SET.look=look;SM.scale=1;drawWorld();
SET.look=look;draw();
const bad=[];
for(const j of T62_JOINTS) {
const slope=j.v===4 || j.v===6?-1:1;
const end=j.end*SCREW_DIRS[j.v][0];
const dx=j.vertical?0:end,dy=j.vertical?end*slope:0;
const ox=j.corner && j.vertical?end*4:0,oy=j.corner && !j.vertical?end*slope*4:0;
if(j.corner)continue;
for(const side of [-1,1])for(const along of [-0.3,0.3]) {
const x=j.bx*4+2+ox+dx*(2+along)-dy*side*1.6;
const y=j.by*4+2+oy+dy*(2+along)+dx*side*1.6;
const x=j.bx*4+2+dx*(2+along)-dy*side*1.6;
const y=j.by*4+2+dy*(2+along)+dx*side*1.6;
const p=worldToScreenPx(x,y),c=R.ctx.getImageData(Math.floor(p.x),Math.floor(p.y),1,1).data;
if(!(c[0]>c[1]*1.15 && c[1]>c[2]*1.1))bad.push([j,x,y,Array.from(c)]);
}
}
const sample=(bx,by)=>{
const colors=[];
for(let y=0;y<4;y++)for(let x=0;x<4;x++) {
const p=worldToScreenPx(bx*4+x+0.5,by*4+y+0.5);
colors.push(...R.ctx.getImageData(Math.floor(p.x),Math.floor(p.y),1,1).data);
}return colors.join(',');
};
for(const j of T62_JOINTS.filter(j=>j.corner)) {
const [dx,dy]=SCREW_DIRS[j.v],sx=j.bx+j.end*dx,sy=j.by+j.end*dy;
const collar=sample(j.bx,j.by),straight=sample(sx,sy);
const joins=diagonalScrewJoins(j.bx,j.by,j.v),end=j.end*dx>0?1:0;
if(!(joins & (64<<end)) || (joins>>(end*3)&7))bad.push(['missing collar',j]);
removeBlock(sx,sy);draw();
if(sample(j.bx,j.by)!==collar)bad.push(['diagonal differs from isolated end',j]);
placeBlock(BP_SCREW,j.straight,sx,sy,true);
removeBlock(j.bx,j.by);draw();
if(sample(sx,sy)!==straight)bad.push(['straight differs from normal end',j]);
placeBlock(BP_SCREW,j.v,j.bx,j.by,true);draw();
}
out[look]=bad;
}return out;
} finally {performance.now=now;}
}''')
for look, bad in mouths.items():
check(f"{look}: all 64 screw joints have closed casing across both mouth edges", not bad, bad[:2])
check(f"{look}: edge elbows stay closed and corners show two separate free ends", not bad, bad[:2])
# a browser without hardware drawing gets the pixel look and a note
pg2 = b.new_page(viewport={'width': 800, 'height': 500})
pg2.add_init_script('localStorage.clear();')
+4 -3
View File
@@ -1,6 +1,7 @@
// Self-contained grid. Rows: UR, DR, DL, UL. Each run has both end joins.
// Columns: horizontal edge, horizontal corner, vertical edge, vertical corner;
// repeat those four with the straight travel reversed. Camera fits --zoom 6 --viewport 2100x1300.
// Self-contained grid. Rows: UR, DR, DL, UL. Each run shows both ends.
// Columns: horizontal edge, horizontal corner, vertical edge, vertical corner.
// Corner columns stay unconnected, with two separate ends.
// Repeat those four with the straight travel reversed. Camera fits --zoom 6 --viewport 2100x1300.
// Run after clear-sky.js or by itself. Empty, paused screws expose the whole casing.
money = 50000;
for (let y = 64; y < 280; y++) for (let x = 112; x < 456; x++) {