/** * A harbour member is a true-metre object, and every board has to agree. * * `size(metres, floor)` used to take `floor` in scene units, tuned at 391 m to * the unit on Southern California. On the merged California board (1,919 m/unit) * those floors always won: a 340 m channel became 1.05 km, and San Pedro Bay * read as a river. The floor is metres now. This is the property that would * have caught it: two boards five times apart draw the same channel on the * water. */ import assert from "node:assert/strict"; import test from "node:test"; import * as THREE from "three"; import { createPorts } from "../../engine/ports.ts"; import type { Port } from "../../engine/types.ts"; import type { World } from "../../engine/world.ts"; /** * One straight channel and one gantry. Widths come from the kit, not from the * pack, so the fixture only has to exist — two points of water, two points of * rail. */ const SAMPLE: Port = { id: "USXXX", name: "Sample", lat: 33.74, lng: -118.27, harborType: "CB", channel: [ [33.74, -118.27], [33.75, -118.27], ], cranes: [ { from: [33.74, -118.26], to: [33.741, -118.26], count: 1, bearing: 90, height: 82, outreach: 70, idleFraction: 0, }, ], }; function board(latScale: number): World { const centre = { lat: 33.82, lng: -118.05 }; const lngScale = latScale * Math.cos((centre.lat * Math.PI) / 180); const metresPerUnit = 111_320 / latScale; return { project(lat: number, lng: number): [number, number] { return [(lng - centre.lng) * lngScale, -(lat - centre.lat) * latScale]; }, groundAt(): number { return 0; }, metres(value: number): number { return (value / metresPerUnit) * 3.4; }, metresPerUnit, } as unknown as World; } function namedMesh(root: THREE.Object3D, name: string): THREE.Mesh { let found: THREE.Mesh | undefined; root.traverse((object) => { if (object instanceof THREE.Mesh && object.name === name) found = object; }); assert.ok(found, `no mesh named ${name}`); return found; } /** The dredged strip's width, in metres of real water. */ function channelMetres(world: World): number { const mesh = namedMesh(createPorts(world, [SAMPLE]), "ports:channel"); const pos = mesh.geometry.getAttribute("position"); assert.ok(pos && pos.count >= 2, "the channel strip has no rails to measure"); const width = Math.hypot(pos.getX(0) - pos.getX(1), pos.getZ(0) - pos.getZ(1)) * world.metresPerUnit; return width; } /** * Distance between the two portal legs of the one gantry, in metres. * * `pushCraneMatrices` emits the waterside leg first and the landside leg next, * `gauge` apart along the boom. Measuring the drawn matrices is the same move * `craneLights.test.ts` makes for the apex: a helper that recomputes the * number would pass while the mesh drifted. */ function craneGaugeMetres(world: World): number { const mesh = namedMesh(createPorts(world, [SAMPLE]), "ports:cranes") as THREE.InstancedMesh; assert.ok(mesh.count >= 2, "a gantry is at least two portal legs"); const a = new THREE.Matrix4(); const b = new THREE.Matrix4(); const pa = new THREE.Vector3(); const pb = new THREE.Vector3(); mesh.getMatrixAt(0, a); mesh.getMatrixAt(1, b); pa.setFromMatrixPosition(a); pb.setFromMatrixPosition(b); return Math.hypot(pa.x - pb.x, pa.z - pb.z) * world.metresPerUnit; } test("San Pedro's channel is the same width on a 391 m board and a 1,919 m one", () => { const metro = channelMetres(board(285)); const state = channelMetres(board(58)); assert.ok( Math.abs(metro - state) < 2, `channel is ${metro.toFixed(0)} m at Los Angeles and ${state.toFixed(0)} m on California`, ); assert.ok( metro > 300 && metro < 400, `channel measures ${metro.toFixed(0)} m; that is not a river`, ); }); test("a gantry's rail gauge is the same metres on both boards", () => { const metro = craneGaugeMetres(board(285)); const state = craneGaugeMetres(board(58)); assert.ok( Math.abs(metro - state) < 1, `gauge is ${metro.toFixed(1)} m at Los Angeles and ${state.toFixed(1)} m on California`, ); assert.ok( metro > 30 && metro < 60, `gauge measures ${metro.toFixed(1)} m; that is not a city block`, ); });