Palette resolution

DS game only γ¨γ‚“γŒγ‚Šγƒœγ‚¦γ‚·γ¨γŠγ—γ‚ƒγ‚Œγͺまほう぀かい The field guide documents the DS game's internals.

Four-tier rule for matching an NCGR to its NCLR: sibling, container-shared, ARM9-pinned external, single-NCLR container.

NCGR tile data and NCLR palette data are separate Nitro resources. The right palette for an NCGR is found by walking these rules in order; the first match wins.

BGR555 color format

NCLR colors are 16-bit little-endian:

bits 0..4   R  (5 bits)
bits 5..9   G  (5 bits)
bits 10..14 B  (5 bits)
bit 15      ignored on hardware

R8 = (c >> 0)  & 0x1F << 3      # scale 5β†’8 by left-shifting 3
G8 = (c >> 5)  & 0x1F << 3
B8 = (c >> 10) & 0x1F << 3

Slot 0 is conventionally transparent in OBJ palettes.

Palette banks (4bpp NCGR)

A 4bpp NCGR uses 16-color palettes β€” but an NCLR can hold many such banks (up to 16 banks Γ— 16 colors = 256 entries). The selection of which bank applies to a given tile is encoded:

When an NCLR has 2+ sections, the PMCP section maps each local bank index to a VRAM bank index. Without PMCP, banks are assumed sequential 0..N-1.

Four-tier resolution rule

Tier 1: Sibling NCLR in same RESO

Within a container, the NCLR adjacent to the NCGR (in TOC index order, immediately following the NCGR or preceding the next NCGR sentinel) is the sibling palette. Implemented by group_siblings() in src/translator/_sprite_assoc.py. Covers the NCGR/NCLR/NCER triplet pattern.

Tier 2: Container-wide shared NCLR

When a container batches several NCGRs at the start with no per-NCGR NCLR, the container's first NCLR serves all of them. Used by buyitem.lzs and similar shop containers (5 NCGRs + 3 shared NCLRs + 6 NSCRs).

Tier 3: ARM9-pinned external NCLR

The palette lives in a different container, pinned by ARM9 loader code. Empirically resolved mappings:

Source container (NCGRs)NCGR countbppPalette container + entryNotes
item/itemicon.ofs6568bpp global.lzs RESO entry 5 256-color. Entry 6 = grayed-out variant.
2d/inputmagic/mgcall.ofs3894bpp 2d/inputmagic/mgc80.ofs entry 0 16-color. Entry 2 = alt scheme.
2d/inputmagic/mgc01..09c.lzs823 total4bpp 2d/inputmagic/mgc80.ofs entry 0 Shared with mgcall.

Tier 4: Single-NCLR container

When a container has exactly one NCLR (e.g. trendranking/npcicon.ofs β€” 141 NCGRs + 1 NCLR), that single NCLR is the shared palette for every NCGR. The sibling heuristic alone misses this because the NCLR isn't grouped adjacent.

Fallback

Render with a grayscale palette. Used as a last resort.

Resolution stats

From notes/palette_association.json (4,337 total NCGR groups):

Render kindCount
NSCR BG screen1,099
NCER cell composition926
External magic glyph823
External item icon656
Single sprite466
Tilebank-only296
Orphan (no palette resolved)469

Of the remaining 469 orphan NCGRs, the bulk are in: 2d/signboard.lzs (80), 2d/designlogo.lzs (74), 2d/designcloth.lzs (58), 2d/selmaterial.lzs (52), 2d/expect.ofs (51), 2d/buyitem.lzs (31, shop bg variants).

ARM9 external-palette evidence (item icons)

The 256-color OBJ palette for item/itemicon.ofs lives in global.lzs, RESO entries 5 (main) and 6 (greyed-out). Each NCLR is a 570-byte chunk with bpp_code=4 (8bpp = 256 colors), palette_data_size=512. The loader is the generic asset-loader at 0x02052D20:

02052D4C  LDR  R0, [PC, #0x30]      ; r0 = 0x020AA740  (item-list ptr)
02052D4E  LDR  R1, [PC, #0x34]      ; r1 = 0x020A55D8  ("item/itemicon.ofs")
02052D50  LSLS R3, R3, #0xA          ; r3 = 0x7800     (flags)
02052D52  BLX  0x02009894            ; call generic file-loader
                                     ; r2 = u16 sub-index from caller's [r5+0x84]

No static (icon_index β†’ palette_index) table exists because none is needed: every item icon uses the same global OBJ palette uploaded once at boot from global.lzs.

Discovering external mappings

Recipe used when palette mapping is unknown:

  1. Determine NCGR bpp distribution in the container.
  2. If 4bpp β†’ palette is 16-color (32 bytes + header). If 8bpp β†’ 256-color (512 bytes + header).
  3. Histogram palette indices used across all NCGRs in the container. Clustering in one half of the index range constrains which slots must be populated.
  4. Filter candidate NCLRs by color count and visual content (slot 0 is often a saturated transparency key).
  5. Score each candidate by color variety of actually-used indices when rendered.

Source