@riddledc/riddle-proof 0.5.36 → 0.5.37
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 +16 -0
- package/dist/chunk-NSWT3VSV.js +361 -0
- package/dist/engine-harness.js +2 -2
- package/dist/index.cjs +364 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +23 -11
- package/dist/playability.cjs +389 -0
- package/dist/playability.d.cts +44 -0
- package/dist/playability.d.ts +44 -0
- package/dist/playability.js +14 -0
- package/dist/types.d.cts +2 -1
- package/dist/types.d.ts +2 -1
- package/package.json +7 -2
- package/runtime/lib/author.py +2 -1
- package/runtime/lib/verify.py +333 -2
- package/dist/{chunk-7R6ZQE3X.js → chunk-GN7HSZ6G.js} +3 -3
package/README.md
CHANGED
|
@@ -158,6 +158,22 @@ transport state, current playhead time, instrument lane readiness, measured
|
|
|
158
158
|
frame drift, console errors, and screenshots of the intended view. The reviewer
|
|
159
159
|
still decides whether it sounds good.
|
|
160
160
|
|
|
161
|
+
### Playable / Game Proof
|
|
162
|
+
|
|
163
|
+
For games, canvas scenes, and interactive toys, use `verification_mode:
|
|
164
|
+
"playable"` or `"gameplay"` when a static screenshot is not enough. The capture
|
|
165
|
+
must prove the experience responds over time:
|
|
166
|
+
|
|
167
|
+
- accepted keyboard, pointer, or touch input
|
|
168
|
+
- game state or HUD values changed after input
|
|
169
|
+
- elapsed play/animation time progressed
|
|
170
|
+
- non-HUD playfield/canvas pixels changed by a measured threshold
|
|
171
|
+
|
|
172
|
+
Expose that as `window.__riddleProofEvidence.playability` or
|
|
173
|
+
`playability_evidence` with `version: "riddle-proof.playability.v1"`. A nice
|
|
174
|
+
still frame, including a generated image plate, is supporting evidence only and
|
|
175
|
+
does not satisfy playable proof by itself.
|
|
176
|
+
|
|
161
177
|
### Server Preview Usage
|
|
162
178
|
|
|
163
179
|
Server preview proof is most useful when the request, capture, and PR comment
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// src/playability.ts
|
|
2
|
+
var RIDDLE_PROOF_PLAYABILITY_VERSION = "riddle-proof.playability.v1";
|
|
3
|
+
var RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION = "riddle-proof.playability.assessment.v1";
|
|
4
|
+
var PLAYABILITY_MODES = /* @__PURE__ */ new Set(["playable", "gameplay", "game"]);
|
|
5
|
+
var PLAYABILITY_CONTAINER_KEYS = [
|
|
6
|
+
"playability",
|
|
7
|
+
"playability_evidence",
|
|
8
|
+
"playabilityEvidence",
|
|
9
|
+
"playable",
|
|
10
|
+
"gameplay",
|
|
11
|
+
"gameplay_evidence",
|
|
12
|
+
"gameplayEvidence"
|
|
13
|
+
];
|
|
14
|
+
var INPUT_ASSERTION_KEYS = [
|
|
15
|
+
"inputAccepted",
|
|
16
|
+
"inputObserved",
|
|
17
|
+
"inputReceived",
|
|
18
|
+
"controlsWorked",
|
|
19
|
+
"keyboardWorked",
|
|
20
|
+
"pointerWorked",
|
|
21
|
+
"touchWorked",
|
|
22
|
+
"steeringInputAccepted",
|
|
23
|
+
"userInputObserved"
|
|
24
|
+
];
|
|
25
|
+
var STATE_ASSERTION_KEYS = [
|
|
26
|
+
"stateChanged",
|
|
27
|
+
"gameStateChanged",
|
|
28
|
+
"playStarted",
|
|
29
|
+
"simulationAdvanced",
|
|
30
|
+
"distanceAdvanced",
|
|
31
|
+
"scoreChanged",
|
|
32
|
+
"hudChanged",
|
|
33
|
+
"positionChanged",
|
|
34
|
+
"speedChanged"
|
|
35
|
+
];
|
|
36
|
+
var MOTION_ASSERTION_KEYS = [
|
|
37
|
+
"motionObserved",
|
|
38
|
+
"visualMotion",
|
|
39
|
+
"canvasChanged",
|
|
40
|
+
"pixelChanged",
|
|
41
|
+
"playfieldMoved",
|
|
42
|
+
"playfieldPixelsChanged",
|
|
43
|
+
"nonHudPixelsChanged",
|
|
44
|
+
"animationAdvanced",
|
|
45
|
+
"frameChanged",
|
|
46
|
+
"framesChanged"
|
|
47
|
+
];
|
|
48
|
+
var TIME_ASSERTION_KEYS = [
|
|
49
|
+
"timeProgressed",
|
|
50
|
+
"clockAdvanced",
|
|
51
|
+
"animationAdvanced",
|
|
52
|
+
"simulationAdvanced",
|
|
53
|
+
"playStarted"
|
|
54
|
+
];
|
|
55
|
+
var PERCENT_KEYS = [
|
|
56
|
+
"changed_percent",
|
|
57
|
+
"change_percent",
|
|
58
|
+
"percent_changed",
|
|
59
|
+
"diff_percent",
|
|
60
|
+
"motion_percent",
|
|
61
|
+
"pixel_change_percent"
|
|
62
|
+
];
|
|
63
|
+
var RATIO_KEYS = [
|
|
64
|
+
"changed_ratio",
|
|
65
|
+
"change_ratio",
|
|
66
|
+
"diff_ratio",
|
|
67
|
+
"motion_ratio",
|
|
68
|
+
"pixel_change_ratio"
|
|
69
|
+
];
|
|
70
|
+
var PIXEL_KEYS = [
|
|
71
|
+
"changed_pixels",
|
|
72
|
+
"changed_pixel_count",
|
|
73
|
+
"diff_pixels",
|
|
74
|
+
"motion_pixels",
|
|
75
|
+
"pixel_delta",
|
|
76
|
+
"changedPixels"
|
|
77
|
+
];
|
|
78
|
+
var AVERAGE_DELTA_KEYS = [
|
|
79
|
+
"average_delta",
|
|
80
|
+
"avg_delta",
|
|
81
|
+
"mean_delta",
|
|
82
|
+
"avg_abs_delta",
|
|
83
|
+
"mean_abs_delta"
|
|
84
|
+
];
|
|
85
|
+
var TIME_DELTA_KEYS = [
|
|
86
|
+
"time_delta_ms",
|
|
87
|
+
"elapsed_ms",
|
|
88
|
+
"duration_ms",
|
|
89
|
+
"sample_duration_ms",
|
|
90
|
+
"animation_delta_ms"
|
|
91
|
+
];
|
|
92
|
+
function isRiddleProofPlayabilityMode(mode) {
|
|
93
|
+
return PLAYABILITY_MODES.has(stringValue(mode).toLowerCase());
|
|
94
|
+
}
|
|
95
|
+
function extractPlayabilityEvidence(...sources) {
|
|
96
|
+
const seen = /* @__PURE__ */ new Set();
|
|
97
|
+
for (const source of sources) {
|
|
98
|
+
const found = findPlayabilityEvidence(source, seen);
|
|
99
|
+
if (found) return found;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
function assessPlayabilityEvidence(evidence, options = {}) {
|
|
104
|
+
const thresholds = {
|
|
105
|
+
minChangedPercent: options.minChangedPercent ?? 0.5,
|
|
106
|
+
minChangedPixels: options.minChangedPixels ?? 1e3,
|
|
107
|
+
minAverageDelta: options.minAverageDelta ?? 1,
|
|
108
|
+
minTimeDeltaMs: options.minTimeDeltaMs ?? 250
|
|
109
|
+
};
|
|
110
|
+
const required = {
|
|
111
|
+
requireInput: options.requireInput ?? true,
|
|
112
|
+
requireStateChange: options.requireStateChange ?? true,
|
|
113
|
+
requireMotion: options.requireMotion ?? true,
|
|
114
|
+
requireTimeProgression: options.requireTimeProgression ?? true
|
|
115
|
+
};
|
|
116
|
+
const record = extractPlayabilityEvidence(evidence);
|
|
117
|
+
const metrics = {};
|
|
118
|
+
const concerns = [];
|
|
119
|
+
if (!record) {
|
|
120
|
+
return {
|
|
121
|
+
version: RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION,
|
|
122
|
+
evidence_present: false,
|
|
123
|
+
passed: false,
|
|
124
|
+
input_observed: false,
|
|
125
|
+
state_changed: false,
|
|
126
|
+
motion_observed: false,
|
|
127
|
+
time_progressed: false,
|
|
128
|
+
concerns: ["playability evidence is missing"],
|
|
129
|
+
metrics,
|
|
130
|
+
thresholds,
|
|
131
|
+
required,
|
|
132
|
+
evidence_keys: []
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const assertions = recordValue(record.assertions) || record;
|
|
136
|
+
const inputObserved = inputObservedFrom(record, assertions);
|
|
137
|
+
const stateChanged = stateChangedFrom(record, assertions, metrics);
|
|
138
|
+
const motionObserved = motionObservedFrom(record, assertions, thresholds, metrics);
|
|
139
|
+
const timeProgressed = timeProgressedFrom(record, assertions, thresholds, metrics);
|
|
140
|
+
const explicitFailure = hasExplicitPlayabilityFailure(record, assertions);
|
|
141
|
+
if (required.requireInput && !inputObserved) {
|
|
142
|
+
concerns.push("no accepted player input was observed");
|
|
143
|
+
}
|
|
144
|
+
if (required.requireStateChange && !stateChanged) {
|
|
145
|
+
concerns.push("game state did not measurably change");
|
|
146
|
+
}
|
|
147
|
+
if (required.requireMotion && !motionObserved) {
|
|
148
|
+
concerns.push("playfield/canvas pixels did not measurably change");
|
|
149
|
+
}
|
|
150
|
+
if (required.requireTimeProgression && !timeProgressed) {
|
|
151
|
+
concerns.push("play time or animation time did not measurably progress");
|
|
152
|
+
}
|
|
153
|
+
if (explicitFailure) {
|
|
154
|
+
concerns.push("playability evidence includes an explicit failed assertion");
|
|
155
|
+
}
|
|
156
|
+
const passed = Boolean(
|
|
157
|
+
(!required.requireInput || inputObserved) && (!required.requireStateChange || stateChanged) && (!required.requireMotion || motionObserved) && (!required.requireTimeProgression || timeProgressed) && !explicitFailure
|
|
158
|
+
);
|
|
159
|
+
return {
|
|
160
|
+
version: RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION,
|
|
161
|
+
evidence_present: true,
|
|
162
|
+
passed,
|
|
163
|
+
input_observed: inputObserved,
|
|
164
|
+
state_changed: stateChanged,
|
|
165
|
+
motion_observed: motionObserved,
|
|
166
|
+
time_progressed: timeProgressed,
|
|
167
|
+
concerns,
|
|
168
|
+
metrics,
|
|
169
|
+
thresholds,
|
|
170
|
+
required,
|
|
171
|
+
evidence_keys: Object.keys(record)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function findPlayabilityEvidence(value, seen, depth = 0) {
|
|
175
|
+
if (depth > 6 || value === null || value === void 0) return null;
|
|
176
|
+
if (typeof value === "string") {
|
|
177
|
+
const parsed = parseJson(value);
|
|
178
|
+
return parsed === null ? null : findPlayabilityEvidence(parsed, seen, depth + 1);
|
|
179
|
+
}
|
|
180
|
+
if (Array.isArray(value)) {
|
|
181
|
+
if (seen.has(value)) return null;
|
|
182
|
+
seen.add(value);
|
|
183
|
+
for (const item of value) {
|
|
184
|
+
const found = findPlayabilityEvidence(item, seen, depth + 1);
|
|
185
|
+
if (found) return found;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const record = recordValue(value);
|
|
190
|
+
if (!record || seen.has(record)) return null;
|
|
191
|
+
seen.add(record);
|
|
192
|
+
if (record.version === RIDDLE_PROOF_PLAYABILITY_VERSION || hasPlayabilityShape(record)) {
|
|
193
|
+
return record;
|
|
194
|
+
}
|
|
195
|
+
for (const key of PLAYABILITY_CONTAINER_KEYS) {
|
|
196
|
+
if (Object.prototype.hasOwnProperty.call(record, key)) {
|
|
197
|
+
const nested = findPlayabilityEvidence(record[key], seen, depth + 1);
|
|
198
|
+
if (nested) return nested;
|
|
199
|
+
if (typeof record[key] === "boolean") {
|
|
200
|
+
return { assertions: { [key]: record[key] } };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const item of Object.values(record)) {
|
|
205
|
+
const found = findPlayabilityEvidence(item, seen, depth + 1);
|
|
206
|
+
if (found) return found;
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
function hasPlayabilityShape(record) {
|
|
211
|
+
return Boolean(
|
|
212
|
+
record.input_events || record.state_delta || record.pixel_delta || record.canvas_delta || record.motion_delta || record.playfield_delta || record.time_delta_ms !== void 0 || hasAnyKey(recordValue(record.assertions) || record, [
|
|
213
|
+
...INPUT_ASSERTION_KEYS,
|
|
214
|
+
...STATE_ASSERTION_KEYS,
|
|
215
|
+
...MOTION_ASSERTION_KEYS,
|
|
216
|
+
...TIME_ASSERTION_KEYS
|
|
217
|
+
])
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
function inputObservedFrom(record, assertions) {
|
|
221
|
+
if (trueForAnyKey(assertions, INPUT_ASSERTION_KEYS)) return true;
|
|
222
|
+
if (listValue(record.input_events).length > 0) return true;
|
|
223
|
+
if (listValue(record.inputs).length > 0) return true;
|
|
224
|
+
if (listValue(record.interactions).length > 0) return true;
|
|
225
|
+
const eventCount = numericFromAnyKey(record, ["input_event_count", "input_count", "interaction_count"]);
|
|
226
|
+
return eventCount !== null && eventCount > 0;
|
|
227
|
+
}
|
|
228
|
+
function stateChangedFrom(record, assertions, metrics) {
|
|
229
|
+
if (trueForAnyKey(assertions, STATE_ASSERTION_KEYS)) return true;
|
|
230
|
+
const stateDelta = recordValue(record.state_delta) || recordValue(record.stateDelta);
|
|
231
|
+
if (stateDelta) {
|
|
232
|
+
const changedKeys = listValue(stateDelta.changed_keys || stateDelta.changedKeys);
|
|
233
|
+
metrics.state_changed_keys = changedKeys;
|
|
234
|
+
if (stateDelta.changed === true || changedKeys.length > 0) return true;
|
|
235
|
+
}
|
|
236
|
+
const delta = numericFromAnyKey(record, [
|
|
237
|
+
"distance_delta",
|
|
238
|
+
"score_delta",
|
|
239
|
+
"position_delta",
|
|
240
|
+
"speed_delta",
|
|
241
|
+
"hud_delta"
|
|
242
|
+
]);
|
|
243
|
+
if (delta !== null) {
|
|
244
|
+
metrics.state_numeric_delta = delta;
|
|
245
|
+
return Math.abs(delta) > 0;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
function motionObservedFrom(record, assertions, thresholds, metrics) {
|
|
250
|
+
if (trueForAnyKey(assertions, MOTION_ASSERTION_KEYS)) return true;
|
|
251
|
+
for (const source of [
|
|
252
|
+
recordValue(record.playfield_delta),
|
|
253
|
+
recordValue(record.playfieldDelta),
|
|
254
|
+
recordValue(record.non_hud_delta),
|
|
255
|
+
recordValue(record.nonHudDelta),
|
|
256
|
+
recordValue(record.pixel_delta),
|
|
257
|
+
recordValue(record.pixelDelta),
|
|
258
|
+
recordValue(record.canvas_delta),
|
|
259
|
+
recordValue(record.canvasDelta),
|
|
260
|
+
recordValue(record.motion_delta),
|
|
261
|
+
recordValue(record.motionDelta),
|
|
262
|
+
recordValue(record.visual_delta),
|
|
263
|
+
recordValue(record.visualDelta),
|
|
264
|
+
record
|
|
265
|
+
]) {
|
|
266
|
+
if (!source) continue;
|
|
267
|
+
const percent = percentFrom(source);
|
|
268
|
+
const pixels = numericFromAnyKey(source, PIXEL_KEYS);
|
|
269
|
+
const averageDelta = numericFromAnyKey(source, AVERAGE_DELTA_KEYS);
|
|
270
|
+
if (percent !== null) metrics.changed_percent = percent;
|
|
271
|
+
if (pixels !== null) metrics.changed_pixels = pixels;
|
|
272
|
+
if (averageDelta !== null) metrics.average_delta = averageDelta;
|
|
273
|
+
if (percent !== null && percent >= thresholds.minChangedPercent) return true;
|
|
274
|
+
if (pixels !== null && pixels >= thresholds.minChangedPixels) return true;
|
|
275
|
+
if (averageDelta !== null && averageDelta >= thresholds.minAverageDelta) return true;
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
function timeProgressedFrom(record, assertions, thresholds, metrics) {
|
|
280
|
+
if (trueForAnyKey(assertions, TIME_ASSERTION_KEYS)) return true;
|
|
281
|
+
const timeDelta = numericFromAnyKey(record, TIME_DELTA_KEYS);
|
|
282
|
+
if (timeDelta !== null) {
|
|
283
|
+
metrics.time_delta_ms = timeDelta;
|
|
284
|
+
return timeDelta >= thresholds.minTimeDeltaMs;
|
|
285
|
+
}
|
|
286
|
+
const stateDelta = recordValue(record.state_delta) || recordValue(record.stateDelta);
|
|
287
|
+
const nestedDelta = numericFromAnyKey(stateDelta || {}, TIME_DELTA_KEYS);
|
|
288
|
+
if (nestedDelta !== null) {
|
|
289
|
+
metrics.time_delta_ms = nestedDelta;
|
|
290
|
+
return nestedDelta >= thresholds.minTimeDeltaMs;
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
function hasExplicitPlayabilityFailure(record, assertions) {
|
|
295
|
+
if (record.passed === false || record.playable === false || assertions.playabilityPassed === false) {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
for (const key of [
|
|
299
|
+
...INPUT_ASSERTION_KEYS,
|
|
300
|
+
...STATE_ASSERTION_KEYS,
|
|
301
|
+
...MOTION_ASSERTION_KEYS,
|
|
302
|
+
...TIME_ASSERTION_KEYS
|
|
303
|
+
]) {
|
|
304
|
+
if (assertions[key] === false) return true;
|
|
305
|
+
}
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
function trueForAnyKey(record, keys) {
|
|
309
|
+
return keys.some((key) => record[key] === true);
|
|
310
|
+
}
|
|
311
|
+
function hasAnyKey(record, keys) {
|
|
312
|
+
return keys.some((key) => Object.prototype.hasOwnProperty.call(record, key));
|
|
313
|
+
}
|
|
314
|
+
function percentFrom(record) {
|
|
315
|
+
const percent = numericFromAnyKey(record, PERCENT_KEYS);
|
|
316
|
+
if (percent !== null) return percent;
|
|
317
|
+
const ratio = numericFromAnyKey(record, RATIO_KEYS);
|
|
318
|
+
if (ratio !== null) return ratio <= 1 ? ratio * 100 : ratio;
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
function numericFromAnyKey(record, keys) {
|
|
322
|
+
for (const key of keys) {
|
|
323
|
+
const value = numericValue(record[key]);
|
|
324
|
+
if (value !== null) return value;
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
function recordValue(value) {
|
|
329
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
330
|
+
}
|
|
331
|
+
function listValue(value) {
|
|
332
|
+
return Array.isArray(value) ? value : [];
|
|
333
|
+
}
|
|
334
|
+
function numericValue(value) {
|
|
335
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
336
|
+
if (typeof value === "string" && value.trim()) {
|
|
337
|
+
const parsed = Number(value);
|
|
338
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
function stringValue(value) {
|
|
343
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
344
|
+
}
|
|
345
|
+
function parseJson(value) {
|
|
346
|
+
const trimmed = value.trim();
|
|
347
|
+
if (!trimmed || !/^[{[]/.test(trimmed)) return null;
|
|
348
|
+
try {
|
|
349
|
+
return JSON.parse(trimmed);
|
|
350
|
+
} catch {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export {
|
|
356
|
+
RIDDLE_PROOF_PLAYABILITY_VERSION,
|
|
357
|
+
RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION,
|
|
358
|
+
isRiddleProofPlayabilityMode,
|
|
359
|
+
extractPlayabilityEvidence,
|
|
360
|
+
assessPlayabilityEvidence
|
|
361
|
+
};
|
package/dist/engine-harness.js
CHANGED
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
createDisabledRiddleProofAgentAdapter,
|
|
3
3
|
readRiddleProofRunStatus,
|
|
4
4
|
runRiddleProofEngineHarness
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GN7HSZ6G.js";
|
|
6
6
|
import "./chunk-OASB3CYU.js";
|
|
7
|
-
import "./chunk-4YCWZVBN.js";
|
|
8
7
|
import "./chunk-J2MERROF.js";
|
|
8
|
+
import "./chunk-4YCWZVBN.js";
|
|
9
9
|
export {
|
|
10
10
|
createDisabledRiddleProofAgentAdapter,
|
|
11
11
|
readRiddleProofRunStatus,
|