Composition pipelines

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

Three strategies the engine uses to turn Nitro resources into screen pixels: NSCR BG, NCER OBJ, tad-driven widget table.

Strategy A: NSCR-driven BG screen

Inputs: NCGR tilebank (8Γ—8 tiles) + NCLR palette + NSCR screen map. Output: 256Γ—192 BG image (32Γ—24 cells).

     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
NCLR β”‚ palette  │──────┐
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β–Ό
NCGR β”‚ tilebank │──┐  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  └─►│ compose_nscr_screen│──► 256Γ—192 RGBA
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β–Ίβ”‚  for each cell:    β”‚
NSCR β”‚ screen   β”‚β”€β”€β”˜  β”‚   tile_idx (10b)   β”‚
     β”‚ (32Γ—24)  β”‚     β”‚   palette_bank(4b) β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚   hflip / vflip    β”‚
                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
# src/translator/_sprite_decode.py::compose_nscr_screen
for cell_y in range(24):
    for cell_x in range(32):
        word = nscr.cells[cell_y * 32 + cell_x]
        tile = word & 0x3FF
        hflip = (word >> 10) & 1
        vflip = (word >> 11) & 1
        bank  = (word >> 12) & 0xF
        tile_pixels = ncgr.tile(tile)              # 8Γ—8 indices
        if hflip: tile_pixels = flip_x(tile_pixels)
        if vflip: tile_pixels = flip_y(tile_pixels)
        for py in range(8):
            for px in range(8):
                idx = tile_pixels[py][px]
                color = nclr.bank(bank)[idx]
                out[cell_y*8 + py][cell_x*8 + px] = color

Used by 1,099 NSCR-bearing containers. Examples: buyitem.lzs shop chrome, title_pack.ofs castle art, every map-screen overlay.

Strategy B: NCER-driven OBJ sprite

Inputs: NCGR tilebank + NCLR palette + NCER cell definition. Output: one or more sprite cells, each composed of multiple OAM "chips."

     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
NCLR β”‚ palette  │──────────┐
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β–Ό
NCGR β”‚ tilebank │──┐ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β–Ίβ”‚ compose_ncer_cell   │──► per-cell PNG
                    β”‚   for each OBJ:     β”‚
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚     (x,y,w,h,tile,  β”‚
NCER β”‚ cells +  │──►│      palette,flip)  β”‚
     β”‚ OAM list β”‚   β”‚     blit into canvasβ”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Each cell holds obj_count OAM entries; per OBJ the (shape, size) pair selects pixel dimensions via the standard NDS lookup:

size β†’0123
shape 00 (square)8Γ—816Γ—1632Γ—3264Γ—64
shape 01 (wide)16Γ—832Γ—832Γ—1664Γ—32
shape 10 (tall)8Γ—168Γ—3216Γ—3232Γ—64

Used by 926 NCER-bearing containers.

Strategy C: tad widget table

Inputs: NCER cell (selected by cell_id) + NCLR + the tad/widget-table record (palette, sub, flags, x, y). Output: composed UI screen at the abs_x/abs_y of each record.

The table can sit in two places:

Either way the engine walks the table, calls a sprite-constructor for each entry, and the constructor resolves the asset bytes from the content cache. See sprite pipeline for the runtime side.

50 containers ship with a static recipe: 37 with a rnan-only table, 8 with tad, 5 with nib.

Strategy D: code-driven (no static recipe)

123 NCER-bearing containers ship without any static placement table. The composition logic is hard-coded in the controller/body overlay pair. Examples:

Runtime shadow-OAM

The engine never writes OAM RAM (0x07000000 / 0x07000400) directly during scene composition. It builds a shadow OAM in main RAM via STR/STRH at sprite-slot addresses computed from *0x021907BC + i*0x40 + field_offset chains. At VBlank, FUN_02185BF8 (ov137) and FUN_0207001C (arm9) DMA the shadow buffer into hardware OAM.

OAM/VRAM transfer wrappers (all four resolve to FUN_02017D9C = DMA0):

AddressRoleDestination
0x020769C0OAM main0x07000000 + dst_off
0x02076A00OAM sub0x07000400 + dst_off
0x02076A40OBJ VRAM main0x06400000 + dst_off
0x02076A7COBJ VRAM sub0x06600000 + dst_off

Source