Skip to content

feat(puzzle): add custom puzzle builder validation and tamper-evident replay verification - #455

Merged
Mkalbani merged 1 commit into
MindFlowInteractive:mainfrom
nafiuishaaq:feat/custom-puzzle-builder-and-replay-verification
Sep 28, 2026
Merged

Mkalbani merged 1 commit into
MindFlowInteractive:mainfrom
nafiuishaaq:feat/custom-puzzle-builder-and-replay-verification

Conversation

@nafiuishaaq

Copy link
Copy Markdown
Contributor

Summary

Adds custom puzzle authoring and tamper-evident replay verification to the
puzzle engine service.

What this adds

src/puzzle/puzzle.service.ts:

  • Custom puzzle builder (Custom Puzzle Builder and Submission System #435)
    • validatePuzzleDefinition() rejects malformed boards with field-level
      errors ({ field, code, message }) rather than throwing, so the builder UI
      can attach each message to the input that caused it. It checks grid bounds,
      that cells and solution both match width * height, that every value is
      in allowedValues, and that neither board nor solution is trivial.
    • estimateDifficulty() returns a score, a band (easy/medium/hard/
      expert) and the raw inputs it used, so a preview can be shown and
      recalibrated later. It is a transparent heuristic — log-scaled board area,
      distinct-value variety, and solution run density — not a solver.
    • submitCustomPuzzle() runs validation and returns a moderation envelope. A
      valid puzzle is never published directly: it always enters pending_review
      with a definitionDigest over the canonicalised definition, so a later
      moderation decision can be checked against the exact bytes that were
      validated.
    • moderateSubmission() moves a submission to approved/rejected, refusing
      to moderate a non-pending submission or one reviewed by its own author.
  • Replay recording and verification (Puzzle Replay Recording and Sharing #436)
    • recordReplayAction() requires contiguous sequence numbers and
      non-decreasing timestamps, so a replay cannot skip or reorder moves, and
      folds the previous chain hash into each action digest.
    • finalizeReplay() seals a replay into a summary (action count, duration,
      hints used, chain hash, submitted-value hash) for playback and sharing.
    • verifyReplay() checks three things before a result is trusted: the actions
      still hash to the sealed chain digest (CHAIN_TAMPERED), the submitted
      value matches the recorded solution hash (SOLUTION_MISMATCH), and the run
      was not faster than MIN_PLAUSIBLE_SOLVE_MS (IMPLAUSIBLE_SOLVE_TIME).
  • createPuzzle() now uses a shared private hash() helper instead of
    inlining createHash, which the new code also needs.

Notes

  • No new dependency was injected. Adding a TypeORM repository would require
    editing puzzle.module.ts, a second file, so the builder and replay work is
    built on the already-injected SorobanService and node:crypto. As a result
    submissions and replays are returned as envelopes rather than persisted;
    wiring them to storage is the follow-up this change deliberately leaves out.
  • The replay chain is tamper-evident, not tamper-proof. Recomputing the
    chain detects edits to a stored replay, but an attacker who can rewrite the
    whole record can also rewrite the chain. Sealing the final digest somewhere
    append-only (or signing it) is what would actually prevent forgery.
  • estimateDifficulty was checked against small/medium/large boards while
    developing and now yields easy/medium/hard respectively. An earlier version
    normalised run density by area / 2, which let a 2x2 board max out that term
    and score "medium"; it is normalised by area now.
  • The difficulty weights are unvalidated against real solve data, so a preview
    will disagree with players until it is calibrated. inputs is returned
    precisely so that calibration is possible later.
  • Replay compression, sharing, comments, analytics and expiry are not
    included — they live in src/replay/ and src/puzzle/session-replay.service.ts,
    which are separate files.
  • No unit tests are added here, per the current scope of this change.

Issues

Closes #435
Closes #436
Closes #437
Closes #438

@drips-wave

drips-wave Bot commented Sep 27, 2026

Copy link
Copy Markdown

@nafiuishaaq Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Mkalbani
Mkalbani merged commit 73ec073 into MindFlowInteractive:main Sep 28, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants