Files
sunrise/tests/summit-marker.test.ts
T
horoli eb49ea4b84 Improve summit terrain, mobile HUD and multiplayer night equipment
Add the Blender flashlight asset with a 600 lm beam and reduced bloom, shared-mode self badge, and compact phone controls. Relocate the summit marker, blend terrain seams, widen walking routes, and align local server configuration on port 2027.
2026-10-31 23:14:44 +09:00

59 lines
2.9 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { readFileSync } from 'node:fs';
import { BoxGeometry, Group, Mesh, MeshStandardMaterial, Vector3 } from 'three';
import { createSummitMarker } from '../src/scene/summitMarker.ts';
import { summitPosition } from '../src/controls/summitCoordinates.ts';
import { SUMMIT_TERRAIN } from '../src/constants/summitTerrain.ts';
import { SUMMIT_PLATEAU } from '../src/constants/summitPlateau.ts';
test('marker placement matches the map pin plus the estimated satellite offset', () => {
const spec = JSON.parse(readFileSync(new URL('../scripts/constants/marker-location.json', import.meta.url), 'utf8'));
const pin = summitPosition(spec.pin.latitude, spec.pin.longitude, SUMMIT_TERRAIN.start);
const expected = [pin.x + spec.offsetFromPinMeters[0], pin.z + spec.offsetFromPinMeters[1]];
SUMMIT_PLATEAU.marker.offset.forEach((value, axis) => assert.ok(Math.abs(value-expected[axis]) < .001));
});
test('exported marker embeds the current baked textures, not stale packed images', () => {
const glb = readFileSync(new URL('../public/models/summit/summit-marker.glb', import.meta.url));
const jsonLength = glb.readUInt32LE(12);
const document = JSON.parse(glb.subarray(20, 20 + jsonLength).toString());
const binaryStart = 28 + jsonLength;
assert.equal(document.images.length, 4);
for (const image of document.images) {
const view = document.bufferViews[image.bufferView];
const start = binaryStart + (view.byteOffset ?? 0);
const embedded = glb.subarray(start, start + view.byteLength);
const source = readFileSync(new URL(`../public/terrain/materials/${image.name}.png`, import.meta.url));
assert.ok(embedded.equals(source), `${image.name} must match the latest bake`);
}
});
test('marker seats its foot on sloping bedrock without deforming the stone', () => {
const asset = new Group();
const mesh = new Mesh(new BoxGeometry(4, 4, 2), new MeshStandardMaterial());
mesh.position.y = 2;
asset.add(mesh);
const groundAt = (x: number, z: number) => 12 + .3*x - .2*z;
const rock = new MeshStandardMaterial();
const marker = createSummitMarker(asset, {x:0,z:0}, groundAt, rock);
marker.group.updateMatrixWorld(true);
const position = mesh.geometry.getAttribute('position');
const point = new Vector3();
let nearestContact = Infinity;
for (let i=0; i<position.count; i++) {
point.fromBufferAttribute(position,i).applyMatrix4(mesh.matrixWorld);
if (position.getY(i)<0) {
const burial = groundAt(point.x,point.z)-point.y;
assert.ok(burial > .05, `base must stay below terrain: ${burial}`);
assert.equal(position.getY(i),-2,'base geometry remains unchanged');
nearestContact = Math.min(nearestContact,burial);
} else {
assert.equal(position.getY(i),2,'upper geometry remains unchanged');
}
}
assert.ok(nearestContact < .3,'the stone is seated close to its lowest contact');
marker.dispose();
rock.dispose();
});