#!/usr/bin/env python3
"""Audit whether map1_01a edge candidates are original generic transition triggers."""
from __future__ import annotations

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

from probe_exe_scene_tables import find_cns_strings, offset_to_va, read_sections, va_to_offset


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
EXE = ROOT / "Hwanse2.exe"
SOURCE = "map1_01a"
TARGET = "map2_02d"
FAILED_EDGE_TRIGGER_GATE_IDS = [
    "edge-to-transition-control-flow",
    "runtime-edge-selected-root-proof",
    "strict-source-hotspot",
]
EDGE_TRIGGER_MISSING_EVIDENCE = [
    "a branch/call from the boundary collision path to the map loader or scene selector",
    "a runtime watchpoint showing an edge candidate changes the selected map/root",
    "a strict map1_01a source hotspot/event record for map2_02d",
]
EDGE_TRIGGER_EVIDENCE_REFS = [
    {"path": "Hwanse2.exe", "fields": [".text", ".rdata", ".data"]},
    {
        "path": "out/runtime_movement.json",
        "fields": ["actorController", "collisionHelper", "directionBlockBits", "objectOverlapResponse"],
    },
    {
        "path": "out/original_collision_route_audit.json",
        "fields": ["routeCandidates", "runtimeMapping", "promotionStatus"],
    },
]

ACTOR_CONTROLLER_FUNCTION = 0x0043022D
ACTOR_CONTROLLER_RANGE = (0x0043022D, 0x00431350)
ACTOR_COLLISION_HELPER_FUNCTION = 0x004319F8
ACTOR_COLLISION_HELPER_RANGE = (0x004319F8, 0x00431FE8)
ACTOR_OVERLAP_LOOP = 0x00431CA6
MAP_LOADER_FUNCTION = 0x0042449C
SCRIPT_RUNNER_FUNCTION = 0x00402321
SELECTOR_TABLE_FUNCTION_OR_DATA = 0x00442D35
SELECTED_POINTER_GLOBAL = 0x0059DE30
CURRENT_SELECTOR_ROOT = 0x00540714
DIRECTION_LATCH_GLOBAL = 0x00574533
LATCH_WINDOW_BEFORE = 96
LATCH_WINDOW_AFTER = 128
CALLER_WINDOW_BEFORE = 192
CALLER_WINDOW_AFTER = 192
CALL_GRAPH_MAX_DEPTH = 3
CALL_GRAPH_DEPTH_SENSITIVITY_DEPTHS = [1, 2, 3, 4, 5, 6]
CALL_GRAPH_MAX_FUNCTION_BYTES = 0x900

SIDE_TO_DIRECTION = {
    "top": "up",
    "bottom": "down",
    "left": "left",
    "right": "right",
}

BOUNDARY_SNIPPETS = {
    "down": {
        "edgeCheckVa": 0x00431A48,
        "caseExitJumpVa": 0x00431ACE,
        "blockedFallbackLatchHex": "0x01",
        "expectedHex": "3b c2 0f 85 07 00 00 00 c6 05 33 45 57 00 01",
    },
    "up": {
        "edgeCheckVa": 0x00431AE6,
        "caseExitJumpVa": 0x00431B6C,
        "blockedFallbackLatchHex": "0x02",
        "expectedHex": "85 c9 0f 85 07 00 00 00 c6 05 33 45 57 00 02",
    },
    "left": {
        "edgeCheckVa": 0x00431B84,
        "caseExitJumpVa": 0x00431BDF,
        "blockedFallbackLatchHex": "0x03",
        "expectedHex": "85 c9 0f 85 07 00 00 00 c6 05 33 45 57 00 03",
    },
    "right": {
        "edgeCheckVa": 0x00431C0C,
        "caseExitJumpVa": 0x00431C74,
        "blockedFallbackLatchHex": "0x04",
        "expectedHex": "3b c2 0f 85 07 00 00 00 c6 05 33 45 57 00 04",
    },
}


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


def sign16(value: int) -> int:
    return value - 0x10000 if value & 0x8000 else value


def sign32(value: int) -> int:
    return value - 0x100000000 if value & 0x80000000 else value


def labels_text(values: list[str] | None) -> str:
    return ",".join(values or []) or "-"


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


def read_at(exe: bytes, sections: list[dict], va: int, size: int) -> bytes:
    offset = va_to_offset(sections, va)
    if offset is None:
        raise ValueError(f"VA {hex32(va)} is outside raw sections")
    return exe[offset: offset + size]


def bytes_hex(exe: bytes, sections: list[dict], va: int, size: int) -> str:
    return read_at(exe, sections, va, size).hex(" ")


def rel32_target(exe: bytes, sections: list[dict], va: int) -> int | None:
    opcode = read_at(exe, sections, va, 1)[0]
    if opcode not in (0xE8, 0xE9):
        return None
    rel = struct.unpack("<i", read_at(exe, sections, va + 1, 4))[0]
    return va + 5 + rel


def range_bytes(exe: bytes, sections: list[dict], va_range: tuple[int, int]) -> bytes:
    start, end = va_range
    start_offset = va_to_offset(sections, start)
    end_offset = va_to_offset(sections, end)
    if start_offset is None or end_offset is None:
        raise ValueError(f"VA range {hex32(start)}..{hex32(end)} is outside raw sections")
    return exe[start_offset:end_offset]


def section_range(sections: list[dict], name: str) -> tuple[int, int]:
    for section in sections:
        if section.get("name") == name:
            return int(section["va"]), int(section["va"]) + int(section["raw_size"]) - 1
    raise ValueError(f"section {name} not found")


def in_range(value: int, va_range: tuple[int, int]) -> bool:
    return va_range[0] <= value < va_range[1]


def count_raw_immediate(exe: bytes, sections: list[dict], va_range: tuple[int, int], value: int) -> int:
    return range_bytes(exe, sections, va_range).count(struct.pack("<I", value))


def rel_target_hits(exe: bytes, sections: list[dict], va_range: tuple[int, int], target: int) -> list[str]:
    start, end = va_range
    data = range_bytes(exe, sections, va_range)
    hits = []
    for index in range(0, max(len(data) - 4, 0)):
        opcode = data[index]
        if opcode not in (0xE8, 0xE9):
            continue
        rel = struct.unpack("<i", data[index + 1: index + 5])[0]
        destination = start + index + 5 + rel
        if destination == target:
            hits.append(hex32(start + index))
    return hits


def raw_immediate_hits(exe: bytes, sections: list[dict], va_range: tuple[int, int], value: int) -> list[str]:
    start, _end = va_range
    data = range_bytes(exe, sections, va_range)
    pattern = struct.pack("<I", value)
    hits = []
    index = 0
    while True:
        hit = data.find(pattern, index)
        if hit < 0:
            break
        hits.append(hex32(start + hit))
        index = hit + 1
    return hits


def direct_call_edges(exe: bytes, sections: list[dict], va_range: tuple[int, int]) -> list[dict]:
    start, end = va_range
    data = range_bytes(exe, sections, va_range)
    text_range = section_range(sections, ".text")
    rows = []
    for index in range(0, max(len(data) - 4, 0)):
        if data[index] != 0xE8:
            continue
        rel = struct.unpack("<i", data[index + 1: index + 5])[0]
        call_va = start + index
        target = call_va + 5 + rel
        if not in_range(target, text_range):
            continue
        rows.append({
            "callVaHex": hex32(call_va),
            "targetVaHex": hex32(target),
            "targetVa": target,
        })
    return rows


def first_ret_after(exe: bytes, sections: list[dict], start_va: int, max_bytes: int) -> int | None:
    text_start, text_end = section_range(sections, ".text")
    start_offset = va_to_offset(sections, start_va)
    if start_offset is None:
        return None
    max_end_va = min(text_end, start_va + max_bytes)
    max_end_offset = va_to_offset(sections, max_end_va)
    if max_end_offset is None:
        max_end_offset = len(exe)
    for offset in range(start_offset, min(max_end_offset, len(exe))):
        opcode = exe[offset]
        if opcode == 0xC3:
            return offset_to_va(sections, offset + 1) or (start_va + (offset - start_offset) + 1)
        if opcode == 0xC2 and offset + 2 < len(exe):
            return offset_to_va(sections, offset + 3) or (start_va + (offset - start_offset) + 3)
    return None


def bounded_function_range(exe: bytes, sections: list[dict], start_va: int) -> tuple[int, int]:
    text_start, text_end = section_range(sections, ".text")
    start = max(text_start, start_va)
    fallback_end = min(text_end, start + CALL_GRAPH_MAX_FUNCTION_BYTES)
    ret_end = first_ret_after(exe, sections, start, CALL_GRAPH_MAX_FUNCTION_BYTES)
    if ret_end is None:
        return start, fallback_end
    return start, max(start, min(text_end, ret_end))


def indirect_call_like_count(exe: bytes, sections: list[dict], va_range: tuple[int, int]) -> int:
    data = range_bytes(exe, sections, va_range)
    count = 0
    for index in range(0, max(len(data) - 1, 0)):
        if data[index] != 0xFF:
            continue
        reg_opcode = (data[index + 1] >> 3) & 0x07
        if reg_opcode in {2, 4}:
            count += 1
    return count


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


def parse_range_hex(value: str | None) -> tuple[int, int] | None:
    if not value or ".." not in value:
        return None
    start_text, end_text = value.split("..", 1)
    try:
        return int(start_text, 16), int(end_text, 16)
    except ValueError:
        return None


def read_u32_va(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("<I", exe[offset: offset + 4])[0]


def jump_table_targets(
    exe: bytes,
    sections: list[dict],
    table_va: int | None,
    max_entries: int = 64,
) -> list[int]:
    if table_va is None:
        return []
    rows = []
    for index in range(max_entries):
        value = read_u32_va(exe, sections, table_va + index * 4)
        if value is None:
            break
        if section_name_for_va(sections, value) != ".text":
            break
        rows.append(value)
    return rows


def encoded_scan_targets(strings: dict[str, int]) -> list[dict]:
    targets = [
        {
            "label": "map-loader-function",
            "va": MAP_LOADER_FUNCTION,
            "vaHex": hex32(MAP_LOADER_FUNCTION),
            "group": "transition",
        },
        {
            "label": "script-runner-function",
            "va": SCRIPT_RUNNER_FUNCTION,
            "vaHex": hex32(SCRIPT_RUNNER_FUNCTION),
            "group": "transition",
        },
        {
            "label": "selector-table-function-or-data",
            "va": SELECTOR_TABLE_FUNCTION_OR_DATA,
            "vaHex": hex32(SELECTOR_TABLE_FUNCTION_OR_DATA),
            "group": "transition",
        },
        {
            "label": "selected-pointer-global",
            "va": SELECTED_POINTER_GLOBAL,
            "vaHex": hex32(SELECTED_POINTER_GLOBAL),
            "group": "selected-pointer",
        },
        {
            "label": "current-selector-root",
            "va": CURRENT_SELECTOR_ROOT,
            "vaHex": hex32(CURRENT_SELECTOR_ROOT),
            "group": "current",
        },
    ]
    for name, label in ((SOURCE, "source-map-string"), (TARGET, "target-map-string")):
        va = strings.get(name)
        if va is not None:
            targets.append({
                "label": label,
                "va": va,
                "vaHex": hex32(va),
                "group": "route-string",
            })
    return targets


def encoded_target_rows_for_range(
    exe: bytes,
    sections: list[dict],
    va_range: tuple[int, int],
    label: str,
    targets: list[dict],
) -> list[dict]:
    start, _end = va_range
    data = range_bytes(exe, sections, va_range)
    rows = []
    seen: set[tuple[str, int, int, str]] = set()
    for offset in range(len(data)):
        site_va = start + offset
        values: list[tuple[str, int, int | None, int]] = []
        if offset + 2 <= len(data):
            u16_value = struct.unpack_from("<H", data, offset)[0]
            values.extend([
                ("abs16-low", u16_value, None, 2),
                ("signed-rel16-site-plus2", sign16(u16_value), site_va + 2, 2),
            ])
        if offset + 4 <= len(data):
            u32_value = struct.unpack_from("<I", data, offset)[0]
            values.append(("signed-rel32-site-plus4", sign32(u32_value), site_va + 4, 4))
        for kind, scalar, rel_base, width in values:
            for target in targets:
                target_va = target["va"]
                direct_exact = (
                    width == 2
                    and offset + 4 <= len(data)
                    and struct.unpack_from("<I", data, offset)[0] == target_va
                )
                if kind == "abs16-low":
                    matches = scalar == (target_va & 0xFFFF) and not direct_exact
                else:
                    matches = rel_base is not None and rel_base + scalar == target_va
                if not matches:
                    continue
                key = (kind, site_va, target_va, label)
                if key in seen:
                    continue
                seen.add(key)
                rows.append({
                    "scanLabel": label,
                    "siteVaHex": hex32(site_va),
                    "rangeHex": f"{hex32(va_range[0])}..{hex32(va_range[1])}",
                    "kind": kind,
                    "valueHex": hex32(scalar & 0xFFFFFFFF),
                    "targetHex": hex32(target_va),
                    "targetLabel": target["label"],
                    "targetGroup": target["group"],
                    "siteSection": section_name_for_va(sections, site_va),
                    "promotionStatus": "blocked",
                    "promotesRouteExecution": False,
                })
    return rows


def encoded_target_summary(rows: list[dict]) -> dict:
    group_counts: dict[str, int] = {}
    label_counts: dict[str, int] = {}
    kind_counts: dict[str, int] = {}
    scan_label_counts: dict[str, int] = {}
    for row in rows:
        group = row.get("targetGroup")
        label = row.get("targetLabel")
        kind = row.get("kind")
        scan_label = row.get("scanLabel")
        if group:
            group_counts[group] = group_counts.get(group, 0) + 1
        if label:
            label_counts[label] = label_counts.get(label, 0) + 1
        if kind:
            kind_counts[kind] = kind_counts.get(kind, 0) + 1
        if scan_label:
            scan_label_counts[scan_label] = scan_label_counts.get(scan_label, 0) + 1
    transition_rows = [row for row in rows if row.get("targetGroup") == "transition"]
    route_proof_rows = [row for row in rows if row.get("targetGroup") in {"current", "route-string"}]
    selected_pointer_rows = [row for row in rows if row.get("targetGroup") == "selected-pointer"]
    return {
        "classification": (
            "edge-encoded-route-target-scalars-nonpromoting"
            if rows
            else "no-encoded-route-target-scalars-in-edge-windows"
        ),
        "rawScalarCandidateCount": len(rows),
        "transitionRawScalarCandidateCount": len(transition_rows),
        "routeProofRawScalarCandidateCount": len(route_proof_rows),
        "selectedPointerRawScalarCandidateCount": len(selected_pointer_rows),
        "promotingCandidateCount": 0,
        "targetGroupCounts": dict(sorted(group_counts.items())),
        "targetLabelCounts": dict(sorted(label_counts.items())),
        "kindCounts": dict(sorted(kind_counts.items())),
        "scanLabelCounts": dict(sorted(scan_label_counts.items())),
        "sampleRows": rows[:32],
        "promotionStatus": "blocked",
    }


def encoded_scan_for_ranges(
    exe: bytes,
    sections: list[dict],
    ranges: list[dict],
    targets: list[dict],
) -> dict:
    rows = []
    seen: set[tuple[str, str, str]] = set()
    for item in ranges:
        va_range = item.get("range")
        label = item.get("label") or "range"
        if not va_range:
            continue
        for row in encoded_target_rows_for_range(exe, sections, va_range, label, targets):
            key = (
                str(row.get("kind")),
                str(row.get("siteVaHex")),
                str(row.get("targetHex")),
            )
            if key in seen:
                continue
            seen.add(key)
            rows.append(row)
    return encoded_target_summary(rows)


def unique_window_ranges(rows: list[dict]) -> list[dict]:
    seen: set[tuple[int, int, str]] = set()
    unique_rows = []
    for row in rows:
        va_range = row.get("range")
        label = row.get("label") or "window"
        if not va_range:
            continue
        key = (va_range[0], va_range[1], label)
        if key in seen:
            continue
        seen.add(key)
        unique_rows.append(row)
    return unique_rows


def edge_handler_target_class(target_va: int) -> str:
    if in_range(target_va, ACTOR_CONTROLLER_RANGE):
        return "actor-controller-local"
    if in_range(target_va, ACTOR_COLLISION_HELPER_RANGE):
        return "collision-helper-local"
    return "outside-edge-handler"


def jump_table_target_class_counts(target_hexes: list[str]) -> dict[str, int]:
    counts: dict[str, int] = {}
    for target_hex in target_hexes:
        if not target_hex:
            continue
        target_class = edge_handler_target_class(int(target_hex, 16))
        counts[target_class] = counts.get(target_class, 0) + 1
    return counts


def indexed_jump_table_audit_rows(rows: list[dict]) -> list[dict]:
    audit_rows = []
    for row in rows:
        if row.get("operandKind") != "indexed-jump-table":
            continue
        target_hexes = row.get("indexedJumpTableTargetHexes") or []
        class_counts = jump_table_target_class_counts(target_hexes)
        outside_count = class_counts.get("outside-edge-handler", 0)
        transition_labels = row.get("transitionTableLabels") or []
        route_labels = row.get("routeTableLabels") or []
        audit_rows.append({
            "functionLabel": row.get("functionLabel"),
            "depth": row.get("depth"),
            "rangeHex": row.get("rangeHex"),
            "instructionVaHex": row.get("instructionVaHex"),
            "kind": row.get("kind"),
            "indexedJumpTableVaHex": row.get("indexedJumpTableVaHex"),
            "indexedJumpTableSection": row.get("indexedJumpTableSection"),
            "indexedJumpTableEntryCount": row.get("indexedJumpTableEntryCount") or 0,
            "indexedJumpTableSampleTargetHexes": row.get("indexedJumpTableSampleTargetHexes") or [],
            "targetClassCounts": class_counts,
            "allTargetsLocalToEdgeHandlers": bool(target_hexes) and outside_count == 0,
            "outsideEdgeHandlerTargetCount": outside_count,
            "transitionTableLabels": transition_labels,
            "transitionTableLabelCount": len(transition_labels),
            "routeTableLabels": route_labels,
            "routeTableLabelCount": len(route_labels),
            "transitionOrRouteTableLabelCount": len(transition_labels) + len(route_labels),
        })
    return audit_rows


def indirect_call_like_rows(
    exe: bytes,
    sections: list[dict],
    strings: dict[str, int],
    va_range: tuple[int, int],
    label: str,
    depth: int,
    path: list[str],
) -> list[dict]:
    start, _end = va_range
    data = range_bytes(exe, sections, va_range)
    route_values = {
        "selectedPointerGlobal": SELECTED_POINTER_GLOBAL,
        "currentSelectorRoot": CURRENT_SELECTOR_ROOT,
        "sourceMapString": strings.get(SOURCE),
        "targetMapString": strings.get(TARGET),
    }
    transition_targets = {
        "mapLoaderFunction": MAP_LOADER_FUNCTION,
        "scriptRunnerFunction": SCRIPT_RUNNER_FUNCTION,
        "selectorTableFunctionOrData": SELECTOR_TABLE_FUNCTION_OR_DATA,
    }
    rows = []
    for index in range(0, max(len(data) - 1, 0)):
        if data[index] != 0xFF:
            continue
        modrm = data[index + 1]
        reg_opcode = (modrm >> 3) & 0x07
        if reg_opcode not in {2, 4}:
            continue
        mod = (modrm >> 6) & 0x03
        rm = modrm & 0x07
        cursor = index + 2
        sib = None
        sib_base = None
        sib_index = None
        disp_size = 0
        disp_value = None
        absolute_memory_va = None
        indexed_table_va = None
        if mod != 3 and rm == 4 and cursor < len(data):
            sib = data[cursor]
            sib_base = sib & 0x07
            sib_index = (sib >> 3) & 0x07
            cursor += 1
        if mod == 0:
            if rm == 5 or (rm == 4 and sib_base == 5):
                disp_size = 4
        elif mod == 1:
            disp_size = 1
        elif mod == 2:
            disp_size = 4
        if disp_size and cursor + disp_size <= len(data):
            raw = data[cursor: cursor + disp_size]
            if disp_size == 1:
                disp_value = struct.unpack("<b", raw)[0]
            else:
                disp_value = struct.unpack("<I", raw)[0]
                if mod == 0:
                    if rm == 4 and sib_base == 5 and sib_index != 4:
                        indexed_table_va = disp_value
                    else:
                        absolute_memory_va = disp_value
        resolved_pointer = (
            read_u32_va(exe, sections, absolute_memory_va)
            if absolute_memory_va is not None
            else None
        )
        table_targets = jump_table_targets(exe, sections, indexed_table_va)
        transition_operand_label = next(
            (name for name, value in transition_targets.items() if value == absolute_memory_va),
            None,
        )
        transition_resolved_label = next(
            (name for name, value in transition_targets.items() if value == resolved_pointer),
            None,
        )
        transition_table_labels = sorted({
            name
            for name, value in transition_targets.items()
            if value in table_targets
        })
        route_operand_label = next(
            (
                name
                for name, value in route_values.items()
                if value is not None and value == absolute_memory_va
            ),
            None,
        )
        route_resolved_label = next(
            (
                name
                for name, value in route_values.items()
                if value is not None and value == resolved_pointer
            ),
            None,
        )
        route_table_labels = sorted({
            name
            for name, value in route_values.items()
            if value is not None and value in table_targets
        })
        if mod == 3:
            operand_kind = "register"
        elif indexed_table_va is not None:
            operand_kind = "indexed-jump-table"
        elif absolute_memory_va is not None:
            operand_kind = "absolute-memory"
        else:
            operand_kind = "computed-memory"
        rows.append({
            "functionLabel": label,
            "depth": depth,
            "path": path,
            "rangeHex": f"{hex32(va_range[0])}..{hex32(va_range[1])}",
            "instructionVaHex": hex32(start + index),
            "kind": "call" if reg_opcode == 2 else "jmp",
            "modRmHex": f"0x{modrm:02x}",
            "mod": mod,
            "rm": rm,
            "sibHex": f"0x{sib:02x}" if sib is not None else None,
            "operandKind": operand_kind,
            "displacementHex": hex32(disp_value) if isinstance(disp_value, int) and disp_value >= 0 else (
                f"-0x{abs(disp_value):x}" if isinstance(disp_value, int) else None
            ),
            "absoluteMemoryVaHex": hex32(absolute_memory_va),
            "absoluteMemorySection": section_name_for_va(sections, absolute_memory_va),
            "indexedJumpTableVaHex": hex32(indexed_table_va),
            "indexedJumpTableSection": section_name_for_va(sections, indexed_table_va),
            "indexedJumpTableEntryCount": len(table_targets),
            "indexedJumpTableTargetHexes": [hex32(value) for value in table_targets],
            "indexedJumpTableSampleTargetHexes": [hex32(value) for value in table_targets[:16]],
            "resolvedPointerHex": hex32(resolved_pointer),
            "resolvedPointerSection": section_name_for_va(sections, resolved_pointer),
            "transitionOperandLabel": transition_operand_label,
            "transitionResolvedLabel": transition_resolved_label,
            "transitionTableLabels": transition_table_labels,
            "routeOperandLabel": route_operand_label,
            "routeResolvedLabel": route_resolved_label,
            "routeTableLabels": route_table_labels,
        })
    return rows


def indirect_call_graph_rejection(rows: list[dict]) -> dict:
    static_absolute_rows = [row for row in rows if row.get("operandKind") == "absolute-memory"]
    indexed_table_rows = [row for row in rows if row.get("operandKind") == "indexed-jump-table"]
    indexed_table_audit_rows = indexed_jump_table_audit_rows(indexed_table_rows)
    indexed_table_target_hexes = [
        target_hex
        for row in indexed_table_rows
        for target_hex in row.get("indexedJumpTableTargetHexes") or []
        if target_hex
    ]
    indexed_table_target_vas = [int(target_hex, 16) for target_hex in indexed_table_target_hexes]
    indexed_table_target_class_counts: dict[str, int] = {}
    for target_va in indexed_table_target_vas:
        target_class = edge_handler_target_class(target_va)
        indexed_table_target_class_counts[target_class] = (
            indexed_table_target_class_counts.get(target_class, 0) + 1
        )
    outside_edge_handler_target_count = indexed_table_target_class_counts.get(
        "outside-edge-handler",
        0,
    )
    resolved_rows = [row for row in rows if row.get("resolvedPointerHex")]
    transition_hits = [
        row for row in rows
        if (
            row.get("transitionOperandLabel")
            or row.get("transitionResolvedLabel")
            or row.get("transitionTableLabels")
        )
    ]
    route_hits = [
        row for row in rows
        if row.get("routeOperandLabel") or row.get("routeResolvedLabel") or row.get("routeTableLabels")
    ]
    unresolved_rows = [
        row for row in rows
        if row.get("operandKind") in {"register", "computed-memory"}
    ]
    table_entry_count = sum(row.get("indexedJumpTableEntryCount") or 0 for row in indexed_table_rows)
    proof_found = bool(transition_hits or route_hits)
    if proof_found:
        classification = "bounded-indirect-callgraph-transition-candidate"
    elif rows:
        classification = "bounded-indirect-callgraph-no-static-transition-target"
    else:
        classification = "bounded-indirect-callgraph-no-indirect-candidates"
    return {
        "classification": classification,
        "proofFound": proof_found,
        "candidateCount": len(rows),
        "staticAbsoluteMemoryCandidateCount": len(static_absolute_rows),
        "indexedJumpTableCandidateCount": len(indexed_table_rows),
        "indexedJumpTableEntryCount": table_entry_count,
        "indexedJumpTableTargetCount": len(indexed_table_target_vas),
        "indexedJumpTableUniqueTargetCount": len(set(indexed_table_target_vas)),
        "indexedJumpTableTargetClassCounts": indexed_table_target_class_counts,
        "indexedJumpTableAuditRows": indexed_table_audit_rows,
        "indexedJumpTableAllTargetsLocalToEdgeHandlers": (
            bool(indexed_table_rows) and outside_edge_handler_target_count == 0
        ),
        "indexedJumpTableOutsideEdgeHandlerTargetCount": outside_edge_handler_target_count,
        "resolvedPointerCandidateCount": len(resolved_rows),
        "unresolvedRegisterOrComputedCount": len(unresolved_rows),
        "transitionTargetHitCount": len(transition_hits),
        "routeImmediateHitCount": len(route_hits),
        "transitionHitRows": transition_hits[:16],
        "routeHitRows": route_hits[:16],
        "sampleRows": rows[:32],
        "conclusion": (
            "The indirect call/jump-like opcodes reachable from the actor movement/collision ranges have no "
            "static absolute or resolved target matching the map loader, script runner, selector table, selected "
            "pointer, current root, or route map strings. Their indexed jump-table entries stay inside the "
            "actor-controller/collision-helper edge handlers, so they are local movement branches rather than "
            "map-transition proof."
        )
        if not proof_found
        else (
            "At least one indirect call/jump-like opcode has a static route or transition target candidate and "
            "must be reviewed before generic edge-trigger promotion."
        ),
    }


def direct_call_graph_evidence(
    exe: bytes,
    sections: list[dict],
    strings: dict[str, int],
    max_depth: int = CALL_GRAPH_MAX_DEPTH,
) -> dict:
    roots = [
        {
            "label": "actor-controller",
            "startVaHex": hex32(ACTOR_CONTROLLER_RANGE[0]),
            "rangeHex": f"{hex32(ACTOR_CONTROLLER_RANGE[0])}..{hex32(ACTOR_CONTROLLER_RANGE[1])}",
            "range": ACTOR_CONTROLLER_RANGE,
        },
        {
            "label": "collision-helper",
            "startVaHex": hex32(ACTOR_COLLISION_HELPER_RANGE[0]),
            "rangeHex": f"{hex32(ACTOR_COLLISION_HELPER_RANGE[0])}..{hex32(ACTOR_COLLISION_HELPER_RANGE[1])}",
            "range": ACTOR_COLLISION_HELPER_RANGE,
        },
    ]
    targets = {
        "mapLoaderFunction": MAP_LOADER_FUNCTION,
        "scriptRunnerFunction": SCRIPT_RUNNER_FUNCTION,
        "selectorTableFunctionOrData": SELECTOR_TABLE_FUNCTION_OR_DATA,
    }
    queue = []
    visited_ranges: set[tuple[int, int]] = set()
    visited_targets: set[int] = set()
    reachable_functions = []
    call_edges = []
    transition_hit_rows = []
    route_immediate_hit_rows = []
    indirect_rows = []
    indirect_count = 0
    transition_target_hit_count = 0
    route_immediate_hit_count = 0
    max_observed_depth = 0

    for root in roots:
        root_range = root["range"]
        queue.append({
            "label": root["label"],
            "depth": 0,
            "range": root_range,
            "path": [root["label"]],
        })

    while queue:
        item = queue.pop(0)
        va_range = item["range"]
        if va_range in visited_ranges:
            continue
        visited_ranges.add(va_range)
        depth = item["depth"]
        max_observed_depth = max(max_observed_depth, depth)
        refs = direct_ref_counts(exe, sections, strings, va_range)
        transition_hits = [
            {"target": name, "callVaHex": va_hex}
            for name, hits in (refs.get("transitionLikeDirectRelHits") or {}).items()
            for va_hex in hits
        ]
        route_immediate_count = sum([
            refs.get("selectedPointerGlobalImmediateCount", 0),
            refs.get("currentSelectorRootImmediateCount", 0),
            refs.get("sourceMapStringImmediateCount", 0),
            refs.get("targetMapStringImmediateCount", 0),
        ])
        if transition_hits:
            transition_hit_rows.append({
                "label": item["label"],
                "depth": depth,
                "rangeHex": refs.get("rangeHex"),
                "path": item["path"],
                "hits": transition_hits,
            })
        if route_immediate_count:
            route_immediate_hit_rows.append({
                "label": item["label"],
                "depth": depth,
                "rangeHex": refs.get("rangeHex"),
                "path": item["path"],
                "selectedPointerGlobalImmediateCount": refs.get("selectedPointerGlobalImmediateCount", 0),
                "currentSelectorRootImmediateCount": refs.get("currentSelectorRootImmediateCount", 0),
                "sourceMapStringImmediateCount": refs.get("sourceMapStringImmediateCount", 0),
                "targetMapStringImmediateCount": refs.get("targetMapStringImmediateCount", 0),
            })
        transition_target_hit_count += len(transition_hits)
        route_immediate_hit_count += route_immediate_count
        range_indirect_rows = indirect_call_like_rows(
            exe,
            sections,
            strings,
            va_range,
            item["label"],
            depth,
            item["path"],
        )
        indirect_rows.extend(range_indirect_rows)
        indirect_count += len(range_indirect_rows)
        reachable_functions.append({
            "label": item["label"],
            "depth": depth,
            "rangeHex": refs.get("rangeHex"),
            "path": item["path"],
            "directCallCount": len(direct_call_edges(exe, sections, va_range)),
            "transitionLikeDirectRelHitCount": len(transition_hits),
            "routeImmediateHitCount": route_immediate_count,
        })
        if depth >= max_depth:
            continue
        for edge in direct_call_edges(exe, sections, va_range):
            target_va = int(edge["targetVa"])
            label = next(
                (name for name, value in targets.items() if value == target_va),
                f"sub_{target_va:08x}",
            )
            call_edges.append({
                "from": item["label"],
                "depth": depth + 1,
                "callVaHex": edge["callVaHex"],
                "targetVaHex": edge["targetVaHex"],
                "targetLabel": label,
                "path": [*item["path"], f"{edge['callVaHex']}->{edge['targetVaHex']}"],
            })
            if target_va in visited_targets:
                continue
            visited_targets.add(target_va)
            queue.append({
                "label": label,
                "depth": depth + 1,
                "range": bounded_function_range(exe, sections, target_va),
                "path": [*item["path"], f"{edge['callVaHex']}->{edge['targetVaHex']}"],
            })

    reachable_target_vas = {
        int(edge["targetVaHex"], 16)
        for edge in call_edges
    }
    target_reachability = {
        name: value in reachable_target_vas
        for name, value in targets.items()
    }
    indirect_rejection = indirect_call_graph_rejection(indirect_rows)
    proof_found = (
        any(target_reachability.values())
        or transition_target_hit_count > 0
        or route_immediate_hit_count > 0
        or indirect_rejection["proofFound"] is True
    )
    classification = (
        "direct-callgraph-transition-path-found"
        if proof_found
        else "bounded-direct-callgraph-no-transition-path"
    )
    return {
        "classification": classification,
        "proofFound": proof_found,
        "maxDepth": max_depth,
        "observedMaxDepth": max_observed_depth,
        "rootRanges": [
            {key: value for key, value in root.items() if key != "range"}
            for root in roots
        ],
        "reachableFunctionCount": len(reachable_functions),
        "directCallEdgeCount": len(call_edges),
        "transitionTargetReachability": target_reachability,
        "transitionTargetReachableCount": sum(1 for value in target_reachability.values() if value),
        "transitionTargetHitCount": transition_target_hit_count,
        "routeImmediateHitCount": route_immediate_hit_count,
        "indirectCallLikeByteCount": indirect_count,
        "indirectCallGraphRejection": indirect_rejection,
        "indirectCallGraphRejectionClassification": indirect_rejection["classification"],
        "indirectCallGraphProofFound": indirect_rejection["proofFound"],
        "indirectCallGraphCandidateCount": indirect_rejection["candidateCount"],
        "indirectCallGraphStaticAbsoluteMemoryCandidateCount": indirect_rejection[
            "staticAbsoluteMemoryCandidateCount"
        ],
        "indirectCallGraphIndexedJumpTableCandidateCount": indirect_rejection[
            "indexedJumpTableCandidateCount"
        ],
        "indirectCallGraphIndexedJumpTableEntryCount": indirect_rejection[
            "indexedJumpTableEntryCount"
        ],
        "indirectCallGraphIndexedJumpTableTargetCount": indirect_rejection[
            "indexedJumpTableTargetCount"
        ],
        "indirectCallGraphIndexedJumpTableUniqueTargetCount": indirect_rejection[
            "indexedJumpTableUniqueTargetCount"
        ],
        "indirectCallGraphIndexedJumpTableTargetClassCounts": indirect_rejection[
            "indexedJumpTableTargetClassCounts"
        ],
        "indirectCallGraphIndexedJumpTableAuditRows": indirect_rejection[
            "indexedJumpTableAuditRows"
        ],
        "indirectCallGraphIndexedJumpTableAllTargetsLocalToEdgeHandlers": indirect_rejection[
            "indexedJumpTableAllTargetsLocalToEdgeHandlers"
        ],
        "indirectCallGraphIndexedJumpTableOutsideEdgeHandlerTargetCount": indirect_rejection[
            "indexedJumpTableOutsideEdgeHandlerTargetCount"
        ],
        "indirectCallGraphResolvedPointerCandidateCount": indirect_rejection[
            "resolvedPointerCandidateCount"
        ],
        "indirectCallGraphUnresolvedRegisterOrComputedCount": indirect_rejection[
            "unresolvedRegisterOrComputedCount"
        ],
        "indirectCallGraphTransitionTargetHitCount": indirect_rejection["transitionTargetHitCount"],
        "indirectCallGraphRouteImmediateHitCount": indirect_rejection["routeImmediateHitCount"],
        "transitionHitRows": transition_hit_rows[:24],
        "routeImmediateHitRows": route_immediate_hit_rows[:24],
        "sampleCallEdges": call_edges[:32],
        "reachableFunctions": reachable_functions,
        "sampleReachableFunctions": reachable_functions[:32],
        "conclusion": (
            "A bounded direct-call graph from the actor-controller and collision-helper ranges did not reach "
            "the map loader, script runner, selector table, selected-pointer global, current root, or route "
            "map strings. The indirect call/jump-like candidates in the same reachable graph also have no "
            "static route or transition target; computed operands still require runtime proof."
        )
        if not proof_found
        else (
            "A bounded direct-call graph found transition-like evidence. This must be reviewed before any "
            "generic edge trigger can be promoted."
        ),
    }


def compact_call_graph_depth_row(max_depth: int, evidence: dict) -> dict:
    return {
        "maxDepth": max_depth,
        "classification": evidence.get("classification"),
        "proofFound": evidence.get("proofFound"),
        "observedMaxDepth": evidence.get("observedMaxDepth"),
        "reachableFunctionCount": evidence.get("reachableFunctionCount"),
        "directCallEdgeCount": evidence.get("directCallEdgeCount"),
        "transitionTargetReachableCount": evidence.get("transitionTargetReachableCount"),
        "transitionTargetHitCount": evidence.get("transitionTargetHitCount"),
        "routeImmediateHitCount": evidence.get("routeImmediateHitCount"),
        "indirectCallLikeByteCount": evidence.get("indirectCallLikeByteCount"),
        "indirectCallGraphRejectionClassification": evidence.get(
            "indirectCallGraphRejectionClassification"
        ),
        "indirectCallGraphProofFound": evidence.get("indirectCallGraphProofFound"),
        "indirectCallGraphCandidateCount": evidence.get("indirectCallGraphCandidateCount"),
        "indirectCallGraphIndexedJumpTableCandidateCount": evidence.get(
            "indirectCallGraphIndexedJumpTableCandidateCount"
        ),
        "indirectCallGraphIndexedJumpTableEntryCount": evidence.get(
            "indirectCallGraphIndexedJumpTableEntryCount"
        ),
        "indirectCallGraphIndexedJumpTableTargetCount": evidence.get(
            "indirectCallGraphIndexedJumpTableTargetCount"
        ),
        "indirectCallGraphIndexedJumpTableUniqueTargetCount": evidence.get(
            "indirectCallGraphIndexedJumpTableUniqueTargetCount"
        ),
        "indirectCallGraphIndexedJumpTableTargetClassCounts": evidence.get(
            "indirectCallGraphIndexedJumpTableTargetClassCounts"
        ) or {},
        "indirectCallGraphIndexedJumpTableAllTargetsLocalToEdgeHandlers": evidence.get(
            "indirectCallGraphIndexedJumpTableAllTargetsLocalToEdgeHandlers"
        ),
        "indirectCallGraphIndexedJumpTableOutsideEdgeHandlerTargetCount": evidence.get(
            "indirectCallGraphIndexedJumpTableOutsideEdgeHandlerTargetCount"
        ),
        "indirectCallGraphTransitionTargetHitCount": evidence.get(
            "indirectCallGraphTransitionTargetHitCount"
        ),
        "indirectCallGraphRouteImmediateHitCount": evidence.get(
            "indirectCallGraphRouteImmediateHitCount"
        ),
    }


def direct_call_graph_depth_sensitivity(
    exe: bytes,
    sections: list[dict],
    strings: dict[str, int],
    depths: list[int] | None = None,
) -> dict:
    checked_depths = sorted(set(depths or CALL_GRAPH_DEPTH_SENSITIVITY_DEPTHS))
    rows = [
        compact_call_graph_depth_row(
            max_depth,
            direct_call_graph_evidence(exe, sections, strings, max_depth=max_depth),
        )
        for max_depth in checked_depths
    ]
    default_row = next(
        (row for row in rows if row.get("maxDepth") == CALL_GRAPH_MAX_DEPTH),
        rows[-1] if rows else {},
    )
    stable_keys = [
        "reachableFunctionCount",
        "directCallEdgeCount",
        "transitionTargetReachableCount",
        "transitionTargetHitCount",
        "routeImmediateHitCount",
        "indirectCallLikeByteCount",
        "indirectCallGraphCandidateCount",
        "indirectCallGraphIndexedJumpTableCandidateCount",
        "indirectCallGraphIndexedJumpTableEntryCount",
        "indirectCallGraphTransitionTargetHitCount",
        "indirectCallGraphRouteImmediateHitCount",
    ]
    baseline = tuple(default_row.get(key) for key in stable_keys)
    rows_at_or_beyond_default = [
        row for row in rows
        if isinstance(row.get("maxDepth"), int) and row["maxDepth"] >= CALL_GRAPH_MAX_DEPTH
    ]
    proof_absent = all(
        row.get("proofFound") is False and row.get("indirectCallGraphProofFound") is False
        for row in rows
    )
    counts_stable = bool(rows_at_or_beyond_default) and all(
        tuple(row.get(key) for key in stable_keys) == baseline
        for row in rows_at_or_beyond_default
    )
    max_checked = max(checked_depths) if checked_depths else None
    depth_text = ",".join(str(depth) for depth in checked_depths) or "-"
    return {
        "depthsChecked": checked_depths,
        "defaultDepth": CALL_GRAPH_MAX_DEPTH,
        "maxDepthChecked": max_checked,
        "proofAbsentAcrossCheckedDepths": proof_absent,
        "countsStableAtAndBeyondDefaultDepth": counts_stable,
        "defaultDepthRow": default_row,
        "rows": rows,
        "conclusion": (
            f"Depth sensitivity over max depths {depth_text} found no transition target, route immediate, "
            "or indirect route target. Counts are stable at and beyond the default depth, so increasing the "
            "bounded call-graph depth does not make the edge candidates promotable."
        ),
    }


def cns_string_vas(exe: bytes, sections: list[dict]) -> dict[str, int]:
    return {name[:-4]: va for va, name in find_cns_strings(exe, sections).items()}


def direct_ref_counts(
    exe: bytes,
    sections: list[dict],
    strings: dict[str, int],
    va_range: tuple[int, int],
) -> dict:
    source_string = strings.get(SOURCE)
    target_string = strings.get(TARGET)
    transition_targets = [
        ("mapLoaderFunction", MAP_LOADER_FUNCTION),
        ("scriptRunnerFunction", SCRIPT_RUNNER_FUNCTION),
        ("selectorTableFunctionOrData", SELECTOR_TABLE_FUNCTION_OR_DATA),
    ]
    return {
        "rangeHex": f"{hex32(va_range[0])}..{hex32(va_range[1])}",
        "mapLoaderDirectRelHitCount": len(rel_target_hits(exe, sections, va_range, MAP_LOADER_FUNCTION)),
        "scriptRunnerDirectRelHitCount": len(rel_target_hits(exe, sections, va_range, SCRIPT_RUNNER_FUNCTION)),
        "selectorTableDirectRelHitCount": len(
            rel_target_hits(exe, sections, va_range, SELECTOR_TABLE_FUNCTION_OR_DATA)
        ),
        "transitionLikeDirectRelHits": {
            name: rel_target_hits(exe, sections, va_range, target)
            for name, target in transition_targets
        },
        "selectedPointerGlobalImmediateCount": count_raw_immediate(
            exe, sections, va_range, SELECTED_POINTER_GLOBAL
        ),
        "currentSelectorRootImmediateCount": count_raw_immediate(
            exe, sections, va_range, CURRENT_SELECTOR_ROOT
        ),
        "sourceMapStringImmediateCount": (
            count_raw_immediate(exe, sections, va_range, source_string) if source_string else 0
        ),
        "targetMapStringImmediateCount": (
            count_raw_immediate(exe, sections, va_range, target_string) if target_string else 0
        ),
    }


def classify_latch_reference(exe: bytes, sections: list[dict], ref_va: int) -> dict:
    offset = va_to_offset(sections, ref_va)
    instruction_va = ref_va
    if offset is None:
        return {
            "referenceVaHex": hex32(ref_va),
            "instructionVaHex": hex32(instruction_va),
            "instructionBytesHex": "",
            "kind": "unknown",
            "range": "unknown",
            "valueHex": None,
        }
    kind = "immediate"
    value = None
    if offset >= 2 and exe[offset - 2:offset] == b"\xc6\x05":
        kind = "write-immediate-u8"
        instruction_va = ref_va - 2
        if offset + 4 < len(exe):
            value = exe[offset + 4]
    elif offset >= 1 and exe[offset - 1] == 0xA0:
        kind = "read-al"
        instruction_va = ref_va - 1
    elif offset >= 1 and exe[offset - 1] == 0xA2:
        kind = "write-al"
        instruction_va = ref_va - 1
    elif offset >= 3 and exe[offset - 3:offset] == b"\x0f\xb6\x05":
        kind = "read-movzx-u8"
        instruction_va = ref_va - 3
    elif offset >= 2 and exe[offset - 2:offset] in (b"\x8a\x0d", b"\x8a\x05"):
        kind = "read-byte-to-reg"
        instruction_va = ref_va - 2
    if ACTOR_CONTROLLER_RANGE[0] <= ref_va < ACTOR_CONTROLLER_RANGE[1]:
        range_name = "actor-controller"
    elif ACTOR_COLLISION_HELPER_RANGE[0] <= ref_va < ACTOR_COLLISION_HELPER_RANGE[1]:
        range_name = "collision-helper"
    else:
        range_name = "other-text"
    return {
        "referenceVaHex": hex32(ref_va),
        "instructionVaHex": hex32(instruction_va),
        "instructionBytesHex": bytes_hex(exe, sections, instruction_va, 12),
        "kind": kind,
        "range": range_name,
        "valueHex": f"0x{value:02x}" if value is not None else None,
    }


def route_immediate_count(near_refs: dict) -> int:
    return sum([
        near_refs.get("selectedPointerGlobalImmediateCount", 0),
        near_refs.get("currentSelectorRootImmediateCount", 0),
        near_refs.get("sourceMapStringImmediateCount", 0),
        near_refs.get("targetMapStringImmediateCount", 0),
    ])


def transition_rel_count(near_refs: dict) -> int:
    return sum(
        len(values)
        for values in (near_refs.get("transitionLikeDirectRelHits") or {}).values()
    )


def classify_latch_other_text_consumer(row: dict, near_refs: dict) -> dict:
    instruction_va = int(str(row.get("instructionVaHex") or "0x0"), 16)
    local_transition_count = transition_rel_count(near_refs)
    local_route_count = route_immediate_count(near_refs)
    if instruction_va == 0x00430155:
        consumer_class = "movement-state-initializer-reset"
        effect = "writes direction latch 0x00 while resetting actor/list display state"
    elif 0x0043135D <= instruction_va <= 0x0043138D:
        consumer_class = "actor-state-continuation-read"
        effect = "compares latch against 0x05..0x08 and selects actor +0x68 state 0x07 or 0x03"
    elif 0x004313BB <= instruction_va <= 0x004313EB:
        consumer_class = "actor-state-continuation-read"
        effect = "compares latch against 0x05..0x08 and selects actor +0x68 state 0x08 or 0x04"
    else:
        consumer_class = "unclassified-other-text-reference"
        effect = "not classified by the current static audit"
    return {
        **row,
        "consumerClass": consumer_class,
        "observedEffect": effect,
        "windowHex": near_refs.get("rangeHex"),
        "transitionLikeWindowRelHitCount": local_transition_count,
        "routeImmediateWindowHitCount": local_route_count,
        "transitionConsumerProofFound": local_transition_count > 0 or local_route_count > 0,
    }


def direction_latch_reference_evidence(exe: bytes, sections: list[dict], strings: dict[str, int]) -> dict:
    text_start, text_end = section_range(sections, ".text")
    text = range_bytes(exe, sections, (text_start, text_end))
    pattern = struct.pack("<I", DIRECTION_LATCH_GLOBAL)
    refs = []
    index = 0
    transition_rel_hit_vas = set()
    route_immediate_window_hit_count = 0
    route_window_hit_rows = []
    window_ranges = []
    by_kind: dict[str, int] = {}
    by_range: dict[str, int] = {}
    other_text_audit_rows = []
    while True:
        hit = text.find(pattern, index)
        if hit < 0:
            break
        ref_va = text_start + hit
        row = classify_latch_reference(exe, sections, ref_va)
        window = (
            max(text_start, ref_va - LATCH_WINDOW_BEFORE),
            min(text_end, ref_va + LATCH_WINDOW_AFTER),
        )
        near_refs = direct_ref_counts(exe, sections, strings, window)
        window_ranges.append({
            "label": f"direction-latch:{hex32(ref_va)}",
            "range": window,
            "rangeHex": near_refs.get("rangeHex"),
        })
        for hits in (near_refs.get("transitionLikeDirectRelHits") or {}).values():
            for va_hex in hits:
                transition_rel_hit_vas.add(va_hex)
        row_route_immediate_count = route_immediate_count(near_refs)
        route_immediate_window_hit_count += row_route_immediate_count
        if row_route_immediate_count or any(near_refs.get("transitionLikeDirectRelHits", {}).values()):
            route_window_hit_rows.append({
                **row,
                "windowHex": near_refs.get("rangeHex"),
                "nearReferences": near_refs,
            })
        if row["range"] == "other-text":
            other_text_audit_rows.append(classify_latch_other_text_consumer(row, near_refs))
        refs.append(row)
        by_kind[row["kind"]] = by_kind.get(row["kind"], 0) + 1
        by_range[row["range"]] = by_range.get(row["range"], 0) + 1
        index = hit + 1
    read_count = sum(
        count for kind, count in by_kind.items()
        if kind.startswith("read")
    )
    write_count = sum(
        count for kind, count in by_kind.items()
        if kind.startswith("write")
    )
    other_text_transition_consumer_count = sum(
        1 for row in other_text_audit_rows
        if row.get("transitionConsumerProofFound") is True
    )
    other_text_reset_count = sum(
        1 for row in other_text_audit_rows
        if row.get("consumerClass") == "movement-state-initializer-reset"
    )
    other_text_actor_state_read_count = sum(
        1 for row in other_text_audit_rows
        if row.get("consumerClass") == "actor-state-continuation-read"
    )
    other_text_unclassified_count = sum(
        1 for row in other_text_audit_rows
        if row.get("consumerClass") == "unclassified-other-text-reference"
    )
    other_text_route_window_hit_count = sum(
        row.get("routeImmediateWindowHitCount", 0)
        for row in other_text_audit_rows
    )
    other_text_transition_window_hit_count = sum(
        row.get("transitionLikeWindowRelHitCount", 0)
        for row in other_text_audit_rows
    )
    return {
        "directionLatchGlobalHex": hex32(DIRECTION_LATCH_GLOBAL),
        "textRangeHex": f"{hex32(text_start)}..{hex32(text_end)}",
        "windowBeforeBytes": LATCH_WINDOW_BEFORE,
        "windowAfterBytes": LATCH_WINDOW_AFTER,
        "directRefCount": len(refs),
        "readRefCount": read_count,
        "writeRefCount": write_count,
        "otherTextRefCount": by_range.get("other-text", 0),
        "byKind": dict(sorted(by_kind.items())),
        "byRange": dict(sorted(by_range.items())),
        "transitionLikeWindowRelHitCount": len(transition_rel_hit_vas),
        "transitionLikeWindowRelHitVaHexes": sorted(transition_rel_hit_vas),
        "routeImmediateWindowHitCount": route_immediate_window_hit_count,
        "routeWindowHitRows": route_window_hit_rows,
        "windowRanges": window_ranges,
        "allOtherTextReferences": other_text_audit_rows,
        "otherTextTransitionConsumerCount": other_text_transition_consumer_count,
        "otherTextResetCount": other_text_reset_count,
        "otherTextActorStateContinuationReadCount": other_text_actor_state_read_count,
        "otherTextUnclassifiedCount": other_text_unclassified_count,
        "otherTextRouteImmediateWindowHitCount": other_text_route_window_hit_count,
        "otherTextTransitionLikeWindowRelHitCount": other_text_transition_window_hit_count,
        "otherTextReferencesClassifiedNonTransition": (
            other_text_transition_consumer_count == 0
            and other_text_unclassified_count == 0
            and len(other_text_audit_rows) == by_range.get("other-text", 0)
        ),
        "sampleReferences": refs[:20],
        "conclusion": (
            "All direct .text references to the direction latch were scanned with local windows. None of those "
            "windows contains a direct map-loader/script-runner/selector-table branch or a selected-pointer, "
            "current-root, source-map, or target-map immediate. The refs outside the bounded helper/controller "
            "ranges are one movement-state reset and two nearby actor-state compare clusters that select actor "
            "+0x68 states, not a map transition consumer. Direction latch use therefore remains actor "
            "movement/collision state, not edge-transition proof."
        ),
    }


def global_transition_reference_evidence(exe: bytes, sections: list[dict], strings: dict[str, int]) -> dict:
    text_range = section_range(sections, ".text")
    direct_refs = direct_ref_counts(exe, sections, strings, text_range)
    actor_controller_hits = rel_target_hits(exe, sections, text_range, ACTOR_CONTROLLER_FUNCTION)
    collision_helper_hits = rel_target_hits(exe, sections, text_range, ACTOR_COLLISION_HELPER_FUNCTION)
    return {
        "textRangeHex": f"{hex32(text_range[0])}..{hex32(text_range[1])}",
        "directReferences": direct_refs,
        "actorControllerDirectRelHitCount": len(actor_controller_hits),
        "actorControllerDirectRelHitVaHexes": actor_controller_hits[:24],
        "collisionHelperDirectRelHitCount": len(collision_helper_hits),
        "collisionHelperDirectRelHitVaHexes": collision_helper_hits[:24],
        "conclusion": (
            "A whole-.text direct-reference scan finds actor movement/collision entry points, but no direct "
            "map-loader or selector-table branch tied to the edge path. The few direct script-runner calls are "
            "outside the actor movement/collision ranges and do not carry the current source/target immediates."
        ),
    }


def caller_window_evidence(
    exe: bytes,
    sections: list[dict],
    strings: dict[str, int],
    target: int,
    label: str,
) -> dict:
    text_start, text_end = section_range(sections, ".text")
    hit_vas = rel_target_hits(exe, sections, (text_start, text_end), target)
    route_immediate_window_hit_count = 0
    transition_rel_hit_vas = set()
    route_window_hit_rows = []
    sample_rows = []
    window_ranges = []
    for hit_hex in hit_vas:
        hit_va = int(hit_hex, 16)
        window = (
            max(text_start, hit_va - CALLER_WINDOW_BEFORE),
            min(text_end, hit_va + CALLER_WINDOW_AFTER),
        )
        near_refs = direct_ref_counts(exe, sections, strings, window)
        window_ranges.append({
            "label": f"{label}-caller:{hit_hex}",
            "range": window,
            "rangeHex": near_refs.get("rangeHex"),
        })
        transition_hits = [
            va_hex
            for hits in (near_refs.get("transitionLikeDirectRelHits") or {}).values()
            for va_hex in hits
        ]
        for va_hex in transition_hits:
            transition_rel_hit_vas.add(va_hex)
        route_immediate_count = sum([
            near_refs.get("selectedPointerGlobalImmediateCount", 0),
            near_refs.get("currentSelectorRootImmediateCount", 0),
            near_refs.get("sourceMapStringImmediateCount", 0),
            near_refs.get("targetMapStringImmediateCount", 0),
        ])
        route_immediate_window_hit_count += route_immediate_count
        row = {
            "callVaHex": hit_hex,
            "windowHex": near_refs.get("rangeHex"),
            "transitionLikeDirectRelHitCount": len(transition_hits),
            "routeImmediateHitCount": route_immediate_count,
            "nearReferences": near_refs,
        }
        if len(sample_rows) < 24:
            sample_rows.append(row)
        if route_immediate_count or transition_hits:
            route_window_hit_rows.append(row)
    return {
        "targetLabel": label,
        "targetVaHex": hex32(target),
        "textRangeHex": f"{hex32(text_start)}..{hex32(text_end)}",
        "windowBeforeBytes": CALLER_WINDOW_BEFORE,
        "windowAfterBytes": CALLER_WINDOW_AFTER,
        "callerCount": len(hit_vas),
        "transitionLikeWindowRelHitCount": len(transition_rel_hit_vas),
        "transitionLikeWindowRelHitVaHexes": sorted(transition_rel_hit_vas),
        "routeImmediateWindowHitCount": route_immediate_window_hit_count,
        "routeWindowHitRows": route_window_hit_rows,
        "windowRanges": window_ranges,
        "sampleCallerWindows": sample_rows,
        "conclusion": (
            f"All direct callers of {label} were scanned with local windows. Any transition-like branch "
            "or source/target/current-root immediate near those calls must still be tied to the edge path "
            "before it can prove a generic boundary transition."
        ),
    }


def script_runner_call_context_evidence(exe: bytes, sections: list[dict], strings: dict[str, int]) -> dict:
    text_start, text_end = section_range(sections, ".text")
    hit_vas = rel_target_hits(exe, sections, (text_start, text_end), SCRIPT_RUNNER_FUNCTION)
    sample_rows = []
    window_ranges = []
    totals = {
        "selectedPointerGlobalImmediateWindowHitCount": 0,
        "currentSelectorRootImmediateWindowHitCount": 0,
        "sourceMapStringImmediateWindowHitCount": 0,
        "targetMapStringImmediateWindowHitCount": 0,
        "mapLoaderWindowRelHitCount": 0,
        "selectorTableWindowRelHitCount": 0,
    }
    actor_range_count = 0
    collision_range_count = 0
    for hit_hex in hit_vas:
        hit_va = int(hit_hex, 16)
        if ACTOR_CONTROLLER_RANGE[0] <= hit_va < ACTOR_CONTROLLER_RANGE[1]:
            actor_range_count += 1
        if ACTOR_COLLISION_HELPER_RANGE[0] <= hit_va < ACTOR_COLLISION_HELPER_RANGE[1]:
            collision_range_count += 1
        window = (
            max(text_start, hit_va - CALLER_WINDOW_BEFORE),
            min(text_end, hit_va + CALLER_WINDOW_AFTER),
        )
        refs = direct_ref_counts(exe, sections, strings, window)
        window_ranges.append({
            "label": f"script-runner-call:{hit_hex}",
            "range": window,
            "rangeHex": refs.get("rangeHex"),
        })
        totals["selectedPointerGlobalImmediateWindowHitCount"] += refs.get(
            "selectedPointerGlobalImmediateCount", 0
        )
        totals["currentSelectorRootImmediateWindowHitCount"] += refs.get(
            "currentSelectorRootImmediateCount", 0
        )
        totals["sourceMapStringImmediateWindowHitCount"] += refs.get(
            "sourceMapStringImmediateCount", 0
        )
        totals["targetMapStringImmediateWindowHitCount"] += refs.get(
            "targetMapStringImmediateCount", 0
        )
        totals["mapLoaderWindowRelHitCount"] += refs.get("mapLoaderDirectRelHitCount", 0)
        totals["selectorTableWindowRelHitCount"] += refs.get("selectorTableDirectRelHitCount", 0)
        sample_rows.append({
            "callVaHex": hit_hex,
            "windowHex": refs.get("rangeHex"),
            "selectedPointerGlobalImmediateCount": refs.get("selectedPointerGlobalImmediateCount", 0),
            "currentSelectorRootImmediateCount": refs.get("currentSelectorRootImmediateCount", 0),
            "sourceMapStringImmediateCount": refs.get("sourceMapStringImmediateCount", 0),
            "targetMapStringImmediateCount": refs.get("targetMapStringImmediateCount", 0),
            "mapLoaderDirectRelHitCount": refs.get("mapLoaderDirectRelHitCount", 0),
            "selectorTableDirectRelHitCount": refs.get("selectorTableDirectRelHitCount", 0),
        })
    route_immediate_count = sum(
        totals[key]
        for key in (
            "selectedPointerGlobalImmediateWindowHitCount",
            "currentSelectorRootImmediateWindowHitCount",
            "sourceMapStringImmediateWindowHitCount",
            "targetMapStringImmediateWindowHitCount",
        )
    )
    return {
        "targetLabel": "script-runner",
        "targetVaHex": hex32(SCRIPT_RUNNER_FUNCTION),
        "textRangeHex": f"{hex32(text_start)}..{hex32(text_end)}",
        "windowBeforeBytes": CALLER_WINDOW_BEFORE,
        "windowAfterBytes": CALLER_WINDOW_AFTER,
        "callerCount": len(hit_vas),
        "actorControllerRangeCallerCount": actor_range_count,
        "collisionHelperRangeCallerCount": collision_range_count,
        "routeImmediateWindowHitCount": route_immediate_count,
        **totals,
        "windowRanges": window_ranges,
        "sampleCallWindows": sample_rows,
        "conclusion": (
            "All direct script-runner calls were scanned with local windows. They are outside the "
            "actor-controller/collision-helper ranges and their windows contain no map-loader or selector-table "
            "rel branches and no selected-pointer, current-root, source-map, or target-map immediates."
        ),
    }


def selected_pointer_immediate_context_evidence(
    exe: bytes,
    sections: list[dict],
    strings: dict[str, int],
) -> dict:
    text_start, text_end = section_range(sections, ".text")
    hit_vas = raw_immediate_hits(exe, sections, (text_start, text_end), SELECTED_POINTER_GLOBAL)
    totals = {
        "currentSelectorRootImmediateWindowHitCount": 0,
        "sourceMapStringImmediateWindowHitCount": 0,
        "targetMapStringImmediateWindowHitCount": 0,
        "mapLoaderWindowRelHitCount": 0,
        "scriptRunnerWindowRelHitCount": 0,
        "selectorTableWindowRelHitCount": 0,
    }
    sample_rows = []
    window_ranges = []
    for hit_hex in hit_vas:
        hit_va = int(hit_hex, 16)
        window = (
            max(text_start, hit_va - CALLER_WINDOW_BEFORE),
            min(text_end, hit_va + CALLER_WINDOW_AFTER),
        )
        refs = direct_ref_counts(exe, sections, strings, window)
        window_ranges.append({
            "label": f"selected-pointer-ref:{hit_hex}",
            "range": window,
            "rangeHex": refs.get("rangeHex"),
        })
        totals["currentSelectorRootImmediateWindowHitCount"] += refs.get(
            "currentSelectorRootImmediateCount", 0
        )
        totals["sourceMapStringImmediateWindowHitCount"] += refs.get(
            "sourceMapStringImmediateCount", 0
        )
        totals["targetMapStringImmediateWindowHitCount"] += refs.get(
            "targetMapStringImmediateCount", 0
        )
        totals["mapLoaderWindowRelHitCount"] += refs.get("mapLoaderDirectRelHitCount", 0)
        totals["scriptRunnerWindowRelHitCount"] += refs.get("scriptRunnerDirectRelHitCount", 0)
        totals["selectorTableWindowRelHitCount"] += refs.get("selectorTableDirectRelHitCount", 0)
        sample_rows.append({
            "refVaHex": hit_hex,
            "windowHex": refs.get("rangeHex"),
            "selectedPointerGlobalImmediateCount": refs.get("selectedPointerGlobalImmediateCount", 0),
            "currentSelectorRootImmediateCount": refs.get("currentSelectorRootImmediateCount", 0),
            "sourceMapStringImmediateCount": refs.get("sourceMapStringImmediateCount", 0),
            "targetMapStringImmediateCount": refs.get("targetMapStringImmediateCount", 0),
            "mapLoaderDirectRelHitCount": refs.get("mapLoaderDirectRelHitCount", 0),
            "scriptRunnerDirectRelHitCount": refs.get("scriptRunnerDirectRelHitCount", 0),
            "selectorTableDirectRelHitCount": refs.get("selectorTableDirectRelHitCount", 0),
        })
    route_specific_window_hit_count = sum(totals.values())
    return {
        "targetLabel": "selected-pointer-global",
        "targetVaHex": hex32(SELECTED_POINTER_GLOBAL),
        "textRangeHex": f"{hex32(text_start)}..{hex32(text_end)}",
        "windowBeforeBytes": CALLER_WINDOW_BEFORE,
        "windowAfterBytes": CALLER_WINDOW_AFTER,
        "immediateRefCount": len(hit_vas),
        "routeSpecificWindowHitCount": route_specific_window_hit_count,
        **totals,
        "windowRanges": window_ranges,
        "sampleReferenceWindows": sample_rows,
        "conclusion": (
            "All direct selected-pointer global immediates in .text were scanned with local windows. "
            "Those windows do not contain current-root, source-map, target-map, map-loader, script-runner, "
            "or selector-table evidence that would tie the global selected-pointer access to this edge path."
        ),
    }


def boundary_case_evidence(exe: bytes, sections: list[dict], runtime_movement: dict) -> list[dict]:
    helper = ((runtime_movement.get("actorMotion") or {}).get("collisionHelper") or {})
    cases = {
        row.get("direction"): row
        for row in helper.get("directionCases") or []
    }
    rows = []
    for direction, expected in BOUNDARY_SNIPPETS.items():
        expected_bytes = bytes.fromhex(expected["expectedHex"])
        actual_hex = bytes_hex(exe, sections, expected["edgeCheckVa"], len(expected_bytes))
        jump_target = rel32_target(exe, sections, expected["caseExitJumpVa"])
        case = cases.get(direction) or {}
        rows.append({
            "direction": direction,
            "inputLatchHex": case.get("inputLatchHex"),
            "boundaryRule": case.get("boundaryRule"),
            "edgeCheckVaHex": hex32(expected["edgeCheckVa"]),
            "caseExitJumpVaHex": hex32(expected["caseExitJumpVa"]),
            "caseExitJumpTargetHex": hex32(jump_target),
            "fallsThroughToActorOverlapLoop": jump_target == ACTOR_OVERLAP_LOOP,
            "blockedFallbackLatchHex": expected["blockedFallbackLatchHex"],
            "expectedSnippetHex": expected["expectedHex"],
            "actualSnippetHex": actual_hex,
            "snippetMatchesExpected": actual_hex == expected["expectedHex"],
        })
    return rows


def source_boundary_match(candidate: dict, source_stats: dict) -> bool:
    side = candidate.get("side")
    x = candidate.get("x")
    y = candidate.get("y")
    width = source_stats.get("width")
    height = source_stats.get("height")
    if side == "top":
        return y == 0
    if side == "bottom":
        return isinstance(height, int) and y == height - 1
    if side == "left":
        return x == 0
    if side == "right":
        return isinstance(width, int) and x == width - 1
    return False


def candidate_rows(original_collision_route_audit: dict, runtime_movement: dict) -> list[dict]:
    route_candidates = original_collision_route_audit.get("routeCandidates") or []
    stats = (original_collision_route_audit.get("originalLayer1MapStats") or {}).get(SOURCE) or {}
    case_rows = {
        row.get("direction"): row
        for row in (((runtime_movement.get("actorMotion") or {}).get("collisionHelper") or {}).get("directionCases") or [])
    }
    rows = []
    for candidate in route_candidates:
        side = candidate.get("side")
        direction = SIDE_TO_DIRECTION.get(str(side), "")
        case = case_rows.get(direction) or {}
        at_source_boundary = source_boundary_match(candidate, stats)
        out_of_bounds = candidate.get("outwardMoveWouldStayInBounds") is False
        rows.append({
            "side": side,
            "direction": direction,
            "x": candidate.get("x"),
            "y": candidate.get("y"),
            "edgeDistance": candidate.get("edgeDistance"),
            "autoTrigger": candidate.get("autoTrigger"),
            "sourceAtOuterBoundary": at_source_boundary,
            "outwardMoveWouldStayInBounds": candidate.get("outwardMoveWouldStayInBounds"),
            "outwardMoveAllowedInsideMap": candidate.get("outwardMoveAllowedInsideMap"),
            "boundaryCandidate": at_source_boundary and out_of_bounds,
            "originalStandable": candidate.get("originalStandable"),
            "outwardFootprintDirectionClear": candidate.get("outwardFootprintDirectionClear"),
            "boundaryRule": case.get("boundaryRule"),
            "edgeCheckVaHex": case.get("edgeCheckVaHex"),
            "blockedFallbackLatchHex": case.get("blockedFallbackLatchHex"),
            "targetSpawn": candidate.get("targetSpawnOriginalLayer1Flags") or {},
            "routeAssistUrl": candidate.get("routeAssistUrl"),
        })
    return rows


def build_summary(
    exe_path: Path = EXE,
    runtime_movement: dict | None = None,
    original_collision_route_audit: dict | None = None,
) -> dict:
    runtime_movement = runtime_movement or load_json(OUT / "runtime_movement.json", {})
    original_collision_route_audit = original_collision_route_audit or load_json(
        OUT / "original_collision_route_audit.json",
        {},
    )
    exe = exe_path.read_bytes()
    sections = read_sections(exe)
    strings = cns_string_vas(exe, sections)
    candidates = candidate_rows(original_collision_route_audit, runtime_movement)
    boundary_candidates = [row for row in candidates if row.get("boundaryCandidate")]
    helper_refs = direct_ref_counts(exe, sections, strings, ACTOR_COLLISION_HELPER_RANGE)
    controller_refs = direct_ref_counts(exe, sections, strings, ACTOR_CONTROLLER_RANGE)
    latch_refs = direction_latch_reference_evidence(exe, sections, strings)
    global_refs = global_transition_reference_evidence(exe, sections, strings)
    script_runner_call_context = script_runner_call_context_evidence(exe, sections, strings)
    selected_pointer_context = selected_pointer_immediate_context_evidence(exe, sections, strings)
    direct_call_graph = direct_call_graph_evidence(exe, sections, strings)
    call_graph_depth_sensitivity = direct_call_graph_depth_sensitivity(exe, sections, strings)
    actor_controller_caller_windows = caller_window_evidence(
        exe,
        sections,
        strings,
        ACTOR_CONTROLLER_FUNCTION,
        "actor-controller",
    )
    collision_helper_caller_windows = caller_window_evidence(
        exe,
        sections,
        strings,
        ACTOR_COLLISION_HELPER_FUNCTION,
        "collision-helper",
    )
    controller_helper_calls = rel_target_hits(
        exe,
        sections,
        ACTOR_CONTROLLER_RANGE,
        ACTOR_COLLISION_HELPER_FUNCTION,
    )
    boundary_cases = boundary_case_evidence(exe, sections, runtime_movement)
    all_boundary_snippets_match = all(row.get("snippetMatchesExpected") for row in boundary_cases)
    all_boundary_cases_fall_to_overlap = all(row.get("fallsThroughToActorOverlapLoop") for row in boundary_cases)
    transition_ref_count = sum(
        len(helper_refs["transitionLikeDirectRelHits"][name])
        + len(controller_refs["transitionLikeDirectRelHits"][name])
        for name in helper_refs["transitionLikeDirectRelHits"]
    )
    direct_route_ref_count = sum([
        helper_refs["selectedPointerGlobalImmediateCount"],
        helper_refs["currentSelectorRootImmediateCount"],
        helper_refs["sourceMapStringImmediateCount"],
        helper_refs["targetMapStringImmediateCount"],
        controller_refs["selectedPointerGlobalImmediateCount"],
        controller_refs["currentSelectorRootImmediateCount"],
        controller_refs["sourceMapStringImmediateCount"],
        controller_refs["targetMapStringImmediateCount"],
    ])
    encoded_targets = encoded_scan_targets(strings)
    edge_handler_encoded_scan = encoded_scan_for_ranges(
        exe,
        sections,
        [
            {"label": "collision-helper", "range": ACTOR_COLLISION_HELPER_RANGE},
            {"label": "actor-controller", "range": ACTOR_CONTROLLER_RANGE},
        ],
        encoded_targets,
    )
    latch_caller_ranges = unique_window_ranges([
        *(latch_refs.get("windowRanges") or []),
        *(actor_controller_caller_windows.get("windowRanges") or []),
        *(collision_helper_caller_windows.get("windowRanges") or []),
    ])
    edge_local_window_encoded_scan = encoded_scan_for_ranges(
        exe,
        sections,
        latch_caller_ranges,
        encoded_targets,
    )
    call_graph_ranges = [
        {
            "label": f"callgraph:{row.get('label')}",
            "range": parse_range_hex(row.get("rangeHex")),
        }
        for row in direct_call_graph.get("reachableFunctions") or []
    ]
    call_graph_encoded_scan = encoded_scan_for_ranges(
        exe,
        sections,
        call_graph_ranges,
        encoded_targets,
    )
    global_contrast_window_encoded_scan = encoded_scan_for_ranges(
        exe,
        sections,
        unique_window_ranges([
            *(script_runner_call_context.get("windowRanges") or []),
            *(selected_pointer_context.get("windowRanges") or []),
        ]),
        encoded_targets,
    )
    return {
        "title": "map1_01a Edge Trigger Gap",
        "source": SOURCE,
        "target": TARGET,
        "promotionStatus": "blocked-generic-edge-trigger-unproven",
        "promotionAllowed": False,
        "proofFound": False,
        "edgeTriggerProofFound": False,
        "failedEdgeTriggerGateIds": FAILED_EDGE_TRIGGER_GATE_IDS,
        "missingEvidence": EDGE_TRIGGER_MISSING_EVIDENCE,
        "evidenceRefs": EDGE_TRIGGER_EVIDENCE_REFS,
        "evidenceRefCount": len(EDGE_TRIGGER_EVIDENCE_REFS),
        "routeCandidateCount": len(candidates),
        "sourceBoundaryCandidateCount": len(boundary_candidates),
        "autoBoundaryCandidateCount": sum(1 for row in boundary_candidates if row.get("autoTrigger") is True),
        "candidateRows": candidates,
        "collisionHelperEvidence": {
            "functionVaHex": hex32(ACTOR_COLLISION_HELPER_FUNCTION),
            "rangeHex": f"{hex32(ACTOR_COLLISION_HELPER_RANGE[0])}..{hex32(ACTOR_COLLISION_HELPER_RANGE[1])}",
            "directionLatchGlobalHex": hex32(DIRECTION_LATCH_GLOBAL),
            "actorOverlapLoopVaHex": hex32(ACTOR_OVERLAP_LOOP),
            "boundaryCases": boundary_cases,
            "allBoundarySnippetsMatchExpected": all_boundary_snippets_match,
            "allBoundaryCasesFallThroughToActorOverlapLoop": all_boundary_cases_fall_to_overlap,
            "directReferences": helper_refs,
        },
        "actorControllerEvidence": {
            "functionVaHex": hex32(ACTOR_CONTROLLER_FUNCTION),
            "rangeHex": f"{hex32(ACTOR_CONTROLLER_RANGE[0])}..{hex32(ACTOR_CONTROLLER_RANGE[1])}",
            "collisionHelperCallCount": len(controller_helper_calls),
            "collisionHelperCallVaHexes": controller_helper_calls,
            "directReferences": controller_refs,
        },
        "directionLatchReferenceEvidence": latch_refs,
        "globalTransitionReferenceEvidence": global_refs,
        "scriptRunnerCallContextEvidence": script_runner_call_context,
        "selectedPointerImmediateContextEvidence": selected_pointer_context,
        "encodedScanTargets": encoded_targets,
        "edgeHandlerEncodedTargetScan": edge_handler_encoded_scan,
        "edgeHandlerEncodedTargetClassification": edge_handler_encoded_scan["classification"],
        "edgeHandlerEncodedTargetRawScalarCandidateCount": edge_handler_encoded_scan[
            "rawScalarCandidateCount"
        ],
        "edgeHandlerEncodedTargetTransitionRawScalarCandidateCount": edge_handler_encoded_scan[
            "transitionRawScalarCandidateCount"
        ],
        "edgeHandlerEncodedTargetRouteProofRawScalarCandidateCount": edge_handler_encoded_scan[
            "routeProofRawScalarCandidateCount"
        ],
        "edgeHandlerEncodedTargetSelectedPointerRawScalarCandidateCount": edge_handler_encoded_scan[
            "selectedPointerRawScalarCandidateCount"
        ],
        "edgeHandlerEncodedTargetPromotingCandidateCount": edge_handler_encoded_scan[
            "promotingCandidateCount"
        ],
        "edgeLocalWindowEncodedTargetScan": edge_local_window_encoded_scan,
        "edgeLocalWindowEncodedTargetClassification": edge_local_window_encoded_scan["classification"],
        "edgeLocalWindowEncodedTargetRawScalarCandidateCount": edge_local_window_encoded_scan[
            "rawScalarCandidateCount"
        ],
        "edgeLocalWindowEncodedTargetTransitionRawScalarCandidateCount": edge_local_window_encoded_scan[
            "transitionRawScalarCandidateCount"
        ],
        "edgeLocalWindowEncodedTargetRouteProofRawScalarCandidateCount": edge_local_window_encoded_scan[
            "routeProofRawScalarCandidateCount"
        ],
        "edgeLocalWindowEncodedTargetSelectedPointerRawScalarCandidateCount": edge_local_window_encoded_scan[
            "selectedPointerRawScalarCandidateCount"
        ],
        "edgeLocalWindowEncodedTargetPromotingCandidateCount": edge_local_window_encoded_scan[
            "promotingCandidateCount"
        ],
        "edgeCallGraphEncodedTargetScan": call_graph_encoded_scan,
        "edgeCallGraphEncodedTargetClassification": call_graph_encoded_scan["classification"],
        "edgeCallGraphEncodedTargetRawScalarCandidateCount": call_graph_encoded_scan[
            "rawScalarCandidateCount"
        ],
        "edgeCallGraphEncodedTargetTransitionRawScalarCandidateCount": call_graph_encoded_scan[
            "transitionRawScalarCandidateCount"
        ],
        "edgeCallGraphEncodedTargetRouteProofRawScalarCandidateCount": call_graph_encoded_scan[
            "routeProofRawScalarCandidateCount"
        ],
        "edgeCallGraphEncodedTargetSelectedPointerRawScalarCandidateCount": call_graph_encoded_scan[
            "selectedPointerRawScalarCandidateCount"
        ],
        "edgeCallGraphEncodedTargetPromotingCandidateCount": call_graph_encoded_scan[
            "promotingCandidateCount"
        ],
        "edgeGlobalContrastWindowEncodedTargetScan": global_contrast_window_encoded_scan,
        "edgeGlobalContrastWindowEncodedTargetClassification": (
            global_contrast_window_encoded_scan["classification"]
        ),
        "edgeGlobalContrastWindowEncodedTargetRawScalarCandidateCount": (
            global_contrast_window_encoded_scan["rawScalarCandidateCount"]
        ),
        "edgeGlobalContrastWindowEncodedTargetTransitionRawScalarCandidateCount": (
            global_contrast_window_encoded_scan["transitionRawScalarCandidateCount"]
        ),
        "edgeGlobalContrastWindowEncodedTargetRouteProofRawScalarCandidateCount": (
            global_contrast_window_encoded_scan["routeProofRawScalarCandidateCount"]
        ),
        "edgeGlobalContrastWindowEncodedTargetSelectedPointerRawScalarCandidateCount": (
            global_contrast_window_encoded_scan["selectedPointerRawScalarCandidateCount"]
        ),
        "edgeGlobalContrastWindowEncodedTargetPromotingCandidateCount": (
            global_contrast_window_encoded_scan["promotingCandidateCount"]
        ),
        "directCallGraphEvidence": direct_call_graph,
        "directCallGraphDepthSensitivity": call_graph_depth_sensitivity,
        "directCallGraphTransitionRejectionClassification": direct_call_graph.get("classification"),
        "directCallGraphTransitionProofFound": direct_call_graph.get("proofFound"),
        "directCallGraphReachableFunctionCount": direct_call_graph.get("reachableFunctionCount"),
        "directCallGraphDirectCallEdgeCount": direct_call_graph.get("directCallEdgeCount"),
        "directCallGraphTransitionTargetReachableCount": direct_call_graph.get(
            "transitionTargetReachableCount"
        ),
        "directCallGraphTransitionTargetHitCount": direct_call_graph.get("transitionTargetHitCount"),
        "directCallGraphRouteImmediateHitCount": direct_call_graph.get("routeImmediateHitCount"),
        "directCallGraphIndirectCallLikeByteCount": direct_call_graph.get("indirectCallLikeByteCount"),
        "directCallGraphIndirectRejectionClassification": direct_call_graph.get(
            "indirectCallGraphRejectionClassification"
        ),
        "directCallGraphIndirectProofFound": direct_call_graph.get("indirectCallGraphProofFound"),
        "directCallGraphIndirectStaticAbsoluteMemoryCandidateCount": direct_call_graph.get(
            "indirectCallGraphStaticAbsoluteMemoryCandidateCount"
        ),
        "directCallGraphIndirectIndexedJumpTableCandidateCount": direct_call_graph.get(
            "indirectCallGraphIndexedJumpTableCandidateCount"
        ),
        "directCallGraphIndirectIndexedJumpTableEntryCount": direct_call_graph.get(
            "indirectCallGraphIndexedJumpTableEntryCount"
        ),
        "directCallGraphIndirectIndexedJumpTableTargetCount": direct_call_graph.get(
            "indirectCallGraphIndexedJumpTableTargetCount"
        ),
        "directCallGraphIndirectIndexedJumpTableUniqueTargetCount": direct_call_graph.get(
            "indirectCallGraphIndexedJumpTableUniqueTargetCount"
        ),
        "directCallGraphIndirectIndexedJumpTableTargetClassCounts": direct_call_graph.get(
            "indirectCallGraphIndexedJumpTableTargetClassCounts"
        ),
        "directCallGraphIndirectIndexedJumpTableAuditRows": direct_call_graph.get(
            "indirectCallGraphIndexedJumpTableAuditRows"
        ),
        "directCallGraphIndirectIndexedJumpTableAllTargetsLocalToEdgeHandlers": direct_call_graph.get(
            "indirectCallGraphIndexedJumpTableAllTargetsLocalToEdgeHandlers"
        ),
        "directCallGraphIndirectIndexedJumpTableOutsideEdgeHandlerTargetCount": direct_call_graph.get(
            "indirectCallGraphIndexedJumpTableOutsideEdgeHandlerTargetCount"
        ),
        "directCallGraphIndirectResolvedPointerCandidateCount": direct_call_graph.get(
            "indirectCallGraphResolvedPointerCandidateCount"
        ),
        "directCallGraphIndirectUnresolvedRegisterOrComputedCount": direct_call_graph.get(
            "indirectCallGraphUnresolvedRegisterOrComputedCount"
        ),
        "directCallGraphIndirectTransitionTargetHitCount": direct_call_graph.get(
            "indirectCallGraphTransitionTargetHitCount"
        ),
        "directCallGraphIndirectRouteImmediateHitCount": direct_call_graph.get(
            "indirectCallGraphRouteImmediateHitCount"
        ),
        "directCallGraphDepthSensitivityMaxDepthChecked": call_graph_depth_sensitivity.get(
            "maxDepthChecked"
        ),
        "directCallGraphDepthSensitivityProofAbsentAcrossCheckedDepths": (
            call_graph_depth_sensitivity.get("proofAbsentAcrossCheckedDepths")
        ),
        "directCallGraphDepthSensitivityCountsStableAtAndBeyondDefaultDepth": (
            call_graph_depth_sensitivity.get("countsStableAtAndBeyondDefaultDepth")
        ),
        "actorControllerCallerWindowEvidence": actor_controller_caller_windows,
        "collisionHelperCallerWindowEvidence": collision_helper_caller_windows,
        "sourceTargetStringVas": {
            SOURCE: hex32(strings.get(SOURCE)),
            TARGET: hex32(strings.get(TARGET)),
        },
        "transitionLikeDirectRelHitCountInHelperOrController": transition_ref_count,
        "directRouteImmediateCountInHelperOrController": direct_route_ref_count,
        "missingPromotionEvidence": EDGE_TRIGGER_MISSING_EVIDENCE,
        "conclusion": (
            "The top and bottom map1_01a routeAssist candidates are real map-edge boundary candidates, "
            "but the original collision helper evidence only shows boundary fallback writes to the direction "
            "latch before returning to the actor collision/overlap path. No direct map-loader, selected-pointer, "
            "current-root, or map1_01a/map2_02d string reference is present in the helper/controller ranges or "
            "near any direct direction-latch reference in .text, and the bounded direct-call graph from the "
            "actor movement/collision ranges remains stable through the deeper sensitivity pass without "
            "reaching a transition target or route-specific immediate. This keeps the manual movement trigger "
            "consumer unidentified; it does not support a scene-auto-transition interpretation, and a generic "
            "edge-trigger transition remains unproven."
        ),
    }


def markdown(summary: dict) -> str:
    lines = [
        "# map1_01a Edge Trigger Gap",
        "",
        f"Route: `{summary['source']} -> {summary['target']}`",
        "",
        (
            "This audit checks whether the geometry-only edge candidates can be promoted as a generic "
            "original edge/boundary transition. It keeps promotion blocked."
        ),
        "",
        "## Gate",
        "",
        f"- promotionAllowed: `{summary['promotionAllowed']}`",
        f"- promotionStatus: `{summary['promotionStatus']}`",
        f"- proofFound: `{summary['proofFound']}`",
        f"- failed edge trigger gates: `{', '.join(summary['failedEdgeTriggerGateIds'])}`",
        f"- missingEvidenceCount: `{len(summary['missingEvidence'])}`",
        f"- evidenceRefs: `{summary.get('evidenceRefCount')}`",
        f"- source boundary candidates: `{summary['sourceBoundaryCandidateCount']}`",
        f"- auto boundary candidates: `{summary['autoBoundaryCandidateCount']}`",
        f"- transition-like direct rel hits in helper/controller: `{summary['transitionLikeDirectRelHitCountInHelperOrController']}`",
        f"- route direct immediates in helper/controller: `{summary['directRouteImmediateCountInHelperOrController']}`",
        f"- direction latch direct .text refs: `{summary['directionLatchReferenceEvidence']['directRefCount']}`",
        f"- direction latch route-window hits: `{summary['directionLatchReferenceEvidence']['transitionLikeWindowRelHitCount']}` rel / `{summary['directionLatchReferenceEvidence']['routeImmediateWindowHitCount']}` immediates",
        f"- actor-controller caller-window hits: `{summary['actorControllerCallerWindowEvidence']['transitionLikeWindowRelHitCount']}` rel / `{summary['actorControllerCallerWindowEvidence']['routeImmediateWindowHitCount']}` immediates",
        f"- collision-helper caller-window hits: `{summary['collisionHelperCallerWindowEvidence']['transitionLikeWindowRelHitCount']}` rel / `{summary['collisionHelperCallerWindowEvidence']['routeImmediateWindowHitCount']}` immediates",
        f"- script-runner call windows: `{summary['scriptRunnerCallContextEvidence']['callerCount']}` calls / `{summary['scriptRunnerCallContextEvidence']['routeImmediateWindowHitCount']}` route immediates / `{summary['scriptRunnerCallContextEvidence']['mapLoaderWindowRelHitCount']}` map-loader rel / `{summary['scriptRunnerCallContextEvidence']['selectorTableWindowRelHitCount']}` selector-table rel",
        f"- selected-pointer immediate windows: `{summary['selectedPointerImmediateContextEvidence']['immediateRefCount']}` refs / `{summary['selectedPointerImmediateContextEvidence']['routeSpecificWindowHitCount']}` route-specific hits",
        f"- edge handler encoded route-target scalars: `{summary['edgeHandlerEncodedTargetRawScalarCandidateCount']}` / `{summary['edgeHandlerEncodedTargetTransitionRawScalarCandidateCount']}` / `{summary['edgeHandlerEncodedTargetRouteProofRawScalarCandidateCount']}` / `{summary['edgeHandlerEncodedTargetSelectedPointerRawScalarCandidateCount']}` / `{summary['edgeHandlerEncodedTargetPromotingCandidateCount']}` (`{summary['edgeHandlerEncodedTargetClassification']}`)",
        f"- edge local-window encoded route-target scalars: `{summary['edgeLocalWindowEncodedTargetRawScalarCandidateCount']}` / `{summary['edgeLocalWindowEncodedTargetTransitionRawScalarCandidateCount']}` / `{summary['edgeLocalWindowEncodedTargetRouteProofRawScalarCandidateCount']}` / `{summary['edgeLocalWindowEncodedTargetSelectedPointerRawScalarCandidateCount']}` / `{summary['edgeLocalWindowEncodedTargetPromotingCandidateCount']}` (`{summary['edgeLocalWindowEncodedTargetClassification']}`)",
        f"- edge call-graph encoded route-target scalars: `{summary['edgeCallGraphEncodedTargetRawScalarCandidateCount']}` / `{summary['edgeCallGraphEncodedTargetTransitionRawScalarCandidateCount']}` / `{summary['edgeCallGraphEncodedTargetRouteProofRawScalarCandidateCount']}` / `{summary['edgeCallGraphEncodedTargetSelectedPointerRawScalarCandidateCount']}` / `{summary['edgeCallGraphEncodedTargetPromotingCandidateCount']}` (`{summary['edgeCallGraphEncodedTargetClassification']}`)",
        f"- global contrast-window encoded route-target scalars: `{summary['edgeGlobalContrastWindowEncodedTargetRawScalarCandidateCount']}` / `{summary['edgeGlobalContrastWindowEncodedTargetTransitionRawScalarCandidateCount']}` / `{summary['edgeGlobalContrastWindowEncodedTargetRouteProofRawScalarCandidateCount']}` / `{summary['edgeGlobalContrastWindowEncodedTargetSelectedPointerRawScalarCandidateCount']}` / `{summary['edgeGlobalContrastWindowEncodedTargetPromotingCandidateCount']}` (`{summary['edgeGlobalContrastWindowEncodedTargetClassification']}`)",
        f"- direct call graph: `{summary['directCallGraphTransitionRejectionClassification']}` / proof `{summary['directCallGraphTransitionProofFound']}` / functions `{summary['directCallGraphReachableFunctionCount']}` / edges `{summary['directCallGraphDirectCallEdgeCount']}` / transition targets `{summary['directCallGraphTransitionTargetReachableCount']}` / transition hits `{summary['directCallGraphTransitionTargetHitCount']}` / route immediates `{summary['directCallGraphRouteImmediateHitCount']}` / indirect-like bytes `{summary['directCallGraphIndirectCallLikeByteCount']}`",
        f"- direct call graph depth sensitivity: max depth `{summary['directCallGraphDepthSensitivityMaxDepthChecked']}` / no proof `{summary['directCallGraphDepthSensitivityProofAbsentAcrossCheckedDepths']}` / stable beyond default `{summary['directCallGraphDepthSensitivityCountsStableAtAndBeyondDefaultDepth']}`",
        f"- indirect call graph rejection: `{summary['directCallGraphIndirectRejectionClassification']}` / proof `{summary['directCallGraphIndirectProofFound']}` / absolute `{summary['directCallGraphIndirectStaticAbsoluteMemoryCandidateCount']}` / jump tables `{summary['directCallGraphIndirectIndexedJumpTableCandidateCount']}`/`{summary['directCallGraphIndirectIndexedJumpTableEntryCount']}` entries / targets `{summary['directCallGraphIndirectIndexedJumpTableTargetCount']}`/`{summary['directCallGraphIndirectIndexedJumpTableUniqueTargetCount']}` unique / local `{summary['directCallGraphIndirectIndexedJumpTableAllTargetsLocalToEdgeHandlers']}` / outside `{summary['directCallGraphIndirectIndexedJumpTableOutsideEdgeHandlerTargetCount']}` / resolved `{summary['directCallGraphIndirectResolvedPointerCandidateCount']}` / unresolved `{summary['directCallGraphIndirectUnresolvedRegisterOrComputedCount']}` / transition hits `{summary['directCallGraphIndirectTransitionTargetHitCount']}` / route hits `{summary['directCallGraphIndirectRouteImmediateHitCount']}`",
        "- global .text transition refs: "
        f"`{summary['globalTransitionReferenceEvidence']['directReferences']['mapLoaderDirectRelHitCount']}` map-loader / "
        f"`{summary['globalTransitionReferenceEvidence']['directReferences']['scriptRunnerDirectRelHitCount']}` script-runner / "
        f"`{summary['globalTransitionReferenceEvidence']['directReferences']['selectorTableDirectRelHitCount']}` selector-table rel hits",
        "",
        "## Candidate Rows",
        "",
        "| side | tile | direction | edge | auto | source boundary | in-bounds move | original standable | helper rule | helper check |",
        "| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | --- | --- |",
    ]
    for row in summary["candidateRows"]:
        lines.append(
            f"| {row.get('side')} | {row.get('x')},{row.get('y')} | {row.get('direction')} | "
            f"{row.get('edgeDistance')} | {row.get('autoTrigger')} | {row.get('boundaryCandidate')} | "
            f"{row.get('outwardMoveWouldStayInBounds')} | {row.get('originalStandable')} | "
            f"{row.get('boundaryRule') or '-'} | `{row.get('edgeCheckVaHex') or '-'}` |"
        )
    lines.extend([
        "",
        "## Boundary Helper Evidence",
        "",
        "| direction | rule | check | fallback latch | exit target | snippet |",
        "| --- | --- | --- | --- | --- | ---: |",
    ])
    for row in summary["collisionHelperEvidence"]["boundaryCases"]:
        lines.append(
            f"| {row.get('direction')} | {row.get('boundaryRule')} | `{row.get('edgeCheckVaHex')}` | "
            f"`{row.get('blockedFallbackLatchHex')}` | `{row.get('caseExitJumpTargetHex')}` | "
            f"{row.get('snippetMatchesExpected')} |"
        )
    helper_refs = summary["collisionHelperEvidence"]["directReferences"]
    controller_refs = summary["actorControllerEvidence"]["directReferences"]
    global_refs = summary["globalTransitionReferenceEvidence"]["directReferences"]
    lines.extend([
        "",
        "## Direct Reference Checks",
        "",
        "| range | map loader rel | script runner rel | selector table rel | selected ptr imm | current root imm | source string imm | target string imm |",
        "| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
    ])
    for label, refs in (
        ("collision helper", helper_refs),
        ("actor controller", controller_refs),
        ("global .text", global_refs),
    ):
        lines.append(
            f"| {label} `{refs.get('rangeHex')}` | {refs.get('mapLoaderDirectRelHitCount')} | "
            f"{refs.get('scriptRunnerDirectRelHitCount')} | {refs.get('selectorTableDirectRelHitCount')} | "
            f"{refs.get('selectedPointerGlobalImmediateCount')} | {refs.get('currentSelectorRootImmediateCount')} | "
            f"{refs.get('sourceMapStringImmediateCount')} | {refs.get('targetMapStringImmediateCount')} |"
        )
    latch = summary["directionLatchReferenceEvidence"]
    lines.extend([
        "",
        "## Selected Pointer Immediate Context",
        "",
        "| ref | window | selected ptr imm | current root imm | source string imm | target string imm | map-loader rel | script-runner rel | selector-table rel |",
        "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
    ])
    for row in summary["selectedPointerImmediateContextEvidence"]["sampleReferenceWindows"]:
        lines.append(
            f"| `{row.get('refVaHex')}` | `{row.get('windowHex')}` | "
            f"{row.get('selectedPointerGlobalImmediateCount')} | {row.get('currentSelectorRootImmediateCount')} | "
            f"{row.get('sourceMapStringImmediateCount')} | {row.get('targetMapStringImmediateCount')} | "
            f"{row.get('mapLoaderDirectRelHitCount')} | {row.get('scriptRunnerDirectRelHitCount')} | "
            f"{row.get('selectorTableDirectRelHitCount')} |"
        )
    lines.extend([
        "",
        summary["selectedPointerImmediateContextEvidence"]["conclusion"],
        "",
        "## Script Runner Call Context",
        "",
        "| call | window | selected ptr imm | current root imm | source string imm | target string imm | map-loader rel | selector-table rel |",
        "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |",
    ])
    for row in summary["scriptRunnerCallContextEvidence"]["sampleCallWindows"]:
        lines.append(
            f"| `{row.get('callVaHex')}` | `{row.get('windowHex')}` | "
            f"{row.get('selectedPointerGlobalImmediateCount')} | {row.get('currentSelectorRootImmediateCount')} | "
            f"{row.get('sourceMapStringImmediateCount')} | {row.get('targetMapStringImmediateCount')} | "
            f"{row.get('mapLoaderDirectRelHitCount')} | {row.get('selectorTableDirectRelHitCount')} |"
        )
    lines.extend([
        "",
        summary["scriptRunnerCallContextEvidence"]["conclusion"],
        "",
        "## Direct Call Graph",
        "",
        f"- classification: `{summary['directCallGraphEvidence']['classification']}`",
        f"- proof found: `{summary['directCallGraphEvidence']['proofFound']}`",
        f"- reachable functions / direct call edges: `{summary['directCallGraphEvidence']['reachableFunctionCount']}` / `{summary['directCallGraphEvidence']['directCallEdgeCount']}`",
        f"- transition targets reachable / transition hits / route immediates: `{summary['directCallGraphEvidence']['transitionTargetReachableCount']}` / `{summary['directCallGraphEvidence']['transitionTargetHitCount']}` / `{summary['directCallGraphEvidence']['routeImmediateHitCount']}`",
        f"- indirect call-like bytes: `{summary['directCallGraphEvidence']['indirectCallLikeByteCount']}`",
        f"- depth sensitivity max / no proof / stable beyond default: `{summary['directCallGraphDepthSensitivity']['maxDepthChecked']}` / `{summary['directCallGraphDepthSensitivity']['proofAbsentAcrossCheckedDepths']}` / `{summary['directCallGraphDepthSensitivity']['countsStableAtAndBeyondDefaultDepth']}`",
        f"- indirect rejection: `{summary['directCallGraphEvidence']['indirectCallGraphRejectionClassification']}` / proof `{summary['directCallGraphEvidence']['indirectCallGraphProofFound']}` / absolute `{summary['directCallGraphEvidence']['indirectCallGraphStaticAbsoluteMemoryCandidateCount']}` / jump tables `{summary['directCallGraphEvidence']['indirectCallGraphIndexedJumpTableCandidateCount']}`/`{summary['directCallGraphEvidence']['indirectCallGraphIndexedJumpTableEntryCount']}` entries / targets `{summary['directCallGraphEvidence']['indirectCallGraphIndexedJumpTableTargetCount']}`/`{summary['directCallGraphEvidence']['indirectCallGraphIndexedJumpTableUniqueTargetCount']}` unique / local `{summary['directCallGraphEvidence']['indirectCallGraphIndexedJumpTableAllTargetsLocalToEdgeHandlers']}` / outside `{summary['directCallGraphEvidence']['indirectCallGraphIndexedJumpTableOutsideEdgeHandlerTargetCount']}` / resolved `{summary['directCallGraphEvidence']['indirectCallGraphResolvedPointerCandidateCount']}` / unresolved `{summary['directCallGraphEvidence']['indirectCallGraphUnresolvedRegisterOrComputedCount']}` / transition hits `{summary['directCallGraphEvidence']['indirectCallGraphTransitionTargetHitCount']}` / route hits `{summary['directCallGraphEvidence']['indirectCallGraphRouteImmediateHitCount']}`",
        "",
        "| function | depth | range | calls | transition hits | route immediates |",
        "| --- | ---: | --- | ---: | ---: | ---: |",
    ])
    for row in summary["directCallGraphEvidence"]["sampleReachableFunctions"]:
        lines.append(
            f"| {row.get('label')} | {row.get('depth')} | `{row.get('rangeHex')}` | "
            f"{row.get('directCallCount')} | {row.get('transitionLikeDirectRelHitCount')} | "
            f"{row.get('routeImmediateHitCount')} |"
        )
    lines.extend([
        "",
        "### Direct Call Graph Depth Sensitivity",
        "",
        "| max depth | observed depth | functions | edges | transition targets | transition hits | route immediates | indirect-like bytes | proof | stable class |",
        "| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |",
    ])
    for row in summary["directCallGraphDepthSensitivity"]["rows"]:
        lines.append(
            f"| {row.get('maxDepth')} | {row.get('observedMaxDepth')} | "
            f"{row.get('reachableFunctionCount')} | {row.get('directCallEdgeCount')} | "
            f"{row.get('transitionTargetReachableCount')} | {row.get('transitionTargetHitCount')} | "
            f"{row.get('routeImmediateHitCount')} | {row.get('indirectCallLikeByteCount')} | "
            f"{row.get('proofFound')} | {row.get('classification')} |"
        )
    lines.extend([
        "",
        summary["directCallGraphDepthSensitivity"]["conclusion"],
        "",
        "",
        "### Indirect Call/Jump Candidates",
        "",
        "| function | depth | instruction | kind | operand | table | entries | absolute | resolved | target hit | route hit |",
        "| --- | ---: | --- | --- | --- | --- | ---: | --- | --- | --- | --- |",
    ])
    for row in (summary["directCallGraphEvidence"].get("indirectCallGraphRejection") or {}).get("sampleRows") or []:
        lines.append(
            f"| {row.get('functionLabel')} | {row.get('depth')} | `{row.get('instructionVaHex')}` | "
            f"{row.get('kind')} | {row.get('operandKind')} | `{row.get('indexedJumpTableVaHex') or '-'}` | "
            f"{row.get('indexedJumpTableEntryCount') or 0} | `{row.get('absoluteMemoryVaHex') or '-'}` | "
            f"`{row.get('resolvedPointerHex') or '-'}` | "
            f"{row.get('transitionOperandLabel') or row.get('transitionResolvedLabel') or labels_text(row.get('transitionTableLabels'))} | "
            f"{row.get('routeOperandLabel') or row.get('routeResolvedLabel') or labels_text(row.get('routeTableLabels'))} |"
        )
    lines.extend([
        "",
        "### Indirect Jump Table Audit",
        "",
        "| function | instruction | table | entries | target classes | local-only | outside | transition labels | route labels |",
        "| --- | --- | --- | ---: | --- | --- | ---: | --- | --- |",
    ])
    for row in (summary["directCallGraphEvidence"].get("indirectCallGraphRejection") or {}).get("indexedJumpTableAuditRows") or []:
        target_classes = ",".join(
            f"{key}:{value}"
            for key, value in sorted((row.get("targetClassCounts") or {}).items())
        ) or "-"
        lines.append(
            f"| {row.get('functionLabel')} | `{row.get('instructionVaHex')}` | "
            f"`{row.get('indexedJumpTableVaHex')}` | {row.get('indexedJumpTableEntryCount')} | "
            f"`{target_classes}` | {row.get('allTargetsLocalToEdgeHandlers')} | "
            f"{row.get('outsideEdgeHandlerTargetCount')} | "
            f"`{labels_text(row.get('transitionTableLabels'))}` | "
            f"`{labels_text(row.get('routeTableLabels'))}` |"
        )
    lines.extend([
        "",
        summary["directCallGraphEvidence"]["conclusion"],
        "",
        (summary["directCallGraphEvidence"].get("indirectCallGraphRejection") or {}).get("conclusion", ""),
        "",
        "## Direction Latch Global Scan",
        "",
        f"- latch: `{latch['directionLatchGlobalHex']}`",
        f"- text refs: `{latch['directRefCount']}` (`{latch['readRefCount']}` reads / `{latch['writeRefCount']}` writes / `{latch['otherTextRefCount']}` outside helper/controller range)",
        f"- local route-window hits: `{latch['transitionLikeWindowRelHitCount']}` transition rel / `{latch['routeImmediateWindowHitCount']}` route immediates",
        f"- outside helper/controller refs: reset `{latch['otherTextResetCount']}`, actor-state continuation reads `{latch['otherTextActorStateContinuationReadCount']}`, transition consumers `{latch['otherTextTransitionConsumerCount']}`, unclassified `{latch['otherTextUnclassifiedCount']}`",
        f"- outside helper/controller route-window hits: `{latch['otherTextTransitionLikeWindowRelHitCount']}` transition rel / `{latch['otherTextRouteImmediateWindowHitCount']}` route immediates",
        f"- outside refs classified non-transition: `{latch['otherTextReferencesClassifiedNonTransition']}`",
        f"- by kind: `{', '.join(f'{k}={v}' for k, v in latch['byKind'].items())}`",
        "",
        "| ref | range | kind | value |",
        "| --- | --- | --- | --- |",
    ])
    for row in latch["sampleReferences"]:
        lines.append(
            f"| `{row.get('referenceVaHex')}` | {row.get('range')} | {row.get('kind')} | `{row.get('valueHex') or '-'}` |"
        )
    lines.extend([
        "",
        latch["conclusion"],
        "",
        "### Direction Latch Outside Helper/Controller Audit",
        "",
        "| instruction | ref | class | effect | transition rel | route imm | bytes |",
        "| --- | --- | --- | --- | ---: | ---: | --- |",
    ])
    for row in latch["allOtherTextReferences"]:
        lines.append(
            f"| `{row.get('instructionVaHex')}` | `{row.get('referenceVaHex')}` | "
            f"{row.get('consumerClass')} | {row.get('observedEffect')} | "
            f"{row.get('transitionLikeWindowRelHitCount')} | {row.get('routeImmediateWindowHitCount')} | "
            f"`{row.get('instructionBytesHex')}` |"
        )
    lines.extend([
        "",
        "## Caller Window Scans",
        "",
        "| target | callers | local transition rel hits | local route immediates | hit windows |",
        "| --- | ---: | ---: | ---: | ---: |",
    ])
    for calls in (
        summary["actorControllerCallerWindowEvidence"],
        summary["collisionHelperCallerWindowEvidence"],
    ):
        lines.append(
            f"| {calls['targetLabel']} `{calls['targetVaHex']}` | {calls['callerCount']} | "
            f"{calls['transitionLikeWindowRelHitCount']} | {calls['routeImmediateWindowHitCount']} | "
            f"{len(calls['routeWindowHitRows'])} |"
        )
    lines.extend([
        "",
        summary["actorControllerCallerWindowEvidence"]["conclusion"],
        "",
        summary["collisionHelperCallerWindowEvidence"]["conclusion"],
    ])
    lines.extend([
        "",
        "## Conclusion",
        "",
        summary["conclusion"],
        "",
        "## Missing Evidence",
        "",
    ])
    for item in summary["missingPromotionEvidence"]:
        lines.append(f"- {item}")
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    candidate_rows = []
    for row in summary["candidateRows"]:
        candidate_rows.append(
            "\n".join([
                "<tr>",
                f"  <td>{html.escape(str(row.get('side')))}</td>",
                f"  <td>{html.escape(str(row.get('x')))}, {html.escape(str(row.get('y')))}</td>",
                f"  <td>{html.escape(str(row.get('direction')))}</td>",
                f"  <td>{html.escape(str(row.get('edgeDistance')))}</td>",
                f"  <td>{html.escape(str(row.get('autoTrigger')))}</td>",
                f"  <td>{html.escape(str(row.get('boundaryCandidate')))}</td>",
                f"  <td>{html.escape(str(row.get('outwardMoveWouldStayInBounds')))}</td>",
                f"  <td>{html.escape(str(row.get('originalStandable')))}</td>",
                f"  <td>{html.escape(str(row.get('boundaryRule') or '-'))}</td>",
                f"  <td><code>{html.escape(str(row.get('edgeCheckVaHex') or '-'))}</code></td>",
                "</tr>",
            ])
        )
    boundary_rows = []
    for row in summary["collisionHelperEvidence"]["boundaryCases"]:
        boundary_rows.append(
            "\n".join([
                "<tr>",
                f"  <td>{html.escape(str(row.get('direction')))}</td>",
                f"  <td>{html.escape(str(row.get('boundaryRule')))}</td>",
                f"  <td><code>{html.escape(str(row.get('edgeCheckVaHex')))}</code></td>",
                f"  <td><code>{html.escape(str(row.get('blockedFallbackLatchHex')))}</code></td>",
                f"  <td><code>{html.escape(str(row.get('caseExitJumpTargetHex')))}</code></td>",
                f"  <td>{html.escape(str(row.get('snippetMatchesExpected')))}</td>",
                "</tr>",
            ])
        )
    direct_rows = []
    global_refs = summary["globalTransitionReferenceEvidence"]["directReferences"]
    for label, refs in (
        ("collision helper", summary["collisionHelperEvidence"]["directReferences"]),
        ("actor controller", summary["actorControllerEvidence"]["directReferences"]),
        ("global .text", global_refs),
    ):
        direct_rows.append(
            "\n".join([
                "<tr>",
                f"  <td>{html.escape(label)} <code>{html.escape(str(refs.get('rangeHex')))}</code></td>",
                f"  <td>{refs.get('mapLoaderDirectRelHitCount')}</td>",
                f"  <td>{refs.get('scriptRunnerDirectRelHitCount')}</td>",
                f"  <td>{refs.get('selectorTableDirectRelHitCount')}</td>",
                f"  <td>{refs.get('selectedPointerGlobalImmediateCount')}</td>",
                f"  <td>{refs.get('currentSelectorRootImmediateCount')}</td>",
                f"  <td>{refs.get('sourceMapStringImmediateCount')}</td>",
                f"  <td>{refs.get('targetMapStringImmediateCount')}</td>",
                "</tr>",
            ])
        )
    latch = summary["directionLatchReferenceEvidence"]
    call_graph_rows = []
    for row in summary["directCallGraphEvidence"]["sampleReachableFunctions"]:
        call_graph_rows.append(
            "\n".join([
                "<tr>",
                f"  <td>{html.escape(str(row.get('label')))}</td>",
                f"  <td>{html.escape(str(row.get('depth')))}</td>",
                f"  <td><code>{html.escape(str(row.get('rangeHex')))}</code></td>",
                f"  <td>{html.escape(str(row.get('directCallCount')))}</td>",
                f"  <td>{html.escape(str(row.get('transitionLikeDirectRelHitCount')))}</td>",
                f"  <td>{html.escape(str(row.get('routeImmediateHitCount')))}</td>",
                "</tr>",
            ])
        )
    depth_sensitivity_rows = []
    for row in summary["directCallGraphDepthSensitivity"]["rows"]:
        depth_sensitivity_rows.append(
            "\n".join([
                "<tr>",
                f"  <td>{html.escape(str(row.get('maxDepth')))}</td>",
                f"  <td>{html.escape(str(row.get('observedMaxDepth')))}</td>",
                f"  <td>{html.escape(str(row.get('reachableFunctionCount')))}</td>",
                f"  <td>{html.escape(str(row.get('directCallEdgeCount')))}</td>",
                f"  <td>{html.escape(str(row.get('transitionTargetReachableCount')))}</td>",
                f"  <td>{html.escape(str(row.get('transitionTargetHitCount')))}</td>",
                f"  <td>{html.escape(str(row.get('routeImmediateHitCount')))}</td>",
                f"  <td>{html.escape(str(row.get('indirectCallLikeByteCount')))}</td>",
                f"  <td>{html.escape(str(row.get('proofFound')))}</td>",
                f"  <td>{html.escape(str(row.get('classification')))}</td>",
                "</tr>",
            ])
        )
    indirect_call_graph_rows = []
    for row in (summary["directCallGraphEvidence"].get("indirectCallGraphRejection") or {}).get("sampleRows") or []:
        indirect_call_graph_rows.append(
            "\n".join([
                "<tr>",
                f"  <td>{html.escape(str(row.get('functionLabel')))}</td>",
                f"  <td>{html.escape(str(row.get('depth')))}</td>",
                f"  <td><code>{html.escape(str(row.get('instructionVaHex')))}</code></td>",
                f"  <td>{html.escape(str(row.get('kind')))}</td>",
                f"  <td>{html.escape(str(row.get('operandKind')))}</td>",
                f"  <td><code>{html.escape(str(row.get('indexedJumpTableVaHex') or '-'))}</code></td>",
                f"  <td>{html.escape(str(row.get('indexedJumpTableEntryCount') or 0))}</td>",
                f"  <td><code>{html.escape(str(row.get('absoluteMemoryVaHex') or '-'))}</code></td>",
                f"  <td><code>{html.escape(str(row.get('resolvedPointerHex') or '-'))}</code></td>",
                f"  <td>{html.escape(str(row.get('transitionOperandLabel') or row.get('transitionResolvedLabel') or labels_text(row.get('transitionTableLabels'))))}</td>",
                f"  <td>{html.escape(str(row.get('routeOperandLabel') or row.get('routeResolvedLabel') or labels_text(row.get('routeTableLabels'))))}</td>",
                "</tr>",
            ])
        )
    indirect_jump_table_audit_rows = []
    for row in (summary["directCallGraphEvidence"].get("indirectCallGraphRejection") or {}).get("indexedJumpTableAuditRows") or []:
        target_classes = ",".join(
            f"{key}:{value}"
            for key, value in sorted((row.get("targetClassCounts") or {}).items())
        ) or "-"
        indirect_jump_table_audit_rows.append(
            "\n".join([
                "<tr>",
                f"  <td>{html.escape(str(row.get('functionLabel')))}</td>",
                f"  <td><code>{html.escape(str(row.get('instructionVaHex')))}</code></td>",
                f"  <td><code>{html.escape(str(row.get('indexedJumpTableVaHex')))}</code></td>",
                f"  <td>{html.escape(str(row.get('indexedJumpTableEntryCount')))}</td>",
                f"  <td><code>{html.escape(target_classes)}</code></td>",
                f"  <td>{html.escape(str(row.get('allTargetsLocalToEdgeHandlers')))}</td>",
                f"  <td>{html.escape(str(row.get('outsideEdgeHandlerTargetCount')))}</td>",
                f"  <td><code>{html.escape(labels_text(row.get('transitionTableLabels')))}</code></td>",
                f"  <td><code>{html.escape(labels_text(row.get('routeTableLabels')))}</code></td>",
                "</tr>",
            ])
        )
    caller_window_rows = []
    for calls in (
        summary["actorControllerCallerWindowEvidence"],
        summary["collisionHelperCallerWindowEvidence"],
    ):
        caller_window_rows.append(
            "\n".join([
                "<tr>",
                f"  <td>{html.escape(str(calls.get('targetLabel')))} <code>{html.escape(str(calls.get('targetVaHex')))}</code></td>",
                f"  <td>{calls.get('callerCount')}</td>",
                f"  <td>{calls.get('transitionLikeWindowRelHitCount')}</td>",
                f"  <td>{calls.get('routeImmediateWindowHitCount')}</td>",
                f"  <td>{len(calls.get('routeWindowHitRows') or [])}</td>",
                "</tr>",
            ])
        )
    latch_rows = []
    for row in latch["sampleReferences"]:
        latch_rows.append(
            "\n".join([
                "<tr>",
                f"  <td><code>{html.escape(str(row.get('referenceVaHex')))}</code></td>",
                f"  <td>{html.escape(str(row.get('range')))}</td>",
                f"  <td>{html.escape(str(row.get('kind')))}</td>",
                f"  <td><code>{html.escape(str(row.get('valueHex') or '-'))}</code></td>",
                "</tr>",
            ])
        )
    latch_other_text_rows = []
    for row in latch["allOtherTextReferences"]:
        latch_other_text_rows.append(
            "\n".join([
                "<tr>",
                f"  <td><code>{html.escape(str(row.get('instructionVaHex')))}</code></td>",
                f"  <td><code>{html.escape(str(row.get('referenceVaHex')))}</code></td>",
                f"  <td>{html.escape(str(row.get('consumerClass')))}</td>",
                f"  <td>{html.escape(str(row.get('observedEffect')))}</td>",
                f"  <td>{html.escape(str(row.get('transitionLikeWindowRelHitCount')))}</td>",
                f"  <td>{html.escape(str(row.get('routeImmediateWindowHitCount')))}</td>",
                f"  <td><code>{html.escape(str(row.get('instructionBytesHex')))}</code></td>",
                "</tr>",
            ])
        )
    script_runner_rows = []
    for row in summary["scriptRunnerCallContextEvidence"]["sampleCallWindows"]:
        script_runner_rows.append(
            "\n".join([
                "<tr>",
                f"  <td><code>{html.escape(str(row.get('callVaHex')))}</code></td>",
                f"  <td><code>{html.escape(str(row.get('windowHex')))}</code></td>",
                f"  <td>{row.get('selectedPointerGlobalImmediateCount')}</td>",
                f"  <td>{row.get('currentSelectorRootImmediateCount')}</td>",
                f"  <td>{row.get('sourceMapStringImmediateCount')}</td>",
                f"  <td>{row.get('targetMapStringImmediateCount')}</td>",
                f"  <td>{row.get('mapLoaderDirectRelHitCount')}</td>",
                f"  <td>{row.get('selectorTableDirectRelHitCount')}</td>",
                "</tr>",
            ])
        )
    selected_pointer_rows = []
    for row in summary["selectedPointerImmediateContextEvidence"]["sampleReferenceWindows"]:
        selected_pointer_rows.append(
            "\n".join([
                "<tr>",
                f"  <td><code>{html.escape(str(row.get('refVaHex')))}</code></td>",
                f"  <td><code>{html.escape(str(row.get('windowHex')))}</code></td>",
                f"  <td>{row.get('selectedPointerGlobalImmediateCount')}</td>",
                f"  <td>{row.get('currentSelectorRootImmediateCount')}</td>",
                f"  <td>{row.get('sourceMapStringImmediateCount')}</td>",
                f"  <td>{row.get('targetMapStringImmediateCount')}</td>",
                f"  <td>{row.get('mapLoaderDirectRelHitCount')}</td>",
                f"  <td>{row.get('scriptRunnerDirectRelHitCount')}</td>",
                f"  <td>{row.get('selectorTableDirectRelHitCount')}</td>",
                "</tr>",
            ])
        )
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>map1_01a Edge Trigger Gap</title>",
        "  <style>",
        "    :root { color-scheme: dark; font-family: system-ui, sans-serif; background: #101010; color: #eee; }",
        "    body { margin: 0; padding: 24px; }",
        "    h1 { margin: 0 0 8px; font-size: 24px; }",
        "    h2 { margin: 26px 0 10px; font-size: 18px; }",
        "    p { margin: 0 0 14px; color: #bbb; max-width: 980px; line-height: 1.45; }",
        "    table { width: 100%; border-collapse: collapse; margin: 0 0 16px; font-size: 13px; }",
        "    th, td { border-bottom: 1px solid #303030; padding: 7px 8px; text-align: left; vertical-align: top; }",
        "    th { position: sticky; top: 0; background: #181818; z-index: 1; color: #ddd; }",
        "    code { color: #d8d8d8; }",
        "  </style>",
        "</head>",
        "<body>",
        "  <h1>map1_01a Edge Trigger Gap</h1>",
        f"  <p>Route <code>{html.escape(summary['source'])} -&gt; {html.escape(summary['target'])}</code>; promotion <code>{html.escape(summary['promotionStatus'])}</code>; allowed <code>{summary['promotionAllowed']}</code>.</p>",
        f"  <p>proof found <code>{summary['proofFound']}</code>; failed edge trigger gates <code>{html.escape(', '.join(summary['failedEdgeTriggerGateIds']))}</code>; missing evidence count <code>{len(summary['missingEvidence'])}</code>; evidence refs <code>{summary.get('evidenceRefCount')}</code>.</p>",
        f"  <p>source boundary candidates <code>{summary['sourceBoundaryCandidateCount']}</code>; auto boundary candidates <code>{summary['autoBoundaryCandidateCount']}</code>; transition rel hits <code>{summary['transitionLikeDirectRelHitCountInHelperOrController']}</code>; route immediate hits <code>{summary['directRouteImmediateCountInHelperOrController']}</code>.</p>",
        f"  <p>direction latch direct .text refs <code>{latch['directRefCount']}</code>; direction latch route-window hits <code>{latch['transitionLikeWindowRelHitCount']}</code> rel / <code>{latch['routeImmediateWindowHitCount']}</code> immediates.</p>",
        f"  <p>direction latch outside helper/controller refs: reset <code>{latch['otherTextResetCount']}</code>; actor-state continuation reads <code>{latch['otherTextActorStateContinuationReadCount']}</code>; transition consumers <code>{latch['otherTextTransitionConsumerCount']}</code>; unclassified <code>{latch['otherTextUnclassifiedCount']}</code>; non-transition classified <code>{html.escape(str(latch['otherTextReferencesClassifiedNonTransition']))}</code>.</p>",
        f"  <p>actor-controller caller-window hits <code>{summary['actorControllerCallerWindowEvidence']['transitionLikeWindowRelHitCount']}</code> rel / <code>{summary['actorControllerCallerWindowEvidence']['routeImmediateWindowHitCount']}</code> immediates; collision-helper caller-window hits <code>{summary['collisionHelperCallerWindowEvidence']['transitionLikeWindowRelHitCount']}</code> rel / <code>{summary['collisionHelperCallerWindowEvidence']['routeImmediateWindowHitCount']}</code> immediates.</p>",
        f"  <p>script-runner call windows <code>{summary['scriptRunnerCallContextEvidence']['callerCount']}</code>; route immediates <code>{summary['scriptRunnerCallContextEvidence']['routeImmediateWindowHitCount']}</code>; map-loader rel <code>{summary['scriptRunnerCallContextEvidence']['mapLoaderWindowRelHitCount']}</code>; selector-table rel <code>{summary['scriptRunnerCallContextEvidence']['selectorTableWindowRelHitCount']}</code>.</p>",
        f"  <p>selected-pointer immediate windows <code>{summary['selectedPointerImmediateContextEvidence']['immediateRefCount']}</code>; route-specific hits <code>{summary['selectedPointerImmediateContextEvidence']['routeSpecificWindowHitCount']}</code>.</p>",
        f"  <p>edge handler encoded scalars raw/transition/route/selected/promoting <code>{summary['edgeHandlerEncodedTargetRawScalarCandidateCount']}</code>/<code>{summary['edgeHandlerEncodedTargetTransitionRawScalarCandidateCount']}</code>/<code>{summary['edgeHandlerEncodedTargetRouteProofRawScalarCandidateCount']}</code>/<code>{summary['edgeHandlerEncodedTargetSelectedPointerRawScalarCandidateCount']}</code>/<code>{summary['edgeHandlerEncodedTargetPromotingCandidateCount']}</code> (<code>{html.escape(str(summary['edgeHandlerEncodedTargetClassification']))}</code>).</p>",
        f"  <p>edge local-window encoded scalars raw/transition/route/selected/promoting <code>{summary['edgeLocalWindowEncodedTargetRawScalarCandidateCount']}</code>/<code>{summary['edgeLocalWindowEncodedTargetTransitionRawScalarCandidateCount']}</code>/<code>{summary['edgeLocalWindowEncodedTargetRouteProofRawScalarCandidateCount']}</code>/<code>{summary['edgeLocalWindowEncodedTargetSelectedPointerRawScalarCandidateCount']}</code>/<code>{summary['edgeLocalWindowEncodedTargetPromotingCandidateCount']}</code> (<code>{html.escape(str(summary['edgeLocalWindowEncodedTargetClassification']))}</code>).</p>",
        f"  <p>edge call-graph encoded scalars raw/transition/route/selected/promoting <code>{summary['edgeCallGraphEncodedTargetRawScalarCandidateCount']}</code>/<code>{summary['edgeCallGraphEncodedTargetTransitionRawScalarCandidateCount']}</code>/<code>{summary['edgeCallGraphEncodedTargetRouteProofRawScalarCandidateCount']}</code>/<code>{summary['edgeCallGraphEncodedTargetSelectedPointerRawScalarCandidateCount']}</code>/<code>{summary['edgeCallGraphEncodedTargetPromotingCandidateCount']}</code> (<code>{html.escape(str(summary['edgeCallGraphEncodedTargetClassification']))}</code>).</p>",
        f"  <p>global contrast-window encoded scalars raw/transition/route/selected/promoting <code>{summary['edgeGlobalContrastWindowEncodedTargetRawScalarCandidateCount']}</code>/<code>{summary['edgeGlobalContrastWindowEncodedTargetTransitionRawScalarCandidateCount']}</code>/<code>{summary['edgeGlobalContrastWindowEncodedTargetRouteProofRawScalarCandidateCount']}</code>/<code>{summary['edgeGlobalContrastWindowEncodedTargetSelectedPointerRawScalarCandidateCount']}</code>/<code>{summary['edgeGlobalContrastWindowEncodedTargetPromotingCandidateCount']}</code> (<code>{html.escape(str(summary['edgeGlobalContrastWindowEncodedTargetClassification']))}</code>).</p>",
        f"  <p>direct call graph <code>{html.escape(str(summary['directCallGraphTransitionRejectionClassification']))}</code>; proof <code>{html.escape(str(summary['directCallGraphTransitionProofFound']))}</code>; functions <code>{summary['directCallGraphReachableFunctionCount']}</code>; edges <code>{summary['directCallGraphDirectCallEdgeCount']}</code>; transition targets <code>{summary['directCallGraphTransitionTargetReachableCount']}</code>; transition hits <code>{summary['directCallGraphTransitionTargetHitCount']}</code>; route immediates <code>{summary['directCallGraphRouteImmediateHitCount']}</code>; indirect-like bytes <code>{summary['directCallGraphIndirectCallLikeByteCount']}</code>.</p>",
        f"  <p>direct call graph depth sensitivity: max depth <code>{summary['directCallGraphDepthSensitivityMaxDepthChecked']}</code>; no proof <code>{html.escape(str(summary['directCallGraphDepthSensitivityProofAbsentAcrossCheckedDepths']))}</code>; stable beyond default <code>{html.escape(str(summary['directCallGraphDepthSensitivityCountsStableAtAndBeyondDefaultDepth']))}</code>.</p>",
        f"  <p>indirect call graph <code>{html.escape(str(summary['directCallGraphIndirectRejectionClassification']))}</code>; proof <code>{html.escape(str(summary['directCallGraphIndirectProofFound']))}</code>; absolute <code>{summary['directCallGraphIndirectStaticAbsoluteMemoryCandidateCount']}</code>; jump tables <code>{summary['directCallGraphIndirectIndexedJumpTableCandidateCount']}</code>/<code>{summary['directCallGraphIndirectIndexedJumpTableEntryCount']}</code>; targets <code>{summary['directCallGraphIndirectIndexedJumpTableTargetCount']}</code>/<code>{summary['directCallGraphIndirectIndexedJumpTableUniqueTargetCount']}</code> unique; local <code>{html.escape(str(summary['directCallGraphIndirectIndexedJumpTableAllTargetsLocalToEdgeHandlers']))}</code>; outside <code>{summary['directCallGraphIndirectIndexedJumpTableOutsideEdgeHandlerTargetCount']}</code>; resolved <code>{summary['directCallGraphIndirectResolvedPointerCandidateCount']}</code>; unresolved <code>{summary['directCallGraphIndirectUnresolvedRegisterOrComputedCount']}</code>; transition hits <code>{summary['directCallGraphIndirectTransitionTargetHitCount']}</code>; route hits <code>{summary['directCallGraphIndirectRouteImmediateHitCount']}</code>.</p>",
        f"  <p>global .text transition refs: map-loader <code>{summary['globalTransitionReferenceEvidence']['directReferences']['mapLoaderDirectRelHitCount']}</code>; script-runner <code>{summary['globalTransitionReferenceEvidence']['directReferences']['scriptRunnerDirectRelHitCount']}</code>; selector-table <code>{summary['globalTransitionReferenceEvidence']['directReferences']['selectorTableDirectRelHitCount']}</code>.</p>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        "  <h2>Missing Evidence</h2>",
        "  <ul>",
        *(f"    <li>{html.escape(item)}</li>" for item in summary["missingEvidence"]),
        "  </ul>",
        "  <h2>Candidate Rows</h2>",
        "  <table>",
        "    <thead><tr><th>side</th><th>tile</th><th>direction</th><th>edge</th><th>auto</th><th>boundary</th><th>in-bounds</th><th>standable</th><th>rule</th><th>check</th></tr></thead>",
        "    <tbody>",
        *candidate_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Boundary Helper Evidence</h2>",
        "  <table>",
        "    <thead><tr><th>direction</th><th>rule</th><th>check</th><th>fallback</th><th>exit target</th><th>snippet</th></tr></thead>",
        "    <tbody>",
        *boundary_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Direct Reference Checks</h2>",
        "  <table>",
        "    <thead><tr><th>range</th><th>map loader rel</th><th>script runner rel</th><th>selector table rel</th><th>selected ptr imm</th><th>current root imm</th><th>source string imm</th><th>target string imm</th></tr></thead>",
        "    <tbody>",
        *direct_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Direct Call Graph</h2>",
        f"  <p>{html.escape(summary['directCallGraphEvidence']['conclusion'])}</p>",
        "  <table>",
        "    <thead><tr><th>function</th><th>depth</th><th>range</th><th>calls</th><th>transition hits</th><th>route immediates</th></tr></thead>",
        "    <tbody>",
        *call_graph_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Direct Call Graph Depth Sensitivity</h2>",
        f"  <p>{html.escape(summary['directCallGraphDepthSensitivity']['conclusion'])}</p>",
        "  <table>",
        "    <thead><tr><th>max depth</th><th>observed depth</th><th>functions</th><th>edges</th><th>transition targets</th><th>transition hits</th><th>route immediates</th><th>indirect-like bytes</th><th>proof</th><th>classification</th></tr></thead>",
        "    <tbody>",
        *depth_sensitivity_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Indirect Call/Jump Candidates</h2>",
        f"  <p>{html.escape(str((summary['directCallGraphEvidence'].get('indirectCallGraphRejection') or {}).get('conclusion') or ''))}</p>",
        "  <table>",
        "    <thead><tr><th>function</th><th>depth</th><th>instruction</th><th>kind</th><th>operand</th><th>table</th><th>entries</th><th>absolute</th><th>resolved</th><th>target hit</th><th>route hit</th></tr></thead>",
        "    <tbody>",
        *indirect_call_graph_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Indirect Jump Table Audit</h2>",
        "  <table>",
        "    <thead><tr><th>function</th><th>instruction</th><th>table</th><th>entries</th><th>target classes</th><th>local-only</th><th>outside</th><th>transition labels</th><th>route labels</th></tr></thead>",
        "    <tbody>",
        *indirect_jump_table_audit_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Selected Pointer Immediate Context</h2>",
        f"  <p>{html.escape(summary['selectedPointerImmediateContextEvidence']['conclusion'])}</p>",
        "  <table>",
        "    <thead><tr><th>ref</th><th>window</th><th>selected ptr imm</th><th>current root imm</th><th>source string imm</th><th>target string imm</th><th>map-loader rel</th><th>script-runner rel</th><th>selector-table rel</th></tr></thead>",
        "    <tbody>",
        *selected_pointer_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Script Runner Call Context</h2>",
        f"  <p>{html.escape(summary['scriptRunnerCallContextEvidence']['conclusion'])}</p>",
        "  <table>",
        "    <thead><tr><th>call</th><th>window</th><th>selected ptr imm</th><th>current root imm</th><th>source string imm</th><th>target string imm</th><th>map-loader rel</th><th>selector-table rel</th></tr></thead>",
        "    <tbody>",
        *script_runner_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Direction Latch Global Scan</h2>",
        f"  <p>{html.escape(latch['conclusion'])}</p>",
        "  <table>",
        "    <thead><tr><th>ref</th><th>range</th><th>kind</th><th>value</th></tr></thead>",
        "    <tbody>",
        *latch_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Direction Latch Outside Helper/Controller Audit</h2>",
        "  <table>",
        "    <thead><tr><th>instruction</th><th>ref</th><th>class</th><th>effect</th><th>transition rel</th><th>route imm</th><th>bytes</th></tr></thead>",
        "    <tbody>",
        *latch_other_text_rows,
        "    </tbody>",
        "  </table>",
        "  <h2>Caller Window Scans</h2>",
        "  <table>",
        "    <thead><tr><th>target</th><th>callers</th><th>local transition rel hits</th><th>local route immediates</th><th>hit windows</th></tr></thead>",
        "    <tbody>",
        *caller_window_rows,
        "    </tbody>",
        "  </table>",
        "</body>",
        "</html>",
        "",
    ])


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


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--exe", type=Path, default=EXE)
    parser.add_argument("--runtime-movement", type=Path, default=OUT / "runtime_movement.json")
    parser.add_argument("--original-collision-route-audit", type=Path, default=OUT / "original_collision_route_audit.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary(
        args.exe,
        load_json(args.runtime_movement, {}),
        load_json(args.original_collision_route_audit, {}),
    )
    write_outputs(summary, args.out_dir)
    print(f"wrote map1_01a edge trigger gap -> {args.out_dir / 'map1_01a_edge_trigger_gap.md'}")


if __name__ == "__main__":
    main()
