================================================================
修改圖片的操作方法
—— 更換「選角畫面」的兩個圖形：①大頭像（左上）②24×24 圖標（游標格）
================================================================
本文件「自帶完整腳本」：只要有這個 ROM＋兩張圖片＋本檔案，就能完成。
把 2.4 與 3.4 的腳本各自複製成 .ps1 檔即可執行（或直接用已附的
Z:\s9\replace-portrait.ps1、Z:\s9\replace-icon.ps1）。

0. 適用 ROM 與執行環境
----------------------------------------------------------------
- ROM：Z:\s9\1\sfz2-shin-v42.smc
  大小 12,583,424 bytes（= 0xC00000 + 0x200 header）
  SHA-256：A82C4C4F96B3C129CD5BEA009A99869FBC3BE0349C67A8675322F24B50EF7D8B
- 這個 ROM 是「SFA2 美版 v3.0.3 + S-DD1 解壓縮改造」的 12MB ROM。
- 只改「資料」，不要改程式碼；所有資料都用「覆蓋」方式寫入，不可插入/刪除位元組，
  檔案長度必須保持 12,583,424 bytes。
- 改之前先備份 ROM，例如：Copy-Item ROM ROM.v1備份20261108
- 環境：Windows + PowerShell（Windows PowerShell 5.1 或 PowerShell 7 皆可，
  需要 .NET 的 System.Drawing；僅 Windows 可用）。
- 若「執行原則」擋住腳本，用其中一種方式：
    powershell -ExecutionPolicy Bypass -File .\replace-portrait.ps1 -Rom ... -Png ... -Dst ...
  或先執行：Set-ExecutionPolicy -Scope Process Bypass

1. 影像格式（兩個圖形共用）
----------------------------------------------------------------
1.1 SNES 4bpp tile（8×8 像素，32 bytes/tile）
    每個 tile 的 32 bytes 排列：
      第 0..15 bytes：row0..row7 的 plane0/plane1 交錯（每 row 2 bytes）
      第 16..31 bytes：row0..row7 的 plane2/plane3 交錯（每 row 2 bytes）
    某一 row 的 4 個 byte：
      b0 = base + y*2        （plane0）
      b1 = base + y*2 + 1    （plane1）
      b2 = base + 16 + y*2   （plane2）
      b3 = base + 16 + y*2+1 （plane3）
    pixel x（0..7，左到右）的顏色索引：
      c = ((b0>>(7-x))&1) | ((b1>>(7-x))&1)<<1 |
          ((b2>>(7-x))&1)<<2 | ((b3>>(7-x))&1)<<3
    → 顏色索引 0..15；索引 0 = 透明（不畫）。

1.2 調色盤：16 色 × 2 bytes = 32 bytes，BGR555 小端序
      word = (b5<<10) | (g5<<5) | r5
      每個分量 0..31（8-bit 值換算：v5 = round(v8 * 31 / 255)）
      索引 0 的顏色無意義（透明），通常寫 0x0000。

1.3 顯示尺寸/位置（v42 實測）
      大頭像：asset 128×128，畫面實際顯示 96×112，左上角在畫面 (0,24)
      24×24 圖標：畫面 (69,174)（3×3 個 8×8 sprite，X=69/77/85、Y=174/182/190）

1.4 圖片準備（重點）
      - PNG，32-bit RGBA，透明背景（alpha < 128 會被當成透明）。
      - 大頭像：建議直接做一張 96×112 的圖（就是畫面看到的大小）。
        腳本對「非 96×112」的圖會：預設「等比例縮小、放進 96×112 的左上角」
        （不變形、不裁切）；加 -Stretch 則強制拉滿 96×112（可能變形）。
      - 圖標：建議直接做 24×24；其他尺寸會等比例縮放後置中。
      - 兩張圖都建議先減色到 16 色（含透明），避免腳本自動併色造成色偏。

2. ① 大頭像（左上，96×112）
----------------------------------------------------------------
2.1 位置與大小
    - tiles 本體：檔案 0x276200，長度 8192 bytes（128×128，16×16 個 tile）
      ※ 遊戲在 col0 會從 bank $4E:$E000（=此檔案位置）以 DMA 載入；
        本 ROM 的 cave 會驗證/分段補上（見 2.5）。
    - 調色盤：檔案 0x6D5440，長度 32 bytes
      （= $DA:D240；cave 的 palette routine 每 frame 把它寫到 CGRAM 0x60-0x6F）
    - 透明遮罩來源（原版 Akuma，不可修改）：檔案 0x86A200，8192 bytes
      （只讀它來當「底部切齊」的遮罩）

2.2 圖片需求
    - PNG，透明背景；建議 96×112（就是畫面看到的大小）。
    - 顏色最多 15 色（索引 0 保留給透明）。
    - 人物位置/大小建議與原本 Akuma 相近，否則底部遮罩可能會切到圖。

2.3 處理流程
    1) 讀入 PNG → 正規化成 128×128（可見區 = 左上 96×112）。
    2) 轉成顏色索引表（透明=0，其餘 1..15）；超過 15 色自動併到最接近色。
    3) 套用「原版 Akuma 的透明遮罩」（只套 asset rows 40..127）→ 底部跟原版
       Akuma 一樣斜切；rows 0..39（頭髮）不動。
    4) 編碼成 256 個 4bpp tile（8192 bytes）→ 寫到 0x276200。
    5) 15 色調色盤（+索引0）以 BGR555 寫到 0x6D5440（32 bytes）。

2.4 完整腳本（複製成 Z:\s9\replace-portrait.ps1）
----------------------------------------------------------------
param(
    [Parameter(Mandatory=$true)][string]$Rom,
    [Parameter(Mandatory=$true)][string]$Png,
    [string]$Dst = '',
    [int]$MaskFromY = 40,
    [switch]$NoPalette,
    [switch]$Stretch
)
Add-Type -AssemblyName System.Drawing
$ErrorActionPreference = 'Stop'
if ($Dst -eq '') { $Dst = $Rom }
$b = [IO.File]::ReadAllBytes($Rom)
if ($b.Length -ne 12583424) { throw "ROM size is not 12583424" }

# --- 1) load image and normalize to 128x128 (visible area = top-left 96x112) ---
$src = [System.Drawing.Bitmap]::FromFile($Png)
$work = New-Object System.Drawing.Bitmap 128,128
$g = [System.Drawing.Graphics]::FromImage($work)
$g.Clear([System.Drawing.Color]::Transparent)
$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
# visible area = 96x112 at the asset top-left.
# -Stretch: force fill 96x112 (may distort). default: keep aspect ratio, fit inside.
if ($Stretch) {
    $g.DrawImage($src, 0, 0, 96, 112)
} elseif ($src.Width -eq 96 -and $src.Height -eq 112) {
    $g.DrawImage($src, 0, 0, 96, 112)
} else {
    $sc = [Math]::Min(96.0 / $src.Width, 112.0 / $src.Height)
    $dw = [int][Math]::Round($src.Width * $sc)
    $dh = [int][Math]::Round($src.Height * $sc)
    $g.DrawImage($src, 0, 0, $dw, $dh)
}
$g.Dispose()
$src.Dispose()

# --- 2) build the 128x128 colour-index map (alpha<128 => 0, other => 1..15) ---
$idx = New-Object int[] 16384
$pal = New-Object System.Collections.ArrayList
$map = @{}
for ($y = 0; $y -lt 128; $y++) {
    for ($x = 0; $x -lt 128; $x++) {
        $c = $work.GetPixel($x, $y)
        if ($c.A -lt 128) { $idx[$y*128+$x] = 0; continue }
        $r5 = [int][Math]::Round($c.R * 31.0 / 255.0)
        $g5 = [int][Math]::Round($c.G * 31.0 / 255.0)
        $b5 = [int][Math]::Round($c.B * 31.0 / 255.0)
        $key = "$r5,$g5,$b5"
        if (-not $map.ContainsKey($key)) {
            if ($pal.Count -lt 15) {
                $map[$key] = $pal.Count + 1
                [void]$pal.Add(@($r5,$g5,$b5))
            } else {
                $best = 1; $bd = 999999
                for ($i = 0; $i -lt $pal.Count; $i++) {
                    $dr = $pal[$i][0]-$r5; $dg = $pal[$i][1]-$g5; $db = $pal[$i][2]-$b5
                    $d = $dr*$dr + $dg*$dg + $db*$db
                    if ($d -lt $bd) { $bd = $d; $best = $i + 1 }
                }
                $map[$key] = $best
            }
        }
        $idx[$y*128+$x] = $map[$key]
    }
}
$work.Dispose()

# --- 3) apply the original Akuma transparency mask (rows MaskFromY..127) ---
function Decode4bpp([byte[]]$rom, [int]$off) {
    $a = New-Object int[] 16384
    for ($t = 0; $t -lt 256; $t++) {
        $tx = ($t % 16) * 8
        $ty = [int][Math]::Floor($t / 16) * 8
        $base = $off + $t * 32
        for ($y = 0; $y -lt 8; $y++) {
            $b0 = $rom[$base + $y*2]; $b1 = $rom[$base + $y*2 + 1]
            $b2 = $rom[$base + 16 + $y*2]; $b3 = $rom[$base + 16 + $y*2 + 1]
            for ($x = 0; $x -lt 8; $x++) {
                $bit = 7 - $x
                $a[($ty+$y)*128 + ($tx+$x)] = (($b0 -shr $bit) -band 1) -bor
                    ((($b1 -shr $bit) -band 1) -shl 1) -bor
                    ((($b2 -shr $bit) -band 1) -shl 2) -bor
                    ((($b3 -shr $bit) -band 1) -shl 3)
            }
        }
    }
    return $a
}
$orig = Decode4bpp $b 0x86A200
for ($y = $MaskFromY; $y -lt 128; $y++) {
    for ($x = 0; $x -lt 128; $x++) {
        if ($orig[$y*128+$x] -eq 0) { $idx[$y*128+$x] = 0 }
    }
}

# --- 4) encode 256 4bpp tiles to 0x276200 ---
for ($t = 0; $t -lt 256; $t++) {
    $tx = ($t % 16) * 8
    $ty = [int][Math]::Floor($t / 16) * 8
    $base = 0x276200 + $t * 32
    for ($y = 0; $y -lt 8; $y++) {
        $b0=0; $b1=0; $b2=0; $b3=0
        for ($x = 0; $x -lt 8; $x++) {
            $c = $idx[($ty+$y)*128 + ($tx+$x)]
            $bit = 7 - $x
            if ($c -band 1) { $b0 = $b0 -bor (1 -shl $bit) }
            if ($c -band 2) { $b1 = $b1 -bor (1 -shl $bit) }
            if ($c -band 4) { $b2 = $b2 -bor (1 -shl $bit) }
            if ($c -band 8) { $b3 = $b3 -bor (1 -shl $bit) }
        }
        $b[$base + $y*2] = $b0
        $b[$base + $y*2 + 1] = $b1
        $b[$base + 16 + $y*2] = $b2
        $b[$base + 16 + $y*2 + 1] = $b3
    }
}

# --- 5) palette to 0x6D5440 (CGRAM 0x60) ---
if (-not $NoPalette) {
    $po = 0x6D5440
    for ($i = 0; $i -lt 16; $i++) {
        if ($i -eq 0) { $w = 0 }
        elseif ($i -le $pal.Count) {
            $p = $pal[$i-1]
            $w = ($p[2] -shl 10) -bor ($p[1] -shl 5) -bor $p[0]
        } else { $w = 0 }
        $b[$po + $i*2] = $w -band 0xFF
        $b[$po + $i*2 + 1] = ($w -shr 8) -band 0xFF
    }
}

[IO.File]::WriteAllBytes($Dst, $b)
Write-Output "written: $Dst"
Write-Output ("colors used: {0}" -f $pal.Count)
Write-Output ("SHA-256: {0}" -f (Get-FileHash $Dst -Algorithm SHA256).Hash)
----------------------------------------------------------------

執行例：
  cd Z:\s9
  .\replace-portrait.ps1 -Rom Z:\s9\1\sfz2-shin-v42.smc -Png Z:\akuma.png -Dst Z:\s9\1\test-portrait.smc
  要強制拉滿（可能變形）：加 -Stretch
  不改調色盤（沿用舊色）：加 -NoPalette
（此腳本已實測：寫入後 VRAM 0x2000 與 ROM 0x276200 為 8192/8192）

2.5 驗證
    - 用 Snes9x 開 test-portrait.smc，從標題進選角（不要載入舊存檔/state）。
    - 游標移到 col0（左下角），確認大頭像正確；離開再回來看是否仍正確。
    - harness（本專案）：執行 Z:\s9\1 的 runtest.ps1，腳本用 v42_verify.txt
      （快照 z:\bin\Saves\sfa2-usa-nochip-v3.0.3.000），會 middump 到
      z:\s9\1\testout\mid_v_c0a_vram.bin ...，再比對 VRAM 0x2000 與 ROM 0x276200
      應為 8192/8192；col1（mid_v_c1）應為原版 0x86A200 8192/8192。
    - cave 的分段上傳：進 col0 後約 4 frame 內會分 4 段（每段 2048 bytes）補上，
      所以「一進去」的前 1~4 frame 可能還是原圖，這是正常現象。

2.6 注意
    - 這 8192 bytes 就是遊戲在 col0 會載入的圖（bank $4E:$E000）。
      cave 的「探針」是直接比對 ROM 本身，所以換圖後不需要改任何程式碼。
    - 不要把 0x276200 的長度改成別的（固定 8192）。
    - 不要動 0x86A200（原版 Akuma；遮罩來源，也是 col1 的大頭像）。
    - 若不想套底部遮罩（要完整矩形），把 -MaskFromY 設 128。

3. ② 24×24 圖標（游標格右上角的小圖，畫面 (69,174)）
----------------------------------------------------------------
3.1 位置與大小
    - tiles：檔案 0x6D5200，長度 288 bytes（3×3 個 tile，24×24）
      上傳到 VRAM word $6D00（byte $DA00）
    - 調色盤：檔案 0x6D5320，長度 32 bytes（16 色 BGR555）
      上傳到 CGRAM 0xD0-0xDF
    - OAM：檔案 0x6D5340，長度 36 bytes（9 個 sprite × 4 bytes）
      格式：[X][Y][tile][attr]，目前值：
        45 AE D0 3A   4D AE D1 3A   55 AE D2 3A
        45 B6 D3 3A   4D B6 D4 3A   55 B6 D5 3A
        45 BE D6 3A   4D BE D7 3A   55 BE D8 3A
      → X=0x45/0x4D/0x55（69/77/85）、Y=0xAE/0xB6/0xBE（174/182/190）、
        tile=0xD0..0xD8、attr=0x3A
    - tile 順序：D0=左上、D1=中上、D2=右上、D3=中左、…、D8=右下
      （腳本已照這個順序編碼，不用手動排）
    - 顯示時機：只在 col0（本 ROM 把左下角那格做成真豪鬼格）顯示；
      離開時 cave 會把 sprite 119–127 的 Y 設成 0xF0 隱藏。
    - 24×24 圖標不需要改 OAM（除非要改大小/位置）。

3.2 圖片需求
    - PNG，透明背景，最多 15 色（索引 0 = 透明）。
    - 建議直接做 24×24；其他尺寸會等比例縮放後置中。
    - 檔案目前只有 24×24 的空間（288 bytes）。

3.3 處理流程
    1) 讀入 PNG → 正規化成 24×24。
    2) 顏色索引（透明=0，其餘 1..15）。
    3) 3×3 個 8×8 tile → 288 bytes → 寫到 0x6D5200。
    4) 15 色調色盤（BGR555）+索引0 → 32 bytes → 寫到 0x6D5320。
    5) OAM 不動（0x6D5340 保持原值）。

3.4 完整腳本（複製成 Z:\s9\replace-icon.ps1）
----------------------------------------------------------------
param(
    [Parameter(Mandatory=$true)][string]$Rom,
    [Parameter(Mandatory=$true)][string]$Png,
    [string]$Dst = ''
)
Add-Type -AssemblyName System.Drawing
$ErrorActionPreference = 'Stop'
if ($Dst -eq '') { $Dst = $Rom }
$b = [IO.File]::ReadAllBytes($Rom)
if ($b.Length -ne 12583424) { throw "ROM size is not 12583424" }

# --- 1) load image and normalize to 24x24 ---
$src = [System.Drawing.Bitmap]::FromFile($Png)
$work = New-Object System.Drawing.Bitmap 24,24
$g = [System.Drawing.Graphics]::FromImage($work)
$g.Clear([System.Drawing.Color]::Transparent)
$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
$g.DrawImage($src, 0, 0, 24, 24)
$g.Dispose()
$src.Dispose()

# --- 2) colour-index map (alpha<128 => 0, other => 1..15) ---
$idx = New-Object int[] 576
$pal = New-Object System.Collections.ArrayList
$map = @{}
for ($y = 0; $y -lt 24; $y++) {
    for ($x = 0; $x -lt 24; $x++) {
        $c = $work.GetPixel($x, $y)
        if ($c.A -lt 128) { $idx[$y*24+$x] = 0; continue }
        $r5 = [int][Math]::Round($c.R * 31.0 / 255.0)
        $g5 = [int][Math]::Round($c.G * 31.0 / 255.0)
        $b5 = [int][Math]::Round($c.B * 31.0 / 255.0)
        $key = "$r5,$g5,$b5"
        if (-not $map.ContainsKey($key)) {
            if ($pal.Count -lt 15) { $map[$key] = $pal.Count + 1; [void]$pal.Add(@($r5,$g5,$b5)) }
            else {
                $best = 1; $bd = 999999
                for ($i = 0; $i -lt $pal.Count; $i++) {
                    $dr = $pal[$i][0]-$r5; $dg = $pal[$i][1]-$g5; $db = $pal[$i][2]-$b5
                    $d = $dr*$dr + $dg*$dg + $db*$db
                    if ($d -lt $bd) { $bd = $d; $best = $i + 1 }
                }
                $map[$key] = $best
            }
        }
        $idx[$y*24+$x] = $map[$key]
    }
}
$work.Dispose()

# --- 3) 9 8x8 tiles (top-left -> right, then down) to 0x6D5200 ---
for ($t = 0; $t -lt 9; $t++) {
    $tx = ($t % 3) * 8
    $ty = [int][Math]::Floor($t / 3) * 8
    $base = 0x6D5200 + $t * 32
    for ($y = 0; $y -lt 8; $y++) {
        $b0=0; $b1=0; $b2=0; $b3=0
        for ($x = 0; $x -lt 8; $x++) {
            $c = $idx[($ty+$y)*24 + ($tx+$x)]
            $bit = 7 - $x
            if ($c -band 1) { $b0 = $b0 -bor (1 -shl $bit) }
            if ($c -band 2) { $b1 = $b1 -bor (1 -shl $bit) }
            if ($c -band 4) { $b2 = $b2 -bor (1 -shl $bit) }
            if ($c -band 8) { $b3 = $b3 -bor (1 -shl $bit) }
        }
        $b[$base + $y*2] = $b0
        $b[$base + $y*2 + 1] = $b1
        $b[$base + 16 + $y*2] = $b2
        $b[$base + 16 + $y*2 + 1] = $b3
    }
}

# --- 4) palette to 0x6D5320 (CGRAM 0xD0) ---
$po = 0x6D5320
for ($i = 0; $i -lt 16; $i++) {
    if ($i -eq 0) { $w = 0 }
    elseif ($i -le $pal.Count) { $p = $pal[$i-1]; $w = ($p[2] -shl 10) -bor ($p[1] -shl 5) -bor $p[0] }
    else { $w = 0 }
    $b[$po + $i*2] = $w -band 0xFF
    $b[$po + $i*2 + 1] = ($w -shr 8) -band 0xFF
}

[IO.File]::WriteAllBytes($Dst, $b)
Write-Output "written: $Dst"
Write-Output ("colors used: {0}" -f $pal.Count)
Write-Output ("SHA-256: {0}" -f (Get-FileHash $Dst -Algorithm SHA256).Hash)
----------------------------------------------------------------

執行例：
  cd Z:\s9
  .\replace-icon.ps1 -Rom Z:\s9\1\sfz2-shin-v42.smc -Png Z:\123_24.png -Dst Z:\s9\1\test-icon.smc
（此腳本已實測：VRAM 0xDA00 與 ROM 0x6D5200 為 288/288、
  CGRAM 0xD0 與 0x6D5320 為 32/32）

3.5 驗證
    - 進選角、游標移到 col0，看圖標是否正確（位置固定在畫面 (69,174)）。
    - harness：middump 後比對 VRAM 0xDA00 與 ROM 0x6D5200（288 bytes）應一致；
      OAM sprite 119 應為 X=0x45、Y=0xAE、tile=0xD0、attr=0x3A。
    - 圖標每次進 col0 只上傳一次；離開 col0 會隱藏（Y=0xF0）。

3.6 若要改圖標大小/位置
    - 24×24 以外的大小需要同時改：
      * OAM（0x6D5340，每個 sprite 4 bytes；目前 9 個 = 3×3）
      * tile 數量（0x6D5200 的 288 bytes 是 9 個 tile；更多 tile 需要找空白區放）
      * cave 的上傳常式（0x6D50F7 附近的 tile 迴圈 count=0x0120、
        0x6D5125 附近的 OAM 複製 count=0x24）→ 這屬於「改程式碼」，不建議。
    - 建議維持 24×24。

4. 共同注意事項（重要）
----------------------------------------------------------------
1. 改之前一定先備份 ROM。
2. 只覆蓋資料，不可改變檔案長度（12,583,424）。
3. 不要動這些位址（程式碼/其他資產）：
   - 0x86A200（原版 Akuma 大頭像；col1 用、也是遮罩來源）
   - 0x6D50C5 之後的 cave 程式碼（除非你很確定）
   - 0x1F0260（OBJ palette 7 共用表）
4. 換圖後「不需要」改任何程式碼：cave 的探針/上傳都是讀 ROM 本身。
5. 測試請「從標題重新進選角」，不要載入舊存檔或舊 state（會蓋掉 VRAM/WRAM）。
6. 大頭像進 col0 後約 4 frame 內分段補上（正常）。
7. 兩支腳本可以「先後套用在同一份 ROM」：先換圖、再把輸出當成下一次的 -Rom。
   （記得每一步都輸出到新檔，保留原始 ROM）
8. 圖片建議先用 16 色（含透明）減色，避免腳本自動併色造成色偏。
9. 若用管線（| Select-Object）執行腳本，注意不要讓腳本提早結束
   （曾發生 WriteAllBytes 沒執行、ROM 不存在 → Snes9x access violation）。

5. 常見問題（Troubleshooting）
----------------------------------------------------------------
Q1. 進遊戲後圖沒變？
    → 確認 ROM 是修改後的那份（看 SHA-256）；確認是「從標題重新進選角」，
      不是載入舊存檔/state；大頭像要等進 col0 後約 4 frame。
Q2. 大頭像顏色不對/怪色？
    → 調色盤沒更新或被覆蓋。確認沒有加 -NoPalette，且寫入位置 0x6D5440。
Q3. 大頭像底部被切掉或形狀怪？
    → 那是「底部遮罩」（rows 40..127）。要完整矩形請加 -MaskFromY 128；
      要調整切多少，改 -MaskFromY（例如 60 只切更下面）。
Q4. 圖標位置不對？
    → 圖標位置是 OAM（0x6D5340）固定的，換圖不會動位置。要改位置改 OAM 的 X/Y。
Q5. 圖片變形？
    → 預設會等比例縮小；要拉滿 96×112 請加 -Stretch（可能變形）。
Q6. 執行腳本被系統擋住？
    → 用 powershell -ExecutionPolicy Bypass -File ... 或
      Set-ExecutionPolicy -Scope Process Bypass。
Q7. 畫面出現 1 frame 黑？
    → 那是原版遊戲在某些格（例如 SAKURA）本來就有的行為（已用未修改 ROM 驗證），
      與換圖無關。

6. 快速檢查清單（做完打勾）
----------------------------------------------------------------
[ ] 已備份 ROM
[ ] 大頭像 PNG 是 96×112（或 128×128）、透明背景、≤16 色
[ ] 圖標 PNG 是 24×24、透明背景、≤16 色
[ ] 執行 replace-portrait.ps1 → 產生新 ROM
[ ] 執行 replace-icon.ps1（-Rom 用上一步的輸出）→ 產生最終 ROM
[ ] 檢查最終 ROM 大小 = 12,583,424
[ ] 從標題進選角、移到 col0：大頭像正確、圖標正確、名字 AKUMA
[ ] 離開 col0 再回來：大頭像仍正確
[ ] （可選）用 harness 驗 VRAM 8192/8192、288/288、CGRAM 32/32

7. 附錄：目前的資料（僅供對照）
----------------------------------------------------------------
7.1 大頭像調色盤（0x6D5440，16 色 BGR555 小端序）
    4E 1D 1C 32 98 21 14 11 90 00 4C 00 9E 31 D9 18
    16 00 10 00 8C 39 4A 31 08 29 C6 20 63 0C 06 29

7.2 圖標調色盤（0x6D5320）
    00 00 FF 7F 2C 00 4C 24 6C 4C 15 2E 75 25 3A 00
    F1 0C 36 15 72 00 1A 5B 30 39 CF 5C 91 04 8B 39

7.3 圖標 OAM（0x6D5340，9 個 sprite）
    45 AE D0 3A  4D AE D1 3A  55 AE D2 3A
    45 B6 D3 3A  4D B6 D4 3A  55 B6 D5 3A
    45 BE D6 3A  4D BE D7 3A  55 BE D8 3A

7.4 大頭像的 cave 相關（僅說明，不用改）
    - col0 進入後，cave 會檢查 VRAM $1050 / $1C10 兩個 word 是否等於
      ROM $4E:E0A0 / $4E:F820（即 0x276200 的 offset 0xA0 / 0x1820）。
    - 不一致就分 4 段（每段 2048 bytes）把 0x276200 的資料上傳到 VRAM $1000。
    - 因此換圖後「第一次進 col0 / 第二次進 col0 / 久置」都會是新的圖。

================================================================
（文件結束）
