Pieces render true to pattern: dice = chunk grid, wedges = exploded sectors

Dice show the criss-cross (drawn 1.6x true size — honest proportions
vanish against the board at game camera distance); wedges explode 0.28
from center so gaps and uneven angles read. Slices keep the fan. Plus
scripts/deploy.sh (partly.party/toastsim, cratewars pattern) — pending
an ssh permission grant to run the final docker cp step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-18 15:13:21 +10:00
parent 03e3d046fc
commit 8f5641a57f
2 changed files with 86 additions and 12 deletions

30
scripts/deploy.sh Normal file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env bash
# TOASTSIM — deploy to partly.party/toastsim (games VPS, forum-nginx pattern).
# ./scripts/deploy.sh
# Build with subpath base → rsync to VPS staging → docker cp into web root → curl verify.
set -euo pipefail
VPS="humanjing@100.71.119.27"
CONTAINER="forum-nginx"
REMOTE_WEB_ROOT="/usr/share/nginx/html/toastsim"
STAGING="/tmp/toastsim_deploy"
REPO="$(cd "$(dirname "$0")/.." && pwd)"
echo "[1/4] Building (base=/toastsim/)..."
cd "$REPO"
./node_modules/.bin/tsc --noEmit
./node_modules/.bin/vite build --base=/toastsim/
echo "[2/4] Rsync dist → $VPS:$STAGING ..."
ssh "$VPS" "rm -rf '$STAGING' && mkdir -p '$STAGING'"
rsync -az dist/ "$VPS:$STAGING/"
echo "[3/4] Swap into $CONTAINER:$REMOTE_WEB_ROOT ..."
ssh "$VPS" "docker exec '$CONTAINER' rm -rf '$REMOTE_WEB_ROOT' \
&& docker cp '$STAGING' '$CONTAINER:$REMOTE_WEB_ROOT'"
echo "[4/4] Verify..."
code=$(curl -so /dev/null -w '%{http_code}' "https://partly.party/toastsim/?cb=$(date +%s)")
echo "https://partly.party/toastsim/ → HTTP $code"
[ "$code" = 200 ] || { echo "NOT DEPLOYED (non-200)"; exit 1; }
echo "done."

View File

@ -429,18 +429,62 @@ export class PrepView implements View {
color: new THREE.Color().setRGB(...this.ing.colors.flesh, THREE.SRGBColorSpace),
roughness: 0.5,
});
const edges = [-0.5, ...[...s.cuts].sort((a, b) => a - b), 0.5];
let x = -this.ing.size * 0.55 - edges.length * 0.02;
for (let i = 0; i + 1 < edges.length; i++) {
const w = (edges[i + 1] - edges[i]) * this.ing.size;
const mid = (edges[i] + edges[i + 1]) / 2;
const rHere = Math.sqrt(Math.max(0.05, 0.25 - mid * mid)) * this.ing.size;
const piece = new THREE.Mesh(new THREE.CylinderGeometry(rHere, rHere, Math.max(0.02, w * 0.92), 20), flesh);
piece.rotation.z = Math.PI / 2;
piece.castShadow = true;
piece.position.set(x + w / 2, BOARD_Y * 2 + rHere, 0.1);
this.pieces.add(piece);
x += w + 0.05;
if (s.pattern.kind === 'dice') {
// A real grid of chunks: pass-A cuts × pass-B cuts, jittered like a board.
// Drawn ~1.6x true size and spread wide — honest proportions, readable
// at game camera distance (true-scale cubes vanish against the board).
const CHUNK = 1.6;
const ea = [-0.5, ...[...s.diceCutsA].sort((a, b) => a - b), 0.5];
const eb = [-0.5, ...[...s.cuts].sort((a, b) => a - b), 0.5];
for (let i = 0; i + 1 < ea.length; i++) {
for (let j = 0; j + 1 < eb.length; j++) {
const wa = (ea[i + 1] - ea[i]) * this.ing.size * CHUNK;
const wb = (eb[j + 1] - eb[j]) * this.ing.size * CHUNK;
const h = Math.min(wa, wb, this.ing.size * 0.55);
const piece = new THREE.Mesh(new THREE.BoxGeometry(wa * 0.85, h, wb * 0.85), flesh);
piece.castShadow = true;
piece.position.set(
((ea[i] + ea[i + 1]) / 2) * this.ing.size * CHUNK * 1.1 + (this.rng.next() - 0.5) * 0.06,
BOARD_Y * 2 + h / 2,
((eb[j] + eb[j + 1]) / 2) * this.ing.size * CHUNK * 1.1 + (this.rng.next() - 0.5) * 0.06,
);
piece.rotation.y = (this.rng.next() - 0.5) * 0.4;
this.pieces.add(piece);
}
}
} else if (s.pattern.kind === 'wedges') {
// Diametral cuts: each angle makes two wedges. Sectors exploded slightly.
const angs = [...s.cuts].sort((a, b) => a - b).flatMap((c) => [c * Math.PI, c * Math.PI + Math.PI]);
const r = this.ing.size * 0.5;
const h = this.ing.size * 0.35;
for (let i = 0; i < angs.length; i++) {
const a0 = angs[i];
const a1 = i + 1 < angs.length ? angs[i + 1] : angs[0] + Math.PI * 2;
const mid = (a0 + a1) / 2;
const piece = new THREE.Mesh(
new THREE.CylinderGeometry(r, r, h, 16, 1, false, a0, a1 - a0),
flesh,
);
piece.castShadow = true;
// Exploded well apart so the gaps (and any uneven angles) read.
piece.position.set(Math.cos(mid) * 0.28, BOARD_Y * 2 + h / 2, -Math.sin(mid) * 0.28);
piece.rotation.y = (this.rng.next() - 0.5) * 0.15;
this.pieces.add(piece);
}
} else {
const edges = [-0.5, ...[...s.cuts].sort((a, b) => a - b), 0.5];
let x = -this.ing.size * 0.55 - edges.length * 0.02;
for (let i = 0; i + 1 < edges.length; i++) {
const w = (edges[i + 1] - edges[i]) * this.ing.size;
const mid = (edges[i] + edges[i + 1]) / 2;
const rHere = Math.sqrt(Math.max(0.05, 0.25 - mid * mid)) * this.ing.size;
const piece = new THREE.Mesh(new THREE.CylinderGeometry(rHere, rHere, Math.max(0.02, w * 0.92), 20), flesh);
piece.rotation.z = Math.PI / 2;
piece.castShadow = true;
piece.position.set(x + w / 2, BOARD_Y * 2 + rHere, 0.1);
this.pieces.add(piece);
x += w + 0.05;
}
}
this.root.add(this.pieces);
}