feat: build deterministic freeway and Lumbridge EV v2
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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());
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user