#!/usr/bin/env python3
"""Summarize the address-adjacent selector root before the current 2:0 root."""
from __future__ import annotations

import argparse
import html
import json
import struct
import sys
from pathlib import Path
from typing import Any

sys.path.insert(0, str(Path(__file__).resolve().parent))

from probe_exe_scene_tables import read_sections
from summarize_script_handler_table import handler_for_opcode


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
SOURCE = "map1_01a"
TARGET = "map2_02d"
SOURCE_SELECTOR = "0:0"
LOGICAL_PREDECESSOR_SELECTOR = "1:0"
CURRENT_SELECTOR = "2:0"
CURRENT_WRITER = 0x005428BC
LEAF_TABLE_START = 0x005429A8
LEAF_TABLE_END = 0x00542A10
FRONTIER_LEAF = 0x00542AE8
FRONTIER_READER = 0x00542B0C
SOURCE_RECORD = 0x00542B44
TARGET_RECORD = 0x00542BAC


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


def hex8(value: int) -> str:
    return f"0x{value:02x}"


def load_json(path: Path, fallback: Any) -> Any:
    if not path.exists():
        return fallback
    return json.loads(path.read_text(encoding="utf-8"))


def selector_key(row: dict) -> str:
    return f"{row.get('group')}:{row.get('slot')}"


def find_selector(selectors: list[dict], key: str) -> dict:
    for row in selectors:
        if selector_key(row) == key:
            return row
    raise ValueError(f"selector {key} not found")


def parse_hex(value: str | None) -> int | None:
    return int(value, 16) if value else None


def va_to_offset(sections: list[dict], va: int) -> int | None:
    for section in sections:
        start = int(section["va"])
        end = start + int(section["raw_size"])
        if start <= va < end:
            return int(section["raw"]) + (va - start)
    return None


def section_for_va(sections: list[dict], va: int) -> str | None:
    for section in sections:
        start = int(section["va"])
        end = start + int(section["raw_size"])
        if start <= va < end:
            return str(section.get("name") or "")
    return None


def dword_at(exe: bytes, sections: list[dict], va: int) -> int | None:
    offset = va_to_offset(sections, va)
    if offset is None or offset + 4 > len(exe):
        return None
    return struct.unpack_from("<I", exe, offset)[0]


def unique_roots(selectors: list[dict]) -> list[int]:
    roots = {
        int(row["selectedPointerHex"], 16)
        for row in selectors
        if row.get("fieldMaps") and row.get("selectedPointerHex")
    }
    return sorted(roots)


def selectors_for_root(selectors: list[dict], root_va: int) -> list[dict]:
    return [
        row for row in selectors
        if parse_hex(row.get("selectedPointerHex")) == root_va
    ]


def root_range(selectors: list[dict], root_va: int) -> tuple[int, int | None]:
    roots = unique_roots(selectors)
    index = roots.index(root_va)
    end = roots[index + 1] if index + 1 < len(roots) else None
    return root_va, end


def dword_refs_in_range(
    exe: bytes,
    sections: list[dict],
    start_va: int,
    end_va: int | None,
    target_va: int,
) -> list[dict]:
    if end_va is None:
        return []
    start_offset = va_to_offset(sections, start_va)
    end_offset = va_to_offset(sections, end_va)
    if start_offset is None or end_offset is None or end_offset <= start_offset:
        return []
    data = exe[start_offset:end_offset]
    pattern = struct.pack("<I", target_va)
    refs = []
    cursor = 0
    while True:
        index = data.find(pattern, cursor)
        if index < 0:
            break
        ref_va = start_va + index
        refs.append({
            "refVaHex": hex32(ref_va),
            "targetVaHex": hex32(target_va),
            "section": section_for_va(sections, ref_va),
            "offsetFromRangeStart": index,
            "offsetFromRangeStartHex": hex(index),
        })
        cursor = index + 1
    return refs


def annotate_current_selector_ref_roles(refs: list[dict], current_selector: dict) -> list[dict]:
    selected_entry_hex = current_selector.get("selectedPointerVaHex")
    row_pointer_hex = current_selector.get("rowPointerHex")
    annotated = []
    for ref in refs:
        role = "data-reference"
        if ref.get("refVaHex") == selected_entry_hex:
            role = "current-selector-row-selected-root-entry"
        annotated.append({
            **ref,
            "role": role,
            "isCurrentSelectorSelectedRootEntry": ref.get("refVaHex") == selected_entry_hex,
            "isCurrentSelectorRowPointer": ref.get("refVaHex") == row_pointer_hex,
        })
    return annotated


def printable_string_entries_after(
    exe: bytes,
    sections: list[dict],
    va: int,
    limit: int = 512,
    max_strings: int = 64,
) -> list[dict]:
    offset = va_to_offset(sections, va)
    if offset is None:
        return []
    chunk = exe[offset:offset + limit]
    entries = []
    cursor = 0
    while cursor < len(chunk):
        end = chunk.find(b"\x00", cursor)
        if end < 0:
            break
        raw = chunk[cursor:end]
        if len(raw) >= 4 and all(32 <= byte < 127 for byte in raw):
            entries.append({
                "vaHex": hex32(va + cursor),
                "text": raw.decode("ascii"),
            })
        cursor = end + 1
    return entries[:max_strings]


def printable_strings_after(
    exe: bytes,
    sections: list[dict],
    va: int,
    limit: int = 512,
    max_strings: int = 64,
) -> list[str]:
    return [
        row["text"]
        for row in printable_string_entries_after(exe, sections, va, limit, max_strings)
    ]


def fill_root_for(root_hex: str, secondary_fill_roots: dict | None) -> dict:
    for row in (secondary_fill_roots or {}).get("roots") or []:
        if row.get("rootHex") == root_hex:
            return row
    return {}


def simulate_opcode12_selected_slot(table: list[int], start: int) -> int:
    slot = start & 0xFF
    for _ in range(12):
        slot = slot - 1 if slot else 0x0B
        if table[slot]:
            break
    for _ in range(12):
        slot = 0 if slot + 1 >= 0x0C else slot + 1
        if table[slot]:
            break
    return slot


def secondary_table_for_fill(fill: dict) -> list[int] | None:
    if fill.get("helperArgumentHex") != "0x00":
        return None
    try:
        count = int(str(fill.get("streamPlus1Hex")), 16)
    except ValueError:
        return None
    if not 0 <= count <= 12:
        return None
    return [1 if index < count else 0 for index in range(12)]


def fill_effect(fills: list[dict]) -> dict:
    if not fills:
        return {
            "uniqueFillValueHexes": [],
            "lastFillValueHex": None,
            "lastFillHelperArgumentHex": None,
            "lastFillStreamPlus1Hex": None,
            "secondaryBranchStateAfterLastFill": None,
            "lastFillAllStartsPassCurrentReader": False,
            "outcomes": [],
        }
    last_fill = fills[-1]
    table = secondary_table_for_fill(last_fill)
    outcomes = []
    if table is not None:
        for start in range(12):
            selected = simulate_opcode12_selected_slot(table, start)
            outcomes.append({
                "startSlot": start,
                "selectedSlot": selected,
                "selectedValue": table[selected],
                "frontierReaderFallsThrough": table[selected] == 1,
            })
    return {
        "uniqueFillValueHexes": sorted({fill.get("valueHex") for fill in fills if fill.get("valueHex")}),
        "lastFillValueHex": last_fill.get("valueHex"),
        "lastFillHelperArgumentHex": last_fill.get("helperArgumentHex"),
        "lastFillStreamPlus1Hex": last_fill.get("streamPlus1Hex"),
        "secondaryBranchStateAfterLastFill": table,
        "lastFillAllStartsPassCurrentReader": bool(outcomes) and all(
            row["frontierReaderFallsThrough"] for row in outcomes
        ),
        "outcomes": outcomes,
    }


def decode_op10(value: int) -> dict | None:
    if (value & 0xFF) != 0x10:
        return None
    stream_plus_1 = (value >> 8) & 0xFF
    helper_arg = (value >> 16) & 0xFF
    stream_plus_3 = (value >> 24) & 0xFF
    state_table = "primaryBranchState" if stream_plus_1 == 0 else "secondaryBranchState"
    helper_valid = helper_arg <= 0x0B
    script_shaped = stream_plus_3 <= 0x02
    return {
        "streamPlus1Hex": hex8(stream_plus_1),
        "helperArgumentHex": hex8(helper_arg),
        "streamPlus3Hex": hex8(stream_plus_3),
        "stateTable": state_table,
        "helperDispatchValid": helper_valid,
        "scriptShaped": script_shaped,
        "validSecondaryFill": state_table == "secondaryBranchState" and helper_valid and script_shaped,
    }


def classify_current_root_pointer(value: int, current_root: int, current_end: int | None) -> str | None:
    if value == current_root:
        return "current-root-exact"
    if value == FRONTIER_READER:
        return "frontier-reader"
    if value == FRONTIER_LEAF:
        return "frontier-leaf"
    if value == SOURCE_RECORD:
        return "source-record"
    if value == TARGET_RECORD:
        return "target-record"
    if LEAF_TABLE_START <= value < LEAF_TABLE_END:
        return "leaf-table-window"
    if current_end is None or not current_root <= value < current_end:
        return None
    if value < CURRENT_WRITER:
        return "current-root-before-writer"
    if CURRENT_WRITER <= value < FRONTIER_READER:
        return "writer-to-reader-window"
    if FRONTIER_READER < value < current_end:
        return "post-reader-current-root"
    return "current-root-other"


def tail_after_last_fill_summary(
    exe: bytes,
    sections: list[dict],
    tail_start: int | None,
    tail_end: int | None,
    current_root: int,
    current_end: int | None,
) -> dict:
    if tail_start is None or tail_end is None or tail_start >= tail_end:
        return {
            "rangeHex": None,
            "dwordCount": 0,
            "pointerDwordCount": 0,
            "dataPointerDwordCount": 0,
            "textPointerDwordCount": 0,
            "relocPointerDwordCount": 0,
            "currentRootRangePointerCount": 0,
            "currentRootExactRefCount": 0,
            "frontierReaderRefCount": 0,
            "sourceRecordRefCount": 0,
            "targetRecordRefCount": 0,
            "leafTableWindowPointerCount": 0,
            "dataHandlerLowByteCount": 0,
            "defaultHandlerLowByteCount": 0,
            "opcode10LowByteCount": 0,
            "validSecondaryFillCount": 0,
            "looksLikeDescriptorData": False,
            "currentRootPointerClasses": {},
            "lowOpcodeTop": [],
            "samples": [],
        }
    low_opcode_counts: dict[str, int] = {}
    pointer_count = 0
    data_pointer_count = 0
    text_pointer_count = 0
    reloc_pointer_count = 0
    current_root_pointer_classes: dict[str, int] = {}
    current_root_pointer_count = 0
    exact_current_root_count = 0
    frontier_reader_count = 0
    source_record_count = 0
    target_record_count = 0
    leaf_table_count = 0
    data_handler_count = 0
    default_handler_count = 0
    opcode10_count = 0
    valid_secondary_count = 0
    samples = []
    for va in range(tail_start, tail_end, 4):
        value = dword_at(exe, sections, va)
        if value is None:
            continue
        opcode = value & 0xFF
        opcode_hex = hex8(opcode)
        low_opcode_counts[opcode_hex] = low_opcode_counts.get(opcode_hex, 0) + 1
        handler = handler_for_opcode(exe, sections, opcode)
        if handler.get("handlerSection") == ".data":
            data_handler_count += 1
        if handler.get("isDefaultHandler"):
            default_handler_count += 1
        pointer_section = section_for_va(sections, value)
        if pointer_section:
            pointer_count += 1
            if pointer_section == ".data":
                data_pointer_count += 1
            elif pointer_section == ".text":
                text_pointer_count += 1
            elif pointer_section == ".reloc":
                reloc_pointer_count += 1
        target_class = classify_current_root_pointer(value, current_root, current_end)
        if target_class:
            current_root_pointer_count += 1
            current_root_pointer_classes[target_class] = current_root_pointer_classes.get(target_class, 0) + 1
            if target_class == "current-root-exact":
                exact_current_root_count += 1
            elif target_class == "frontier-reader":
                frontier_reader_count += 1
            elif target_class == "source-record":
                source_record_count += 1
            elif target_class == "target-record":
                target_record_count += 1
            elif target_class == "leaf-table-window":
                leaf_table_count += 1
        op10 = decode_op10(value)
        if op10:
            opcode10_count += 1
            if op10["validSecondaryFill"]:
                valid_secondary_count += 1
        if target_class or handler.get("handlerSection") == ".data" or op10:
            if len(samples) < 32:
                samples.append({
                    "vaHex": hex32(va),
                    "valueHex": hex32(value),
                    "lowOpcodeHex": opcode_hex,
                    "handlerVaHex": handler.get("handlerVaHex"),
                    "handlerSection": handler.get("handlerSection"),
                    "pointerSection": pointer_section,
                    "currentRootPointerClass": target_class,
                    "op10": op10,
                })
    dword_count = max(0, (tail_end - tail_start) // 4)
    low_opcode_top = [
        {"lowOpcodeHex": key, "count": value}
        for key, value in sorted(low_opcode_counts.items(), key=lambda item: (-item[1], item[0]))[:12]
    ]
    looks_like_descriptor_data = (
        pointer_count >= 50
        and current_root_pointer_count >= 10
        and exact_current_root_count == 0
        and frontier_reader_count == 0
        and source_record_count == 0
        and target_record_count == 0
        and data_handler_count > 0
    )
    return {
        "rangeHex": f"{hex32(tail_start)}..{hex32(tail_end)}",
        "dwordCount": dword_count,
        "pointerDwordCount": pointer_count,
        "dataPointerDwordCount": data_pointer_count,
        "textPointerDwordCount": text_pointer_count,
        "relocPointerDwordCount": reloc_pointer_count,
        "currentRootRangePointerCount": current_root_pointer_count,
        "currentRootExactRefCount": exact_current_root_count,
        "frontierReaderRefCount": frontier_reader_count,
        "sourceRecordRefCount": source_record_count,
        "targetRecordRefCount": target_record_count,
        "leafTableWindowPointerCount": leaf_table_count,
        "dataHandlerLowByteCount": data_handler_count,
        "defaultHandlerLowByteCount": default_handler_count,
        "opcode10LowByteCount": opcode10_count,
        "validSecondaryFillCount": valid_secondary_count,
        "looksLikeDescriptorData": looks_like_descriptor_data,
        "currentRootPointerClasses": dict(sorted(current_root_pointer_classes.items())),
        "lowOpcodeTop": low_opcode_top,
        "samples": samples,
    }


def root_record(selectors: list[dict], root_va: int, root_end_va: int | None, secondary_fill_roots: dict | None) -> dict:
    rows = selectors_for_root(selectors, root_va)
    first = rows[0] if rows else {}
    field_maps = first.get("fieldMaps") or []
    fill_root = fill_root_for(hex32(root_va), secondary_fill_roots)
    fills = fill_root.get("fills") or []
    last_fill = fills[-1] if fills else {}
    effect = fill_effect(fills)
    last_fill_va = parse_hex(last_fill.get("vaHex"))
    return {
        "selectors": [selector_key(row) for row in rows],
        "rootHex": hex32(root_va),
        "rangeHex": f"{hex32(root_va)}..{hex32(root_end_va) if root_end_va is not None else '?'}",
        "fieldMaps": field_maps,
        "containsSource": SOURCE in field_maps,
        "containsTarget": TARGET in field_maps,
        "fillCount": fill_root.get("fillCount", 0),
        "firstFillHex": (fills[0] or {}).get("vaHex") if fills else None,
        "lastFillHex": last_fill.get("vaHex"),
        "tailRangeAfterLastFillHex": (
            f"{hex32(last_fill_va + 4)}..{hex32(root_end_va)}"
            if last_fill_va is not None and root_end_va is not None and last_fill_va + 4 <= root_end_va
            else None
        ),
        "tailHasKnownSecondaryFillAfterLastFill": False if fills else None,
        **effect,
    }


def build_summary(exe: bytes, selectors: list[dict], secondary_fill_roots: dict | None = None) -> dict:
    sections = read_sections(exe)
    current_row = find_selector(selectors, CURRENT_SELECTOR)
    source_row = find_selector(selectors, SOURCE_SELECTOR)
    logical_predecessor_row = find_selector(selectors, LOGICAL_PREDECESSOR_SELECTOR)
    current_root = int(current_row["selectedPointerHex"], 16)
    roots = unique_roots(selectors)
    root_index = roots.index(current_root)
    address_predecessor_root = roots[root_index - 1]
    address_successor_root = roots[root_index + 1] if root_index + 1 < len(roots) else None
    previous_start, previous_end = root_range(selectors, address_predecessor_root)
    current_start, current_end = root_range(selectors, current_root)
    successor_record = (
        root_record(selectors, address_successor_root, root_range(selectors, address_successor_root)[1], secondary_fill_roots)
        if address_successor_root is not None
        else {}
    )
    source_maps = set(source_row.get("fieldMaps") or [])
    logical_predecessor_maps = set(logical_predecessor_row.get("fieldMaps") or [])
    address_predecessor_rows = selectors_for_root(selectors, address_predecessor_root)
    address_predecessor_row = address_predecessor_rows[0] if address_predecessor_rows else {}
    address_predecessor_maps = set(address_predecessor_row.get("fieldMaps") or [])
    current_maps = set(current_row.get("fieldMaps") or [])
    previous_to_current_refs = annotate_current_selector_ref_roles(dword_refs_in_range(
        exe,
        sections,
        previous_start,
        previous_end,
        current_root,
    ), current_row)
    current_to_previous_refs = dword_refs_in_range(
        exe,
        sections,
        current_start,
        current_end,
        address_predecessor_root,
    )
    following_string_entries = (
        printable_string_entries_after(exe, sections, int(previous_to_current_refs[0]["refVaHex"], 16) + 4)
        if previous_to_current_refs
        else []
    )
    following_strings = [row["text"] for row in following_string_entries]
    address_predecessor_selector = selector_key(address_predecessor_row) if address_predecessor_row else None
    address_predecessor_root_record = root_record(
        selectors,
        address_predecessor_root,
        previous_end,
        secondary_fill_roots,
    )
    current_root_record = root_record(selectors, current_root, current_end, secondary_fill_roots)
    last_fill_va = parse_hex(address_predecessor_root_record.get("lastFillHex"))
    address_predecessor_tail = tail_after_last_fill_summary(
        exe,
        sections,
        last_fill_va + 4 if last_fill_va is not None else None,
        previous_end,
        current_root,
        current_end,
    )
    selector_index_order_supports_execution = False
    address_contiguity_proves_execution = False
    dword_ref_classification = (
        "current-selector-row-entry-with-resource-list"
        if any(row.get("isCurrentSelectorSelectedRootEntry") for row in previous_to_current_refs)
        else
        "resource-list-pointer-context"
        if following_strings and any(item.endswith(".cns") or item.endswith(".mlk") for item in following_strings)
        else "unclassified-data-reference"
        if previous_to_current_refs
        else "missing"
    )
    promotion_status = "blocked"
    conclusion = (
        f"The address-adjacent field-map root before selector {CURRENT_SELECTOR} is "
        f"{address_predecessor_selector} at {hex32(address_predecessor_root)}. It ends exactly at the current root "
        f"{hex32(current_root)} and has {address_predecessor_root_record.get('fillCount')} "
        "secondaryBranchState fill-shaped rows. Its last known fill would satisfy the current reader for every "
        "start slot if that root executed immediately before the current root and the state persisted. However it is "
        f"selector {address_predecessor_selector}, not the logical previous selector {LOGICAL_PREDECESSOR_SELECTOR}; "
        f"it contains {TARGET} but not {SOURCE}; and its direct current-root dword hit is the current selector's own "
        f"selected-root entry at {current_row.get('selectedPointerVaHex')}, followed by resource strings "
        f"{', '.join(following_strings) or 'none'}. "
        f"After the last fill, the tail has {address_predecessor_tail['currentRootRangePointerCount']} current-root-range "
        f"pointers, but exact current-root, frontier-reader, source-record, and target-record refs are "
        f"{address_predecessor_tail['currentRootExactRefCount']}/"
        f"{address_predecessor_tail['frontierReaderRefCount']}/"
        f"{address_predecessor_tail['sourceRecordRefCount']}/"
        f"{address_predecessor_tail['targetRecordRefCount']}. "
        "Address adjacency, tail pointer density, a passing fill effect, and this selector-row metadata still do not prove that normal gameplay executes this root before "
        f"the {CURRENT_SELECTOR} frontier reader or that {SOURCE}->{TARGET} has a strict source hotspot."
    )
    return {
        "source": SOURCE,
        "target": TARGET,
        "sourceSelector": SOURCE_SELECTOR,
        "logicalPredecessorSelector": LOGICAL_PREDECESSOR_SELECTOR,
        "currentSelector": CURRENT_SELECTOR,
        "sourceRootHex": source_row.get("selectedPointerHex"),
        "logicalPredecessorRootHex": logical_predecessor_row.get("selectedPointerHex"),
        "currentRootHex": current_row.get("selectedPointerHex"),
        "currentSelectorRowPointerTableEntryHex": current_row.get("rowPointerVaHex"),
        "currentSelectorRowPointerHex": current_row.get("rowPointerHex"),
        "currentSelectorSelectedRootEntryHex": current_row.get("selectedPointerVaHex"),
        "addressPredecessorSelector": address_predecessor_selector,
        "addressPredecessorRootHex": hex32(address_predecessor_root),
        "addressPredecessorRangeHex": f"{hex32(previous_start)}..{hex32(previous_end) if previous_end is not None else '?'}",
        "addressSuccessorRootHex": hex32(address_successor_root) if address_successor_root is not None else None,
        "sourceMaps": source_row.get("fieldMaps") or [],
        "logicalPredecessorMaps": logical_predecessor_row.get("fieldMaps") or [],
        "addressPredecessorMaps": address_predecessor_row.get("fieldMaps") or [],
        "currentMaps": current_row.get("fieldMaps") or [],
        "addressPredecessorContainsSource": SOURCE in address_predecessor_maps,
        "addressPredecessorContainsTarget": TARGET in address_predecessor_maps,
        "addressPredecessorEqualsLogicalPredecessorMapSet": address_predecessor_maps == logical_predecessor_maps,
        "addressPredecessorEqualsCurrentMapSet": address_predecessor_maps == current_maps,
        "currentEqualsAddressPredecessorPlusSource": current_maps == (address_predecessor_maps | {SOURCE}),
        "sourceSelectorExtraMapsOmittedByCurrent": sorted(source_maps - current_maps),
        "addressPredecessorToCurrentRootRefCount": len(previous_to_current_refs),
        "addressPredecessorToCurrentRootRefs": previous_to_current_refs,
        "addressPredecessorToCurrentRootRefIsCurrentSelectorRowEntry": any(
            row.get("isCurrentSelectorSelectedRootEntry") for row in previous_to_current_refs
        ),
        "currentToAddressPredecessorRootRefCount": len(current_to_previous_refs),
        "currentToAddressPredecessorRootRefs": current_to_previous_refs,
        "addressPredecessorCurrentRootRefClassification": dword_ref_classification,
        "addressPredecessorCurrentRootRefFollowingStrings": following_strings,
        "addressPredecessorCurrentRootRefFollowingStringEntries": following_string_entries,
        "addressPredecessorRoot": address_predecessor_root_record,
        "currentRoot": current_root_record,
        "addressSuccessorRoot": successor_record,
        "addressPredecessorTailAfterLastFill": address_predecessor_tail,
        "addressPredecessorTailDwordCount": address_predecessor_tail["dwordCount"],
        "addressPredecessorTailPointerDwordCount": address_predecessor_tail["pointerDwordCount"],
        "addressPredecessorTailCurrentRootRangePointerCount": address_predecessor_tail[
            "currentRootRangePointerCount"
        ],
        "addressPredecessorTailCurrentRootExactRefCount": address_predecessor_tail["currentRootExactRefCount"],
        "addressPredecessorTailFrontierReaderRefCount": address_predecessor_tail["frontierReaderRefCount"],
        "addressPredecessorTailSourceRecordRefCount": address_predecessor_tail["sourceRecordRefCount"],
        "addressPredecessorTailTargetRecordRefCount": address_predecessor_tail["targetRecordRefCount"],
        "addressPredecessorTailLeafTableWindowPointerCount": address_predecessor_tail[
            "leafTableWindowPointerCount"
        ],
        "addressPredecessorTailDataHandlerLowByteCount": address_predecessor_tail["dataHandlerLowByteCount"],
        "addressPredecessorTailValidSecondaryFillCount": address_predecessor_tail["validSecondaryFillCount"],
        "addressPredecessorTailLooksLikeDescriptorData": address_predecessor_tail["looksLikeDescriptorData"],
        "addressPredecessorLastFillAllStartsPassCurrentReader": address_predecessor_root_record.get(
            "lastFillAllStartsPassCurrentReader"
        ),
        "addressPredecessorTailHasKnownSecondaryFillAfterLastFill": address_predecessor_root_record.get(
            "tailHasKnownSecondaryFillAfterLastFill"
        ),
        "addressPredecessorPassingFillStillNotExecutionProof": (
            address_predecessor_root_record.get("lastFillAllStartsPassCurrentReader") is True
            and not address_contiguity_proves_execution
        ),
        "selectorIndexOrderSupportsExecution": selector_index_order_supports_execution,
        "addressContiguityProvesExecution": address_contiguity_proves_execution,
        "executionOrderProven": False,
        "strictHotspotFound": False,
        "promotionStatus": promotion_status,
        "remainingProofs": [
            f"prove whether address-adjacent root {address_predecessor_selector} executes before selector {CURRENT_SELECTOR}",
            "trace the selector loader or selected-pointer runtime state instead of treating 0x0053f328 metadata as execution",
            "capture real selector 2:0 savedata or find a strict map1_01a source hotspot",
        ],
        "conclusion": conclusion,
    }


def markdown(summary: dict) -> str:
    lines = [
        "# Save Selector Address Predecessor Context",
        "",
        f"- route: `{summary['source']} -> {summary['target']}`",
        f"- source selector: `{summary['sourceSelector']}` root `{summary['sourceRootHex']}`",
        f"- logical predecessor: `{summary['logicalPredecessorSelector']}` root `{summary['logicalPredecessorRootHex']}`",
        f"- address predecessor: `{summary['addressPredecessorSelector']}` root `{summary['addressPredecessorRootHex']}` range `{summary['addressPredecessorRangeHex']}`",
        f"- current selector: `{summary['currentSelector']}` root `{summary['currentRootHex']}`",
        f"- address predecessor equals logical predecessor map set: {summary['addressPredecessorEqualsLogicalPredecessorMapSet']}",
        f"- current equals address predecessor plus source: {summary['currentEqualsAddressPredecessorPlusSource']}",
        f"- address predecessor -> current root refs: {summary['addressPredecessorToCurrentRootRefCount']}",
        f"- current-root ref is current selector row entry: {summary['addressPredecessorToCurrentRootRefIsCurrentSelectorRowEntry']}",
        f"- current selector row pointer: `{summary['currentSelectorRowPointerHex']}`",
        f"- current selector selected-root entry: `{summary['currentSelectorSelectedRootEntryHex']}`",
        f"- current -> address predecessor root refs: {summary['currentToAddressPredecessorRootRefCount']}",
        f"- current-root ref classification: `{summary['addressPredecessorCurrentRootRefClassification']}`",
        f"- last fill passes current reader: {summary['addressPredecessorLastFillAllStartsPassCurrentReader']}",
        f"- tail has known secondary fill after last fill: {summary['addressPredecessorTailHasKnownSecondaryFillAfterLastFill']}",
        f"- passing fill still not execution proof: {summary['addressPredecessorPassingFillStillNotExecutionProof']}",
        f"- tail current-root-range pointers: {summary['addressPredecessorTailCurrentRootRangePointerCount']}",
        f"- tail exact current-root refs: {summary['addressPredecessorTailCurrentRootExactRefCount']}",
        f"- tail frontier-reader refs: {summary['addressPredecessorTailFrontierReaderRefCount']}",
        f"- tail source/target record refs: {summary['addressPredecessorTailSourceRecordRefCount']}/{summary['addressPredecessorTailTargetRecordRefCount']}",
        f"- tail descriptor-like data: {summary['addressPredecessorTailLooksLikeDescriptorData']}",
        f"- address contiguity proves execution: {summary['addressContiguityProvesExecution']}",
        f"- execution order proven: {summary['executionOrderProven']}",
        f"- promotion status: `{summary['promotionStatus']}`",
        "",
        summary["conclusion"],
        "",
        "## Root Summary",
        "",
        "| role | selectors | range | contains source | contains target | fills | first fill | last fill | last fill value | all-starts pass | tail after last fill |",
        "| --- | --- | --- | --- | --- | ---: | --- | --- | --- | --- | --- |",
    ]
    for role, row in [
        ("address predecessor", summary["addressPredecessorRoot"]),
        ("current", summary["currentRoot"]),
        ("address successor", summary["addressSuccessorRoot"]),
    ]:
        if not row:
            continue
        lines.append(
            f"| {role} | {', '.join(f'`{item}`' for item in row.get('selectors') or [])} | "
            f"`{row.get('rangeHex')}` | {row.get('containsSource')} | {row.get('containsTarget')} | "
            f"{row.get('fillCount')} | `{row.get('firstFillHex') or '-'}` | `{row.get('lastFillHex') or '-'}` | "
            f"`{row.get('lastFillValueHex') or '-'}` | {row.get('lastFillAllStartsPassCurrentReader')} | "
            f"`{row.get('tailRangeAfterLastFillHex') or '-'}` |"
        )
    lines.extend(["", "## Direct Refs", ""])
    refs = summary["addressPredecessorToCurrentRootRefs"]
    if refs:
        lines.extend(
            f"- `{row['refVaHex']}` -> `{row['targetVaHex']}` section `{row['section']}` offset `{row['offsetFromRangeStartHex']}` role `{row.get('role')}`"
            for row in refs
        )
    else:
        lines.append("- none")
    lines.append("")
    lines.append(
        "- following strings: "
        + (", ".join(f"`{item}`" for item in summary["addressPredecessorCurrentRootRefFollowingStrings"]) or "-")
    )
    tail = summary["addressPredecessorTailAfterLastFill"]
    lines.extend([
        "",
        "## Tail After Last Fill",
        "",
        f"- range: `{tail.get('rangeHex')}`",
        f"- dwords: {tail.get('dwordCount')}",
        f"- pointer dwords: {tail.get('pointerDwordCount')} (data {tail.get('dataPointerDwordCount')}, text {tail.get('textPointerDwordCount')}, reloc {tail.get('relocPointerDwordCount')})",
        f"- current-root-range pointers: {tail.get('currentRootRangePointerCount')}",
        f"- exact current-root / frontier-reader / source-record / target-record refs: {tail.get('currentRootExactRefCount')}/{tail.get('frontierReaderRefCount')}/{tail.get('sourceRecordRefCount')}/{tail.get('targetRecordRefCount')}",
        f"- leaf-table window pointers: {tail.get('leafTableWindowPointerCount')}",
        f"- data-handler low-byte rows: {tail.get('dataHandlerLowByteCount')}",
        f"- valid secondary fills after last fill: {tail.get('validSecondaryFillCount')}",
        f"- tail descriptor-like data: {tail.get('looksLikeDescriptorData')}",
        "",
        "current-root pointer classes: "
        + (", ".join(f"{key}:{value}" for key, value in (tail.get("currentRootPointerClasses") or {}).items()) or "-"),
        "",
        "| va | value | low opcode | handler | pointer section | current-root class | valid secondary fill |",
        "| --- | --- | --- | --- | --- | --- | --- |",
    ])
    for row in tail.get("samples") or []:
        lines.append(
            f"| `{row.get('vaHex')}` | `{row.get('valueHex')}` | `{row.get('lowOpcodeHex')}` | "
            f"`{row.get('handlerVaHex') or '-'}` {row.get('handlerSection') or '-'} | "
            f"{row.get('pointerSection') or '-'} | {row.get('currentRootPointerClass') or '-'} | "
            f"{(row.get('op10') or {}).get('validSecondaryFill') if row.get('op10') else '-'} |"
        )
    lines.extend(["", "## Remaining Proofs", ""])
    lines.extend(f"- {item}" for item in summary["remainingProofs"])
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    root_rows = []
    for role, row in [
        ("address predecessor", summary["addressPredecessorRoot"]),
        ("current", summary["currentRoot"]),
        ("address successor", summary["addressSuccessorRoot"]),
    ]:
        if not row:
            continue
        root_rows.append(
            "<tr>"
            f"<td>{html.escape(role)}</td>"
            f"<td>{html.escape(', '.join(row.get('selectors') or []))}</td>"
            f"<td><code>{html.escape(row.get('rangeHex') or '')}</code></td>"
            f"<td>{row.get('containsSource')}</td>"
            f"<td>{row.get('containsTarget')}</td>"
            f"<td>{row.get('fillCount')}</td>"
            f"<td><code>{html.escape(row.get('firstFillHex') or '-')}</code></td>"
            f"<td><code>{html.escape(row.get('lastFillHex') or '-')}</code></td>"
            f"<td><code>{html.escape(row.get('lastFillValueHex') or '-')}</code></td>"
            f"<td>{row.get('lastFillAllStartsPassCurrentReader')}</td>"
            f"<td><code>{html.escape(row.get('tailRangeAfterLastFillHex') or '-')}</code></td>"
            "</tr>"
        )
    ref_rows = [
        "<tr>"
        f"<td><code>{html.escape(row['refVaHex'])}</code></td>"
        f"<td><code>{html.escape(row['targetVaHex'])}</code></td>"
        f"<td>{html.escape(str(row['section']))}</td>"
        f"<td><code>{html.escape(row['offsetFromRangeStartHex'])}</code></td>"
        f"<td>{html.escape(str(row.get('role') or ''))}</td>"
        "</tr>"
        for row in summary["addressPredecessorToCurrentRootRefs"]
    ]
    tail = summary["addressPredecessorTailAfterLastFill"]
    tail_rows = [
        "<tr>"
        f"<td><code>{html.escape(row.get('vaHex') or '')}</code></td>"
        f"<td><code>{html.escape(row.get('valueHex') or '')}</code></td>"
        f"<td><code>{html.escape(row.get('lowOpcodeHex') or '')}</code></td>"
        f"<td><code>{html.escape(row.get('handlerVaHex') or '-')}</code> {html.escape(row.get('handlerSection') or '-')}</td>"
        f"<td>{html.escape(row.get('pointerSection') or '-')}</td>"
        f"<td>{html.escape(row.get('currentRootPointerClass') or '-')}</td>"
        f"<td>{html.escape(str((row.get('op10') or {}).get('validSecondaryFill') if row.get('op10') else '-'))}</td>"
        "</tr>"
        for row in tail.get("samples") or []
    ]
    proofs = "".join(f"<li>{html.escape(item)}</li>" for item in summary["remainingProofs"])
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>Save Selector Address Predecessor Context</title>",
        "  <style>body{font-family:system-ui,sans-serif;background:#111;color:#eee;margin:24px}table{border-collapse:collapse;width:100%;max-width:1180px}td,th{border:1px solid #333;padding:6px 8px;text-align:left;vertical-align:top}th{background:#1f1f1f}code{color:#9bd4ff}</style>",
        "</head>",
        "<body>",
        "  <h1>Save Selector Address Predecessor Context</h1>",
        f"  <p>route <code>{html.escape(summary['source'])} -&gt; {html.escape(summary['target'])}</code>; logical predecessor <code>{summary['logicalPredecessorSelector']}</code>; address predecessor <code>{summary['addressPredecessorSelector']}</code>; current <code>{summary['currentSelector']}</code>; promotion status <code>{summary['promotionStatus']}</code>.</p>",
        f"  <p>address predecessor equals logical predecessor map set: {summary['addressPredecessorEqualsLogicalPredecessorMapSet']}; current equals address predecessor plus source: {summary['currentEqualsAddressPredecessorPlusSource']}; address predecessor -&gt; current root refs: {summary['addressPredecessorToCurrentRootRefCount']}; current-root ref is current selector row entry: {summary['addressPredecessorToCurrentRootRefIsCurrentSelectorRowEntry']}; current selector row pointer: <code>{summary['currentSelectorRowPointerHex']}</code>; current selector selected-root entry: <code>{summary['currentSelectorSelectedRootEntryHex']}</code>; current-root ref classification: {html.escape(summary['addressPredecessorCurrentRootRefClassification'])}; address contiguity proves execution: {summary['addressContiguityProvesExecution']}; execution order proven: {summary['executionOrderProven']}.</p>",
        f"  <p>last fill passes current reader: {summary['addressPredecessorLastFillAllStartsPassCurrentReader']}; tail has known secondary fill after last fill: {summary['addressPredecessorTailHasKnownSecondaryFillAfterLastFill']}; passing fill still not execution proof: {summary['addressPredecessorPassingFillStillNotExecutionProof']}.</p>",
        f"  <p>tail current-root-range pointers: {summary['addressPredecessorTailCurrentRootRangePointerCount']}; exact current-root refs: {summary['addressPredecessorTailCurrentRootExactRefCount']}; frontier-reader refs: {summary['addressPredecessorTailFrontierReaderRefCount']}; source/target record refs: {summary['addressPredecessorTailSourceRecordRefCount']}/{summary['addressPredecessorTailTargetRecordRefCount']}; tail descriptor-like data: {summary['addressPredecessorTailLooksLikeDescriptorData']}.</p>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        "  <h2>Root Summary</h2>",
        "  <table><thead><tr><th>role</th><th>selectors</th><th>range</th><th>source</th><th>target</th><th>fills</th><th>first fill</th><th>last fill</th><th>last fill value</th><th>all-starts pass</th><th>tail after last fill</th></tr></thead><tbody>",
        *root_rows,
        "  </tbody></table>",
        "  <h2>Direct Refs</h2>",
        "  <table><thead><tr><th>ref</th><th>target</th><th>section</th><th>offset</th><th>role</th></tr></thead><tbody>",
        *(ref_rows or ["<tr><td colspan=\"5\">none</td></tr>"]),
        "  </tbody></table>",
        f"  <p>following strings: {html.escape(', '.join(summary['addressPredecessorCurrentRootRefFollowingStrings']) or '-')}</p>",
        "  <h2>Tail After Last Fill</h2>",
        f"  <p>range <code>{html.escape(tail.get('rangeHex') or '-')}</code>; dwords: {tail.get('dwordCount')}; pointer dwords: {tail.get('pointerDwordCount')} (data {tail.get('dataPointerDwordCount')}, text {tail.get('textPointerDwordCount')}, reloc {tail.get('relocPointerDwordCount')}); current-root-range pointers: {tail.get('currentRootRangePointerCount')}; exact current-root/frontier-reader/source-record/target-record refs: {tail.get('currentRootExactRefCount')}/{tail.get('frontierReaderRefCount')}/{tail.get('sourceRecordRefCount')}/{tail.get('targetRecordRefCount')}; leaf-table window pointers: {tail.get('leafTableWindowPointerCount')}; data-handler low-byte rows: {tail.get('dataHandlerLowByteCount')}; valid secondary fills: {tail.get('validSecondaryFillCount')}; tail descriptor-like data: {tail.get('looksLikeDescriptorData')}.</p>",
        f"  <p>current-root pointer classes: {html.escape(', '.join(f'{key}:{value}' for key, value in (tail.get('currentRootPointerClasses') or {}).items()) or '-')}</p>",
        "  <table><thead><tr><th>va</th><th>value</th><th>low opcode</th><th>handler</th><th>pointer section</th><th>current-root class</th><th>valid secondary fill</th></tr></thead><tbody>",
        *(tail_rows or ["<tr><td colspan=\"7\">none</td></tr>"]),
        "  </tbody></table>",
        "  <h2>Remaining Proofs</h2>",
        f"  <ul>{proofs}</ul>",
        "</body>",
        "</html>",
        "",
    ])


def write_outputs(summary: dict, out_dir: Path) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "save_selector_address_predecessor_context.json").write_text(
        json.dumps(summary, ensure_ascii=False, separators=(",", ":")) + "\n",
        encoding="utf-8",
    )


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--exe", type=Path, default=ROOT / "Hwanse2.exe")
    parser.add_argument("--selectors", type=Path, default=OUT / "save_scene_selectors.json")
    parser.add_argument("--secondary-fill-roots", type=Path, default=OUT / "save_selector_secondary_fill_roots.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary(
        args.exe.read_bytes(),
        load_json(args.selectors, []),
        load_json(args.secondary_fill_roots, {}),
    )
    write_outputs(summary, args.out_dir)
    print(f"wrote save selector address predecessor context -> {args.out_dir / 'save_selector_address_predecessor_context.json'}")


if __name__ == "__main__":
    main()
