bda7a61acd
`size(metres, floor)` took `floor` in scene units, tuned at 391 m to the unit. On the merged California board those floors always won: a 340 m channel became 1.05 km. Floors are the metres those constants measured on SoCal, so that board does not move and every other board draws the same stone. `portScale.test.ts` is the property that would have caught it: two boards five times apart draw the same channel on the water.
132 lines
4.2 KiB
TypeScript
132 lines
4.2 KiB
TypeScript
/**
|
|
* 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`,
|
|
);
|
|
});
|