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:
@@ -486,6 +486,11 @@ above, including:
|
|||||||
on warns first that the save will be marked, and the mark stays with the world; any future
|
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
|
achievements won't unlock in a marked world. Besides money, items and upgrades it may have
|
||||||
game speed and other things just for fun.
|
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
|
## Ideas not built yet
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -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).
|
Per-tick machine work: steam engines, fans, belts, filters, screw conveyors (machinesTick).
|
||||||
- functions: engineIntake, fanBlow, 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.
|
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
|
- variables: CARRY_DEPTH, filterR, filterL, filtersDirty
|
||||||
|
|
||||||
### src/sim/slope-conveyors.js (230 lines)
|
### 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
|
- functions: moldCellsAt, canPlaceMold, newMoldRecord, addMold, castMix, cavityState, troughState, clearCavity, moldPlugCells, rakeAbove, furnaceJunkHint, unplugMold, ejectIngot, checkMolds, moldAt, tally, collectMold, removeMold
|
||||||
- variables: DROSS, grains, barWord
|
- 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.
|
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
|
- 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
|
- 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
|
- functions: slopeBeltConnections, flatBeltCaps, prepareSlopeBelts, slopeBeltSegment, slopeBeltCorner, slopeBeltShape, slopeBeltAt, slopeBeltColor, screenLoaded, slopeWallShape, slopeWallAt, slopeDetailAt, slopePreviewCleat
|
||||||
- variables: slopeBeltJoins, slopeBeltHalo, beltCaps, slopeAcross, slopeAlong, slopeDistance, slopeWallOwner
|
- 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
|
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
|
- functions: diagonalScrewJoins, diagonalScrewShape, diagonalScrewBlock
|
||||||
- variables: SCREW_CLASS, diagonalJoins, diagonalAcross, diagonalAlong, diagonalCap
|
- 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.
|
Smooth slope shading, kept beside the shared CPU geometry in slope-belts.js.
|
||||||
- variables: SM_SLOPE_HELPERS, SM_SLOPE_DRAW
|
- 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.
|
Smooth shaders: vertex and fragment programs for the rounded world look.
|
||||||
- variables: SM_VS, SM_FS
|
- 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/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/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/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.
|
||||||
|
|||||||
@@ -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
|
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: 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)
|
## 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
|
- Straight screws touching a diagonal's end only at the corner now join, in both looks and in
|
||||||
|
|||||||
@@ -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.
|
// Drawing only: the four transport lanes remain in the square block.
|
||||||
let diagonalJoins = new Uint8Array(0);
|
let diagonalJoins = new Uint8Array(0);
|
||||||
function diagonalScrewJoins(bx, by, v, planned = null) {
|
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;
|
const dy = v === 4 || v === 6 ? -1 : 1;
|
||||||
let joins = 0;
|
let joins = 0;
|
||||||
for (let end = 0; end < 2; end++) {
|
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)) ||
|
if ((code === BP_SCREW && nv < 4 && (nv & 1) === (dir & 1)) ||
|
||||||
((code === BP_BELT || code === BP_FILTER) && (dir & 1))) { join = dir + 1; break; }
|
((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);
|
joins |= join << (end * 3);
|
||||||
if (!join) joins |= 64 << end;
|
if (!join) joins |= 64 << end;
|
||||||
}
|
}
|
||||||
@@ -37,15 +36,10 @@ function diagonalScrewShape(x, y, b, v, joins) {
|
|||||||
for (let end = 0; end < 2; end++) {
|
for (let end = 0; end < 2; end++) {
|
||||||
const join = (joins >> (end * 3)) & 7, side = end ? 1 : -1;
|
const join = (joins >> (end * 3)) & 7, side = end ? 1 : -1;
|
||||||
const slope = v === 4 || v === 6 ? -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 dx = 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 dy = 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 * dx + ly * dy, across = -lx * dy + ly * dx;
|
||||||
const along = (lx - ox) * dx + (ly - oy) * dy, across = -(lx - ox) * dy + (ly - oy) * dx;
|
const radius = Math.hypot(across, Math.min(0, along));
|
||||||
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 cap = along - (join ? 2 : 2 * Math.SQRT2);
|
const cap = along - (join ? 2 : 2 * Math.SQRT2);
|
||||||
const sd = Math.max(radius - 2, join || (joins & (64 << end)) ? cap : -10);
|
const sd = Math.max(radius - 2, join || (joins & (64 << end)) ? cap : -10);
|
||||||
if (sd < best) {
|
if (sd < best) {
|
||||||
|
|||||||
@@ -265,12 +265,9 @@ ${SM_SLOPE_DRAW}
|
|||||||
int join = (joins >> (end * 3)) & 7;
|
int join = (joins >> (end * 3)) & 7;
|
||||||
float side = end == 0 ? -1.0 : 1.0;
|
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 :
|
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);
|
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, ray), cross2 = dot(local, vec2(-ray.y, ray.x));
|
||||||
float t = dot(local - offset, ray), cross2 = dot(local - offset, vec2(-ray.y, ray.x));
|
|
||||||
float radius = length(vec2(cross2, min(0.0, t))), cap = t - (join == 0 ? 2.82842712 : 2.0);
|
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.
|
// 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);
|
float sd = max(radius - 2.0, (joins & (64 << end)) != 0 ? cap : -10.0);
|
||||||
if (sd < screwSD) {
|
if (sd < screwSD) {
|
||||||
|
|||||||
+6
-16
@@ -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.
|
// 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.
|
// Prefer the next diagonal block, then either straight join, then the open head.
|
||||||
function diagonalScrewNext(i, p) {
|
function diagonalScrewNext(i, p) {
|
||||||
@@ -198,11 +184,15 @@ function diagonalScrewNext(i, p) {
|
|||||||
if (xx < 0 || xx >= W) return -1;
|
if (xx < 0 || xx >= W) return -1;
|
||||||
if ((xx >> 2) === (x >> 2)) return i + dx;
|
if ((xx >> 2) === (x >> 2)) return i + dx;
|
||||||
const yy = y + dy * BS;
|
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 (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;
|
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);
|
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;
|
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.
|
// 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
@@ -90,13 +90,6 @@ function entScrewTick(e, p) {
|
|||||||
if ((tick & 1) || netSat(ci) <= 0 || rnd() > netSat(ci)) return;
|
if ((tick & 1) || netSat(ci) <= 0 || rnd() > netSat(ci)) return;
|
||||||
const dx = SCREW_DX[p], dy = SCREW_DY[p];
|
const dx = SCREW_DX[p], dy = SCREW_DY[p];
|
||||||
if (dx && dy) { entDiagonalScrewTick(e, p); return; }
|
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;
|
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++) {
|
for (let k = 0; k < (dx ? BAR_H : BAR_W); k++) {
|
||||||
const i = entScrewEdge(e, dx, dy, 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;
|
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]]) {
|
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);
|
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 (SCREW_DX[q] * dx + SCREW_DY[q] * dy < 0) return;
|
||||||
if (entScrewMove(e, x, y, true)) stats.moved++;
|
if (entScrewMove(e, x, y, true)) stats.moved++;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ function machinesTick() {
|
|||||||
if (shifted) continue;
|
if (shifted) continue;
|
||||||
}
|
}
|
||||||
if (x + dx < 0 || x + dx >= W || y + dy < 0 || y + dy >= H) 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;
|
if (dest < 0) continue;
|
||||||
const nx = dest % W, ny = (dest / W) | 0;
|
const nx = dest % W, ny = (dest / W) | 0;
|
||||||
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
||||||
|
|||||||
+30
-34
@@ -48,38 +48,35 @@ for (const v of [4, 5, 6, 7]) {
|
|||||||
const joined = drain(c,500);
|
const joined = drain(c,500);
|
||||||
check(`direction ${v}: straight to diagonal to straight`,joined.out===16 && joined.leaks===0,JSON.stringify(joined));
|
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.
|
// Corner placements are separate mouths, on both axes and in either transfer direction.
|
||||||
function cornerLine(v, vertical = false) {
|
for (const v of [4,5,6,7]) for (const vertical of [false,true]) for (const fromDiagonal of [false,true]) {
|
||||||
const s = line(v), vv = vertical ? (s.dy < 0 ? 0 : 2) : (s.dx > 0 ? 1 : 3);
|
fresh();
|
||||||
const tx = s.bx-s.dx, ty = s.by-s.dy, hx = s.head+s.dx, hy = s.hy+s.dy;
|
const [dx,dy]=run(`SCREW_DIRS[${v}]`), sv=vertical?(dy<0?0:2):(dx>0?1:3);
|
||||||
run(`for(let k=0;k<3;k++) {
|
const sx=20+(fromDiagonal?0:-dx), sy=20+(fromDiagonal?0:-dy);
|
||||||
placeBlock(BP_SCREW,${vv},${tx}-k*${vertical?0:s.dx},${ty}-k*${vertical?s.dy:0},true);
|
const tx=20+(fromDiagonal?dx:0), ty=20+(fromDiagonal?dy:0);
|
||||||
placeBlock(BP_SCREW,${vv},${hx}+k*${vertical?0:s.dx},${hy}+k*${vertical?s.dy:0},true);
|
const source=fromDiagonal?v:sv, target=fromDiagonal?sv:v;
|
||||||
}`);
|
const label=`corner ${vertical?'vertical':'horizontal'} ${v} ${fromDiagonal?'diagonal to straight':'straight to diagonal'}`;
|
||||||
return {...s, tail:tx-2*(vertical?0:s.dx), by:ty-2*(vertical?s.dy:0),
|
run(`placeBlock(BP_SCREW,${source},${sx},${sy},true);placeBlock(BP_SCREW,${target},${tx},${ty},true);rebuildPower();`);
|
||||||
head:hx+2*(vertical?0:s.dx), hy:hy+2*(vertical?s.dy:0)};
|
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++){
|
||||||
for(const v of [4,5,6,7]) for(const vertical of [false,true]) {
|
tick+=2;tickStamp++;machinesTick();
|
||||||
const s=cornerLine(v,vertical);
|
for(let i=0;i<N;i++)if(mat[i]===SAND){
|
||||||
rect(run,s.tail*4,s.by*4,s.tail*4+3,s.by*4+3,run("SAND"));
|
const x=i%W>>2,y=(i/W|0)>>2;
|
||||||
const r=run(`(()=>{let out=0,leak=0;for(let k=0;k<700;k++){
|
if(x===${tx} && y===${ty})crossed++;
|
||||||
simTick();
|
if(x!==${sx} || y!==${sy})setCell(i,EMPTY,20,0);
|
||||||
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++;
|
|
||||||
}flyers=[];
|
}flyers=[];
|
||||||
}return {out,leak,inside:mat.reduce((n,m)=>n+(m===SAND),0)};})()`);
|
}return crossed;})()`);
|
||||||
check(`corner ${vertical?"vertical":"horizontal"} joins ${v} carry every grain to the far mouth`,r.out===16 && !r.leak,JSON.stringify(r));
|
check(`${label} does not pass grains`,grains===0,grains);
|
||||||
cornerLine(v,vertical);
|
run('mat.forEach((m,i)=>{if(m===SAND)setCell(i,EMPTY,20,0);});');
|
||||||
putBar(s.tail*4,s.by*4); putBar(s.tail*4,s.by*4+2);
|
for(const lane of [0,1,2]) {
|
||||||
const bars=run(`(()=>{let out=0,overlap=false;for(let k=0;k<700;k++){
|
putBar(sx*4,sy*4+lane);
|
||||||
simTick();
|
const bar=run(`(()=>{const e=ents[0];let crossed=false;for(let k=0;k<80;k++){
|
||||||
for(let a=0;a<ents.length;a++)for(let b=0;b<a;b++) {
|
tick+=8;entMark(e,0);entScrewTick(e,SCREW_PARTS[${source}]);entMark(e,1);
|
||||||
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;
|
if((e.ix>>2)===${tx} && (e.iy>>2)===${ty})crossed=true;
|
||||||
}
|
if(!entScrewAt(e.ix,e.iy))break;
|
||||||
if(mat.some((m,i)=>occ[i] && (PH[m]===PH_SOLID || PH[m]===PH_POWDER)))overlap=true;
|
}removeEnt(e);return crossed;})()`);
|
||||||
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);}
|
check(`${label} does not pass a bar in lane ${lane}`,!bar);
|
||||||
}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.
|
// A saturated ten-block line: 80 warmup ticks, then 600 measured ticks at 60 Hz.
|
||||||
const rates = [];
|
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();
|
fresh();
|
||||||
const slope=v===4 || v===6?-1:1, nx=20+end, ny=20+end*slope, shift=end>0?3:0;
|
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);`);
|
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} 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} 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} 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} 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} 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;})()`));
|
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']) {
|
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']) {
|
||||||
|
|||||||
+43
-19
@@ -97,34 +97,58 @@ with sync_playwright() as p:
|
|||||||
}''')
|
}''')
|
||||||
check('sidewall stays behind the grain colour in pixels and smooth',
|
check('sidewall stays behind the grain colour in pixels and smooth',
|
||||||
wall and wall['colors'][0] == wall['colors'][1] and wall['grainClass'] == 2, wall)
|
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})
|
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("part.fill(0); blockP.fill(0); occ.fill(0); mach.fill(0); ents=[]; ghostBlocks.clear();")
|
||||||
ev("() => {" + (root / "tools/scenes/t62-joins.js").read_text() + "}")
|
ev("() => {" + (root / "tools/scenes/t62-joins.js").read_text() + "}")
|
||||||
mouths = ev('''() => {
|
mouths = ev('''() => {
|
||||||
SET.zoom=6; SET.lighting="off"; SET.glow="off"; resizeRender();
|
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();
|
bot.x=CAM[0]-bot.w/2;bot.y=CAM[1]-bot.h/2;snapCamera();
|
||||||
const out={};
|
const now=performance.now.bind(performance),frozen=now();
|
||||||
for(const look of ["pixel","smooth"]) {
|
performance.now=()=>frozen;
|
||||||
SET.look=look;SM.scale=1;drawWorld();
|
try {
|
||||||
const bad=[];
|
const out={};
|
||||||
for(const j of T62_JOINTS) {
|
const draw=()=>{SM.scale=1;drawWorld();};
|
||||||
const slope=j.v===4 || j.v===6?-1:1;
|
for(const look of ["pixel","smooth"]) {
|
||||||
const end=j.end*SCREW_DIRS[j.v][0];
|
SET.look=look;draw();
|
||||||
const dx=j.vertical?0:end,dy=j.vertical?end*slope:0;
|
const bad=[];
|
||||||
const ox=j.corner && j.vertical?end*4:0,oy=j.corner && !j.vertical?end*slope*4:0;
|
for(const j of T62_JOINTS) {
|
||||||
for(const side of [-1,1])for(const along of [-0.3,0.3]) {
|
const slope=j.v===4 || j.v===6?-1:1;
|
||||||
const x=j.bx*4+2+ox+dx*(2+along)-dy*side*1.6;
|
const end=j.end*SCREW_DIRS[j.v][0];
|
||||||
const y=j.by*4+2+oy+dy*(2+along)+dx*side*1.6;
|
const dx=j.vertical?0:end,dy=j.vertical?end*slope:0;
|
||||||
const p=worldToScreenPx(x,y),c=R.ctx.getImageData(Math.floor(p.x),Math.floor(p.y),1,1).data;
|
if(j.corner)continue;
|
||||||
if(!(c[0]>c[1]*1.15 && c[1]>c[2]*1.1))bad.push([j,x,y,Array.from(c)]);
|
for(const side of [-1,1])for(const along of [-0.3,0.3]) {
|
||||||
|
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)=>{
|
||||||
out[look]=bad;
|
const colors=[];
|
||||||
}return out;
|
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():
|
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
|
# a browser without hardware drawing gets the pixel look and a note
|
||||||
pg2 = b.new_page(viewport={'width': 800, 'height': 500})
|
pg2 = b.new_page(viewport={'width': 800, 'height': 500})
|
||||||
pg2.add_init_script('localStorage.clear();')
|
pg2.add_init_script('localStorage.clear();')
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Self-contained grid. Rows: UR, DR, DL, UL. Each run has both end joins.
|
// Self-contained grid. Rows: UR, DR, DL, UL. Each run shows both ends.
|
||||||
// Columns: horizontal edge, horizontal corner, vertical edge, vertical corner;
|
// Columns: horizontal edge, horizontal corner, vertical edge, vertical corner.
|
||||||
// repeat those four with the straight travel reversed. Camera fits --zoom 6 --viewport 2100x1300.
|
// 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.
|
// Run after clear-sky.js or by itself. Empty, paused screws expose the whole casing.
|
||||||
money = 50000;
|
money = 50000;
|
||||||
for (let y = 64; y < 280; y++) for (let x = 112; x < 456; x++) {
|
for (let y = 64; y < 280; y++) for (let x = 112; x < 456; x++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user