#!/usr/bin/env python3
from __future__ import annotations

import html
import json
import struct
from pathlib import Path

from probe_exe_scene_tables import offset_to_va, read_sections, va_to_offset


ROOT = Path(__file__).resolve().parents[1]
EXE = ROOT / "Hwanse2.exe"
OUT = ROOT / "out"
WEB = ROOT / "web"


def hx(v: int) -> str:
    return f"0x{v:08x}"


def scan_pattern(exe: bytes, sections: list[dict], needle: bytes, names={".data", ".rdata"}) -> list[int]:
    hits: list[int] = []
    for sec in sections:
        if sec["name"] not in names:
            continue
        raw = sec["raw"]
        blob = exe[raw : raw + sec["raw_size"]]
        start = 0
        while True:
            i = blob.find(needle, start)
            if i < 0:
                break
            hits.append(offset_to_va(sections, raw + i))
            start = i + 1
    return hits


def scan_active_op58_known_coords(exe: bytes, sections: list[dict], coords: list[tuple[str, int, int]]) -> list[dict]:
    rows: list[dict] = []
    for label, x, y in coords:
        hits = []
        for sec in sections:
            if sec["name"] not in {".data", ".rdata"}:
                continue
            raw = sec["raw"]
            blob = exe[raw : raw + sec["raw_size"]]
            for i in range(len(blob) - 8):
                if blob[i] != 0x58:
                    continue
                sx = struct.unpack_from("<H", blob, i + 4)[0]
                sy = struct.unpack_from("<H", blob, i + 6)[0]
                if sx == x and sy == y:
                    hits.append(offset_to_va(sections, raw + i))
        rows.append({"label": label, "x": x, "y": y, "activeOpcode58HitCount": len(hits), "hitVas": [hx(v) for v in hits[:16]]})
    return rows


def scan_active_op64_known_coords(exe: bytes, sections: list[dict], coords: list[tuple[str, int, int]]) -> list[dict]:
    coord_lookup = {(x, y): label for label, x, y in coords}
    hits_by_coord: dict[tuple[int, int], list[dict]] = {key: [] for key in coord_lookup}
    for sec in sections:
        if sec["name"] not in {".data", ".rdata"}:
            continue
        raw = sec["raw"]
        blob = exe[raw : raw + sec["raw_size"]]
        for i in range(len(blob) - 8):
            if blob[i] != 0x64:
                continue
            mode = blob[i + 1]
            if mode not in {1, 2, 3, 4}:
                continue
            x = struct.unpack_from("<H", blob, i + 2)[0]
            y = struct.unpack_from("<H", blob, i + 4)[0]
            key = (x, y)
            if key not in hits_by_coord:
                continue
            state = blob[i + 7]
            if state > 0x40:
                continue
            hits_by_coord[key].append(
                {
                    "commandVa": offset_to_va(sections, raw + i),
                    "commandVaHex": hx(offset_to_va(sections, raw + i)),
                    "section": sec["name"],
                    "mode": mode,
                    "x": x,
                    "y": y,
                    "objectId": blob[i + 6],
                    "objectIdHex": f"0x{blob[i + 6]:02x}",
                    "state": state,
                    "stateHex": f"0x{state:02x}",
                    "rawHex": blob[i : i + 8].hex(" "),
                }
            )
    rows: list[dict] = []
    for label, x, y in coords:
        hits = hits_by_coord[(x, y)]
        rows.append(
            {
                "label": label,
                "x": x,
                "y": y,
                "activeOpcode64HitCount": len(hits),
                "hits": hits[:16],
            }
        )
    return rows


def resource_rows_for_examples() -> list[dict]:
    return [
        {
            "map": "map1_02b",
            "asset": "map_b3",
            "resourceLoadRow": "0x0043e7bc",
            "load": "op11 mode=0x30 slot=0x000c cns=0x0043c070 rectTable=0x0043c17a",
            "showRow": "0x0043e7e4",
            "show": "op0c mode=0x30 slot=0x000c",
            "fieldMapRow": "0x0043e7ec",
            "fieldMap": "op10 field-map ptr=0x0043c0ca",
            "manualPlacementSummary": "map_b3 R01 at tile (15,8), user-confirmed",
        },
        {
            "map": "map2_02d",
            "asset": "map_d3",
            "resourceLoadRow": "0x0044d83c",
            "load": "op11 mode=0x30 slot=0x000c cns=0x00449514 rectTable=0x0044966a",
            "showRow": "0x0044d87c",
            "show": "op0c mode=0x30 slot=0x000c",
            "fieldMapRow": "0x0044d88c",
            "fieldMap": "op10 field-map ptr=0x00449577",
            "manualPlacementSummary": "map_d3 R01 at (8,7),(20,7),(36,7), R02 at (71,6), user-corrected",
        },
        {
            "map": "map3_02f",
            "asset": "map_f3",
            "resourceLoadRow": "0x0043e814",
            "load": "op11 mode=0x30 slot=0x000c cns=0x0043c091 rectTable=0x0043c17a",
            "showRow": "0x0043e854",
            "show": "op0c mode=0x30 slot=0x000c",
            "fieldMapRow": "0x0043e864",
            "fieldMap": "op10 field-map ptr=0x0043c0e4",
            "manualPlacementSummary": "map_f3 R01 eight doors, R02 one door, user-confirmed",
        },
    ]


def known_placement_rows() -> list[dict]:
    return [
        {"label": "map1_02b map_b3 R01", "map": "map1_02b", "asset": "map_b3", "rect": "R01", "rectIndex": 0, "x": 15, "y": 8, "w": 3, "h": 6, "note": "user-confirmed door/object"},
        {"label": "map2_02d map_d3 R01-a", "map": "map2_02d", "asset": "map_d3", "rect": "R01", "rectIndex": 0, "x": 8, "y": 7, "w": 3, "h": 4, "note": "user-corrected door"},
        {"label": "map2_02d map_d3 R01-b", "map": "map2_02d", "asset": "map_d3", "rect": "R01", "rectIndex": 0, "x": 20, "y": 7, "w": 3, "h": 4, "note": "user-corrected door"},
        {"label": "map2_02d map_d3 R01-c", "map": "map2_02d", "asset": "map_d3", "rect": "R01", "rectIndex": 0, "x": 36, "y": 7, "w": 3, "h": 4, "note": "user-corrected door"},
        {"label": "map2_02d map_d3 R02", "map": "map2_02d", "asset": "map_d3", "rect": "R02", "rectIndex": 1, "x": 71, "y": 6, "w": 5, "h": 2, "note": "user-corrected large door"},
        {"label": "map3_02f map_f3 R02", "map": "map3_02f", "asset": "map_f3", "rect": "R02", "rectIndex": 1, "x": 19, "y": 20, "w": 3, "h": 4, "note": "user-confirmed door"},
        {"label": "map3_02f map_f3 R01-a", "map": "map3_02f", "asset": "map_f3", "rect": "R01", "rectIndex": 0, "x": 46, "y": 20, "w": 3, "h": 4, "note": "user-confirmed door"},
        {"label": "map3_02f map_f3 R01-b", "map": "map3_02f", "asset": "map_f3", "rect": "R01", "rectIndex": 0, "x": 62, "y": 20, "w": 3, "h": 4, "note": "user-confirmed door"},
        {"label": "map3_02f map_f3 R01-c", "map": "map3_02f", "asset": "map_f3", "rect": "R01", "rectIndex": 0, "x": 82, "y": 20, "w": 3, "h": 4, "note": "user-confirmed door"},
        {"label": "map3_02f map_f3 R01-d", "map": "map3_02f", "asset": "map_f3", "rect": "R01", "rectIndex": 0, "x": 98, "y": 20, "w": 3, "h": 4, "note": "user-confirmed door"},
        {"label": "map3_02f map_f3 R01-e", "map": "map3_02f", "asset": "map_f3", "rect": "R01", "rectIndex": 0, "x": 46, "y": 41, "w": 3, "h": 4, "note": "user-confirmed door"},
        {"label": "map3_02f map_f3 R01-f", "map": "map3_02f", "asset": "map_f3", "rect": "R01", "rectIndex": 0, "x": 62, "y": 41, "w": 3, "h": 4, "note": "user-confirmed door"},
        {"label": "map3_02f map_f3 R01-g", "map": "map3_02f", "asset": "map_f3", "rect": "R01", "rectIndex": 0, "x": 82, "y": 41, "w": 3, "h": 4, "note": "user-confirmed door"},
        {"label": "map3_02f map_f3 R01-h", "map": "map3_02f", "asset": "map_f3", "rect": "R01", "rectIndex": 0, "x": 98, "y": 41, "w": 3, "h": 4, "note": "user-confirmed door"},
        {"label": "map2_02d map_d3 extra candidate", "map": "map2_02d", "asset": "map_d3", "rect": "R01", "rectIndex": 0, "x": 11, "y": 32, "w": 3, "h": 4, "note": "EXE candidate repeated with the same map_d3 door rect; needs visual/gameplay review"},
    ]


def load_active_object_inventory() -> list[dict]:
    path = OUT / "active_object_script_inventory.json"
    if not path.exists():
        return []
    data = json.loads(path.read_text(encoding="utf-8"))
    if isinstance(data, dict):
        rows = data.get("inventory", [])
        return rows if isinstance(rows, list) else []
    return data if isinstance(data, list) else []


def match_known_placements(inventory: list[dict]) -> tuple[list[dict], list[dict]]:
    slot_rows = [
        row
        for row in inventory
        if isinstance(row, dict) and str(row.get("field0x28Hex", "")).startswith("0x000c")
    ]
    matches: list[dict] = []
    for known in known_placement_rows():
        field = f"0x000c{known['rectIndex']:04x}"
        hits = [
            row
            for row in slot_rows
            if row.get("xCandidate") == known["x"]
            and row.get("yCandidate") == known["y"]
            and row.get("wCandidate") == known["w"]
            and row.get("hCandidate") == known["h"]
            and row.get("field0x28Hex") == field
        ]
        matches.append(
            {
                **known,
                "expectedField0x28Hex": field,
                "matchCount": len(hits),
                "matches": [
                    {
                        "initializerVaHex": row.get("initializerVaHex"),
                        "scriptVaHex": row.get("scriptVaHex"),
                        "field0x16Hex": row.get("field0x16Hex"),
                        "field0x2cHex": row.get("field0x2cHex"),
                        "scriptClassification": row.get("scriptClassification"),
                    }
                    for row in hits[:12]
                ],
            }
        )
    return slot_rows, matches


def build() -> dict:
    exe = EXE.read_bytes()
    sections = read_sections(exe)

    resource_table = 0x0048ACA8
    active_table = 0x00440538
    resource_handlers = {}
    active_handlers = {}
    for op in range(0x20):
        resource_handlers[f"0x{op:02x}"] = hx(struct.unpack_from("<I", exe, va_to_offset(sections, resource_table + op * 4))[0])
    for op in [0x51, 0x52, 0x58, 0x5E, 0x64, 0x6C, 0x70]:
        active_handlers[f"0x{op:02x}"] = hx(struct.unpack_from("<I", exe, va_to_offset(sections, active_table + op * 4))[0])

    active_51_slot_0c = scan_pattern(exe, sections, b"\x51\x00\x0c\x00")
    resource_0c_slot_0c = scan_pattern(exe, sections, b"\x0c\x30\x0c\x00")

    known_coords = [
        ("map1_02b map_b3 R01", 15, 8),
        ("map2_02d map_d3 R01-a", 8, 7),
        ("map2_02d map_d3 R01-b", 20, 7),
        ("map2_02d map_d3 R01-c", 36, 7),
        ("map2_02d map_d3 R02", 71, 6),
        ("map3_02f map_f3 R02", 19, 20),
        ("map3_02f map_f3 R01-a", 46, 20),
        ("map3_02f map_f3 R01-b", 62, 20),
        ("map3_02f map_f3 R01-c", 82, 20),
        ("map3_02f map_f3 R01-d", 98, 20),
        ("map3_02f map_f3 R01-e", 46, 41),
        ("map3_02f map_f3 R01-f", 62, 41),
        ("map3_02f map_f3 R01-g", 82, 41),
        ("map3_02f map_f3 R01-h", 98, 41),
    ]
    op58_rows = scan_active_op58_known_coords(exe, sections, known_coords)
    op58_hit_total = sum(row["activeOpcode58HitCount"] for row in op58_rows)
    op64_rows = scan_active_op64_known_coords(exe, sections, known_coords)
    op64_hit_total = sum(row["activeOpcode64HitCount"] for row in op64_rows)
    inventory = load_active_object_inventory()
    slot_rows, placement_matches = match_known_placements(inventory)
    matched_known_count = sum(1 for row in placement_matches if row["matchCount"] > 0)

    rect_summary = {}
    rect_path = OUT / "map_extra_rects.json"
    if rect_path.exists():
        data = json.loads(rect_path.read_text())
        rect_summary = {
            "status": data.get("status"),
            "assetCount": data.get("assetCount"),
            "assetsWithVisibleRects": data.get("assetsWithVisibleRects"),
            "totalUniqueRectCount": data.get("totalUniqueRectCount"),
        }

    return {
        "status": "map-extra-source-display-and-active-object-overlay-path-grounded",
        "summary": {
            "sourceRectConsumerGrounded": True,
            "resourceDisplayConsumerGrounded": True,
            "field0x28DrawSourceSelectorGrounded": True,
            "activeBoundsHotspotConsumerGrounded": True,
            "activeOverlayDrawPathFound": True,
            "drawPositionCommandPathFound": True,
            "staticInitializerDrawPositionBindingStillOpen": True,
            "activeRedrawSlot0cFound": True,
            "activeObjectPlacementInitializerFound": True,
            "directLayerWritePlacementTableFound": False,
            "activeOpcode58KnownPlacementHitCount": op58_hit_total,
            "activeOpcode64KnownPlacementHitCount": op64_hit_total,
            "field0x28Slot0cRowCount": len(slot_rows),
            "knownManualPlacementMatchedCount": matched_known_count,
            "knownManualPlacementTotal": len(placement_matches),
            "placementConclusion": "map_*3 CNS and rect tables are loaded by the resource VM. field0x28 is now grounded as a draw-source selector: high word = surface slot, low word = EXE source-rect/frame index. For map_*3 examples the slot is 0x000c. The active object x/y/w/h bytes are separately grounded as bounds/hotspot values by the movement/contact consumer. The common overlay renderer consumes field0x28 plus object+0x1c/+0x20 projected draw coordinates, so static objects are very likely drawn as active overlays above the map. Active opcode 0x64 is now identified as a tile-to-draw projection command: it converts stream tile x/y minus camera origin into object+0x1c/+0x20 while updating object+0xe8/+0xea. Known manual coordinates have several opcode64 hits, including map1_02b (15,8), but the exact binding from every static map_*3 initializer row to a map/resource root is still incomplete. No direct active opcode 0x58 live-layer write table was found for the known door coordinates.",
        },
        "handlers": {
            "resourceVmBase": hx(resource_table),
            "resourceHandlers": resource_handlers,
            "activeVmBase": hx(active_table),
            "activeHandlers": active_handlers,
        },
        "groundedConsumers": [
            {
                "name": "resource opcode 0x11/0x12/0x16 load surface",
                "handler": "0x004245da",
                "effect": "loads CNS through 0x00422f7c and registers surface object through 0x0041878c/0x004186e5",
            },
            {
                "name": "rect surface registration",
                "handler": "0x0041878c",
                "effect": "stores object in 0x0055abd8[slot], sets draw callback 0x00419d6c and size callback 0x00419907",
            },
            {
                "name": "resource opcode 0x0c show/register display priority",
                "handler": "0x00424313",
                "effect": "reads stream slot word and mode byte, then calls 0x004184e2 -> 0x004191ad",
            },
            {
                "name": "active opcode 0x51 redraw slot",
                "handler": "0x00406f2a",
                "effect": "reads word [stream+2] and calls 0x00425067 for that surface slot",
            },
            {
                "name": "tile redraw consumer",
                "handler": "0x00425163",
                "effect": "reads live layer0 at 0x00595af0, combines it with current slot at 0x0055b580, then calls 0x004175d3",
            },
            {
                "name": "active overlay draw source selector",
                "handler": "0x00416ce2",
                "effect": "reads object+0x28, splits low word as source rect/frame index and high word as surface slot via 0x0055abd8; reads object+0x1c/+0x20 as projected draw x/y",
            },
            {
                "name": "object dirty/occlusion rect projection",
                "handler": "0x00424cd6",
                "effect": "calls 0x00416ce2, converts the returned pixel rect to 16px tile coverage, and updates 0x005957d0 dirty/occlusion flags",
            },
            {
                "name": "active object bounds/contact consumer",
                "handler": "0x00431ca6",
                "effect": "iterates active objects at 0x00574100..0x005741ec and reads object+0xe6/+0xe7 size plus object+0xe8/+0xea tile position for movement/contact overlap",
            },
            {
                "name": "active command tile-to-draw projection",
                "handler": "opcode 0x64 -> 0x00407d09 / opcode 0x70 -> 0x004090d2-like path",
                "effect": "for matching active objects, converts stream tile x/y minus camera origin 0x004576dc/0x004576de into object+0x1c/+0x20 fixed-point draw coordinates and mirrors tile x/y into object+0xe8/+0xea",
            },
            {
                "name": "active opcode 0x58 live layer writer",
                "handler": "0x00407686",
                "effect": "writes a tile/layer word directly to 0x00595af0 or 0x0058d7d0 at command x/y; known map_*3 door coordinates have zero hits here",
            },
        ],
        "overlayDrawEvidence": {
            "field0x28SelectorConsumer": {
                "handler": "0x00416ce2",
                "keyReads": [
                    "0x00416ceb..0x00416cf6: object+0x28 & 0xffff -> low-word rect/frame selector",
                    "0x00416cf9..0x00416d07: object+0x28 >> 16 -> high-word surface slot, then 0x0055abd8[slot]",
                    "0x00416d11..0x00416d3d: object+0x1c/+0x20 -> projected draw x/y",
                ],
            },
            "dirtyRectConsumer": {
                "handler": "0x00424cd6",
                "keyReads": [
                    "0x00424cfe..0x00424d0b: call 0x00416ce2(object, rectOut)",
                    "0x00424d1d..0x00424df8: convert pixel rect to tile coverage",
                    "0x00424e99..0x00425024: update 0x005957d0 coverage/occlusion bytes",
                ],
            },
            "activeBoundsConsumer": {
                "handler": "0x00431ca6",
                "keyReads": [
                    "object+0xe6/+0xe7 are width/height-like active bounds",
                    "object+0xe8/+0xea are tile-position-like active bounds",
                    "consumer updates contact/overlap state, so these are not just visual source rects",
                ],
            },
            "tileToDrawProjectionWriter": {
                "handler": "opcode 0x64 -> 0x00407d09 / opcode 0x70 -> 0x004090d2-like path",
                "keyReads": [
                    "opcode 0x64 stream format is 8 bytes: 0x64, mode, x:u16, y:u16, objectId:u8, state:u8",
                    "iterates active object order through 0x004576e8 / 0x00574538 / 0x0059dd70 and matches stream object id against object+0x16",
                    "stream+2 x: (x - cameraX 0x004576dc) * 16 + 24 -> object+0x1c, and stream x -> object+0xe8",
                    "stream+4 y: (y - cameraY 0x004576de) * 16 + 8 -> object+0x20, and stream y -> object+0xea",
                    "this proves a tile-to-draw projection command exists, but does not alone bind every static map_*3 initializer to a map/root",
                ],
            },
        },
        "resourceRows": resource_rows_for_examples(),
        "counts": {
            "resourceOp0cMode30Slot000cCount": len(resource_0c_slot_0c),
            "resourceOp0cMode30Slot000cSampleVas": [hx(v) for v in resource_0c_slot_0c[:32]],
            "activeOp51Slot000cCount": len(active_51_slot_0c),
            "activeOp51Slot000cVas": [hx(v) for v in active_51_slot_0c],
        },
        "directPlacementNegativeScan": {
            "activeOpcode58Semantics": "layer write: layer selector, tile/collision word, x word, y word",
            "knownCoordinateRows": op58_rows,
            "knownCoordinateHitTotal": op58_hit_total,
        },
        "tileToDrawProjectionScan": {
            "activeOpcode64Semantics": "tile-to-draw projection: mode, tile x/y, object id, state/action; updates active object projected draw x/y",
            "knownCoordinateRows": op64_rows,
            "knownCoordinateHitTotal": op64_hit_total,
        },
        "activeObjectPlacement": {
            "source": "out/active_object_script_inventory.json",
            "interpretation": "field0x28 high word = surface slot, low word = EXE source-rect/frame index; for map_*3 examples the slot is 0x000c. x/y/w/h are active object bounds/hotspots consumed by movement/contact code. Opcode 0x64 separately projects stream tile x/y into object+0x1c/+0x20 draw coordinates and mirrors x/y into object+0xe8/+0xea. Repeated initializer rows with identical bounds/source are expected when visually similar maps share door/building layout but vary NPC or scenario resources.",
            "field0x28Slot0cRowCount": len(slot_rows),
            "knownPlacementMatches": placement_matches,
        },
        "rectSummary": rect_summary,
        "nextEvidenceNeeded": [
            "Use field0x28=0x000cNNNN initializers as map_*3 active-object draw-source evidence and x/y/w/h as bounds/hotspot evidence; do not treat x/y/w/h alone as final draw placement proof.",
            "Bind opcode 0x64 tile-to-draw projection rows back to exact static map_*3 initializer rows and map/resource roots.",
            "Connect each initializer range back to its exact map/resource root so repeated map variants can be grouped cleanly.",
            "Review extra candidates such as map_d3-like rect at tile (11,32), because EXE placement exists but the gameplay role is not yet named.",
        ],
    }


def write_outputs(report: dict) -> None:
    OUT.mkdir(exist_ok=True)
    WEB.mkdir(exist_ok=True)
    (OUT / "map_extra_consumer_review.json").write_text(json.dumps(report, ensure_ascii=False, indent=2))

    rows_html = "".join(
        f"<tr><td><code>{html.escape(row['map'])}</code></td><td><code>{html.escape(row['asset'])}</code></td>"
        f"<td><code>{html.escape(row['load'])}</code></td><td><code>{html.escape(row['show'])}</code></td>"
        f"<td>{html.escape(row['manualPlacementSummary'])}</td></tr>"
        for row in report["resourceRows"]
    )
    consumers_html = "".join(
        f"<tr><td><code>{html.escape(row['handler'])}</code></td><td>{html.escape(row['name'])}</td><td>{html.escape(row['effect'])}</td></tr>"
        for row in report["groundedConsumers"]
    )
    coord_html = "".join(
        f"<tr><td>{html.escape(row['label'])}</td><td>{row['x']},{row['y']}</td><td>{row['activeOpcode58HitCount']}</td></tr>"
        for row in report["directPlacementNegativeScan"]["knownCoordinateRows"]
    )
    op64_html = "".join(
        f"<tr><td>{html.escape(row['label'])}</td><td>{row['x']},{row['y']}</td>"
        f"<td>{row['activeOpcode64HitCount']}</td>"
        f"<td>{html.escape(', '.join(hit['commandVaHex'] + ' oid=' + hit['objectIdHex'] + ' state=' + hit['stateHex'] for hit in row['hits'][:6]))}</td></tr>"
        for row in report["tileToDrawProjectionScan"]["knownCoordinateRows"]
    )
    placement_html = "".join(
        f"<tr><td><code>{html.escape(row['label'])}</code></td>"
        f"<td><code>{html.escape(row['expectedField0x28Hex'])}</code></td>"
        f"<td>{row['x']},{row['y']}</td><td>{row['w']}x{row['h']}</td>"
        f"<td>{row['matchCount']}</td><td>{html.escape(row['note'])}</td>"
        f"<td>{html.escape(', '.join(str(match.get('initializerVaHex')) for match in row['matches'][:6]))}</td></tr>"
        for row in report["activeObjectPlacement"]["knownPlacementMatches"]
    )
    page = f"""<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<link rel="icon" href="../favicon.ico" />
<title>map_*3 Consumer Review</title>
<style>
body{{font-family:system-ui,sans-serif;margin:24px;line-height:1.45;background:#f7f7f4;color:#202020}}
table{{border-collapse:collapse;width:100%;margin:16px 0;background:#fff}}
th,td{{border:1px solid #d0d0ca;padding:8px;vertical-align:top}}
th{{background:#ecece6;text-align:left}}
.tag{{display:inline-block;padding:2px 6px;border-radius:4px;background:#e9f2ff;border:1px solid #b8d3ef}}
.warn{{background:#fff4d8;border-color:#e2c069}}
code{{background:#f0f0ec;padding:1px 3px;border-radius:3px}}
</style>
</head>
<body>
<main data-page="map-extra-consumer-review">
<h1>map_*3 Consumer Review</h1>
<p><span class="tag">{html.escape(report['status'])}</span></p>
<p>{html.escape(report['summary']['placementConclusion'])}</p>
<ul>
<li>source rect consumer grounded: <code>{report['summary']['sourceRectConsumerGrounded']}</code></li>
<li>display consumer grounded: <code>{report['summary']['resourceDisplayConsumerGrounded']}</code></li>
<li>field0x28 draw source selector grounded: <code>{report['summary']['field0x28DrawSourceSelectorGrounded']}</code></li>
<li>active bounds/hotspot consumer grounded: <code>{report['summary']['activeBoundsHotspotConsumerGrounded']}</code></li>
<li>active overlay draw path found: <code>{report['summary']['activeOverlayDrawPathFound']}</code></li>
<li>draw position command path found: <code>{report['summary']['drawPositionCommandPathFound']}</code></li>
<li>static initializer draw-position binding still open: <code>{report['summary']['staticInitializerDrawPositionBindingStillOpen']}</code></li>
<li>active redraw slot 0x000c hits: <code>{report['counts']['activeOp51Slot000cCount']}</code> {html.escape(str(report['counts']['activeOp51Slot000cVas']))}</li>
<li>resource op0c mode30 slot0c count: <code>{report['counts']['resourceOp0cMode30Slot000cCount']}</code></li>
<li>active object bounds/hotspot initializer evidence: <span class="tag">found</span></li>
<li>field0x28 slot 0x000c rows: <code>{report['summary']['field0x28Slot0cRowCount']}</code></li>
<li>known manual placements matched: <code>{report['summary']['knownManualPlacementMatchedCount']}/{report['summary']['knownManualPlacementTotal']}</code></li>
<li>opcode 0x64 tile-to-draw known coordinate hits: <code>{report['summary']['activeOpcode64KnownPlacementHitCount']}</code></li>
<li>direct layer-write coordinate table proof: <span class="tag warn">not found</span></li>
</ul>
<h2>Grounded Consumers</h2>
<table><thead><tr><th>Handler</th><th>Name</th><th>Effect</th></tr></thead><tbody>{consumers_html}</tbody></table>
<h2>Example Resource Rows</h2>
<table><thead><tr><th>Map</th><th>Asset</th><th>Load</th><th>Show</th><th>Manual Placement</th></tr></thead><tbody>{rows_html}</tbody></table>
<h2>Active Object Placement Matches</h2>
<p>{html.escape(report['activeObjectPlacement']['interpretation'])}</p>
<table><thead><tr><th>Label</th><th>field0x28</th><th>Tile</th><th>Size</th><th>Matches</th><th>Note</th><th>Initializer VAs</th></tr></thead><tbody>{placement_html}</tbody></table>
<h2>Known Coordinate Negative Scan</h2>
<table><thead><tr><th>Label</th><th>Coord</th><th>opcode 0x58 hits</th></tr></thead><tbody>{coord_html}</tbody></table>
<h2>Tile-To-Draw Projection Scan</h2>
<p>{html.escape(report['tileToDrawProjectionScan']['activeOpcode64Semantics'])}</p>
<table><thead><tr><th>Label</th><th>Coord</th><th>opcode 0x64 hits</th><th>Hits</th></tr></thead><tbody>{op64_html}</tbody></table>
<script>
window.HWANSE_MAP_EXTRA_CONSUMER_REVIEW_READY = true;
window.HWANSE_MAP_EXTRA_CONSUMER_REVIEW = {json.dumps(report['summary'], ensure_ascii=False)};
</script>
</main>
</body>
</html>
"""
    (WEB / "map_extra_consumer_review.html").write_text(page)


def main() -> None:
    write_outputs(build())


if __name__ == "__main__":
    main()
