feat: initialize sunrise with 100-player shared rooms

This commit is contained in:
2026-10-30 16:36:35 +09:00
commit 31d6eac4c2
257 changed files with 66063 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
// Verify the exported artifacts using the same Three.js loader as the application.
import assert from 'node:assert/strict';
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { Box3, Raycaster, Vector3 } from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { STRUCTURES } from '../src/constants/structures.ts';
const directory = resolve('public/models/summit');
const manifest = JSON.parse(readFileSync(resolve(directory, 'manifest.json'), 'utf8'));
const specs = [...STRUCTURES.towers, { id: 'weather-radar', ...STRUCTURES.radar }, ...STRUCTURES.decks];
assert.deepEqual(manifest.assets.map(a => a.id).sort(), specs.map(s => s.id).sort());
const report = { loader: 'Three.js GLTFLoader', checks: 'passed', assets: [] };
for (const asset of manifest.assets) {
const spec = specs.find(s => s.id === asset.id);
const bytes = readFileSync(resolve(directory, asset.file));
assert.equal(bytes.toString('ascii', 0, 4), 'glTF');
assert.equal(bytes.readUInt32LE(4), 2);
assert.equal(bytes.readUInt32LE(8), bytes.length);
const jsonLength = bytes.readUInt32LE(12);
const json = JSON.parse(bytes.toString('utf8', 20, 20 + jsonLength));
assert.equal(json.scenes.length, 1, `${asset.id}: exactly one scene`);
assert.equal(json.scenes[0].nodes.length, 1, `${asset.id}: exactly one asset root`);
assert.ok(!(json.images?.length), 'No reference images or external textures');
assert.ok(!json.buffers.some(b => b.uri), 'Embedded geometry only');
assert.ok(!(json.cameras?.length), 'No studio camera');
assert.ok(!json.nodes.some(n => /Cube|Studio|Camera/.test(n.name ?? '')), 'No unrelated scene objects');
assert.equal(asset.latitude, spec.latitude);
assert.equal(asset.longitude, spec.longitude);
assert.equal(asset.heading, spec.heading ?? 0);
for (const [key, value] of Object.entries(asset.dimensions)) assert.equal(value, spec[key]);
const data = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
const gltf = await new GLTFLoader().parseAsync(data, '');
const root = gltf.scene.getObjectByName(asset.id);
assert.ok(root, `${asset.id}: named root survives export`);
assert.ok(root.position.length() < 1e-6, 'No gallery offset baked into GLB');
assert.equal(root.userData.dimensionsEstimated, true);
let triangles = 0, meshes = 0;
root.traverse(object => {
if (!object.isMesh) return;
meshes++;
const geometry = object.geometry;
const positions = geometry.getAttribute('position');
assert.ok(positions.count > 0);
assert.ok([...positions.array].every(Number.isFinite), 'Finite vertex coordinates');
const normals = geometry.getAttribute('normal');
assert.ok(normals && [...normals.array].every(Number.isFinite), 'Finite normals');
if (geometry.index) {
assert.ok([...geometry.index.array].every(i => i < positions.count));
triangles += geometry.index.count / 3;
} else triangles += positions.count / 3;
assert.ok(object.material.isMeshStandardMaterial, 'PBR material survives');
});
gltf.scene.updateMatrixWorld(true);
const bounds = new Box3().setFromObject(root);
const size = bounds.getSize(new Vector3());
assert.ok([...bounds.min.toArray(), ...bounds.max.toArray()].every(Number.isFinite));
if (asset.kind === 'tower') {
assert.ok(Math.abs(bounds.max.y - (spec.height + 4)) < .01, 'Original tower height + 4m antenna');
assert.ok(Math.abs(bounds.min.y) < .15, 'Base at origin');
} else if (asset.kind === 'radar') {
assert.ok(Math.abs(bounds.max.y - (spec.buildingHeight + 1.8 * spec.radius)) < .1);
const ray = new Raycaster(new Vector3(0, 3, 20), new Vector3(0, 0, -1));
assert.ok(ray.intersectObject(root, true).length, 'Building is visible to app raycasts');
} else {
const ray = new Raycaster(new Vector3(.31, 2, .037), new Vector3(0, -1, 0));
const hits = ray.intersectObject(root, true);
assert.ok(hits.length && Math.abs(hits[0].point.y) < .02, 'Upper walking surface at y=0');
assert.ok(bounds.min.z < -spec.length / 2 - spec.stairLength, 'Stairs extend toward -Z');
assert.ok(asset.terrainSupportAnchors.length > 0);
assert.ok(asset.terrainSupportAnchors.every(a => a.position.every(Number.isFinite)));
}
report.assets.push({ id: asset.id, bytes: bytes.length, meshes, triangles,
bounds: { min: bounds.min.toArray(), max: bounds.max.toArray() },
sizeMetres: size.toArray().map(n => Number(n.toFixed(3))) });
}
report.totalBytes = report.assets.reduce((n, a) => n + a.bytes, 0);
report.totalTriangles = report.assets.reduce((n, a) => n + a.triangles, 0);
writeFileSync(resolve('assets/summit/verification.json'), JSON.stringify(report, null, 2));
console.table(report.assets.map(({ id, bytes, meshes, triangles, sizeMetres }) => ({ id, KiB: +(bytes / 1024).toFixed(1), meshes, triangles, sizeMetres: sizeMetres.join(' × ') })));
console.log(`PASS: ${report.assets.length} assets; ${report.totalTriangles} triangles; ${(report.totalBytes / 1024 / 1024).toFixed(2)} MiB`);