1
0

feat: add deterministic office robot jobs

This commit is contained in:
2026-08-19 02:54:49 -07:00
parent 0557a26e6b
commit e378a03740
18 changed files with 1937 additions and 1779 deletions
+82 -5
View File
@@ -12,15 +12,19 @@ import {
DRIVE_INACTION,
OFFICE_NAV_INACTION,
OFFICE_NAV_SCENARIOS,
OFFICE_JOBS_INACTION,
OFFICE_JOBS_SCENARIOS,
CaliforniaFlightEnvironment,
CrowNavEnvironment,
Drive101Environment,
OfficeNavEnvironment,
OfficeJobsEnvironment,
arenaChecksum,
californiaFlightScriptedBaseline,
crowNavScriptedBaseline,
driveScriptedBaseline,
officeNavScriptedBaseline,
officeJobsScriptedBaseline,
type ArenaEnvironment,
type ArenaManifest,
type ArenaScenarioRegistry,
@@ -36,6 +40,7 @@ interface EnvironmentCase {
registry: ArenaScenarioRegistry<object>;
inaction: unknown;
scripted(observation: unknown): unknown;
successReason: string;
}
const CASES: EnvironmentCase[] = [
@@ -45,6 +50,7 @@ const CASES: EnvironmentCase[] = [
registry: DRIVE_101_SCENARIOS as ArenaScenarioRegistry<object>,
inaction: DRIVE_INACTION,
scripted: () => driveScriptedBaseline(),
successReason: "goal",
},
{
name: "office",
@@ -54,6 +60,17 @@ const CASES: EnvironmentCase[] = [
scripted: (observation) => officeNavScriptedBaseline(
observation as Parameters<typeof officeNavScriptedBaseline>[0],
),
successReason: "goal",
},
{
name: "office-jobs",
create: () => new OfficeJobsEnvironment() as AnyEnvironment,
registry: OFFICE_JOBS_SCENARIOS as ArenaScenarioRegistry<object>,
inaction: OFFICE_JOBS_INACTION,
scripted: (observation) => officeJobsScriptedBaseline(
observation as Parameters<typeof officeJobsScriptedBaseline>[0],
),
successReason: "job-complete",
},
{
name: "crow",
@@ -63,6 +80,7 @@ const CASES: EnvironmentCase[] = [
scripted: (observation) => crowNavScriptedBaseline(
observation as Parameters<typeof crowNavScriptedBaseline>[0],
),
successReason: "goal",
},
{
name: "flight",
@@ -72,6 +90,7 @@ const CASES: EnvironmentCase[] = [
scripted: (observation) => californiaFlightScriptedBaseline(
observation as Parameters<typeof californiaFlightScriptedBaseline>[0],
),
successReason: "goal",
},
];
@@ -96,9 +115,9 @@ function run(
}
describe("arena contract and manifests", () => {
it("exports four versioned renderer-independent manifests with disjoint public splits", () => {
it("exports five versioned renderer-independent manifests with disjoint public splits", () => {
assert.deepEqual(ARENA_MANIFESTS.map((manifest: ArenaManifest) => manifest.id), [
"drive-101-v1", "office-nav-v1", "crow-nav-v1", "california-flight-v1",
"drive-101-v1", "office-nav-v1", "office-jobs-v1", "crow-nav-v1", "california-flight-v1",
]);
for (const manifest of ARENA_MANIFESTS) {
assert.equal(manifest.apiVersion, ARENA_API_VERSION);
@@ -158,7 +177,7 @@ describe("arena contract and manifests", () => {
const scripted = run(entry, id, 5, entry.scripted).final;
assert.equal(scripted.terminated, true, entry.name);
assert.equal(scripted.truncated, false, entry.name);
assert.equal(scripted.info.terminalReason, "goal", entry.name);
assert.equal(scripted.info.terminalReason, entry.successReason, entry.name);
}
});
});
@@ -170,12 +189,12 @@ describe("arena baseline proofs", () => {
for (const seed of [1, 0xdecafbad]) {
const idle = run(entry, definition.id, seed, () => entry.inaction);
assert.ok(idle.total < 0, `${entry.name}/${definition.id}/${seed} inaction=${idle.total}`);
assert.notEqual(idle.final.info.terminalReason, "goal");
assert.notEqual(idle.final.info.terminalReason, entry.successReason);
const scripted = run(entry, definition.id, seed, entry.scripted);
assert.equal(
scripted.final.info.terminalReason,
"goal",
entry.successReason,
`${entry.name}/${definition.id}/${seed}`,
);
assert.ok(scripted.total > 0, `${entry.name}/${definition.id}/${seed}=${scripted.total}`);
@@ -206,6 +225,26 @@ describe("arena baseline proofs", () => {
}
assert.equal(officeReason, "collision-stall");
const officeJobs = new OfficeJobsEnvironment();
officeJobs.reset(2, "train-la-directory-inspection");
let officeJobsReason: string | null = null;
for (let index = 0; index < 8; index += 1) {
const result = officeJobs.step({ x: 0, z: 0, interact: true });
officeJobsReason = result.info.terminalReason;
if (result.terminated) break;
}
assert.equal(officeJobsReason, "wrong-interaction-limit");
const collisionJobs = new OfficeJobsEnvironment();
collisionJobs.reset(2, "dev-sf-parcel-delivery");
let collisionJobsReason: string | null = null;
for (let index = 0; index < 80; index += 1) {
const result = collisionJobs.step({ x: 0, z: 1, interact: false });
collisionJobsReason = result.info.terminalReason;
if (result.terminated) break;
}
assert.equal(collisionJobsReason, "collision-stall");
const crow = new CrowNavEnvironment();
crow.reset(2, "train-east-crosswind");
let crowReason: string | null = null;
@@ -229,6 +268,33 @@ describe("arena baseline proofs", () => {
});
describe("arena snapshot, trace and replay", () => {
it("restores and replays every SF/LA office-job scenario", () => {
for (const definition of OFFICE_JOBS_SCENARIOS.definitions) {
const environment = new OfficeJobsEnvironment();
let observation = environment.reset(311, definition.id).observation;
for (let index = 0; index < 10; index += 1) {
observation = environment.step(officeJobsScriptedBaseline(observation)).observation;
}
const checkpoint = environment.snapshot();
const action = officeJobsScriptedBaseline(observation);
const expected = environment.step(action);
environment.restore(checkpoint);
assert.deepEqual(environment.step(action), expected, definition.id);
const traced = new OfficeJobsEnvironment();
observation = traced.reset(311, definition.id).observation;
for (let index = 0; index < 50; index += 1) {
const result = traced.step(officeJobsScriptedBaseline(observation));
observation = result.observation;
if (result.terminated) break;
}
const trace = traced.trace();
const replay = new OfficeJobsEnvironment().replay(trace);
assert.equal(replay.finalStateChecksum, trace.finalStateChecksum, definition.id);
assert.equal(replay.cumulativeReward, trace.cumulativeReward, definition.id);
}
});
it("restores each simulator bit-for-bit and preserves the next transition", () => {
for (const entry of CASES) {
const environment = entry.create();
@@ -301,6 +367,17 @@ describe("arena snapshot, trace and replay", () => {
}),
/frame 1 is invalid/,
);
const jobs = new OfficeJobsEnvironment();
jobs.reset(17, "train-sf-display-inspection");
const jobsSnapshot = jobs.snapshot();
const invalidSimulation = structuredClone(jobsSnapshot.simulation);
invalidSimulation.activity.robots[0]!.battery = 9;
const { checksum: _jobsChecksum, ...jobsCore } = { ...jobsSnapshot, simulation: invalidSimulation };
assert.throws(
() => jobs.restore({ ...jobsCore, checksum: arenaChecksum(jobsCore) }),
/invalid/,
);
});
it("refuses post-terminal stepping until reset", () => {
+149
View File
@@ -0,0 +1,149 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { createRobotActivity } from "../interiors/robotActivity.ts";
import { resolveRobotOperations } from "../interiors/robotOperations.ts";
import { createRobotRouter } from "../interiors/robotRoutes.ts";
import { Plan } from "../interiors/plan.ts";
import { LUMBRIDGE_HQ } from "../offices/lumbridge-hq.ts";
import { MATEO_COURT } from "../offices/mateo-court.ts";
import { LUMBRIDGE_HQ_ROBOT_OPERATIONS } from "../offices/operations/lumbridge-hq.ts";
import { MATEO_COURT_ROBOT_OPERATIONS } from "../offices/operations/mateo-court.ts";
const CASES = [
[LUMBRIDGE_HQ, LUMBRIDGE_HQ_ROBOT_OPERATIONS],
[MATEO_COURT, MATEO_COURT_ROBOT_OPERATIONS],
] as const;
describe("robot operations and routes", () => {
it("resolves explicit Plan anchors and keeps every authored job on one routable floor", () => {
for (const [office, definition] of CASES) {
const plan = new Plan(office, { depth: "public", warn: false });
const operations = resolveRobotOperations(plan, definition);
const router = createRobotRouter(plan);
for (const robot of operations.robots.values()) {
let previous = operations.stations.get(robot.spawnStationId)!;
for (const jobId of robot.schedule) {
const job = operations.jobs.get(jobId)!;
for (const stationId of job.stationIds) {
const station = operations.stations.get(stationId)!;
assert.equal(station.levelId, robot.levelId, `${robot.id}/${job.id}`);
const route = router.route(robot.levelId, previous.position, station.position);
assert.ok(route, `${robot.id}/${job.id}/${station.id}`);
assert.ok(route.lengthM >= 0);
previous = station;
}
}
}
}
});
it("rejects operations that imply real activity or invent a vertical route", () => {
const plan = new Plan(LUMBRIDGE_HQ, { warn: false });
assert.throws(
() => resolveRobotOperations(plan, {
...LUMBRIDGE_HQ_ROBOT_OPERATIONS,
disclosure: "Live operations",
}),
/simulated/,
);
assert.throws(
() => resolveRobotOperations(plan, {
...LUMBRIDGE_HQ_ROBOT_OPERATIONS,
jobs: [{
id: "bad-route",
label: "Bad route",
kind: "deliver",
stationIds: ["sf-l1-parcel-pickup", "sf-l2-parcel-drop"],
dwellTicks: 1,
}],
robots: [],
}),
/crosses levels/,
);
});
});
describe("robot activity determinism", () => {
it("is cadence-independent at the fixed-step boundary and seed-stable", () => {
const plan = new Plan(LUMBRIDGE_HQ, { warn: false });
const operations = resolveRobotOperations(plan, LUMBRIDGE_HQ_ROBOT_OPERATIONS);
const stepped = createRobotActivity(plan, operations, { seed: 73 });
const ticked = createRobotActivity(plan, operations, { seed: 73 });
for (let index = 0; index < 600; index += 1) stepped.step();
for (let index = 0; index < 300; index += 1) ticked.tick(0.2);
assert.deepEqual(ticked.snapshot(), stepped.snapshot());
const different = createRobotActivity(plan, operations, { seed: 74 });
assert.notDeepEqual(
different.states().map((robot) => [robot.id, robot.scheduleCursor, robot.idleTicks]),
stepped.trace().initial.robots.map((robot) => [robot.id, robot.scheduleCursor, robot.idleTicks]),
);
});
it("restores the exact next transition and replays JSON-safe traces", () => {
const plan = new Plan(MATEO_COURT, { warn: false });
const operations = resolveRobotOperations(plan, MATEO_COURT_ROBOT_OPERATIONS);
const activity = createRobotActivity(plan, operations, { seed: 0xdecafbad });
for (let index = 0; index < 420; index += 1) activity.step();
const checkpoint = activity.snapshot();
const expected = activity.step();
activity.restore(JSON.parse(JSON.stringify(checkpoint)));
assert.deepEqual(activity.step(), expected);
for (let index = 0; index < 60; index += 1) activity.step();
const trace = JSON.parse(JSON.stringify(activity.trace()));
assert.deepEqual(activity.replay(trace), trace.final);
});
it("rejects tampered snapshots before state mutation", () => {
const plan = new Plan(LUMBRIDGE_HQ, { warn: false });
const operations = resolveRobotOperations(plan, LUMBRIDGE_HQ_ROBOT_OPERATIONS);
const activity = createRobotActivity(plan, operations, { seed: 4 });
const before = activity.snapshot();
const tampered = structuredClone(before);
tampered.robots[0]!.battery = 9;
assert.throws(() => activity.restore(tampered), /invalid/);
assert.deepEqual(activity.snapshot(), before);
});
it("exposes deterministic blocked recovery for a controlled robot", () => {
const plan = new Plan(LUMBRIDGE_HQ, { warn: false });
const operations = resolveRobotOperations(plan, LUMBRIDGE_HQ_ROBOT_OPERATIONS);
const id = "sf-l1-courier";
const activity = createRobotActivity(plan, operations, {
seed: 1,
robotIds: [id],
controlledRobotIds: [id],
});
let recovered = false;
for (let index = 0; index < 80; index += 1) {
const robot = activity.step({ [id]: { x: 0, z: 1, interact: false } }).robots[0]!;
if (robot.mode === "blocked-recovery") recovered = true;
}
assert.equal(recovered, true);
});
});
describe("robot activity soak", () => {
it("runs thirty deterministic simulated minutes without a floor escape or terminal", () => {
for (const [office, definition] of CASES) {
const plan = new Plan(office, { warn: false });
const operations = resolveRobotOperations(plan, definition);
const activity = createRobotActivity(plan, operations, { seed: 0x5eed1234 });
const seen = new Set<string>();
for (let tick = 0; tick < 18_000; tick += 1) {
for (const robot of activity.step().robots) {
seen.add(robot.mode);
assert.equal(robot.terminalReason, null, `${office.id}/${robot.id}/${tick}`);
assert.ok(plan.roomAt(robot.levelId, robot.position), `${office.id}/${robot.id}/${tick}`);
assert.equal(plan.blocked(robot.levelId, robot.position, robot.position, 0.28), false);
}
}
assert.ok(seen.has("patrol"));
assert.ok(seen.has("deliver"));
assert.ok(seen.has("inspect"));
assert.ok(seen.has("charge"));
assert.ok(activity.states().every((robot) => robot.completedJobs > 20));
}
});
});