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.
59 lines
2.9 KiB
TypeScript
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();
|
|
});
|