Skip to content

Latest commit

 

History

History
86 lines (68 loc) · 4.2 KB

File metadata and controls

86 lines (68 loc) · 4.2 KB

Pixie Engine sprite replay format (replay.json)

What we've reverse-engineered about the per-sprite drawing replay — the recording of how a sprite was drawn, used by the archive's replay player (/load/, see static-site/generate.mjs + postmaster.js, which call loadReplayFromURL) and as a moderation signal (static-site/moderation/scan-replay.mjs). Documented 2026-06-02 from live data.

Location & availability

  • URL: https://<id%4>.pixiecdn.com/sprites/<id>/replay.json (the same 0..3.pixiecdn.com shard scheme as the images). CORS: GET only (a cross-origin HEAD gets no Access-Control-Allow-Origin).
  • Only exists for ids ≳ 110,000 (the post-2017 editor that records replays). Older sprites return 404 (no replay was ever captured). Per the archive notes, re-uploads load but don't animate — so a 404 on a post-2017 id can indicate a re-upload rather than a drawn sprite.

Two formats

v0 — stroke array (older, ~110k–~140k)

A plain JSON array of strokes; each stroke is an array of pixel edits:

[
  [ {"x":0,"y":5,"color":"rgba(0, 0, 0, 1)"}, {"x":1,"y":5,"color":"rgba(0, 0, 0, 1)"}, ],
  [ … next stroke … ]
]
  • color is a CSS rgba(r, g, b, a) string.
  • A stroke ≈ one mouse-down→up. ops = array.length (number of strokes); total pixels = sum of stroke lengths.

v1 — operation log (newer, ~144k+)

A JSON object:

{
  "version": "1",
  "palette": ["#000000", … 32 colors …],
  "history": [ { "name": "PutImageData", "x": 17, "y": 29, "imageData": {"data":"II4…","width":…,"height":…} }, ],
  "initialState": { "data": "II18…", }
}
  • version: "1".
  • palette: the editor's color palette (observed length 32).
  • history: ordered array of edit operations, each { name, … }. Observed name values:
    • PutImageData {x, y, imageData:{data,width,height}} — paints a region (a brush stamp, a fill, or a paste).
    • Resize {size:{width,height}, sizePrevious:{…}, imageData:{…}} — changes canvas size (an upload typically resizes the canvas to the image's dimensions).
    • (Other op names may exist; treat unknown names as ordinary edits.)
  • initialState: the starting canvas.
  • ops = history.length.

Encoding caveat

imageData.data and initialState.data are a proprietary string codec (the "II18…" / "II4…" blobs), not standard base64 — decoding them needs the editor's codec. For pixel-level analysis, decode the sprite's original.png instead; replay.json is best used for op structure, not pixels.

Moderation signals (see scan-replay.mjs)

Signal Detection Meaning
empty v0 ops = 0 (0 px) Blank stroke array = a genuinely blank canvas — likely trash (sev 2)
no-edits v1 history = 0 NOT necessarily blankinitialState may hold an uploaded image that was never edited (some are ~22 KB PNGs). Treat as an upload SIGNAL, review on merit (sev 1)
single-stroke v0 ops = 1 (px ≈ whole canvas → a paste) One action
single-op v1 history.length = 1 One action
single-resize-upload v1 single op is Resize Canvas resized once = an uploaded image
large-canvas v1 Resize size max-dim > 256 Big canvas (e.g. 1280×720) — upload (real pixel art rarely this large)
no-replay 404 on id ≳ 110k Possibly a re-upload

It also recovers dimensions for post-2017 sprites (from Resize.size) — useful because the dataset only carries w/h for the 2017-DB rows.

An upload/paste is a SIGNAL, not a verdict. It does not mean the content is poor — people paste perfectly good art, and off-topic raster dumps are common but not always removable. Use these flags to prioritize human review (the review tool: remove / adult / false-positive), never as an automatic takedown. Only empty is a strong stand-alone "trash" signal.

What we still don't know

  • The full set of v1 history op names (only PutImageData + Resize observed).
  • The imageData/initialState string codec (would let us reconstruct frames without the editor).
  • Exact id where v0→v1 switched (somewhere between ~140k and ~144k in samples).