Joins follow the travel direction: the next piece goes right after the end (t67 phase 2b)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+14
-10
@@ -486,16 +486,20 @@ 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.
|
||||||
- **One join rule for every transport piece (2026-10-27, replaces the t62 same-row rule).** The
|
- **One join rule for every transport piece: the next piece goes right after the end
|
||||||
owner wants all pieces to place and join the same way, so players know what to expect when
|
(2026-10-27, replaces the t62 same-row rule).** The owner wants all pieces to place and join
|
||||||
building. At a diagonal piece's end, call E the block diagonally past its last block (the
|
the same way, so players know what to expect when building. Joins follow the direction the
|
||||||
block its end points into). Another diagonal of the same slope continues in E. A horizontal
|
material travels: a piece's outlet points at the block right after its end, which is the
|
||||||
piece (flat belt, flat filter, horizontal screw) joins on the same row at the high end, and
|
next block along a straight piece, or the block diagonally past a diagonal's end. The next
|
||||||
in E, one block lower, at the low end: the rule sloped belts already had. A vertical screw
|
piece joins when it starts in that block and carries the same general way. So a horizontal
|
||||||
joins in E at either end. Straight pieces join the next block along their axis. Any other
|
then a diagonal (up or down) starts the diagonal in the next block on the same row; a
|
||||||
placement is two separate ends and nothing passes between them. The owner found the old
|
diagonal then a horizontal or vertical starts it diagonally past the diagonal's end; a
|
||||||
screw rule, which needed the horizontal placed over the diagonal's last block at the low
|
vertical going up then a diagonal starts the diagonal directly above. Any other placement is
|
||||||
end, inconsistent with every other piece.
|
two separate ends that pass nothing. Belts carry grains on top, so they bend the rule where
|
||||||
|
grains can't follow it: an uphill belt's top feeds a flat belt on the same row, and a flat
|
||||||
|
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.
|
||||||
|
|
||||||
## Ideas not built yet
|
## Ideas not built yet
|
||||||
|
|
||||||
|
|||||||
+21
-19
@@ -118,15 +118,17 @@ dist/, shots/ build output and screenshots (not committed)
|
|||||||
- kind `mold`.
|
- kind `mold`.
|
||||||
`hidden` pieces are not in the menu. The menu's tabs and order are `BUILD_CATS` in
|
`hidden` pieces are not in the menu. The menu's tabs and order are `BUILD_CATS` in
|
||||||
`src/ui/build-menu.js`.
|
`src/ui/build-menu.js`.
|
||||||
- **Transport geometry:** `joins.js` owns geometric ends and joins for belts, filters and screws.
|
- **Transport geometry:** `joins.js` owns travel-directed ends and mutual joins for belts,
|
||||||
`pieceEnds(code, v)` returns precomputed left/right ends (vertical screws: up/down).
|
filters and screws. `pieceEnds(code, v)` returns precomputed geometric left/right ends
|
||||||
`endJoin(bx, by, endIndex, planned = null)` returns `neighbourBlock * 2 + neighbourEnd`,
|
(vertical screws: up/down), with travel and inlet/outlet roles. `endJoin(bx, by, endIndex,
|
||||||
or -1; `pieceJoins(bx, by, planned = null)` returns bits 0/1 for the joined ends.
|
planned = null)` returns `neighbourBlock * 2 + neighbourEnd`, or -1; `pieceJoins` returns
|
||||||
Plans override world blocks, including the queried block. Continuing diagonals take priority
|
bits 0/1. Plans override world blocks, including the queried block. Outlets target the next
|
||||||
over elbows; a join is returned only when both ends choose each other. Belt and screw drawing
|
block along travel, with the belt surface exceptions. Inlet ties prefer same-shape travel,
|
||||||
use this module, as do diagonal screw grain and bar transfers (`diagonalScrewNext` and
|
then top-to-bottom, left-to-right; a join must be reciprocal. Both looks, ghosts, previews,
|
||||||
`straightScrewNext`). Straight screws still feed the side of lifts. E elbows bend in the
|
diagonal screw grain/bar transfers and straight screw entry into diagonals share this rule.
|
||||||
straight end block; both looks, ghosts and previews share that geometry.
|
Flat-belt bar entry uses `entBeltScrewEnter` and the swept-path collision check. Straight
|
||||||
|
screw side-feeding into lifts remains independent. Edge elbows bend inside the
|
||||||
|
diagonal; diagonal outlets bend inside the receiving straight block.
|
||||||
- **Placing and removing:**
|
- **Placing and removing:**
|
||||||
- `placeBlock` / `removeBlock` (`blocks.js`);
|
- `placeBlock` / `removeBlock` (`blocks.js`);
|
||||||
- `placeMachineAt` / `addMachine` / `removeMachine` (`actions.js`, `machines.js`);
|
- `placeMachineAt` / `addMachine` / `removeMachine` (`actions.js`, `machines.js`);
|
||||||
@@ -274,12 +276,12 @@ 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/joins.js (71 lines)
|
### src/sim/joins.js (73 lines)
|
||||||
Geometric transport ends. Tables are built once; querying a world or plan allocates nothing. Ends are left/right, except vertical screws (up/down). Travel direction is irrelevant.
|
Travel-directed transport ends. Precomputed probes; world/plan queries allocate nothing. End indices stay geometric: left/right, or up/down for vertical screws.
|
||||||
- functions: pieceEnds, joinCandidate, endJoin, pieceJoins
|
- functions: pieceEnds, joinCandidate, endJoin, pieceJoins
|
||||||
- variables: JOIN_ENDS
|
- variables: JOIN_ENDS
|
||||||
|
|
||||||
### src/sim/conveyors.js (223 lines)
|
### src/sim/conveyors.js (211 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, screwAccepts, straightScrewNext, diagonalScrewNext
|
- functions: conveyorStep, carryColumn, beltDrain, launchGrain, rebuildFilters, filterCarry, carryBeltsAndFilters, setFilter, filterRun, roomFor, screwSpread, screwIntake, screwAccepts, straightScrewNext, diagonalScrewNext
|
||||||
- variables: CARRY_DEPTH, filterR, filterL, filtersDirty
|
- variables: CARRY_DEPTH, filterR, filterL, filtersDirty
|
||||||
@@ -298,9 +300,9 @@ 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 (307 lines)
|
### src/sim/items.js (317 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, entBeltScrewEnter, 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
|
||||||
|
|
||||||
### src/sim/tools.js (52 lines)
|
### src/sim/tools.js (52 lines)
|
||||||
@@ -376,17 +378,17 @@ Drawing (src/render/): browser only. This file: the renderer state R, colour tab
|
|||||||
Propagated coloured light from glowing and burning cells.
|
Propagated coloured light from glowing and burning cells.
|
||||||
- functions: builtForLight, looseForLight, computeLight
|
- functions: builtForLight, looseForLight, computeLight
|
||||||
|
|
||||||
### src/render/slope-belts.js (157 lines)
|
### src/render/slope-belts.js (169 lines)
|
||||||
Drawing only: the face stays on its simulation cells; the band hangs below it.
|
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 (78 lines)
|
### src/render/diagonal-screws.js (79 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, straightScrewJoins, diagonalScrewShape, diagonalScrewBlock
|
- functions: diagonalScrewJoins, straightScrewJoins, diagonalScrewShape, diagonalScrewBlock
|
||||||
- variables: SCREW_CLASS, diagonalJoins, diagonalAcross, diagonalAlong, diagonalCap
|
- variables: SCREW_CLASS, diagonalJoins, diagonalAcross, diagonalAlong, diagonalCap
|
||||||
|
|
||||||
### src/render/world.js (404 lines)
|
### src/render/world.js (405 lines)
|
||||||
Scale colour without changing the smooth-look class.
|
Scale colour without changing the smooth-look class.
|
||||||
- functions: fogColor, drawWorld
|
- functions: fogColor, drawWorld
|
||||||
|
|
||||||
@@ -403,7 +405,7 @@ Smooth look: WebGL setup, texture uploads, drawing and adaptive resolution.
|
|||||||
- functions: smCompile, smoothAvailable, smUpload, smoothDraw, smoothPace
|
- functions: smCompile, smoothAvailable, smUpload, smoothDraw, smoothPace
|
||||||
- variables: SM, smBytes
|
- variables: SM, smBytes
|
||||||
|
|
||||||
### src/render/slope-belt-shader.js (111 lines)
|
### src/render/slope-belt-shader.js (126 lines)
|
||||||
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
|
||||||
|
|
||||||
@@ -582,7 +584,7 @@ The frame loop and boot.
|
|||||||
- `tools/scenes/bar-lift.js`: A mold drops copper bars onto a belt, through a screw lift and onto the upper belt.
|
- `tools/scenes/bar-lift.js`: A mold drops copper bars onto a belt, through a screw lift and onto the upper belt.
|
||||||
- `tools/scenes/belt-heap.js`: A belt feeding an upward screw: sand underneath a deep heap of copper ore. Run clear-sky.js first, e.g. python3 tools/shot.py /tmp/belt-heap.png --setup tools/scenes/clear-sky.js --js "$(cat tools/scenes/belt-heap.js)" --ticks 240 --zoom 7
|
- `tools/scenes/belt-heap.js`: A belt feeding an upward screw: sand underneath a deep heap of copper ore. Run clear-sky.js first, e.g. python3 tools/shot.py /tmp/belt-heap.png --setup tools/scenes/clear-sky.js --js "$(cat tools/scenes/belt-heap.js)" --ticks 240 --zoom 7
|
||||||
- `tools/scenes/clear-sky.js`: A clear work area left of the bot's starting point (the trade dock is to its right): open sky 160 grains wide and 40 tall, over a flat stone floor. Money and a bag of building stuff to build with. Sets FLOOR (the first row of the floor), BX, BY (the leftmost block of the area that stands on the floor) and CAM (the middle of the area).
|
- `tools/scenes/clear-sky.js`: A clear work area left of the bot's starting point (the trade dock is to its right): open sky 160 grains wide and 40 tall, over a flat stone floor. Money and a bag of building stuff to build with. Sets FLOOR (the first row of the floor), BX, BY (the leftmost block of the area that stands on the floor) and CAM (the middle of the area).
|
||||||
- `tools/scenes/joins.js`: Shared join grid: eight rows (UR/DR/DL/UL, left/right ends), nine columns. Screw H and V, then belt H; each tries the side, above/below, and E. Self-contained, paused and empty. Use --zoom 4 --viewport 2100x1500 --no-light.
|
- `tools/scenes/joins.js`: Travel join grid: UR/DR/DL/UL, both ends, and all three neighbouring placements. Screw H and V, then belt H; each tries the side, above/below, and E. Self-contained, paused and empty. Use --zoom 4 --viewport 2100x1500 --no-light.
|
||||||
- `tools/scenes/mold-labels.js`: Run after clear-sky.js: filling, blocked, set, and hot machinery in one view.
|
- `tools/scenes/mold-labels.js`: Run after clear-sky.js: filling, blocked, set, and hot machinery in one view.
|
||||||
- `tools/scenes/silo.js`: Run after clear-sky.js; leave paused so the open gate keeps its demonstration charge.
|
- `tools/scenes/silo.js`: Run after clear-sky.js; leave paused so the open gate keeps its demonstration charge.
|
||||||
- `tools/scenes/slope-conveyors.js`: Loaded sidewall conveyor and screen. Use --js 'slopeScene(false)' (belt) or 'slopeScene(true)' (screen).
|
- `tools/scenes/slope-conveyors.js`: Loaded sidewall conveyor and screen. Use --js 'slopeScene(false)' (belt) or 'slopeScene(true)' (screen).
|
||||||
|
|||||||
@@ -4,6 +4,25 @@ 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 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
|
||||||
|
rest. New rule (DESIGN.md): a piece's outlet points at its block plus its travel vector, and
|
||||||
|
the next piece joins when it sits there travelling the same general way. Belts bend it where
|
||||||
|
grains can't follow: an uphill belt's top feeds a flat belt on the same row, and a flat belt
|
||||||
|
one block below an uphill low end still joins.
|
||||||
|
- `src/sim/joins.js` reworked around inlets and outlets; screw drawing, belt drawing, screw
|
||||||
|
grain flow and bars follow it. Straight-screw mouths no longer reach a diagonal across a
|
||||||
|
corner; straight-to-straight transfers (lift side-feeding) unchanged.
|
||||||
|
- Files: `src/sim/joins.js`, `src/sim/conveyors.js`, `src/sim/items.js`,
|
||||||
|
`src/render/diagonal-screws.js`, `src/render/slope-belts.js`, `src/render/slope-belt-shader.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`.
|
||||||
|
- 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.
|
||||||
|
|
||||||
## 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)
|
||||||
|
|
||||||
- Screw drawing, grain flow and bars now all go through `endJoin`. New joins: a horizontal in
|
- Screw drawing, grain flow and bars now all go through `endJoin`. New joins: a horizontal in
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ function diagonalScrewJoins(bx, by, v, planned = null) {
|
|||||||
const code = next ? next[0] : blockP[nb], nv = next ? next[1] : blockV[nb];
|
const code = next ? next[0] : blockP[nb], nv = next ? next[1] : blockV[nb];
|
||||||
if (pieceEnds(code, nv)[join & 1].diagonal) continue;
|
if (pieceEnds(code, nv)[join & 1].diagonal) continue;
|
||||||
const horizontal = code !== BP_SCREW || (nv & 1);
|
const horizontal = code !== BP_SCREW || (nv & 1);
|
||||||
const kind = ((nb / BW) | 0) === by ? (end ? 2 : 4) : horizontal ? 5 : 6;
|
const ny = (nb / BW) | 0, nx = nb % BW;
|
||||||
|
const kind = ny === by ? (nx > bx ? 2 : 4) : nx === bx ? (ny < by ? 1 : 3) : horizontal ? 5 : 6;
|
||||||
joins |= kind << (end * 3);
|
joins |= kind << (end * 3);
|
||||||
}
|
}
|
||||||
return joins;
|
return joins;
|
||||||
|
|||||||
@@ -23,13 +23,27 @@ 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 = slopeStroke(p, vec2(-2.0, a.y), a, worldOrigin.x - 2.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);
|
||||||
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 = slopeStroke(p, b, vec2(6, b.y), worldOrigin.x + b.x);
|
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);
|
||||||
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;
|
||||||
@@ -77,8 +91,9 @@ 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 joins = int(tag.a * 255.0 + 0.5);
|
int packedX = int(tag.g * 255.0 + 0.5);
|
||||||
vec2 local = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w);
|
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);
|
||||||
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;
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ 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);
|
||||||
joins |= (isSlopeBelt(next ? next[0] : blockP[nb]) ? 4 : 1) << end;
|
const slope = isSlopeBelt(next ? next[0] : blockP[nb]) &&
|
||||||
|
((nb / BW) | 0) - by === beltGrade(code) * (nb % BW - bx);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@@ -56,11 +60,19 @@ 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) {
|
||||||
slopeBeltSegment(x, y, -2, ay, ax, ay, ox - 2);
|
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);
|
||||||
slopeBeltCorner(x, y, ax, ay, ox + ax);
|
slopeBeltCorner(x, y, ax, ay, ox + ax);
|
||||||
}
|
}
|
||||||
if (joins & 2) {
|
if (joins & 2) {
|
||||||
slopeBeltSegment(x, y, bx, by, 6, by, ox + bx);
|
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);
|
||||||
slopeBeltCorner(x, y, bx, by, ox + bx);
|
slopeBeltCorner(x, y, bx, by, ox + bx);
|
||||||
}
|
}
|
||||||
return slopeDistance;
|
return slopeDistance;
|
||||||
|
|||||||
+2
-1
@@ -279,8 +279,9 @@ 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) << 8);
|
((y - ((sb / BW) | 0) * BS + 8) << 16) | (((x - (sb % BW) * BS + 8) | (slopeBeltJoins[sb] & 128)) << 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
-16
@@ -185,22 +185,10 @@ function straightScrewNext(i, p) {
|
|||||||
const dx = SCREW_DX[p], dy = SCREW_DY[p], x = i % W, y = (i / W) | 0;
|
const dx = SCREW_DX[p], dy = SCREW_DY[p], x = i % W, y = (i / W) | 0;
|
||||||
const xx = x + dx, yy = y + dy, next = yy * W + xx;
|
const xx = x + dx, yy = y + dy, next = yy * W + xx;
|
||||||
if ((xx >> 2) === (x >> 2) && (yy >> 2) === (y >> 2)) return next;
|
if ((xx >> 2) === (x >> 2) && (yy >> 2) === (y >> 2)) return next;
|
||||||
const end = dx + dy > 0 ? 1 : 0;
|
const q = part[next];
|
||||||
const q = part[next], nb = (yy >> 2) * BW + (xx >> 2);
|
if (!(SCREW_DX[q] && SCREW_DY[q])) return next;
|
||||||
if (blockP[nb] && !(SCREW_DX[q] && SCREW_DY[q])) return next;
|
const join = endJoin(x >> 2, y >> 2, dx + dy > 0 ? 1 : 0);
|
||||||
const join = endJoin(x >> 2, y >> 2, end);
|
return join >= 0 && (join >> 1) === (yy >> 2) * BW + (xx >> 2) ? next : -1;
|
||||||
if (join >= 0) {
|
|
||||||
const target = join >> 1, v = blockV[target];
|
|
||||||
if (blockP[target] === BP_SCREW && v >= 4) {
|
|
||||||
if (target === nb) return next;
|
|
||||||
if (!blockP[nb]) {
|
|
||||||
const lane = dx ? y & 3 : x & 3;
|
|
||||||
const tx = target % BW * BS + (join & 1 ? BS - 1 : 0);
|
|
||||||
return (((target / BW) | 0) * BS + lane) * W + tx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return SCREW_DX[q] && SCREW_DY[q] ? -1 : next;
|
|
||||||
}
|
}
|
||||||
// Four lanes cross a block in four beats; only a mutual geometric join can receive them.
|
// Four lanes cross a block in four beats; only a mutual geometric join can receive them.
|
||||||
function diagonalScrewNext(i, p) {
|
function diagonalScrewNext(i, p) {
|
||||||
|
|||||||
+18
-8
@@ -93,13 +93,6 @@ function entScrewTick(e, p) {
|
|||||||
const frontX = e.ix + (dx > 0 ? BAR_W - 1 : 0), frontY = e.iy + (dy > 0 ? BAR_H - 1 : 0);
|
const frontX = e.ix + (dx > 0 ? BAR_W - 1 : 0), frontY = e.iy + (dy > 0 ? BAR_H - 1 : 0);
|
||||||
const front = frontY * W + frontX, dest = straightScrewNext(front, p);
|
const front = frontY * W + frontX, dest = straightScrewNext(front, p);
|
||||||
if (dest < 0) return;
|
if (dest < 0) return;
|
||||||
if (dest !== front + dx + dy * W) {
|
|
||||||
const q = part[dest];
|
|
||||||
if (!screwAccepts(q, dx, dy)) return;
|
|
||||||
const x = (dest % W) & ~3, y = entScrewLane((dest / 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);
|
||||||
@@ -149,6 +142,23 @@ function entDiagonalScrewTick(e, p) {
|
|||||||
for (let yy = y; yy < y + BAR_H; yy++) for (let xx = x; xx < x + BAR_W; xx++) if (part[yy * W + xx]) return;
|
for (let yy = y; yy < y + BAR_H; yy++) for (let xx = x; xx < x + BAR_W; xx++) if (part[yy * W + xx]) return;
|
||||||
if (entScrewMove(e, x, y, false, dy < 0)) { e.vx = dx * 0.5; e.vy = dy * 0.6; stats.moved++; }
|
if (entScrewMove(e, x, y, false, dy < 0)) { e.vx = dx * 0.5; e.vy = dy * 0.6; stats.moved++; }
|
||||||
}
|
}
|
||||||
|
// A same-row flat belt drops its bar into a joined screw, just as grains fall off its face.
|
||||||
|
function entBeltScrewEnter(e) {
|
||||||
|
if (e.ix & 3) return false;
|
||||||
|
const y = e.iy + BAR_H;
|
||||||
|
if (y >= H || (y & 3)) return false;
|
||||||
|
const b = (y >> 2) * BW + (e.ix >> 2), c = blockP[b], v = blockV[b];
|
||||||
|
if (c !== BP_BELT && c !== BP_FILTER) return false;
|
||||||
|
if (c === BP_FILTER && filterPassAt(e.m, y * W + e.ix)) return false;
|
||||||
|
if (netSat(y * W + e.ix) <= 0) return false;
|
||||||
|
const d = c === BP_FILTER ? (v & 128 ? 1 : -1) : (v & 1 ? 1 : -1);
|
||||||
|
const join = endJoin(b % BW, (b / BW) | 0, d > 0 ? 1 : 0);
|
||||||
|
if (join < 0 || blockP[join >> 1] !== BP_SCREW) return false;
|
||||||
|
e.x = e.ix; e.y = e.iy; e.vx = e.vy = 0;
|
||||||
|
const nb = join >> 1;
|
||||||
|
if (!(tick & 1) && entScrewMove(e, nb % BW * BS, ((nb / BW) | 0) * BS, true, true)) stats.moved++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// Bars stay level; their highest contact follows the face one grain per belt beat.
|
// Bars stay level; their highest contact follows the face one grain per belt beat.
|
||||||
function entSlopeBeltTick(e) {
|
function entSlopeBeltTick(e) {
|
||||||
const bottom = e.iy + BAR_H;
|
const bottom = e.iy + BAR_H;
|
||||||
@@ -188,7 +198,7 @@ function entTick() {
|
|||||||
let ix = e.ix; const iy = e.iy;
|
let ix = e.ix; const iy = e.iy;
|
||||||
const screw = entScrewAt(ix, iy);
|
const screw = entScrewAt(ix, iy);
|
||||||
if (screw) entScrewTick(e, screw);
|
if (screw) entScrewTick(e, screw);
|
||||||
else if (!entSlopeBeltTick(e)) {
|
else if (!entBeltScrewEnter(e) && !entSlopeBeltTick(e)) {
|
||||||
// what is underneath?
|
// what is underneath?
|
||||||
let belt = 0, bsat = 0, transport = false, support = iy + BAR_H >= H;
|
let belt = 0, bsat = 0, transport = false, support = iy + BAR_H >= H;
|
||||||
if (!support) {
|
if (!support) {
|
||||||
|
|||||||
+40
-38
@@ -1,46 +1,48 @@
|
|||||||
// Geometric transport ends. Tables are built once; querying a world or plan allocates nothing.
|
// Travel-directed transport ends. Precomputed probes; world/plan queries allocate nothing.
|
||||||
// Ends are left/right, except vertical screws (up/down). Travel direction is irrelevant.
|
// End indices stay geometric: left/right, or up/down for vertical screws.
|
||||||
const JOIN_ENDS = [[], ...[
|
const JOIN_ENDS = [[]];
|
||||||
['screw-horizontal', 1, 0], ['screw-vertical', 0, 1],
|
for (const [kind, tx, ty] of [
|
||||||
['screw-diagonal', 1, -1], ['screw-diagonal', 1, 1],
|
...SCREW_DIRS.map(([x,y]) => [x && y ? 'screw-diagonal' : x ? 'screw-horizontal' : 'screw-vertical',x,y]),
|
||||||
['belt-flat', 1, 0], ['belt-sloped', 1, -1], ['belt-sloped', 1, 1]
|
['belt-flat',-1,0], ['belt-flat',1,0],
|
||||||
].map(([kind, dx, dy]) => [-1, 1].map(s => ({
|
['belt-sloped',-1,1], ['belt-sloped',1,-1],
|
||||||
dx: s * dx, dy: s * dy, diagonal: !!(dx && dy),
|
['belt-sloped',-1,-1], ['belt-sloped',1,1]
|
||||||
high: !!(dx && dy && s * dy < 0), low: !!(dx && dy && s * dy > 0), kind,
|
]) {
|
||||||
targets: []
|
const outlet = (tx || ty) > 0 ? 1 : 0;
|
||||||
})))];
|
JOIN_ENDS.push([0,1].map(e => ({
|
||||||
|
dx: (e ? 1 : -1) * Math.abs(tx), dy: (e ? 1 : -1) * (tx ? ty * tx : 1),
|
||||||
|
diagonal: !!(tx && ty), kind, tx, ty, outlet: e === outlet, targets: []
|
||||||
|
})));
|
||||||
|
}
|
||||||
function pieceEnds(code, v) {
|
function pieceEnds(code, v) {
|
||||||
if (code === BP_SCREW) {
|
if (code === BP_SCREW) return JOIN_ENDS[v >= 0 && v < 8 ? v + 1 : 0];
|
||||||
if (v < 0 || v > 7) return JOIN_ENDS[0];
|
if (!isBelt(code) && !isFilter(code)) return JOIN_ENDS[0];
|
||||||
return JOIN_ENDS[v < 4 ? (v & 1 ? 1 : 2) : (SCREW_DIRS[v][0] * SCREW_DIRS[v][1] < 0 ? 3 : 4)];
|
const d = isFilter(code) ? (v & 128 ? 1 : 0) : (v & 1);
|
||||||
|
return JOIN_ENDS[9 + d + (beltGrade(code) < 0 ? 2 : beltGrade(code) > 0 ? 4 : 0)];
|
||||||
}
|
}
|
||||||
if (code === BP_BELT || code === BP_FILTER) return JOIN_ENDS[5];
|
for (const ends of JOIN_ENDS) for (let e = 0; e < ends.length; e++) {
|
||||||
return isSlopeBelt(code) ? JOIN_ENDS[beltGrade(code) < 0 ? 6 : 7] : JOIN_ENDS[0];
|
const a = ends[e];
|
||||||
}
|
if (!a.outlet) continue;
|
||||||
// Compile the diagonal-end rule and its inverse into bounded neighbour probes.
|
|
||||||
for (const ends of JOIN_ENDS) for (const end of ends) {
|
|
||||||
for (const others of JOIN_ENDS) for (let n = 0; n < others.length; n++) {
|
for (const others of JOIN_ENDS) for (let n = 0; n < others.length; n++) {
|
||||||
const other = others[n], screw = end.kind.startsWith('screw'), otherScrew = other.kind.startsWith('screw');
|
const b = others[n], screw = a.kind.startsWith('screw'), otherScrew = b.kind.startsWith('screw');
|
||||||
if (screw !== otherScrew && (end.kind === 'screw-vertical' || other.kind === 'screw-vertical' ||
|
if (b.outlet || a.tx * b.tx + a.ty * b.ty <= 0) continue;
|
||||||
(end.diagonal && other.diagonal))) continue;
|
if (screw !== otherScrew && (a.kind === 'screw-vertical' || b.kind === 'screw-vertical' ||
|
||||||
let dx, dy;
|
(a.diagonal && b.diagonal))) continue;
|
||||||
if (end.diagonal && other.diagonal) {
|
if (a.diagonal && b.diagonal && (a.tx !== b.tx || a.ty !== b.ty)) continue;
|
||||||
if (screw !== otherScrew || end.dx !== -other.dx || end.dy !== -other.dy) continue;
|
const dy = !screw && a.ty < 0 ? 0 : a.ty;
|
||||||
dx = end.dx; dy = end.dy;
|
const offsets = [[a.tx, dy]];
|
||||||
} else if (end.diagonal) {
|
// Grains on a flat belt below an uphill low end already reach its carrying face.
|
||||||
if (other.dx ? other.dx !== -end.dx : other.dy !== -end.dy) continue;
|
if (!screw && !otherScrew && !a.ty && b.ty < 0) offsets.push([a.tx, -1]);
|
||||||
dx = end.dx; dy = other.dx && end.high ? 0 : end.dy;
|
for (const [dx, yy] of offsets) {
|
||||||
} else if (other.diagonal) {
|
a.targets.push({ dx, dy: yy, ends: others, end: n });
|
||||||
if (end.dx ? end.dx !== -other.dx : end.dy !== -other.dy) continue;
|
b.targets.push({ dx: -dx, dy: -yy, ends, end: e });
|
||||||
dx = -other.dx; dy = end.dx && other.high ? 0 : -other.dy;
|
|
||||||
} else {
|
|
||||||
if (end.dx !== -other.dx || end.dy !== -other.dy) continue;
|
|
||||||
dx = end.dx; dy = end.dy;
|
|
||||||
}
|
}
|
||||||
end.targets.push({ dx, dy, ends: others, end: n });
|
|
||||||
}
|
}
|
||||||
// Prefer a continuing diagonal run to an elbow when both are present.
|
}
|
||||||
end.targets.sort((a, b) => Number(b.ends[b.end].diagonal) - Number(a.ends[a.end].diagonal));
|
for (const ends of JOIN_ENDS) for (const end of ends) {
|
||||||
|
const sameShape = t => t.ends[t.end].kind === end.kind &&
|
||||||
|
t.ends[t.end].tx === end.tx && t.ends[t.end].ty === end.ty;
|
||||||
|
end.targets.sort((a,b) => end.outlet ? Math.abs(a.dy - end.ty) - Math.abs(b.dy - end.ty) :
|
||||||
|
Number(sameShape(b)) - Number(sameShape(a)) || a.dy - b.dy || a.dx - b.dx);
|
||||||
}
|
}
|
||||||
// Result: neighbour block index * 2 + its end index; -1 means an open end.
|
// Result: neighbour block index * 2 + its end index; -1 means an open end.
|
||||||
function joinCandidate(bx, by, endIndex, planned) {
|
function joinCandidate(bx, by, endIndex, planned) {
|
||||||
|
|||||||
+1
-1
@@ -110,7 +110,7 @@ function drawPreviews() {
|
|||||||
if (dd !== GAME.dragDir) { GAME.dragDir = dd; hudDirty = true; }
|
if (dd !== GAME.dragDir) { GAME.dragDir = dd; hudDirty = true; }
|
||||||
const dIdx = dd >= 0 ? dd : (GAME.pieceDir[P.id] || 0);
|
const dIdx = dd >= 0 ? dd : (GAME.pieceDir[P.id] || 0);
|
||||||
const afford = canAfford(pieceCostNow(P), cells.length);
|
const afford = canAfford(pieceCostNow(P), cells.length);
|
||||||
const planned = code === BP_SCREW || isSlopeBelt(code) ? new Map(cells.map(([x, y]) => [y * BW + x, [code, dIdx]])) : null;
|
const planned = code === BP_SCREW || isSlopeBelt(code) ? new Map(cells.map(([x, y]) => [y * BW + x, [code, isFilter(code) ? v : dIdx]])) : null;
|
||||||
for (const [bx, by] of cells) {
|
for (const [bx, by] of cells) {
|
||||||
const err = canPlaceBlock(bx, by, code), rb = by * BW + bx;
|
const err = canPlaceBlock(bx, by, code), rb = by * BW + bx;
|
||||||
const swap = GAME.replace && !!blockP[rb] && /already built/.test(err) && !blockIs(rb, code, v, pieceExtra(P));
|
const swap = GAME.replace && !!blockP[rb] && /already built/.test(err) && !blockIs(rb, code, v, pieceExtra(P));
|
||||||
|
|||||||
+55
-40
@@ -12,8 +12,8 @@ function line(v, chain = false) {
|
|||||||
run(`for(let k=0;k<10;k++) placeBlock(BP_SCREW,${v},${bx}+k*${dx},${by}+k*${dy},true);`);
|
run(`for(let k=0;k<10;k++) placeBlock(BP_SCREW,${v},${bx}+k*${dx},${by}+k*${dy},true);`);
|
||||||
let ty = by, tail = bx, head = bx + 9 * dx;
|
let ty = by, tail = bx, head = bx + 9 * dx;
|
||||||
if (chain) {
|
if (chain) {
|
||||||
run(`for(let k=1;k<=3;k++) { placeBlock(BP_SCREW,${dx > 0 ? 1 : 3},${bx}-k*${dx},${by+(dy<0?1:0)},true); placeBlock(BP_SCREW,${dx > 0 ? 1 : 3},${head}+k*${dx},${by + 9 * dy+(dy>0?1:0)},true); }`);
|
run(`for(let k=1;k<=3;k++) { placeBlock(BP_SCREW,${dx > 0 ? 1 : 3},${bx}-k*${dx},${by},true); placeBlock(BP_SCREW,${dx > 0 ? 1 : 3},${head}+k*${dx},${by + 10 * dy},true); }`);
|
||||||
ty += dy<0?1:0; tail -= 3 * dx; head += 3 * dx;
|
tail -= 3 * dx; head += 3 * dx;
|
||||||
}
|
}
|
||||||
return { dx, dy, bx, by, ty, tail, head, hy: by + 9 * dy };
|
return { dx, dy, bx, by, ty, tail, head, hy: by + 9 * dy };
|
||||||
}
|
}
|
||||||
@@ -50,34 +50,30 @@ 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));
|
||||||
}
|
}
|
||||||
// Every neighbouring block, including the old edge probes and E, in both directions.
|
// Every straight direction and neighbour offset, with actual transfers in both source orders.
|
||||||
for (const v of [4,5,6,7]) for (const side of [-1,1]) for (const vertical of [false,true]) {
|
for(const v of [4,5,6,7])for(const sv of [0,1,2,3])for(const [ox,oy] of [[-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[-1,1],[0,1],[1,1]]) {
|
||||||
const slope=v===4||v===6?-1:1, end=side>0?1:0, dy=side*slope;
|
const [dx,dy]=run(`SCREW_DIRS[${v}]`),[sxv,syv]=run(`SCREW_DIRS[${sv}]`),vertical=!sxv;
|
||||||
for (const [ox,oy] of [[-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[-1,1],[0,1],[1,1]]) {
|
const out=ox===dx&&oy===dy&&dx*sxv+dy*syv>0;
|
||||||
const joined=vertical ? ox===side && oy===dy : ox===side && oy===(dy<0?0:dy);
|
const incoming=ox===-sxv&&oy===-syv&&dx*sxv+dy*syv>0;
|
||||||
const nx=20+ox, ny=20+oy, sv=vertical?(dy<0?0:2):(side>0?1:3);
|
const joined=out||incoming,end=out?(dx>0?1:0):(dx>0?0:1),nx=20+ox,ny=20+oy;
|
||||||
const label=`diagonal ${v} end ${end} ${vertical?'vertical':'horizontal'} offset ${ox},${oy}`;
|
const label=`diagonal ${v} straight ${sv} offset ${ox},${oy}`;
|
||||||
fresh();
|
fresh();run(`placeBlock(BP_SCREW,${v},20,20,true);placeBlock(BP_SCREW,${sv},${nx},${ny},true);`);
|
||||||
run(`placeBlock(BP_SCREW,${v},20,20,true);placeBlock(BP_SCREW,${sv},${nx},${ny},true);`);
|
const kind=joined?(oy===0?(ox>0?2:4):ox===0?(oy<0?1:3):vertical?6:5):0;
|
||||||
const kind=joined?(oy===0?(side>0?2:4):vertical?6:5):0;
|
check(`${label}: drawing agrees with travel joins`,run(`(()=>{const j=diagonalScrewJoins(20,20,${v});return pieceJoins(20,20)===${joined?1<<end:0}&&${joined?`(j>>${end*3}&7)===${kind}`:'(j&192)===192'};})()`));
|
||||||
check(`${label}: drawing agrees with endJoin`,run(`(()=>{const j=diagonalScrewJoins(20,20,${v}),e=endJoin(20,20,${end});return (e>=0)===${joined} && (j>>${end*3}&7)===${kind} && !!(j&${64<<end})===${!joined};})()`));
|
check(`${label}: complete ghost plan matches placed joins`,run(`(()=>{
|
||||||
check(`${label}: planned drawing agrees`,run(`(()=>{const j=diagonalScrewJoins(20,20,${v}),sj=straightScrewJoins(${nx},${ny},${sv});removeBlock(${nx},${ny});const plan=new Map([[${ny}*BW+${nx},[BP_SCREW,${sv}]]]);return diagonalScrewJoins(20,20,${v},plan)===j && straightScrewJoins(${nx},${ny},${sv},plan)===sj;})()`));
|
const j=diagonalScrewJoins(20,20,${v}),sj=straightScrewJoins(${nx},${ny},${sv});removeBlock(20,20);removeBlock(${nx},${ny});
|
||||||
check(`${label}: a complete ghost plan matches placed joins`,run(`(()=>{placeBlock(BP_SCREW,${sv},${nx},${ny},true);const j=diagonalScrewJoins(20,20,${v}),sj=straightScrewJoins(${nx},${ny},${sv});removeBlock(20,20);removeBlock(${nx},${ny});const plan=new Map([[20*BW+20,[BP_SCREW,${v}]],[${ny}*BW+${nx},[BP_SCREW,${sv}]]]);return diagonalScrewJoins(20,20,${v},plan)===j && straightScrewJoins(${nx},${ny},${sv},plan)===sj;})()`));
|
const p=new Map([[20*BW+20,[BP_SCREW,${v}]],[${ny}*BW+${nx},[BP_SCREW,${sv}]]]);
|
||||||
|
return diagonalScrewJoins(20,20,${v},p)===j&&straightScrewJoins(${nx},${ny},${sv},p)===sj;
|
||||||
|
})()`));
|
||||||
for(const forward of [true,false]) {
|
for(const forward of [true,false]) {
|
||||||
fresh();
|
fresh();run(`placeBlock(BP_SCREW,${v},20,20,true);placeBlock(BP_SCREW,${sv},${nx},${ny},true);rebuildPower();`);
|
||||||
const dv=(side>0)===(v<6)?v:(v<6?v+2:v-2), straight=forward?sv:(sv+2)%4, diag=forward?dv:(dv<6?dv+2:dv-2);
|
const sx=forward?20:nx,sy=forward?20:ny,tx=forward?nx:20,ty=forward?ny:20,passes=forward?out:incoming;
|
||||||
const sx=forward?20:nx,sy=forward?20:ny,tx=forward?nx:20,ty=forward?ny:20;
|
|
||||||
run(`placeBlock(BP_SCREW,${diag},20,20,true);placeBlock(BP_SCREW,${straight},${nx},${ny},true);rebuildPower();`);
|
|
||||||
rect(run,sx*4,sy*4,sx*4+3,sy*4+3,run('SAND'));
|
rect(run,sx*4,sy*4,sx*4+3,sy*4+3,run('SAND'));
|
||||||
const crossed=run(`(()=>{let n=0;for(let k=0;k<40;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})n++;if(x!==${sx}||y!==${sy})setCell(i,EMPTY,20,0);}flyers=[];}return n;})()`);
|
const crossed=run(`(()=>{let n=0;for(let k=0;k<40;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})n++;if(x!==${sx}||y!==${sy})setCell(i,EMPTY,20,0);}flyers=[];}return n;})()`);
|
||||||
check(`${label}: grains ${forward?'out':'in'} ${joined?'cross':'stay separate'}`,joined?crossed===16:crossed===0,crossed);
|
check(`${label}: grains ${forward?'out':'in'} ${passes?'cross':'stay separate'}`,passes?crossed===16:crossed===0,crossed);
|
||||||
{
|
run('for(let i=0;i<N;i++)if(mat[i]===SAND)setCell(i,EMPTY,20,0);');putBar(sx*4,sy*4+1);
|
||||||
run('for(let i=0;i<N;i++)if(mat[i]===SAND)setCell(i,EMPTY,20,0);');
|
|
||||||
putBar(sx*4,sy*4+1);
|
|
||||||
const bar=run(`(()=>{const e=ents[0];for(let k=0;k<24;k++){tick+=8;entMark(e,0);entScrewTick(e,entScrewAt(e.ix,e.iy));entMark(e,1);if((e.ix>>2)===${tx}&&(e.iy>>2)===${ty})return true;if(!entScrewAt(e.ix,e.iy))break;}return false;})()`);
|
const bar=run(`(()=>{const e=ents[0];for(let k=0;k<24;k++){tick+=8;entMark(e,0);entScrewTick(e,entScrewAt(e.ix,e.iy));entMark(e,1);if((e.ix>>2)===${tx}&&(e.iy>>2)===${ty})return true;if(!entScrewAt(e.ix,e.iy))break;}return false;})()`);
|
||||||
check(`${label}: bar ${forward?'out':'in'} ${joined?'crosses':'stays separate'}`,bar===joined);
|
check(`${label}: bar ${forward?'out':'in'} ${passes?'crosses':'stays separate'}`,bar===passes);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fresh();
|
fresh();
|
||||||
@@ -86,21 +82,40 @@ rect(run,80,80,83,83,run('SAND'));
|
|||||||
check('a horizontal screw still feeds the side of a vertical lift',run(`(()=>{for(let k=0;k<24;k++){tick+=2;tickStamp++;machinesTick();for(let y=80;y<84;y++)for(let x=84;x<88;x++)if(mat[y*W+x]===SAND)return true;}return false;})()`));
|
check('a horizontal screw still feeds the side of a vertical lift',run(`(()=>{for(let k=0;k<24;k++){tick+=2;tickStamp++;machinesTick();for(let y=80;y<84;y++)for(let x=84;x<88;x++)if(mat[y*W+x]===SAND)return true;}return false;})()`));
|
||||||
// The shared opposite-direction check preserves all eight direction combinations.
|
// The shared opposite-direction check preserves all eight direction combinations.
|
||||||
check('screw entry rejects exactly the opposite direction',run('SCREW_PARTS.every(p=>SCREW_DIRS.every(([dx,dy])=>screwAccepts(p,dx,dy)===(SCREW_DX[p]!==-dx||SCREW_DY[p]!==-dy)))'));
|
check('screw entry rejects exactly the opposite direction',run('SCREW_PARTS.every(p=>SCREW_DIRS.every(([dx,dy])=>screwAccepts(p,dx,dy)===(SCREW_DX[p]!==-dx||SCREW_DY[p]!==-dy)))'));
|
||||||
// Flat belts and filters receive both high same-row and low E discharges on their top face.
|
// Flat belts and filters receive diagonal outlets in E, on their carrying face.
|
||||||
for (const v of [4,5,6,7]) for (const side of [-1,1]) for (const code of ['BP_BELT','BP_FILTER']) {
|
for (const v of [4,5,6,7]) for (const side of [-1,1]) for (const code of ['BP_BELT','BP_FILTER']) {
|
||||||
fresh();
|
fresh();
|
||||||
const slope=v===4||v===6?-1:1,dy=side*slope,nx=20+side,ny=20+(dy<0?0:dy);
|
const slope=v===4||v===6?-1:1,dy=side*slope,nx=20+side,ny=20+dy;
|
||||||
const dv=(side>0)===(v<6)?v:(v<6?v+2:v-2),end=side>0?1:0,kind=dy<0?(end?2:4):5;
|
const dv=(side>0)===(v<6)?v:(v<6?v+2:v-2),end=side>0?1:0,kind=5;
|
||||||
run(`placeBlock(BP_SCREW,${dv},20,20,true);placeBlock(${code},${side>0?1:0},${nx},${ny},true);rebuildPower();`);
|
run(`placeBlock(BP_SCREW,${dv},20,20,true);placeBlock(${code},${code==='BP_FILTER'?(side>0?128:0):(side>0?1:0)},${nx},${ny},true);rebuildPower();`);
|
||||||
check(`${code} at diagonal ${v} end ${end} draws the shared join`,run(`(diagonalScrewJoins(20,20,${dv})>>${end*3}&7)===${kind}`));
|
check(`${code} at diagonal ${v} end ${end} draws the shared join`,run(`(diagonalScrewJoins(20,20,${dv})>>${end*3}&7)===${kind}`));
|
||||||
check(`${code} at diagonal ${v} end ${end} receives every grain lane above its face`,run(`(()=>{for(let l=0;l<4;l++){const i=(80+l)*W+${side>0?83:80};if(diagonalScrewNext(i,SCREW_PARTS[${dv}])!==${ny*4-1}*W+${side>0?84:79})return false;}return true;})()`));
|
check(`${code} at diagonal ${v} end ${end} receives every grain lane above its face`,run(`(()=>{for(let l=0;l<4;l++){const i=(80+l)*W+${side>0?83:80};if(diagonalScrewNext(i,SCREW_PARTS[${dv}])!==${ny*4-1}*W+${side>0?84:79})return false;}return true;})()`));
|
||||||
putBar(80,81);
|
putBar(80,81);
|
||||||
check(`${code} at diagonal ${v} end ${end} receives a bar on its face`,run(`(()=>{const e=ents[0];tick=8;entMark(e,0);entDiagonalScrewTick(e,SCREW_PARTS[${dv}]);entMark(e,1);return e.ix===${nx*4}&&e.iy===${ny*4-2};})()`));
|
check(`${code} at diagonal ${v} end ${end} receives a bar on its face`,run(`(()=>{const e=ents[0];tick=8;entMark(e,0);entDiagonalScrewTick(e,SCREW_PARTS[${dv}]);entMark(e,1);return e.ix===${nx*4}&&e.iy===${ny*4-2};})()`));
|
||||||
}
|
}
|
||||||
// A target's travel direction never changes which geometric corner receives a lane.
|
// Flat carriers enter every diagonal at its same-row inlet; blocked mouths must wait safely.
|
||||||
|
for(const v of [4,5,6,7])for(const filter of [false,true]) {
|
||||||
|
fresh();const dx=run(`SCREW_DIRS[${v}][0]`),nx=20+dx;
|
||||||
|
run(`placeBlock(${filter?'BP_FILTER':'BP_BELT'},${filter?(dx>0?128:0):(dx>0?1:0)},20,20,true);placeBlock(BP_SCREW,${v},${nx},20,true);rebuildPower();`);
|
||||||
|
putBar(80,78);
|
||||||
|
run(`setCell(80*W+${nx*4},SAND,20,0);`);
|
||||||
|
check(`${filter?'filter':'belt'} → diagonal ${v}: a blocked joined inlet holds the bar`,run(`(()=>{
|
||||||
|
const e=ents[0];entMark(e,0);tick=8;entCurM=e.m;const held=entBeltScrewEnter(e);entMark(e,1);
|
||||||
|
return held&&e.ix===80&&e.iy===78;
|
||||||
|
})()`));
|
||||||
|
run(`setCell(80*W+${nx*4},EMPTY,20,0);`);
|
||||||
|
check(`${filter?'filter':'belt'} → diagonal ${v}: clearing the inlet resumes the drop`,run(`(()=>{
|
||||||
|
const e=ents[0];entMark(e,0);const moved=entBeltScrewEnter(e);entMark(e,1);
|
||||||
|
return moved&&e.ix===${nx*4}&&e.iy===80&&!!entScrewAt(e.ix,e.iy);
|
||||||
|
})()`));
|
||||||
|
fresh();run(`placeBlock(${filter?'BP_FILTER':'BP_BELT'},${filter?(dx>0?128:0):(dx>0?1:0)},20,20,true);placeBlock(BP_SCREW,${v<6?v+2:v-2},${nx},20,true);rebuildPower();`);
|
||||||
|
putBar(80,78);
|
||||||
|
check(`${filter?'filter':'belt'} → diagonal ${v}: incompatible travel never snaps a bar`,run('(()=>{const e=ents[0];entMark(e,0);const entered=entBeltScrewEnter(e);entMark(e,1);return !entered&&e.ix===80&&e.iy===78;})()'));
|
||||||
|
}
|
||||||
|
// The removed straight-to-diagonal corner path stays open.
|
||||||
fresh();
|
fresh();
|
||||||
run('placeBlock(BP_SCREW,1,20,20,true);placeBlock(BP_SCREW,6,21,19,true);');
|
run('placeBlock(BP_SCREW,1,20,20,true);placeBlock(BP_SCREW,6,21,19,true);');
|
||||||
check('a straight mouth uses the joined corner even when the diagonal points back',run('straightScrewNext(82*W+83,P_SCREW_R)===78*W+84'));
|
check('a straight mouth never takes the removed corner shortcut',run('straightScrewNext(82*W+83,P_SCREW_R)===82*W+84'));
|
||||||
// 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 = [];
|
||||||
for (const v of [0, 4, 5]) {
|
for (const v of [0, 4, 5]) {
|
||||||
@@ -125,9 +140,9 @@ console.log('Saturated throughput, ten blocks, grains/s: straight/up-diagonal/do
|
|||||||
check('diagonals match straight throughput per block',rates.every(n=>n===120),rates);
|
check('diagonals match straight throughput per block',rates.every(n=>n===120),rates);
|
||||||
// A belt's last carrying cell feeds the tail side of an upward diagonal screw.
|
// A belt's last carrying cell feeds the tail side of an upward diagonal screw.
|
||||||
const belt=line(4);
|
const belt=line(4);
|
||||||
run('for(let bx=10;bx<16;bx++)placeBlock(BP_BELT,1,bx,33,true);');
|
run('for(let bx=10;bx<16;bx++)placeBlock(BP_BELT,1,bx,32,true);');
|
||||||
rect(run,39,100,39,131,run('STONE'));
|
rect(run,39,100,39,127,run('STONE'));
|
||||||
rect(run,40,120,55,131,run('SAND'));
|
rect(run,40,116,55,127,run('SAND'));
|
||||||
const beltOut=run(`(()=>{let out=0;for(let k=0;k<1000;k++){simTick();for(let f=flyers.length-1;f>=0;f--)if(flyers[f].m===SAND && flyers[f].x>=104){out++;flyers.splice(f,1);}}return out;})()`);
|
const beltOut=run(`(()=>{let out=0;for(let k=0;k<1000;k++){simTick();for(let f=flyers.length-1;f>=0;f--)if(flyers[f].m===SAND && flyers[f].x>=104){out++;flyers.splice(f,1);}}return out;})()`);
|
||||||
check('a belt feeds sand into a diagonal tail and out its head',beltOut>150,beltOut);
|
check('a belt feeds sand into a diagonal tail and out its head',beltOut>150,beltOut);
|
||||||
for(const v of [4,5,6,7]) {
|
for(const v of [4,5,6,7]) {
|
||||||
@@ -157,9 +172,9 @@ for(const v of [4,5,6,7]) for(const chain of [false,true]) {
|
|||||||
const r=watchBars(s);
|
const r=watchBars(s);
|
||||||
check(`bars travel diagonal ${v}${chain?' and both straight joins':''} without overlap`,r.out===2 && !r.overlap,JSON.stringify(r));
|
check(`bars travel diagonal ${v}${chain?' and both straight joins':''} without overlap`,r.out===2 && !r.overlap,JSON.stringify(r));
|
||||||
}
|
}
|
||||||
// Vertical joins live in E at both ends, then continue along the lift axis.
|
// A lift feeds a diagonal directly ahead; the diagonal feeds the next lift in E.
|
||||||
for (const v of [4,5,6,7]) {
|
for (const v of [4,5,6,7]) {
|
||||||
const s=line(v), vv=s.dy<0?0:2, tailX=s.bx-s.dx, headX=s.head+s.dx;
|
const s=line(v), vv=s.dy<0?0:2, tailX=s.bx, headX=s.head+s.dx;
|
||||||
run(`for(let k=1;k<=3;k++){placeBlock(BP_SCREW,${vv},${tailX},${s.by}-k*${s.dy},true);placeBlock(BP_SCREW,${vv},${headX},${s.hy}+k*${s.dy},true);}`);
|
run(`for(let k=1;k<=3;k++){placeBlock(BP_SCREW,${vv},${tailX},${s.by}-k*${s.dy},true);placeBlock(BP_SCREW,${vv},${headX},${s.hy}+k*${s.dy},true);}`);
|
||||||
putBar(tailX*4,(s.by-3*s.dy)*4+1);
|
putBar(tailX*4,(s.by-3*s.dy)*4+1);
|
||||||
const r=run(`(()=>{let out=false;for(let k=0;k<500;k++){simTick();if(${s.dy}<0?ents[0].iy+2<=${(s.hy-3)*4}:ents[0].iy>=${(s.hy+4)*4})out=true;}return {out,overlap:mat.some((m,i)=>occ[i] && PH[m]===PH_SOLID)};})()`);
|
const r=run(`(()=>{let out=false;for(let k=0;k<500;k++){simTick();if(${s.dy}<0?ents[0].iy+2<=${(s.hy-3)*4}:ents[0].iy>=${(s.hy+4)*4})out=true;}return {out,overlap:mat.some((m,i)=>occ[i] && PH[m]===PH_SOLID)};})()`);
|
||||||
@@ -171,10 +186,10 @@ for (const v of [4,5,6,7]) {
|
|||||||
}
|
}
|
||||||
for(const v of [4,5]) {
|
for(const v of [4,5]) {
|
||||||
const s=line(v);
|
const s=line(v);
|
||||||
run('for(let bx=10;bx<16;bx++)placeBlock(BP_BELT,1,bx,'+(s.by+1)+',true);spawnBar(BARCU,44,'+(s.by*4+2)+',0,0);');
|
run('for(let bx=10;bx<16;bx++)placeBlock(BP_BELT,1,bx,'+s.by+',true);spawnBar(BARCU,44,'+(s.by*4-2)+',0,0);');
|
||||||
check(`a belt feeds a bar into diagonal ${v}`,watchBars(s).out===1);
|
check(`a belt feeds a bar into diagonal ${v}`,watchBars(s).out===1);
|
||||||
// An ascending screw discharges above the receiving belt; a descending one lands on its top.
|
// Both diagonal outlet grades discharge onto the joined belt in E.
|
||||||
const b=line(v), beltY=v===4?b.hy:b.hy+1;
|
const b=line(v), beltY=b.hy+b.dy;
|
||||||
run(`for(let k=1;k<=8;k++)placeBlock(BP_BELT,1,${b.head}+k,${beltY},true);`);
|
run(`for(let k=1;k<=8;k++)placeBlock(BP_BELT,1,${b.head}+k,${beltY},true);`);
|
||||||
rect(run,b.bx*4,b.by*4,b.bx*4+3,b.by*4+3,run('SAND'));
|
rect(run,b.bx*4,b.by*4,b.bx*4+3,b.by*4+3,run('SAND'));
|
||||||
const onBelt=run(`(()=>{let seen=false;for(let k=0;k<300;k++){simTick();for(let x=${(b.head+2)*4};x<${(b.head+8)*4};x++)if(mat[${beltY*4-1}*W+x]===SAND)seen=true;}return seen;})()`);
|
const onBelt=run(`(()=>{let seen=false;for(let k=0;k<300;k++){simTick();for(let x=${(b.head+2)*4};x<${(b.head+8)*4};x++)if(mat[${beltY*4-1}*W+x]===SAND)seen=true;}return seen;})()`);
|
||||||
|
|||||||
+98
-76
@@ -7,29 +7,26 @@ run(`
|
|||||||
var joinCases = [];
|
var joinCases = [];
|
||||||
for (let v = 0; v < 8; v++) joinCases.push([BP_SCREW, v]);
|
for (let v = 0; v < 8; v++) joinCases.push([BP_SCREW, v]);
|
||||||
for (const c of [BP_BELT, BP_FILTER, BP_BELT_UP, BP_BELT_DOWN, BP_FILTER_UP, BP_FILTER_DOWN])
|
for (const c of [BP_BELT, BP_FILTER, BP_BELT_UP, BP_BELT_DOWN, BP_FILTER_UP, BP_FILTER_DOWN])
|
||||||
for (const v of c === BP_FILTER || isFilter(c) ? [0, 128, 255] : [0, 1]) joinCases.push([c, v]);
|
for (const v of c === BP_FILTER || isFilter(c) ? [0, 127, 128, 255] : [0, 1]) joinCases.push([c, v]);
|
||||||
`);
|
`);
|
||||||
const cases = run('joinCases');
|
const cases = run('joinCases');
|
||||||
const screw = run('BP_SCREW');
|
const screw = run('BP_SCREW');
|
||||||
const dirs = run('SCREW_DIRS');
|
const dirs = run('SCREW_DIRS');
|
||||||
function describe([code, v]) {
|
function describe([code, v]) {
|
||||||
if (code === screw) {
|
if(code===screw)return {family:'screw',x:dirs[v][0],y:dirs[v][1],diagonal:!!(dirs[v][0]&&dirs[v][1])};
|
||||||
const [x, y] = dirs[v];
|
const x=run(`isFilter(${code})`)?(v&128?1:-1):(v&1?1:-1),y=run(`beltGrade(${code})`)*x;
|
||||||
return { family: 'screw', diagonal: !!(x && y), x: x ? 1 : 0, y: x ? (x * y) : 1 };
|
return {family:'belt',x,y,diagonal:!!y};
|
||||||
}
|
}
|
||||||
const grade = run(`beltGrade(${code})`);
|
function directed(a,b,dx,dy) {
|
||||||
return { family: 'belt', diagonal: !!grade, x: 1, y: grade };
|
if(a.x*b.x+a.y*b.y<=0)return false;
|
||||||
|
if(a.family!==b.family&&((!a.x||!b.x)||(a.diagonal&&b.diagonal)))return false;
|
||||||
|
if(a.diagonal&&b.diagonal&&(a.x!==b.x||a.y!==b.y))return false;
|
||||||
|
return dx===a.x&&(dy===(a.family==='belt'&&a.y<0?0:a.y)||
|
||||||
|
(a.family==='belt'&&b.family==='belt'&&!a.y&&b.y<0&&dy===-1));
|
||||||
}
|
}
|
||||||
function oracle(a,b,dx,dy,ae,be) {
|
function oracle(a,b,dx,dy,ae,be) {
|
||||||
const ax = a.x * (ae ? 1 : -1), ay = a.y * (ae ? 1 : -1);
|
const ao=(a.x||a.y)>0?1:0,bo=(b.x||b.y)>0?1:0;
|
||||||
const bx = b.x * (be ? 1 : -1), by = b.y * (be ? 1 : -1);
|
return ae===ao&&be!==bo?directed(a,b,dx,dy):ae!==ao&&be===bo&&directed(b,a,-dx,-dy);
|
||||||
if (a.family !== b.family && ((!a.x || !b.x) || (a.diagonal && b.diagonal))) return false;
|
|
||||||
if (a.diagonal && b.diagonal)
|
|
||||||
return a.family === b.family && ax === -bx && ay === -by && dx === ax && dy === ay;
|
|
||||||
if (a.diagonal)
|
|
||||||
return (b.x ? bx === -ax : by === -ay) && dx === ax && dy === (b.x && ay < 0 ? 0 : ay);
|
|
||||||
if (b.diagonal) return oracle(b, a, -dx, -dy, be, ae);
|
|
||||||
return ax === -bx && ay === -by && dx === ax && dy === ay;
|
|
||||||
}
|
}
|
||||||
let tableErrors = [], symmetryErrors = [], pairCount = 0;
|
let tableErrors = [], symmetryErrors = [], pairCount = 0;
|
||||||
for (const ac of cases) for (const bc of cases) {
|
for (const ac of cases) for (const bc of cases) {
|
||||||
@@ -66,22 +63,22 @@ function pair(label, a, b, dx, dy, expected) {
|
|||||||
})()`) === expected);
|
})()`) === expected);
|
||||||
}
|
}
|
||||||
const ids = run('[BP_SCREW,BP_BELT,BP_FILTER,BP_BELT_UP,BP_BELT_DOWN]');
|
const ids = run('[BP_SCREW,BP_BELT,BP_FILTER,BP_BELT_UP,BP_BELT_DOWN]');
|
||||||
pair('downhill low end meets a horizontal in E', [screw,5], [ids[1],0], 1,1,true);
|
for(const [label,av,bv,dx,dy,yes] of [
|
||||||
pair('downhill low end leaves a same-row horizontal separate', [screw,5], [ids[1],0], 1,0,false);
|
['H(right) → DR next same row',1,5,1,0,true],['DR → H(right) diagonally past',5,1,1,1,true],
|
||||||
pair('uphill high end meets a same-row horizontal', [screw,4], [ids[2],128], 1,0,true);
|
['H(right) → UR next same row',1,4,1,0,true],['UR → H(right) diagonally past',4,1,1,-1,true],
|
||||||
pair('uphill high end leaves a horizontal in E separate', [screw,4], [ids[2],128], 1,-1,false);
|
['V(up) → UR directly above',0,4,0,-1,true],['V(up) → UL directly above',0,7,0,-1,true],
|
||||||
for (const side of [-1,1]) {
|
['UR → V(up) diagonally past',4,0,1,-1,true],['DR → V(down) diagonally past',5,2,1,1,true],
|
||||||
pair(`vertical screw meets diagonal in E at end ${side}`, [screw,4], [screw,0], side,-side,true);
|
['DR → H same row stays free',5,1,1,0,false],['H → DR one right and lower stays free',1,5,1,1,false],
|
||||||
pair(`vertical screw directly above or below stays separate at end ${side}`, [screw,4], [screw,0], 0,side,false);
|
['V(up) → UR diagonally above-right stays free',0,4,1,-1,false]
|
||||||
}
|
])pair(label,[screw,av],[screw,bv],dx,dy,yes);
|
||||||
pair('opposite travel direction continues the same diagonal slope', [screw,4], [screw,6], 1,-1,true);
|
pair('uphill belt outlet stays on its row',[ids[3],1],[ids[1],1],1,0,true);
|
||||||
pair('different diagonal slopes stay separate', [screw,4], [screw,5], 1,-1,false);
|
pair('uphill slope outlet also targets a slope on the same row',[ids[3],1],[ids[3],1],1,0,true);
|
||||||
pair('different diagonal families stay separate', [screw,4], [ids[3],0], 1,-1,false);
|
pair('uphill slope leaves a diagonally adjacent slope free',[ids[3],1],[ids[3],1],1,-1,false);
|
||||||
pair('horizontal screw meets a sloped belt', [ids[4],0], [screw,3], 1,1,true);
|
pair('flat belt feeds uphill low end on same row',[ids[1],1],[ids[3],1],1,0,true);
|
||||||
pair('vertical screw never meets a flat belt', [ids[1],0], [screw,0], 1,0,false);
|
pair('flat belt also feeds uphill low end from below',[ids[1],1],[ids[3],1],1,-1,true);
|
||||||
check('planned source, neighbour, direction and empty entries override the world', run(`(() => {
|
check('planned source, neighbour, direction and empty entries override the world', run(`(() => {
|
||||||
blockP.fill(0); blockV.fill(0);
|
blockP.fill(0); blockV.fill(0);
|
||||||
const a=10*BW+10,b=a+1, p=new Map([[a,[BP_BELT,0]],[b,[BP_SCREW,3,'extra']]]);
|
const a=10*BW+10,b=a+1, p=new Map([[a,[BP_BELT,1]],[b,[BP_SCREW,1,'extra']]]);
|
||||||
blockP[a]=BP_WALL; blockP[b]=BP_SCREW; blockV[b]=0;
|
blockP[a]=BP_WALL; blockP[b]=BP_SCREW; blockV[b]=0;
|
||||||
if(endJoin(10,10,1,p)!==b*2 || endJoin(11,10,0,p)!==a*2+1) return false;
|
if(endJoin(10,10,1,p)!==b*2 || endJoin(11,10,0,p)!==a*2+1) return false;
|
||||||
p.set(b,[0,0]);
|
p.set(b,[0,0]);
|
||||||
@@ -93,56 +90,81 @@ check('non-transport pieces, invalid ends and world edges are open without row w
|
|||||||
endJoin(0,-1,0)===-1 && endJoin(0,BH,0)===-1 && endJoin(0,0,0)===-1 &&
|
endJoin(0,-1,0)===-1 && endJoin(0,BH,0)===-1 && endJoin(0,0,0)===-1 &&
|
||||||
endJoin(BW-1,BH-1,1)===-1 && endJoin(1,1,2)===-1;
|
endJoin(BW-1,BH-1,1)===-1 && endJoin(1,1,2)===-1;
|
||||||
})()`));
|
})()`));
|
||||||
// Drive the existing column carrier across a single boundary in both directions. No gravity,
|
check('same-shape inlet continuation beats an elbow',run(`(()=>{
|
||||||
// power or cleat timing can disguise a wrong face height as a successful handoff.
|
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||||
for (const grade of [-1,1]) for (const side of [-1,1]) for (const joined of [true,false]) for (const reverse of [false,true]) {
|
for(const [n,v] of [[b,4],[b+BW-1,4],[b-1,1]]){blockP[n]=BP_SCREW;blockV[n]=v;}
|
||||||
flatWorld(run,96,96,92);
|
return endJoin(10,10,0)===(b+BW-1)*2+1&&endJoin(9,10,1)===-1;
|
||||||
const result = run(`(() => {
|
|
||||||
const c=${grade<0?'BP_BELT_UP':'BP_BELT_DOWN'}, side=${side}, low=side*${grade}>0;
|
|
||||||
const fy=10+(${joined} ? Number(low) : (low ? 0 : -1));
|
|
||||||
placeBlock(c,1,10,10,true); placeBlock(BP_BELT,1,10+side,fy,true);
|
|
||||||
const sx=side>0?43:40, fx=sx+side, sy=beltFaceY(c,10,10,sx), ty=fy*BS;
|
|
||||||
const x=${reverse}?fx:sx, y=${reverse}?ty:sy, d=${reverse}?-side:side;
|
|
||||||
const dest=(${reverse}?sy-1:ty-1)*W+(${reverse}?sx:fx);
|
|
||||||
setCell((y-1)*W+x,SAND,20,0); tickStamp++;
|
|
||||||
const step=conveyorStep(x,y,d);
|
|
||||||
carryColumn((y-1)*W+x,d,null);
|
|
||||||
return [endJoin(10,10,side>0?1:0)>=0,mat[dest]===SAND,step];
|
|
||||||
})()`);
|
|
||||||
check(`grade ${grade} ${side<0?'left':'right'} ${joined?'joined':'separate'} ${reverse?'flat to slope':'slope to flat'} sand handoff matches geometry`,result[0]===joined && result[1]===joined,JSON.stringify(result));
|
|
||||||
}
|
|
||||||
// Render adapters keep their established bit layouts and honour plans without world writes.
|
|
||||||
const fs = require('fs');
|
|
||||||
run(fs.readFileSync(require('path').join(__dirname, '../src/render/slope-belts.js'), 'utf8'));
|
|
||||||
const setup = fs.readFileSync(require('path').join(__dirname, '../src/render/setup.js'), 'utf8');
|
|
||||||
run(setup.slice(setup.indexOf('function beltLike('), setup.indexOf('\n', setup.indexOf('function beltLike('))));
|
|
||||||
check('filter frames follow slope ends and horizontal screw joins', run(`(() => {
|
|
||||||
blockP.fill(0); blockV.fill(0);
|
|
||||||
const b=10*BW+10;
|
|
||||||
blockP[b]=BP_FILTER; blockP[b+1]=BP_BELT_UP;
|
|
||||||
if(beltLike(b,1)) return false;
|
|
||||||
blockP[b+1]=BP_BELT_DOWN;
|
|
||||||
if(!beltLike(b,1)) return false;
|
|
||||||
blockP[b+1]=BP_SCREW; blockV[b+1]=1;
|
|
||||||
return beltLike(b,1);
|
|
||||||
})()`));
|
})()`));
|
||||||
check('planned flat caps and slope flat/run bits agree with the shared ends', run(`(() => {
|
check('equal-shape inlet ties use top to bottom then left to right',run(`(()=>{
|
||||||
blockP.fill(0); blockV.fill(0);
|
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||||
const b=10*BW+10,p=new Map([[b,[BP_BELT_DOWN,0]],[b-1,[BP_FILTER,128]],[b+BW+1,[BP_FILTER_DOWN,128]]]);
|
for(const [n,v] of [[b,1],[b-BW-1,5],[b+BW-1,4]]){blockP[n]=BP_SCREW;blockV[n]=v;}
|
||||||
|
return endJoin(10,10,0)===(b-BW-1)*2+1&&endJoin(9,11,1)===-1;
|
||||||
|
})()`));
|
||||||
|
const fs=require('fs');
|
||||||
|
run(fs.readFileSync('src/render/slope-belts.js','utf8'));
|
||||||
|
const setup=fs.readFileSync('src/render/setup.js','utf8');
|
||||||
|
run(setup.slice(setup.indexOf('function beltLike('),setup.indexOf('\n',setup.indexOf('function beltLike('))));
|
||||||
|
check('filter frames open at an uphill low-end feed and compatible screws',run(`(()=>{
|
||||||
|
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||||
|
blockP[b]=BP_FILTER;blockV[b]=255;blockP[b+1]=BP_BELT_UP;blockV[b+1]=1;
|
||||||
|
if(!beltLike(b,1))return false;
|
||||||
|
blockP[b+1]=BP_SCREW;blockV[b+1]=1;if(!beltLike(b,1))return false;
|
||||||
|
blockV[b+1]=3;return !beltLike(b,1);
|
||||||
|
})()`));
|
||||||
|
check('planned flat caps and slope continuations use the shared ends',run(`(()=>{
|
||||||
|
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||||
|
const p=new Map([[b,[BP_BELT_DOWN,1]],[b-1,[BP_FILTER,255]],[b+BW+1,[BP_FILTER_DOWN,128]]]);
|
||||||
const before=JSON.stringify([Array.from(blockP),Array.from(blockV)]);
|
const before=JSON.stringify([Array.from(blockP),Array.from(blockV)]);
|
||||||
if(slopeBeltConnections(10,10,BP_BELT_DOWN,p)!==9||flatBeltCaps(b-1,p)!==1)return false;
|
if(slopeBeltConnections(10,10,BP_BELT_DOWN,p)!==9||flatBeltCaps(b-1,p)!==1)return false;
|
||||||
p.set(b,[0,0]);
|
p.set(b,[0,0]);return flatBeltCaps(b-1,p)===3&&before===JSON.stringify([Array.from(blockP),Array.from(blockV)]);
|
||||||
return flatBeltCaps(b-1,p)===3 && before===JSON.stringify([Array.from(blockP),Array.from(blockV)]);
|
|
||||||
})()`));
|
})()`));
|
||||||
check('competing diagonal and flat neighbours keep every returned join reciprocal', run(`(() => {
|
check('a flat belt picks the same-row uphill inlet before the below-low-end exception',run(`(()=>{
|
||||||
blockP.fill(0); blockV.fill(0);
|
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||||
const b=10*BW+10;
|
for(const n of [b,b+1,b-BW+1]){blockP[n]=n===b?BP_BELT:BP_BELT_UP;blockV[n]=1;}
|
||||||
blockP[b]=BP_BELT_DOWN; blockP[b+BW+1]=BP_BELT_DOWN; blockP[b+BW+2]=BP_FILTER;
|
return endJoin(10,10,1)===(b+1)*2&&endJoin(11,9,0)===-1;
|
||||||
blockP[b-1]=BP_FILTER; blockP[b-BW-1]=BP_BELT_DOWN;
|
})()`));
|
||||||
for(let y=8;y<14;y++) for(let x=8;x<14;x++) for(let e=0;e<2;e++) {
|
check('same-row uphill low end draws the drop in placed and planned geometry',run(`(()=>{
|
||||||
const j=endJoin(x,y,e);
|
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||||
if(j>=0 && endJoin((j>>1)%BW,((j>>1)/BW)|0,j&1)!==(y*BW+x)*2+e) return false;
|
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;
|
||||||
|
})()`));
|
||||||
|
check('uphill same-row slope joins draw a high elbow and a low drop',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;
|
||||||
|
})()`));
|
||||||
|
// 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]) {
|
||||||
|
flatWorld(run,96,96,92);
|
||||||
|
const d=grade,c=filter?(grade<0?'BP_FILTER_UP':'BP_FILTER_DOWN'):(grade<0?'BP_BELT_UP':'BP_BELT_DOWN');
|
||||||
|
const r=run(`(()=>{
|
||||||
|
const d=${d},c=${c},out=${outgoing},bx=10,by=10;
|
||||||
|
placeBlock(c,${filter?(d>0?128:0):(d>0?1:0)},bx,by,true);
|
||||||
|
const fx=bx+(out?d:-d),fy=by+(out?1:0)+(${joined}?0:out?-1:1);
|
||||||
|
placeBlock(BP_BELT,d>0?1:0,fx,fy,true);
|
||||||
|
const sx=bx*BS+(d>0?3:0),ix=bx*BS+(d>0?0:3),x=out?sx:ix-d;
|
||||||
|
const y=out?beltFaceY(c,bx,by,sx):fy*BS;
|
||||||
|
const targetY=out?fy*BS:beltFaceY(c,bx,by,ix);
|
||||||
|
setCell((y-1)*W+x,SAND,20,0);tickStamp++;
|
||||||
|
carryColumn((y-1)*W+x,d,null);
|
||||||
|
return [endJoin(bx,by,out?(d>0?1:0):(d>0?0:1))>=0,mat[(targetY-1)*W+x+d]===SAND];
|
||||||
|
})()`);
|
||||||
|
check(`grade ${grade} ${filter?'screen':'belt'} ${outgoing?'outlet':'inlet'} ${joined?'joined':'free'} sand handoff agrees`,r[0]===joined&&r[1]===joined,r);
|
||||||
|
}
|
||||||
|
// The same-row low-end feed drops onto the face before the uphill cleats carry it.
|
||||||
|
for(const grade of [-1,1])for(const below of [false,true]) {
|
||||||
|
flatWorld(run,160,128,120);
|
||||||
|
const d=-grade,code=grade<0?'BP_BELT_UP':'BP_BELT_DOWN';
|
||||||
|
const r=run(`(()=>{
|
||||||
|
const d=${d},c=${code},v=${d>0?1:0},bx=20,by=20,fy=by+${below?1:0};
|
||||||
|
placeBlock(BP_BELT,d>0?1:0,bx-d,fy,true);
|
||||||
|
for(let k=0;k<4;k++)placeBlock(c,v,bx+k*d,by-k,true);
|
||||||
|
rebuildPower();const source=fy*BS-1,x=(bx-d)*BS+(d>0?3:0);
|
||||||
|
setCell(source*W+x,SAND,20,0);
|
||||||
|
let seen=false;
|
||||||
|
for(let k=0;k<320;k++) {simTick();for(let i=0;i<N;i++)if(mat[i]===SAND&&((i/W|0)<by*BS-4))seen=true;}
|
||||||
|
return [endJoin(bx-d,fy,d>0?1:0)>=0,seen];
|
||||||
|
})()`);
|
||||||
|
check(`grade ${grade} belt low end accepts sand from ${below?'below':'same row'} and carries it up`,r[0]&&r[1],r);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
})()`));
|
|
||||||
done();
|
done();
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ for (const code of ['BP_BELT_UP', 'BP_BELT_DOWN', 'BP_FILTER_UP', 'BP_FILTER_DOW
|
|||||||
run(`
|
run(`
|
||||||
mat.fill(EMPTY); part.fill(0); blockP.fill(0); blockV.fill(0);
|
mat.fill(EMPTY); part.fill(0); blockP.fill(0); blockV.fill(0);
|
||||||
var artCode = ${code}, artGrade = beltGrade(artCode), artB = 12 * BW + 12;
|
var artCode = ${code}, artGrade = beltGrade(artCode), artB = 12 * BW + 12;
|
||||||
for (let k = -2; k <= 2; k++) placeBlock(artCode, 1, 12 + k, 12 + artGrade * k, true);
|
for (let k = -2; k <= 2; k++) placeBlock(artCode, artGrade > 0 ? 1 : 0, 12 + k, 12 + artGrade * k, true);
|
||||||
prepareSlopeBelts(0, 0, W, H);
|
prepareSlopeBelts(0, 0, W, H);
|
||||||
`);
|
`);
|
||||||
check(`${code}: diagonal neighbours continue the band`, run('(slopeBeltJoins[artB] & 12) === 12'));
|
check(`${code}: downhill diagonal neighbours continue the band`, run('(slopeBeltJoins[artB] & 12) === 12'));
|
||||||
check(`${code}: corner seals use the same band`, run(`(() => {
|
check(`${code}: corner seals use the same band`, run(`(() => {
|
||||||
const i = slopeSeal(artCode, 12, 12);
|
const i = slopeSeal(artCode, 12, 12);
|
||||||
return i < 0 || slopeBeltAt(i, i % W, (i / W) | 0) >= 0;
|
return i < 0 || slopeBeltAt(i, i % W, (i / W) | 0) >= 0;
|
||||||
@@ -35,7 +35,7 @@ for (const code of ['BP_BELT_UP', 'BP_BELT_DOWN', 'BP_FILTER_UP', 'BP_FILTER_DOW
|
|||||||
for (let end = 0; end < 2; end++) {
|
for (let end = 0; end < 2; end++) {
|
||||||
const side = end ? 1 : -1;
|
const side = end ? 1 : -1;
|
||||||
removeBlock(12 + side, 12 + side * artGrade, true);
|
removeBlock(12 + side, 12 + side * artGrade, true);
|
||||||
placeBlock(BP_BELT, 1, 12 + side, 12 + (end ? artGrade > 0 : artGrade < 0), true);
|
placeBlock(BP_BELT, artGrade > 0 ? 1 : 0, 12 + side, 12 + (end ? artGrade > 0 : artGrade < 0), true);
|
||||||
}
|
}
|
||||||
prepareSlopeBelts(0, 0, W, H);
|
prepareSlopeBelts(0, 0, W, H);
|
||||||
`);
|
`);
|
||||||
|
|||||||
+37
-2
@@ -97,7 +97,7 @@ 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)
|
||||||
# Shared joins: E elbows bend inside the straight block; other placements keep collars.
|
# Travel joins: edge elbows in diagonals, E elbows in straight receivers, otherwise collars.
|
||||||
pg.set_viewport_size({"width": 2100, "height": 1500})
|
pg.set_viewport_size({"width": 2100, "height": 1500})
|
||||||
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/joins.js").read_text() + "}")
|
ev("() => {" + (root / "tools/scenes/joins.js").read_text() + "}")
|
||||||
@@ -153,7 +153,7 @@ with sync_playwright() as p:
|
|||||||
const bad=[], now=performance.now.bind(performance),frozen=now();
|
const bad=[], now=performance.now.bind(performance),frozen=now();
|
||||||
performance.now=()=>frozen;
|
performance.now=()=>frozen;
|
||||||
try {
|
try {
|
||||||
for(const look of ['pixel','smooth'])for(const j of JOIN_CASES.filter(j=>j.code===BP_SCREW && j.spot===2 && j.end===1 && (j.v===5&&!j.vertical || j.v===4&&j.vertical))) {
|
for(const look of ['pixel','smooth'])for(const j of JOIN_CASES.filter(j=>j.code===BP_SCREW && j.joined && j.kind>=5 && (j.v===5&&!j.vertical || j.v===4&&j.vertical))) {
|
||||||
SET.look=look;
|
SET.look=look;
|
||||||
const side=j.end?1:-1,slope=j.v===4||j.v===6?-1:1;
|
const side=j.end?1:-1,slope=j.v===4||j.v===6?-1:1;
|
||||||
const ux=2+(j.vertical?side:-side)*1.8,uy=2+(j.vertical?-side*slope:side*slope)*1.8;
|
const ux=2+(j.vertical?side:-side)*1.8,uy=2+(j.vertical?-side*slope:side*slope)*1.8;
|
||||||
@@ -196,6 +196,41 @@ with sync_playwright() as p:
|
|||||||
} finally {performance.now=now;}
|
} finally {performance.now=now;}
|
||||||
}''')
|
}''')
|
||||||
check('smooth: trees behind a straight screw cannot become elbow join bits', not tree_tags, tree_tags)
|
check('smooth: trees behind a straight screw cannot become elbow join bits', not tree_tags, tree_tags)
|
||||||
|
drops = ev("""() => {
|
||||||
|
const detail=slopeDetailAt,now=performance.now.bind(performance),frozen=now(),out={};
|
||||||
|
const j=JOIN_CASES.find(j=>j.v===4&&j.end===0&&j.code===BP_BELT&&j.spot===0);
|
||||||
|
try {
|
||||||
|
slopeDetailAt=()=>0;performance.now=()=>frozen;
|
||||||
|
const sample=()=>{const p=worldToScreenPx(j.bx*BS+0.4,j.by*BS+2.5);return Array.from(R.ctx.getImageData(Math.floor(p.x),Math.floor(p.y),1,1).data);};
|
||||||
|
for(const look of ['pixel','smooth']) {
|
||||||
|
SET.look=look;SM.scale=1;drawWorld();const joined=sample();
|
||||||
|
removeBlock(j.nx,j.ny);drawWorld();const free=sample();
|
||||||
|
placeBlock(BP_BELT,j.straight,j.nx,j.ny,true);
|
||||||
|
out[look]=[joined,free];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
} 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])
|
||||||
|
filter_plans = ev("""() => {
|
||||||
|
const saved={...GAME},original=slopeBeltConnections,seen=[];
|
||||||
|
try {
|
||||||
|
GAME.tool=TOOLS.findIndex(t=>t.id==='build');GAME.pieceIdx=PIECES.findIndex(p=>p.id==='filter');
|
||||||
|
GAME.selDrag=null;GAME.paste=false;GAME.filterCfg={m:[],inv:0};GAME.pieceDir={filter:1};
|
||||||
|
slopeBeltConnections=(x,y,c,p)=>{
|
||||||
|
if(p)seen.push([c,p.get(y*BW+x)?.[1]]);
|
||||||
|
return original(x,y,c,p);
|
||||||
|
};
|
||||||
|
for(const grade of [-1,1]) {
|
||||||
|
GAME.drag={bx:50,by:30};GAME.aim={x:52*BS,y:(30+2*grade)*BS};
|
||||||
|
drawPreviews();
|
||||||
|
}
|
||||||
|
return seen;
|
||||||
|
} finally {slopeBeltConnections=original;Object.assign(GAME,saved);}
|
||||||
|
}""")
|
||||||
|
check('filter drag previews pass stored travel bits to the join rule',
|
||||||
|
bool(filter_plans) and all(v == (128 if c == ev('BP_FILTER_DOWN') else 0) for c, v in filter_plans), filter_plans)
|
||||||
# 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,12 +1,12 @@
|
|||||||
// Shared join grid: eight rows (UR/DR/DL/UL, left/right ends), nine columns.
|
// Travel join grid: UR/DR/DL/UL, both ends, and all three neighbouring placements.
|
||||||
// Screw H and V, then belt H; each tries the side, above/below, and E.
|
// Screw H and V, then belt H; each tries the side, above/below, and E.
|
||||||
// Self-contained, paused and empty. Use --zoom 4 --viewport 2100x1500 --no-light.
|
// Self-contained, paused and empty. Use --zoom 4 --viewport 2100x1500 --no-light.
|
||||||
money = 50000;
|
money = 50000;
|
||||||
for (let y = 40; y < 420; y++) for (let x = 100; x < 560; x++) {
|
for (let y = 0; y < 460; y++) for (let x = 0; x < 600; x++) {
|
||||||
const i = y * W + x;
|
const i = y * W + x;
|
||||||
setCell(i, EMPTY, 20, 0); bgm[i] = 0;
|
setCell(i, EMPTY, 20, 0); bgm[i] = 0;
|
||||||
}
|
}
|
||||||
for (let x = 100; x < 560; x++) surfY[x] = 420;
|
for (let x = 0; x < 600; x++) surfY[x] = 420;
|
||||||
window.JOIN_CASES = [];
|
window.JOIN_CASES = [];
|
||||||
for (let row = 0; row < 8; row++) for (let col = 0; col < 9; col++) {
|
for (let row = 0; row < 8; row++) for (let col = 0; col < 9; col++) {
|
||||||
const v = 4 + (row >> 1), end = row & 1, side = end ? 1 : -1;
|
const v = 4 + (row >> 1), end = row & 1, side = end ? 1 : -1;
|
||||||
@@ -15,13 +15,14 @@ for (let row = 0; row < 8; row++) for (let col = 0; col < 9; col++) {
|
|||||||
const bx = 34 + col * 12, by = 24 + row * 10;
|
const bx = 34 + col * 12, by = 24 + row * 10;
|
||||||
const nx = bx + (spot === 1 ? 0 : side), ny = by + (spot === 0 ? 0 : dy);
|
const nx = bx + (spot === 1 ? 0 : side), ny = by + (spot === 0 ? 0 : dy);
|
||||||
const code = belt ? BP_BELT : BP_SCREW;
|
const code = belt ? BP_BELT : BP_SCREW;
|
||||||
const straight = belt ? (side > 0 ? 1 : 0) : vertical ? (dy < 0 ? 0 : 2) : (side > 0 ? 1 : 3);
|
const [tx, ty] = SCREW_DIRS[v];
|
||||||
|
const straight = belt ? (tx > 0 ? 1 : 0) : vertical ? (ty < 0 ? 0 : 2) : (tx > 0 ? 1 : 3);
|
||||||
const diagonalCode = belt ? (slope < 0 ? BP_BELT_UP : BP_BELT_DOWN) : BP_SCREW;
|
const diagonalCode = belt ? (slope < 0 ? BP_BELT_UP : BP_BELT_DOWN) : BP_SCREW;
|
||||||
const diagonalV = belt ? (SCREW_DIRS[v][0] > 0 ? 1 : 0) : v;
|
const diagonalV = belt ? (SCREW_DIRS[v][0] > 0 ? 1 : 0) : v;
|
||||||
for (let k = 0; k < 2; k++) placeBlock(diagonalCode, diagonalV, bx - k * side, by - k * dy, true);
|
for (let k = 0; k < 2; k++) placeBlock(diagonalCode, diagonalV, bx - k * side, by - k * dy, true);
|
||||||
for (let k = 0; k < 2; k++) placeBlock(code, straight, nx + (vertical ? 0 : k * side * (spot === 1 ? -1 : 1)), ny + (vertical ? k * dy * (spot === 0 ? -1 : 1) : 0), true);
|
for (let k = 0; k < 2; k++) placeBlock(code, straight, nx + (vertical ? 0 : k * side * (spot === 1 ? -1 : 1)), ny + (vertical ? k * dy * (spot === 0 ? -1 : 1) : 0), true);
|
||||||
const joined = endJoin(bx, by, end) >= 0;
|
const joined = endJoin(bx, by, end) >= 0;
|
||||||
const kind = joined ? (ny === by ? (end ? 2 : 4) : vertical ? 6 : 5) : 0;
|
const kind = joined ? (ny === by ? (end ? 2 : 4) : nx === bx ? (ny < by ? 1 : 3) : vertical ? 6 : 5) : 0;
|
||||||
JOIN_CASES.push({v, end, bx, by, nx, ny, vertical, code, straight, joined, kind, spot});
|
JOIN_CASES.push({v, end, bx, by, nx, ny, vertical, code, straight, joined, kind, spot});
|
||||||
}
|
}
|
||||||
updateRoof();
|
updateRoof();
|
||||||
@@ -34,7 +35,7 @@ drawBot = () => {
|
|||||||
const p = worldToScreenPx(j.bx * BS - 8, j.by * BS - 14);
|
const p = worldToScreenPx(j.bx * BS - 8, j.by * BS - 14);
|
||||||
ctx.fillStyle = '#17222c';
|
ctx.fillStyle = '#17222c';
|
||||||
const name = ['UR', 'DR', 'DL', 'UL'][j.v - 4], axis = j.code === BP_BELT ? 'belt' : j.vertical ? 'V' : 'H';
|
const name = ['UR', 'DR', 'DL', 'UL'][j.v - 4], axis = j.code === BP_BELT ? 'belt' : j.vertical ? 'V' : 'H';
|
||||||
ctx.fillText(`${name} ${j.end ? 'R' : 'L'} ${axis} ${['side', 'up/down', 'E'][j.spot]}: ${j.joined ? 'JOIN' : 'free'}`, p.x, p.y);
|
ctx.fillText(`${name} ${['↗','↘','↙','↖'][j.v-4]} ${j.end ? 'R' : 'L'} ${axis} ${j.vertical ? (SCREW_DIRS[j.v][1]<0?'↑':'↓') : (SCREW_DIRS[j.v][0]>0?'→':'←')} ${['row', 'axis', 'diag'][j.spot]}: ${j.joined ? 'JOIN' : 'free'}`, p.x, p.y);
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user