@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 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
+ };
@@ -2,10 +2,10 @@ import {
2
2
  createDisabledRiddleProofAgentAdapter,
3
3
  readRiddleProofRunStatus,
4
4
  runRiddleProofEngineHarness
5
- } from "./chunk-7R6ZQE3X.js";
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,