ravensight-playtest 0.4.0 → 0.5.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/README.md +60 -4
- package/docs/SIMULATION.md +1 -1
- package/docs/godot-driver.md +310 -0
- package/package.json +2 -1
- package/src/api/client.js +2 -2
- package/src/cli.js +3 -1
- package/src/commands/run.js +51 -19
- package/src/run/actionSelector.js +3 -2
- package/src/run/aggregate.js +4 -1
- package/src/run/args.js +2 -0
- package/src/run/comparison.js +32 -0
- package/src/run/controllers.js +17 -0
- package/src/run/drivers/driver.js +5 -5
- package/src/run/drivers/godot.js +131 -39
- package/src/run/drivers/recording.js +148 -0
- package/src/run/drivers/stdio.js +24 -8
- package/src/run/drivers/web.js +131 -67
- package/src/run/drivers/webdriver.js +1 -1
- package/src/run/personaLoop.js +140 -49
- package/src/run/resume.js +16 -5
- package/src/upload/index.js +4 -2
- package/src/upload/recording.js +56 -0
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ the reports and renders them.
|
|
|
7
7
|
Simulation Runner adds seeded headless batches for mechanics, economy and
|
|
8
8
|
progression checks through your own game adapter. It runs locally without
|
|
9
9
|
model calls and saves bounded results to the same Playtest workspace. See
|
|
10
|
-
[Simulation Runner](docs/SIMULATION.md) for
|
|
10
|
+
[Simulation Runner](docs/SIMULATION.md) for setup.
|
|
11
11
|
|
|
12
12
|
You need a Ravensight account and Node 22 or newer. You do not need an
|
|
13
13
|
Anthropic API key: model calls go through Ravensight under the same token you
|
|
@@ -374,7 +374,10 @@ splitting the job in two is what gets everything played.
|
|
|
374
374
|
| `--max-actions <n>` | Lower the actions one persona may take. It can only lower the persona's own patience (and the server's per run ceiling), never raise it. |
|
|
375
375
|
| `--long` | A long persona run: twice the actions, model budget and wall time of a standard run, at twice the price. The price is read from the estimate and confirmed before any charge. |
|
|
376
376
|
| `--concurrency <n>` | Personas at once. Default 2; `check` recommends a number for this machine. |
|
|
377
|
-
| `--
|
|
377
|
+
| `--controller <name>` | One playthrough using `standard` or an available System One controller. The server must advertise and quote that exact choice. |
|
|
378
|
+
| `--compare-with jev` | Separate agent/skill and Jev playthroughs for each chosen persona. Shows their combined quote before purchase. Comparisons run one playthrough at a time. |
|
|
379
|
+
| `--record-video` | Capture the selected web or graphical Godot game locally. Does not upload the video. |
|
|
380
|
+
| `--upload-video` | Record and explicitly upload `session.webm` or `session.mp4` with the reports. |
|
|
378
381
|
| `--upload-transcript` | Upload `transcript.jsonl` as well as the reports. |
|
|
379
382
|
| `--dry-upload` | List every file and byte that would be sent, and send nothing. |
|
|
380
383
|
| `--key-modifier <meta\|control>` | The modifier the browser driver uses for editing shortcuts such as select all. Defaults to this machine's own (Command on macOS, Control elsewhere); set it when the browser's platform is not this machine's. `RAVENSIGHT_KEY_MODIFIER` does the same. |
|
|
@@ -780,7 +783,7 @@ to it. The short version:
|
|
|
780
783
|
|
|
781
784
|
Uploaded by default: `report.md`, `report.json`, `usage.json`, `changeset.json`,
|
|
782
785
|
the aggregate and capability reports, and `screenshots/`. Uploaded only when you ask:
|
|
783
|
-
`transcript.jsonl` (`--upload-transcript`) and `session.webm`
|
|
786
|
+
`transcript.jsonl` (`--upload-transcript`) and `session.webm` or `session.mp4`
|
|
784
787
|
(`--upload-video`). **Source code never, at any path.** The upload allowlist has
|
|
785
788
|
no entry for it, the CLI refuses it locally, and the server refuses anything not
|
|
786
789
|
on its own copy of the list. `src/upload/allowlist.js` is that copy, and a test
|
|
@@ -881,6 +884,59 @@ before registering a paid job. No provider key belongs in the CLI. Selection cos
|
|
|
881
884
|
share the existing persona-run budget and do not create another wallet charge.
|
|
882
885
|
Resume preserves the controller recorded in the job journal.
|
|
883
886
|
|
|
887
|
+
### Recorded playthroughs and comparisons
|
|
888
|
+
|
|
889
|
+
The `0.5.0` candidate adds local recording and explicit comparisons. It requires a
|
|
890
|
+
matching server release for controller plans; an older server is refused before
|
|
891
|
+
purchase. Rune is recognized as a future controller but is unavailable until
|
|
892
|
+
provider access, pricing and acceptance are complete. These commands do not
|
|
893
|
+
activate graphical AWS hosting.
|
|
894
|
+
|
|
895
|
+
To keep one playthrough's video on your computer:
|
|
896
|
+
|
|
897
|
+
```sh
|
|
898
|
+
ravensight-playtest run --driver playwright_web --build-url https://your-game.example/ --record-video
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
To request two reports, one from the agent/skill controller and one from Jev:
|
|
902
|
+
|
|
903
|
+
```sh
|
|
904
|
+
ravensight-playtest run --driver playwright_web --build-url https://your-game.example/ --personas curious-kid --compare-with jev --record-video
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
The server quotes every persona/controller run before the CLI asks for payment.
|
|
908
|
+
Each keeps its own existing model budget and settlement rules. Two standard runs
|
|
909
|
+
at the current $1 unit price total $2; the displayed server quote is authoritative.
|
|
910
|
+
No comparison model call or extra recording fee is added. Use `--upload-video`
|
|
911
|
+
instead of `--record-video` only when you want the videos uploaded for private
|
|
912
|
+
dashboard playback. Recording is silent and captures only the selected game,
|
|
913
|
+
not your desktop. Browser popups and navigation to another origin are blocked
|
|
914
|
+
while recording.
|
|
915
|
+
|
|
916
|
+
Web recording produces WebM. Graphical Godot recording needs an existing FFmpeg
|
|
917
|
+
installation and produces MP4; a headless Godot run or missing encoder is reported
|
|
918
|
+
as unsupported. Capture is bounded to 15 minutes, 200 MiB of video, 1280 by 720
|
|
919
|
+
pixels and 10 frames per second for the Godot encoder. Godot raw frames have a
|
|
920
|
+
separate 256 MiB bound. This is evidence capture, not a promise of smooth video
|
|
921
|
+
at the game's original frame rate. Cancellation, crashes and limits can leave a
|
|
922
|
+
playable partial video; local `recording.json` records that outcome. Neither that
|
|
923
|
+
file nor raw frames are uploaded as artifacts.
|
|
924
|
+
|
|
925
|
+
With `--upload-video`, completion also sends a bounded capture status, duration
|
|
926
|
+
and stop reason. This is the runner's account of capture, separate from verified
|
|
927
|
+
upload or report success. Without video-upload consent, no capture status is sent.
|
|
928
|
+
Older recordings and later byte-only uploads can show status as unreported.
|
|
929
|
+
Malformed local status is omitted so it cannot prevent an existing report from
|
|
930
|
+
being completed.
|
|
931
|
+
|
|
932
|
+
Each controller starts a separate game session and produces its own report.
|
|
933
|
+
Saved progress and random events may still differ; this is not a guaranteed
|
|
934
|
+
identical-state benchmark. The aggregate compares evidence-bearing findings and
|
|
935
|
+
marks missing, failed or partial playthroughs as inconclusive. Agreement between
|
|
936
|
+
reports is evidence to review, not confirmation that a bug exists. In the
|
|
937
|
+
dashboard, choose a playthrough and its Recording tab; timestamp links refer only
|
|
938
|
+
to that run's uploaded video. Headless Simulation Runner has no rendered video.
|
|
939
|
+
|
|
884
940
|
For Jump Bunny, use `https://reality-software-entertainment.github.io/poc-jump-bunny/`
|
|
885
941
|
and the public player label **Ravensight Playtester**. This is a local browser run,
|
|
886
942
|
not a hosted browser service. Human playtesting is still needed for subjective
|
|
@@ -892,7 +948,7 @@ Browser and Godot runners share the paid playtest lifecycle with desktop, Unity/
|
|
|
892
948
|
|
|
893
949
|
## Simulation Runner
|
|
894
950
|
|
|
895
|
-
Available
|
|
951
|
+
Available since `0.4.0`. Simulation Runner requires macOS or Linux (including WSL); native Windows is refused before purchase.
|
|
896
952
|
|
|
897
953
|
Run seeded mechanics tests through a local game adapter, compare explicit strategies and replay selected traces. A batch uses the existing standard Playtest purchase of 5 AI units from prepaid balance, confirmed against the server quote. Only the summary is uploaded; game execution and replay traces stay local. It does not automatically call a model or replace testing the player interface.
|
|
898
954
|
|
package/docs/SIMULATION.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Simulation Runner
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Available since `0.4.0`, with the matching released Ravensight server.
|
|
4
4
|
|
|
5
5
|
Simulation Runner exercises game mechanics through a developer-owned local adapter. It can run seeded games across explicit strategies, summarize outcomes and metrics, and save selected replay traces. Use it for questions such as whether an economy supports several viable strategies or whether a progression rule traps a simulated player. It does not judge animation, interface usability, feel or subjective fun.
|
|
6
6
|
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# The Godot driver
|
|
2
|
+
|
|
3
|
+
`godot_driver` lets a playtest persona observe and control a native Godot game
|
|
4
|
+
by launching the developer's own Godot on their own machine. It has two halves:
|
|
5
|
+
|
|
6
|
+
- `addons/ravensight_driver/` (GDScript, Godot 4.2+): a TCP server that answers
|
|
7
|
+
JSON-RPC requests from inside the running game.
|
|
8
|
+
- `src/run/drivers/godot.js` (Node): starts Godot, speaks that protocol, turns
|
|
9
|
+
the scene tree into an observation, and writes screenshots.
|
|
10
|
+
|
|
11
|
+
Spec 07 is the contract for both halves. This document is how the two sides fit
|
|
12
|
+
together and what the driver is and is not able to do.
|
|
13
|
+
|
|
14
|
+
## The driver never reaches players
|
|
15
|
+
|
|
16
|
+
The addon refuses to start unless every one of these holds:
|
|
17
|
+
|
|
18
|
+
1. The build is not an export template, **or** it was exported with the custom
|
|
19
|
+
feature tag `ravensight_driver`. A normal release export never activates it.
|
|
20
|
+
2. **And** either `--ravensight-driver` is in the user args (after the `--`
|
|
21
|
+
separator) or `RAVENSIGHT_DRIVER=1` is set.
|
|
22
|
+
3. It binds `127.0.0.1` only. There is no code path that binds any other
|
|
23
|
+
address, and the Node client refuses to dial a non loopback host.
|
|
24
|
+
4. If `RAVENSIGHT_DRIVER_TOKEN` is set, the first request on a connection must
|
|
25
|
+
be `hello{token}`. A wrong token gets `-32002` and the connection closes.
|
|
26
|
+
5. One client at a time. A second connection is answered with `-32001 busy` and
|
|
27
|
+
hung up on.
|
|
28
|
+
|
|
29
|
+
When any of that fails the autoload calls `queue_free()` on itself in
|
|
30
|
+
`_ready()`, so an inactive driver is not a node that idles in `_process`, it is
|
|
31
|
+
not in the tree at all.
|
|
32
|
+
|
|
33
|
+
On top of refusing to run, `export_plugin.gd` **strips the whole addon** from
|
|
34
|
+
any export whose preset was not built with the `ravensight_driver` feature tag,
|
|
35
|
+
so the usual release build does not contain the driver's code.
|
|
36
|
+
|
|
37
|
+
One file does not simply vanish. A developer who installs the addon in their own
|
|
38
|
+
project gets `RavensightDriver` written into `project.godot`, and `project.godot`
|
|
39
|
+
ships in the export. Deleting `driver.gd` outright would leave that autoload
|
|
40
|
+
pointing at a file that is not there, and the exported game would greet the
|
|
41
|
+
player with a load error before its first frame. So the strip leaves a four line
|
|
42
|
+
inert stub at `driver.gd`'s path: `extends Node` and a `_ready()` that calls
|
|
43
|
+
`queue_free()`. The autoload resolves, nothing listens, and no driver code
|
|
44
|
+
ships. The alternative, failing the export with a message, was rejected because
|
|
45
|
+
it punishes a developer for having installed the addon at all.
|
|
46
|
+
|
|
47
|
+
This is covered by a test, not an assertion of intent: `godot-real.test.js`
|
|
48
|
+
exports the same project twice with `--export-pack` (which needs no export
|
|
49
|
+
templates) and checks that the plain pack contains the stub and none of the
|
|
50
|
+
driver's source, while a pack built with the feature tag contains all of it.
|
|
51
|
+
|
|
52
|
+
## How the addon gets into the project
|
|
53
|
+
|
|
54
|
+
The CLI never writes to the developer's project. `prepareProjectCopy` copies the
|
|
55
|
+
project to a temp directory (mode `0700`, `.git` and `node_modules` excluded,
|
|
56
|
+
refused above 2 GB), drops the addon into `addons/ravensight_driver/` in the
|
|
57
|
+
copy, and registers the autoload **twice** in that copy:
|
|
58
|
+
|
|
59
|
+
- in `override.cfg`, which is the mechanism spec 07 describes, and
|
|
60
|
+
- in the copy's own `project.godot`.
|
|
61
|
+
|
|
62
|
+
Spec 07's open question 1 asks whether an `override.cfg` autoload works for
|
|
63
|
+
every project layout. Rather than leave that unanswered at runtime, both are
|
|
64
|
+
written. They name the same script, so whichever Godot honors, the result is
|
|
65
|
+
identical. A project that already ships an `override.cfg` or already registers
|
|
66
|
+
other autoloads keeps them: only the one `RavensightDriver` key is added.
|
|
67
|
+
|
|
68
|
+
**Symlinks are the trap here.** `project.godot`, `override.cfg` and `addons/`
|
|
69
|
+
can each be a symlink pointing outside the project, which is what a developer
|
|
70
|
+
gets from a shared settings repo or a vendored addons checkout. Writing to a
|
|
71
|
+
symlink writes to its target, so a naive copy would put the injection into the
|
|
72
|
+
developer's real files, permanently, breaking the one promise spec 07 makes
|
|
73
|
+
about the project directory. Three things prevent it:
|
|
74
|
+
|
|
75
|
+
- the copy runs with `dereference: true`, so a symlink becomes a real file or
|
|
76
|
+
directory in the copy rather than a pointer back out of it;
|
|
77
|
+
- every path this module writes to is materialized first, component by
|
|
78
|
+
component, replacing any surviving link with a real copy of its content;
|
|
79
|
+
- `measureProject` follows symlinked directories when it sizes the project,
|
|
80
|
+
because sizing that ignored them reported a few hundred bytes for a tree the
|
|
81
|
+
copy then dereferenced into gigabytes, and the 2 GB cap did nothing.
|
|
82
|
+
|
|
83
|
+
Dereferencing brings its own failure mode, so the copy filter drops two shapes
|
|
84
|
+
outright. A link resolving to the project directory or any ancestor of it
|
|
85
|
+
(`selfref -> .`, `up -> ..`) is dropped: dereferenced, the copy walks the project
|
|
86
|
+
into itself, finds the link again, and repeats until `PATH_MAX`, which one such
|
|
87
|
+
link turned into 33 nested copies. And a directory already dereferenced once is
|
|
88
|
+
not dereferenced again, because `measureProject` counts a shared target once and
|
|
89
|
+
the two have to agree or the cap stops meaning anything.
|
|
90
|
+
|
|
91
|
+
Both rules compare **realpaths**, not `path.resolve()` output. On macOS
|
|
92
|
+
`os.tmpdir()` is `/var/folders/...`, a symlink to `/private/var/folders/...`, so
|
|
93
|
+
a project living there never matches its own symlink target unless both sides are
|
|
94
|
+
fully resolved, and the rule quietly does nothing. That is a test that caught it,
|
|
95
|
+
not a hypothetical.
|
|
96
|
+
|
|
97
|
+
A failed `prepareProjectCopy` deletes the temp root it created, so a project that
|
|
98
|
+
cannot be prepared does not leave a half written copy on disk. A `tempRoot` the
|
|
99
|
+
caller passed in is left alone: it is theirs.
|
|
100
|
+
|
|
101
|
+
A dangling symlink is dropped from the copy instead of failing the launch:
|
|
102
|
+
`fs.cp` throws on one it cannot dereference, and one broken link is not a reason
|
|
103
|
+
a project cannot be playtested.
|
|
104
|
+
|
|
105
|
+
## Launch
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
<godot> --path <temp copy> --resolution 1280x720 -- --ravensight-driver --ravensight-driver-port=<port>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The binary is found the way spec 17 describes: `--godot-path`, then `$GODOT`,
|
|
112
|
+
then `PATH` (`godot`, `godot4`), then the platform's usual install locations.
|
|
113
|
+
When the project declares an engine version in `config/features`, a binary with
|
|
114
|
+
that major.minor wins over a newer one. Anything below 4.2 is refused.
|
|
115
|
+
|
|
116
|
+
The environment matters as much as the arguments:
|
|
117
|
+
|
|
118
|
+
| Variable | Read by | Why |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `RAVENSIGHT_DRIVER=1` | the addon | activation |
|
|
121
|
+
| `RAVENSIGHT_DRIVER_PORT` | the addon | loopback port, default 47800 |
|
|
122
|
+
| `RAVENSIGHT_DRIVER_TOKEN` | the addon | per run handshake secret |
|
|
123
|
+
| `RAVENSIGHT_PLAYTEST_TOKEN` | the game's own `Ravensight.gd` | playtest session auth |
|
|
124
|
+
| `RAVENSIGHT_PLAYTEST_RUN_ID` | the game's own `Ravensight.gd` | `pt_run` tag, throwaway device id |
|
|
125
|
+
| `RAVENSIGHT_PLAYTEST_JOB_ID` | the game's own `Ravensight.gd` | `pt_job` tag |
|
|
126
|
+
| `RAVENSIGHT_PLAYTEST_PERSONA` | the game's own `Ravensight.gd` | `persona` tag |
|
|
127
|
+
|
|
128
|
+
Only those, plus a fixed allowlist of variables a process needs to start at all
|
|
129
|
+
(`PATH`, `HOME`, the `XDG_*`, `DISPLAY`, `LD_LIBRARY_PATH` and
|
|
130
|
+
`LIBGL_ALWAYS_SOFTWARE` set that the Xvfb path needs, the Windows equivalents),
|
|
131
|
+
reach Godot. The environment is **not** inherited wholesale: the game is the
|
|
132
|
+
customer's own code running on the developer's machine, and it has no business
|
|
133
|
+
seeing `ANTHROPIC_API_KEY`, `RAVENSIGHT_TOKEN`, cloud credentials or CI secrets.
|
|
134
|
+
`ENV_ALLOWLIST` is the list, and `extraEnv` is the deliberate escape hatch for a
|
|
135
|
+
caller that needs one more variable through.
|
|
136
|
+
|
|
137
|
+
The last four are the whole reason a driven run shows up correctly in the
|
|
138
|
+
dashboard: the SDK reads them in `_ready()` and every event the game already
|
|
139
|
+
tracks gets `synthetic: true`, `persona`, `pt_job`, `pt_run` and `pt_tier`
|
|
140
|
+
without the developer writing anything. The driver sets them and otherwise
|
|
141
|
+
stays out of the SDK's way. When the SDK autoload is present the driver also
|
|
142
|
+
connects to its `event_tracked` signal and mirrors each tracked event to the
|
|
143
|
+
runner as a `ravensight_event` notification.
|
|
144
|
+
|
|
145
|
+
The runner then dials `127.0.0.1`, retrying every 250 ms for up to 60 s, sends
|
|
146
|
+
`hello`, and calls `ping`. Godot's stdout and stderr go to `<run dir>/godot.log`
|
|
147
|
+
and are scanned for `SCRIPT ERROR` / `ERROR:` lines, which `consoleErrors()`
|
|
148
|
+
returns with the `at:` source line attached. That is spec 07's stdout fallback:
|
|
149
|
+
the engine's `Logger` hook is not available on every 4.x version the CLI
|
|
150
|
+
supports, so it is not used.
|
|
151
|
+
|
|
152
|
+
## The driver interface
|
|
153
|
+
|
|
154
|
+
`src/run/drivers/driver.js` does not exist on this branch, so `godot.js` writes
|
|
155
|
+
the five method shape out in its own header instead of importing it:
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
launch({ recordVideo, viewport }) // -> {}
|
|
159
|
+
observe({ vision }) // -> Observation
|
|
160
|
+
act(action) // -> { ok, error?, code?, data?, changed }
|
|
161
|
+
screenshot(name) // -> { path } | { path: null, skipped }
|
|
162
|
+
stop({ reason }) // -> { videoPath, recording, frameCount, chapters, logPath, exitCode }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
plus the two optional extras spec 03 lists, `consoleErrors()` and
|
|
166
|
+
`chapter(title)`.
|
|
167
|
+
|
|
168
|
+
`act` maps the spec 03 action kinds onto driver methods:
|
|
169
|
+
|
|
170
|
+
| Action | Driver calls |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `key` | `key` press, `wait_frames{1}`, `key` release |
|
|
173
|
+
| `type` | one press and release per character, each carrying its unicode, then Enter when `submit` |
|
|
174
|
+
| `click` with a string target | `click{node_path}` (the center of the Control's global rect) |
|
|
175
|
+
| `click` with coordinates | `click{x,y}` |
|
|
176
|
+
| `press_action` | `press_action{action, duration_ms}` |
|
|
177
|
+
| `wait` | `wait_frames`, milliseconds converted at 60 fps |
|
|
178
|
+
| `set_time_scale` | `set_time_scale` |
|
|
179
|
+
| `quit` | `quit` |
|
|
180
|
+
|
|
181
|
+
`changed` compares the hash of the scene tree outline before and after the
|
|
182
|
+
action. Two details make that answer honest:
|
|
183
|
+
|
|
184
|
+
- input reaches the game a frame after it is parsed, and a handler that updates
|
|
185
|
+
a label runs a frame after that, so `act` waits `settleFrames` (default 2)
|
|
186
|
+
before taking the comparison snapshot;
|
|
187
|
+
- `observe()` always takes its own `get_state`. An earlier version reused the
|
|
188
|
+
snapshot `act()` had just taken, to save a round trip, and that handed the
|
|
189
|
+
persona a screen one action out of date, which is the opposite of what
|
|
190
|
+
`observe()` is for. Two `get_state` calls a turn is the price of the
|
|
191
|
+
observation being current.
|
|
192
|
+
|
|
193
|
+
A driver level error is returned, not thrown: an undefined input action comes
|
|
194
|
+
back as `{ ok: false, code: -32012, data: { available_actions } }` so the
|
|
195
|
+
persona can pick a real one instead of the run ending.
|
|
196
|
+
|
|
197
|
+
## Observations cite files
|
|
198
|
+
|
|
199
|
+
Every node in a `get_state` snapshot carries its `path`, and also its `scene`
|
|
200
|
+
(`scene_file_path`) and `script` (the script's `resource_path`) when it has
|
|
201
|
+
them. `buildObservation` collects all of those and puts them at the top of the
|
|
202
|
+
observation text:
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
scene: res://main.tscn
|
|
206
|
+
scene node: /root/Main
|
|
207
|
+
scripts: res://ravensight_stub.gd, res://main.gd
|
|
208
|
+
|
|
209
|
+
Window root
|
|
210
|
+
Node2D Main @(0,0) [groups=ravensight_state] {res://main.tscn} <res://main.gd>
|
|
211
|
+
CanvasLayer HUD
|
|
212
|
+
Label TrustLabel "Trust: 4" @(24,16)
|
|
213
|
+
Button BeginButton "Begin" @(600,400) [focus]
|
|
214
|
+
|
|
215
|
+
game state:
|
|
216
|
+
/root/Main: {"phase":"HEALING","trust":4}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
That is what lets a finding say which scene and which script to open. The
|
|
220
|
+
driver's own autoload is filtered out of the snapshot, so its script path never
|
|
221
|
+
appears in that list.
|
|
222
|
+
|
|
223
|
+
Subtrees with nothing a player could see or press are dropped, the outline is
|
|
224
|
+
capped at 12k characters, and `needsVision()` asks for a screenshot when the
|
|
225
|
+
remaining outline is under 200 characters, which is the canvas drawn or 3D case.
|
|
226
|
+
|
|
227
|
+
Spec 07's `extensions/navigation.gd` (`nav_sample`, `nav_path`, `teleport`,
|
|
228
|
+
`raycast`) is **not** part of this addon: those methods exist for spec 06 level
|
|
229
|
+
probing, not for a persona run, and they can be added later through
|
|
230
|
+
`register_method()` without touching the protocol. `list_actions` is the one
|
|
231
|
+
extension method implemented here, because a persona that gets `-32012` needs to
|
|
232
|
+
know what it may press instead.
|
|
233
|
+
|
|
234
|
+
The author hook is unchanged from the spec: a node in the `ravensight_state`
|
|
235
|
+
group with a `_ravensight_state() -> Dictionary` method. Vectors become arrays,
|
|
236
|
+
recursion stops at 8 levels, each result is capped at 64 KB, and a hook that
|
|
237
|
+
returns something other than a Dictionary gets `-32020` naming the node path.
|
|
238
|
+
A broken hook is recorded as a console error against the game, not treated as a
|
|
239
|
+
driver failure.
|
|
240
|
+
|
|
241
|
+
## Headless is for smoke tests, not for playtests
|
|
242
|
+
|
|
243
|
+
Verified against Godot on macOS: in `--headless` mode the engine routes no
|
|
244
|
+
synthesized mouse or GUI input at all. A `click` on a Button whose rect is right
|
|
245
|
+
there does nothing, and `screenshot` returns `-32010` because there is no
|
|
246
|
+
viewport. The same clicks work windowed.
|
|
247
|
+
|
|
248
|
+
So `--headless` is only useful for a game driven entirely by InputMap actions
|
|
249
|
+
that manages its own focus. `launch()` pushes a console entry saying so when it
|
|
250
|
+
detects a headless run, and the local CLI default stays windowed.
|
|
251
|
+
|
|
252
|
+
`--record-video` opts into local capture of this game's viewport; it does not
|
|
253
|
+
consent to upload. `--upload-video` still enables both recording and optional
|
|
254
|
+
video upload. Audio capture is off. Godot samples up to ten frames per second,
|
|
255
|
+
records their actual timestamps, and uses optional local FFmpeg to create
|
|
256
|
+
`session.mp4`. Set `RAVENSIGHT_FFMPEG` to an existing executable when it is not
|
|
257
|
+
on PATH. No package or encoder is downloaded automatically.
|
|
258
|
+
|
|
259
|
+
Recording is capped at 15 minutes, 1280 by 720 pixels, 256 MiB of temporary
|
|
260
|
+
frames and the existing 200 MiB final video limit. Temporary frames are removed
|
|
261
|
+
after encoding. A missing encoder or headless viewport is reported as
|
|
262
|
+
`unsupported` in local `recording.json`; frames are never labeled as video.
|
|
263
|
+
Cancellation, a game crash, or a recording limit can produce a playable partial
|
|
264
|
+
video, explicitly marked with its stop reason. Encoding failure produces no
|
|
265
|
+
uploadable video. Normal stop and concurrent cancellation share one bounded
|
|
266
|
+
finalization. A hard kill of the CLI or machine cannot guarantee finalization.
|
|
267
|
+
|
|
268
|
+
`recording.json` stays local and includes timestamps, duration, size, status,
|
|
269
|
+
reason and `audio: false`. When optional FFprobe is available, duration is read
|
|
270
|
+
from the actual silent media stream; otherwise Godot uses frame timestamps and
|
|
271
|
+
Playwright uses capture clock time, labeled by `durationSource`. This metadata
|
|
272
|
+
is not part of the upload allowlist. Video upload remains separately opted in,
|
|
273
|
+
with the existing per-run limits and server retention unchanged.
|
|
274
|
+
|
|
275
|
+
## Verifying a change
|
|
276
|
+
|
|
277
|
+
The GDScript is parse checked with a real Godot binary, which is the only thing
|
|
278
|
+
that catches an undeclared identifier:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
/Users/.../game_tracker/scripts/check-gdscript.sh \
|
|
282
|
+
/Applications/Godot.app/Contents/MacOS/Godot \
|
|
283
|
+
addons/ravensight_driver/driver.gd \
|
|
284
|
+
addons/ravensight_driver/plugin.gd \
|
|
285
|
+
addons/ravensight_driver/export_plugin.gd
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
It works on this standalone package: `--check-only --script` needs no
|
|
289
|
+
`project.godot`, and `res://` resolves against the working directory, so
|
|
290
|
+
`plugin.gd`'s `preload` of `export_plugin.gd` is checked too. Run it from the
|
|
291
|
+
package root.
|
|
292
|
+
|
|
293
|
+
Most Node tests (`npm test`) run against `testing/fake-godot.js`, a fake driver
|
|
294
|
+
socket that speaks the same wire protocol and enforces the same single client,
|
|
295
|
+
hello before anything, and keycode resolution rules. No Godot binary is involved
|
|
296
|
+
there, so those tests prove the Node side's shapes and nothing about the
|
|
297
|
+
GDScript.
|
|
298
|
+
|
|
299
|
+
`godot-real.test.js` covers what only a real engine can: the activation matrix
|
|
300
|
+
(nothing listens without the flag, a wrong token is refused, a second client is
|
|
301
|
+
refused, an unknown method and a bad param get the right codes), the author hook,
|
|
302
|
+
typing a space, and the export strip. It drives the fixture project at
|
|
303
|
+
`test/fixtures/godot-project/` and **skips itself** when no Godot 4.2+ binary is
|
|
304
|
+
found, the same way the Ravensight repo's ClickHouse integration tests skip
|
|
305
|
+
without a server. A skip there is expected, not a failure to fix, but it does
|
|
306
|
+
mean a change to `driver.gd` is unverified until it has run on a machine with
|
|
307
|
+
Godot installed.
|
|
308
|
+
|
|
309
|
+
The fixture is never written to: every case copies it to a temp directory first,
|
|
310
|
+
so `--import` and `--export-pack` leave no `.godot/` or `out/` behind.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ravensight-playtest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Local AI playtests and seeded game simulations with reports in Ravensight.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://ravensight.io/docs/#playtest",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"addons",
|
|
30
30
|
"docs/ADAPTERS.md",
|
|
31
31
|
"docs/NATIVE_RUNNER.md",
|
|
32
|
+
"docs/godot-driver.md",
|
|
32
33
|
"docs/SIMULATION.md",
|
|
33
34
|
"examples/simulation",
|
|
34
35
|
"README.md",
|
package/src/api/client.js
CHANGED
|
@@ -194,9 +194,9 @@ export function createClient(options = {}) {
|
|
|
194
194
|
* persona run's price; anything else leaves `run_length` out, which the
|
|
195
195
|
* server reads as a standard run.
|
|
196
196
|
*/
|
|
197
|
-
estimate: (gameId, { modules = [], personas = [], runLength = null, simulation } = {}) =>
|
|
197
|
+
estimate: (gameId, { modules = [], personas = [], runLength = null, simulation, controllers, driver } = {}) =>
|
|
198
198
|
request('POST', `${gameBase(gameId)}/estimate`, {
|
|
199
|
-
body: { modules, personas, ...(runLength === 'long' ? { run_length: 'long' } : {}), ...(simulation ? { simulation } : {}) }
|
|
199
|
+
body: { modules, personas, ...(controllers ? { controllers, driver } : {}), ...(runLength === 'long' ? { run_length: 'long' } : {}), ...(simulation ? { simulation } : {}) }
|
|
200
200
|
}).then(answer => answer.body),
|
|
201
201
|
|
|
202
202
|
/**
|
package/src/cli.js
CHANGED
|
@@ -54,7 +54,9 @@ export const RUNNER_HELP = `
|
|
|
54
54
|
Run options (parsed by the runner, see src/run/args.js):
|
|
55
55
|
--personas <list> comma separated persona slugs, default one (curious-kid); "personas" lists them
|
|
56
56
|
--driver <name> playwright_web, godot_driver, desktop_driver, unity_driver, unreal_driver, android_driver, ios_driver or cli_stdio
|
|
57
|
-
--controller <name> standard
|
|
57
|
+
--controller <name> one controller: standard, jev or rune (if enabled)
|
|
58
|
+
--compare-with <names> separate standard + jev and/or rune playthroughs; quotes every run
|
|
59
|
+
--record-video record this game locally; --upload-video also uploads it
|
|
58
60
|
--build-url <url> the web build to play
|
|
59
61
|
--godot-project <dir> the Godot project directory
|
|
60
62
|
--driver-config <file> local WebDriver or JSON-RPC bridge configuration
|