# export.pdb value-construction rules (RE findings) This file records the exact rules rekordbox uses to populate `export.pdb` fields that maple currently writes wrong or as zero. Each section is a self-contained spec for the `maple` writer to implement. Evidence is from reverse-engineering `rekordbox.exe` (DeviceSQL "Page Manager", `dsql*` / `edb_dyn_*`) in Ghidra plus byte-level analysis of the reference export `maple-py/data/acemo/PIONEER/rekordbox/export.pdb` (6 tracks, 1 album, 1 artist, 1 key, 6 artwork). Reference-pdb facts used throughout: - `len_page` = 4096. - Row index grows backward from end of page; each group spans 36 (0x24) bytes: `group_base = len_page - group_index*36`. Within a group, `present_flags` (u2) live at `group_base-4`; the row offset for slot `i` (0..15) is the u2 at `group_base-(6+2*i)`; offsets are relative to the heap start (0x28, i.e. end of the 0x28-byte page header). --- ## 1. Track row `index_shift` (offset 0x02, u2) — SOLVED **Field:** `TrackRow.index_shift` (also present on artist/album/genre/label/ key rows — every row type whose layout begins `subtype:u2, index_shift:u2`). **Rule:** ``` index_shift = slot_index * 0x20 # i.e. slot_index << 5 ``` where `slot_index` is the row's **0-based position in the page row index** (`group_index*16 + slot_within_group`), NOT its ordinal among present rows and NOT its position in the heap. It resets to 0 at the start of every data page. Key consequences for the writer: - The first row index slot (slot 0) gets `index_shift = 0`. - Deleted/absent slots still consume a slot number, so a gap in `present_flags` produces a gap in `index_shift`. (See evidence: the AceMo page has slot 5 deleted, so the row in slot 6 has `index_shift = 0xc0`, skipping `0xa0`.) - `index_shift` is purely a function of the row's slot, so the value is fully reproducible from the page layout maple already controls — there is no external/runtime input. If maple writes each track to slot `i`, set `index_shift = i*0x20`. **Evidence:** - Reference AceMo `tracks` page (page 2): present slots are 1,2,3,4,6 (slots 0 and 5 are deleted), with `index_shift` = 0x20,0x40,0x60,0x80,0xc0 respectively. Overflow page (page 48) has one row in slot 1 → `index_shift = 0x20`. The 0x80→0xc0 jump across the deleted slot 5 proves the value tracks `slot_index`, not present-count. - Cross-checked against `artists` (page 5) and `albums` (page 7) rows: every row satisfies `index_shift == slot_index*0x20` with **0 mismatches** over all present rows in the reference file (`maple-py/re/scripts`-style raw parse, 8 rows checked, see RE notes). - Ghidra: the value is assigned inside the DeviceSQL Page Manager record-insert path, which is reached only through vtable dispatch (`(**(code**)(*obj + 0x4d0))(obj,...)` etc. in `FUN_141b8b4c0` / `FUN_141cac390`), so there is no single statically-anchored `index_shift = slot<<5` instruction to cite; a constant-scan for `shl ,5` paired with the row-index masks (0x1fff/0x7ff) and stride 0x24 produced only CRT/`printf("%a")` false positives (`FUN_14232a86c`). The empirical rule above is exact and zero-mismatch, so confidence is **high** on the formula; confidence on "no hidden runtime input" is high because the value is fully explained by slot position alone. **Confidence:** high. --- ## 2. Track row 0x14 / 0x18 / 0x1a (opaque id blob) — SOLVED (stored value, not a formula) **Fields:** `TrackRow.opaque_track_id_lo` (u4 @0x14), `TrackRow.opaque_seed_lo` (u2 @0x18), `TrackRow.opaque_seed_hi` (u2 @0x1a). Together these are one 8-byte little-endian value: ``` blob_u64 = opaque_track_id_lo | (opaque_seed << 32) opaque_seed = opaque_seed_lo | (opaque_seed_hi << 16) # u4 at offset 0x18 ``` (@flesniak's schema labels these "Some ID? unknown", "always 19048?", "always 30967?". The "19048"/"30967" = 0x4A68/0x78F7 are only correct for some older exports; they are NOT universal — see below.) **What they are (proven empirically across 5 GENUINE rekordbox exports):** > NOTE (2026-06-09 correction): a prior draft included a 5th row > "usb (176), seq=1, seed 0x78f74a68, lo all 0" and concluded rekordbox writes > lo=0 / seed 0x78f74a68 for unanalyzed tracks. **That file > (`data/usb/.../export.pdb`) is maple's OWN output, not a rekordbox export** > (it is the compare_pdb.py "ours" path). Reading it back was circular; that > claim is WRONG and has been dropped. Provenance rule: maple output = seed > `0x78f74a68` + ALL per-track lo == 0; genuine rekordbox = per-install nonzero > seed + nonzero per-track lo. | export (tracks) | header.seq | opaque_seed | opaque_track_id_lo | |----------------------------|-----------:|---------------|-------------------------------| | acemo (6) | 353 | `0x21b4a7ab` | 6/6 nonzero | | acemo_fresh (7, same lib) | 35 | `0x21b4a7ab` | 7/7 nonzero | | lib_multi (10, same lib) | 56 | `0x21b4a7ab` | 10/10 nonzero | | MAPLE (176, install B) | 546 | `0xd6d3a567` | 176/176 nonzero | | data/PIONEER (847, inst. B)| 4211 | `0xd6d3a567` | 847/847 nonzero | Across **all 1046 real tracks in 5 genuine exports: 100% nonzero per-track lo, exactly one nonzero per-install seed per file. There is NO evidence rekordbox ever writes lo=0 or seed `0x78f74a68`.** The lo values span nearly the full 28-bit space (`0x0003edeb`..`0x0ffe7f4e`). Findings: 1. **`opaque_seed` is constant within a single export** (exactly one value per file) and is **per-rekordbox-library/install**: acemo/acemo_fresh/lib_multi are three exports of the same library and all share `0x21b4a7ab`; the separate install behind MAPLE + data/PIONEER uses `0xd6d3a567`. (The old maple hardcode `0xa567`/`0xd6d3` = `0xd6d3a567` was correct only for install B.) The seed is NOT a binary constant (proven: no `0xa7ab`/`0x21b4`/`0xd6d3`/`0xa567` immediate or data-byte sequence in rekordbox.exe), so it is derived at runtime from the per-library DB (most likely a hash/truncation of the master.db's per-library UUID or creation marker). maple cannot reproduce a specific install's seed (see recommendation). 2. **`opaque_track_id_lo` is a stable per-track value within a library.** Every track that appears in both the acemo and acemo_fresh exports has an **identical** lo (matched by title): "All My Life"→`0x07ff5a56`, "Smooth Move"→`0x0a20de34`, "Jelly Fish Jam"→`0x0f8fd598`, etc. It is NOT per-export-random. 3. **It is NOT derivable from the track's file metadata.** Tested and ruled out (no match, 28-bit and full, utf-8/utf-16, several hash funcs): crc32 / adler32 / md5 / sha1 / fnv1 / fnv1a / djb2 of the title, filename, full `/Contents/...` path, and crc32 of the ANLZ `.DAT`/`.EXT` and the `.mp3` bytes. Also not the USBANLZ folder id, not sequential, not evenly spaced. 4. **The values are runtime-derived, not binary constants.** A full instruction-immediate scan of `rekordbox.exe` found NO occurrence of either real seed (`0x21b4a7ab`, `0xd6d3a567`), the per-track lo values, or the track-row `bitmask` constant `0xc0700`. So these come from rekordbox's internal database (master.db / `djmdContent` + a per-library seed), copied into the row by the DeviceSQL record builder (vtable-dispatched, not statically anchorable). (`0x78f74a68` does not appear either — and per the correction above it was never a real rekordbox value here, just maple's placeholder.) **Round-2 evidence (2026-06-09, after team-lead supplied acemo_fresh and asked to chase an audio-content hash):** 5. **Audio-content-hash hypothesis: DISPROVEN.** Hashed the actual `.mp3` bytes in 8 framings — full file, ID3v2-stripped (audio frames only), first 1k/2k/4k/64k, last 64k, ID3-stripped-first-64k — each with crc32, adler32, md5 (lo/hi, LE+BE), sha1 (LE+BE), sha256, fnv1a, both full-32 and masked-to-28-bit. NONE matched any track's `opaque_track_id_lo`. So it is not a hash of the audio file content (at least not of the file bytes; a hash of decoded PCM cannot be ruled out but there is no evidence for it and it is not reproducible anyway). 6. **Source DB confirmed, and it is ENCRYPTED.** Both `exportLibrary.db` (next to the pdb) and the captured `master.db` (`maple-py/captures/p17328_master.db`) fail to open as plain SQLite ("file is not a database") — rekordbox 6/7 encrypts them with SQLCipher. The `opaque_track_id_lo` originates there; it cannot be read without the key. 7. **The value is a pure stored field, used nowhere else.** It appears in the export.pdb ONLY at each track row's own offset 0x14 (present and stale/deleted rows alike); it does NOT appear in exportExt.pdb (no tag_tracks/ContentID link), nor in the ANLZ `.DAT`/`.EXT` (searched LE+BE), nor does it equal the USBANLZ folder id. So it is not a foreign key the player resolves. 8. **Range note (suggestive, not conclusive):** across the 5 real exports the lo values span nearly the full 28-bit space (`0x0003edeb`..`0x0ffe7f4e`), the band of rekordbox-6/7 9-digit `djmdContent.ID` integers, so the most likely identity is "the rekordbox internal Content ID" — a stored random/sequential DB id, NOT a content-derived hash. Consistent with every other finding and with it being unreadable (encrypted DB). **Conclusion:** this 8-byte blob is a **stored rekordbox-internal id (most likely the encrypted-DB Content ID), not a function of the track metadata or audio bytes maple has. It cannot be reproduced byte-for-byte** for an arbitrary track without rekordbox's own (encrypted) DB. The audio-content-hash idea is ruled out. There is **no "unanalyzed/zero" form** in any real export — every real track carries a nonzero seed and a nonzero lo. **Recommendation for maple's writer:** - Players/crate-digger treat this blob as opaque (never decoded) and play fine regardless — so any value works for playback. - For maximum faithfulness to genuine rekordbox output (which never has lo=0/seed `0x78f74a68`), maple should write a **nonzero per-install seed and a nonzero, stable per-track lo**: - `opaque_seed`: use a real per-install seed. If maple has no install of its own, `0x21b4a7ab` (this RE machine's install) is a known-real value; keep it constant across all tracks in one export. Do NOT use `0x78f74a68` (that was maple's own placeholder, not a rekordbox value). - `opaque_track_id_lo`: a nonzero 28-bit value, stable per track. Since the real value is an opaque DB id maple can't recover, a deterministic stand-in (e.g. a 28-bit hash of a stable per-track key, or the track's own pdb id offset into the ContentID band) is fine — the goal is "looks like a real nonzero ContentID", not byte-parity. - Either way these 8 bytes will NOT match a specific reference export byte-for-byte (the real ids live in that install's encrypted DB) — record the divergence in re/KNOWN_DIVERGENCE.md. **Confidence:** high (5-export empirical proof: 1046/1046 real tracks nonzero; exhaustive negative hash search incl. audio bytes; binary const-absence). The earlier "write 0 / 0x78f74a68" advice was based on a maple-output file and is retracted. **Round-3 (task #10): can pyrekordbox read the real ids out of master.db?** User insight: pyrekordbox holds the SQLCipher key, so the DBs are not truly unreadable. Validated — partly true, but blocked on the data we have: - pyrekordbox 0.4.4 + `sqlcipher3-wheels` installs and wires up correctly. Its master.db key is the single **global** rekordbox-6 key `402fd482c38817c35ffa8ffb8c7d93143b749e7d315df7a81732a1ff43608497` (`deobfuscate(BLOB)`, must start with `402fd`) — same for every install, NOT per-install. So a VALID master.db *would* open and expose `DjmdContent`. - BUT the captured `maple-py/captures/p17328_master.db` is **corrupt/truncated**: 1,024,000 bytes but only 4.4% non-zero, and the first 32 KB (the mandatory SQLCipher salt + page 1) is **all zeros**. No key can decrypt a DB with a zeroed salt. `p17328_master.backup.db` is 0 bytes. The Frida NtWriteFile capture only caught scattered later pages, never the header. → unusable. - The genuine `exportLibrary.db` shipped next to export.pdb (valid 16-byte salt `ed0d3a3c…`) does NOT open with the global master.db key (different key scheme) and pyrekordbox has **no exportLibrary.db support**. → can't read it here. - Strong inference (untested for lack of a readable DB): the per-track `opaque_track_id_lo` is almost certainly `DjmdContent.ID` (the 9-digit rekordbox Content ID; our lo range 0x0003edeb..0x0ffe7f4e fits). If maple can open the user's LIVE `%APPDATA%/Pioneer/rekordbox6/master.db` with pyrekordbox (global key), `db.get_content()` would give `(ID, …)` per track, and the blob would become EXACTLY reproducible: `opaque_track_id_lo = DjmdContent.ID & 0xFFFFFFFF`. - The per-install `opaque_seed` (0x21b4a7ab here) is NOT a DjmdContent column; it is one value for the whole export and most likely derived from a master.db-level per-library marker (a USB/library UUID or an `agentRegistry`/ `local_usn`-style value). Identifying it needs a readable master.db to inspect candidate columns; unconfirmed. **pyrekordbox write / export capability (asked in #10):** - It CAN write the desktop master.db: `add_content`/`add_album`/`add_artist`/ `create_playlist`/`commit`, plus `set_local_usn`, `update_content_path`. But `add_content` lets rekordbox ASSIGN the Content ID; there is no API to force a specific ID. So it is a source of real ids, not a way to dictate them. - It does NOT drive a USB export — only `get_device` (read). The actual `export.pdb`/`exportLibrary.db` writing is rekordbox.exe's DeviceSQL engine (the thing maple reimplements). pyrekordbox manipulates only the desktop library DB, not the USB export. So pyrekordbox cannot replace maple's writer; its only use to maple is as a READ source of the real `DjmdContent.ID` (and potentially the seed) from a valid master.db. **Net for #10:** the approach is sound (pyrekordbox + global key reads a real master.db → Content IDs), so `opaque_track_id_lo` is likely reproducible *if maple reads the live master.db*. It is NOT reproducible from the corrupt captured DB we have, and the seed source is still unidentified. pyrekordbox cannot produce the USB pdb itself. Recommended next step for maple/RE: point pyrekordbox at the user's LIVE rekordbox6 master.db (`%APPDATA%/Pioneer/rekordbox6/master.db`), confirm `DjmdContent.ID == opaque_track_id_lo` for the AceMo tracks, then hunt the seed in that DB. Until then, the nonzero stand-in (above) stays. --- ## 3. File-header `sequence` (and per-page `sequence`) **Field:** `RekordboxPdb.sequence` (u4 @0x14 of the file header) and `Page.sequence` (u4 in each page header). **Rule / meaning (confirmed from schema docs + the 5 genuine exports above):** - `sequence` is a monotonically increasing **edit counter** for the database. The file-header value is the **next** page-sequence number; each page records the counter value as of its last edit (so `header.sequence >= max(page.sequence)`). Observed header values in genuine rekordbox exports: 35, 56, 353, 546, 4211 — scales with edit history, not track count. (No real export with sequence==1 is available; `data/usb`'s seq==1 is maple's own output, not a rekordbox sample — see §2 correction.) - The firmware/players (and crate-digger) use page `sequence` only as informational ordering metadata; **they do not validate it** for playback. No special value is required beyond keeping `header.sequence` greater than any page's `sequence`. - For maple it is enough to (a) pick a header `sequence` larger than every page `sequence` it writes, and (b) give each page a `sequence` <= header. A simple scheme (all pages = N, header = N+1) is accepted. This is what task #3 (incrementing sequence) implements. **Confidence:** high for semantics and "not validated by players"; the exact increment rekordbox uses per edit is not needed for a valid export. --- ## 4. File-header offset 0x10 (u4, schema `_unnamed4`) — HISTORY-DEPENDENT; not byte-reproducible > CORRECTION (2026-06-09): a prior draft cited a "usb fresh, sequence==1, > _unnamed4==0" row as proof that 0 is the fresh value. That file is maple's own > output, not a rekordbox export, so it is removed below. The remaining proof > (same-library divergence) does not depend on it. **Field:** the unnamed u4 between `next_unused_page` (0x0c) and `sequence` (0x14). maple writes 0; the AceMo ref has 4. **Finding (5 genuine rekordbox exports):** an **edit/allocator accumulator**, not a structural value derivable from the tables. It does NOT equal track count, page count, or `next_unused_page - npages`: | export | sequence | _unnamed4@0x10 | next_unused | npages | |---------------|---------:|---------------:|------------:|-------:| | acemo_fresh | 35 | 5 | 52 | 49 | | lib_multi | 56 | 4 | 57 | 52 | | acemo | 353 | 4 | 53 | 49 | | MAPLE | 546 | 5 | — | — | | data/PIONEER | 4211 | 1 | 174 | 174 | **Decisive (same-library proof):** acemo and acemo_fresh are the SAME library (identical track set + identical per-track opaque ids) yet have `_unnamed4` = 4 vs 5. A structural field would be identical; differing proves it is edit state. (We have NO genuine rekordbox export with `sequence==1`, so the exact never-edited value is unconfirmed — but it is clearly not derivable from the file's structure.) **Rule for maple:** there is no reproducible rule. Write **0** (maple already does) as a **reasonable minimal default — NOT a confirmed match** (every genuine export we have is nonzero here, and we have no never-edited rekordbox export to check what fresh rekordbox writes). Record the divergence in re/KNOWN_DIVERGENCE.md. It cannot be matched to the AceMo ref's `4` without that DB's edit history. **Confidence:** high that it is edit-history/allocator state and not structurally reproducible; the specific fresh-write value is unconfirmed (no seq==1 rekordbox sample available). --- ## 5. Page-header offset 0x26 (u2, "unknown7"/strange-page) — HISTORY-DEPENDENT; maple's 0 is a reasonable default > CORRECTION (2026-06-09): do NOT cite `data/usb` (sequence==1) here — it is > maple's OWN output, so "maple's 0 matches usb" would be circular. The > observations below are from the five GENUINE rekordbox exports only. **Field:** the second of the two trailing u2s in the page header (after `transaction_row_index` @0x22 and the "1004 for strange blocks" u2 @0x24). @flesniak: "always 0 except 1 for history pages, num entries for strange pages?". maple writes 0; the AceMo ref has 0/1/2 on various tables' first ("strange") pages. **Finding (5 genuine rekordbox exports — all incrementally edited):** - On DATA pages it is **always 0** (all five exports). This is a solid observation about genuine rekordbox output. - It is nonzero only on a table's first/"strange" page (flags 0x64), and the value does NOT correlate with that table's data-page count or row count (acemo tracks: 2 data pages → 2; data/PIONEER tracks: 105 data pages → 23). It is transaction/edit-tracking state. - **Decisive (same-library proof):** acemo and acemo_fresh hold the identical tracks table, yet its strange-page unknown7 = 2 (acemo) vs 1 (acemo_fresh). Same data → different value ⇒ not structural, it is edit state. (lib_multi: tracks strange unk7 = 2 with 3 data pages / 10 rows — again no count relation.) **Rule for maple:** write **0** on every page (maple already does). The data pages being 0 across all five genuine exports makes 0 a well-supported value *for data pages*. For strange pages the real value is per-export edit state (0/1/2…) that cannot be reproduced — maple's 0 there is a **reasonable minimal default, NOT a confirmed match** (we have no never-edited rekordbox export to check what fresh rekordbox stamps on a strange page). Record the strange-page divergence in re/KNOWN_DIVERGENCE.md. **Confidence:** high that it is history-dependent and that data-page 0 matches real rekordbox; the fresh-write strange-page value is unconfirmed (no seq==1 rekordbox sample). --- ## 6. Row-group trailing u2 (`transaction_row_flags`) — write `present_flags` (NOT 16); residual mismatch is edit history > CORRECTION (2026-06-09): an earlier draft of this section claimed "a fresh > export writes 16 everywhere, so maple's 16 is correct." That was based on > `data/usb/.../export.pdb`, which on re-check is **maple's OWN output**, not a > rekordbox export (opaque seed `0x78f74a68` + all-zero per-track lo + > `sequence`==low). Reading it back was circular. The analysis below uses ONLY > the five genuine rekordbox exports (provenance verified by nonzero per-track > opaque ids + per-install seed): acemo, acemo_fresh, lib_multi, MAPLE, > data/PIONEER. **Field:** the last u2 of each 36-byte row-group block in the page row index (crate-digger `transaction_row_flags`, at `base`; in maple's `pager.py` it is `pack(" NOTE: the earlier "keep 16 / fresh usb writes 0x10 everywhere" framing was > WRONG — it read `data/usb`, which is MAPLE'S OWN OUTPUT (an old build that > emitted the rekordcrate-default 16), not a real export. `data/usb` must NOT be > used as a 'fresh rekordbox' baseline for ANY field (same contamination that > produced the wrong opaque-blob default in §2). The fresh-write value is > established from the REAL `data/acemo_fresh` as `present_flags`. **Confidence:** high that it is history-dependent and that real rekordbox never uses 16; the correct fresh-write value is an open question (no seq==1 sample). --- ## Note on the three "reproducible header fields" (task #9) After multi-export analysis across the FIVE genuine rekordbox exports (acemo, acemo_fresh, lib_multi, MAPLE, data/PIONEER — provenance verified by nonzero per-track opaque ids + per-install seed), all three fields are **edit-history / transaction state, NOT structurally reproducible**: - §4 file-header 0x10: allocator/edit accumulator. → write 0, DIVERGENCE. - §5 page 0x26 unknown7: 0 on all data pages (exact); per-table 0/1/2 on strange pages is edit state. → write 0, strange-page DIVERGENCE. - §6 row-group trailing u2: "rows touched by last transaction" bitmask. Fresh single-transaction value = `present_flags` (12/14 in real data/acemo_fresh; constant 16 = 0/14). → maple writes `present_flags`; residual mismatch vs an EDITED ref is the per-page last-tx bitmask = edit history, DIVERGENCE. The decisive evidence is the **same-library pair** (acemo vs acemo_fresh: identical track data, yet header 0x10 = 4 vs 5, tracks strange unk7 = 2 vs 1, tracks-page trailing differs) — a structural field would be identical. maple's values: §4 header0x10 = 0 and §5 unknown7 = 0 are reasonable minimal defaults (confirmed = real exports on data pages; fresh strange-page value unconfirmed). §6 trailing u2 = `present_flags` is the established fresh-write value (NOT the old constant 16). No further writer change needed; remaining mismatches vs edited references are unavoidable edit history. IMPORTANT corrections to the earlier draft: (1) `data/usb/.../export.pdb` is maple's OWN output (an old build that emitted the rekordcrate-default 16), not a rekordbox export — so any "fresh usb" claim was circular; `data/usb` is NOT an authority for ANY field (same contamination that produced the wrong opaque-blob default in §2). (2) We have NO genuine rekordbox export with sequence==1, so the never-edited header0x10 / strange-page-unknown7 values are unconfirmed. (3) The row-group trailing u2 fresh value is `present_flags`, established from the REAL data/acemo_fresh (12/14); the rekordcrate "Default=16" is a placeholder that matches 0/14 real groups. All three go to re/KNOWN_DIVERGENCE.md, not the writer. --- ## 7. Artwork + USBANLZ path schemes (task #11) Decoded from genuine `data/MAPLE` (176 tracks/176 artwork) + acemo. The guiding principle (per the task): for **player acceptance only PATH SELF-CONSISTENCY matters** — every path stored in the pdb must resolve to a real on-disk file. Matching rekordbox's exact id-derivation is NOT required; maple's own consistent scheme is acceptable. ### 7a. Artwork — SOLVED, exactly reproducible (maple already matches) - Path: `/PIONEER/Artwork/{folder:05d}/a{artwork_id}.jpg` (80×80) plus a `a{artwork_id}_m.jpg` (240×240) variant in the same folder (rekordbox writes both; verified on disk in data/MAPLE). - **Folder bucket rule: `folder = artwork_id // 20 + 1`** (20 ids per bucket; bucket 00001 = ids 1–19 because ids are 1-based, 00002 = 20–39, 00003 = 40–59, …). Verified **176/176** in data/MAPLE. - maple's `artwork._bucket()` is literally `f"{artwork_id // 20 + 1:05d}"` and `extract_artwork` writes both jpg variants at that path → **byte-identical to rekordbox AND self-consistent**. No change needed. ### 7b. USBANLZ — opaque id; maple's own scheme is self-consistent (DIVERGENCE from rekordbox) - Path: `/PIONEER/USBANLZ/P{bucket}/{8-hex}/ANLZ0000.DAT` (+`.EXT`). - rekordbox's `{8-hex}` is a per-track **opaque analysis id** (range seen 0x117..0x309fd over 176 tracks; NOT == track id, NOT == the row-0x14 `opaque_track_id_lo`, NOT a crc of path/title — tested) and the `P{bucket}` (0–127, 94 distinct over 176) is a spreading bucket that is **NOT a derivable function** of the 8-hex or any track field (tested: `&0x7f`, `>>k`, `%127/128`, low-byte — all fail). Like the row-0x14 blob, these come from rekordbox's internal DB and are **not reproducible** without it. - maple (cli.py `build()`): generates its OWN deterministic scheme — `bucket = P{(crc32(file_path) >> 20) & 0xFFF:03X}`, `analyze_id = {crc32(file_path):08X}`, then `write_anlz()` writes the .DAT at exactly that path. This is **self-consistent by construction** (the path it records is the path it writes) and is the correct approach per the self-consistency principle. It does NOT match rekordbox's id/bucket (expected DIVERGENCE). - Minor cosmetic note (not a correctness issue): maple's bucket is 3 hex digits `P000..PFFF` whereas rekordbox uses `P00..P7F` (0–127). Harmless; optionally narrow to `& 0x7F` (`P{..:02X}`) to look more rekordbox-like. ### 7c. Self-consistency verification - **Genuine rekordbox (data/MAPLE): 176/176 ANLZ paths and 176/176 artwork paths resolve to on-disk files.** (rekordbox is fully self-consistent — sanity check that confirms the parser and the principle.) - **maple production `build()` (cli.py): self-consistent BY CONSTRUCTION** — `extract_artwork` (artwork.py:62-65) writes `a{id}.jpg`/`a{id}_m.jpg` at the recorded path; `write_anlz` (cli.py:93) writes `ANLZ0000.DAT` at the recorded `analyze_path`. Each pdb path is computed and the file is written there in the same step, so every path resolves. - CAVEAT — the byte-diff harness `tools/build_acemo.py` is NOT the production path: it MIRRORS a reference rekordbox export (copies its `analyze_path`/ artwork `path` strings verbatim, writes no ANLZ/artwork files), so the `data/usb` export it produces references the reference's USBANLZ files which are not co-located → those paths don't resolve. This is expected for a structural-diff fixture and says nothing about production self-consistency. **Confidence:** high. Artwork rule is exact (176/176). USBANLZ rekordbox ids are opaque/non-reproducible (consistent with the row-0x14 finding); maple's own USBANLZ scheme is self-consistent, which is all the player requires. --- ## 8. Page-manager free-space: `empty_candidate` + free pool (task #13 — THE FREEZE) DECK TEST: maple's pdb FROZE the player (parser hang, not reject). Decoded the page-manager/free-space divergence against genuine rekordbox exports (`data/working` 9trk seq44, `data/MAPLE` 176trk, `data/PIONEER` 847trk — all load) vs maple output (`captures/maple_acemo.pdb`). ### Data-page `next_page` SELF-LOOP — ROOT CAUSE (confirmed divergence; high confidence) - The player reads each table by following `first_page → next_page → …`. maple's **last data page of each multi-page chain set `next_page == its own index`** (page 2→2, 6→6, 8→8, 12, 14, 28, 34, 36, 38, 40 — 10 self-loops in the 9-track build). A page whose `next_page` is itself is an **infinite loop → FREEZE**. - **Genuine rekordbox has ZERO self-loops.** A terminal data page uses `next_page = 0` (the "no next" sentinel; seen on pages 10/16/18/20/…/41-47 in `data/working`) OR points FORWARD to a distinct free page (never to itself). - FIX (maple, done in writer.py): point every chain's last page at a single dedicated zero-filled free-pool page (`free_sentinel`, just past the overflow region) instead of itself; materialise that page and set `next_unused` beyond it. Verified on the rebuilt pdb: 10 self-loops → 0; all 20 chains terminate at the blank sentinel; player tolerates last-page→free-page (genuine exports do the same). This is the primary freeze fix. ### Table-dir `empty_candidate` (u4 after page_type) — SECONDARY divergence (also wrong; lower-confidence freeze contributor) - Layout: each 16-byte table-dir entry is `type:u4, empty_candidate:u4, first_page:u4, last_page:u4`. - `empty_candidate` is the head of that table's **empty/free-page list** (the page the Page Manager grabs when the table needs to grow). - **In EVERY genuine rekordbox export all 20 `empty_candidate`s are NONZERO and distinct**, and each points to an **all-zero "free" page** — either an in-file allocated-but-empty page (e.g. `last_page+1`, or a pooled page like 41–47/49) or a **beyond-EOF sentinel** (index ≥ npages, e.g. 50/51/52 when npages=49 — the firmware tolerates beyond-EOF here). It NEVER points to a used data page and NEVER to 0. - **maple writes `empty_candidate = 0` for all 20 tables.** Page 0 is the FILE HEADER. The firmware Page Manager, walking a table's empty-page list, follows `0` → reads the 4 KB file header AS a page → its "next_page" field (header offset 0x0c = `next_unused_page`) and bogus type/flags send the walk into a loop or out-of-bounds read → **hang/FREEZE**. This is the highest-confidence cause: it is the only systematic, data-independent divergence, and `0` is never valid (collides with the header). ### Free pool / page layout - rekordbox keeps the data pages in chains `1..40,48,…` and pre-allocates a pool of all-zero "free" pages past them; `next_unused_page` = one past the highest allocated page (so `next_unused > npages` is normal, e.g. 53 > 49). - maple **compacts**: 41 pages, every page used, `next_unused == npages == 41`, no free pool. The compaction itself is structurally legal (chains are clean), but it left `empty_candidate = 0` with no free page to point at. - Beyond-EOF links (a page's `next_page` ≥ npages, or `empty_candidate` ≥ npages) are TOLERATED — the genuine `working` file has many (page 4→53, 46→52, empty_candidate 51/52/50/49 all ≥ 49). So beyond-EOF is a valid "no-more-pages / no-free-page" sentinel; it is NOT the freeze. ### Strange-page free-map — NOT the cause (decoded, maple matches) - Each table's first/"strange" page (flags 0x64) carries, after a small prefix `[this_page, next_or_0x03ffffff, 0x03ffffff, 0, 0x1fff0000]`, a heap filled with **1004 × `0x1ffffff8`** (`f8 ff ff 1f` LE) — the empty-heap free-map (count == the `m1004`=1004 marker at header 0x24). `0x03ffffff` in the 2nd u4 is the legitimate **"this table has no data page"** sentinel (genuine rekordbox writes it for every empty table; e.g. labels/pltree/plentries). - maple reproduces this free-map correctly; its strange-page prefixes match rekordbox's pattern byte-for-byte (data table → u1=first_data_page; empty table → u1=0x03ffffff). **So the free-map is NOT the freeze.** ### FIX (for maple → deck test) PRIMARY (high confidence, DONE): **eliminate the data-page `next_page` self-loop** — last page of each chain must point to a blank free page (or use `next_page=0`), never to itself. maple now points all last pages at one zero-filled `free_sentinel` page. Verified: 0 self-loops in the rebuilt pdb. SECONDARY (cheap, also a real divergence — fold into the same build): **stop writing `empty_candidate = 0`.** Point it at the same `free_sentinel` (`pack("