How it works · write-up
Shop screen palette
Same class of bug, same class of fix, as the interstitial/title-screen palette problem (see
XENON2.MD §6.2/§8 and SpriteAtlasExporter.UsesInterstitialPalette's own comment): the sprite
atlas bakes every sprite to one static RGBA snapshot at pack time, using whichever ST palette
happened to be live when the "Masked Sprite" debug window was first opened — normally during
gameplay. The shop screen ("Colin's Bargain Basement", the post-level-1 buy/sell panel) uses a
different palette once its fade-in completes, so its chrome/hand/alien sprites were being baked
with the wrong colors.
Where the shop's palette comes from
ShopScreen_InitAndOpen_FUN_00010520 calls palette_fade_FUN_000088ea(&DAT_0001fb72) (confirmed
via ReVa disassembly of the call site) to fade the screen in toward a fixed 16-word table at ST
address 0x0001FB72. This is ordinary static data compiled into the game's own binary — not a
runtime scratch variable — so, exactly like the interstitial screens' 0x86FA table, it's present
and readable in ST RAM at all times, regardless of which screen is currently showing at capture
time. No live visit to the shop is needed before capturing.
palette_fade_FUN_000088ea fades from black up through $666,$555,...,$000 steps toward this
table (per its own header comment, A2/A1 = pointer to the 16-colour source palette,
destination $FFFF8240 = the ST hardware palette registers) — i.e. this is a genuine, standard ST
fade-in, and the table is the settled end state once the fade completes.
Raw table (0x0001FB72, 16 words, 0x0RGB format, 0-7 per channel)
| Reg | Raw word | R | G | B |
|---|---|---|---|---|
| 0 | 0x0000 |
0 | 0 | 0 |
| 1 | 0x0753 |
7 | 5 | 3 |
| 2 | 0x0742 |
7 | 4 | 2 |
| 3 | 0x0632 |
6 | 3 | 2 |
| 4 | 0x0223 |
2 | 2 | 3 |
| 5 | 0x0334 |
3 | 3 | 4 |
| 6 | 0x0445 |
4 | 4 | 5 |
| 7 | 0x0767 |
7 | 6 | 7 |
| 8 | 0x0556 |
5 | 5 | 6 |
| 9 | 0x0112 |
1 | 1 | 2 |
| 10 | 0x0001 |
0 | 0 | 1 |
| 11 | 0x0200 |
2 | 0 | 0 |
| 12 | 0x0300 |
3 | 0 | 0 |
| 13 | 0x0410 |
4 | 1 | 0 |
| 14 | 0x0750 |
7 | 5 | 0 |
| 15 | 0x0521 |
5 | 2 | 1 |
Comparison against the gameplay palette
Decoded a real gameplay capture (assets/palette.bin, Hatari's own STRGBPalette snapshot) back
into the same 0-7-per-channel form (each byte in that file is a clean multiple of 0x22, i.e.
byte = channelValue * 0x22 — confirmed empirically from the dump, not assumed) and compared
index by index against the table above:
Registers 4-15 are byte-for-byte identical between the shop and gameplay palettes. Only registers 0-3 differ, and register 0 is black in both — so really just 1, 2, and 3 are genuinely redefined:
| Reg | Gameplay (R,G,B) | Shop (R,G,B) | Same? |
|---|---|---|---|
| 0 | 0,0,0 | 0,0,0 | yes |
| 1 | 1,1,1 | 7,5,3 | no |
| 2 | 3,2,1 | 7,4,2 | no |
| 3 | 6,3,1 | 6,3,2 | no (blue channel only, off by 1) |
| 4-15 | (12 entries) | (same 12 entries) | yes, all 12 |
This lines up with what's visible on screen: registers 1-3 are exactly the tones used for the
shop's chrome/border/text elements (the warm tan/orange-brown "orange dashes on grey metal panel"
look in the screenshots), while registers 4-15 — the wall-tile/sprite/enemy palette shared with
gameplay — are untouched. That also explains why the alien/hand sprites (ordinary SpriteData
format, same as gameplay objects, and drawn with colors mostly in the 4-15 range) looked
reasonably close to correct even before this fix, while the chrome specifically did not.
The fix
Mirrors InterstitialPaletteDump_SaveToFile/UsesInterstitialPalette exactly:
src/xenonRender.c:ShopPaletteDump_SaveToFilereads the 16 words at0x1FB72, converts each throughST2RGB[](the same live conversion tableSTRGBPaletteitself is built from), and writesassets/palette_shop.bin. Declared insrc/includes/screentrace.h.src/screentrace.c: called from the same one-shot "Masked Sprite" debug-window trigger (DebugWindow_UpdateFromSTLowResBase) aspalette.bin/palette_interstitial.bin— no extra manual step needed.hatari_dotnet/Program.cs: readspalette_shop.binalongside the other two, falling back to the main palette if the file is missing (pre-fix capture).hatari_dotnet/SpriteAtlasExporter.cs:UsesShopPalette(stAddress)gates on a single address range,0x00012640-0x0001F412(exclusive) — confirmed contiguous with zero gaps across every shop-specific region (the "shop sprites"SpriteDatawalk from0x12640, theUNMASKED_CUSTOMchrome pieces from0x181AA, and the raw96 alien head/door blocks through0x1EF12), stopping short of the shared 4x8 font at0x1F412.SelectPalettenow picks interstitial / shop / main palette per record, in that priority order.
Same caveat as the interstitial fix: this is one static snapshot of the settled post-fade-in palette, not a live per-frame reproduction — though unlike the interstitial screens, the shop's own fade is confirmed to settle (not continuously cycle), so a single snapshot is the actual correct steady-state value here, not just "less wrong than gameplay's."