ravensight-playtest 0.5.2 → 0.7.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.
Files changed (99) hide show
  1. package/README.md +319 -14
  2. package/addons/ravensight_driver/driver.gd +406 -8
  3. package/addons/ravensight_simulate/simulate.gd +360 -0
  4. package/docs/godot-driver.md +70 -8
  5. package/package.json +4 -3
  6. package/src/api/README.md +4 -1
  7. package/src/api/client.js +120 -3
  8. package/src/api/errors.js +17 -1
  9. package/src/api/http.js +43 -17
  10. package/src/assets/formats.js +267 -0
  11. package/src/assets/index.js +260 -0
  12. package/src/assets/integration.js +429 -0
  13. package/src/assets/inventory.js +337 -0
  14. package/src/assets/measure.js +392 -0
  15. package/src/cli.js +233 -0
  16. package/src/commands/assets.js +62 -0
  17. package/src/commands/check.js +4 -1
  18. package/src/commands/create.js +734 -0
  19. package/src/commands/fakeCore.js +16 -1
  20. package/src/commands/feature.js +845 -0
  21. package/src/commands/fix.js +647 -70
  22. package/src/commands/init.js +19 -3
  23. package/src/commands/lists.js +206 -0
  24. package/src/commands/profile.js +25 -6
  25. package/src/commands/punch.js +116 -0
  26. package/src/commands/replay.js +204 -0
  27. package/src/commands/resume.js +7 -0
  28. package/src/commands/run.js +200 -24
  29. package/src/commands/tape.js +74 -0
  30. package/src/commands/template.js +70 -0
  31. package/src/commands/validate.js +414 -0
  32. package/src/commands/validateHosted.js +292 -0
  33. package/src/create/scaffold.js +192 -0
  34. package/src/run/acceptance.js +212 -0
  35. package/src/run/args.js +18 -1
  36. package/src/run/checks.js +705 -0
  37. package/src/run/dimension.js +95 -0
  38. package/src/run/drivers/godot-observation.js +59 -11
  39. package/src/run/drivers/godot-project.js +201 -31
  40. package/src/run/drivers/godot.js +532 -45
  41. package/src/run/drivers/web.js +18 -0
  42. package/src/run/evidence.js +170 -0
  43. package/src/run/featureLoop.js +288 -0
  44. package/src/run/fixLoop.js +82 -12
  45. package/src/run/git.js +110 -20
  46. package/src/run/json.js +65 -9
  47. package/src/run/labsOutcome.js +136 -0
  48. package/src/run/liveWork.js +103 -0
  49. package/src/run/model.js +105 -5
  50. package/src/run/openPr.js +107 -0
  51. package/src/run/paths.js +4 -1
  52. package/src/run/pendingRegister.js +81 -0
  53. package/src/run/personaLoop.js +111 -4
  54. package/src/run/profile.js +68 -7
  55. package/src/run/replay.js +236 -0
  56. package/src/run/repoTools.js +227 -27
  57. package/src/run/report.js +7 -1
  58. package/src/run/resume.js +19 -1
  59. package/src/run/secretScan.js +35 -0
  60. package/src/run/settle.js +41 -0
  61. package/src/run/tools.js +33 -4
  62. package/src/run/trace.js +124 -0
  63. package/src/simulation/godotPack.js +112 -0
  64. package/src/ui/index.js +61 -9
  65. package/src/upload/allowlist.js +6 -2
  66. package/src/upload/index.js +5 -4
  67. package/templates/godot-2d/README.md +36 -0
  68. package/templates/godot-2d/addons/ravensight/LICENSE +21 -0
  69. package/templates/godot-2d/addons/ravensight/Ravensight.gd +1823 -0
  70. package/templates/godot-2d/export_presets.cfg +27 -0
  71. package/templates/godot-2d/gitignore +10 -0
  72. package/templates/godot-2d/project.godot +58 -0
  73. package/templates/godot-2d/scenes/main.tscn +67 -0
  74. package/templates/godot-2d/scripts/autoload/game_config.gd +83 -0
  75. package/templates/godot-2d/scripts/autoload/game_state.gd +44 -0
  76. package/templates/godot-2d/scripts/autoload/telemetry.gd +37 -0
  77. package/templates/godot-2d/scripts/main.gd +32 -0
  78. package/templates/godot-2d/scripts/player.gd +10 -0
  79. package/templates/godot-2d/scripts/state_hook.gd +16 -0
  80. package/templates/godot-2d/template.json +14 -0
  81. package/templates/godot-2d/tools/inject_release_key.sh +19 -0
  82. package/templates/godot-2d/tools/selftest.gd +70 -0
  83. package/templates/godot-3d/README.md +49 -0
  84. package/templates/godot-3d/addons/ravensight/LICENSE +21 -0
  85. package/templates/godot-3d/addons/ravensight/Ravensight.gd +1823 -0
  86. package/templates/godot-3d/export_presets.cfg +27 -0
  87. package/templates/godot-3d/gitignore +10 -0
  88. package/templates/godot-3d/project.godot +56 -0
  89. package/templates/godot-3d/scenes/main.tscn +154 -0
  90. package/templates/godot-3d/scripts/autoload/game_config.gd +83 -0
  91. package/templates/godot-3d/scripts/autoload/game_state.gd +44 -0
  92. package/templates/godot-3d/scripts/autoload/telemetry.gd +37 -0
  93. package/templates/godot-3d/scripts/beacon.gd +20 -0
  94. package/templates/godot-3d/scripts/main.gd +49 -0
  95. package/templates/godot-3d/scripts/player.gd +19 -0
  96. package/templates/godot-3d/scripts/state_hook.gd +25 -0
  97. package/templates/godot-3d/template.json +16 -0
  98. package/templates/godot-3d/tools/inject_release_key.sh +19 -0
  99. package/templates/godot-3d/tools/selftest.gd +77 -0
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Fix v2's acceptance contract and its Labs gate.
3
+ *
4
+ * The contract is written from the finding BEFORE anything is edited, sent at
5
+ * register, and frozen by the server on the job. From then on it is the only
6
+ * thing the verify brief is built from, and the apply step reads it but cannot
7
+ * change it: a repair turn is told what failed, never handed a new definition
8
+ * of success.
9
+ *
10
+ * Shape (the server's field names): `{ expected, actual, reproSteps[],
11
+ * regressionPersonas[] }`.
12
+ *
13
+ * Fix v2 is a Labs (Beta) capability, and the server decides whether it is
14
+ * on, not a client flag. Without a server that says so, and without a job
15
+ * that comes back carrying the frozen contract, `fix` behaves exactly as it
16
+ * did before Fix v2.
17
+ */
18
+
19
+ /** Field limits, so a contract built from a long finding still fits. */
20
+ export const ACCEPTANCE_LIMITS = Object.freeze({ text: 1000, steps: 20, step: 300, personas: 5 });
21
+
22
+ /** The Labs capability this module depends on. */
23
+ export const FIX_V2_CAPABILITY = 'fix_v2';
24
+
25
+ function text(value, limit) {
26
+ if (typeof value !== 'string') return '';
27
+ const trimmed = value.trim();
28
+ return trimmed.length <= limit ? trimmed : trimmed.slice(0, limit);
29
+ }
30
+
31
+ /**
32
+ * Build the contract from a finding. Pure; nothing here asks a model.
33
+ *
34
+ * - `actual` is what the persona saw: the finding's own `actual`, else its
35
+ * description, else its title.
36
+ * - `expected` is the finding's own `expected`, else the plain negation of
37
+ * the title, which is what "fixed" means when nobody wrote more.
38
+ * - `reproSteps` are the finding's steps, as recorded.
39
+ * - `regressionPersonas` is the persona who filed it, who also plays verify.
40
+ *
41
+ * @param {Object} finding
42
+ * @param {{persona?: string|null, reproSteps?: Array<string>}} [options]
43
+ * @returns {{expected: string, actual: string, reproSteps: Array<string>, regressionPersonas: Array<string>}}
44
+ */
45
+ export function buildAcceptance(finding = {}, { persona = null, reproSteps = [] } = {}) {
46
+ const title = text(finding.title, ACCEPTANCE_LIMITS.text);
47
+ const actual = text(finding.actual, ACCEPTANCE_LIMITS.text)
48
+ || text(finding.description, ACCEPTANCE_LIMITS.text)
49
+ || title
50
+ || 'the behaviour the finding describes';
51
+ const expected = text(finding.expected, ACCEPTANCE_LIMITS.text)
52
+ || (title ? `This no longer happens: ${title}` : 'The behaviour the finding describes no longer happens.');
53
+ const personas = [persona || finding.persona].filter(entry => typeof entry === 'string' && entry.trim());
54
+ return freeze({
55
+ expected,
56
+ actual,
57
+ reproSteps: reproSteps
58
+ .map(step => text(String(step), ACCEPTANCE_LIMITS.step))
59
+ .filter(Boolean)
60
+ .slice(0, ACCEPTANCE_LIMITS.steps),
61
+ regressionPersonas: [...new Set(personas)].slice(0, ACCEPTANCE_LIMITS.personas)
62
+ });
63
+ }
64
+
65
+ /**
66
+ * The contract the server froze, read from a register answer, or null when
67
+ * the server kept none (an older server, or Labs off). Null means the standard fix.
68
+ *
69
+ * @param {Object} registered
70
+ * @returns {Object|null}
71
+ */
72
+ export function frozenAcceptance(registered) {
73
+ const raw = (registered && (registered.acceptance || (registered.job && registered.job.acceptance))) || null;
74
+ if (!raw || typeof raw !== 'object') return null;
75
+ const expected = text(raw.expected, ACCEPTANCE_LIMITS.text);
76
+ const actual = text(raw.actual, ACCEPTANCE_LIMITS.text);
77
+ if (!expected || !actual) return null;
78
+ const list = value => (Array.isArray(value) ? value.filter(entry => typeof entry === 'string' && entry.trim()) : []);
79
+ return freeze({
80
+ expected,
81
+ actual,
82
+ reproSteps: list(raw.reproSteps),
83
+ regressionPersonas: list(raw.regressionPersonas)
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Whether the server says Fix v2 is live for this game. Anything other than a
89
+ * clear yes is no: a missing route (an older server), an error, Labs off for
90
+ * the game, or the capability's kill switch.
91
+ *
92
+ * Accepted answers: `{ enabled, capabilities: { fix_v2: true } }` or
93
+ * `{ capabilities: { fix_v2: { live: true } } }` (`enabled: true` is read the
94
+ * same as `live`).
95
+ *
96
+ * @param {Object} api
97
+ * @param {string} gameId
98
+ * @returns {Promise<boolean>}
99
+ */
100
+ export async function fixV2Live(api, gameId, { onUnfunded } = {}) {
101
+ if (!api || !api.labs || typeof api.labs.get !== 'function') return false;
102
+ let answer;
103
+ try {
104
+ answer = await api.labs.get(gameId);
105
+ } catch {
106
+ return false;
107
+ }
108
+ if (!capabilityLive(answer, FIX_V2_CAPABILITY)) return false;
109
+ // A register would refuse it, and the ordinary fix is right there at the same price.
110
+ const labs = answer.labs && typeof answer.labs === 'object' ? answer.labs : answer;
111
+ if (labs.funding_met && labs.funding_met[FIX_V2_CAPABILITY] === false) {
112
+ if (onUnfunded) onUnfunded();
113
+ return false;
114
+ }
115
+ return true;
116
+ }
117
+
118
+ /**
119
+ * Whether this game's traces should be uploaded: any Labs capability that reads
120
+ * them back is on for it. Never throws; no answer is no upload.
121
+ *
122
+ * @param {Object} api
123
+ * @param {string} gameId
124
+ * @returns {Promise<boolean>}
125
+ */
126
+ export async function tracesWanted(api, gameId) {
127
+ if (!api || !api.labs || typeof api.labs.get !== 'function') return false;
128
+ try {
129
+ const answer = await api.labs.get(gameId);
130
+ return capabilityLive(answer, 'validate') || capabilityLive(answer, FIX_V2_CAPABILITY);
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * @param {Object} answer
138
+ * @param {string} name
139
+ * @returns {boolean}
140
+ */
141
+ export function capabilityLive(answer, name) {
142
+ if (!answer || typeof answer !== 'object') return false;
143
+ const labs = answer.labs && typeof answer.labs === 'object' ? answer.labs : answer;
144
+ if (labs.enabled === false) return false;
145
+ const entry = labs.capabilities && labs.capabilities[name];
146
+ if (entry === true) return true;
147
+ return Boolean(entry && typeof entry === 'object' && (entry.live === true || entry.enabled === true));
148
+ }
149
+
150
+ /**
151
+ * The contract as the developer reads it, before agreeing to pay.
152
+ *
153
+ * @param {Object} acceptance
154
+ * @returns {Array<string>}
155
+ */
156
+ export function acceptanceLines(acceptance) {
157
+ return [
158
+ 'Acceptance contract (frozen at register; the fix is judged against this and nothing else):',
159
+ ` expected: ${acceptance.expected}`,
160
+ ` actual: ${acceptance.actual}`,
161
+ acceptance.reproSteps.length > 0
162
+ ? ` repro: ${acceptance.reproSteps.map((step, index) => `${index + 1}. ${step}`).join(' ')}`
163
+ : ' repro: none recorded',
164
+ ` verify and regression personas: ${acceptance.regressionPersonas.join(', ') || 'none'}`
165
+ ];
166
+ }
167
+
168
+ /**
169
+ * The contract as a prompt block, marked read-only.
170
+ *
171
+ * @param {Object} acceptance
172
+ * @returns {string}
173
+ */
174
+ export function acceptanceBlock(acceptance) {
175
+ return [
176
+ '<acceptance read_only="true">',
177
+ JSON.stringify(acceptance, null, 2),
178
+ '</acceptance>',
179
+ 'The acceptance contract above was frozen before any edit. You cannot change it, and the fix is judged against it alone.'
180
+ ].join('\n');
181
+ }
182
+
183
+ /**
184
+ * The verify run's brief, built from the frozen contract only: not from the
185
+ * finding, not from the plan, not from anything the apply step said.
186
+ *
187
+ * @param {Object|null} base the game's brief
188
+ * @param {Object} acceptance
189
+ * @returns {Object}
190
+ */
191
+ export function verifyBriefFromAcceptance(base, acceptance) {
192
+ const fields = (base && base.fields) || {};
193
+ return {
194
+ ...(base || {}),
195
+ fields: {
196
+ verify_task: `Check whether this still happens: ${acceptance.actual}`,
197
+ verify_expected: acceptance.expected,
198
+ verify_repro_steps: [...acceptance.reproSteps],
199
+ verify_instruction: 'Follow these steps first and say plainly whether the actual behaviour still happens or the expected behaviour happens instead. Then play as you normally would.',
200
+ ...fields
201
+ }
202
+ };
203
+ }
204
+
205
+ function freeze(value) {
206
+ for (const key of Object.keys(value)) {
207
+ if (Array.isArray(value[key])) Object.freeze(value[key]);
208
+ }
209
+ return Object.freeze(value);
210
+ }
211
+
212
+ export default { buildAcceptance, frozenAcceptance, fixV2Live, capabilityLive, acceptanceLines, acceptanceBlock, verifyBriefFromAcceptance };
package/src/run/args.js CHANGED
@@ -20,6 +20,9 @@ const VALUED = new Map([
20
20
  ['--build-url', 'buildUrl'],
21
21
  ['--build-dir', 'buildDir'],
22
22
  ['--godot-project', 'godotProject'],
23
+ // The Godot binary, as check and the driver docs take it. RAVENSIGHT_GODOT
24
+ // and GODOT still work when it is not given.
25
+ ['--godot-path', 'godotPath'],
23
26
  ['--cli', 'cli'],
24
27
  ['--max-actions', 'maxActions'],
25
28
  ['--concurrency', 'concurrency'],
@@ -40,11 +43,25 @@ const BOOLEAN = new Map([
40
43
  // A long persona run: twice the actions, model budget and wall time of a
41
44
  // standard run, priced by the server as its own unit.
42
45
  ['--long', 'long'],
46
+ // godot_driver only: let the game run in real time between actions instead
47
+ // of pausing it while the persona thinks.
48
+ ['--realtime', 'realtime'],
43
49
  // Native drivers only: register without the free automation probe first.
44
50
  ['--skip-preflight', 'skipPreflight'],
45
51
  // Resume only: replay an interrupted run from the start instead of writing a
46
52
  // partial report from the transcript it already has.
47
- ['--restart-interrupted', 'restartInterrupted']
53
+ ['--restart-interrupted', 'restartInterrupted'],
54
+ // Fix only, Fix v2 (Labs beta): after a verified fix, ask, then commit the
55
+ // fix's files, push the branch and open a pull request with gh.
56
+ ['--open-pr', 'openPr'],
57
+ // Fix only: run the standard fix even when Fix v2 is on for the game.
58
+ ['--no-labs', 'noLabs'],
59
+ // Fix v2: let the checks run the project's own tests, and run on edits to
60
+ // test files.
61
+ ['--allow-tests', 'allowTests'],
62
+ // Fix v2: let the checks import a project whose edits touched editor-side
63
+ // code (project.godot, export presets, addons, .godot, @tool scripts).
64
+ ['--allow-tool-scripts', 'allowToolScripts']
48
65
  ]);
49
66
 
50
67
  /** Options whose value is a number. */