docs(parity): on-device parity audit vs Zune HD disassembly + gap register; correct kinetic provenance

This commit is contained in:
John Smith
2026-09-11 01:20:10 -04:00
parent f8ac21c29a
commit 22ff5f2847
8 changed files with 625 additions and 40 deletions
+3 -1
View File
@@ -147,7 +147,9 @@ The full firmware reverse-engineering corpus (109 reconstructed ARM32 PE modules
`scripts/ghidra_corpus.py` into the out-of-tree `zune-hd-disassembly/`. Synthesized
analysis lives in [`docs/zune-hd-module-inventory.md`](docs/zune-hd-module-inventory.md),
[`docs/zune-hd-api-reference.md`](docs/zune-hd-api-reference.md) and
[`docs/zune-hd-assets.md`](docs/zune-hd-assets.md).
[`docs/zune-hd-assets.md`](docs/zune-hd-assets.md). The on-device parity audit is
[`docs/zune-hd-parity-audit.md`](docs/zune-hd-parity-audit.md) (machine-readable
register: [`docs/zune-hd-parity-gaps.json`](docs/zune-hd-parity-gaps.json)).
### Tests
@@ -20,13 +20,17 @@ object DoradoMotion {
const val STAGGER_MS = 20L
/**
* Kinetic scroller velocity retention per 60 Hz frame. This is
* `XuiTouchSettings[0x1C]` as the Zune HD shell applied it
* (`gemstone.exe` VA `0x1C900`–`0x1CB64`; the XUI default is 0.7, the
* shell raised it to 0.95 for the device's long glide).
* See docs/zune-hd-touch-settings.md §2–3.
* Kinetic scroller velocity retention per frame — an **empirical
* approximation**, not a device constant. The Zune HD integrator
* (`xuidll.dll@0x41841D58`) is a dt-scaled glide (`pos += (dt_ms/1000)·v`,
* with dt floored at 33.333 ms) that updates `v' = v·(1 + c[+0xB0])` clamped
* to `±(step·c[+0xB4])` and ticks at 16 ms (62.5 Hz). It reads the element's
* touch-settings fields `[0x08]/[0x0C]` — **not** `[0x1C]`.
* See docs/zune-hd-touch-settings.md §6 and docs/zune-hd-parity-audit.md §3.
*/
const val KINETIC_FRAME_RETENTION = 0.95f
/** Device tick is 62.5 Hz (`FUN_41848B98`, 16 ms); 60 Hz is the closest frame rate. */
const val KINETIC_FRAME_HZ = 60f
/**
+5
View File
@@ -235,6 +235,11 @@ referencing `dorado/src/Dorado.Application`); re-run it after any desktop
1. **M10 closeout** (S–M) — richer lock-screen Now Playing art/controls + optional sleep timer.
2. **M5/M6 community wishlist** (M) — share-sheet + Zune-Card artwork export, EQ presets, crossfade, live-radio cache.
3. **Corpus mining** (M) — the full 109-module corpus is built (66.6k functions, symbols applied); mine the decompilation/strings for behavioral ground truth to tighten the canon.
4. **Parity-audit follow-ups** (see [`zune-hd-parity-audit.md`](zune-hd-parity-audit.md) + [`zune-hd-parity-gaps.json`](zune-hd-parity-gaps.json)) — queue surface, picture pinch-zoom, Now Playing scrubber, library-search UI, HUD dimmer/status OSD, built-in playlists, inbox/user-card, PIN/Wi-Fi settings, string-parity pass, font-import decision.
The audit also re-derives the kinetic model: the device glide is dt-scaled
(62.5 Hz, 30 fps floor) and the `0.95` retention is an empirical approximation
(canon §6; `zune-hd-touch-settings.md` §6).
Front-loads the remaining always-on surfaces, then the community wishlist and
disassembly-driven fidelity work.
+2 -1
View File
@@ -175,7 +175,8 @@ Raw decompiled C, symbol dumps and string dumps are Microsoft-derived and stay i
the external corpus; the repository keeps only synthesized metadata
([`zune-hd-module-inventory.md`](zune-hd-module-inventory.md),
[`zune-hd-api-reference.md`](zune-hd-api-reference.md),
[`zune-hd-assets.md`](zune-hd-assets.md)).
[`zune-hd-assets.md`](zune-hd-assets.md)), plus the on-device parity audit
([`zune-hd-parity-audit.md`](zune-hd-parity-audit.md)).
### Output Directory Structure (`zune-hd-disassembly/`)
+349
View File
@@ -0,0 +1,349 @@
# Zune HD on-device parity audit — Dorado-HD vs firmware disassembly
**Date:** 2026-09-10
**Method:** systematic comparison of the reconstructed Zune HD firmware corpus
(`zune-hd-disassembly/` — 109 ARM32 PE modules, 66,598 functions, 4,169 named
exports, 22,479 strings) against the Dorado-HD Android app source.
**Scope:** *on-device* parity — every behavior the Zune HD itself had. Dorado-HD's
additions beyond the device (scrobble, lyrics, DSP, Dynamic Mix, cloud, LAN sync,
Now Playing widget) are **not** scored here; they are post-device extensions
(canon §10).
**Provenance.** Device evidence is cited as `module.dll@<VA>` (virtual address in
the reconstructed PE) or as a recovered string. HD evidence is `path:line`. Raw
decompiled C, symbol dumps and strings stay in the external corpus and are never
committed; this document is synthesized analysis only.
**Companion:** [`zune-hd-parity-gaps.json`](zune-hd-parity-gaps.json) is the
machine-readable gap register.
---
## 1. On-device parity scorecard
| Domain | On-device parity | Notes |
|---|---:|---|
| A. Shell & navigation | **~78%** | Core surfaces match; a set of device scenes is absent (audiobooks, share, queue, inbox…). |
| B. Now Playing & transport | **~75%** | Music/video/radio work; no scrubber, queue list, or picture NP. |
| C. Motion & kinetics | **~60%** | Shape matches; the integrator mathematics diverges (see §4). |
| D. HUD / always-on | **~45%** | Transport overlay + shade; no dimmer/power-off, PIN lock, status OSD. |
| E. Library & indexing | **~80%** | Pivots + A–Z rail; no search UI, no built-in playlists, no letter/grid thresholds. |
| F. Playback engine | **~70%** | Playback/shuffle/repeat/hearts; no EQ, queue engine, RDS, WMA/DRM. |
| G. Marketplace & commerce | **~30%** | Frozen 62-entry catalog; no client/search/cart. |
| H. Social / inbox / identity | **~30%** | Feed mock; no inbox, user card, Passport. |
| I. Settings & device | **~70%** | Most pages; no PIN/screen-lock, Wi-Fi info, TV-out. |
| J. Tokens / fonts / strings | **~70%** | Palette/scale canon-derived; label vocabulary diverges. |
**Weighted on-device parity: ~68%.** (The device is 2009 hardware; several device
subsystems — marketplace, DRM, Passport, MTPZ, TV-out, telemetry — are
deliberately out of scope; see §12.)
---
## 2. Shell & navigation (gemstone.exe — 74 `Gem*Scene` identifiers)
Full device scene list recovered from `gemstone.exe.strings.json`. Status:
✅ parity · 🟡 partial · ❌ missing · N/A out of scope.
| Device scene | Dorado-HD | Status |
|---|---|---|
| `GemStartScene` / `GemStartListScene` | `ui/screens/HomeScreens.kt:68,111` | ✅ |
| `GemTiltScene` (Quickplay) | `ui/screens/HomeScreens.kt:147` | ✅ |
| `GemPivotScene` | `design/components/CrossbarBar.kt` | ✅ |
| `GemOverlayScene` | `ui/screens/NowPlayingScreen.kt:477` | ✅ |
| `GemSyncScene` | `ui/screens/DeviceScreen.kt` | ✅ |
| `GemLibrarySongScene` / `LibraryListScene` | `ui/screens/MusicScreen.kt:257` | ✅ |
| `GemLibraryArtistScene` (+About/Related) | `ui/screens/DetailScreens.kt:169,225,320` | ✅ |
| `GemLibraryPodcastScene` / Video | `ui/screens/Podcasts.kt:142`; `MediaScreens.kt:76` | ✅ |
| `GemLibraryLetterPickerScene` | `design/components/KineticList.kt:106` | ✅ |
| `GemNowPlayingRadioPresetListScene` | `ui/screens/Radio.kt:110` | ✅ |
| `GemMarketplaceScene` / Games | `ui/screens/MarketplaceScreens.kt:56,205` | ✅ |
| `GemSettingScene` / About | `ui/screens/SettingsScreen.kt:43,254` | ✅ |
| `GemStartGridScene` | — (list only) | ❌ |
| `GemStartExtrasScene` | Marketplace apps/games pivots | 🟡 |
| `GemQueueListScene` / `GemNowPlayingMusicListScene` | — (`enqueue` exists, no queue UI) | ❌ |
| `GemLibraryAudiobookPartScene` | — (no audiobook model) | ❌ |
| `GemLibrarySendComposeScene` / `…SendFriendsListScene` | — (no `ACTION_SEND`) | ❌ |
| `GemPictureTouchClientScene` / `GemZoomTouch` | `MediaScreens.kt:657` static `Fit` | ❌ |
| `GemLibraryArtistPhotoGridContentScene` | single image `DetailScreens.kt:275` | 🟡 |
| `GemLibraryCategoryScene` | `DetailScreens.kt:484` (genre only) | 🟡 |
| `GemLibraryGameScene` | Marketplace games pivot | 🟡 |
| `GemNowPlayingPicturesScene` / `…MainScene` | no picture NP mode | 🟡 |
| `GemNowPlayingRadioScene` / `…MainScene` | unified music NP reused | 🟡 |
| `GemMarketplaceSearchScene` | — (frozen catalog) | ❌ |
| `GemMarketplaceMusicTopScene` / `…GenresContentScene` | "featured" only `MarketplaceScreens.kt:256` | ❌ |
| `GemMarketplaceDetailsScene` (album/artist) | `DetailScreens.kt:63` | ✅ |
| `GemMarketplaceGamesDetailsScene` (+Screens) | catalog rows only | ❌ |
| `GemCartScene` / `GemCartConfirmScene` | — | ❌ (N-A candidate) |
| `GemSocialScene` | `MediaScreens.kt:632` (mock) | ✅/mock |
| `GemUserCardScene` | — | ❌ |
| `GemInboxListContentScene` / `GemInboxDetailsScene` | `ui/apps/mocks/Mocks.kt:90` (mock) | ❌ |
| `GemSettingPinLockScene` / `…ScreenLockScene` | `LockShade` only (no PIN) | ❌ |
| `GemSettingWifiInfoScene` | — | ❌ |
| `GemSettingStorageScene` | `SettingsScreen.kt:124`, `DeviceScreen.kt:333` | 🟡 |
| `GemSettingLegalScene` | footnote `SettingsScreen.kt:261` | 🟡 |
| `GemRefreshScene` | Settings/library refresh (no scene) | 🟡 |
| `GemTvOutputSelectorScene` | — | ❌ (hardware N-A) |
| `GemAcsFWScene` | `cloud/CloudUpdateService.kt` (different path) | 🟡 |
| Widgets (`GemContextMenu`, `GemWaitCursor`, `GemTimeline`, `GemTunerBar`, …) | Android/Compose runtime | N/A |
### Highest-value missing surfaces
1. **Queue surface** (`GemQueueListScene` / `GemNowPlayingMusicListScene`) — `PlaybackController.enqueue` (`media/PlaybackController.kt:153`) already exists; only the list UI is missing.
2. **Picture pinch-zoom/pan** (`GemPictureTouchClientScene` / `GemZoomTouch`) — device-signature viewer; HD is static.
3. **Sharing** (`GemLibrarySendComposeScene`) — scoped as roadmap M5.
4. **Audiobooks** (`GemLibraryAudiobookPartScene`) — whole media type absent.
5. **Marketplace discovery** (search/top/genres) — makes the frozen catalog navigable.
---
## 3. Motion, touch & kinetics (resolved)
### 3.1 The device integrator (authoritative)
Recovered from `xuidll.dll@0x41841D58` (scroll tick), `@0x4184C310` (drag
velocity), `@0x41848B98` (timer), `@0x41848FC4` (rubber-band),
`@0x4184C660` (default touch settings).
- **Position:** `pos += (dt_ms / 1000) · v`, where `dt_ms = max(33.333, now − lastTick)`
(30 fps floor; literals `xuidll.dll@0x41801920 = 1000.0`,
`@0x41803ce4 = 33.333`).
- **Velocity (active/drag):** `v' = v · (1 + c[+0xB0])`, clamped to
`±( step[+0x88] · c[+0xB4] )`.
- **Stop:** when `pos` reaches the target `[+0xA0]`, the timer is killed and the
shell snap callbacks fire; velocity zero-compare is `0.0`.
- **Tick:** `XuiSetTimer(elem, 1, 0x10)` → **16 ms = 62.5 Hz** (`@0x41848B98`).
- **Drag→velocity:** `(Δpx/Δt) · scale`, hard-capped at **32.0**
(`@0x4184C310`, literal `@0x41804168 = 32`).
- **Rubber-band:** explicit damped spring `-(pos−target)·k[+0xD8] − v[+0x50]·d[+0xD4] + v[+0x50]` (`@0x41848FC4`).
- **Touch properties** (the ~20-float `XuiTouchSettings` vocabulary recovered from
the `xuidll` string table `0x41803F74–0x41804150`): `HorzScroll`, `VertScroll`,
`HorizontalFling`, `VerticalFling`, `HorizontalScrub`, `VerticalScrub`,
`HorizontalRubberBand`, `VerticalRubberBand`, `FadeIn`, `FadeOut`,
`PositionOffset`, `CenterSmallCanvas`, `EnableBandBreak`, `KeyScrollDistance`,
`SmoothScroll`, `ScrollPaddingBefore/After`, `ZoomTarget`, `Hollow`, `Direction`.
### 3.2 Correction: the `0.95` retention provenance is false
The previous docs (`zune-hd-touch-settings.md` §6, canon §6) attributed HD's
per-frame retention to `XuiTouchSettings[0x1C] = 0.95`. **This is not supported:**
- the integrator reads the element's in-place block at `+0xA8` fields **`+0xB0/+0xB4`**
= settings **`[0x08]/[0x0C]`** — `xuidll` defaults **1.9 / 1.5**, shell **1.9 / 1.2**;
- `[0x1C]` defaults to **0.7** and is **never read** by `@0x41841D58`;
- the update `v·(1+c)` is multiplicative-with-clamp, **not** `v·retention`.
### 3.3 HD state and gaps
| Aspect | Device | Dorado-HD | Gap |
|---|---|---|---|
| Tick | 62.5 Hz (`@0x41848B98`) | `KINETIC_FRAME_HZ = 60f` (`design/DoradoMotion.kt:30`) | 4% friction-conversion drift |
| Integrator | dt-scaled, 30 fps floor, step-clamped | continuous `animateDecay` (`design/components/ZuneFling.kt:31`) | model differs |
| Retention | `(1+c)` clamp, no `[0x1C]` | `0.95`/`0.94` (`DoradoMotion.kt:29,38`) | empirical, provenance wrong |
| Rubber-band | explicit spring (`@0x41848FC4`) | platform overscroll; "no springs" invariant | under-models device |
| Axis hysteresis | `XuiTouchEnableAxisHysteresis` (`xuidll@0x418331CC`) | implicit Compose latch | not tunable |
| Velocity cap | 32.0 | none | absent |
| Item snap / SmoothScroll | `XuiTouchSnapToTarget` (`@0x41833134`) | `scrollToItem` for A–Z only | absent |
| Navigation easing | per-element timelines (`TransFrom/To`, `EnterTransIndex/LeaveTransIndex`) | one bezier 380/420/340 ms | different model |
**Recommendation:** re-derive the HD fling friction from the dt-scaled device model
(or explicitly label `0.95/0.94` an empirical approximation), and stop citing
`[0x1C]`. Add the velocity cap and (optionally) item snapping.
---
## 4. HUD / always-on (`zhud_serv.dll`, 1,167 fns, 5 exports)
Device HUD scenes recovered: `HudMediaController{Music,Radio,Video}Scene`,
`HudScreenLockScene`, `HudPinLockScene`, `HudDimmerScene`, `HudOffSwitchScene`,
`HudMessageBoxScene`, `HudWaitMessageBoxScene`, `HudKeyboardScene`,
`HudNetwork{Connect,List}Scene`, `HudAutoConnectScene`, `HudWaitCursorScene`,
`HudDebugOverlayScene`, + root scenes (`ScreenLockRootScene`, `PinLockRootScene`,
`MessageBoxRootScene`, `DimmerRootScene`, …).
Inline HUD dwells are **recoverable** from `XuiSetTimer` call sites (**contradicts
`zune-hd-touch-settings.md` §5**): 7000 ms dim (`@0x419CD420`), 5000 ms saver,
3000 ms notifications, 30000 ms background (`@0x419C0584`), 12000 ms constant.
| Device HUD | Dorado-HD | Status |
|---|---|---|
| Transport overlay (`HudMediaController*`) | `TransportOverlay` `ui/screens/NowPlayingScreen.kt:477` | ✅ |
| Play/Pause/Next/Prev | `PlaybackController.kt:160-171`; `NowPlayingScreen.kt:563-592` | ✅ |
| Volume + mute | swipe volume only; **no mute** | 🟡 |
| `ProgressSlider` + `PlayPosition`/`Elapsed`/`Remaining` | no scrubber in NP | ❌ |
| `Ffwd` | — | ❌ |
| Lock/pin (`HudScreenLock`/`HudPinLock`) | `LockShade` (no PIN) `design/components/LockShade.kt` | ❌/🟡 |
| Dimmer / power-off (`HudDimmer`/`HudOffSwitch`) | — | ❌ |
| Message boxes | not modelled | ❌ |
| Status OSD (`batteryIcon`,`wifiIcon`,`Clock`,`Ambient`) | — | ❌ |
| Screensaver idle | `IDLE_SCREENSAVER_MS=5000` `design/DoradoTokens.kt:68` | ✅ |
| Wait cursor | Compose loading | 🟡 |
---
## 5. Library & indexing (`zcontent_serv.dll` + `zconfig_serv.dll`)
Device substrate: `zunedb.dat`, `zconfig.dat`, `drmStore.dat`, `AlbumArt`,
`mtp:DatabaseGenerationNumber`, `CloudModifiedItems`; `zconfig_serv` exposes
`LibraryLetterThreshold` / `AlbumGridThreshold`.
| Device behavior | Dorado-HD | Status |
|---|---|---|
| Indexed library (tracks/albums/artists/genres/playlists/pictures) | `data/repo/LibraryRepository.kt:35-48` | ✅ |
| A–Z letter picker | `design/components/KineticList.kt:66,176` | ✅ |
| **Indexed search** | `Daos.kt:68` `search()` defined, **no caller** | ❌ |
| Letter/grid thresholds (settings) | — | ❌ |
| Built-in playlists (`BuiltIn-FavoriteTracks`, `…MostPlayedArtists`, `…RecentTracks`) | Quickplay history/pins + Top Played `HomeScreens.kt:311` | 🟡 |
| Sort keys (article-strip, `MMM yyyy`) | plain tags | 🟡 |
| Album-art cache/thumbnails | per-track resolve | 🟡 |
---
## 6. Playback engine (`zmedia_serv.dll`)
Device: `CMediaQueueBase`/`CProgressivePlay`/`CTrackListQueue`, queue
capacities/history, `ZMediaQueue/MediaItemPositioned|Stopped|Paused|Playing`;
EQ (`Software\Microsoft\Zune\Equalizer`, `Preset%02d`); `FFTGRABBER`/`ZMedia/Viz`;
`SmartDJStore.dat`; RDS (`Radio_RDS_Capture.dat`); marketplace image/related
endpoints; DRM (`CeDRM_MGR_ParsePlayOPL`).
| Device behavior | Dorado-HD | Status |
|---|---|---|
| Play/shuffle/repeat/seek/hearts | `media/PlaybackController.kt:142-245` | ✅ |
| Queue engine (capacity/history/position events) | thin Media3 wrapper | ❌ |
| Equalizer presets | — (no `AudioEffect`) | ❌ |
| FFT visualizer bound to playback | procedural bars; real FFT exists separately `analysis/FeatureMath.kt` | 🟡 |
| WMA/WMV + WMDRM/PlayReady | `data/model/MediaFormats.kt:17-23` drops `wma`; no DRM | ❌ |
| RDS / FM tuner | HTTP-stream presets only `Radio.kt` | ❌ |
| Smart DJ store | `PlaybackController.smartShuffleOrder:245` | 🟡 |
---
## 7. Marketplace & commerce (gemstone + `znet_serv.dll`)
Device: `GemMarketplace*`, `GemCart*`; `znet_serv` endpoints
(`album?q=%s`, `/albums?orderby=ReleaseDate`, `/recommendations/artists`,
`chart/zune/tracks`), Passport sign-in (`SignInRequest/Response`,
`urn:passport:compact`), usage reporting (`usageReport`, `SEID_Stats`).
| Device | Dorado-HD | Status |
|---|---|---|
| Marketplace catalog/search/artwork client | frozen 62-entry `data/official/OfficialCatalog.kt`; `MarketplaceScreens.kt:322` | ❌ |
| Music genres / top charts | "featured" only | ❌ |
| Game detail + screenshots | catalog rows | ❌ |
| Cart / purchase / confirm | — | ❌ (N-A candidate) |
| Sign-in / subscriptions | Last.fm + cloud OAuth only | ❌ |
| Usage/telemetry | — | ❌ (privacy: N-A) |
---
## 8. Social / inbox / identity
| Device | Dorado-HD | Status |
|---|---|---|
| Social feed | `MediaScreens.kt:632` (mock); cloud social service exists | ✅/mock |
| Inbox list/details (`GemInbox*`) | `ui/apps/mocks/Mocks.kt:90` | ❌ |
| User card (`GemUserCardScene`) | — | ❌ |
| WMA streaming / DRM (`ZWmtStreamer.dll`, `XDRM*`) | not supported | ❌ (N-A) |
| Credentials/DRM store (`zcredentials_serv`) | no keyvault | ❌ (N-A) |
| MTPZ/UPnP identity (`/Device.xml`, `urn:…mtpz:1`) | custom LAN sync (`sync/`) | 🟡 |
---
## 9. Settings & device (gemstone `GemSetting*` + `zconfig_serv`)
| Device | Dorado-HD | Status |
|---|---|---|
| Settings root / About / Storage | `SettingsScreen.kt:43,254,124` | ✅/🟡 |
| Legal | footnote `SettingsScreen.kt:261` | 🟡 |
| Screen lock / PIN lock | `LockShade` only | ❌ |
| Set time / lock-on | OS-managed / wake shade | N-A |
| Wi-Fi info (`GemSettingWifiInfoScene`) | — | ❌ |
| TV-out selector | — | ❌ (hardware) |
| Config store (`zconfig_serv`: `SwapEffect`, `ImageProduction`, …) | N/A (Android) | N/A |
| Cloud/OTA (`UPDATE`, `DColdReboot`) | `cloud/CloudUpdateService.kt` | 🟡 |
---
## 10. Tokens, typography, strings, assets
- **No color hexes or numeric point sizes exist in the UI binaries.** XUI carries
color/size as *property names* only (`TextColor`, `FillColor`, `PointSize`,
`Font`, `LineHeight`, `TextScale`). HD's palette/scale are canon/review-derived,
not byte-citable — the audit cannot verify them against the binaries.
- **Font posture is correct:** HD bundles only OFL Selawik (`design/DoradoTheme.kt:16-20`);
the device's six Zegoe UI faces live only in the external corpus. **Gap:** the
docs promise "import your own Zegoe" (`zune-hd-ui-canon.md:20`,
`DoradoTheme.kt:13`) but **no font-import path exists**. Also Selawik has no
Black(900)/Medium(500), so `FontWeight.Medium` (`widget/NowPlayingWidget.kt:94`)
snaps.
- **String parity drift** (device vocabulary HD could adopt):
- `"Various Artist"` (`gemstone@0x119A4`) vs HD `"unknown artist"`
(`data/scan/MediaLibraryScanner.kt:64`);
- `" by "` composition (`gemstone@0x15A60`) vs HD em-dash (`ui/components/Common.kt:291`);
- empty-state keys **`Empty`**, **`noItems`/`noItems_local`/`noItems_online`**
(`gemstone@0x12B7C`, `0x14160/0x148BC/0x14840`) vs bespoke HD copy;
- transport/status words HD lacks: **`Ffwd`**, **`Mute`** (`gemstone@0x19D18/0x19D2C`;
`zhud_serv@0x419B4C70/84`), `Disconnected`, `Refresh`, `Wishlist`.
- **Docs↔code naming drift (values equal):** `SurfaceTileHover`→`tilePressed`,
`SurfaceBorderSubtle`→`border`, accent labels; `TYPE_HEADER_CROP_VISIBLE=48`
undocumented (`design/DoradoTokens.kt:25`).
---
## 11. Deliberately out of scope (device features we do not intend to reproduce)
These are device-era subsystems whose servers/hardware are dead or proprietary;
they are recorded as **N-A**, not gaps:
- Marketplace backend + Passport/Microsoft account + subscriptions.
- DRM license acquisition (`CeDRM_Mgr_*`), PlayReady/WMDRM, secure clock.
- WMA/WMV playback and `ZWmtStreamer` HTTP streaming.
- MTPZ/UPnP device identity and Wi-Fi config (`wzcsapi`).
- TV-out selector; per-app power/display/suspend timeouts (ZAM).
- Usage/telemetry reporting (`usageReport`, `SEID_Stats`).
- ACS touch-controller firmware update (`GemAcsFWScene`).
---
## 12. Prioritized gap register (summary)
Full machine-readable register: [`zune-hd-parity-gaps.json`](zune-hd-parity-gaps.json).
| # | Gap | Axis | Effort | Status |
|---|---|---|---|---|
| 1 | Queue surface (`GemQueueListScene`) | device | S | ❌ |
| 2 | Picture pinch-zoom/pan (`GemPictureTouchClientScene`/`GemZoomTouch`) | device | M | ❌ |
| 3 | Now Playing scrubber + elapsed/remaining | device | S | ❌ |
| 4 | Kinetic model re-derivation (dt-scaled; fix `0.95` provenance) | device | M | 🟡 |
| 5 | Library search UI (wire existing `search()`) | device | S | ❌ |
| 6 | HUD dimmer/power-off + status OSD | device | M | ❌ |
| 7 | EQ presets | device | M | ❌ |
| 8 | Share / `ACTION_SEND` + Zune-Card export | device | M | ❌ (M5) |
| 9 | Audiobooks | device | L | ❌ |
| 10 | Marketplace search/top/genres | device | L | ❌ |
| 11 | Built-in playlists | device | M | 🟡 |
| 12 | Inbox + user card | device | M | ❌ |
| 13 | PIN/screen-lock + Wi-Fi info | device | M | ❌ |
| 14 | String parity (`Various Artist`, ` by `, `noItems`, `Ffwd`, `Mute`) | device | XS | 🟡 |
| 15 | Font-import path (or drop the claim) | doc/device | S | ❌ |
| 16 | Velocity cap + item snap | device | S | ❌ |
---
## 13. Corrections to existing docs
- **`zune-hd-touch-settings.md`**: replace the `[0x1C]=0.95` retention provenance
with the resolved integrator model (§3); add the recoverable HUD timer
constants; add the XUI touch property-name vocabulary; fix
`XuiProcessMultiTouchMessage` VA to `0x4181F100`; correct §5 "not recoverable"
claims (dwell timers **and** field names are now recoverable).
- **`zune-hd-ui-canon.md` §6**: cite the device tick (62.5 Hz) and the
`pos += (dt/1000)·v` model; do not cite `[0x1C]`.
- **`design/DoradoMotion.kt`**: mark `0.95/0.94` as empirical approximations of the
device glide, not device constants.
- **Label parity**: canon-cite `"Various Artist"`, the ` by ` separator, and the
`noItems`/`Empty` empty-state vocabulary before changing display copy.
## 14. Roadmap delta
Add to `parity-roadmap.md`: queue surface, picture touch/zoom, NP scrubber,
library-search UI, HUD dimmer/status, built-in playlists, inbox/user-card,
PIN/Wi-Fi settings, string-parity pass, font-import decision; and annotate
marketplace/DRM/Passport/MTPZ as documented N-A (§11).
+181
View File
@@ -0,0 +1,181 @@
{
"audit": "zune-hd-parity-audit",
"date": "2026-09-10",
"corpus": "zune-hd-disassembly (109 modules, 66598 functions, 4169 exports, 22479 strings)",
"axes": {
"device": "behaviour the Zune HD itself had",
"modern": "post-device extension (canon §10)",
"na": "device subsystem deliberately out of scope"
},
"gaps": [
{
"id": "queue-surface",
"surface": "Play queue list",
"axis": "device",
"priority": 1,
"effort": "S",
"status": "missing",
"device_evidence": "gemstone.exe GemQueueListScene / GemNowPlayingMusicListScene",
"hd_state": "PlaybackController.enqueue exists (media/PlaybackController.kt:153); no queue UI"
},
{
"id": "picture-zoom",
"surface": "Picture pinch-zoom / pan client",
"axis": "device",
"priority": 2,
"effort": "M",
"status": "missing",
"device_evidence": "gemstone.exe GemPictureTouchClientScene, GemZoomTouch",
"hd_state": "static ContentScale.Fit (ui/screens/MediaScreens.kt:657)"
},
{
"id": "np-scrubber",
"surface": "Now Playing scrubber + elapsed/remaining",
"axis": "device",
"priority": 3,
"effort": "S",
"status": "missing",
"device_evidence": "zhud_serv.dll ProgressSlider, PlayPosition, ElapsedTime/TotalTime/RemainingText",
"hd_state": "time text only; seekTo exists but no UI (ui/screens/NowPlayingScreen.kt:237)"
},
{
"id": "kinetic-model",
"surface": "Kinetic integrator model + 0.95 provenance",
"axis": "device",
"priority": 4,
"effort": "M",
"status": "partial",
"device_evidence": "xuidll.dll FUN_41841D58 pos+=(dt/1000)*v, dt floor 33.333; v'=v*(1+c[+0xB0]) clamped +/-step*c[+0xB4]; tick 16ms FUN_41848B98; velocity cap 32 FUN_4184C310",
"hd_state": "exponentialDecay, KINETIC_FRAME_RETENTION=0.95 wrongly attributed to XuiTouchSettings[0x1C] (design/DoradoMotion.kt:29; docs/zune-hd-touch-settings.md)"
},
{
"id": "library-search-ui",
"surface": "Indexed library search UI",
"axis": "device",
"priority": 5,
"effort": "S",
"status": "missing",
"device_evidence": "zcontent_serv.dll content service; gemstone search",
"hd_state": "LibraryRepository/Daos search() defined with no caller (data/db/Daos.kt:68)"
},
{
"id": "hud-dimmer-status",
"surface": "HUD dimmer / power-off / status OSD",
"axis": "device",
"priority": 6,
"effort": "M",
"status": "missing",
"device_evidence": "zhud_serv.dll HudDimmerScene/HudOffSwitchScene; batteryIcon/wifiIcon/Clock/Ambient; dwells 7000/5000/3000/30000/12000ms",
"hd_state": "none"
},
{
"id": "eq-presets",
"surface": "Equalizer presets",
"axis": "device",
"priority": 7,
"effort": "M",
"status": "missing",
"device_evidence": "zmedia_serv.dll Software\\Microsoft\\Zune\\Equalizer, Preset%02d",
"hd_state": "no AudioEffect/Equalizer"
},
{
"id": "share-send",
"surface": "Share / ACTION_SEND + Zune-Card export",
"axis": "device",
"priority": 8,
"effort": "M",
"status": "missing",
"device_evidence": "gemstone.exe GemLibrarySendComposeScene, GemLibrarySendFriendsListScene",
"hd_state": "none (roadmap M5)"
},
{
"id": "audiobooks",
"surface": "Audiobook parts",
"axis": "device",
"priority": 9,
"effort": "L",
"status": "missing",
"device_evidence": "gemstone.exe GemLibraryAudiobookPartScene; AAXSDKWin.dll/audible.dll",
"hd_state": "no audiobook model"
},
{
"id": "marketplace-discovery",
"surface": "Marketplace search / top / genres / game details",
"axis": "device",
"priority": 10,
"effort": "L",
"status": "missing",
"device_evidence": "gemstone.exe GemMarketplaceSearchScene/MusicTopScene/MusicGenresContentScene/GamesDetailsScene; znet_serv.dll catalog endpoints",
"hd_state": "frozen 62-entry OfficialCatalog (ui/screens/MarketplaceScreens.kt:322)"
},
{
"id": "builtin-playlists",
"surface": "Built-in playlists",
"axis": "device",
"priority": 11,
"effort": "M",
"status": "partial",
"device_evidence": "zcontent_serv/zmedia_serv mtp:PlaylistRef_Favorite/Recent/TopArtists, BuiltIn-*",
"hd_state": "Quickplay history/pins + Top Played only"
},
{
"id": "inbox-usercard",
"surface": "Inbox + social user card",
"axis": "device",
"priority": 12,
"effort": "M",
"status": "missing",
"device_evidence": "gemstone.exe GemInboxListContentScene/GemInboxDetailsScene/GemUserCardScene",
"hd_state": "mock shells only (ui/apps/mocks/Mocks.kt:90)"
},
{
"id": "pinlock-wifi",
"surface": "PIN/screen lock + Wi-Fi info settings",
"axis": "device",
"priority": 13,
"effort": "M",
"status": "missing",
"device_evidence": "gemstone.exe GemSettingPinLockScene/GemSettingScreenLockScene/GemSettingWifiInfoScene",
"hd_state": "wake shade only (design/components/LockShade.kt)"
},
{
"id": "string-parity",
"surface": "Label/empty-state vocabulary",
"axis": "device",
"priority": 14,
"effort": "XS",
"status": "partial",
"device_evidence": "gemstone.exe 'Various Artist'@0x119A4, ' by '@0x15A60, noItems/Empty@0x14160/0x12B7C, Ffwd/Mute@0x19D18/0x19D2C",
"hd_state": "'unknown artist' (MediaLibraryScanner.kt:64), em-dash separator (Common.kt:291), bespoke empty states"
},
{
"id": "font-import",
"surface": "Zegoe import path (or drop the claim)",
"axis": "device",
"priority": 15,
"effort": "S",
"status": "missing",
"device_evidence": "assets/fonts/ZegoeUI*.ttf; registered via registry Software\\Microsoft\\Zune\\Fonts",
"hd_state": "docs promise import (zune-hd-ui-canon.md:20) but no code path; Selawik lacks Black/Medium"
},
{
"id": "velocity-cap-snap",
"surface": "Fling velocity cap + item snap + rubber-band",
"axis": "device",
"priority": 16,
"effort": "S",
"status": "missing",
"device_evidence": "xuidll.dll FUN_4184C310 cap 32.0; XuiTouchSnapToTarget@0x41833134; FUN_41848FC4 rubber-band",
"hd_state": "no cap/snap; relies on platform overscroll"
}
],
"na": [
{ "id": "na-marketplace-backend", "surface": "Marketplace backend + Passport + subscriptions", "device_evidence": "znet_serv.dll SEID_Catalog/SignInRequest/urn:passport:compact" },
{ "id": "na-drm", "surface": "DRM license / PlayReady / WMDRM", "device_evidence": "CeDRM_Mgr_*, ZWmtStreamer XDRM*, wmdrm.dll" },
{ "id": "na-wma", "surface": "WMA/WMV playback + HTTP streaming", "device_evidence": "ZWmtStreamer.dll 'Zune MarketPlace HTTP Streaming Source'" },
{ "id": "na-mtpz", "surface": "MTPZ/UPnP identity + Wi-Fi config", "device_evidence": "znet_serv /Device.xml, urn:microsoft-com:device:mtpz:1; wzcsapi.dll" },
{ "id": "na-tvout", "surface": "TV-out selector", "device_evidence": "gemstone.exe GemTvOutputSelectorScene" },
{ "id": "na-telemetry", "surface": "Usage/telemetry reporting", "device_evidence": "znet_serv usageReport, SEID_Stats/UsageData" },
{ "id": "na-acs", "surface": "ACS touch firmware update", "device_evidence": "gemstone.exe GemAcsFWScene, Acsapi_BeginFwUpdate" }
]
}
+43 -23
View File
@@ -33,7 +33,7 @@ typedef struct XuiTouchSettings { // 80 bytes, 20 x float32
| `XuiScrollEndGetDirection` | `0x41832010` | Returns the scroll-end direction. |
| `XuiTouchEnableAxisHysteresis/HorizontalAxis/VerticalAxis` | `0x418331CC` / … | Axis lock + hysteresis toggles. |
| `XuiTouchGetPositionOffset` / `XuiTouchGetPositionTarget` / `XuiTouchGetState` / `XuiTouchGetCanvasSize` | `0x4183301C` … | Scroller state getters. |
| `XuiProcessMultiTouchMessage` | — | Multi-touch dispatch. |
| `XuiProcessMultiTouchMessage` | `0x4181F100` | Multi-touch dispatch. |
The default struct is populated by `FUN_4184C660` (`xuidll.dll` VA `0x4184C660`).
@@ -95,9 +95,17 @@ remain here for a future pass if symbol data surfaces.
- **High confidence:** the existence, address, size and 20 values of
`XuiTouchSettings`; the accessor/override model; the per-offset values in §2.
- **Low confidence:** any field *name*. No `.pdb`/`.map`/`.sym` is present in
`PavoBaseline.Cab`; the Xbox 360/Windows CE XUI headers are not public.
Names are therefore intentionally omitted rather than guessed.
- **Medium confidence (recovered 2026-09-10):** the field *vocabulary*. The `xuidll`
string table (`0x41803F74–0x41804150`) exposes ~20 touch/scroll property names —
`HorzScroll`, `VertScroll`, `HorizontalFling`, `VerticalFling`,
`HorizontalScrub`, `VerticalScrub`, `HorizontalRubberBand`,
`VerticalRubberBand`, `FadeIn`, `FadeOut`, `PositionOffset`,
`CenterSmallCanvas`, `EnableBandBreak`, `KeyScrollDistance`, `SmoothScroll`,
`ScrollPaddingBefore/After`, `ZoomTarget`, `Hollow`, `Direction` — matching the
struct arity. The name→offset **mapping is not proven**, so offsets remain the
authoritative identifier.
- **Low confidence:** the exact name↔offset assignment (no `.pdb`/`.map`/`.sym`
exists in `PavoBaseline.Cab`; the XUI headers are not public).
- **Method:** `scripts/disassemble_zune_hd.py` (ROM TOC → ARM32 PE rebuild),
`llvm-objdump --triple=armv6-none-eabi`, and Ghidra headless decompilation
(`ZuneHD_Project`). Float literals resolved from the reconstructed map.
@@ -108,10 +116,12 @@ These were investigated for app parity and could not be pinned to a value from
the corpus, so **no code change was made** (a guessed value would violate the
canon-first rule):
- **HUD/overlay/volume dwell** — `zhud_serv.dll` drives its overlay timers
through imported `XuiSetTimer`/`XuiKillTimer` (indirect via the IAT) and
timeline assets rather than inline constants, so the dwell is not a
recoverable float. Dorado-HD keeps its existing timing tokens.
- **HUD/overlay/volume dwell — RECOVERED (2026-09-10).** `zhud_serv.dll` hides its
dwell values behind `XuiSetTimer` call sites, recoverable from the decompiled
timer arguments: **7000 ms** dim (`FUN_419CD420`), **5000 ms** screensaver,
**3000 ms** notifications (`FUN_419CDAC8`/`FUN_419CDF00`), **30000 ms** background
(`FUN_419C0584`), **12000 ms** constant. HD's single `IDLE_SCREENSAVER_MS = 5000`
matches the saver but has no dimmer/notification ladder.
- **Now Playing screensaver idle timeout** — no inline timeout constant is
present near `GemNowPlayingMusicShowScene`; the value arrives through the
shell's config reader (`gemstone.exe` `0x704AC`), not a literal.
@@ -122,23 +132,33 @@ canon-first rule):
not redistributed; Dorado-HD ships metric-compatible Selawik, so no token
change is warranted.
## 6. Kinetic scroll integrator (observed)
## 6. Kinetic scroll integrator (resolved 2026-09-10)
The XUI scroll tick lives at `xuidll.dll` VA `0x41841D58` (`FUN_41841D58`).
It steps a scroll object's float state each frame:
The XUI scroll tick is `xuidll.dll` VA `0x41841D58`. State (uint index → byte
offset): flags `[0x25]`=+0x94 (bit 2 = active), velocity `[0x2A]`=+0xA8,
position `[0x27]`=+0x9C, target `[0x28]`=+0xA0, step `[0x22]`=+0x88, last-tick
`[0x2E]`=+0xB8, coefficients `[0x2C]`=+0xB0 and `[0x2D]`=+0xB4 (the element's
in-place touch block at `+0xA8`, fields `+0x08`/`+0x0C`).
- velocity at `+0xA8`, position at `+0x9C`, bound/target at `+0xA0`;
- decay coefficients read from `+0xB0` and `+0xB4` (the element's in-place
`XuiTouchSettings` bytes `+0x08` and `+0x0C` — both `1.9` in the shell, the
second the only field the shell tuned down from the XUI default `1.5` to
`1.2`);
- the new velocity is clamped against `param[0x22] * coefficient` and applied
to the position.
- **Position:** `pos += (dt_ms / 1000) · v`, dt floored at **33.333 ms** (30 fps);
the divisor literal is `1000.0` (`xuidll@0x41801920`) and the floor is
`33.333` (`@0x41803CE4`).
- **Velocity:** `v' = v · (1 + c[+0xB0])`, clamped to `±( step · c[+0xB4] )`.
- **Stop:** when `pos` reaches the target the timer is killed (`XuiKillTimer`)
and the shell snap callback runs; the velocity zero-compare is `0.0`.
- **Tick:** `XuiSetTimer(elem, 1, 0x10)` = **16 ms (62.5 Hz)** (`FUN_41848B98`).
- **Drag→velocity:** `(Δpx/Δt)·scale`, capped at **32.0** (`FUN_4184C310`;
literal `@0x41804168 = 32`).
- **Rubber-band:** explicit damped spring
`-(pos−target)·k[+0xD8] − v[+0x50]·d[+0xD4] + v[+0x50]` (`FUN_41848FC4`).
This is the engine behind the `KINETIC_FRAME_RETENTION` model in
`DoradoMotion`: velocity is reduced proportionally each frame and the position
integrates until the velocity threshold is reached — proportional (exponential)
decay, no springs. Exact per-field names remain unavailable (see §4), so the
integrator is recorded here for provenance rather than turned into tokens.
**Correction:** the earlier claim that `XuiTouchSettings[0x1C]` (=0.95) is the
per-frame retention is **not supported** — the integrator reads `+0xB0/+0xB4`
(= fields `[0x08]/[0x0C]`; defaults 1.9/1.5, shell 1.9/1.2), never `[0x1C]`
(default 0.7). The device is a **dt-scaled, clamped proportional glide at 62.5 Hz
with a 30 fps floor**, not a per-frame `retention^k` model. HD's
`DoradoMotion.KINETIC_FRAME_RETENTION` (0.95) / `KINETIC_LANE_FRAME_RETENTION`
(0.94) are empirically tuned approximations of this glide and should be labelled
as such (see [`zune-hd-parity-audit.md`](zune-hd-parity-audit.md) §3).
+33 -10
View File
@@ -136,11 +136,18 @@ Ratings (tri-state heart, from the Zune desktop/HD family):
- Everything fast: Metro is "designed to feel fast and responsive".
- The device's touch/kinetic parameters are the `XuiTouchSettings` values the
shell applied (`gemstone.exe` VA `0x1C900`–`0x1CB64`, handed to
`XuiSetTouchSettings`). Dorado-HD consumes the corroborated fields:
the drag/skip deadband `25.0` (`0x10` → `DoradoTokens.SKIP_DRAG_PX`) and the
per-frame kinetic velocity retention `0.95` (`0x1C` →
`DoradoMotion.KINETIC_FRAME_RETENTION`). Full extraction:
`docs/zune-hd-touch-settings.md`.
`XuiSetTouchSettings`). Dorado-HD consumes the corroborated drag/skip deadband
`25.0` (`0x10` → `DoradoTokens.SKIP_DRAG_PX`).
- **Kinetic integrator (device, cited).** `xuidll.dll@0x41841D58` integrates
`pos += (dt_ms/1000)·v` with `dt` floored at 33.333 ms (30 fps), updates
`v' = v·(1 + c[+0xB0])` clamped to `±(step·c[+0xB4])`, and ticks at 16 ms
(62.5 Hz, `FUN_41848B98`); drag velocity is capped at 32.0 (`FUN_4184C310`).
**Correction:** the earlier attribution of HD's retention `0.95` to
`XuiTouchSettings[0x1C]` is wrong — the integrator reads `[0x08]/[0x0C]`, never
`[0x1C]`. `DoradoMotion.KINETIC_FRAME_RETENTION` (0.95) /
`…LANE_FRAME_RETENTION` (0.94) are empirical approximations of this glide.
Full extraction: `docs/zune-hd-touch-settings.md` §6; audit:
`docs/zune-hd-parity-audit.md` §3.
## 7. Banned in Dorado-HD (invariant list, mirrors Dorado)
- `RoundedCornerShape` / any nonzero corner radius on UI chrome.
@@ -199,8 +206,24 @@ and Device Link (mDNS discovery + paired LAN sync).
**Pending:** EQ presets, crossfade, live-radio pause-and-cache, richer lock-screen
art/controls, and a sleep timer — tracked in `docs/parity-roadmap.md` (M5/M6/M10).
- **Lane fling tuning.** The device applied one global kinetic retention to
every surface (`XuiTouchSettings[0x1C]` = 0.95). Dorado-HD keeps that for
vertical lists but uses a shorter retention
(`DoradoMotion.KINETIC_LANE_FRAME_RETENTION` = 0.94) for horizontal lanes,
because the device value over-glides a carousel on a modern phone.
- **Lane fling tuning.** The device's kinetic glide is `pos += (dt/1000)·v` at
62.5 Hz (`xuidll.dll@0x41841D58`; see §6). Dorado-HD approximates it with a
`KINETIC_FRAME_RETENTION` of 0.95 for vertical lists and 0.94 for horizontal
lanes (the longer retention over-glides a carousel on a modern phone). These are
empirical approximations, not device constants (`zune-hd-parity-audit.md` §3).
## 11. Device label vocabulary (cited)
Original wording recovered from the shell resource keys, recorded so display copy
can be aligned canon-first (do not change copy without updating this section):
- `"Various Artist"` (`gemstone.exe@0x119A4`) — HD currently uses `"unknown artist"`.
- `" by "` title/artist separator (`gemstone.exe@0x15A60`) — HD uses an em dash.
- Empty-state keys `noItems` / `noItems_local` / `noItems_online` / `Empty`
(`gemstone.exe@0x14160/0x148BC/0x14840/0x12B7C`) — HD uses bespoke per-screen copy.
- Transport/status words `Ffwd`, `Mute`, `Disconnected`, `Refresh`, `Wishlist`
(`gemstone.exe@0x19D18/0x19D2C/0x19D50/0x12054/0x19790`).
- Transport labels and the volume formatter also live in `zhud_serv.dll`
(`Play/Pause/Ffwd/Mute@0x419B4C58-84`, `FormatVolumeEx@0x419D7E88`).
Tracked in [`zune-hd-parity-audit.md`](zune-hd-parity-audit.md) §10.