@adatechnology/game-test-room 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,6 +46,7 @@ const room = createTestRoom({
46
46
  { id: 'my_crate_breaks', name: 'My crate breaks', run: ({ room, step, expect }) => {
47
47
  const o = room.spawn('my_crate', [0, 0, -4])
48
48
  room.setTool('pistol_9mm')
49
+ room.waitUntilReady() // setTool plays the draw: the tool answers `drawing` until the rule's draw time is over
49
50
  room.look([0, 1, -2], [0, 0.5, -4])
50
51
  room.use()
51
52
  step(2)
@@ -68,6 +69,31 @@ const report = room.loadModule(RENTAL_CARS) // { module, items, scenarios, ok, p
68
69
 
69
70
  The items join the catalogue under the module's group and its scenarios become buttons. The contract checks run at once and write one line per result to the log: `checkDestructible` for every item with an `impl`, `checkAnimation` for every clip (`✓ Rental car: destructible contract ok (4 parts...)` or `✗ ...: <problem>`). The animations panel lists the clips of the aimed or held item; `room.playAnimation(clipId)` plays one (poses are offsets from the rest pose, events go to the log and `sound` events to `onSound`). The example (`examples/test-room`) loads a `DEMO_MODULE` rental car that owns its damage rule, its door `open` clip and a wheel that comes off with a wrench.
70
71
 
72
+ ## Module bench
73
+
74
+ A module that declares `controls`, `layers` or `metrics` (see `@adatechnology/game-contracts`) gets a bench: `loadModule` checks it with
75
+ `checkBench` (a `✓ ... bench ok` / `✗ ... bench broken` line), lends `host.bench` to `attach`, then sends one change per control and layer. The
76
+ panel between the animations and the tests shows the active module (the one of the last `openArea`; else the first loaded with a bench, a later module never takes the panel from the one on screen): a
77
+ range per number, a checkbox per boolean, buttons (up to four options) or a select per choice, a button per action, the layers, and the
78
+ module counters at 4 Hz with a bar when they have a budget (`warn` from 80 %, `bad` past it). A compact line on top shows the room counters
79
+ (`fps`, `frame_ms`, `draw_calls`, `triangles`, `objects`, `fragments`, `particles`, `decals`); `fps` uses the real frame time, so slow motion
80
+ does not change it.
81
+
82
+ ```ts
83
+ const room = createTestRoom({ ..., metrics: ['fps', 'draw_calls'], bench: { state: { lab: params.get('bench.lab') ?? '' }, onChange: (moduleId, encoded) => keepInUrl(moduleId, encoded) } })
84
+ room.bench()?.set('size', 2) // clamped and snapped; false for an unknown id, a wrong type or an option not offered
85
+ room.metrics() // { room: { fps, draw_calls, ... }, module: { particles: 120 } }
86
+ ```
87
+
88
+ The state is `id=value;id=value` (`encodeBenchState` / `decodeBenchState`); decoding treats it as hostile input. `bench.state` is keyed by
89
+ module id, so two modules can share a control id; a module loaded again keeps its live values. The room never touches
90
+ `location`: the example page keeps it in `?bench.<moduleId>=`. `fps` and `frame_ms` use the real frame time, even past the 50 ms the simulation is clamped to. Loading a module again detaches the previous `attach` first. Q now reports the new fire
91
+ mode (`mode` event) and `shot` events carry `rounds` and `mode`.
92
+
93
+ Grenades are cooked: hold the button to pull the pin and let go to throw with the fuse that is left (`cook` host events: `start`, `throw`, `burst` when it goes off in the hand; `room.use(hold)` does the same
94
+ by script). A charge set down gets a marker (`charge_marker`, a stake with a flag) and, when it waits for a trip wire (the Claymore), the wire is drawn (`charge_wire`) and the charge goes off by itself
95
+ when a body (the player, an object of the room) crosses it; whatever was already on the wire when it was set is let through until it steps off. Y only fires the charges for the remote detonator (C4).
96
+
71
97
  ## Controls
72
98
 
73
99
  | Input | Action |
@@ -110,6 +136,9 @@ Uma sala de testes em primeira pessoa para three.js. Você passa o seu catálogo
110
136
  derrama líquidos, e lê o que aconteceu em linguagem simples no registro de comportamento (também exportável em JSON, NDJSON ou texto). Os cenários
111
137
  automáticos conferem o resultado e gravam aprovado/reprovado no mesmo registro. Use `locale: 'pt-BR'` para textos em português.
112
138
 
139
+ Um módulo que declara controles, camadas ou contadores ganha um painel de bancada genérico; o estado vai para a URL do exemplo (`?bench.<idDoMódulo>=`) e
140
+ `room.bench()` / `room.metrics()` dirigem e leem a bancada por script.
141
+
113
142
  ## Known limitations
114
143
 
115
144
  The quality select does not recreate the destruction renderer; it only changes the room's own settings. Unit tests use an injected stub renderer (no WebGL).
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ import { SpecFromObjectOptions, DestructionRenderer, Quality } from '@adatechnol
5
5
  import { Arsenal, WeaponState, WeaponDef } from '@adatechnology/game-weapons';
6
6
  import { FluidRenderer } from '@adatechnology/game-fluids-three';
7
7
  import { ContainerSpec, FluidWorld, LiquidDef, ItemUseDef, SurfaceSpec } from '@adatechnology/game-fluids';
8
- import { DestructibleImpl, AnimationClipSpec, TestableModule } from '@adatechnology/game-contracts';
8
+ import { ModuleControl, ControlValue, ModuleLayer, DestructibleImpl, AnimationClipSpec, TestableModule } from '@adatechnology/game-contracts';
9
9
  export { AnimationClipSpec, DestructibleImpl, ItemAnimationContract, ModuleScenario, TestableItem, TestableModule } from '@adatechnology/game-contracts';
10
10
 
11
11
  type NewEntry = NewLogEntry;
@@ -186,6 +186,40 @@ interface TestRoomOptions {
186
186
  onSound?: (key: string, point: Vec3, volume: number) => void;
187
187
  /** Show the built-in HUD and panels (default true). */
188
188
  ui?: boolean;
189
+ /** Room counters the bench panel shows and `TestRoom.metrics()` returns (default all); `false` for none. */
190
+ metrics?: readonly RoomMetricId[] | false;
191
+ /**
192
+ * Module bench state. `state` maps a module id to its encoded state, restored when that module is first loaded (a module reloaded
193
+ * keeps its live state instead) before its first changes are sent; ids are per module, so two modules may share a control id.
194
+ * `onChange` gets the encoded state of a module whenever it changes (a page keeps it in its URL). The room never touches `location`.
195
+ */
196
+ bench?: {
197
+ readonly state?: Readonly<Record<string, string>>;
198
+ readonly onChange?: (moduleId: string, encoded: string) => void;
199
+ };
200
+ }
201
+ /** Counters the room measures itself. `fps` and `frame_ms` use the real frame time, so slow motion does not change them. */
202
+ type RoomMetricId = 'fps' | 'frame_ms' | 'draw_calls' | 'triangles' | 'objects' | 'fragments' | 'particles' | 'decals';
203
+ /** A module control as the room holds it: the declaration with the module's patches applied, and its value (none for an action). */
204
+ type BenchControl = ModuleControl & {
205
+ readonly disabled: boolean;
206
+ readonly value?: ControlValue;
207
+ };
208
+ type BenchLayer = ModuleLayer & {
209
+ readonly on: boolean;
210
+ };
211
+ /** The bench of a loaded module, driven from outside the panel (tests, scripts, a page that keeps the state in its URL). */
212
+ interface BenchHandle {
213
+ controls(): BenchControl[];
214
+ layers(): BenchLayer[];
215
+ value(id: string): ControlValue | undefined;
216
+ layer(id: string): boolean;
217
+ /** Numbers are clamped and snapped to the step. False for an unknown id, a wrong type, an option not offered or a disabled control. */
218
+ set(id: string, value: ControlValue): boolean;
219
+ setLayer(id: string, on: boolean): boolean;
220
+ act(id: string): boolean;
221
+ /** `id=value;id=value`, the format `TestRoomOptions.bench.state[moduleId]` reads back. */
222
+ encode(): string;
189
223
  }
190
224
  interface SpawnedObject {
191
225
  objectId: string;
@@ -230,6 +264,8 @@ interface TestRoom {
230
264
  spawned(): SpawnedObject[];
231
265
  /** Advance one frame (only needed when the host owns the loop). */
232
266
  update(dt: number): void;
267
+ /** Advance frames until the tool in hand has no phase left (the draw `setTool` plays, a reload, a cycle) or `maxSeconds` pass; plus one frame so the next pull is not inside the phase; returns the seconds waited. */
268
+ waitUntilReady(maxSeconds?: number): number;
233
269
  on(type: 'spawn' | 'tool' | 'grab' | 'throw' | 'event', fn: (data: unknown) => void): () => void;
234
270
  /** Tilt the held item by `rad` radians around its own x axis (hold Z in the room). Pouring and spilling follow from the tilt. */
235
271
  tilt(rad: number): void;
@@ -273,6 +309,13 @@ interface TestRoom {
273
309
  * offsets from the rest pose, interpolated linearly; the item keeps the last pose. Events are logged (and `sound` events reach `onSound`).
274
310
  */
275
311
  playAnimation(clipId: string, objectId?: string): boolean;
312
+ /** The bench of a loaded module (default the active one: the module of the last `openArea`, else the last loaded with a bench), or null. */
313
+ bench(moduleId?: string): BenchHandle | null;
314
+ /** Room counters and the active module's metrics, read now. */
315
+ metrics(): {
316
+ room: Partial<Record<RoomMetricId, number>>;
317
+ module: Record<string, number | string>;
318
+ };
276
319
  dispose(): void;
277
320
  }
278
321
  /** What `TestRoom.loadModule` did. */
@@ -437,4 +480,9 @@ interface BuildFromSpecOptions {
437
480
  */
438
481
  declare function buildFromSpec(THREE: typeof THREE_NS, spec: DestructibleSpec, options?: BuildFromSpecOptions): THREE_NS.Group;
439
482
 
440
- export { type AnimationInfo, type BuildFromSpecOptions, DEMO_ITEMS, DEMO_MATERIALS, type HudState, type InspectInfo, type LogEntry, MATERIAL_COLORS, type ModuleReport, type NewEntry, type NewLogEntry, type RoomStrings, SCENARIOS, type ScenarioContext, type ScenarioReport, type SpawnedObject, type TestRoom, type TestRoomItem, type TestRoomLocale, type TestRoomLog, type TestRoomOptions, type TestRoomScenario, type ThreeNamespace, type WritableLog, brickWallSpec, buildFromSpec, createLog, createTestRoom, demoItems, locales, makeScenarios, resolveStrings, scenariosFor };
483
+ /** `id=value;id=value`, values with `encodeURIComponent`, booleans and layers as `1` / `0`. */
484
+ declare function encodeBenchState(controls: readonly BenchControl[], layers: readonly BenchLayer[]): string;
485
+ /** Reads an encoded state as hostile input: unknown ids, NaN, options not offered and malformed pairs are dropped; numbers are clamped. */
486
+ declare function decodeBenchState(encoded: string, controls: readonly ModuleControl[], layers: readonly ModuleLayer[]): Map<string, ControlValue>;
487
+
488
+ export { type AnimationInfo, type BenchControl, type BenchHandle, type BenchLayer, type BuildFromSpecOptions, DEMO_ITEMS, DEMO_MATERIALS, type HudState, type InspectInfo, type LogEntry, MATERIAL_COLORS, type ModuleReport, type NewEntry, type NewLogEntry, type RoomMetricId, type RoomStrings, SCENARIOS, type ScenarioContext, type ScenarioReport, type SpawnedObject, type TestRoom, type TestRoomItem, type TestRoomLocale, type TestRoomLog, type TestRoomOptions, type TestRoomScenario, type ThreeNamespace, type WritableLog, brickWallSpec, buildFromSpec, createLog, createTestRoom, decodeBenchState, demoItems, encodeBenchState, locales, makeScenarios, resolveStrings, scenariosFor };