Shift-drag draws a line for belts and other transport pieces

Belts, filters, screw conveyors, shafts and pipe pieces never fill a rectangle;
a diagonal belt drag stays sloped with Shift held.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 23:27:06 -04:00
co-authored by Claude Opus 5.5
parent 664e086969
commit 18528a7f73
10 changed files with 36 additions and 12 deletions
+4
View File
@@ -477,6 +477,10 @@ above, including:
chose to make stickiness grain against grain only: on anything built, every material slides chose to make stickiness grain against grain only: on anything built, every material slides
freely, so slopes are useful for every material, while heaps keep their shape. Real chutes clog freely, so slopes are useful for every material, while heaps keep their shape. Real chutes clog
with sticky material and need cleaning, but that is micromanagement the game doesn't need. with sticky material and need cleaning, but that is micromanagement the game doesn't need.
- **Belts and other transport pieces never fill a rectangle (2026-10-27).** The owner said Shift
doesn't need to fill a rectangle for belts, conveyors and the like. Belts, filters, screw
conveyors, shafts, pipes, pumps and outlets (`lineOnly()`) always draw a line, Shift or not, and
a diagonal belt drag stays sloped. Walls, grates and gates still Shift-fill.
## Ideas not built yet ## Ideas not built yet
+7 -7
View File
@@ -205,9 +205,9 @@ Material definitions: ids, properties and metal families. ids, and drill/tank ti
Material tables: typed properties, casting groups, flags, parts and tool tiers. Typed property tables Material tables: typed properties, casting groups, flags, parts and tool tiers. Typed property tables
- variables: PH, DENS, KC, ICAP, CASTMETAL, METAL_TIER, SOLIDMETAL, F_NAT, F_CAST, F_BURN, P_NONE, P_SCREW_U, P_SCREW_D, P_SCREW_L, P_SCREW_R, P_GRATE, P_GOPEN, P_WHEEL, P_INTAKE, P_FILTER, P_PIPE, P_SCREW_UR, P_SCREW_DR, P_SCREW_DL, P_SCREW_UL, SCREW_PARTS, SCREW_DIRS, IS_SCREW, SCREW_DX, SCREW_DY, BIT_TIERS, TANK_TIERS, REACH - variables: PH, DENS, KC, ICAP, CASTMETAL, METAL_TIER, SOLIDMETAL, F_NAT, F_CAST, F_BURN, P_NONE, P_SCREW_U, P_SCREW_D, P_SCREW_L, P_SCREW_R, P_GRATE, P_GOPEN, P_WHEEL, P_INTAKE, P_FILTER, P_PIPE, P_SCREW_UR, P_SCREW_DR, P_SCREW_DL, P_SCREW_UL, SCREW_PARTS, SCREW_DIRS, IS_SCREW, SCREW_DX, SCREW_DY, BIT_TIERS, TANK_TIERS, REACH
### src/sim/pieces.js (177 lines) ### src/sim/pieces.js (179 lines)
Everything the player can build (PIECES): blocks, machines and molds, with costs and descriptions; filter settings; wall materials and their cost. Everything the player can build (PIECES): blocks, machines and molds, with costs and descriptions; filter settings; wall materials and their cost.
- functions: isSlopeBelt, isFilter, isBelt, beltGrade, slopeShape, beltFaceY, filterVariant, beltDirection, isSlope, isWall, slopeCell, slopeCode, isPipeCode, filterable, cleanFilterCfg, filterRec, filterPassAt, filterInfo, sameFilter, wallCost - functions: isSlopeBelt, isFilter, isBelt, beltGrade, slopeShape, beltFaceY, filterVariant, beltDirection, isSlope, isWall, slopeCell, slopeCode, isPipeCode, lineOnly, filterable, cleanFilterCfg, filterRec, filterPassAt, filterInfo, sameFilter, wallCost
- variables: BS, BP_WALL, BP_BELT, BP_SCREW, BP_SHAFT, BP_GRATE, BP_GATE, BP_FILTER, BP_PIPE, BP_PUMP, BP_OUTLET, BP_GRIND, BP_SAW, BP_PRESS, BP_RACK, BP_SLOPE_BL, BP_SLOPE_BR, BP_SLOPE_TL, BP_SLOPE_TR, BP_BELT_UP, BP_BELT_DOWN, BP_FILTER_UP, BP_FILTER_DOWN, filterCfg, WALL_OF, WALL_LIST, BAR_OF, GRAINS_PER_BAR, POWER_FREE, CASH, PIECES, PIECE_BY_ID, PIECE_BY_CODE, BELT_POWER, SCREW_POWER, FAN_POWER, WHEEL_K, ENGINE_K, MOLDS - variables: BS, BP_WALL, BP_BELT, BP_SCREW, BP_SHAFT, BP_GRATE, BP_GATE, BP_FILTER, BP_PIPE, BP_PUMP, BP_OUTLET, BP_GRIND, BP_SAW, BP_PRESS, BP_RACK, BP_SLOPE_BL, BP_SLOPE_BR, BP_SLOPE_TL, BP_SLOPE_TR, BP_BELT_UP, BP_BELT_DOWN, BP_FILTER_UP, BP_FILTER_DOWN, filterCfg, WALL_OF, WALL_LIST, BAR_OF, GRAINS_PER_BAR, POWER_FREE, CASH, PIECES, PIECE_BY_ID, PIECE_BY_CODE, BELT_POWER, SCREW_POWER, FAN_POWER, WHEEL_K, ENGINE_K, MOLDS
### src/sim/economy.js (152 lines) ### src/sim/economy.js (152 lines)
@@ -230,7 +230,7 @@ Exploration only: never changes grains or the random stream.
- functions: fogAlloc, fogMark, fogSeen, fogInit, fogLook, fogRevealAround, fogRevealAll - functions: fogAlloc, fogMark, fogSeen, fogInit, fogLook, fogRevealAround, fogRevealAll
- variables: FOG, FOG_SENSE, FOG_SENSE_FADE, fog, FW, FH - variables: FOG, FOG_SENSE, FOG_SENSE_FADE, fog, FW, FH
### src/sim/cells.js (223 lines) ### src/sim/cells.js (225 lines)
Cell helpers (setCell, move, enter) and the falling-sand movement of powders, liquids and gases (simMove). Cell helpers (setCell, move, enter) and the falling-sand movement of powders, liquids and gases (simMove).
- functions: setCell, swapCells, move, enter, enterFree, powder, passable, liquid, gas, wakeBotPowder, simMove - functions: setCell, swapCells, move, enter, enterFree, powder, passable, liquid, gas, wakeBotPowder, simMove
- variables: SORTP, SORTP_FUEL, LSTEP, LFILM, CORNER_SEAL - variables: SORTP, SORTP_FUEL, LSTEP, LFILM, CORNER_SEAL
@@ -274,9 +274,9 @@ Cleats are rubber ribs standing up from a sloped belt's face, one every CLEAT_SP
- functions: ribMove, cleatFoot, ribCells, slopeUphill, slopeFaceAt, pushRow, scoop, pocketRoom, advanceCleat, slopeRunKey, slopeCleatsTick, rebuildCleatMask, expelFromRib, screenNudge, wakeCleatLoad - functions: ribMove, cleatFoot, ribCells, slopeUphill, slopeFaceAt, pushRow, scoop, pocketRoom, advanceCleat, slopeRunKey, slopeCleatsTick, rebuildCleatMask, expelFromRib, screenNudge, wakeCleatLoad
- variables: CLEAT_SPACING, CLEAT_HEIGHT, CLEAT_REACH, SCREEN_NUDGE_TICKS, cleatChunks, cleatMask, cleatCells, slopePhase, ribLog - variables: CLEAT_SPACING, CLEAT_HEIGHT, CLEAT_REACH, SCREEN_NUDGE_TICKS, cleatChunks, cleatMask, cleatCells, slopePhase, ribLog
### src/sim/blocks.js (219 lines) ### src/sim/blocks.js (235 lines)
Building blocks: placing, integrity, removing, lines and rectangles of blocks. Building blocks: placing, integrity, removing, lines and rectangles of blocks.
- functions: blockOf, blockCellList, pieceCost, setSlopeSeal, slopeSeal, slopeSealOwner, wallCells, canPlaceBlock, placeBlock, blockIntegrity, removeWallCell, removeBlock, diagonalDrag, blockLine, blockRect, blockInReach, blockIs, placeBlocks - functions: blockOf, blockCellList, pieceCost, setSlopeSeal, slopeSeal, slopeSealOwner, blockOwnsCell, clearStraySeals, wallCells, canPlaceBlock, placeBlock, blockIntegrity, removeWallCell, removeBlock, diagonalDrag, blockLine, blockRect, blockInReach, blockIs, placeBlocks
### src/sim/molds.js (317 lines) ### src/sim/molds.js (317 lines)
Molds: cavities for castings; ingot troughs empty themselves below. Removal returns set metal. Molds: cavities for castings; ingot troughs empty themselves below. Removal returns set metal.
@@ -347,7 +347,7 @@ World generation helpers: noise and removal of tiny hard rock specks.
World generation: staged terrain, ore, caves and placement, with synchronous and async entry points. World generation: staged terrain, ore, caves and placement, with synchronous and async entry points.
- functions: generateWorld, generateWorldAsync - functions: generateWorld, generateWorldAsync
### src/sim/save.js (179 lines) ### src/sim/save.js (180 lines)
Saving and loading a world (serializeWorld / deserializeWorld) and old-save migration. Saving and loading a world (serializeWorld / deserializeWorld) and old-save migration.
- functions: serializeWorld, deserializeWorld, startLoadWake, wakeLoadArea, wakeLoadNearBot, loadWakeTick - functions: serializeWorld, deserializeWorld, startLoadWake, wakeLoadArea, wakeLoadNearBot, loadWakeTick
- variables: SAVE_VERSION, TOFF - variables: SAVE_VERSION, TOFF
@@ -449,7 +449,7 @@ The build menu along the bottom: categories, piece tiles and their icons.
- functions: pieceIcon, catOf, selectBuildCat, cycleBuildCat, buildVariantChips, renderBuildBar, setupBuildBar - functions: pieceIcon, catOf, selectBuildCat, cycleBuildCat, buildVariantChips, renderBuildBar, setupBuildBar
- variables: BUILD_CATS, BUILD_ORDER, PIECE_ICONS, lastBuildKey, lastBuildMenu - variables: BUILD_CATS, BUILD_ORDER, PIECE_ICONS, lastBuildKey, lastBuildMenu
### src/ui/selection.js (120 lines) ### src/ui/selection.js (121 lines)
Tool selection: cycling, drill tuning, piece variants and costs. Tool selection: cycling, drill tuning, piece variants and costs.
- functions: pieceSize, pieceUsesWall, setDrillWidth, tuneUnderCursor, cycleItem, cycleWall, rotate, toggleBlow, pickFilterUnderCursor, toggleFilterMat, setBrush, pieceV, pieceExtra, filterBlockAt, chipCost, pieceCostNow, pieceCode, turnSlope - functions: pieceSize, pieceUsesWall, setDrillWidth, tuneUnderCursor, cycleItem, cycleWall, rotate, toggleBlow, pickFilterUnderCursor, toggleFilterMat, setBrush, pieceV, pieceExtra, filterBlockAt, chipCost, pieceCostNow, pieceCode, turnSlope
+11
View File
@@ -4,6 +4,17 @@ 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: Shift-drag draws a line for transport pieces (Claude Code)
- Owner request: Shift shouldn't fill a rectangle for belts, conveyors and similar. New
`lineOnly(code)` in `src/sim/pieces.js` (belts, filters, screw conveyors, shafts, pipe pieces);
`primaryRelease` (`src/ui/actions.js`) and the drag preview (`src/ui/previews.js`) ignore Shift
for them, and `pieceCode` (`src/ui/selection.js`) no longer flattens a diagonal belt drag on
Shift. Build-menu "How to place" and the notebook say so; the notebook's stale "cleats up or
down" line now says uphill only.
- Tests: play.py checks a Shift-drag with a belt lays a 3-piece sloped line. Full
`run_all --browser` passes.
## 2026-10-27: t61 sticky materials slide freely on built surfaces (Claude Code) ## 2026-10-27: t61 sticky materials slide freely on built surfaces (Claude Code)
- The owner's video showed clay lumps creeping down a sloped wall; removing a piece was - The owner's video showed clay lumps creeping down a sloped wall; removing a piece was
+2
View File
@@ -32,6 +32,8 @@ function slopeCode(dx, dy, orientation) {
return dx * dy > 0 ? (orientation >= 2 ? BP_SLOPE_TR : BP_SLOPE_BL) : (orientation >= 2 ? BP_SLOPE_TL : BP_SLOPE_BR); return dx * dy > 0 ? (orientation >= 2 ? BP_SLOPE_TR : BP_SLOPE_BL) : (orientation >= 2 ? BP_SLOPE_TL : BP_SLOPE_BR);
} }
function isPipeCode(c) { return c === BP_PIPE || c === BP_PUMP || c === BP_OUTLET; } function isPipeCode(c) { return c === BP_PIPE || c === BP_PUMP || c === BP_OUTLET; }
// pieces that carry something along a run: a Shift-drag still draws a line, never a filled rectangle
function lineOnly(c) { return isBelt(c) || isFilter(c) || c === BP_SCREW || c === BP_SHAFT || isPipeCode(c); }
// Filters: each filter block has a set of materials and a mode. In drop mode (inv 0) // Filters: each filter block has a set of materials and a mode. In drop mode (inv 0)
// the chosen materials fall through and everything else rides along on top; in carry // the chosen materials fall through and everything else rides along on top; in carry
// mode (inv 1) the chosen ones ride along and everything else falls through. The block // mode (inv 1) the chosen ones ride along and everything else falls through. The block
+1 -1
View File
@@ -167,7 +167,7 @@ function primaryRelease(shift) {
} }
if (t === 'build' && GAME.drag) { if (t === 'build' && GAME.drag) {
const P = piece(), b = aimBlock(), code = pieceCode(P, b); const P = piece(), b = aimBlock(), code = pieceCode(P, b);
const list = shift ? blockRect(GAME.drag.bx, GAME.drag.by, b.bx, b.by) : blockLine(GAME.drag.bx, GAME.drag.by, b.bx, b.by, isWall(code) || isSlopeBelt(code) || code === BP_SCREW); const list = shift && !lineOnly(code) ? blockRect(GAME.drag.bx, GAME.drag.by, b.bx, b.by) : blockLine(GAME.drag.bx, GAME.drag.by, b.bx, b.by, isWall(code) || isSlopeBelt(code) || code === BP_SCREW);
const dd = dragDirFor(P, GAME.drag, b); const dd = dragDirFor(P, GAME.drag, b);
if (dd >= 0) GAME.pieceDir[P.id] = dd; if (dd >= 0) GAME.pieceDir[P.id] = dd;
const v = pieceV(P), ex = pieceExtra(P); const v = pieceV(P), ex = pieceExtra(P);
+1 -1
View File
@@ -87,7 +87,7 @@ function renderBuildBar() {
if (!tip) return; if (!tip) return;
const size = tip.kind === 'machine' ? `${tip.bw}×${tip.bh} blocks` : tip.kind === 'mold' ? `${MOLDS[tip.mold].bw}×${MOLDS[tip.mold].bh} blocks` : '1 block'; const size = tip.kind === 'machine' ? `${tip.bw}×${tip.bh} blocks` : tip.kind === 'mold' ? `${MOLDS[tip.mold].bw}×${MOLDS[tip.mold].bh} blocks` : '1 block';
pop.innerHTML = `<div class="bb-title">${pieceIcon(tip.id)}<b>${esc(tip.name)}</b></div><div class="bb-chips">${chipCost(pieceCostNow(tip))}<span class="chip">${size}</span>${tip.power ? `<span class="chip">${tip.power} power</span>` : ''}</div> pop.innerHTML = `<div class="bb-title">${pieceIcon(tip.id)}<b>${esc(tip.name)}</b></div><div class="bb-chips">${chipCost(pieceCostNow(tip))}<span class="chip">${size}</span>${tip.power ? `<span class="chip">${tip.power} power</span>` : ''}</div>
<div class="bb-desc">${esc(tip.desc)}</div><div class="bb-place"><b>How to place</b> ${tip.kind === 'block' ? 'Drag for a line, Shift-drag for a rectangle; out of reach becomes ghosts.' : 'Click to place.'}${tip.dirs && tip.kind === 'block' ? ' A dragged line runs the way you drag it; a single click uses <kbd>R</kbd>.' : tip.dirs ? ' <kbd>R</kbd> turns it.' : pieceUsesWall(tip) ? ' <kbd>R</kbd> cycles materials; <kbd>F</kbd> opens the material menu.' : ''}</div> <div class="bb-desc">${esc(tip.desc)}</div><div class="bb-place"><b>How to place</b> ${tip.kind === 'block' ? (lineOnly(tip.code) ? 'Drag for a line' : 'Drag for a line, Shift-drag for a rectangle') + '; out of reach becomes ghosts.' : 'Click to place.'}${tip.dirs && tip.kind === 'block' ? ' A dragged line runs the way you drag it; a single click uses <kbd>R</kbd>.' : tip.dirs ? ' <kbd>R</kbd> turns it.' : pieceUsesWall(tip) ? ' <kbd>R</kbd> cycles materials; <kbd>F</kbd> opens the material menu.' : ''}</div>
${tip.filter ? `<div class="bb-place">New ones will ${esc(filterSentence(GAME.filterCfg))}. <kbd>F</kbd> sets materials and mode (or click materials in the bag, or <kbd>T</kbd> over one); <kbd>F</kbd> on a placed filter changes its whole row.</div>` : ''} ${tip.filter ? `<div class="bb-place">New ones will ${esc(filterSentence(GAME.filterCfg))}. <kbd>F</kbd> sets materials and mode (or click materials in the bag, or <kbd>T</kbd> over one); <kbd>F</kbd> on a placed filter changes its whole row.</div>` : ''}
<div class="bb-place">hold <kbd>G</kbd> replace · <kbd>Ctrl</kbd>+<kbd>Z</kbd> undo · right click take back · middle mouse pick · hold <kbd>C</kbd>/<kbd>X</kbd> copy/cut · <kbd>V</kbd> paste</div>`; <div class="bb-place">hold <kbd>G</kbd> replace · <kbd>Ctrl</kbd>+<kbd>Z</kbd> undo · right click take back · middle mouse pick · hold <kbd>C</kbd>/<kbd>X</kbd> copy/cut · <kbd>V</kbd> paste</div>`;
const tile = bar.querySelector(`[data-piece="${tip.id}"]`); if (!tile) { pop.hidden = true; return; } const tile = bar.querySelector(`[data-piece="${tip.id}"]`); if (!tile) { pop.hidden = true; return; }
+1 -1
View File
@@ -71,7 +71,7 @@ function controlsHtml() {
const tools = [ const tools = [
['1 Resonance drill', 'Hold left click to send pulses from the bot toward the cursor: rock they shake hard enough shatters and falls as rubble, ore, coal or scrap, and trees come apart into wood chunks and acorns. Q and E set the pulse width: narrow bores a tunnel fast, wide clears a big cone slowly, medium is in between. R over a material tunes the drill to it, so only that shatters (great for pulling a vein out of the rock); R again untunes. Built blocks are never harmed. Better bits dig harder rock, faster and further.'], ['1 Resonance drill', 'Hold left click to send pulses from the bot toward the cursor: rock they shake hard enough shatters and falls as rubble, ore, coal or scrap, and trees come apart into wood chunks and acorns. Q and E set the pulse width: narrow bores a tunnel fast, wide clears a big cone slowly, medium is in between. R over a material tunes the drill to it, so only that shatters (great for pulling a vein out of the rock); R again untunes. Built blocks are never harmed. Better bits dig harder rock, faster and further.'],
['2 Pneumatic nozzle', "Left drag picks up the selection, or what you click if nothing is selected. Right click blows the selection. Click a bag name or Q/E selects one; Ctrl+click mixes. Hold Ctrl to see everything found. Clear removes the selection. Bars blow alone. Blowing out the last of a material deselects it. Shift while not flying takes everything; Shift + right click blows everything loose in the bag."], ['2 Pneumatic nozzle', "Left drag picks up the selection, or what you click if nothing is selected. Right click blows the selection. Click a bag name or Q/E selects one; Ctrl+click mixes. Hold Ctrl to see everything found. Clear removes the selection. Bars blow alone. Blowing out the last of a material deselects it. Shift while not flying takes everything; Shift + right click blows everything loose in the bag."],
['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, screw conveyors, filters, grates, gates, pipes, pumps, outlets, Grinders, Saws, Brick presses and Bar racks, machines (fans and silos) and molds. Drag for a line, hold Shift while dragging for a filled rectangle. A diagonal wall drag makes a sealed sloped face, solid below; T flips it. For one wall block, T cycles square and four slope corners. Diagonal belts use cleats to carry pockets up or down. Extra grains roll back. Diagonal filters always screen downhill, whichever way you drag. R leaves a picked sloped filter downhill. Right click takes a piece back. Your bot flies through anything you build.'], ['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, screw conveyors, filters, grates, gates, pipes, pumps, outlets, Grinders, Saws, Brick presses and Bar racks, machines (fans and silos) and molds. Drag for a line, hold Shift while dragging for a filled rectangle (belts, screw conveyors, filters, shafts and pipes always draw a line). A diagonal wall drag makes a sealed sloped face, solid below; T flips it. For one wall block, T cycles square and four slope corners. Uphill belts use cleats to carry pockets up; downhill they carry like a flat belt. Extra grains roll back. Diagonal filters always screen downhill, whichever way you drag. R leaves a picked sloped filter downhill. Right click takes a piece back. Your bot flies through anything you build.'],
['4 Igniter', 'Heats a small spot to 640°C, and its flame reaches a few grains into a pile. Enough to light coal and wood that touch air.'], ['4 Igniter', 'Heats a small spot to 640°C, and its flame reaches a few grains into a pile. Enough to light coal and wood that touch air.'],
['Copy, cut and paste', 'Hold C (copy) or X (cut) and drag a box over what you built; let go and it is picked up, ready to paste. Cut takes it down and refunds it to your bag. Click to place ghosts: your bot builds them from your bag when it is close. R flips, V pastes again, right click or Esc stops. Ctrl+Z undoes building. The notebook (B) keeps saved blueprints and a few starter designs.'], ['Copy, cut and paste', 'Hold C (copy) or X (cut) and drag a box over what you built; let go and it is picked up, ready to paste. Cut takes it down and refunds it to your bag. Click to place ghosts: your bot builds them from your bag when it is close. R flips, V pastes again, right click or Esc stops. Ctrl+Z undoes building. The notebook (B) keeps saved blueprints and a few starter designs.'],
]; ];
+1 -1
View File
@@ -105,7 +105,7 @@ function drawPreviews() {
if (P.kind === 'block') { if (P.kind === 'block') {
const v = pieceV(P); const v = pieceV(P);
col = previewColor(code, v); col = previewColor(code, v);
cells = GAME.drag ? (modShift() ? blockRect(GAME.drag.bx, GAME.drag.by, b.bx, b.by) : blockLine(GAME.drag.bx, GAME.drag.by, b.bx, b.by, isWall(code) || isSlopeBelt(code) || code === BP_SCREW)) : [[b.bx, b.by]]; cells = GAME.drag ? (modShift() && !lineOnly(code) ? blockRect(GAME.drag.bx, GAME.drag.by, b.bx, b.by) : blockLine(GAME.drag.bx, GAME.drag.by, b.bx, b.by, isWall(code) || isSlopeBelt(code) || code === BP_SCREW)) : [[b.bx, b.by]];
const dd = GAME.drag ? dragDirFor(P, GAME.drag, b) : -1; const dd = GAME.drag ? dragDirFor(P, GAME.drag, b) : -1;
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);
+2 -1
View File
@@ -99,7 +99,8 @@ function pieceCostNow(P, m = GAME.wallMat) {
function pieceCode(P, end = aimBlock()) { function pieceCode(P, end = aimBlock()) {
if (P.id === 'belt' || P.filter) { if (P.id === 'belt' || P.filter) {
const a = GAME.drag || end; const a = GAME.drag || end;
if (GAME.drag && diagonalDrag(end.bx - a.bx, end.by - a.by) && !(typeof modShift === 'function' && modShift())) // Shift changes nothing here: belts and filters only ever draw a line
if (GAME.drag && diagonalDrag(end.bx - a.bx, end.by - a.by))
return (P.filter ? BP_FILTER_UP : BP_BELT_UP) + ((end.bx - a.bx) * (end.by - a.by) > 0 ? 1 : 0); return (P.filter ? BP_FILTER_UP : BP_BELT_UP) + ((end.bx - a.bx) * (end.by - a.by) > 0 ? 1 : 0);
if (end.bx === a.bx && end.by === a.by) return GAME.pieceShape && GAME.pieceShape[P.id] || P.code; if (end.bx === a.bx && end.by === a.by) return GAME.pieceShape && GAME.pieceShape[P.id] || P.code;
return P.code; return P.code;
+6
View File
@@ -663,6 +663,12 @@ with sync_playwright() as p:
drag(lambda: blk(bbx - 10, bby - 8), lambda: blk(bbx - 8, bby - 6), 4, True) drag(lambda: blk(bbx - 10, bby - 8), lambda: blk(bbx - 8, bby - 6), 4, True)
nb2 = ev('blockP.reduce((a, v) => a + (v === BP_WALL), 0)') nb2 = ev('blockP.reduce((a, v) => a + (v === BP_WALL), 0)')
check('Shift-drag fills a 3×3 rectangle', nb2 - nb1 == 9, nb2 - nb1) check('Shift-drag fills a 3×3 rectangle', nb2 - nb1 == 9, nb2 - nb1)
ev("GAME.pieceIdx = PIECE_BY_ID.belt.index; for (const [m, n] of PIECE_BY_ID.belt.cost) bag[m] += n * 10; hudDirty = true")
drag(lambda: blk(bbx + 3, bby - 10), lambda: blk(bbx + 5, bby - 8), 4, True)
belts = ev('[blockP.reduce((a, v) => a + isBelt(v), 0), blockP.reduce((a, v) => a + isSlopeBelt(v), 0), ghostBlocks.size]')
check('Shift-drag with a belt still lays a sloped line, not a rectangle', belts == [3, 3, 0], f'{belts}, {ev("GAME.status")}')
ev('doUndo()'); settle(30)
ev("GAME.pieceIdx = PIECE_BY_ID.wall.index; hudDirty = true")
r0 = ev('bag[RSTONE]') r0 = ev('bag[RSTONE]')
pg.mouse.move(*blk(bbx + 3, bby - 4)); pg.mouse.down(button='right'); settle(120); pg.mouse.up(button='right'); settle(80) pg.mouse.move(*blk(bbx + 3, bby - 4)); pg.mouse.down(button='right'); settle(120); pg.mouse.up(button='right'); settle(80)
check('right click takes a block back with a refund', ev('bag[RSTONE]') - r0 == 16 and ev('blockP.reduce((a, v) => a + (v === BP_WALL), 0)') == nb2 - 1, ev('bag[RSTONE]') - r0) check('right click takes a block back with a refund', ev('bag[RSTONE]') - r0 == 16 and ev('blockP.reduce((a, v) => a + (v === BP_WALL), 0)') == nb2 - 1, ev('bag[RSTONE]') - r0)