@volter/play 0.5.210 → 0.5.212
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/NOTICE +5 -5
- package/README.md +125 -172
- package/contributions/game-panel.tsx +908 -0
- package/contributions/model-play.verbs.ts +44 -0
- package/contributions/play.page.tsx +211 -0
- package/contributions/std-scene.document.tsx +345 -0
- package/dist-node/serving.mjs +664 -0
- package/package.json +257 -12
- package/serving/index.ts +13 -0
- package/serving/std-dev.ts +514 -0
- package/serving/std-scene-runtime.js +119 -0
- package/serving/std-scene.ts +213 -0
- package/src/editor-blender.ts +33 -0
- package/src/editor-threejs.ts +19 -0
- package/src/model-play.ts +60 -287
- package/src/play-clock.ts +62 -0
- package/src/play-log.ts +77 -62
- package/src/std-controls.ts +128 -0
- package/src/std-export.ts +128 -0
- package/src/std-game-adapter.ts +20 -0
- package/src/std-game.ts +841 -0
- package/src/std-level-files.ts +33 -0
- package/src/std-level.ts +107 -0
- package/src/std-scene-level.ts +59 -0
- package/src/std-view-bar.tsx +78 -0
- package/contributions/model-play.command.ts +0 -37
- package/contributions/model-play.service.tsx +0 -152
- package/src/camera-transition.ts +0 -87
- package/src/play-materials.ts +0 -199
- package/src/play-script.ts +0 -841
package/NOTICE
CHANGED
|
@@ -4,9 +4,9 @@ This package is developed in the public volter-ai/editor repository and is
|
|
|
4
4
|
Volter AI, Inc.'s own work. It was first published under AGPL-3.0-only and is
|
|
5
5
|
licensed under Apache-2.0 from this notice on. The owner's words are "the
|
|
6
6
|
runtime should be apache, the editor is agpl"; that this package is runtime
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
repository root. LICENSE contains the Apache-2.0 text.
|
|
7
|
+
is the reading recorded as ruling D336 on board task t_b315eb39 and in
|
|
8
|
+
COMMERCIAL.md at the repository root. LICENSE contains the Apache-2.0 text.
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
The package imports `@volter/sdk` (Apache-2.0), three and React, and nothing of Blender. A game it plays is a
|
|
11
|
+
volter-ai/std game (Apache-2.0; the Blender scripts std ships are GPL-3.0-or-later), whose packages are the game's own
|
|
12
|
+
dependencies: this package runs the game's own commands and pages and carries none of std's code.
|
package/README.md
CHANGED
|
@@ -1,185 +1,138 @@
|
|
|
1
1
|
# Play
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
3
|
+
A Cyclotron game is a [volter-ai/std](https://github.com/volter-ai/std) game: three.js, React Three Fiber, Rapier and
|
|
4
|
+
Colyseus, used as their own docs describe them, with std adding only what they lack. This repository pins std as
|
|
5
|
+
`external/std`. Play runs the game itself, on its own dev server: each player is one of the game's pages, a std device,
|
|
6
|
+
standing in the model document's stage.
|
|
7
|
+
|
|
8
|
+
## A std game
|
|
9
|
+
|
|
10
|
+
A project with `std.json` that depends on `@volter-ai/std-dev` is a std game (`src/std-game.ts` asks the session,
|
|
11
|
+
`serving/std-dev.ts`). `cyclotron create --template game` makes one with std's own `std new`; `cyclotron add-play`
|
|
12
|
+
runs `std new` into an existing project and moves its `src/models/*.blend` into `levels/`. Either installs the game's
|
|
13
|
+
dependencies with pnpm, as std's README does; the editor hears `std.json` arrive and needs no reload.
|
|
14
|
+
|
|
15
|
+
Pressing Play on a level saves its `.blend` first (editor-blender's `modelDocument.flush`) and waits for std's export of
|
|
16
|
+
it (`src/std-level.ts`), so the game plays the current edit. Play then starts the game's dev server where none runs
|
|
17
|
+
(`npm run dev -- --no-open`, owned by the session and ended with it), finds it by the record std writes in Vite's
|
|
18
|
+
cacheDir (`node_modules/.vite/std-dev.json`, whose token stays in the session), and stands each player in the stage
|
|
19
|
+
as an iframe.
|
|
20
|
+
|
|
21
|
+
- **The view bar**, over the viewport: Edit, Player N (each "you" or "autopilot"), Grid (Edit and every player tiled,
|
|
22
|
+
each player's tile its whole view scaled down, as its page lays out at the stage's size in every view; a tile clicked
|
|
23
|
+
opens it) and Players [-][+], each one a client joining or leaving. Edit brings the level's own area
|
|
24
|
+
back, live for editing, while the game runs on.
|
|
25
|
+
- **The keys** 1 to 4 open Player N, Tab the next view and Escape Edit (Escape in Edit stops the game), whenever the
|
|
26
|
+
editor holds the keyboard: in Edit, in Grid, or after a press on the view bar. A viewed player's page holds the
|
|
27
|
+
keyboard, and its keys, 1 to 4 included, are the game's, as Unity's Multiplayer Play Mode and Unreal's Play In
|
|
28
|
+
Editor treat a focused game window.
|
|
29
|
+
- **Each player's page** is told apart by the slot its address names (`?slot=<run>-<n>`), which its trace's first
|
|
30
|
+
line says; a page that loads again is found again by it. A player whose page never joins is taken out, and its
|
|
31
|
+
failure stands.
|
|
32
|
+
- Play, Stop and Restart load, unload and reload the pages.
|
|
33
|
+
|
|
34
|
+
## std's device controls
|
|
35
|
+
|
|
36
|
+
Every word Play says to a player's page is one of std's device controls (`POST /__std/devices/<id>/control`), sent
|
|
37
|
+
through `src/std-controls.ts` and the session's route, which holds the dev server's token:
|
|
38
|
+
|
|
39
|
+
| Control | What Play uses it for |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `clock` (`pause`, `resume`, `step`, `speed`) | The Game panel's pause, step and speed, sent to every joined player's page; the panel shows the clock std answers |
|
|
42
|
+
| `autoplay`, `manual` | Who drives the viewed player: its bot, with its slot and goal, or the person |
|
|
43
|
+
| `inspect` | `editor.inspect` while a player is in view: the page's live three.js objects and labelled screen nodes |
|
|
44
|
+
| `act` | `editor.document.key` with no selector, and `editor.document.click('role=<role>[name="<name>"]')` (the page's labelled node pressed at its middle), while a player is in view: std-driver/2 actions through the page's own input |
|
|
45
|
+
| `capture` | `capture --region play`: the viewed page's next frame, written by std's receiver |
|
|
27
46
|
|
|
28
|
-
|
|
47
|
+
`editor.document.query` answers the editor's DOM as always and, beside it, the viewed page's labelled nodes
|
|
48
|
+
(`framed`; `@volter/sdk/framed-document-driver`). A refusal is std's own answer, shown with its words.
|
|
29
49
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`tick` (the update in progress). Facts are snapshotted as JSON when logged. `simT` and `tick` are the Game panel's
|
|
33
|
-
clock: they stand still while paused and run at the speed (see Time below). The
|
|
34
|
-
runner adds `play-start`, `play-restart` (`{ speed }`, right after a Restart's
|
|
35
|
-
`play-start`), `script-reload` (`{ reason: 'saved' | 'dependency-deleted', path }`),
|
|
36
|
-
`script-error` (`{ phase, message }`), `tint-unsupported` (`{ object, material, why }`),
|
|
37
|
-
`tint-unknown-object` (`{ object, call }`, once per name: `tint` or `setOpacity` was given a
|
|
38
|
-
name the model has none of, and did nothing), `autoplay-on` / `autoplay-off`
|
|
39
|
-
(`{ by: 'panel' | 'cli' | 'takeover' | 'script' }`, see Autoplay below),
|
|
40
|
-
`autoplay-unavailable` (`{ why }`, when a script's first update has run and it registered no
|
|
41
|
-
bot; once per run, and again only if a bot came and went),
|
|
42
|
-
`pause`, `resume`, `step` (`{ dt }`, one per stepped update), `speed`
|
|
43
|
-
(`{ speed, from }` on a change; `{ speed }` at a start that is not 1×) and
|
|
44
|
-
`play-stop` (`{ reason }`). Log transitions rather than every frame. Logging
|
|
45
|
-
never throws and never changes timing or state; a script's `log`, `tint` and
|
|
46
|
-
`setOpacity` do nothing once it has been replaced or Play has stopped.
|
|
47
|
-
|
|
48
|
-
Each model document keeps its own log; a read takes the active Play's unless it
|
|
49
|
-
names a document. The newest 5000 entries are kept (older ones are counted as
|
|
50
|
-
`dropped`), each entry's facts up to 2048 characters of JSON. Play from a fresh
|
|
51
|
-
copy, and Restart, empty the document's log; a script reload does not, and the log stays readable after Stop until the
|
|
52
|
-
next Play. `console.log` does not reach the session's console feed (only
|
|
53
|
-
warnings and errors do); the play log does.
|
|
50
|
+
The clock holds the pages. A room on the dev server runs on: Colyseus has no pause for a room, so a timer a room runs
|
|
51
|
+
keeps running while the pages are paused.
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
cyclotron play-log # every kept entry, one per line
|
|
57
|
-
cyclotron play-log --kind death # one kind
|
|
58
|
-
cyclotron play-log --since 12.5 --json # entries at or after simT 12.5, as JSON
|
|
59
|
-
cyclotron play-log --document <id> # another model document's log
|
|
60
|
-
cyclotron eval "(await editor.modelPlayLog({ kind: 'death' })).entries"
|
|
61
|
-
```
|
|
53
|
+
## Autoplay
|
|
62
54
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Use these instead of editing materials directly: a presented mesh's `material`
|
|
72
|
-
is an array with one `MeshPhysicalMaterial` per Blender material slot, shared by
|
|
73
|
-
every object with that material; the presenter re-assigns those slots whenever
|
|
74
|
-
it re-applies shading; `clone()` drops its shader hooks; and a node graph that
|
|
75
|
-
drives Base Color or Alpha ignores `color` and `opacity`. A tinted or faded
|
|
76
|
-
object wears copies of its own slots that draw the material's constant inputs
|
|
77
|
-
(so a node graph's other inputs are not drawn while it does), and keeps them
|
|
78
|
-
across a script reload. An object with a material the document cannot copy
|
|
79
|
-
(one the script made itself) is left as it is, and the log says so once with
|
|
80
|
-
`tint-unsupported`.
|
|
55
|
+
The game's bot is its own program, `autopilot/index.ts`, whose goals are named by words (std's `autopilot({ goals })`).
|
|
56
|
+
Play lists them from the session's route `/__editor/play/std/goals`, which loads that program in Node as std does.
|
|
57
|
+
The Game panel's Autoplay turns the viewed player's bot on with the goal the panel picks, and armed while stopped turns
|
|
58
|
+
it on as the run starts; the person's own key or click in its page takes over. `cyclotron play autoplay on [<goal>]` is
|
|
59
|
+
std's own headless play of that goal (`/__std/play`): its bot pages, out of view, play the goal to its end whether or not
|
|
60
|
+
the editor is seen, the viewed player and the panel's goal untouched, and `play state`'s `autoplay.state` and the play
|
|
61
|
+
log's `autoplay-end` say how it ended. Every other player is a bot from the start.
|
|
81
62
|
|
|
82
|
-
##
|
|
63
|
+
## The play log
|
|
83
64
|
|
|
84
|
-
|
|
65
|
+
Each player's page traces its run (std's trace, `build/runs/<run>/<page>.jsonl`), and the run reads each trace into
|
|
66
|
+
the document's play log, each entry tagged with its player's slot (`source: 'script'`). Play adds its own entries
|
|
67
|
+
(`source: 'play'`): `play-start`, `running`, `player-joined`, `player-page-loaded`, `player-left`, `view`,
|
|
68
|
+
`autoplay-on` and `autoplay-off` (with `by`), `pause`, `resume`, `step` and `speed` (each with the clock std answered),
|
|
69
|
+
`capture`, `script-error` (with its `phase`) and `play-stop`. Entries carry the run's clock as std last answered it.
|
|
85
70
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
```
|
|
71
|
+
A room's exceptions are not in any page's trace: Colyseus logs them on the dev server's error output (stderr), with
|
|
72
|
+
Vite's errors and warnings. Of a dev server Play started, each run reads that output from the run's start
|
|
73
|
+
(`/__editor/play/std/errors`) in blocks, a line and the indented lines under it: a block that names an `Error` or
|
|
74
|
+
carries a stack is a `script-error` with the phase `server` and an error in the console, once by its first line; any
|
|
75
|
+
other is a `server-output` entry.
|
|
92
76
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
- Autoplay is **off** whenever Play starts or restarts.
|
|
105
|
-
- Only the Game panel's **Autoplay** toggle, `cyclotron play autoplay on|off` or
|
|
106
|
-
`await editor.command('volter.model-play.autoplay', { on: true })` turn it on (or off).
|
|
107
|
-
- **A bot exists only while the game runs**: `play.autoplay` is called by the play script, so
|
|
108
|
-
a stopped game (or one still starting) has none, and neither has a running script that never
|
|
109
|
-
registers one. The panel says which, in words beside the toggle — "Available once the game is
|
|
110
|
-
running" or "No autoplay — this game doesn't provide a bot" — and `on` is refused with the
|
|
111
|
-
same reason, which `play state` also gives as `autoplay.why`.
|
|
112
|
-
- **Arming.** Pressed while stopped, the panel's Autoplay arms the next start: autoplay turns
|
|
113
|
-
on (`by: 'panel'`) as soon as that run's script has run its first update with a bot, and the
|
|
114
|
-
arm is dropped if it offers none. A person's key or click in the game before then drops it
|
|
115
|
-
too (`autoplay-off` with `{ by: 'takeover', armed: true }`): the person always wins. Only an
|
|
116
|
-
explicit arm carries over; Stop, and closing the model, drop it.
|
|
117
|
-
- **The person always wins.** A new key press the game would hear, or a pointer pressed in the
|
|
118
|
-
game's area (the HUD included), turns autoplay off before that key reaches `play.keys`, and
|
|
119
|
-
the panel reads "You're driving" until someone switches the bot on again. Synthetic keys and
|
|
120
|
-
clicks (`editor.document.key`, `click`) count as a person's: they are how an agent plays by
|
|
121
|
-
hand. A key repeat is not a new press; keys typed in a text field, or while the game's surface
|
|
122
|
-
does not hold the keyboard, are not the game's and do not take over.
|
|
123
|
-
- A bot that throws turns autoplay off (`by: 'script'`, with a `script-error` of phase
|
|
124
|
-
`autoplay`), as does a reload whose script offers no bot.
|
|
125
|
-
|
|
126
|
-
Each change is a play-log entry, `autoplay-on` or `autoplay-off`, with `by`.
|
|
127
|
-
|
|
128
|
-
## Time
|
|
129
|
-
|
|
130
|
-
`update(dt)` is the only clock a play script is given, and the editor decides it:
|
|
131
|
-
|
|
132
|
-
| Control | What the script sees |
|
|
133
|
-
| --- | --- |
|
|
134
|
-
| Playing at 1× | One `update` per drawn frame, `dt` = the frame's seconds (at most 0.1). |
|
|
135
|
-
| Speed 0.25× – 4× | `dt` scaled by the speed. A scaled frame longer than 0.1 s is split into equal updates, so no single `dt` exceeds 0.1. |
|
|
136
|
-
| Pause | No `update` at all. The copy, the camera and the HUD hold the last frame. |
|
|
137
|
-
| Step (while paused) | One `update` with `dt` = 1/60. |
|
|
138
|
-
| Restart | The run begins again on a fresh detached copy, the clock at zero, without the camera's fly-in. |
|
|
139
|
-
|
|
140
|
-
A script that reads the page's own clock (`performance.now()`, `Date.now()`) instead of
|
|
141
|
-
summing `dt` is outside the reach of pause and speed.
|
|
142
|
-
|
|
143
|
-
The run's clock — simulation time (the sum of the `dt`s handed to the script) and tick (the
|
|
144
|
-
number of updates) — is kept per document beside whether it plays (`src/model-play.ts`).
|
|
145
|
-
Speed is kept for the page's life; pause and the clock reset on every Play.
|
|
146
|
-
When the run plays but no game runs — the script failed to start, or threw — the clock's
|
|
147
|
-
`failure` says why (the Game panel shows it in place of "Playing"); a save of the script
|
|
148
|
-
retries, and the next running game clears it.
|
|
77
|
+
One log per model document, each its latest run's; each Play replaces it, and it outlives Stop. The newest entries are
|
|
78
|
+
kept in a bounded ring (older ones counted as `dropped`). `console.log` does not reach the session's console feed;
|
|
79
|
+
the play log does.
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
cyclotron play-log # every kept entry, one per line
|
|
83
|
+
cyclotron play-log --kind coin # one kind
|
|
84
|
+
cyclotron play-log --since 12.5 --json # entries at or after 12.5 s, as JSON
|
|
85
|
+
cyclotron play-log --document <id> # another model document's log
|
|
86
|
+
```
|
|
149
87
|
|
|
150
88
|
## Controls
|
|
151
89
|
|
|
152
|
-
The
|
|
153
|
-
(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
`
|
|
185
|
-
|
|
90
|
+
The model document's Play is registered through editor-blender's API (`registerModelPlay`) from play's page half
|
|
91
|
+
(`contributions/play.page.tsx`), and drawn by play's Game panel in the bottom area. The panel's controls are also
|
|
92
|
+
commands, so an agent drives the same run the person sees: `cyclotron play play|stop|pause|resume|restart`,
|
|
93
|
+
`play step [count]`, `play speed <0.25|0.5|1|2|4>`, `play mode [game|animation]` and `play autoplay on [<goal>]|off`,
|
|
94
|
+
each answering with the panel's state.
|
|
95
|
+
|
|
96
|
+
A std game's Export (the panel's button and menu; `volter.model-play.export { target, add? }` under `eval`) builds the
|
|
97
|
+
game for the web (`std build`, into `dist/`) or exports it to an engine (`std export --target <engine>`). An engine not
|
|
98
|
+
yet one of the game's targets is offered as "Add Roblox and export", which adds it first (`std target add <engine>`: its
|
|
99
|
+
package installed, its `std.json` settings and its scripts); the verb refuses one unless `add` is true. A game is built
|
|
100
|
+
for an engine only once someone asks for it, as an engine's editor builds for a platform once it is chosen. Each line
|
|
101
|
+
std prints goes to the console, and the panel says how the export stands and where it is.
|
|
102
|
+
|
|
103
|
+
## A std game's editor
|
|
104
|
+
|
|
105
|
+
A std game carries no editor code. Play supplies its adapter (`src/std-game-adapter.ts`, declared in
|
|
106
|
+
`contributes.volter.projectAdapters` for a project with `std.json`): Blender's layout, keymap and properties inspector,
|
|
107
|
+
with every `.blend` under `levels/` as a document. A project's own `editor/volter.adapter.ts` wins over it.
|
|
108
|
+
|
|
109
|
+
## A level's export
|
|
110
|
+
|
|
111
|
+
Each save of a level's `.blend` (`levels/**/*.blend`) runs std's level export, its two halves as std states them. The
|
|
112
|
+
Blender half, std-dev's `blender/blend-to-scene.py`, is the save's own script (editor-blender's
|
|
113
|
+
`modelDocument.setSaveScript`): it runs in the save's turn, on the very scene that save wrote, and the save's answer
|
|
114
|
+
carries the files it wrote (`levels/<name>.glb`, its description, a copied sky image, a baked `<name>.view.cube`).
|
|
115
|
+
Then, one save after another, those files are carried into the project and the Node half,
|
|
116
|
+
`std level <levels/name.glb> --description <levels/name.level.json>`, runs in the game's folder at the session route
|
|
117
|
+
`/__editor/play/std/level`, by the game's own std, and writes `<Name>Level.tsx`, `<name>.canvas.ts`, the environment
|
|
118
|
+
and grading. std's dev server takes the level by its own hot reload. Both halves' output is the console's
|
|
119
|
+
(`std-level`); a half that fails is an error there.
|
|
120
|
+
|
|
121
|
+
The game's `prefabs/` folder is a Blender asset library named Prefabs whose import method is Link
|
|
122
|
+
(editor-blender's `setAssetLibrary`), so a prefab dragged from it is linked, not appended.
|
|
123
|
+
|
|
124
|
+
## A level's Scene view
|
|
125
|
+
|
|
126
|
+
"Play: Open Scene View" (`volter.std-scene { level? }`) opens the std level whose `.blend` was active last, or the
|
|
127
|
+
`levels/<name>.blend` it names, drawn as the game draws it: its generated component, its environment, its canvas's tone
|
|
128
|
+
mapping and shadows and its view transform, with the game's own React Three Fiber, react-three-rapier and std, in
|
|
129
|
+
editor-threejs's viewport (`contributions/std-scene.document.tsx`; its module, `/@volter-std-scene/<levels/name.blend>`,
|
|
130
|
+
is `serving/std-scene.ts`). Overlays draws colliders and sensors and signs each std marker with its name. The view edits
|
|
131
|
+
nothing: the `.blend` is the level's source, and the view draws the level again as each save exports it
|
|
132
|
+
(`docs/adr/std-scene-view.md`).
|
|
133
|
+
|
|
134
|
+
## Outside the editor
|
|
135
|
+
|
|
136
|
+
`cyclotron export web|roblox|unreal` runs the game's own `std build` or `std export --target <engine>`, which refuses an
|
|
137
|
+
engine that is not one of the game's targets, naming `std target add` (std's guide,
|
|
138
|
+
`node_modules/@volter-ai/std-dev/docs/03-targets.md`).
|