Files
maple/maple-py/re/KNOWN_DIVERGENCE.md
T

9.8 KiB
Raw Blame History

KNOWN_DIVERGENCE.md — fields maple's writer intentionally does NOT match

This is the canonical list of bytes that differ between maple's captures/maple_acemo.pdb and the AceMo reference data/acemo/PIONEER/rekordbox/export.pdb on purpose, because they are history-dependent artifacts of the particular reference file rather than rules of the .pdb format.

TL;DR

The AceMo reference export.pdb is not a fresh from-scratch export — it is an incrementally-edited rekordbox database. rekordbox's DeviceSQL page manager edits rows in place: when a row changes it appends a new copy, clears the old slot's presence bit, and advances used_size past the dead bytes without compacting. The reference therefore carries the residue of its entire edit history.

maple writes a fresh, fully-compacted database (tightly 4-byte-aligned rows, no dead rows, no leftover slots). That output is byte-correct for a from-scratch rekordbox export, but it cannot — and should not — reproduce another file's edit history. The fields below are the consequences of that, and the team has decided not to chase them. They only become matchable against a fresh from-scratch reference export (export to an empty USB, never edited afterward).

Everything NOT listed here is fair game and must match. The task-#7 diff should filter out (and reference) this list, then drive the remaining bytes to zero.

Evidence (tracks table, page 2 of the reference)

5 present rows, each with ~377–417 bytes of real content, yet:

row id ofs real content len next ofs gap gap bytes are…
1 428 377 856 51 zero
5 856 377 1324 91 partial stale string
2 1324 417 1792 51 zero
3 1792 386 2656 478 a whole dead track row
4 2656 404 3996* 936 two whole dead track rows

used_size=3996, free_size=38. The gap after id=3 holds a dead copy of a row ending "...Jelly Fish Jam.mp3"; the gap after id=4 holds dead copies of "Myrtle Ave Party Track" and "Smooth Move" rows. These are previous versions of edited rows, left uncompacted. (Verified by ASCII-dumping the gap bytes.)

The fields we do NOT match (and why)

  1. File / page count and next_unused_page. Reference: 49 pages, next_unused=53. maple: 41 pages, next_unused=41. The reference is larger purely because dead-row bloat pushed live rows onto extra pages. A compacted export needs fewer pages.

  2. Overflow / data-page placement (tracks last_page). Reference tracks table spans pages 1 → 48 (live track id=7 spilled onto overflow page 48 because dead rows "filled" page 2). maple spans 1 → 2 (all 6 tracks fit one compacted page). This is the only table-directory entry that differs — the other 19 tables match first/last page exactly. The divergence is entirely a function of the dead-row bloat above.

  3. File-header sequence value. Reference: 353. maple: an incrementing counter (reads the destination export's sequence + 1, default 1; see writer._next_sequence). rekordbox's value comes from its internal master.db transaction counter and is not reproducible from content. We match the semantics (monotonic increment), not the absolute value.

  4. Per-page sequence (page-header offset 16). Reference pages carry assorted values (e.g. 26, 45, 320, 342, 347) — each a snapshot of the global counter at the moment that page was last rewritten. maple writes a clean per-build value. Same root cause as (3); not reproducible.

  5. transaction_row_count / transaction_row_index (page-header u2 pair). Reference: nonzero per edited page (e.g. 1/5, 1/8, 2/0). maple: 0/0. These track the last in-place transaction on the page; meaningless for a freshly written, never-edited page.

  6. Dead-row offset arrays + num_row_offsets > num_rows. Reference row groups list offsets for dead slots too: e.g. tracks page 2 has nro=9, nrows=5 with offsets [0,428,856,1324,1792,2224,2656,3112,3568] (slots 0,5,7,8 dead); artists page 6 has nro=9, nrows=1. maple emits exactly nro == nrows with no dead slots. Reproducing dead offsets would mean reproducing the edit history.

  7. Live row in a non-zero slot. Reference artists page 6 keeps its single live row in slot 8 (slots 0–7 are dead). maple places the single live row in slot 0. A from-scratch export also writes slot 0.

  8. free_size / used_size totals on data pages. Reference used+free totals vary (4034, 4050, …) because used_size covers the dead-row region and the strange-block markers differ. maple's pages total a clean 4020 (= heap capacity for a single 36-byte row group). Direct consequence of (6) — dead bytes are counted as "used".

  9. Track row opaque id blob — 0x14/0x18/0x1a (8 bytes/track). One 8-byte LE value opaque_track_id_lo | (opaque_seed << 32), both halves stored in rekordbox's internal master.db (a per-install opaque_seed + a per-track opaque_track_id_lo). Proven non-derivable from track metadata across every real export + an exhaustive negative hash search (FORMAT_SPEC.md s2). Every real rekordbox export has BOTH halves non-zero (this install's seed = 0x21b4a7ab; data/MAPLE = 0xd6d3a567); the "unanalyzed default" lo=0/seed=0x78f74a68 was a false lead — it came from maple's own earlier output (data/usb), not a real export. maple emits a plausible non-zero stand-in of the right shape: opaque_seed = 0x21b4a7ab (module const TRACK_OPAQUE_SEED, overridable per install) and a stable per-track opaque_track_id_lo mapped into the real Content-ID band (OPAQUE_LO_MIN..MAX = 0x08000000..0x0F900000, where every observed AceMo lo lands) via crc32(id|file_path). This will NOT match rekordbox's master.db id byte-for-byte — unreproducible without its DB — but it is non-zero, in-range and deterministic, the safest shape for deck acceptance. Players treat the blob as opaque.

  10. Strange-page free-map extension + per-page seq on tracks/history. On the tracks (type 0) and history (type 19) strange pages only, the free-space map carries an extra accounting word (body offset 16: 01 00, offset 20: a u4 = the data page's used_size for history=320; tracks differs because of its dead-row/overflow state). maple writes the plain free-map. Tied to the same content/overflow state as items (1)/(2); not reproducible for a compacted writer. (The rest of every strange page — header, free-map head, 1004× f8 ff ff 1f, the data-page pointer word, and unknown7 — now matches the fresh reference byte-for-byte.)

  11. Table-directory empty_candidate (u32, table_dir entry +4) + header next_unused_page. Reference: a nonzero per-table empty_candidate (e.g. 0x33, 0x04, 0x2e…); maple: 0. rekordcrate documents this field as "maybe links to a chain of empty pages if the database is ever garbage collected", and its fixtures show non-sequential free-pool page indices (47, 4, 49, 8, 50, 46, 42, 16…). It is free-list/GC allocator state, not derivable from content. next_unused_page (rekordcrate: "points past end of file", unused) is the same class — see also item (1).

Canonical reference note: the diff target is now the FRESH export data/acemo_fresh/PIONEER/rekordbox/export.pdb (7 tracks). It is cleaner than the edited data/acemo (6 tracks) but still incrementally built per-file, so the divergences above still apply — just smaller (e.g. track 7's dead-row span is 464B not 3568B, but it still overflows to page 48).

What IS reproducible (NOW matched against the fresh reference)

  • The 20-table directory layout and page placement: an empty table is strange-page-only; a table gets a data page IFF it has rows (CORE_TABLES is now empty — the old genres data page was an edit-history artifact of the edited ref).
  • page_flags: data pages are 0x34 for tracks and history only, 0x24 for every other table (the wider {0,2,3,13,17,19} was edited-ref noise); empty data pages 0x44; strange pages 0x64.
  • Strange-page body: the full DeviceSQL free-space map — u4(page_index), u4(first_data_page or 0x03ffffff), the 12-byte const tail, 1004× f8 ff ff 1f, 20 zero bytes — plus header free=used=0, txc=txi=0x1fff, next_page = page_index+1, and unknown7 = 1 for tracks/history else 0.
  • Track row index_shift (slot_index<<5) and every other live-row content field except the opaque blob in divergence item (9).
  • Three header fields re decoded (FORMAT_SPEC §4/§5/§6) as edit-history state whose FRESH-export value maple already writes — so they're matched on a fresh export and only diverge against edited refs:
    • file-header offset 16 (_unnamed4): fresh=0 (maple writes 0); edited DBs accumulate a small nonzero with no structural meaning.
    • strange/data-page unknown7 (0x26): fresh=0 everywhere (maple writes 0); nonzero only on edited DBs' strange pages.
    • row-group trailing u2: it is the "rows-touched-in-last-transaction" bitmask. On a fresh export it equals the row-present mask, so maple writes presence (matches 12/14 groups in data/acemo_fresh; the 2 misses are edited pages — divergence). NOTE: re's "keep constant 16" advice came from reading data/usb, which is maple's OWN output, not a real export — a real fresh export uses the presence value, not 16.

See re/FORMAT_SPEC.md for the value-construction rules and re/README.md for the open RE questions.