clone

an endless marble-race generator in the browser; the url is the seed, so every track can be shared

main
marbles / tests / supports_test.ts
supports_test.ts 2.9K
1/**2 * Pillar vocabulary (composition law) — pure, no wasm. The measured3 * table + containment rule live in src/gen/supports.ts; these pin the4 * contracts: stacks always legal (containment upward), heights exact,5 * feet at contact, deterministic, and genuinely varied.6 */7import { assert, assertEquals } from "@std/assert";8import type { Placement } from "../src/shared/types.ts";9import {10 emitSupportStack,11 mayStandOn,12 type SupportKind,13} from "../src/gen/supports.ts";1415const kindOf = (p: Placement): SupportKind =>16 p.pieceId.split("-")[1] as SupportKind;17const segOf = (p: Placement) => p.pieceId.split("-")[2];1819function stack(20 height: number,21 cradle: boolean,22 x = 3,23 z = 7,24 bias: SupportKind = "two",25 baseAt = 0,26): Placement[] {27 const out: Placement[] = [];28 emitSupportStack(out, "support", bias, x, z, height, 0, cradle, baseAt);29 return out;30}3132Deno.test("containment law: single never stands on two; four carries all", () => {33 assert(mayStandOn("single", "four"));34 assert(mayStandOn("two", "four"));35 assert(!mayStandOn("single", "two"));36 assert(!mayStandOn("four", "two"));37 assert(!mayStandOn("four", "single"));38 // Sweep many stacks: every emitted neighbor pair obeys the law.39 for (let i = 0; i < 200; i++) {40 const h = 0.5 + (i % 8) * 0.5;41 const ps = stack(h, i % 2 === 0, i * 1.5, -i * 2.5).sort(42 (a, b) => a.pos[1] - b.pos[1],43 );44 for (let j = 1; j < ps.length; j++) {45 if (segOf(ps[j]) === "top") continue; // cradle arms overhang by design46 assert(47 mayStandOn(kindOf(ps[j]), kindOf(ps[j - 1])),48 `illegal ${ps[j].pieceId} on ${ps[j - 1].pieceId} (stack ${i})`,49 );50 }51 }52});5354Deno.test("heights exact: cradle at channel base, plain flush, feet at contact", () => {55 for (const h of [0.5, 1, 1.5, 2, 3.5]) {56 const c = stack(h, true, 11, 13, "four", 2);57 const top = c.find((p) => segOf(p) === "top")!;58 assertEquals(top.pos[1], 2 + h - 0.5); // cradle lands at base + h59 const foot = c.reduce((a, b) => (a.pos[1] < b.pos[1] ? a : b));60 assertEquals(foot.pos[1], 2);61 assertEquals(segOf(foot), h === 0.5 ? "top" : "bottom");62 const plain = stack(h, false, 11, 13, "four", 2);63 const highest = plain.reduce((a, b) => (a.pos[1] > b.pos[1] ? a : b));64 assertEquals(highest.pos[1] + 0.5, 2 + h); // flush at channel underside65 }66});6768Deno.test("deterministic per position; varied across positions", () => {69 assertEquals(stack(3, true), stack(3, true));70 const kinds = new Set<string>();71 let tapered = 0;72 for (let i = 0; i < 60; i++) {73 const ps = stack(3, true, i * 2, 40 - i * 3);74 const shaftKinds = new Set(75 ps.filter((p) => segOf(p) !== "top").map(kindOf),76 );77 for (const k of shaftKinds) kinds.add(k);78 if (shaftKinds.size > 1) tapered++;79 }80 assertEquals(kinds.size, 3, "all three kinds should appear across stacks");81 assert(tapered >= 5, `tapered towers should appear (got ${tapered})`);82});