1
0
This repository has been archived on 2026-08-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
tera/src/test/data/vesselGate.test.ts
T
karti bcac6aa41a feat: the crane grows a mast, the harbour works a shift, and the site is re-shot
**The Asset Factory verdict, and it mostly went against the vote.** Nine
candidates were thumbed up. One was taken.

TOOK the STS crane. Rebuilt in `ports.ts` from 5 unit boxes to 11 — an A-frame
mast and apex cap, a forestay to the boom, a backstay to the tail, a sill, the
truck-lane portal beam, a machinery house — still exactly ONE InstancedMesh.
What was missing is the thing that makes a gantry a gantry: on a real STS the
tallest part of a WORKING crane is the A-frame apex, not the boom, and a parked
raised boom clears its own apex by only 15-25%. Before, 56 gantries read from
altitude as 56 crosses — two coincident verticals with one bar through them and
nothing above it — so a berth flattened into a picket fence.

Proportions came from both upvoted candidates agreeing independently (hinge ~58 m
under an apex at 99-104 m), taken conservatively because Tera's packs already
author an 82 m hinge against a real 55-60.

The apex beacon came across as EMISSION: `craneLights()` returns bare positions,
`nightlights.ts` turns them into one additive Points cloud, 56 points, one draw
call, night only, no THREE.Light anywhere. 0.09 units was invisible against the
port's own cream emissive; 0.17 — half a bridge head light — is right, and the
screenshot at 0.09 is what condemned it.

REJECTED all three bridges, city-lights and both aircraft: the incumbents won on
the picture, decisively for the bridge.

TWO PARTS WERE BUILT FROM THE APPROVED CANDIDATES, PHOTOGRAPHED, AND CUT. Four
legs: 14 m of quay spacing is 0.036 units at 391 m/unit against a 0.032 member
floor, so 90% overlap. A portal X-brace: the bay is 0.115 wide by 0.38 tall, so
both diagonals come out near-vertical and add a lump at mid-leg. Both are among
the best things about the factory cranes AT THE FACTORY'S FRAMING. Neither
survives at board scale. That gap is the whole reason a factory asset is
reference geometry and not a drop-in.

Fixed a defect the rebuild exposed: the backreach started a full rail-gauge
behind the hinge, leaving a gap over the portal with the beam floating below it.
One unbroken girder now. And every inclined member goes through a `strut()` that
takes two points in the (distance-along-boom, height) plane, so the
vertical-exaggeration bug the module header warns about is no longer reachable —
it needs a length and an angle, and there is now no way to start from those.

**The harbour works a shift.** It was a frozen tableau: 19 hulls placed from the
pack's berths that never changed. Vessels now arrive through the channel, are met
by a tug, berth, work and depart — seeded, so two people see the same harbour and
a capture script shoots the same frame twice. A ship loses its wake when it ties
up, because the wake is the information.

**Every still and film re-shot.** The site was showing a Tera that no longer
existed — SHOTS_COMMIT b7f5c41, FILMS_COMMIT 2aa4049, against an engine that has
since gained fires, the whole state, ports, ships and night infrastructure. Two
frames were bad and are fixed by moving the hour, not by retouching:
`bay-relief-day` and `peninsula-day` were white lids of marine layer. Four
captions described a Tera that no longer existed and are rewritten to the
delivered frame. `california-relief-night` is measurably brighter than the frame
it replaces (canvas mean 7.91 -> 10.57) despite the state being 30% larger.

Ten budget cells pass, run twice. socal 1,422,025 -> 1,429,993 triangles against
1,700,000, 218 draws against 320. The measured delta is double the geometry
because the crane mesh casts shadow, so renderer.info counts it in both passes —
worth knowing before anyone reads that number as geometry.

Tests 1,540 -> 1,570, server 295.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 03:29:58 -07:00

500 lines
20 KiB
TypeScript

/**
* The vessel gate: three AIS sentinels, one berth, and the position that must
* never exist.
*
* Every assertion in this file is about a defect that **typechecks, throws
* nothing and renders a perfectly plausible harbour**. That is why they are
* here rather than left to a picture: a fleet of ships all facing due north
* looks like a design choice, a hull three kilometres out to sea looks like a
* hull three kilometres out to sea, and a ship cutting the corner of a
* breakwater looks like a ship. None of the three is visible in a still frame
* and all three are wrong.
*
* The load-bearing one is `cog 358.7 survives`. `cog % 360` is the obvious
* normalisation, it is what anybody would write, and it silently converts the
* "not available" sentinel — exactly 360.0 — into a course of zero, due north.
* Real course over ground reaches 358.7 in the store behind this feed, so a
* range check cannot separate them either: the value, and only the value, can.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
AIS_COURSE_UNAVAILABLE,
AIS_HEADING_UNAVAILABLE,
AIS_SOG_UNAVAILABLE_KN,
KNOTS_TO_MPS,
VESSEL_MAKING_WAY_MPS,
aisCourse,
aisHeading,
aisSpeedMps,
berthAnchors,
isMakingWay,
metresBetween,
modelHarbour,
MODELLED_INTERVAL_SECONDS,
promoteVessels,
reckonVessel,
resolveBearing,
vesselStatus,
vesselSummary,
type BerthAnchor,
type VesselBounds,
} from "../../server/vessels.ts";
import type { Port } from "../../engine/types.ts";
import type { VesselsBody, WireVessel } from "../../server/wire.ts";
// ---- Fixtures --------------------------------------------------------------
const SAN_PEDRO: VesselBounds = {
minLat: 33.55,
maxLat: 33.85,
minLng: -118.4,
maxLng: -118.0,
};
/**
* One berth on Pier 400, bearing 118° — a hull lying alongside it points
* east-south-east. Hand-typed like everything else on these boards; the point of
* it here is that the number is *authored* and therefore known before any ship
* arrives.
*/
const BERTH: BerthAnchor = { id: "uslax-p400-a", lat: 33.72, lng: -118.24, bearing: 118 };
function wire(overrides: Partial<WireVessel> = {}): WireVessel {
return {
id: "w-1",
kind: "container",
lat: 33.72,
lon: -118.24,
speed: 0,
course: null,
heading: null,
navStatus: 5,
length: 300,
beam: 45,
ageSeconds: 0,
...overrides,
};
}
function body(vessels: WireVessel[], source: VesselsBody["source"] = "cloud1"): VesselsBody {
return {
source,
fetchedAt: "2026-08-22T02:00:00.000Z",
vessels,
intervalSeconds: 900,
ttlSeconds: 900,
};
}
// ---- The three sentinels ---------------------------------------------------
describe("the AIS sentinels, none of which is ever NULL", () => {
it("rejects sog 102.3, which is 'not available' and 52.6 m/s", () => {
assert.equal(aisSpeedMps(AIS_SOG_UNAVAILABLE_KN), null);
assert.equal(aisSpeedMps(102.3), null);
// Sixty seconds of it is 3.2 km — eight SoCal scene units — which is the
// whole reason this is a rejection and not a clamp.
assert.ok(AIS_SOG_UNAVAILABLE_KN * KNOTS_TO_MPS * 60 > 3_000);
});
it("keeps a real speed, including the zero half the fleet reports", () => {
assert.equal(aisSpeedMps(0), 0);
assert.ok(Math.abs((aisSpeedMps(12) ?? 0) - 12 * KNOTS_TO_MPS) < 1e-9);
// Zero is an answer and not an absence: 554 of 1,138 fixes in the store are
// exactly 0.0, and conflating them with "unknown" would stop every moored
// ship in the product being drawn.
assert.notEqual(aisSpeedMps(0), null);
});
it("rejects heading 511, which 40% of fixes carry", () => {
assert.equal(aisHeading(AIS_HEADING_UNAVAILABLE), null);
assert.equal(aisHeading(511), null);
assert.equal(aisHeading(0), 0);
assert.equal(aisHeading(359.9), 359.9);
});
it("rejects cog exactly 360.0", () => {
assert.equal(aisCourse(AIS_COURSE_UNAVAILABLE), null);
assert.equal(aisCourse(360), null);
assert.equal(aisCourse(360.0), null);
});
it("ACCEPTS cog 358.7 — the assertion this whole file exists for", () => {
// Real course over ground reaches 358.7, and 355.0, 355.4, 355.7, 356.3,
// 356.9 and 357.0 all occur in the same store. A naive range check near
// north, or a `% 360`, eats every one of them or turns the sentinel into a
// course. Neither may happen.
assert.equal(aisCourse(358.7), 358.7);
for (const cog of [355.0, 355.4, 355.7, 356.3, 356.9, 357.0, 358.7, 359.99]) {
assert.equal(aisCourse(cog), cog, `real course ${cog} was eaten by the gate`);
}
// And the trap itself, stated so nobody reintroduces it: `% 360` maps the
// sentinel onto a perfectly good course.
assert.equal(AIS_COURSE_UNAVAILABLE % 360, 0);
assert.notEqual(aisCourse(AIS_COURSE_UNAVAILABLE), 0);
});
it("survives garbage without throwing", () => {
for (const bad of [null, undefined, Number.NaN, Infinity, -1]) {
assert.equal(aisSpeedMps(bad as number), null);
assert.equal(aisHeading(bad as number), null);
assert.equal(aisCourse(bad as number), null);
}
});
});
// ---- Motion is gated on speed, never on nav_status -------------------------
describe("speed gates the motion; nav_status only labels it", () => {
it("treats nav_status 0 at 0.2 kn as stopped", () => {
// 83 of 197 vessels reporting "under way using engine" are under half a
// knot. The status is a word on a card; the speed is the fact.
const speed = aisSpeedMps(0.2);
assert.notEqual(speed, null);
assert.equal(isMakingWay(speed), false);
assert.equal(vesselStatus(0), "under-way");
const promotion = promoteVessels(
body([wire({ lat: 33.7, lon: -118.2, speed: 0.2 * KNOTS_TO_MPS, navStatus: 0, course: 90 })]),
SAN_PEDRO,
[],
);
const drawn = promotion.drawn[0];
assert.ok(drawn, "the hull was dropped rather than drawn stopped");
assert.equal(drawn.status, "under-way", "the label must survive");
assert.equal(drawn.speed, 0, "the motion must not");
assert.equal(promotion.makingWay, 0);
});
it("lets a hull at 6 kn make way", () => {
const promotion = promoteVessels(
body([wire({ lat: 33.7, lon: -118.2, speed: 6 * KNOTS_TO_MPS, navStatus: 0, course: 210 })]),
SAN_PEDRO,
[],
);
assert.equal(promotion.makingWay, 1);
assert.ok((promotion.drawn[0]?.speed ?? 0) >= VESSEL_MAKING_WAY_MPS);
});
it("refuses a wire speed that is the sentinel in metres per second", () => {
// The upstream half of this feed is in another repo on another box and does
// not exist yet, so "already stripped" is a promise nobody can keep today.
const promotion = promoteVessels(
body([wire({ speed: AIS_SOG_UNAVAILABLE_KN * KNOTS_TO_MPS })]),
SAN_PEDRO,
[BERTH],
);
assert.equal(promotion.drawn.length, 0);
assert.equal(promotion.suppressed, 1);
});
});
// ---- Orientation comes from the berth --------------------------------------
describe("a berthed hull points the way its quay does", () => {
it("gives a stopped hull with no heading and no cog its berth's bearing", () => {
// 21 of 150 stopped vessels have neither heading nor cog, and only 75 of the
// 150 have a heading at all. This is the case the layer must not be spun by.
const promotion = promoteVessels(
body([wire({ lat: BERTH.lat, lon: BERTH.lng, heading: null, course: null, speed: 0 })]),
SAN_PEDRO,
[BERTH],
);
const drawn = promotion.drawn[0];
assert.ok(drawn, "a hull with no orientation from the wire was dropped");
assert.equal(drawn.bearing, BERTH.bearing);
assert.equal(drawn.berthId, BERTH.id);
assert.equal(promotion.alongside, 1);
});
it("lets the wire nudge a berthed hull, and never swing it", () => {
const nudged = resolveBearing({ heading: 121, course: null, speedMps: 0, berthBearing: 118 });
assert.equal(nudged, 121);
// A heading 180 degrees off the quay is an AIS unit that is wrong about
// which end is the bow, not a ship moored backwards. The concrete wins.
const absurd = resolveBearing({ heading: 298, course: null, speedMps: 0, berthBearing: 118 });
assert.ok(absurd !== null && Math.abs(absurd - 118) <= 3, `berth bearing was swung to ${absurd}`);
});
it("uses the course for a hull making way, and the heading for one at anchor", () => {
assert.equal(resolveBearing({ heading: 30, course: 210, speedMps: 5, berthBearing: null }), 210);
assert.equal(resolveBearing({ heading: 30, course: 210, speedMps: 0, berthBearing: null }), 30);
});
it("withholds a hull nothing will orient, rather than inventing an angle", () => {
const promotion = promoteVessels(
body([wire({ lat: 33.6, lon: -118.1, heading: null, course: null, speed: 0 })]),
SAN_PEDRO,
[BERTH],
);
assert.equal(promotion.drawn.length, 0);
assert.equal(promotion.withoutOrientation, 1);
assert.equal(promotion.suppressed, 0, "an unoriented hull is not the same as an unreadable one");
assert.match(vesselSummary(promotion), /which way/);
});
it("does not reach across the harbour for a berth", () => {
// A berth is one authored point and the reach is 400 m; a hull a kilometre
// away is not lying on it.
assert.ok(metresBetween(BERTH.lat, BERTH.lng, 33.73, -118.24) > 400);
const promotion = promoteVessels(
body([wire({ lat: 33.73, lon: -118.24, heading: 44, course: null, speed: 0 })]),
SAN_PEDRO,
[BERTH],
);
assert.equal(promotion.drawn[0]?.bearing, 44, "the wire heading should stand off the berth");
assert.equal(promotion.drawn[0]?.berthId, undefined);
});
});
// ---- Dead reckoning, and the position that must not exist ------------------
describe("motion is along the reported course and never between two fixes", () => {
/**
* Two fixes fifteen minutes apart, on a hull that turned.
*
* `A` reports a course of 090 — due east — and eight knots. Fifteen minutes
* later it is reported at `B`, which is to the *south* east, because it came
* round the breakwater in between. The chord from A to B is therefore a line
* nothing sailed, and every point on it except the ends is a place the ship
* never was.
*/
const A = { lat: 33.72, lng: -118.28, speed: 8 * KNOTS_TO_MPS, course: 90 };
const B = { lat: 33.69, lng: -118.2 };
it("advances along the course at the speed", () => {
const after = reckonVessel(A, 450);
const metres = metresBetween(A.lat, A.lng, after.lat, after.lng);
assert.ok(
Math.abs(metres - A.speed * 450) < 1,
`advanced ${metres.toFixed(1)} m instead of ${(A.speed * 450).toFixed(1)}`,
);
// Due east means the latitude does not move.
assert.ok(Math.abs(after.lat - A.lat) < 1e-9, "a course of 090 changed the latitude");
assert.ok(after.lng > A.lng, "a course of 090 went west");
});
it("does not arrive at the second fix, which is what an interpolator does", () => {
// The crispest statement of the rule. An interpolator hands back exactly `B`
// at the end of the interval; a dead-reckoner hands back wherever the
// reported course took the ship, which here is five kilometres away because
// the ship turned and the course did not say so until the next fix.
const atInterval = reckonVessel(A, 900);
const missBy = metresBetween(atInterval.lat, atInterval.lng, B.lat, B.lng);
assert.ok(missBy > 3_000, `reckoning landed ${missBy.toFixed(0)} m from the second fix`);
});
it("never lands on the chord between two fixes", () => {
// The geometric statement of "no interpolation": walk the interval and
// assert every reckoned point stays clear of the segment A->B. It cannot be
// otherwise, because `reckonVessel` is handed one fix and has no second
// point to reach toward — but that is the property being pinned.
//
// From 150 s rather than from zero, because the first fix *is* an endpoint
// of the chord and the ship genuinely was there: a clearance test that
// started at t=0 would be asserting the ship was never at its own reported
// position.
let closest = Infinity;
for (let t = 150; t <= 900; t += 30) {
const at = reckonVessel(A, t);
closest = Math.min(closest, metresToSegment(at, A, B));
}
assert.ok(
closest > 200,
`a reckoned position came within ${closest.toFixed(0)} m of the chord between two fixes`,
);
});
it("stops rather than sailing on for ever once the next fix is overdue", () => {
const atLimit = reckonVessel(A, 900);
const wayPast = reckonVessel(A, 4_000);
assert.deepEqual(wayPast, atLimit);
});
it("does not move a hull with no course, whatever its speed says", () => {
const still = reckonVessel({ lat: 33.72, lng: -118.28, speed: 6, course: null }, 600);
assert.deepEqual(still, { lat: 33.72, lng: -118.28 });
});
it("does not move a stopped hull", () => {
const still = reckonVessel({ lat: 33.72, lng: -118.28, speed: 0, course: 90 }, 600);
assert.deepEqual(still, { lat: 33.72, lng: -118.28 });
});
});
// ---- The empty state, designed first ---------------------------------------
describe("the harbour with nothing in it says which kind of nothing it is", () => {
it("distinguishes an unconfigured feed from an empty board", () => {
const unconfigured = promoteVessels(null, SAN_PEDRO, []);
assert.equal(unconfigured.source, "none");
assert.match(vesselSummary(unconfigured), /No vessel feed is configured/i);
const answered = promoteVessels(body([]), SAN_PEDRO, []);
assert.equal(answered.source, "cloud1");
assert.match(vesselSummary(answered), /answered/i);
});
it("counts what it withheld rather than going blank", () => {
const promotion = promoteVessels(
body([
wire({ id: "off", lat: 30.0, lon: -118.2, heading: 10 }),
wire({ id: "bad", lat: Number.NaN, heading: 10 }),
wire({ id: "blind", lat: 33.6, lon: -118.1, heading: null, course: null }),
]),
SAN_PEDRO,
[],
);
assert.equal(promotion.drawn.length, 0);
assert.equal(promotion.offBoard, 1);
assert.equal(promotion.suppressed, 1);
assert.equal(promotion.withoutOrientation, 1);
const summary = vesselSummary(promotion);
assert.match(summary, /outside the frame/);
assert.match(summary, /unreadable/);
});
it("never labels a hull laden or in ballast", () => {
const promotion = promoteVessels(
body([wire({ lat: BERTH.lat, lon: BERTH.lng })]),
SAN_PEDRO,
[BERTH],
);
// The owner asked "whether they are empty or full". The honest answer is a
// port figure — 348,691 of 460,467 boxes left Los Angeles empty in July
// 2026 — and it is never attached to a hull, because `vessels` carries no
// draught column and the static AIS message is absent for most ships.
assert.match(vesselSummary(promotion), /not a ship one/);
assert.equal("draught" in (promotion.drawn[0] ?? {}), false);
});
});
// ---- The modelled harbour --------------------------------------------------
const PORT: Port = {
id: "USLAX",
name: "Port of Los Angeles",
lat: 33.73,
lng: -118.26,
harborType: "CB",
channel: [
[33.705, -118.26],
[33.72, -118.255],
[33.74, -118.25],
],
berths: [
{ id: "a", lat: 33.735, lng: -118.262, bearing: 118, maxLength: 400 },
{ id: "b", lat: 33.737, lng: -118.259, bearing: 118, maxLength: 400 },
{ id: "c", lat: 33.739, lng: -118.256, bearing: 118, maxLength: 340 },
{ id: "d", lat: 33.741, lng: -118.253, bearing: 296, maxLength: 120 },
],
};
describe("the modelled harbour, which is what runs this round", () => {
it("is deterministic — two people see the same ships", () => {
const a = modelHarbour([PORT], { seed: 115, atMs: 1_000_000 });
const b = modelHarbour([PORT], { seed: 115, atMs: 1_000_000 });
assert.deepEqual(a, b);
const other = modelHarbour([PORT], { seed: 116, atMs: 1_000_000 });
assert.notDeepEqual(other.vessels.map((v) => v.id), []);
});
it("carries no name, no MMSI, no callsign and no destination", () => {
// The store has CSCL INDIAN OCEAN and EVER LOVELY in it right now, and
// hardcoding them would be the fire layer's twenty-two orange marks in a
// nicer costume. Identity arrives with a licence entry or not at all.
for (const vessel of modelHarbour([PORT], { seed: 115 }).vessels) {
for (const forbidden of ["name", "mmsi", "callsign", "destination", "draught", "laden"]) {
assert.equal(forbidden in vessel, false, `a modelled vessel carried ${forbidden}`);
}
}
});
it("says it is modelled, and the panel says so too", () => {
const modelled = modelHarbour([PORT], { seed: 115 });
assert.equal(modelled.source, "modelled");
/**
* A minute, not the fifteen a real AIS listener declares.
*
* `intervalSeconds` is a property of the *source*, and this source is a
* closed form of the clock that can be asked for any instant — see
* `MODELLED_INTERVAL_SECONDS`. Fifteen minutes was mimicry, and it drew
* arriving ships over Terminal Island, because a consumer dead-reckons along
* a straight course and the Main Channel bends. A real feed still arrives
* declaring its own 900 and is still dead-reckoned for 900.
*/
assert.equal(modelled.intervalSeconds, MODELLED_INTERVAL_SECONDS);
const promotion = promoteVessels(modelled, SAN_PEDRO, berthAnchors([PORT]));
assert.match(vesselSummary(promotion), /Modelled/);
assert.match(vesselSummary(promotion), /no names and no MMSIs/);
});
it("volunteers no heading, so the berths have to do the work", () => {
const modelled = modelHarbour([PORT], { seed: 115 });
assert.ok(modelled.vessels.length > 0);
for (const vessel of modelled.vessels) assert.equal(vessel.heading, null);
const promotion = promoteVessels(modelled, SAN_PEDRO, berthAnchors([PORT]));
assert.ok(promotion.alongside > 0, "no modelled hull found its berth");
for (const drawn of promotion.drawn) {
if (!drawn.berthId) continue;
const berth = PORT.berths?.find((b) => b.id === drawn.berthId);
assert.equal(drawn.bearing, berth?.bearing);
}
});
it("puts a handful under way on the channel and the rest alongside", () => {
const promotion = promoteVessels(
modelHarbour([PORT], { seed: 115, underWayPerPort: 3 }),
SAN_PEDRO,
berthAnchors([PORT]),
);
/**
* A band, not a number, and the band is the honest assertion.
*
* `underWayPerPort` sizes the berth cycle so that the transits add up to the
* target, but the legs differ in length and each arriving or departing ship
* may have a tug attending her, so the count breathes. What must hold is
* that the channel is neither empty nor a traffic jam.
*/
assert.ok(promotion.makingWay >= 2, `only ${promotion.makingWay} under way`);
assert.ok(promotion.makingWay <= 8, `${promotion.makingWay} under way is a jam`);
assert.ok(promotion.alongside >= 1, "the quays came out empty");
// Every moving hull has a course, or the layer could not reckon it and would
// not draw a wake — which is the one thing that reads at board scale.
for (const drawn of promotion.drawn) {
if (drawn.speed > 0) assert.notEqual(drawn.course, null);
}
});
it("draws nothing at all for a board with no ports", () => {
const modelled = modelHarbour(undefined, { seed: 115 });
assert.deepEqual(modelled.vessels, []);
const promotion = promoteVessels(modelled, SAN_PEDRO, []);
assert.deepEqual(promotion.drawn, []);
});
});
// ---- Geometry helper -------------------------------------------------------
/** Metres from a point to the segment `a`-`b`, in the flat local approximation. */
function metresToSegment(
p: { lat: number; lng: number },
a: { lat: number; lng: number },
b: { lat: number; lng: number },
): number {
const scale = Math.cos((a.lat * Math.PI) / 180);
const px = (p.lng - a.lng) * scale;
const py = p.lat - a.lat;
const bx = (b.lng - a.lng) * scale;
const by = b.lat - a.lat;
const denominator = bx * bx + by * by;
const t = denominator > 0 ? Math.max(0, Math.min(1, (px * bx + py * by) / denominator)) : 0;
const dx = px - bx * t;
const dy = py - by * t;
return Math.hypot(dx, dy) * 111_320;
}