@riddledc/riddle-proof 0.5.56 → 0.6.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.
package/README.md CHANGED
@@ -214,6 +214,32 @@ Expose that as `window.__riddleProofEvidence.playability` or
214
214
  still frame, including a generated image plate, is supporting evidence only and
215
215
  does not satisfy playable proof by itself.
216
216
 
217
+ ### Basic Gameplay Helpers
218
+
219
+ `@riddledc/riddle-proof/basic-gameplay` exposes reusable proof primitives for
220
+ route-by-route game/site suites. The helpers assess the public
221
+ `riddle-proof.basic-gameplay.v1` evidence schema, attach Riddle screenshot
222
+ artifact hashes back to phase metrics, resolve visual false negatives when
223
+ artifact screenshots differ, and convert progression failures into catch
224
+ records.
225
+
226
+ ```ts
227
+ import {
228
+ assessBasicGameplayEvidence,
229
+ attachBasicGameplayArtifactScreenshotHashes,
230
+ createBasicGameplayCatchRecords,
231
+ } from "@riddledc/riddle-proof/basic-gameplay";
232
+
233
+ attachBasicGameplayArtifactScreenshotHashes(evidence, { artifacts });
234
+ const assessment = assessBasicGameplayEvidence(evidence);
235
+ const catches = createBasicGameplayCatchRecords(assessment, evidence);
236
+ ```
237
+
238
+ The package owns generic contracts such as `state_path`, `state_call`,
239
+ `property_path`, `number_unchanged`, held-key/window-call/evaluate action type
240
+ constants, and JSON-safe text compaction. Site-specific manifests, selectors,
241
+ and deterministic game scripts should stay in the caller.
242
+
217
243
  ### Server Preview Usage
218
244
 
219
245
  Server preview proof is most useful when the request, capture, and PR comment
@@ -20,18 +20,56 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/basic-gameplay.ts
21
21
  var basic_gameplay_exports = {};
22
22
  __export(basic_gameplay_exports, {
23
+ BASIC_GAMEPLAY_ACTION_TYPES: () => BASIC_GAMEPLAY_ACTION_TYPES,
24
+ BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES: () => BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES,
23
25
  RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION: () => RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
24
26
  RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION: () => RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION,
25
27
  RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION: () => RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION,
26
28
  assessBasicGameplayEvidence: () => assessBasicGameplayEvidence,
29
+ assessBasicGameplayProgressionCheck: () => assessBasicGameplayProgressionCheck,
30
+ assessBasicGameplayProgressionChecks: () => assessBasicGameplayProgressionChecks,
27
31
  assessBasicGameplayRoute: () => assessBasicGameplayRoute,
32
+ attachBasicGameplayArtifactScreenshotHashes: () => attachBasicGameplayArtifactScreenshotHashes,
33
+ augmentBasicGameplayAssessmentWithProgressionChecks: () => augmentBasicGameplayAssessmentWithProgressionChecks,
34
+ compactBasicGameplayText: () => compactBasicGameplayText,
35
+ createBasicGameplayCatchRecords: () => createBasicGameplayCatchRecords,
28
36
  createBasicGameplayCatchSummary: () => createBasicGameplayCatchSummary,
29
- extractBasicGameplayEvidence: () => extractBasicGameplayEvidence
37
+ extractBasicGameplayEvidence: () => extractBasicGameplayEvidence,
38
+ resolveBasicGameplayProgressionCheckWithArtifactScreenshots: () => resolveBasicGameplayProgressionCheckWithArtifactScreenshots,
39
+ sanitizeBasicGameplayJsonString: () => sanitizeBasicGameplayJsonString
30
40
  });
31
41
  module.exports = __toCommonJS(basic_gameplay_exports);
32
42
  var RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION = "riddle-proof.basic-gameplay.v1";
33
43
  var RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION = "riddle-proof.basic-gameplay.assessment.v1";
34
44
  var RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION = "riddle-proof.basic-gameplay.catch.v1";
45
+ var BASIC_GAMEPLAY_ACTION_TYPES = [
46
+ "wait",
47
+ "key",
48
+ "key-down",
49
+ "key-up",
50
+ "hold-key",
51
+ "repeat",
52
+ "click",
53
+ "click-by-text",
54
+ "wait-for-text",
55
+ "window-call",
56
+ "evaluate"
57
+ ];
58
+ var BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES = [
59
+ "selector_count_increases",
60
+ "selector_count_at_least",
61
+ "selector_absent",
62
+ "selector_text_matches",
63
+ "number_increases",
64
+ "number_decreases",
65
+ "number_unchanged",
66
+ "number_stays_equal",
67
+ "number_equals",
68
+ "canvas_hash_changes",
69
+ "screenshot_hash_changes",
70
+ "visual_hash_changes",
71
+ "state_changes"
72
+ ];
35
73
  var BASIC_GAMEPLAY_CONTAINER_KEYS = [
36
74
  "basic_gameplay",
37
75
  "basicGameplay",
@@ -40,6 +78,19 @@ var BASIC_GAMEPLAY_CONTAINER_KEYS = [
40
78
  "gameplay_proof",
41
79
  "gameplayProof"
42
80
  ];
81
+ var ARTIFACT_VISUAL_CHANGE_CHECKS = /* @__PURE__ */ new Set([
82
+ "canvas_hash_changes",
83
+ "screenshot_hash_changes",
84
+ "state_changes",
85
+ "visual_hash_changes"
86
+ ]);
87
+ var PHASE_SCREENSHOT_SUFFIXES = {
88
+ initial: "before",
89
+ after_action: "after",
90
+ after_continue: "after-continue",
91
+ after_restart: "after-restart",
92
+ revisit: "revisit"
93
+ };
43
94
  function assessBasicGameplayEvidence(evidence, options = {}) {
44
95
  const run = extractBasicGameplayEvidence(evidence);
45
96
  if (!run) {
@@ -55,12 +106,16 @@ function assessBasicGameplayEvidence(evidence, options = {}) {
55
106
  route_results: []
56
107
  };
57
108
  }
58
- const routeResults = (run.results || []).map((route) => assessBasicGameplayRoute(route, options));
109
+ const routeResults = (run.results || []).map((route) => augmentRouteAssessmentWithProgressionChecks(
110
+ assessBasicGameplayRoute(route, options),
111
+ route
112
+ ));
59
113
  const failingRoutes = routeResults.filter((result) => !result.ok).map((result) => ({
60
114
  name: result.name,
61
115
  path: result.path,
62
116
  failures: result.failures,
63
- warnings: result.warnings
117
+ warnings: result.warnings,
118
+ suite_failures: result.suite_failures
64
119
  }));
65
120
  return {
66
121
  version: RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
@@ -141,6 +196,255 @@ function assessBasicGameplayRoute(route, options = {}) {
141
196
  }
142
197
  };
143
198
  }
199
+ function sanitizeBasicGameplayJsonString(value) {
200
+ const text = String(value ?? "");
201
+ let output = "";
202
+ for (let index = 0; index < text.length; index += 1) {
203
+ const code = text.charCodeAt(index);
204
+ if (code >= 55296 && code <= 56319) {
205
+ const next = text.charCodeAt(index + 1);
206
+ if (next >= 56320 && next <= 57343) {
207
+ output += text[index] + text[index + 1];
208
+ index += 1;
209
+ }
210
+ continue;
211
+ }
212
+ if (code >= 56320 && code <= 57343) continue;
213
+ output += text[index];
214
+ }
215
+ return output;
216
+ }
217
+ function compactBasicGameplayText(value, max = 160) {
218
+ const compacted = sanitizeBasicGameplayJsonString(value).replace(/\s+/g, " ").trim();
219
+ return Array.from(compacted).slice(0, max).join("");
220
+ }
221
+ function assessBasicGameplayProgressionCheck(check) {
222
+ const before = metricValue(check.before);
223
+ const after = metricValue(check.after);
224
+ const type = String(check.type || "");
225
+ const hasExplicitResult = typeof check.ok === "boolean";
226
+ let ok = hasExplicitResult ? check.ok === true : true;
227
+ let reason = check.reason ?? null;
228
+ if (type === "selector_count_increases") {
229
+ ok = numberValue(after?.count) > numberValue(before?.count);
230
+ reason = ok ? null : "selector_count_did_not_increase";
231
+ } else if (type === "selector_count_at_least") {
232
+ if (numericValue(check.min) === null && hasExplicitResult) return resolveBasicGameplayProgressionCheckWithArtifactScreenshots({ ...check, ok, reason });
233
+ ok = numberValue(after?.count) >= numberValue(check.min);
234
+ reason = ok ? null : "selector_count_below_min";
235
+ } else if (type === "selector_absent") {
236
+ ok = !after?.present || numberValue(after?.count) === 0;
237
+ reason = ok ? null : "selector_still_present";
238
+ } else if (type === "selector_text_matches") {
239
+ if (!check.pattern && hasExplicitResult) return resolveBasicGameplayProgressionCheckWithArtifactScreenshots({ ...check, ok, reason });
240
+ ok = textMatches(after?.text, check.pattern, check.flags);
241
+ reason = ok ? null : "selector_text_did_not_match";
242
+ } else if (type === "number_increases") {
243
+ ok = numericValue(before?.number) !== null && numericValue(after?.number) !== null && numberValue(after?.number) > numberValue(before?.number);
244
+ reason = ok ? null : "number_did_not_increase";
245
+ } else if (type === "number_decreases") {
246
+ ok = numericValue(before?.number) !== null && numericValue(after?.number) !== null && numberValue(after?.number) < numberValue(before?.number);
247
+ reason = ok ? null : "number_did_not_decrease";
248
+ } else if (type === "number_unchanged" || type === "number_stays_equal") {
249
+ ok = numericValue(before?.number) !== null && numericValue(after?.number) !== null && numberValue(after?.number) === numberValue(before?.number);
250
+ reason = ok ? null : "number_changed";
251
+ } else if (type === "number_equals") {
252
+ const expected = numericValue(check.expected ?? check.value);
253
+ if (expected === null && hasExplicitResult) return resolveBasicGameplayProgressionCheckWithArtifactScreenshots({ ...check, ok, reason });
254
+ ok = numericValue(after?.number) !== null && expected !== null && numberValue(after?.number) === expected;
255
+ reason = ok ? null : "number_did_not_equal_expected";
256
+ } else if (type === "canvas_hash_changes") {
257
+ ok = hashChanged(before, after, ["first_canvas_hash", "first_canvas_visual_sample_hash", "visual_sample_hash"]);
258
+ reason = ok ? null : "canvas_or_visual_sample_hash_did_not_change";
259
+ } else if (type === "screenshot_hash_changes") {
260
+ ok = hashChanged(before, after, ["screenshot_hash", "visual_sample_hash", "artifact_screenshot_hash"]);
261
+ reason = ok ? null : "screenshot_hash_did_not_change";
262
+ } else if (type === "visual_hash_changes") {
263
+ ok = hashChanged(before, after, ["visual_sample_hash", "screenshot_hash", "artifact_screenshot_hash"]);
264
+ reason = ok ? null : "visual_hash_did_not_change";
265
+ } else if (type === "state_changes") {
266
+ ok = hashChanged(before, after, [
267
+ "dom_signature_hash",
268
+ "body_text_hash",
269
+ "screenshot_hash",
270
+ "visual_sample_hash",
271
+ "artifact_screenshot_hash",
272
+ "first_canvas_hash",
273
+ "first_canvas_visual_sample_hash"
274
+ ]);
275
+ reason = ok ? null : "state_hash_did_not_change";
276
+ }
277
+ const assessed = {
278
+ ...check,
279
+ ok,
280
+ reason
281
+ };
282
+ return resolveBasicGameplayProgressionCheckWithArtifactScreenshots(assessed);
283
+ }
284
+ function assessBasicGameplayProgressionChecks(route) {
285
+ return progressionChecksForRoute(route).map((check) => assessBasicGameplayProgressionCheck(check));
286
+ }
287
+ function augmentBasicGameplayAssessmentWithProgressionChecks(assessment, evidence) {
288
+ const run = extractBasicGameplayEvidence(evidence);
289
+ if (!run?.results?.length) return assessment;
290
+ const routeResults = assessment.route_results.map(
291
+ (result, index) => augmentRouteAssessmentWithProgressionChecks(result, run.results?.[index] || {})
292
+ );
293
+ const failingRoutes = routeResults.filter((result) => !result.ok).map((result) => ({
294
+ name: result.name,
295
+ path: result.path,
296
+ failures: result.failures,
297
+ warnings: result.warnings,
298
+ suite_failures: result.suite_failures
299
+ }));
300
+ return {
301
+ ...assessment,
302
+ passed: failingRoutes.length === 0,
303
+ passing_routes: routeResults.filter((result) => result.ok).length,
304
+ failing_routes: failingRoutes,
305
+ failure_counts: countCodes(routeResults.flatMap((result) => result.failures)),
306
+ warning_counts: countCodes(routeResults.flatMap((result) => result.warnings)),
307
+ route_results: routeResults
308
+ };
309
+ }
310
+ function resolveBasicGameplayProgressionCheckWithArtifactScreenshots(check) {
311
+ if (check.ok !== false) return check;
312
+ if (!ARTIFACT_VISUAL_CHANGE_CHECKS.has(String(check.type || ""))) return check;
313
+ const beforeHash = stringValue(metricValue(check.before)?.artifact_screenshot_hash);
314
+ const afterHash = stringValue(metricValue(check.after)?.artifact_screenshot_hash);
315
+ if (!beforeHash || !afterHash || beforeHash === afterHash) return check;
316
+ return {
317
+ ...check,
318
+ ok: true,
319
+ reason: null,
320
+ artifact_resolution: {
321
+ source: "riddle_screenshot_artifacts",
322
+ before_hash: beforeHash,
323
+ after_hash: afterHash
324
+ }
325
+ };
326
+ }
327
+ function attachBasicGameplayArtifactScreenshotHashes(evidence, routesOrOptions = {}, maybeArtifacts = []) {
328
+ const routes = Array.isArray(routesOrOptions) ? routesOrOptions : routesOrOptions.routes || [];
329
+ const artifacts = Array.isArray(routesOrOptions) ? maybeArtifacts : routesOrOptions.artifacts || [];
330
+ if (!evidence?.results?.length || !artifacts.length) return evidence;
331
+ const artifactsByName = screenshotArtifactIndex(artifacts);
332
+ if (!artifactsByName.size) return evidence;
333
+ for (const [index, routeEvidence] of evidence.results.entries()) {
334
+ const routeContract = routes[index] || {};
335
+ for (const phase of Object.keys(PHASE_SCREENSHOT_SUFFIXES)) {
336
+ attachPhaseArtifactHash(routeEvidence, routeContract, phase, artifactsByName);
337
+ }
338
+ const checks = progressionChecksForRoute(routeEvidence);
339
+ const resolvedChecks = checks.map((check) => {
340
+ const fromPhase = check.from_phase || "initial";
341
+ const toPhase = check.to_phase || "after_action";
342
+ if (check.before) attachPhaseArtifactHash({ ...routeEvidence, [fromPhase]: check.before }, routeContract, fromPhase, artifactsByName);
343
+ if (check.after) attachPhaseArtifactHash({ ...routeEvidence, [toPhase]: check.after }, routeContract, toPhase, artifactsByName);
344
+ return resolveBasicGameplayProgressionCheckWithArtifactScreenshots(check);
345
+ });
346
+ if (routeEvidence.progression_checks) routeEvidence.progression_checks = resolvedChecks;
347
+ else if (routeEvidence.progressionChecks) routeEvidence.progressionChecks = resolvedChecks;
348
+ routeEvidence.progression_failure_count = resolvedChecks.filter((check) => check.ok === false).length;
349
+ }
350
+ return evidence;
351
+ }
352
+ function createBasicGameplayCatchRecords(assessment, evidence) {
353
+ const run = extractBasicGameplayEvidence(evidence);
354
+ if (!run?.results?.length) return [];
355
+ const catches = [];
356
+ for (const route of run.results) {
357
+ if (numberValue(route.page_error_count) > 0) {
358
+ catches.push({
359
+ site: run.site || null,
360
+ route: route.name,
361
+ path: route.path,
362
+ code: "fatal_page_error",
363
+ label: "page runtime error",
364
+ type: "page_error",
365
+ selector: null,
366
+ reason: stringValue(route.first_page_error) || "page_error",
367
+ page_errors: listValue(route.page_errors),
368
+ summary: `${route.name || route.path || "Route"}: page runtime error (${stringValue(route.first_page_error) || "page_error"})`
369
+ });
370
+ }
371
+ if (numberValue(route.console_error_count) > 0) {
372
+ catches.push({
373
+ site: run.site || null,
374
+ route: route.name,
375
+ path: route.path,
376
+ code: "critical_console_error",
377
+ label: "console error",
378
+ type: "console_error",
379
+ selector: null,
380
+ reason: stringValue(route.first_console_error) || "console_error",
381
+ console_errors: listValue(route.console_errors),
382
+ summary: `${route.name || route.path || "Route"}: console error (${stringValue(route.first_console_error) || "console_error"})`
383
+ });
384
+ }
385
+ for (const [group, phase] of [
386
+ ["action_results", "after_action"],
387
+ ["continued_action_results", "after_continue"],
388
+ ["restart_action_results", "after_restart"]
389
+ ]) {
390
+ for (const actionResult of listValue(route[group])) {
391
+ if (!actionResult || actionResult.ok !== false) continue;
392
+ const action = stringValue(actionResult.action) || group;
393
+ const reason = stringValue(actionResult.reason) || "action_failed";
394
+ catches.push({
395
+ site: run.site || null,
396
+ route: route.name,
397
+ path: route.path,
398
+ code: "action_failed",
399
+ label: action,
400
+ type: action,
401
+ selector: stringValue(actionResult.selector),
402
+ reason,
403
+ phase,
404
+ action_result: actionResult,
405
+ summary: `${route.name || route.path || "Route"}: ${action} failed (${reason})`
406
+ });
407
+ }
408
+ }
409
+ for (const check of assessBasicGameplayProgressionChecks(route).filter((item) => item.ok === false)) {
410
+ catches.push({
411
+ site: run.site || null,
412
+ route: route.name,
413
+ path: route.path,
414
+ code: "progression_assertion_failed",
415
+ label: check.label,
416
+ type: String(check.type || ""),
417
+ selector: check.selector || null,
418
+ state_path: check.state_path || null,
419
+ state_call: check.state_call || null,
420
+ property_path: check.property_path || null,
421
+ reason: check.reason || "progression_assertion_failed",
422
+ from_phase: check.from_phase,
423
+ to_phase: check.to_phase,
424
+ before: compactCatchMetric(check.before),
425
+ after: compactCatchMetric(check.after),
426
+ summary: `${route.name || route.path || "Route"}: ${check.label || check.type || "progression check"} failed (${check.reason || "progression_assertion_failed"})`
427
+ });
428
+ }
429
+ }
430
+ if (!catches.length && assessment.failing_routes.length) {
431
+ for (const route of assessment.failing_routes) {
432
+ for (const code of route.failures || []) {
433
+ catches.push({
434
+ site: run.site || null,
435
+ route: route.name,
436
+ path: route.path,
437
+ code,
438
+ label: code,
439
+ type: code,
440
+ reason: code,
441
+ summary: `${route.name || route.path || "Route"}: ${code}`
442
+ });
443
+ }
444
+ }
445
+ }
446
+ return catches;
447
+ }
144
448
  function createBasicGameplayCatchSummary(input, options = {}) {
145
449
  const before = summarizeAssessment(assessBasicGameplayEvidence(input.before, options));
146
450
  const after = input.after === void 0 ? void 0 : summarizeAssessment(assessBasicGameplayEvidence(input.after, options));
@@ -256,6 +560,122 @@ function changed(before, after) {
256
560
  function canvasHashes(canvases) {
257
561
  return (canvases || []).map((canvas) => canvas.hash).filter(Boolean).join("|");
258
562
  }
563
+ function augmentRouteAssessmentWithProgressionChecks(result, route) {
564
+ const progressionFailures = assessBasicGameplayProgressionChecks(route).filter((check) => check.ok === false);
565
+ if (!progressionFailures.length) return result;
566
+ const failures = result.failures.includes("progression_assertion_failed") ? result.failures : [...result.failures, "progression_assertion_failed"];
567
+ return {
568
+ ...result,
569
+ ok: false,
570
+ failures,
571
+ suite_failures: [
572
+ ...result.suite_failures || [],
573
+ ...progressionFailures.map((check) => ({
574
+ code: "progression_assertion_failed",
575
+ label: check.label,
576
+ reason: check.reason,
577
+ selector: check.selector || null,
578
+ state_path: check.state_path || null,
579
+ state_call: check.state_call || null,
580
+ property_path: check.property_path || null
581
+ }))
582
+ ]
583
+ };
584
+ }
585
+ function progressionChecksForRoute(route) {
586
+ return listValue(route.progression_checks || route.progressionChecks).filter((item) => Boolean(recordValue(item)));
587
+ }
588
+ function metricValue(value) {
589
+ return recordValue(value);
590
+ }
591
+ function textMatches(text, pattern, flags) {
592
+ if (!pattern) return Boolean(text);
593
+ try {
594
+ return new RegExp(String(pattern), typeof flags === "string" ? flags : "i").test(String(text ?? ""));
595
+ } catch {
596
+ return false;
597
+ }
598
+ }
599
+ function hashChanged(before, after, keys) {
600
+ if (!before || !after) return false;
601
+ return keys.some((key) => {
602
+ const beforeValue = stringValue(before[key]);
603
+ const afterValue = stringValue(after[key]);
604
+ return Boolean(beforeValue && afterValue && beforeValue !== afterValue);
605
+ });
606
+ }
607
+ function screenshotArtifactIndex(artifacts) {
608
+ const index = /* @__PURE__ */ new Map();
609
+ for (const artifact of artifacts || []) {
610
+ const hash = stringValue(artifact.sha256 || artifact.hash);
611
+ if (!hash) continue;
612
+ if (String(artifact.kind || "").toLowerCase() !== "screenshot" && !/\.png($|\?)/i.test(`${artifact.name || ""} ${artifact.path || ""} ${artifact.url || ""}`)) continue;
613
+ const filename = artifactBasename(artifact.path || artifact.name || artifact.url);
614
+ if (!filename) continue;
615
+ index.set(filename.toLowerCase(), {
616
+ ...artifact,
617
+ sha256: hash
618
+ });
619
+ }
620
+ return index;
621
+ }
622
+ function attachPhaseArtifactHash(routeEvidence, routeContract, phase, artifactsByName) {
623
+ const suffix = PHASE_SCREENSHOT_SUFFIXES[phase];
624
+ if (!suffix) return null;
625
+ const metric = metricValue(routeEvidence[phase]);
626
+ if (!metric) return null;
627
+ const filename = `${routeArtifactSlug(routeEvidence, routeContract)}-${suffix}.png`.toLowerCase();
628
+ const artifact = artifactsByName.get(filename);
629
+ if (!artifact?.sha256) return null;
630
+ metric.artifact_screenshot_hash = artifact.sha256;
631
+ metric.artifact_screenshot = {
632
+ name: artifact.name || artifactBasename(artifact.path || filename),
633
+ path: artifact.path,
634
+ url: artifact.url,
635
+ kind: artifact.kind,
636
+ sha256: artifact.sha256
637
+ };
638
+ return artifact.sha256;
639
+ }
640
+ function routeArtifactSlug(routeEvidence, routeContract) {
641
+ return slug(routeContract.name || routeEvidence.name || routeContract.path || routeEvidence.path || "route");
642
+ }
643
+ function artifactBasename(value) {
644
+ const raw = stringValue(value);
645
+ if (!raw) return "";
646
+ try {
647
+ const url = new URL(raw);
648
+ return url.pathname.split("/").filter(Boolean).pop() || "";
649
+ } catch {
650
+ return raw.split(/[\\/]/).filter(Boolean).pop() || raw;
651
+ }
652
+ }
653
+ function compactCatchMetric(metric) {
654
+ const value = metricValue(metric);
655
+ if (!value) return null;
656
+ return {
657
+ phase: value.phase,
658
+ text: value.text,
659
+ number: value.number,
660
+ count: value.count,
661
+ present: value.present,
662
+ state_path: value.state_path,
663
+ state_call: value.state_call,
664
+ property_path: value.property_path,
665
+ screenshot_hash: value.screenshot_hash,
666
+ visual_sample_hash: value.visual_sample_hash,
667
+ viewport_screenshot_hash: value.viewport_screenshot_hash,
668
+ artifact_screenshot_hash: value.artifact_screenshot_hash,
669
+ dom_signature_hash: value.dom_signature_hash,
670
+ body_text_hash: value.body_text_hash,
671
+ first_canvas_hash: value.first_canvas_hash,
672
+ first_canvas_visual_sample_hash: value.first_canvas_visual_sample_hash,
673
+ visible_canvas_count: value.visible_canvas_count
674
+ };
675
+ }
676
+ function slug(value) {
677
+ return String(value || "artifact").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96) || "artifact";
678
+ }
259
679
  function countCodes(codes) {
260
680
  const counts = {};
261
681
  for (const code of codes) counts[code] = (counts[code] || 0) + 1;
@@ -279,6 +699,9 @@ function numericValue(value) {
279
699
  }
280
700
  return null;
281
701
  }
702
+ function stringValue(value) {
703
+ return typeof value === "string" && value.length ? value : null;
704
+ }
282
705
  function recordValue(value) {
283
706
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
284
707
  }
@@ -296,11 +719,21 @@ function parseJson(value) {
296
719
  }
297
720
  // Annotate the CommonJS export names for ESM import in node:
298
721
  0 && (module.exports = {
722
+ BASIC_GAMEPLAY_ACTION_TYPES,
723
+ BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES,
299
724
  RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
300
725
  RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION,
301
726
  RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION,
302
727
  assessBasicGameplayEvidence,
728
+ assessBasicGameplayProgressionCheck,
729
+ assessBasicGameplayProgressionChecks,
303
730
  assessBasicGameplayRoute,
731
+ attachBasicGameplayArtifactScreenshotHashes,
732
+ augmentBasicGameplayAssessmentWithProgressionChecks,
733
+ compactBasicGameplayText,
734
+ createBasicGameplayCatchRecords,
304
735
  createBasicGameplayCatchSummary,
305
- extractBasicGameplayEvidence
736
+ extractBasicGameplayEvidence,
737
+ resolveBasicGameplayProgressionCheckWithArtifactScreenshots,
738
+ sanitizeBasicGameplayJsonString
306
739
  });