1/**2 * Computed generator version: a fingerprint of what the generator actually3 * produces, not a hand-bumped label. Canonical placements of fixed probe4 * seeds (decor included, against a synthetic catalog that exercises every5 * setting rule pattern) are hashed with cyrb128 — pure 32-bit integer ops,6 * bit-identical across JS engines. Any behavior change anywhere in the7 * pipeline changes the version; refactors that preserve output don't.8 *9 * Shown in the HUD and embedded in inspect-mode debug reports, so a version10 * mismatch between a report and a fresh generate means a stale bundle.11 */12import { generateTrack } from "./generateTrack.ts";13import { cyrb128 } from "./rng.ts";1415const PROBE_SEEDS = [16 "version-probe-a",17 "version-probe-b",18 "version-probe-c",19 "version-probe-d",20];2122/** Fake but pattern-covering catalog: decor logic runs deterministically23 * without needing the real manifest at version-compute time. */24const PROBE_CATALOG = {25 packs: [{26 slug: "nature-kit",27 ids: [28 "tree_a",29 "tree_dead",30 "rock_a",31 "stone_b",32 "grass_a",33 "plant_a",34 "flower_a",35 "mushroom_a",36 "stump_a",37 "log_a",38 "cliff_a",39 "fence_a",40 "crop_a",41 ],42 }],43};4445let cached: string | null = null;4647/** ~40ms on first call (4 full generations), cached afterwards. */48export function genVersion(): string {49 if (cached) return cached;50 const parts: string[] = [];51 for (const seed of PROBE_SEEDS) {52 const t = generateTrack(seed, PROBE_CATALOG);53 for (const p of t.placements) {54 parts.push(55 [56 p.pieceId,57 p.pos.map((v) => Math.round(v * 4)).join(","),58 p.rotation,59 p.mirrored ? 1 : 0,60 p.role,61 p.scale !== undefined ? Math.round(p.scale * 16) : "",62 p.spin ? Math.round(p.spin.radPerSec * 16) : "",63 ].join("|"),64 );65 }66 parts.push(`;${seed}`);67 }68 const [a, b] = cyrb128(parts.join("\n"));69 cached = "g" + (a >>> 0).toString(16).padStart(8, "0") +70 ((b >>> 0).toString(16).padStart(8, "0")).slice(0, 4);71 return cached;72}