{
  "kind": "hwanse-palette-pipeline-review",
  "status": "palette-buffer-copy-apply-path-grounded",
  "source": [
    "Hwanse2.exe",
    "out/directdraw_flow.json",
    "web/map_animation_execution_boundary_review.html",
    "tools/build_map_animation_tile_review.py"
  ],
  "summary": {
    "displayMode": "640x480x8 indexed color",
    "paletteObjectGlobal": "0x004676c4",
    "paletteHeaderGlobal": "0x004676e4",
    "paletteEntriesGlobal": "0x004676e8",
    "entryCount": 256,
    "entryStrideBytes": 4,
    "entryLayout": "R, G, B, flags",
    "pcFlagRange": "0x0a..0xf5",
    "pcFlagValue": "0x04",
    "setEntriesHelpers": [
      "0x004165b8",
      "0x00416677"
    ],
    "copyThenApplyHelper": "0x00416736",
    "byteEvidenceRows": 16,
    "coreConclusion": "The EXE keeps a mutable 256-entry palette buffer at 0x004676e8. Palette effects copy or step RGB bytes inside that buffer, then push a range to DirectDraw with SetEntries. This is a color/fade/ramp pipeline, not proof of tile-position animation."
  },
  "memoryModel": [
    {
      "addressHex": "0x004676c4",
      "name": "DirectDraw palette object pointer",
      "meaning": "receives the IDirectDrawPalette pointer created by CreatePalette"
    },
    {
      "addressHex": "0x004676e4",
      "name": "palette header/allocation",
      "meaning": "stores version 0x0300 and entry count 0x0100 before the entry bytes"
    },
    {
      "addressHex": "0x004676e8",
      "name": "current palette entries",
      "meaning": "first mutable PALETTEENTRY; entry i is 0x004676e8 + i*4"
    },
    {
      "addressHex": "0x00559d98",
      "name": "palette dirty flag",
      "meaning": "map/VM palette handlers mark pending palette changes before the deferred SetEntries apply"
    }
  ],
  "directDrawPaletteCalls": [
    {
      "vaHex": "0x0041652e",
      "method": "IDirectDraw::CreatePalette",
      "vtableOffsetHex": "0x14",
      "arguments": "flags=0x04, entries=0x004676e8, out=0x004676c4",
      "role": "creates the 8-bit DirectDraw palette object from the EXE-owned entry buffer"
    },
    {
      "vaHex": "0x00416555",
      "method": "IDirectDrawSurface::SetPalette",
      "vtableOffsetHex": "0x7c",
      "arguments": "surface=primary wrapper at 0x0055abd8, palette=0x004676c4",
      "role": "attaches the DirectDraw palette to the primary display surface"
    },
    {
      "vaHex": "0x00416649",
      "method": "IDirectDrawPalette::SetEntries",
      "vtableOffsetHex": "0x18",
      "arguments": "start/count from helper args, entries=0x004676e8 + start*4",
      "role": "applies a current-palette range after computing the entry pointer"
    },
    {
      "vaHex": "0x00416708",
      "method": "IDirectDrawPalette::SetEntries",
      "vtableOffsetHex": "0x18",
      "arguments": "start/count from helper args, entries=0x004676e8 + start*4",
      "role": "second range apply path used after palette VM/list writes"
    },
    {
      "vaHex": "0x00416856",
      "method": "IDirectDrawPalette::GetEntries",
      "vtableOffsetHex": "0x20",
      "arguments": "reads entries into EXE buffer",
      "role": "palette snapshot/readback helper"
    }
  ],
  "updateRoutines": [
    {
      "vaHex": "0x00415c53",
      "name": "palette buffer initializer",
      "effect": "initializes a DD-style palette header, 256-entry count, entry pointer, and PC flags for entries 0x0a..0xf5",
      "status": "grounded"
    },
    {
      "vaHex": "0x004165b8",
      "name": "range SetEntries helper A",
      "effect": "optionally reattaches the palette to the primary surface, then applies a range from 0x004676e8 + start*4",
      "status": "grounded"
    },
    {
      "vaHex": "0x00416677",
      "name": "range SetEntries helper B",
      "effect": "same range-apply class as A; this one is reached by deferred palette update paths",
      "status": "grounded"
    },
    {
      "vaHex": "0x00416736",
      "name": "RGB copy then SetEntries",
      "effect": "copies count RGB triples into the current palette entry buffer, then calls the range apply helper",
      "status": "grounded"
    }
  ],
  "paletteVmBoundary": {
    "knownOpcodeFamilies": [
      "0x38",
      "0x39"
    ],
    "confirmedMeaning": "0x38 steps palette entries toward target/list/backup RGB values; 0x39 writes/fills current or backup palette ranges/lists.",
    "deferredApply": "if dirty flag 0x00559d98 is set, the VM path applies start 0 count 0x100 through 0x00416677 and clears the flag",
    "boundary": "these handlers explain palette color transforms and fades, but do not identify per-map waterfall/fire tile source-index motion"
  },
  "byteEvidence": [
    {
      "vaHex": "0x00415c58",
      "fileOffsetHex": "0x00015058",
      "bytes": "66 c7 00 00 03 a1 e4 76",
      "available": true,
      "label": "palette header version",
      "meaning": "writes 0x0300 to the palette header at 0x004676e4"
    },
    {
      "vaHex": "0x00415c62",
      "fileOffsetHex": "0x00015062",
      "bytes": "66 c7 40 02 00 01 a1 e4",
      "available": true,
      "label": "palette entry count",
      "meaning": "writes 0x0100 to palette header +2, grounding 256 entries"
    },
    {
      "vaHex": "0x00415c68",
      "fileOffsetHex": "0x00015068",
      "bytes": "a1 e4 76 46 00 83 c0 04 a3 e8 76 46 00 6a",
      "available": true,
      "label": "palette entries pointer",
      "meaning": "sets 0x004676e8 to palette header +4, the first PALETTEENTRY byte"
    },
    {
      "vaHex": "0x00415cb6",
      "fileOffsetHex": "0x000150b6",
      "bytes": "8b 45 f8 8b 0d e4 76 46 00 c6",
      "available": true,
      "label": "PC flag byte loop",
      "meaning": "writes byte 0x04 to entry flag byte for entries 0x0a..0xf5"
    },
    {
      "vaHex": "0x0041652e",
      "fileOffsetHex": "0x0001592e",
      "bytes": "ff 50 14 83 3d c4 76 46",
      "available": true,
      "label": "CreatePalette call",
      "meaning": "calls IDirectDraw::CreatePalette through vtable +0x14"
    },
    {
      "vaHex": "0x00416555",
      "fileOffsetHex": "0x00015955",
      "bytes": "ff 50 7c e9 00 00 00 00",
      "available": true,
      "label": "primary SetPalette call",
      "meaning": "attaches the created palette to the primary surface through vtable +0x7c"
    },
    {
      "vaHex": "0x00416599",
      "fileOffsetHex": "0x00015999",
      "bytes": "55 8b ec 53 56 57 68 ff",
      "available": true,
      "label": "range apply wrapper",
      "meaning": "calls the SetEntries helper for range start 0 count 0xff"
    },
    {
      "vaHex": "0x00416621",
      "fileOffsetHex": "0x00015a21",
      "bytes": "03 05 e8 76 46 00 50 8b 45 0c 25 ff ff 00 00",
      "available": true,
      "label": "SetEntries source pointer",
      "meaning": "computes 0x004676e8 + start * 4 as the PALETTEENTRY source pointer"
    },
    {
      "vaHex": "0x00416649",
      "fileOffsetHex": "0x00015a49",
      "bytes": "ff 50 18 89 45 fc 83 7d",
      "available": true,
      "label": "SetEntries call A",
      "meaning": "calls IDirectDrawPalette::SetEntries through vtable +0x18"
    },
    {
      "vaHex": "0x00416677",
      "fileOffsetHex": "0x00015a77",
      "bytes": "55 8b ec 83 ec 04 53 56",
      "available": true,
      "label": "SetEntries helper B",
      "meaning": "second range-apply helper used by VM/deferred palette paths"
    },
    {
      "vaHex": "0x00416708",
      "fileOffsetHex": "0x00015b08",
      "bytes": "ff 50 18 89 45 fc 83 7d",
      "available": true,
      "label": "SetEntries call B",
      "meaning": "calls IDirectDrawPalette::SetEntries through vtable +0x18"
    },
    {
      "vaHex": "0x0041677a",
      "fileOffsetHex": "0x00015b7a",
      "bytes": "0f bf 45 fc 8b 4d 10 8a 04 81 0f bf 4d fc",
      "available": true,
      "label": "copy red channel",
      "meaning": "copies caller/source red byte into the current palette entry buffer"
    },
    {
      "vaHex": "0x004167a0",
      "fileOffsetHex": "0x00015ba0",
      "bytes": "8b 4d 10 8a 44 81 01 0f bf 4d fc 8b 55 08",
      "available": true,
      "label": "copy green channel",
      "meaning": "copies caller/source green byte into the current palette entry buffer"
    },
    {
      "vaHex": "0x004167c4",
      "fileOffsetHex": "0x00015bc4",
      "bytes": "8b 4d 10 8a 44 81 02 0f bf 4d fc 8b 55 08",
      "available": true,
      "label": "copy blue channel",
      "meaning": "copies caller/source blue byte into the current palette entry buffer"
    },
    {
      "vaHex": "0x004167f1",
      "fileOffsetHex": "0x00015bf1",
      "bytes": "e8 81 fe ff ff 83 c4 08",
      "available": true,
      "label": "copy then apply",
      "meaning": "calls the range SetEntries helper after RGB bytes are copied"
    },
    {
      "vaHex": "0x00416856",
      "fileOffsetHex": "0x00015c56",
      "bytes": "ff 50 10 89 45 fc 83 7d",
      "available": true,
      "label": "GetEntries call",
      "meaning": "reads current palette entries through IDirectDrawPalette::GetEntries"
    }
  ],
  "verification": {
    "hasExe": true,
    "paletteHeaderWriteAvailable": true,
    "createPaletteCallAvailable": true,
    "setEntriesCallAAvailable": true,
    "setEntriesCallBAvailable": true,
    "copyThenApplyAvailable": true,
    "notMapTileMotionProof": true
  },
  "modelBoundaries": [
    {
      "area": "palette fade/ramp",
      "conclusion": "range/list RGB writes plus SetEntries are grounded and should be treated as the EXE palette animation path",
      "status": "promoted"
    },
    {
      "area": "indexed CNS rendering",
      "conclusion": "web rendering should preserve palette-index semantics where fidelity matters; precolored immutable PNGs cannot model later SetEntries updates",
      "status": "implementation-guidance"
    },
    {
      "area": "map animated tiles",
      "conclusion": "palette changes can recolor indexed pixels, but visible waterfall/fire source-tile motion still needs a separate tile/source-index producer",
      "status": "boundary-fixed"
    }
  ],
  "openQuestions": [
    "Name the exact callers that feed palette opcode 0x38/0x39 streams for each scene/map/effect root.",
    "Find the non-palette producer for map tile source-index motion if waterfall/fire animation is not purely palette-based.",
    "Capture or statically bind which palette ranges are used by individual battle helper effects that are palette-only."
  ]
}
