1
0

feat: build deterministic freeway and Lumbridge EV v2

This commit is contained in:
2026-08-19 01:12:47 -07:00
parent 94de2d8bee
commit ddf814a657
15 changed files with 716 additions and 24 deletions
+52
View File
@@ -0,0 +1,52 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import * as THREE from "three";
import CALIFORNIA_CITY from "../cities/california.ts";
import { createFreewayWorld } from "../engine/structures.ts";
import type { World } from "../engine/world.ts";
import CALIFORNIA_TRANSPORT from "../transport/california.ts";
import { buildFreewayWorldPlan } from "../transport/freewayWorld.ts";
describe("freeway world v2", () => {
it("compiles route identity and roadside placement deterministically", () => {
const first = buildFreewayWorldPlan(CALIFORNIA_TRANSPORT, { seed: 115, sampleCount: 32 });
const replay = buildFreewayWorldPlan(CALIFORNIA_TRANSPORT, { seed: 115, sampleCount: 32 });
const alternate = buildFreewayWorldPlan(CALIFORNIA_TRANSPORT, { seed: 116, sampleCount: 32 });
assert.deepEqual(first, replay);
assert.notDeepEqual(first, alternate);
assert.deepEqual(
first.routes.map(({ routeId, identity, shield, character }) => ({ routeId, identity, shield, character })),
[
{ routeId: "la-sf-us-101", identity: "us-highway", shield: "101", character: "coastal-oak" },
{ routeId: "la-sf-i-5", identity: "interstate", shield: "5", character: "central-valley" },
],
);
assert.ok(first.routes.every((route) => route.samples.length === 32));
assert.ok(first.routes.flatMap((route) => route.samples).every((sample) =>
sample.lanesPerDirection >= 2 && sample.lanesPerDirection <= 5 && Number.isFinite(sample.elevationPhase)
));
});
it("renders separated decks, markings, barriers, signs, and batched scenery", () => {
const world = {
city: CALIFORNIA_CITY,
project(lat: number, lng: number): [number, number] {
return [(lng + 121) * 20, -(lat - 36) * 20];
},
groundAt(): number {
return 0;
},
} as unknown as World;
const group = createFreewayWorld(world, CALIFORNIA_TRANSPORT);
assert.equal(group.name, "freeway-world-v2");
assert.equal(group.userData.planSeed, 101_005);
const names: string[] = [];
group.traverse((object) => names.push(object.name));
assert.equal(names.filter((name) => name === "freeway:lane-dashes").length, 8);
assert.equal(names.filter((name) => name === "freeway:median-barrier").length, 4);
assert.equal(names.filter((name) => name === "freeway:outer-guardrail").length, 4);
assert.ok(names.includes("freeway:sign:101"));
assert.ok(names.includes("freeway:sign:5"));
assert.ok(group.children.filter((child) => child instanceof THREE.InstancedMesh).length >= 7);
});
});
+16
View File
@@ -3,8 +3,10 @@ import { describe, it } from "node:test";
import * as THREE from "three";
import {
MODEL_X_METRICS,
LUMBRIDGE_EV_METRICS,
advanceModelXWheels,
buildModelX,
buildLumbridgeEV,
cloneModelX,
disposeModelX,
modelXInstanceParts,
@@ -21,6 +23,20 @@ function meshes(root: THREE.Object3D): THREE.Mesh[] {
}
describe("procedural Model X vehicle asset", () => {
it("publishes the original unbranded Lumbridge EV identity at both LODs", () => {
const follow = buildLumbridgeEV({ detail: "follow" });
const corridor = buildLumbridgeEV({ detail: "corridor" });
assert.equal(LUMBRIDGE_EV_METRICS, MODEL_X_METRICS);
assert.equal(follow.root.userData.design, "lumbridge-ev-01");
assert.equal(follow.root.userData.unbranded, true);
assert.equal(follow.root.userData.lod, "follow");
assert.equal(corridor.root.userData.lod, "corridor");
const followVertices = meshes(follow.root).reduce((sum, mesh) => sum + mesh.geometry.getAttribute("position").count, 0);
const corridorVertices = meshes(corridor.root).reduce((sum, mesh) => sum + mesh.geometry.getAttribute("position").count, 0);
assert.ok(followVertices > corridorVertices, "follow LOD includes a readable interior and denser wheels");
disposeModelX(follow);
disposeModelX(corridor);
});
it("has a metre-scale crossover silhouette and faces -Z", () => {
const rig = buildModelX({ detail: "corridor" });
const size = new THREE.Box3().setFromObject(rig.root).getSize(new THREE.Vector3());
+41
View File
@@ -150,6 +150,47 @@ describe("vehicle controller", () => {
assert.equal(first.final.mode, "manual");
});
it("brakes deterministically for a lead vehicle and emits safety telemetry", () => {
const make = () => new VehicleController(CALIFORNIA_TRANSPORT, {
routeId: "la-sf-us-101",
initialSpeedMps: 30,
mode: "assisted",
});
const first = make();
const replay = make();
for (const controller of [first, replay]) {
controller.setTrafficContext({ leadGapM: 12, leadSpeedMps: 8 });
for (let index = 0; index < 90; index += 1) controller.stepFixed();
}
assert.deepEqual(first.snapshot(), replay.snapshot());
assert.ok(first.state().speedMps < 20);
assert.equal(first.state().trafficIntervention, true);
assert.ok(first.state().collisionRisk > 0);
assert.ok((first.state().timeHeadwaySeconds ?? 0) > 0);
assert.ok(Number.isFinite(first.state().jerkMps3));
assert.ok(first.state().laneKeepingScore >= 0 && first.state().laneKeepingScore <= 1);
});
it("restores nearest-lead context with the checkpointed controller state", () => {
const make = () => new VehicleController(CALIFORNIA_TRANSPORT, {
routeId: "la-sf-us-101",
initialSpeedMps: 24,
mode: "assisted",
});
const original = make();
original.setTrafficContext({ leadGapM: 18, leadSpeedMps: 7 });
for (let index = 0; index < 30; index += 1) original.stepFixed();
const restored = make();
restored.restore(original.snapshot());
original.stepFixed();
restored.stepFixed();
assert.deepEqual(restored.snapshot(), original.snapshot());
assert.equal(restored.state().leadGapM, 18);
assert.equal(restored.state().leadSpeedMps, 7);
});
it("can switch route variants while preserving normalized progress", () => {
const controller = new VehicleController(CALIFORNIA_TRANSPORT, {
routeId: "la-sf-us-101",
+15
View File
@@ -99,4 +99,19 @@ describe("vehicle simulation", () => {
assert.equal(sim.poses(), poses);
assert.equal(sim.poses()[0], poses[0]);
});
it("reports a deterministic same-lane lead vehicle for safety control", () => {
const sim = new VehicleSimulation(CALIFORNIA_TRANSPORT, {
routeId: "la-sf-us-101",
count: 14,
seed: 115,
});
const candidate = sim.poses().slice(1).find((pose) => pose.direction === 1);
assert.ok(candidate);
const observation = sim.leadVehicle(candidate.distanceM - 100, 1, candidate.lane);
assert.ok(observation);
assert.ok(observation.gapM > 0 && observation.gapM <= 100.000001);
assert.ok(Number.isFinite(observation.speedMps));
assert.deepEqual(sim.leadVehicle(candidate.distanceM - 100, 1, candidate.lane), observation);
});
});