glytch/fktry/src/sim/geom.ts
LANE-SIM fcf5d04727 [sim] deterministic factory core: belts, crafting, bandwidth, shipping
Replaces the stub with the real sim per lanes/LANE-SIM.md round 1.

- grid + placement/remove/rotate/setRecipe, footprint rotation, bounds
- belts: 2 items/tile, 0.45 spacing enforced across the tile seam,
  back-pressure, merges, belt<->machine handoff
- crafting: recipe buffers (2x in, stall at 4x out), edge-triggered jams
- bandwidth v1: gen vs draw, compression generates, brownout = gen/draw
  scaling both craft progress and belt movement
- shipping + commission tracking
- output routing ranks belts by tracing the chain to its terminal machine,
  so the demuxer's three outputs reach three different consumers without
  splitters (see NOTES - biggest judgement call of the round)

18 tests. Measured 3,500 entities + 6,820 belt items at 0.42ms/tick (80x
realtime). Full M1 chain ships MELT at tick 1149 in both node and browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:25:22 +10:00

27 lines
879 B
TypeScript

/** Grid math. Origin at world center, +x east, +y south, Dir 0..3 = N,E,S,W clockwise. */
import type { Dir, Vec2 } from '../contracts';
export const DIR_VEC: ReadonlyArray<Vec2> = [
{ x: 0, y: -1 }, // 0 N
{ x: 1, y: 0 }, // 1 E
{ x: 0, y: 1 }, // 2 S
{ x: -1, y: 0 }, // 3 W
];
export const GRID_MIN = -32;
export const GRID_MAX = 31;
/** Packs a tile into a unique number so occupancy can live in a flat Map. */
export function tileKey(x: number, y: number): number {
return (x + 32768) * 65536 + (y + 32768);
}
export function inBounds(x: number, y: number): boolean {
return x >= GRID_MIN && x <= GRID_MAX && y >= GRID_MIN && y <= GRID_MAX;
}
/** Footprint after rotation — facing E or W swaps width and height. */
export function footprintOf(fp: Vec2, dir: Dir): Vec2 {
return dir === 1 || dir === 3 ? { x: fp.y, y: fp.x } : { x: fp.x, y: fp.y };
}