1
0

feat: tone-mapped render rig, studio devices, LA fidelity pass, UI overhaul

The build the studios needed, across eight workstreams and one strict file
partition.

**The render rig was the quality ceiling.** The renderer ran three's
NoToneMapping default while atmosphere drove the sun to 2.35 and assets set
emissives to 3.2, so every value above 1.0 hard-clipped to flat white — which is
why walls blew out and every fitting looked like a white rectangle. ACES filmic
tone mapping and an explicit output colour space land in `stage.ts`, and the
atmosphere intensity table and palette headroom are re-tuned against the new
curve rather than left tuned for the clipping we removed.

`engine/environmentRig.ts` builds a PMREM environment at runtime, procedurally,
so nothing binary is committed. There was no environment map anywhere before, so
every `metalness > 0` role had nothing to reflect and rendered dull grey — a
defect the code already documented against itself in `office/optimus.ts`, where a
whole material role was abandoned over it, and worked around in `modelX.ts` with
a fake emissive that this change deletes. Atmosphere remains the sole light
owner; the rig derives from the `LightingState` it already produced.

**Studio hardware exists.** There was no device concept anywhere in the product:
no type, no route, no state. `devices/types.ts` fixes a declaration/state/
capability/command contract that a smart light, a thermostat, a door sensor and a
charger all fit without a schema change, and both studios now carry a desk mic
and a computer speaker with deterministic simulated behaviour behind an adapter
seam a real API can occupy later. Reads are the demo and are open; commands are a
signed-in action and are kept off the read body entirely, because a shared cache
replaying a GET that turned a microphone on is exactly what the fail-closed
cache default exists to prevent.

**The ADS-B licence hole is closed.** `TERA_ADSB_ENDPOINT` accepted any URL, the
response was served publicly cacheable, and the attribution hardcoded adsb.lol
regardless of where the endpoint pointed — one env var away from republishing
non-redistributable data under an open-terms credit. The host is now allowlisted,
the credit is derived from the host actually configured, public cacheability is
conditional on redistributability, and a refused endpoint demotes to simulated
flights and says so in `degraded[]`. The gate is on the source, not the feature:
live aircraft and their detail cards stay open to anonymous visitors.

**The LA studio was never the smaller pack** — 16 rooms and 248 props against
SF's 4 and 28. Its deficit was fidelity per square metre: 98 of those props were
ceiling troffers, it bound no props to seats, placed none of the habitat kit, and
12 of its 16 rooms had no viewpoint. Density comes from new asset kinds rather
than more instances, because `furnish.ts` draws once per kind and folds colour
into the batch key, so repeat instances add nothing the eye can read.

**The interface stops being forty imperative mutations.** Every visibility
decision moves into a pure, tested `ui/chromeState.ts` and one applier, so the
chrome has coverage for the first time. Deleted: ~100 lines of CSS and two
bindings targeting elements that no longer exist, and a `body:has()` rule that
shifted the desktop layout by 160px for touch controls hidden there. Fixed: the
office picker tabs that drew their label and their badge on top of each other.
Added: a first-run flow, because the product is two verbs and neither was ever
stated on screen. Mobile is designed on its own terms instead of being the
desktop with things hidden — the plan view comes back, and the keyboard-only
shortcuts button is replaced by touch controls.

`arena/studioOps.ts` frames the whole thing as the multi-variable environment it
is, wrapping the same simulators the renderer drives rather than a headless copy.

Also removed `input/vehicle.ts`, which nothing but its own test imported.

Tests 385 -> 961, all passing. Typecheck, build, performance budgets across six
matrix cells, no-binaries, provenance, dependency licences, zero-config boot and
arena source hashes all green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 19:44:24 -07:00
parent 8738367258
commit db074e9cf7
150 changed files with 36237 additions and 2586 deletions
+657
View File
@@ -0,0 +1,657 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
STUDIO_OPS_INACTION,
STUDIO_OPS_MANIFEST,
STUDIO_OPS_SCENARIOS,
StudioOpsEnvironment,
arenaChecksum,
quantizeObservable,
rollout,
studioOpsEnergyPenalty,
studioOpsNoisePenalty,
studioOpsScriptedBaseline,
studioOverflights,
studioSkyAt,
studioVehicleReadiness,
studioWeatherAt,
type StudioOpsAction,
type StudioOpsObservation,
type StudioOpsReward,
} from "../../index.ts";
import { Plan } from "../../interiors/plan.ts";
import { LUMBRIDGE_HQ } from "../../offices/lumbridge-hq.ts";
import { MATEO_COURT } from "../../offices/mateo-court.ts";
const SEEDS = [1, 0xdecafbad];
type Observation = StudioOpsObservation;
function scripted(observation: unknown): StudioOpsAction {
return studioOpsScriptedBaseline(observation as Observation);
}
/** Runs a targeted policy and reports where the episode ended. */
function reach(
scenarioId: string,
seed: number,
policy: (observation: Observation, step: number) => StudioOpsAction,
): { reason: string | null; steps: number; total: number } {
const result = rollout(
new StudioOpsEnvironment(),
(observation, step) => policy(observation as Observation, step),
{ seed, scenario: scenarioId },
);
return { reason: result.final.info.terminalReason, steps: result.steps, total: result.total };
}
describe("studio-ops baselines", () => {
it("keeps inaction below zero and never lets it reach the goal", () => {
for (const definition of STUDIO_OPS_SCENARIOS.definitions) {
for (const seed of SEEDS) {
const idle = rollout(new StudioOpsEnvironment(), () => STUDIO_OPS_INACTION, {
seed,
scenario: definition.id,
});
assert.ok(idle.total < 0, `${definition.id}/${seed} inaction=${idle.total}`);
assert.notEqual(idle.final.info.terminalReason, "job-complete");
}
}
});
it("proves a positive scripted completion that beats inaction on every scenario", () => {
for (const definition of STUDIO_OPS_SCENARIOS.definitions) {
for (const seed of SEEDS) {
const idle = rollout(new StudioOpsEnvironment(), () => STUDIO_OPS_INACTION, {
seed,
scenario: definition.id,
});
const run = rollout(new StudioOpsEnvironment(), scripted, {
seed,
scenario: definition.id,
});
const label = `${definition.id}/${seed}`;
assert.equal(run.final.info.terminalReason, "job-complete", label);
assert.equal(run.final.terminated, true, label);
assert.equal(run.final.truncated, false, label);
assert.ok(run.total > 0, `${label} scripted=${run.total}`);
assert.ok(run.total > idle.total, `${label} ${run.total} <= ${idle.total}`);
}
}
});
it("sums thirteen finite named components and authors no total", () => {
const run = rollout(new StudioOpsEnvironment(), scripted, {
seed: 5,
scenario: "train-sf-clear-morning-desk-check",
});
const components = run.final.rewardComponents as StudioOpsReward;
assert.deepEqual(
Object.keys(components).sort(),
Object.keys(STUDIO_OPS_MANIFEST.rewardComponents).sort(),
);
const sum = Object.values(components).reduce((total, value) => total + value, 0);
assert.ok(Math.abs(sum - run.final.reward) < 1e-12);
assert.ok(Object.values(components).every((value) => Number.isFinite(value)));
});
});
describe("studio-ops terminals are each individually reachable", () => {
it("completes the job", () => {
assert.equal(reach("train-sf-clear-morning-desk-check", 1, scripted).reason, "job-complete");
});
it("stalls against resolved office collision", () => {
// Straight into a wall, forever. The controller's own recovery gives up.
const result = reach("train-la-overcast-inspection", 2, () => ({
...STUDIO_OPS_INACTION,
z: 1,
}));
assert.equal(result.reason, "collision-stall");
});
it("hits the invalid-interaction limit", () => {
const result = reach("train-la-overcast-inspection", 2, () => ({
...STUDIO_OPS_INACTION,
interact: true,
}));
assert.equal(result.reason, "wrong-interaction-limit");
// Exactly at the limit rather than somewhere after it.
assert.equal(result.steps, 8);
});
it("exhausts the studio's energy reserve", () => {
// Everything on and the car plugged into a post that draws from the same
// reserve. This is the terminal the `energyReservePct` observation exists
// to make visible; without it the failure would be unattributable.
for (const definition of STUDIO_OPS_SCENARIOS.definitions) {
const result = reach(definition.id, 3, () => ({
...STUDIO_OPS_INACTION,
speakerVolume: 1,
speakerPlay: true,
vehiclePrecondition: true,
vehicleCharge: true,
}));
assert.equal(result.reason, "battery-depleted", definition.id);
}
});
it("misses the departure", () => {
const result = reach("dev-sf-windy-evening-delivery", 1, () => STUDIO_OPS_INACTION);
assert.equal(result.reason, "departure-missed");
assert.equal(result.steps, 1000);
});
it("declares every one of them except the goal as a safety terminal", () => {
assert.deepEqual([...STUDIO_OPS_MANIFEST.safetyTerminals], [
"collision-stall",
"wrong-interaction-limit",
"battery-depleted",
"departure-missed",
]);
});
});
describe("studio-ops snapshot, trace and replay", () => {
it("continues bit-exactly from a mid-episode snapshot on every scenario", () => {
for (const definition of STUDIO_OPS_SCENARIOS.definitions) {
const environment = new StudioOpsEnvironment();
let observation = environment.reset(311, definition.id).observation;
for (let index = 0; index < 25; index += 1) {
observation = environment.step(scripted(observation)).observation;
}
const checkpoint = environment.snapshot();
const action = scripted(observation);
const expected = environment.step(action);
const restored = environment.restore(checkpoint);
assert.equal(restored.info.step, checkpoint.step, definition.id);
// Deep-equal on the whole transition: observation, reward, every
// component, both flags and the state checksum.
assert.deepEqual(environment.step(action), expected, definition.id);
}
});
it("replays a trace to the identical final checksum and cumulative reward", () => {
for (const definition of STUDIO_OPS_SCENARIOS.definitions) {
const environment = new StudioOpsEnvironment();
let observation = environment.reset(311, definition.id).observation;
for (let index = 0; index < 80; index += 1) {
const result = environment.step(scripted(observation));
observation = result.observation;
if (result.terminated || result.truncated) break;
}
const trace = environment.trace();
assert.equal(arenaChecksum({ ...trace, checksum: undefined }), trace.checksum);
const replay = new StudioOpsEnvironment().replay(trace);
assert.equal(replay.finalStateChecksum, trace.finalStateChecksum, definition.id);
assert.equal(replay.cumulativeReward, trace.cumulativeReward, definition.id);
assert.equal(replay.steps, trace.steps.length, definition.id);
}
});
/** Twelve scripted steps, and the environment left mid-episode. */
function midEpisode(): StudioOpsEnvironment {
const environment = new StudioOpsEnvironment();
let observation = environment.reset(19, "train-la-hot-afternoon-studio").observation;
for (let index = 0; index < 12; index += 1) {
observation = environment.step(scripted(observation)).observation;
}
return environment;
}
it("throws on a tampered snapshot, and refuses to keep running afterwards", () => {
const environment = midEpisode();
const snapshot = environment.snapshot();
assert.throws(
() => environment.restore({ ...snapshot, cumulativeReward: snapshot.cumulativeReward + 1 }),
/checksum mismatch/,
);
// Re-signed, so only the source pins can catch it.
const reserved = structuredClone(snapshot.simulation);
reserved.energyReserveKWh = -1;
const { checksum: _snapshotChecksum, ...snapshotCore } = {
...snapshot,
simulation: reserved,
};
assert.throws(
() => environment.restore({ ...snapshotCore, checksum: arenaChecksum(snapshotCore) }),
/simulation snapshot is invalid/,
);
// And the environment is now un-reset rather than half-restored: the
// episode bookkeeping was written before the simulation payload was
// rejected, so continuing would produce a mixture of two episodes.
assert.throws(() => environment.trace(), /must be reset/);
assert.throws(() => environment.snapshot(), /must be reset/);
// A fresh reset brings it back.
assert.equal(environment.reset(19, "train-la-hot-afternoon-studio").info.step, 0);
});
it("throws on a tampered trace", () => {
const environment = midEpisode();
const trace = environment.trace();
assert.throws(
() => new StudioOpsEnvironment().replay({
...trace,
cumulativeReward: trace.cumulativeReward + 1,
}),
/checksum mismatch/,
);
// A re-signed reward claim: the envelope's own checksum verifies, so the
// only thing standing between this and an accepted rollout is that `replay`
// recomputes the reward and compares it. This is the cheat the whole
// envelope exists to refuse.
const inflated = trace.steps.map((frame, index) =>
index === 4 ? { ...frame, reward: frame.reward + 1 } : frame,
);
const { checksum: _rewardChecksum, ...rewardCore } = { ...trace, steps: inflated };
assert.throws(
() => new StudioOpsEnvironment().replay({
...rewardCore,
checksum: arenaChecksum(rewardCore),
}),
/diverged at step 5/,
);
// And a re-signed action swap, which is refused wherever it first shows —
// at the frame whose checksum no longer matches, or at the final state if
// the swapped action happened to change nothing until the end.
const swapped = trace.steps.map((frame, index) =>
index === 4 ? { ...frame, action: { ...frame.action, speakerPlay: true } } : frame,
);
const { checksum: _traceChecksum, ...traceCore } = { ...trace, steps: swapped };
assert.throws(
() => new StudioOpsEnvironment().replay({
...traceCore,
checksum: arenaChecksum(traceCore),
}),
/diverged at step|final state mismatch/,
);
});
it("reproduces an episode exactly from the same seed, twice", () => {
for (const definition of STUDIO_OPS_SCENARIOS.definitions) {
const first = rollout(new StudioOpsEnvironment(), scripted, {
seed: 88,
scenario: definition.id,
});
const second = rollout(new StudioOpsEnvironment(), scripted, {
seed: 88,
scenario: definition.id,
});
assert.deepEqual(first, second, definition.id);
}
});
});
describe("studio-ops couples its variables rather than stacking five tasks", () => {
it("charges more for the same energy under more cloud, strictly", () => {
// The reward's own implementation, over a fine grid: `advanceSimulation`
// calls exactly this function with exactly these arguments.
for (const loadKw of [0.42, 2.4, 13.9, 152]) {
let previous = 0;
for (let cloud = 0; cloud <= 1.0001; cloud += 0.02) {
const penalty = studioOpsEnergyPenalty(loadKw, cloud);
assert.ok(penalty < 0, `${loadKw}@${cloud}`);
if (cloud > 0) {
assert.ok(
Math.abs(penalty) > Math.abs(previous),
`magnitude did not rise at cloud=${cloud}, load=${loadKw}`,
);
}
previous = penalty;
}
}
// And it is monotone in the load as well, which is the other half of the
// claim that this is an energy price and not a weather penalty.
assert.ok(
Math.abs(studioOpsEnergyPenalty(150, 0.5)) > Math.abs(studioOpsEnergyPenalty(2, 0.5)),
);
});
it("charges more under an overcast sky end to end, even against a heavier load", () => {
// Stronger than "all else equal": the hot LA afternoon draws strictly more
// power than the overcast one (a 13 K climate gap against a 4 K one) and is
// still charged less, because the cloud term dominates the load difference.
const energyAt = (scenarioId: string): { energy: number; cloud: number } => {
const environment = new StudioOpsEnvironment();
environment.reset(1, scenarioId);
const result = environment.step(STUDIO_OPS_INACTION);
return {
energy: (result.rewardComponents as StudioOpsReward).energy,
cloud: (result.observation as Observation).cloudCover,
};
};
const clear = energyAt("train-la-hot-afternoon-studio");
const overcast = energyAt("train-la-overcast-inspection");
assert.ok(clear.cloud < 0.25, `clear cloud=${clear.cloud}`);
assert.ok(overcast.cloud > 0.7, `overcast cloud=${overcast.cloud}`);
assert.ok(
Math.abs(overcast.energy) > Math.abs(clear.energy),
`${overcast.energy} vs ${clear.energy}`,
);
});
it("charges more for playback the closer an aircraft is, and nothing when silent", () => {
const base = {
speakerPlaying: true,
speakerVolume: 0.6,
windKph: 5,
micLive: false,
deskOccupied: false,
};
let previous = 0;
// Walking the aircraft in from beyond audible range to directly overhead.
for (let slant = 5000; slant >= 0; slant -= 100) {
const penalty = studioOpsNoisePenalty({ ...base, nearestAircraftSlantM: slant });
if (slant < 5000) {
assert.ok(penalty < previous, `did not worsen at slant=${slant}`);
}
previous = penalty;
}
assert.ok(previous < 0);
// Beyond the overhead range it is flat, not negative: an aircraft that
// cannot be heard costs nothing.
assert.equal(studioOpsNoisePenalty({ ...base, nearestAircraftSlantM: 9000 }), 0);
// And a silent speaker cannot be ruined by anything at all.
assert.equal(
studioOpsNoisePenalty({ ...base, speakerPlaying: false, nearestAircraftSlantM: 0 }),
0,
);
assert.equal(
studioOpsNoisePenalty({ ...base, speakerVolume: 0, nearestAircraftSlantM: 0 }),
0,
);
});
it("adds wind and microphone bleed to the same penalty", () => {
const quiet = {
speakerPlaying: true,
speakerVolume: 0.5,
nearestAircraftSlantM: 40_000,
windKph: 5,
micLive: false,
deskOccupied: false,
};
assert.equal(studioOpsNoisePenalty(quiet), 0);
assert.ok(studioOpsNoisePenalty({ ...quiet, windKph: 80 }) < 0);
assert.ok(studioOpsNoisePenalty({ ...quiet, micLive: true, deskOccupied: true }) < 0);
// Bleed needs both: a live microphone in an empty room is not on the take.
assert.equal(studioOpsNoisePenalty({ ...quiet, micLive: true }), 0);
});
it("computes the noise component from the sky and wind it publishes", () => {
// The wiring, end to end: whatever the observation says the sky and the
// wind are doing is what the penalty was computed from. A regression that
// read a stale step's weather would break here and nowhere else.
const environment = new StudioOpsEnvironment();
let observation = environment.reset(31, "train-la-hot-afternoon-studio")
.observation as Observation;
let sawOverhead = false;
for (let index = 0; index < 700; index += 1) {
const result = environment.step({
...STUDIO_OPS_INACTION,
micMute: true,
speakerVolume: 0.5,
speakerPlay: true,
});
observation = result.observation as Observation;
const expected = studioOpsNoisePenalty({
speakerPlaying: observation.speakerPlaying,
speakerVolume: observation.speakerVolume,
nearestAircraftSlantM: observation.nearestAircraftSlantM,
windKph: observation.windKph,
micLive: observation.micPowered && !observation.micMuted,
deskOccupied: observation.deskOccupied,
});
assert.equal((result.rewardComponents as StudioOpsReward).noise, expected, `step ${index}`);
if (observation.aircraftOverheadCount > 0) sawOverhead = true;
if (result.terminated || result.truncated) break;
}
assert.ok(sawOverhead, "no aircraft came overhead; the assertion above proved nothing");
});
it("couples the microphone to the robot's job rather than to a schedule", () => {
// The non-negotiable property, observable: the desk in front of
// `sf-desk-mic` is occupied exactly while the robot is working the station
// that stands at it, and a mic left live through the rest of the episode is
// charged for it.
const environment = new StudioOpsEnvironment();
let observation = environment.reset(1, "train-sf-clear-morning-desk-check")
.observation as Observation;
let occupied = 0;
let wasted = 0;
let ready = 0;
for (let index = 0; index < 1200; index += 1) {
const result = environment.step({ ...STUDIO_OPS_INACTION, ...scripted(observation), micMute: false });
observation = result.observation as Observation;
const components = result.rewardComponents as StudioOpsReward;
if (observation.deskOccupied) occupied += 1;
if (components.audioWaste < 0) wasted += 1;
if (components.audioReady > 0) ready += 1;
if (result.terminated || result.truncated) break;
}
assert.ok(occupied > 0, "the robot never reached the desk its microphone serves");
assert.ok(ready >= occupied, "a live mic at an occupied desk must be paid for");
assert.ok(wasted > 0, "a live mic at an empty desk must be charged for");
});
it("stops charging the mic the moment it is muted", () => {
const environment = new StudioOpsEnvironment();
let observation = environment.reset(1, "train-la-overcast-inspection")
.observation as Observation;
const hot = environment.step({ ...STUDIO_OPS_INACTION, micMute: false });
assert.equal((hot.rewardComponents as StudioOpsReward).audioWaste < 0, true);
observation = hot.observation as Observation;
assert.equal(observation.deskOccupied, false);
const muted = environment.step({ ...STUDIO_OPS_INACTION, micMute: true });
assert.equal((muted.rewardComponents as StudioOpsReward).audioWaste, 0);
});
it("shapes the vehicle with a bounded potential that cannot be farmed", () => {
// Potential-based: plugging and unplugging round-trips to zero rather than
// paying twice, because the term is the *change* in a bounded readiness.
const required = 61.6;
assert.equal(studioVehicleReadiness(0, 21, required), 0.5);
assert.equal(studioVehicleReadiness(required, 21, required), 1);
assert.ok(studioVehicleReadiness(61, 21, required) < studioVehicleReadiness(61.5, 21, required));
assert.ok(studioVehicleReadiness(61, 30, required) < studioVehicleReadiness(61, 22, required));
// Bounded on both sides, so the shaping cannot diverge.
for (const soc of [-10, 0, 50, 500]) {
for (const cabin of [-40, 21, 90]) {
const value = studioVehicleReadiness(soc, cabin, required);
assert.ok(value >= 0 && value <= 1, `${soc}/${cabin}`);
}
}
});
});
describe("studio-ops weather and sky are scenario parameters, deterministically evolved", () => {
const parameters = STUDIO_OPS_SCENARIOS.resolve(7, "dev-sf-windy-evening-delivery").parameters;
it("is a pure function of the scenario and the elapsed time", () => {
for (const seconds of [0, 13.7, 60, 119.9]) {
assert.deepEqual(
studioWeatherAt(parameters, seconds),
studioWeatherAt(parameters, seconds),
);
}
assert.deepEqual(studioOverflights(parameters), studioOverflights(parameters));
});
it("actually moves inside one episode, in every field a policy can read", () => {
const samples = [0, 20, 40, 60, 80, 100, 119].map((s) => studioWeatherAt(parameters, s));
for (const field of ["cloudCover", "precipitation", "windKph", "windDirDeg"] as const) {
const values = new Set(samples.map((sample) => sample[field]));
assert.ok(values.size > 1, `${field} never changed`);
}
const sky = [0, 30, 60, 90, 119].map((s) => studioSkyAt(studioOverflights(parameters), s));
assert.ok(new Set(sky.map((entry) => entry.nearestSlantM)).size > 1);
});
it("keeps every reading inside the range its own space declares", () => {
for (let seconds = 0; seconds <= 120; seconds += 0.5) {
const weather = studioWeatherAt(parameters, seconds);
assert.ok(weather.cloudCover >= 0 && weather.cloudCover <= 1);
assert.ok(weather.precipitation >= 0 && weather.precipitation <= 1);
assert.ok(weather.windKph >= 0);
assert.ok(weather.windDirDeg >= 0 && weather.windDirDeg < 360);
assert.ok(weather.visibilityKm >= 0.2);
}
});
it("never claims a profile was observed when it was invented", () => {
// Every shipped scenario is a fixture, and the flag says so. The field
// exists for an operator who freezes a real observation into one.
for (const definition of STUDIO_OPS_SCENARIOS.definitions) {
const resolved = STUDIO_OPS_SCENARIOS.resolve(3, definition.id);
assert.equal(resolved.parameters.weatherReported, false, definition.id);
const environment = new StudioOpsEnvironment();
const observation = environment.reset(3, definition.id).observation as Observation;
assert.equal(observation.weatherReported, false, definition.id);
}
});
it("puts the sky and the weather in the scenario hash, where a trace can carry them", () => {
const a = STUDIO_OPS_SCENARIOS.resolve(3, "train-la-overcast-inspection");
const b = STUDIO_OPS_SCENARIOS.resolve(4, "train-la-overcast-inspection");
assert.notEqual(a.hash, b.hash);
assert.notEqual(a.parameters.aircraftScheduleSeed, b.parameters.aircraftScheduleSeed);
assert.notEqual(a.parameters.cloudPhase, b.parameters.cloudPhase);
// The jitter must not turn a named profile into a different one.
assert.ok(a.parameters.cloudCoverBase > 0.85 && b.parameters.cloudCoverBase > 0.85);
});
});
describe("studio-ops quantises everything a transcendental touched", () => {
it("reports no observation finer than the quantum", () => {
// The cross-runtime defence, checked where it has to hold: if any of these
// carried full double precision, a verifier on other hardware could reject
// an honest rollout over the last bit of a sine.
const environment = new StudioOpsEnvironment();
let observation = environment.reset(2, "train-la-hot-afternoon-studio")
.observation as Observation;
const fields = [
"sunAltitudeDeg", "sunAzimuthDeg", "hourOfDay", "cloudCover", "precipitation",
"visibilityKm", "windKph", "windDirDeg", "nearestAircraftSlantM",
"vehicleSocPct", "vehicleCabinC", "energyReservePct",
] as const;
for (let index = 0; index < 400; index += 1) {
for (const field of fields) {
const value = observation[field];
assert.equal(value, quantizeObservable(value), `${field} at step ${index}`);
}
const result = environment.step(scripted(observation));
observation = result.observation as Observation;
if (result.terminated || result.truncated) break;
}
});
it("puts no Date anywhere near the checksum", () => {
// `canonicalJson` throws on one, so this is a live proof rather than a
// convention: the snapshot survives being hashed.
const environment = new StudioOpsEnvironment();
environment.reset(2, "train-sf-clear-morning-desk-check");
environment.step(STUDIO_OPS_INACTION);
const snapshot = environment.snapshot();
assert.match(arenaChecksum(snapshot.simulation), /^fnv1a64:/);
assert.equal(typeof JSON.parse(JSON.stringify(snapshot.simulation)), "object");
});
});
describe("studio-ops wraps the simulators the renderer drives", () => {
it("simulates exactly the devices the shipped packs declare and Plan resolved", () => {
// Not a headless copy of the device list: the ids the scenarios name are
// the pack's own, and they are the ones `Plan` accepted.
const plans = {
"lumbridge-hq": new Plan(LUMBRIDGE_HQ, { depth: "public", warn: false }),
"mateo-court": new Plan(MATEO_COURT, { depth: "public", warn: false }),
};
for (const definition of STUDIO_OPS_SCENARIOS.definitions) {
const plan = plans[definition.parameters.officeId];
const mic = plan.device(definition.parameters.micId);
const speaker = plan.device(definition.parameters.speakerId);
assert.ok(mic, `${definition.id} names a microphone the plan did not resolve`);
assert.ok(speaker, `${definition.id} names a speaker the plan did not resolve`);
assert.equal(mic.kind, "mic");
assert.equal(speaker.kind, "speaker");
assert.equal(mic.provenance, "simulated");
assert.match(mic.disclosure.toLowerCase(), /simulat/);
}
});
it("names the same simulator stack in its manifest as it imports", () => {
assert.equal(STUDIO_OPS_MANIFEST.id, "studio-ops-v1");
for (const fragment of [
"Plan",
"robotActivity",
"createSimulatedDevices",
"createSimulatedVehicleTelemetry",
"solarPosition",
]) {
assert.ok(STUDIO_OPS_MANIFEST.simulator.includes(fragment), fragment);
}
});
it("observes the robot job, the hardware, the weather, the car and the sky at once", () => {
// The point of the environment, as a shape assertion: forty-four fields
// across five groups, none of them constant across the catalogue.
const environment = new StudioOpsEnvironment();
const observation = environment.reset(1, "dev-la-marine-layer-loft-delivery")
.observation as Observation;
assert.equal(Object.keys(observation).length, STUDIO_OPS_MANIFEST.observationFields.length);
assert.deepEqual(
Object.keys(observation).sort(),
[...STUDIO_OPS_MANIFEST.observationFields].sort(),
);
assert.equal(observation.officeId, "mateo-court");
assert.equal(observation.levelId, "level-2");
assert.equal(observation.micPowered, true);
assert.equal(observation.energyReservePct, 100);
assert.equal(observation.stepsToDeparture, 1000);
});
it("keeps the loft scenario's desk unreachable, on purpose", () => {
// Every LA device is on level 1 and this job runs on level 2, so the
// correct play is to mute and get on with it. A policy that has only seen a
// reachable desk has not learned the difference between "unmute when
// somebody arrives" and "unmute".
const environment = new StudioOpsEnvironment();
let observation = environment.reset(1, "dev-la-marine-layer-loft-delivery")
.observation as Observation;
for (let index = 0; index < 400; index += 1) {
const result = environment.step(scripted(observation));
observation = result.observation as Observation;
assert.equal(observation.deskOccupied, false, `step ${index}`);
if (result.terminated || result.truncated) break;
}
});
});
describe("studio-ops device commands settle instead of churning", () => {
it("stops re-commanding a setpoint the instrument has already reached", () => {
// A gain of 12.005 dB is reported back as 12.01, so an action compared
// against the reading would disagree with itself forever and be charged a
// churn cost on every step for holding a control still. `normalizeAction`
// rounds to the precision the instrument reports, which makes the setpoint
// a fixed point.
const environment = new StudioOpsEnvironment();
environment.reset(1, "train-la-overcast-inspection");
const action: StudioOpsAction = {
...STUDIO_OPS_INACTION,
micGain: 12.005,
micMute: true,
speakerVolume: 0.2225,
};
const first = environment.step(action);
const settled = environment.step(action);
const again = environment.step(action);
const controlOf = (result: typeof first): number =>
(result.rewardComponents as StudioOpsReward).control;
assert.ok(controlOf(first) < 0, "the first step really does issue commands");
assert.equal(controlOf(settled), controlOf(again));
assert.equal(controlOf(settled), 0);
const observation = again.observation as Observation;
assert.equal(observation.micGainDb, 12.01);
assert.equal(observation.speakerVolume, 0.223);
});
});