// THE SACRED PROPERTY: empty manifest + empty IndexedDB must construct exactly // what the game constructed before the workshop existed. Fingerprints the whole // scene graph the course + zones + a cannon build, so any drift shows up as a // changed hash. Run against main and against the branch; the numbers must match. // // It has to be BUNDLED rather than strip-typed: the game modules use TypeScript // parameter properties, which node's --experimental-strip-types refuses. // // ./node_modules/.bin/esbuild scripts/sacred-parity.check.ts --bundle \ // --platform=node --format=esm --outfile=/tmp/sacred.mjs && node /tmp/sacred.mjs // // Measured on fix-runtime and on main at f827f6f, both: // nodes=49 colliders=15 bodies=3 greyboxMeshes=11 blobRadius=0.500000 // sceneFingerprint=49df4f20 import * as THREE from 'three' import RAPIER from '@dimforge/rapier3d-compat' import { buildGreybox } from '../src/course/greybox.ts' import { installZones } from '../src/course/zones.ts' import { PaintCannon } from '../src/paint/cannon.ts' import { AssetRegistry, setAssets } from '../src/assets/registry.ts' import { createBlob } from '../src/blob/createBlob.ts' setAssets(new AssetRegistry({})) // empty manifest, no IndexedDB await RAPIER.init() const scene = new THREE.Scene() const physics = new RAPIER.World({ x: 0, y: -9.81, z: 0 }) const listeners: any = {} const world: any = { scene, physics, rapier: RAPIER, events: { on: (n: string, f: any) => { (listeners[n] ||= []).push(f) }, emit: () => {} }, addSystem: () => {}, onFrame: () => {}, tick: () => {}, renderOnce: () => {}, start: () => {}, } const gb = buildGreybox(world) const blob = createBlob(world, {}) world.blob = blob const skin: any = { boundingRadius: 0.5, splat: () => {}, splatAtWorldPoint: () => {}, coverage: () => ({ total: 0 }) } installZones(world, blob, skin) new PaintCannon({ world, color: 'red', position: new THREE.Vector3(0, 2, 0), target: blob.mesh, paint: skin, targetRadius: 0.5 }) await new Promise((r) => setTimeout(r, 50)) // let any async swap settle function fp(root: THREE.Object3D) { const parts: string[] = [] root.traverse((o) => { const m = o as THREE.Mesh const mat: any = Array.isArray(m.material) ? m.material[0] : m.material parts.push([ o.type, o.name, o.visible ? 1 : 0, o.position.toArray().map((n) => n.toFixed(4)).join(','), o.scale.toArray().map((n) => n.toFixed(4)).join(','), m.isMesh ? (m.geometry.getAttribute('position')?.count ?? 0) : '-', mat ? `${mat.type}:${mat.color?.getHexString?.() ?? ''}:${mat.visible ? 1 : 0}` : '-', ].join('|')) }) return parts } const lines = fp(scene) let h = 0 for (const s of lines) for (let i = 0; i < s.length; i++) h = (Math.imul(31, h) + s.charCodeAt(i)) | 0 console.log(`nodes=${lines.length} colliders=${physics.colliders.len()} bodies=${physics.bodies.len()} greyboxMeshes=${gb.meshes.length} blobRadius=${blob.mesh.geometry.boundingSphere?.radius?.toFixed(6) ?? (blob.mesh.geometry.computeBoundingSphere(), blob.mesh.geometry.boundingSphere!.radius.toFixed(6))}`) console.log(`sceneFingerprint=${(h >>> 0).toString(16)}`)