9.8 KiB
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)
-
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. -
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. -
File-header
sequencevalue. Reference:353. maple: an incrementing counter (reads the destination export's sequence + 1, default 1; seewriter._next_sequence). rekordbox's value comes from its internalmaster.dbtransaction counter and is not reproducible from content. We match the semantics (monotonic increment), not the absolute value. -
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. -
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. -
Dead-row offset arrays +
num_row_offsets>num_rows. Reference row groups list offsets for dead slots too: e.g. tracks page 2 hasnro=9, nrows=5with offsets[0,428,856,1324,1792,2224,2656,3112,3568](slots 0,5,7,8 dead); artists page 6 hasnro=9, nrows=1. maple emits exactlynro == nrowswith no dead slots. Reproducing dead offsets would mean reproducing the edit history. -
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.
-
free_size/used_sizetotals on data pages. Referenceused+freetotals vary (4034, 4050, …) becauseused_sizecovers the dead-row region and the strange-block markers differ. maple's pages total a clean4020(= heap capacity for a single 36-byte row group). Direct consequence of (6) — dead bytes are counted as "used". -
Track row opaque id blob —
0x14/0x18/0x1a(8 bytes/track). One 8-byte LE valueopaque_track_id_lo | (opaque_seed << 32), both halves stored in rekordbox's internal master.db (a per-installopaque_seed+ a per-trackopaque_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 constTRACK_OPAQUE_SEED, overridable per install) and a stable per-trackopaque_track_id_lomapped into the real Content-ID band (OPAQUE_LO_MIN..MAX= 0x08000000..0x0F900000, where every observed AceMo lo lands) viacrc32(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. -
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.) -
Table-directory
empty_candidate(u32, table_dir entry +4) + headernext_unused_page. Reference: a nonzero per-tableempty_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 editeddata/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_TABLESis now empty — the old genres data page was an edit-history artifact of the edited ref). page_flags: data pages are0x34for tracks and history only,0x24for every other table (the wider{0,2,3,13,17,19}was edited-ref noise); empty data pages0x44; strange pages0x64.- 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 headerfree=used=0,txc=txi=0x1fff,next_page = page_index+1, andunknown7 = 1for 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
redecoded (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=0everywhere (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 indata/acemo_fresh; the 2 misses are edited pages — divergence). NOTE:re's "keep constant 16" advice came from readingdata/usb, which is maple's OWN output, not a real export — a real fresh export uses the presence value, not 16.
- file-header offset 16 (
See re/FORMAT_SPEC.md for the value-construction rules and re/README.md for
the open RE questions.