@adatechnology/game-test-room 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +114 -2
- package/dist/index.d.ts +433 -0
- package/dist/index.global.js +15085 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +4543 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anderson Fernandes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,115 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @adatechnology/game-test-room
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A self-contained first-person test room for three.js. Drop your own catalogue of objects in, walk around, shoot, hit, burn, cut, throw, drink and pour,
|
|
4
|
+
and read what happened in plain words in an on-screen behaviour log that you (or an agent) can also export as JSON or NDJSON. Scripted scenarios
|
|
5
|
+
check the behaviour and write pass/fail lines to the same log.
|
|
6
|
+
|
|
7
|
+
It composes `@adatechnology/game-destruction`, `game-destruction-three`, `game-weapons`, `game-fluids` and `game-fluids-three`. `three` is a peer dependency used only as a type: you pass the namespace in.
|
|
8
|
+
|
|
9
|
+
Contracts live in [`@adatechnology/game-contracts`](../game-contracts); the types are re-exported here, so existing imports keep working.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pnpm add @adatechnology/game-test-room three
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import * as THREE from 'three'
|
|
21
|
+
import { createTestRoom, demoItems } from '@adatechnology/game-test-room'
|
|
22
|
+
|
|
23
|
+
const room = createTestRoom({
|
|
24
|
+
THREE,
|
|
25
|
+
container: document.getElementById('app')!,
|
|
26
|
+
locale: 'en', // 'en' or 'pt-BR'
|
|
27
|
+
catalog: demoItems('en'),
|
|
28
|
+
})
|
|
29
|
+
room.runScenarios().then((report) => console.log(report.passed, report.failed))
|
|
30
|
+
console.log(room.log.toNDJSON())
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The IIFE build exposes `GameTestRoom`. Without an injected `renderer` the room owns the canvas and its render loop.
|
|
34
|
+
|
|
35
|
+
## Plugging in your own catalogue and scenarios
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const room = createTestRoom({
|
|
39
|
+
THREE,
|
|
40
|
+
container,
|
|
41
|
+
catalog: [
|
|
42
|
+
...demoItems('en'),
|
|
43
|
+
{ id: 'my_crate', name: 'My crate', group: 'Mine', build: (T) => buildMyCrate(T), specOptions: { /* material per mesh name */ } },
|
|
44
|
+
],
|
|
45
|
+
scenarios: [
|
|
46
|
+
{ id: 'my_crate_breaks', name: 'My crate breaks', run: ({ room, step, expect }) => {
|
|
47
|
+
const o = room.spawn('my_crate', [0, 0, -4])
|
|
48
|
+
room.setTool('pistol_9mm')
|
|
49
|
+
room.look([0, 1, -2], [0, 0.5, -4])
|
|
50
|
+
room.use()
|
|
51
|
+
step(2)
|
|
52
|
+
expect(room.world.state(o.objectId)!.parts.some((p) => p.status !== 'intact'), 'a part was hurt')
|
|
53
|
+
} },
|
|
54
|
+
],
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`DEMO_MATERIALS` is now empty (kept for compatibility: the demo items use only core materials). `buildFromSpec` turns a `DestructibleSpec` into meshes,
|
|
59
|
+
and `brickWallSpec` / `DEMO_ITEMS` / `SCENARIOS` are exported to reuse or extend.
|
|
60
|
+
|
|
61
|
+
## Loading a module
|
|
62
|
+
|
|
63
|
+
A game module hands the room a `TestableModule` from `@adatechnology/game-contracts` (items, each with an optional `impl` for its own destruction and `animations`, plus scenarios):
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const report = room.loadModule(RENTAL_CARS) // { module, items, scenarios, ok, problems }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
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
|
+
## Controls
|
|
72
|
+
|
|
73
|
+
| Input | Action |
|
|
74
|
+
|---|---|
|
|
75
|
+
| Click / Esc | capture / release the mouse (without pointer lock: drag to look) |
|
|
76
|
+
| WASD, Shift, Space | walk, run, jump |
|
|
77
|
+
| 1-0, wheel | pick a tool from the hotbar |
|
|
78
|
+
| Left button | use the tool (shoot, hit, flame, heat, throw, work a joint, extinguish, grab) |
|
|
79
|
+
| E | grab / drop |
|
|
80
|
+
| R | rotate the held item |
|
|
81
|
+
| Right button or G | throw |
|
|
82
|
+
| F | use a fluid item (drink); Z tilt; C fill from the tap; V pour (holding a fluid item) or kick (otherwise, with the `kick` tool whatever is selected) |
|
|
83
|
+
| Tab | show / hide the panels |
|
|
84
|
+
|
|
85
|
+
Panels: catalogue tabs (spawn, Clear, Reset all), slow motion 0.25x, time x10, quality select, inspector (material, state, hp, temperature, char, marks, joints, load and stress) and the behaviour log with filters.
|
|
86
|
+
|
|
87
|
+
## Log format
|
|
88
|
+
|
|
89
|
+
One JSON object per line (`log.toNDJSON()`, "Baixar NDJSON"):
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{"seq":1,"t":0,"channel":"action","type":"spawn","text":"Spawned Steel sheet with stand at (0.0, 0.0, -4.0)","objectId":"steel_sheet_1","itemId":"steel_sheet","data":{"position":[0,0,-4],"movable":false,"massKg":43.6}}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`channel` is `action`, `event` or `check`; `t` is room time in seconds; `data` is machine-readable. `log.toText()` gives the plain-text version and `log.entries({ type, objectId })` filters.
|
|
96
|
+
|
|
97
|
+
## Scenarios
|
|
98
|
+
|
|
99
|
+
`room.scenarios` lists them; `room.runScenarios(list?)` resets the room, runs each one deterministically with `step(seconds)` instead of rendering, and returns
|
|
100
|
+
`{ passed, failed, results: [{ id, name, ok, checks: [{ label, ok }] }] }`. 21 ship with the package (bullets vs steel, concrete and glass, hammer, shotgun, kick, screwdriver,
|
|
101
|
+
fire and torch, gas bottle, bottle drop, thrown chair, coffee sips and spills, jerrycan leak, fuel fire, columns and a roof, extinguisher). Open the example with `?scenario=all` to run them.
|
|
102
|
+
|
|
103
|
+
### Inside a claude.ai Artifact
|
|
104
|
+
|
|
105
|
+
An Artifact page cannot use `<a download>` or blob links. When `globalThis.claude.use('downloads')` resolves, `log.download(format)` offers the file through that capability (the NDJSON goes out as `test-room-log.ndjson.txt`, since `.ndjson` is not on the allowlist) and a declined save writes an `export_cancelled` line to the log; otherwise it falls back to the blob link. **The artifact page must declare the `downloads` capability.** "Copy JSON" falls back to selecting the text in a textarea when `navigator.clipboard` is missing.
|
|
106
|
+
|
|
107
|
+
## Português
|
|
108
|
+
|
|
109
|
+
Uma sala de testes em primeira pessoa para three.js. Você passa o seu catálogo de objetos, anda, atira, bate, queima, corta, arremessa, bebe café e
|
|
110
|
+
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
|
+
automáticos conferem o resultado e gravam aprovado/reprovado no mesmo registro. Use `locale: 'pt-BR'` para textos em português.
|
|
112
|
+
|
|
113
|
+
## Known limitations
|
|
114
|
+
|
|
115
|
+
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
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import * as THREE_NS from 'three';
|
|
2
|
+
import { DestructibleSpec, Vec3, DestructionWorld, DestructionOptions, DestructionEvent, MaterialDef } from '@adatechnology/game-destruction';
|
|
3
|
+
export { DestructionEvent, Vec3 } from '@adatechnology/game-destruction';
|
|
4
|
+
import { SpecFromObjectOptions, DestructionRenderer, Quality } from '@adatechnology/game-destruction-three';
|
|
5
|
+
import { Arsenal, WeaponState, WeaponDef } from '@adatechnology/game-weapons';
|
|
6
|
+
import { FluidRenderer } from '@adatechnology/game-fluids-three';
|
|
7
|
+
import { ContainerSpec, FluidWorld, LiquidDef, ItemUseDef, SurfaceSpec } from '@adatechnology/game-fluids';
|
|
8
|
+
import { DestructibleImpl, AnimationClipSpec, TestableModule } from '@adatechnology/game-contracts';
|
|
9
|
+
export { AnimationClipSpec, DestructibleImpl, ItemAnimationContract, ModuleScenario, TestableItem, TestableModule } from '@adatechnology/game-contracts';
|
|
10
|
+
|
|
11
|
+
type NewEntry = NewLogEntry;
|
|
12
|
+
/** The log the room writes to (a `TestRoomLog` plus the write side). */
|
|
13
|
+
interface WritableLog extends TestRoomLog {
|
|
14
|
+
/** Called before every read, so pending work (the room batches events per frame) is flushed first. */
|
|
15
|
+
setBeforeRead(fn: (() => void) | null): void;
|
|
16
|
+
setClock(fn: () => number): void;
|
|
17
|
+
}
|
|
18
|
+
interface LogOptions {
|
|
19
|
+
/** Text of the `action` line written when the viewer declines (or fails) an export from an Artifact page. */
|
|
20
|
+
exportCancelled?: () => string;
|
|
21
|
+
}
|
|
22
|
+
declare function createLog(options?: LogOptions): WritableLog;
|
|
23
|
+
|
|
24
|
+
/** Locales shipped with the package. Unknown locales fall back to `en`. */
|
|
25
|
+
type TestRoomLocale = 'en' | 'pt-BR';
|
|
26
|
+
interface RoomStrings {
|
|
27
|
+
/** Display name per demo item id (see `DEMO_ITEMS`). */
|
|
28
|
+
items: Record<string, string>;
|
|
29
|
+
/** Display name per demo catalogue group. */
|
|
30
|
+
groups: Record<string, string>;
|
|
31
|
+
/** Every other text: UI, log lines, scenario names and checks. `{name}` placeholders are replaced. */
|
|
32
|
+
text: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
declare const locales: Record<TestRoomLocale, RoomStrings>;
|
|
35
|
+
declare const resolveStrings: (locale: string | undefined) => RoomStrings;
|
|
36
|
+
|
|
37
|
+
interface InspectInfo {
|
|
38
|
+
object: string;
|
|
39
|
+
objectId: string;
|
|
40
|
+
part: string;
|
|
41
|
+
material: string;
|
|
42
|
+
status: string;
|
|
43
|
+
hp: number;
|
|
44
|
+
maxHp: number;
|
|
45
|
+
temperature: number;
|
|
46
|
+
burning: boolean;
|
|
47
|
+
char: number;
|
|
48
|
+
melt: number;
|
|
49
|
+
liters?: number;
|
|
50
|
+
dents: number;
|
|
51
|
+
holes: number;
|
|
52
|
+
cracks: number;
|
|
53
|
+
mass: number;
|
|
54
|
+
/** Structural load in kg and stress (load / capacity) when the core reports them. */
|
|
55
|
+
load?: number;
|
|
56
|
+
stress?: number;
|
|
57
|
+
joints: Array<{
|
|
58
|
+
id: string;
|
|
59
|
+
kind: string;
|
|
60
|
+
tools: string[];
|
|
61
|
+
open: boolean;
|
|
62
|
+
progress: number;
|
|
63
|
+
}>;
|
|
64
|
+
note?: string;
|
|
65
|
+
distance: number;
|
|
66
|
+
}
|
|
67
|
+
interface HudState {
|
|
68
|
+
toolName: string;
|
|
69
|
+
toolId: string;
|
|
70
|
+
ammo: string;
|
|
71
|
+
slots: string[];
|
|
72
|
+
slot: number;
|
|
73
|
+
page: number;
|
|
74
|
+
pages: number;
|
|
75
|
+
status: string;
|
|
76
|
+
progress: {
|
|
77
|
+
label: string;
|
|
78
|
+
value: number;
|
|
79
|
+
} | null;
|
|
80
|
+
held: {
|
|
81
|
+
name: string;
|
|
82
|
+
uses: Array<{
|
|
83
|
+
key: string;
|
|
84
|
+
label: string;
|
|
85
|
+
}>;
|
|
86
|
+
weapon?: string;
|
|
87
|
+
using: string;
|
|
88
|
+
} | null;
|
|
89
|
+
inspect: InspectInfo | null;
|
|
90
|
+
/** Animations of the aimed or held item. */
|
|
91
|
+
animations: AnimationInfo | null;
|
|
92
|
+
locked: boolean;
|
|
93
|
+
timeScale: number;
|
|
94
|
+
fast: boolean;
|
|
95
|
+
tap: boolean;
|
|
96
|
+
quality: string;
|
|
97
|
+
objects: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Public types of @adatechnology/game-test-room.
|
|
102
|
+
*
|
|
103
|
+
* A self-contained test room any team can drop into its viewer: a floor with a target wall, shelves and a table, a first-person player
|
|
104
|
+
* (pointer lock, WASD) with an orbit fallback, a catalogue of the host's objects, and tools. The player can spawn objects, grab them, carry,
|
|
105
|
+
* drop and throw them (a thrown or dropped object takes and deals blunt damage), shoot them with real calibres, hit, cut, burn, melt, blow up
|
|
106
|
+
* and take them apart joint by joint, inspect the state of every part, and use items that hold liquids (fill a cup, drink a coffee, pour, spill, shoot a jerrycan and watch it leak).
|
|
107
|
+
* Every action and result goes to a behaviour log shown on screen in plain words and exportable as JSON / NDJSON, and scripted scenarios run
|
|
108
|
+
* from buttons (or headless in tests) and write pass/fail checks to the same log, so people and agents can verify the behaviour.
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
type ThreeNamespace = typeof THREE_NS;
|
|
112
|
+
/** One entry of the catalogue: something the host can build. */
|
|
113
|
+
interface TestRoomItem {
|
|
114
|
+
/** snake_case id. */
|
|
115
|
+
id: string;
|
|
116
|
+
/** Display name (already translated by the host). */
|
|
117
|
+
name: string;
|
|
118
|
+
/** Catalogue tab (free text: e.g. `Furniture`, `Vehicles`, `Plants`, `House`, `Weapons and items`). */
|
|
119
|
+
group: string;
|
|
120
|
+
/** Build the three.js object, origin at the centre of its footprint on the floor, front facing +z. */
|
|
121
|
+
build: (THREE: ThreeNamespace) => THREE_NS.Object3D;
|
|
122
|
+
/** Destruction spec. Default: `specFromObject3D` on the built object with `specOptions`. A function gets the built root. */
|
|
123
|
+
spec?: DestructibleSpec | ((root: THREE_NS.Object3D) => DestructibleSpec);
|
|
124
|
+
specOptions?: SpecFromObjectOptions;
|
|
125
|
+
/** Can be grabbed and thrown (default: total mass <= `maxCarryKg`). Vehicles and trees are usually `false`. */
|
|
126
|
+
movable?: boolean;
|
|
127
|
+
/** Short note shown in the inspector. */
|
|
128
|
+
note?: string;
|
|
129
|
+
/**
|
|
130
|
+
* The item holds a liquid (a cup of coffee, a bottle, a jerrycan, a fuel tank). `part` is the destruction part that is the container (a
|
|
131
|
+
* bullet hole in it makes it leak). `uses` are the item uses offered while it is held (`drink`, `pour`...; defaults from `ITEM_USES`).
|
|
132
|
+
*/
|
|
133
|
+
fluid?: {
|
|
134
|
+
container: ContainerSpec;
|
|
135
|
+
part?: string;
|
|
136
|
+
uses?: string[];
|
|
137
|
+
offset?: Vec3;
|
|
138
|
+
};
|
|
139
|
+
/** The item is also a weapon the player can equip from the catalogue (e.g. a crowbar, a knife, a pistol on the table). */
|
|
140
|
+
weapon?: string;
|
|
141
|
+
/**
|
|
142
|
+
* The item's own destruction (a `DestructibleImpl` of a game module). The room registers it in the world (`world.register`) instead of
|
|
143
|
+
* building a default object from `spec`; `id` is the object id the room gives it and `DestructibleImpl.id` must be that id. Impl items are not movable.
|
|
144
|
+
*/
|
|
145
|
+
impl?: (root: THREE_NS.Object3D, id: string) => DestructibleImpl;
|
|
146
|
+
/** Use animations the item brings (see `TestRoom.playAnimation`). Part poses move the child of the built object named after the part id. */
|
|
147
|
+
animations?: AnimationClipSpec[];
|
|
148
|
+
}
|
|
149
|
+
interface TestRoomOptions {
|
|
150
|
+
THREE: ThreeNamespace;
|
|
151
|
+
/** Element the bench fills (canvas + HUD). */
|
|
152
|
+
container: HTMLElement;
|
|
153
|
+
/** `en` (default) or `pt-BR`. */
|
|
154
|
+
locale?: string;
|
|
155
|
+
catalog: TestRoomItem[];
|
|
156
|
+
/** Weapons and tools in the hotbar: ids of the arsenal (default `DEFAULT_LOADOUT`), plus extra definitions merged into the arsenal. */
|
|
157
|
+
loadout?: string[];
|
|
158
|
+
weapons?: WeaponDef[];
|
|
159
|
+
/** An existing arsenal (default `createArsenal({ locale })`). */
|
|
160
|
+
arsenal?: Arsenal;
|
|
161
|
+
/** Fluids: an existing world, extra liquids, item uses, and surfaces (the arena floor and the table are added automatically). */
|
|
162
|
+
fluids?: FluidWorld;
|
|
163
|
+
liquids?: LiquidDef[];
|
|
164
|
+
itemUses?: ItemUseDef[];
|
|
165
|
+
surfaces?: SurfaceSpec[];
|
|
166
|
+
/** Extra scripted checks offered as buttons (the built-in `SCENARIOS` are always there). */
|
|
167
|
+
scenarios?: TestRoomScenario[];
|
|
168
|
+
/** Use the host's renderer/scene/camera instead of making its own (then the host calls `bench.update(dt)` every frame). */
|
|
169
|
+
renderer?: THREE_NS.WebGLRenderer;
|
|
170
|
+
scene?: THREE_NS.Scene;
|
|
171
|
+
camera?: THREE_NS.PerspectiveCamera;
|
|
172
|
+
/** World options (seed, extra materials). Or pass an existing world. */
|
|
173
|
+
world?: DestructionWorld;
|
|
174
|
+
worldOptions?: DestructionOptions;
|
|
175
|
+
quality?: Quality;
|
|
176
|
+
/** Heaviest object the player can carry (default 60 kg). */
|
|
177
|
+
maxCarryKg?: number;
|
|
178
|
+
/** Arena half size in metres (default 14). */
|
|
179
|
+
arenaSize?: number;
|
|
180
|
+
/** Spawn these catalogue ids on start, in a row in front of the player. */
|
|
181
|
+
initial?: string[];
|
|
182
|
+
/** Called for every destruction event (to log, play sounds, sync over LAN). Fluid events reach the log too. */
|
|
183
|
+
onEvent?: (event: DestructionEvent) => void;
|
|
184
|
+
onSound?: (key: string, point: Vec3, volume: number) => void;
|
|
185
|
+
/** Show the built-in HUD and panels (default true). */
|
|
186
|
+
ui?: boolean;
|
|
187
|
+
}
|
|
188
|
+
interface SpawnedObject {
|
|
189
|
+
objectId: string;
|
|
190
|
+
itemId: string;
|
|
191
|
+
root: THREE_NS.Object3D;
|
|
192
|
+
movable: boolean;
|
|
193
|
+
}
|
|
194
|
+
interface TestRoom {
|
|
195
|
+
readonly world: DestructionWorld;
|
|
196
|
+
readonly renderer: DestructionRenderer;
|
|
197
|
+
readonly scene: THREE_NS.Scene;
|
|
198
|
+
readonly camera: THREE_NS.PerspectiveCamera;
|
|
199
|
+
/** Spawn a catalogue item at a world position (default in front of the player). */
|
|
200
|
+
spawn(itemId: string, position?: Vec3, rotationY?: number): SpawnedObject;
|
|
201
|
+
/** Remove every spawned object, fragment and particle. */
|
|
202
|
+
clear(): void;
|
|
203
|
+
/** Repair every spawned object in place. */
|
|
204
|
+
resetAll(): void;
|
|
205
|
+
readonly arsenal: Arsenal;
|
|
206
|
+
readonly fluids: FluidWorld;
|
|
207
|
+
/** Equip a weapon or tool of the loadout (or any arsenal id). */
|
|
208
|
+
setTool(weaponId: string): void;
|
|
209
|
+
readonly tool: WeaponState;
|
|
210
|
+
/** Play an item use on the held item (`drink`, `pour`...). Returns false when nothing is held or the use does not apply. */
|
|
211
|
+
useItem(useId: string): boolean;
|
|
212
|
+
/** Use the current tool once from the camera (tests and scripted demos). `hold` seconds for continuous tools. */
|
|
213
|
+
use(hold?: number): void;
|
|
214
|
+
/** Grab what the camera looks at (or drop what is held). */
|
|
215
|
+
grab(): boolean;
|
|
216
|
+
/** Throw what is held at `speed` m/s (default 9). */
|
|
217
|
+
throwHeld(speed?: number): boolean;
|
|
218
|
+
/** Point the camera (scripted demos and tests). */
|
|
219
|
+
look(position: Vec3, target: Vec3): void;
|
|
220
|
+
/** Object and part the camera looks at, with its state, for the inspector. */
|
|
221
|
+
inspect(): {
|
|
222
|
+
objectId: string;
|
|
223
|
+
partId: string;
|
|
224
|
+
itemId: string;
|
|
225
|
+
} | null;
|
|
226
|
+
/** Everything the inspector panel shows for the aimed part (state, joints and the tools that undo them), or null. */
|
|
227
|
+
inspectInfo(): InspectInfo | null;
|
|
228
|
+
spawned(): SpawnedObject[];
|
|
229
|
+
/** Advance one frame (only needed when the host owns the loop). */
|
|
230
|
+
update(dt: number): void;
|
|
231
|
+
on(type: 'spawn' | 'tool' | 'grab' | 'throw' | 'event', fn: (data: unknown) => void): () => void;
|
|
232
|
+
/** Tilt the held item by `rad` radians around its own x axis (hold Z in the room). Pouring and spilling follow from the tilt. */
|
|
233
|
+
tilt(rad: number): void;
|
|
234
|
+
/** Open or close the tap over the table (default: toggle). Returns whether it is open. */
|
|
235
|
+
toggleTap(on?: boolean): boolean;
|
|
236
|
+
/** Stop the simulation (time, physics, animations, tools, walks): the UI, the camera and the log stay live. `step` and `update` do nothing until `resume`. Scenarios run unpaused. */
|
|
237
|
+
pause(): void;
|
|
238
|
+
resume(): void;
|
|
239
|
+
readonly paused: boolean;
|
|
240
|
+
/** Walk the player in a straight line to the floor position `to` (x and z; y is ignored) over `seconds` of simulated time, during the next `step()` / frames. Colliders still block. 0 seconds moves at once. */
|
|
241
|
+
walk(to: Vec3, seconds: number): void;
|
|
242
|
+
/** 0.25x simulation speed. */
|
|
243
|
+
setSlowMotion(on: boolean): void;
|
|
244
|
+
/** Run the simulation 10x per frame (fire, melting and drying take a tenth of the time). */
|
|
245
|
+
setFastForward(on: boolean): void;
|
|
246
|
+
/** Equip a weapon by id, adding it to the hotbar when it is not there. Same as selecting a catalogue item with `TestRoomItem.weapon`. */
|
|
247
|
+
equip(weaponId: string): void;
|
|
248
|
+
/** The scenarios the panel offers: the built-in `SCENARIOS` in the room locale plus `TestRoomOptions.scenarios`. */
|
|
249
|
+
readonly scenarios: TestRoomScenario[];
|
|
250
|
+
/** The liquid renderer of `@adatechnology/game-fluids-three` (containers, streams, stains). */
|
|
251
|
+
readonly fluidRenderer: FluidRenderer;
|
|
252
|
+
/** The behaviour log (see `TestRoomLog`). */
|
|
253
|
+
readonly log: TestRoomLog;
|
|
254
|
+
/** Run scripted checks (no input needed; works headless in tests and from the browser console). */
|
|
255
|
+
runScenarios(scenarios?: TestRoomScenario[]): Promise<ScenarioReport>;
|
|
256
|
+
/**
|
|
257
|
+
* Load a module that follows the `TestableModule` contract of `@adatechnology/game-contracts`: its items join the catalogue under `module.group`,
|
|
258
|
+
* its scenarios become buttons, and the contract checks run now (`checkDestructible` for every item with an `impl`, `checkAnimation` for every
|
|
259
|
+
* clip) writing a check line (pass or fail) per result to the log. Loading the same module twice replaces its items and scenarios.
|
|
260
|
+
*/
|
|
261
|
+
loadModule(module: TestableModule<ScenarioContext>): ModuleReport;
|
|
262
|
+
/** Animations of the aimed (or held) item: what the animations panel shows. */
|
|
263
|
+
animations(): AnimationInfo | null;
|
|
264
|
+
/**
|
|
265
|
+
* Play an animation clip of an item (default the held, else the aimed one). Returns false when there is no such item or clip. Poses are
|
|
266
|
+
* offsets from the rest pose, interpolated linearly; the item keeps the last pose. Events are logged (and `sound` events reach `onSound`).
|
|
267
|
+
*/
|
|
268
|
+
playAnimation(clipId: string, objectId?: string): boolean;
|
|
269
|
+
dispose(): void;
|
|
270
|
+
}
|
|
271
|
+
/** What `TestRoom.loadModule` did. */
|
|
272
|
+
interface ModuleReport {
|
|
273
|
+
module: string;
|
|
274
|
+
/** Catalogue ids added. */
|
|
275
|
+
items: string[];
|
|
276
|
+
/** Scenario ids added. */
|
|
277
|
+
scenarios: string[];
|
|
278
|
+
/** Every contract check passed. */
|
|
279
|
+
ok: boolean;
|
|
280
|
+
/** Each failed check, in words. */
|
|
281
|
+
problems: string[];
|
|
282
|
+
}
|
|
283
|
+
interface AnimationInfo {
|
|
284
|
+
objectId: string;
|
|
285
|
+
itemId: string;
|
|
286
|
+
name: string;
|
|
287
|
+
clips: Array<{
|
|
288
|
+
id: string;
|
|
289
|
+
duration: number;
|
|
290
|
+
}>;
|
|
291
|
+
/** Clip playing now, if any. */
|
|
292
|
+
playing: string | null;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* One line of the behaviour log. Every destruction event becomes one entry with the material, the part status and the numbers that matter, plus
|
|
296
|
+
* the player's actions (spawn, tool use, grab, drop, throw, disassemble) so a reader (a person or an agent) can check the behaviour is right.
|
|
297
|
+
*/
|
|
298
|
+
interface LogEntry {
|
|
299
|
+
/** Monotonic index. */
|
|
300
|
+
seq: number;
|
|
301
|
+
/** Bench time in seconds. */
|
|
302
|
+
t: number;
|
|
303
|
+
/** `action` = something the player or a script did; `event` = a destruction event; `check` = a scenario assertion. */
|
|
304
|
+
channel: 'action' | 'event' | 'check';
|
|
305
|
+
/** Event or action type (`dent`, `hole`, `fracture`, `ignite`, `spawn`, `shoot`, `grab`, `throw`, `disassemble`...). */
|
|
306
|
+
type: string;
|
|
307
|
+
objectId?: string;
|
|
308
|
+
itemId?: string;
|
|
309
|
+
part?: string;
|
|
310
|
+
material?: string;
|
|
311
|
+
/** Part status and numbers after the event (hp, maxHp, temperature, char, melt, liters, group count). */
|
|
312
|
+
state?: Record<string, number | string | boolean>;
|
|
313
|
+
/** Everything else (energy, caliber, depth, diameter, fragment count, joint, tool, pass/fail...). */
|
|
314
|
+
data?: Record<string, unknown>;
|
|
315
|
+
/** One human line in the bench locale, e.g. "Cadeira › perna_1 (madeira macia): amassado 4 mm, hp 38/60". */
|
|
316
|
+
text: string;
|
|
317
|
+
}
|
|
318
|
+
interface TestRoomLog {
|
|
319
|
+
entries(filter?: {
|
|
320
|
+
channel?: LogEntry['channel'];
|
|
321
|
+
type?: string;
|
|
322
|
+
objectId?: string;
|
|
323
|
+
}): LogEntry[];
|
|
324
|
+
clear(): void;
|
|
325
|
+
/** JSON array of entries. */
|
|
326
|
+
toJSON(): string;
|
|
327
|
+
/** One JSON object per line (easy to grep and diff). */
|
|
328
|
+
toNDJSON(): string;
|
|
329
|
+
/** Plain text lines (the on-screen panel). */
|
|
330
|
+
toText(): string;
|
|
331
|
+
/**
|
|
332
|
+
* Download the log as a file (`.ndjson` by default). In a claude.ai Artifact page (a `globalThis.claude.use('downloads')` that resolves) it asks the
|
|
333
|
+
* viewer through the `downloads` capability (`ndjson` is saved as `.ndjson.txt`, the allowlist has no `.ndjson`); if the viewer declines, an
|
|
334
|
+
* `export_cancelled` action line is logged. Elsewhere it uses a blob link.
|
|
335
|
+
*/
|
|
336
|
+
download(format?: 'json' | 'ndjson' | 'txt'): Promise<void>;
|
|
337
|
+
/** Counts per event type and per material (quick summary for checks). */
|
|
338
|
+
summary(): {
|
|
339
|
+
byType: Record<string, number>;
|
|
340
|
+
byMaterial: Record<string, number>;
|
|
341
|
+
objects: number;
|
|
342
|
+
destroyed: number;
|
|
343
|
+
};
|
|
344
|
+
/** Listener for new entries and for in-place updates of coalesced ones (`updated` true, same entry object). */
|
|
345
|
+
on(fn: (entry: LogEntry, updated: boolean) => void): () => void;
|
|
346
|
+
/** Write a line (hosts and scenarios add their own). `seq` and `t` (room time) are filled in. */
|
|
347
|
+
add(entry: NewLogEntry): LogEntry;
|
|
348
|
+
/**
|
|
349
|
+
* Write a line, or update in place the last line written with the same `key` when it was touched less than `windowS` seconds ago (default 1).
|
|
350
|
+
* Used for noisy sequences: melting, dripping, taking something apart.
|
|
351
|
+
*/
|
|
352
|
+
coalesce(key: string, entry: NewLogEntry, windowS?: number): LogEntry;
|
|
353
|
+
}
|
|
354
|
+
/** What `TestRoomLog.add` takes. */
|
|
355
|
+
type NewLogEntry = Omit<LogEntry, 'seq' | 't'> & {
|
|
356
|
+
t?: number;
|
|
357
|
+
};
|
|
358
|
+
/** A scripted check: spawn things, act, then assert on the log and the world state. */
|
|
359
|
+
interface TestRoomScenario {
|
|
360
|
+
id: string;
|
|
361
|
+
/** Shown in the report (already translated). */
|
|
362
|
+
name: string;
|
|
363
|
+
run: (ctx: ScenarioContext) => void | Promise<void>;
|
|
364
|
+
}
|
|
365
|
+
interface ScenarioContext {
|
|
366
|
+
room: TestRoom;
|
|
367
|
+
/** Advance the simulation by `seconds` in fixed steps (no rendering needed). */
|
|
368
|
+
step(seconds: number): void;
|
|
369
|
+
/** Record a pass/fail line in the log (`channel: 'check'`). */
|
|
370
|
+
expect(condition: boolean, label: string, data?: Record<string, unknown>): void;
|
|
371
|
+
}
|
|
372
|
+
interface ScenarioReport {
|
|
373
|
+
passed: number;
|
|
374
|
+
failed: number;
|
|
375
|
+
results: Array<{
|
|
376
|
+
id: string;
|
|
377
|
+
name: string;
|
|
378
|
+
ok: boolean;
|
|
379
|
+
checks: Array<{
|
|
380
|
+
label: string;
|
|
381
|
+
ok: boolean;
|
|
382
|
+
}>;
|
|
383
|
+
}>;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Create the test room. See `TestRoomOptions`. The room owns a renderer, scene and camera unless you pass them; with your own renderer you call
|
|
388
|
+
* `room.update(dt)` every frame and render yourself.
|
|
389
|
+
*/
|
|
390
|
+
declare function createTestRoom(options: TestRoomOptions): TestRoom;
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Extra materials the demo items need on top of the core ones. None any more (the bottle uses the core `glass`, which now breaks from a drop);
|
|
394
|
+
* kept so that code passing it to `worldOptions.materials` keeps working.
|
|
395
|
+
*/
|
|
396
|
+
declare const DEMO_MATERIALS: MaterialDef[];
|
|
397
|
+
/** Spec of the brick target wall: `cols` x `rows` brick panels, the bottom row anchored, mortar joints between neighbours. */
|
|
398
|
+
declare function brickWallSpec(cols?: number, rows?: number, panel?: Vec3): DestructibleSpec;
|
|
399
|
+
/**
|
|
400
|
+
* The generic demo catalogue, names and groups translated for `locale` (`en` or `pt-BR`). Every item is a procedural low-poly build (one mesh per
|
|
401
|
+
* destruction part, drawn by `buildFromSpec`) plus an explicit destruction spec. Containers carry a fluid container where it makes sense.
|
|
402
|
+
*/
|
|
403
|
+
declare function demoItems(locale?: string, extra?: TestRoomItem[]): TestRoomItem[];
|
|
404
|
+
/** The demo catalogue in English (see `demoItems` for other locales). */
|
|
405
|
+
declare const DEMO_ITEMS: TestRoomItem[];
|
|
406
|
+
|
|
407
|
+
type T = (key: string, vars?: Record<string, string | number>) => string;
|
|
408
|
+
type Num = (n: number, digits?: number) => string;
|
|
409
|
+
/** Built-in scripted checks over the generic `DEMO_ITEMS`. Names and check texts come from the room locale. */
|
|
410
|
+
declare function makeScenarios(t: T, num: Num, _locale?: string): TestRoomScenario[];
|
|
411
|
+
/** The built-in scenarios for a locale (`en` or `pt-BR`), standalone: no room needed to list them. */
|
|
412
|
+
declare function scenariosFor(locale?: string): TestRoomScenario[];
|
|
413
|
+
/** The built-in scenarios in English. The room offers them in its own locale. */
|
|
414
|
+
declare const SCENARIOS: TestRoomScenario[];
|
|
415
|
+
|
|
416
|
+
/** Default look of each built-in destruction material (`0xRRGGBB`). */
|
|
417
|
+
declare const MATERIAL_COLORS: Record<string, number>;
|
|
418
|
+
interface BuildFromSpecOptions {
|
|
419
|
+
/** Colour per part id, then per material id (falls back to `MATERIAL_COLORS`, then grey). */
|
|
420
|
+
colors?: Record<string, number>;
|
|
421
|
+
/** Segments of cylinders and spheres (default 12: low poly on purpose). */
|
|
422
|
+
segments?: number;
|
|
423
|
+
/** Cast and receive shadows (default true). */
|
|
424
|
+
shadows?: boolean;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Draw any `DestructibleSpec` as plain meshes: one mesh per part (box, cylinder or sphere) with `name` and `userData.destructionPart` set to the
|
|
428
|
+
* part id and `userData.destructionMaterial` to its material, so `renderer.bind` and `specFromObject3D` find them. Useful for teams that describe
|
|
429
|
+
* objects as specs and want something to look at while they tune them.
|
|
430
|
+
*/
|
|
431
|
+
declare function buildFromSpec(THREE: typeof THREE_NS, spec: DestructibleSpec, options?: BuildFromSpecOptions): THREE_NS.Group;
|
|
432
|
+
|
|
433
|
+
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 };
|