#!/usr/bin/env python3
"""Build a focused review for the selected scene/text root consumer path.

The route-level scene/event VM proof is still blocked, but the selected-pointer
consumer itself is narrow and grounded:

- writers populate global 0x0059de30,
- opcode 0x08 checks and consumes it,
- when nonzero, opcode 0x08 replaces context+0x40 with that selected root.

This report keeps that consumer proof separate from the still-missing proof
that map1_01a actually selects/executes selector 2:0 on a normal route.
"""
from __future__ import annotations

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


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

PE_SECTIONS = [
    (0x00401000, 0x0003978C, 0x00000400),
    (0x0043B000, 0x00000359, 0x00039C00),
    (0x0043C000, 0x0011DE00, 0x0003A000),
    (0x005A0000, 0x00001038, 0x00157E00),
    (0x005A2000, 0x000008E8, 0x00159000),
    (0x005A3000, 0x0001A24A, 0x00159A00),
]

SAVE_LOADER_READ_WRITE_CHECKS = [
    {
        "label": "load state block 0x4576d8 size 0x72",
        "va": 0x004233F3,
        "expected": "6a 00 8d 45 e8 50 6a 72 68 d8 76 45 00 8b 45 e4 50 ff 15 1c 04 5a 00",
    },
    {
        "label": "load descriptor block 0x457750 size 0x288",
        "va": 0x0042341C,
        "expected": "6a 00 8d 45 e8 50 68 88 02 00 00 68 50 77 45 00 8b 45 e4 50 ff 15 1c 04 5a 00",
    },
    {
        "label": "load flag block 0x59db60 size 0x200",
        "va": 0x00423448,
        "expected": "6a 00 8d 45 e8 50 68 00 02 00 00 68 60 db 59 00 8b 45 e4 50 ff 15 1c 04 5a 00",
    },
    {
        "label": "restore selected root from loaded selector group/slot",
        "va": 0x004234A3,
        "expected": "a0 da 76 45 00 8b 04 85 35 2d 44 00 33 c9 8a 0d db 76 45 00 8b 04 88 a3 30 de 59 00",
    },
    {
        "label": "save state block 0x4576d8 size 0x72",
        "va": 0x0042352C,
        "expected": "6a 00 8d 45 e8 50 6a 72 68 d8 76 45 00 8b 45 e4 50 ff 15 04 04 5a 00",
    },
    {
        "label": "save descriptor block 0x457750 size 0x288",
        "va": 0x00423555,
        "expected": "6a 00 8d 45 e8 50 68 88 02 00 00 68 50 77 45 00 8b 45 e4 50 ff 15 04 04 5a 00",
    },
    {
        "label": "save flag block 0x59db60 size 0x200",
        "va": 0x00423581,
        "expected": "6a 00 8d 45 e8 50 68 00 02 00 00 68 60 db 59 00 8b 45 e4 50 ff 15 04 04 5a 00",
    },
]


def load_json(path: Path, default: Any) -> Any:
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        return default


def write_json(path: Path, payload: Any) -> None:
    path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def write_text(path: Path, text: str) -> None:
    path.write_text(text, encoding="utf-8")


def h(value: Any) -> str:
    return html.escape("" if value is None else str(value))


def as_int(value: Any, default: int = 0) -> int:
    try:
        return int(value)
    except (TypeError, ValueError):
        return default


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


def va_to_file_offset(va: int) -> int:
    for vma, size, file_off in PE_SECTIONS:
        if vma <= va < vma + size:
            return file_off + (va - vma)
    raise ValueError(f"VA outside known PE sections: {hx(va)}")


def exe_bytes(va: int, size: int) -> str:
    data = EXE.read_bytes()
    off = va_to_file_offset(va)
    return data[off : off + size].hex(" ")


def build_save_loader_restore_context() -> dict[str, Any]:
    byte_checks = []
    for row in SAVE_LOADER_READ_WRITE_CHECKS:
        expected = row["expected"]
        actual = exe_bytes(row["va"], len(bytes.fromhex(expected)))
        byte_checks.append(
            {
                "label": row["label"],
                "vaHex": hx(row["va"]),
                "expectedBytes": expected,
                "actualBytes": actual,
                "matches": actual == expected,
            }
        )

    return {
        "functionVaHex": "0x00423319",
        "modeArgument": "[ebp+0x08]",
        "loadMode": 0,
        "saveMode": 1,
        "stateBlockVaHex": "0x004576d8",
        "stateBlockSizeHex": "0x72",
        "selectorGroupByteVaHex": "0x004576da",
        "selectorSlotByteVaHex": "0x004576db",
        "descriptorBlockVaHex": "0x00457750",
        "descriptorBlockSizeHex": "0x288",
        "flagBlockVaHex": "0x0059db60",
        "flagBlockSizeHex": "0x200",
        "selectedRootRestoreVaHex": "0x004234a3",
        "selectedPointerGlobalHex": "0x0059de30",
        "selectorGroupTableBaseHex": "0x00442d35",
        "contextGrounded": all(row["matches"] for row in byte_checks),
        "routeProducerPromoted": False,
        "interpretation": (
            "This path restores 0x0059de30 after loading persisted selector bytes "
            "0x4576da/0x4576db. It proves save/load selected-root reconstruction, "
            "not a normal live scene/event route producer."
        ),
        "byteChecks": byte_checks,
    }


def short(value: Any, limit: int = 180) -> str:
    if isinstance(value, (dict, list)):
        value = json.dumps(value, ensure_ascii=False, sort_keys=True)
    text = " ".join((str(value) if value is not None else "").split())
    return text if len(text) <= limit else text[: limit - 1] + "…"


def context_by_name(contexts: list[dict[str, Any]], name: str) -> dict[str, Any]:
    return next((row for row in contexts if row.get("name") == name), {})


def classify_context(row: dict[str, Any]) -> str:
    name = row.get("name")
    if name == "opcode08-activator":
        return "consumer"
    if name in {"save-loader-selector-store", "opcode07-indexed-store", "opcode09-stream-store"}:
        return "producer"
    return "support"


def build(args: argparse.Namespace) -> dict[str, Any]:
    usage = load_json(args.selected_pointer_usage, {})
    opcode_paths = load_json(args.opcode_paths, {})
    activation_windows = load_json(args.activation_windows, {})
    execution_route = load_json(args.execution_route, {})
    selected_external = load_json(args.selected_external, {})
    merge_bridge = load_json(args.merge_bridge, {})
    save_loader_restore = build_save_loader_restore_context()

    contexts = usage.get("selectedPointerHandlerContexts") or []
    consumer = context_by_name(contexts, "opcode08-activator")
    context_rows = []
    for row in contexts:
        context_rows.append(
            {
                "name": row.get("name"),
                "role": classify_context(row),
                "handlerVaHex": row.get("handlerVaHex"),
                "hookVaHexes": row.get("hookVaHexes") or [],
                "verified": bool(row.get("verified")),
                "effect": row.get("contextEffect"),
                "routeImplication": row.get("routeImplication"),
                "traceHook": row.get("traceHook"),
            }
        )

    byte_checks = []
    for row in consumer.get("byteChecks") or []:
        byte_checks.append(
            {
                "label": row.get("label"),
                "vaHex": row.get("vaHex"),
                "bytes": row.get("actualBytes"),
                "matches": bool(row.get("matches")),
            }
        )

    source_predecessor_current_range = int(
        opcode_paths.get("sourceOrPredecessorCurrentRangeWriterCount")
        or activation_windows.get("sourceOrPredecessorCurrentRangeProducerCount")
        or 0
    )
    source_predecessor_current_root = int(
        opcode_paths.get("sourceOrPredecessorCurrentRootWriterCount")
        or activation_windows.get("sourceOrPredecessorCurrentRootProducerCount")
        or 0
    )
    source_predecessor_activators = int(
        opcode_paths.get("sourceOrPredecessorOpcode08ActivatorCount")
        or activation_windows.get("sourceOrPredecessorOpcode08ActivatorCount")
        or 0
    )
    save_loader_gate = selected_external.get("saveLoaderGate") or {}
    execution_bridge_matrix = selected_external.get("executionBridgeMatrix") or {}
    if not execution_bridge_matrix:
        execution_bridge_matrix = merge_bridge
    source_to_current_bridge_hits = as_int(
        execution_bridge_matrix.get("sourceToCurrentBridgeHitCount")
        or execution_bridge_matrix.get("sourceToCurrentHitCount")
    )
    predecessor_to_current_bridge_hits = as_int(
        execution_bridge_matrix.get("predecessorToCurrentHitCount")
        or execution_bridge_matrix.get("predecessorToCurrentBridgeHitCount")
    )
    forward_merge_bridge_hits = as_int(execution_bridge_matrix.get("forwardMergeBridgeHitCount"))
    consumer_hit_probe_count = 0
    route_value_probe_count = 0
    runtime_consumer_proof = False

    summary = {
        "selectedPointerGlobalHex": usage.get("selectedPointerGlobalHex"),
        "currentSelector": usage.get("currentSelector") or opcode_paths.get("currentSelector"),
        "currentSelectorRootHex": usage.get("currentSelectorRootHex"),
        "consumerHandlerVaHex": consumer.get("handlerVaHex"),
        "consumerZeroCheckVaHex": "0x0040add6",
        "consumerReadVaHex": "0x0040adfe",
        "consumerContextStoreVaHex": "0x0040ae06",
        "consumerVerified": bool(consumer.get("verified")),
        "writerHookCount": usage.get("selectedPointerWriterHookCount", 0),
        "readerHookCount": usage.get("selectedPointerReaderHookCount", 0),
        "sourceOrPredecessorOpcode08ActivatorCount": source_predecessor_activators,
        "sourceOrPredecessorCurrentRootProducerCount": source_predecessor_current_root,
        "sourceOrPredecessorCurrentRangeProducerCount": source_predecessor_current_range,
        "currentInternalCurrentRangeProducerCount": activation_windows.get("currentInternalCurrentRangeProducerCount", 0),
        "directExecutionRootFound": execution_route.get("summary", {}).get("directExecutionRootFound", False),
        "routeConsumerProofFound": False,
        "routeBridgeProofFound": False,
        "selectedRootExternalProofFound": bool(selected_external.get("selectedRootExternalProofFound")),
        "realSelector20SaveCount": as_int(save_loader_gate.get("currentSelectorRealSaveCount")),
        "sourceToCurrentBridgeHitCount": source_to_current_bridge_hits,
        "predecessorToCurrentBridgeHitCount": predecessor_to_current_bridge_hits,
        "forwardMergeBridgeHitCount": forward_merge_bridge_hits,
        "producerHotspotProofFound": False,
        "runtimeSelectedRootConsumerProofFound": runtime_consumer_proof,
        "runtimeConsumerHitProbeCount": consumer_hit_probe_count,
        "runtimeRouteValueProbeCount": route_value_probe_count,
        "saveLoaderRestoreFunctionVaHex": save_loader_restore["functionVaHex"],
        "saveLoaderRestoreSelectedRootConsumerVaHex": save_loader_restore["selectedRootRestoreVaHex"],
        "saveLoaderRestoreStateBlockVaHex": save_loader_restore["stateBlockVaHex"],
        "saveLoaderRestoreStateBlockSizeHex": save_loader_restore["stateBlockSizeHex"],
        "saveLoaderRestoreDescriptorBlockVaHex": save_loader_restore["descriptorBlockVaHex"],
        "saveLoaderRestoreDescriptorBlockSizeHex": save_loader_restore["descriptorBlockSizeHex"],
        "saveLoaderRestoreFlagBlockVaHex": save_loader_restore["flagBlockVaHex"],
        "saveLoaderRestoreFlagBlockSizeHex": save_loader_restore["flagBlockSizeHex"],
        "saveLoaderRestoreConsumerContextGrounded": save_loader_restore["contextGrounded"],
        "saveLoaderRestoreConsumerRouteProducerPromoted": save_loader_restore["routeProducerPromoted"],
    }

    route_bridge_gates = [
        {
            "gate": "consumer mechanism",
            "status": "grounded",
            "evidence": "opcode 0x08 reads 0x0059de30 at 0x0040adfe and stores it into context+0x40",
            "blocksRouteProof": False,
        },
        {
            "gate": "save/load selected-root restore",
            "status": "grounded",
            "evidence": (
                "function 0x00423319 reads 0x4576d8/0x457750/0x59db60 on load, "
                "then 0x004234a3 reconstructs 0x0059de30 from 0x4576da/0x4576db"
            ),
            "blocksRouteProof": False,
        },
        {
            "gate": "save/load as live route producer",
            "status": "blocked",
            "evidence": "the same routine is a persisted-state restore/write path; it does not prove a normal scene/event route chose selector 2:0",
            "blocksRouteProof": True,
        },
        {
            "gate": "selected root external proof",
            "status": "blocked",
            "evidence": f"selectedRootExternalProofFound={summary['selectedRootExternalProofFound']}; real selector 2:0 saves={summary['realSelector20SaveCount']}",
            "blocksRouteProof": True,
        },
        {
            "gate": "source/current bridge",
            "status": "blocked",
            "evidence": f"source->current={source_to_current_bridge_hits}; predecessor->current={predecessor_to_current_bridge_hits}; forward merge={forward_merge_bridge_hits}",
            "blocksRouteProof": True,
        },
        {
            "gate": "producer/hotspot",
            "status": "blocked",
            "evidence": f"producerHotspotProofFound={summary['producerHotspotProofFound']}; source/predecessor current-range producers={source_predecessor_current_range}",
            "blocksRouteProof": True,
        },
        {
            "gate": "runtime consumer watchpoint",
            "status": "blocked",
            "evidence": f"runtimeProof={runtime_consumer_proof}; consumer hits={consumer_hit_probe_count}; route-value hits={route_value_probe_count}",
            "blocksRouteProof": True,
        },
    ]

    decisions = [
        {
            "item": "selected root consumer",
            "promotion": "grounded",
            "evidence": "opcode 0x08 handler 0x0040adc9 checks global 0x0059de30, reads it at 0x0040adfe, then writes context+0x40 at 0x0040ae06",
            "remainingGap": "not route-specific by itself; the global value must be proven to be 0x00540714 on the route path",
        },
        {
            "item": "selected root producers",
            "promotion": "grounded",
            "evidence": "save/load restore, opcode 0x07, opcode 0x09 stores to 0x0059de30 are byte-verified",
            "remainingGap": "save/load restore is not a live route producer; source/predecessor roots do not currently produce current selector 2:0 root/range",
        },
        {
            "item": "save/load selected-root restore",
            "promotion": "grounded",
            "evidence": "0x00423319 load mode reads state/descriptor/flag blocks, and 0x004234a3 rebuilds selected root from persisted selector bytes",
            "remainingGap": "does not show which live event command originally wrote 0x4576da/0x4576db or selected selector 2:0",
        },
        {
            "item": "source/predecessor producer scan",
            "promotion": "blocked",
            "evidence": f"opcode 0x08 activators={source_predecessor_activators}, current-root producers={source_predecessor_current_root}, current-range producers={source_predecessor_current_range}",
            "remainingGap": "needs captured selector 2:0 save, runtime selected-pointer trace, or strict map1_01a producer/hotspot",
        },
        {
            "item": "consumer-to-route bridge",
            "promotion": "blocked",
            "evidence": f"consumer is grounded, but source/current bridge={source_to_current_bridge_hits}, predecessor/current bridge={predecessor_to_current_bridge_hits}, runtime consumer proof={runtime_consumer_proof}",
            "remainingGap": "prove 0x0040adfe consumes 0x00540714 on a normal map1_01a route path, not only that the consumer exists",
        },
        {
            "item": "map1 direct execution root",
            "promotion": "gap",
            "evidence": f"directExecutionRootFound={summary['directExecutionRootFound']}",
            "remainingGap": "scene record to executed VM stream is still not proven",
        },
    ]

    pseudo_code = [
        "opcode07: selected = dword[dword[stream+4] + byte[stream+1] * 4]; global_0059de30 = selected; stream += 8",
        "opcode08: stream += 4; if global_0059de30 != 0 { stack.push(stream); stream = global_0059de30 }",
        "opcode09 mode0: stream += 4; global_0059de30 = stream",
        "opcode09 mode1: global_0059de30 = dword[stream+4]; stream += 8",
        "save/load restore: read 0x4576d8[0x72], 0x457750[0x288], 0x59db60[0x200]",
        "save/load restore: global_0059de30 = selectorGroupTable[byte[0x4576da]][byte[0x4576db]]",
    ]

    return {
        "scope": "selected scene/text root consumer trace",
        "promotionStatus": "consumer-grounded-route-proof-blocked",
        "summary": summary,
        "pseudoCode": pseudo_code,
        "handlerContexts": context_rows,
        "consumerByteChecks": byte_checks,
        "saveLoaderRestoreContext": save_loader_restore,
        "routeBridgeGates": route_bridge_gates,
        "decisions": decisions,
        "remainingProofs": [
            "runtime watchpoint at 0x0040adfe proving 0x0059de30 == 0x00540714 on the route path",
            "captured gameplay save whose save bytes select group 2 slot 0",
            "strict map1_01a hotspot/producer that sets selected pointer to selector 2:0 before opcode 0x08",
        ],
        "sourceArtifacts": {
            "selectedPointerUsage": str(args.selected_pointer_usage),
            "opcodePaths": str(args.opcode_paths),
            "activationWindows": str(args.activation_windows),
            "executionRoute": str(args.execution_route),
            "selectedExternal": str(args.selected_external),
            "mergeBridge": str(args.merge_bridge),
            "runtimeHandoff": str(OUT / "scene_event_runtime_evidence_handoff.json"),
        },
    }


def html_page(payload: dict[str, Any]) -> str:
    def chip(status: str) -> str:
        cls = {"grounded": "good", "blocked": "bad", "gap": "bad"}.get(status, "muted")
        return f'<span class="tag {cls}">{h(status)}</span>'

    def table(rows: list[dict[str, Any]], cols: list[tuple[str, str]]) -> str:
        head = "".join(f"<th>{h(label)}</th>" for _key, label in cols)
        body = []
        for row in rows:
            cells = []
            for key, _label in cols:
                value = row.get(key)
                if isinstance(value, list):
                    value = ", ".join(map(str, value))
                if key == "promotion":
                    cells.append(f"<td>{chip(str(value))}</td>")
                elif str(key).endswith("Hex") or key in {"handlerVaHex", "vaHex", "bytes"}:
                    cells.append(f"<td><code>{h(value)}</code></td>")
                else:
                    cells.append(f"<td>{h(short(value, 220))}</td>")
            body.append("<tr>" + "".join(cells) + "</tr>")
        return f"<table><thead><tr>{head}</tr></thead><tbody>{''.join(body)}</tbody></table>"

    s = payload["summary"]
    pseudo = "".join(f"<li><code>{h(row)}</code></li>" for row in payload["pseudoCode"])
    proofs = "".join(f"<li>{h(row)}</li>" for row in payload["remainingProofs"])
    return f"""<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link rel="icon" href="../favicon.ico" />
  <title>Selected Scene/Text Root Consumer</title>
  <style>
    body {{ margin:0; background:#f6f7f9; color:#17202a; font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; line-height:1.45; }}
    main {{ max-width:1500px; margin:0 auto; padding:18px; }}
    header {{ display:flex; justify-content:space-between; gap:16px; align-items:flex-start; }}
    h1 {{ margin:0; font-size:24px; }}
    h2 {{ margin:0; font-size:17px; }}
    nav {{ display:flex; flex-wrap:wrap; gap:10px; justify-content:flex-end; }}
    a {{ color:#185abc; text-decoration:none; font-weight:700; }}
    a:hover {{ text-decoration:underline; }}
    section {{ background:#fff; border:1px solid #d8dee6; border-radius:8px; margin:14px 0; overflow:hidden; }}
    .head {{ padding:12px 14px; background:#eef2f6; border-bottom:1px solid #d8dee6; font-weight:800; }}
    .body {{ padding:14px; }}
    .metrics {{ display:grid; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)); gap:10px; }}
    .metric {{ border:1px solid #d8dee6; border-radius:6px; background:#f8fafc; padding:10px; }}
    .metric strong {{ display:block; font-size:20px; }}
    table {{ width:100%; border-collapse:collapse; }}
    th,td {{ padding:8px 10px; border-bottom:1px solid #d8dee6; vertical-align:top; text-align:left; font-size:13px; }}
    th {{ background:#f8fafc; color:#344050; }}
    code {{ font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }}
    .tag {{ display:inline-block; padding:2px 7px; border-radius:999px; background:#edf2f7; color:#334155; font-size:12px; white-space:nowrap; }}
    .tag.good {{ color:#0f766e; background:#e6f4f1; }}
    .tag.bad {{ color:#b42318; background:#fdebea; }}
    .muted {{ color:#607080; }}
  </style>
</head>
<body>
<main data-page="selected-scene-text-root-consumer-review">
  <header>
    <div>
      <h1>Selected Scene/Text Root Consumer</h1>
      <p class="muted">0x0059de30 selected pointer를 실제로 소비하는 handler와 아직 남은 route proof를 분리한다.</p>
    </div>
    <nav>
      <a href="index.html">관리 홈</a>
      <a href="scene_event_vm_execution_route_review.html">VM 실행 루트</a>
      <a href="scene_event_vm_branch_flag_review.html">branch/flag</a>
      <a href="scene_event_vm_prompt_sequence_review.html">prompt sequence</a>
      <a href="scene_event_vm_choice_target_review.html">choice target</a>
      <a href="scene_event_vm_random_gate_review.html">random gate</a>
      <a href="scene_event_runtime_evidence_handoff.html">runtime handoff</a>
      <a href="scene_event_vm_review.html">opcode review</a>
      <a href="../out/selected_scene_text_root_consumer_review.json">JSON</a>
    </nav>
  </header>
  <section>
    <div class="head">요약</div>
    <div class="body metrics">
      <div class="metric"><strong>{h(s['selectedPointerGlobalHex'])}</strong><span>selected pointer global</span></div>
      <div class="metric"><strong>{h(s['consumerHandlerVaHex'])}</strong><span>consumer handler opcode 0x08</span></div>
      <div class="metric"><strong>{h(s['consumerReadVaHex'])}</strong><span>narrow runtime proof point</span></div>
      <div class="metric"><strong>{h(s['saveLoaderRestoreSelectedRootConsumerVaHex'])}</strong><span>save/load selected-root restore</span></div>
      <div class="metric"><strong>{h(s['sourceOrPredecessorCurrentRangeProducerCount'])}</strong><span>source/predecessor current-range producers</span></div>
      <div class="metric"><strong>{h(s['sourceToCurrentBridgeHitCount'])}</strong><span>source/current bridge hits</span></div>
      <div class="metric"><strong>{h(s['runtimeConsumerHitProbeCount'])}</strong><span>runtime consumer hits</span></div>
    </div>
  </section>
  <section><div class="head">Pseudo Code</div><div class="body"><ul>{pseudo}</ul></div></section>
  <section><div class="head">Handler Contexts</div>{table(payload['handlerContexts'], [('name','name'),('role','role'),('handlerVaHex','handler'),('hookVaHexes','hooks'),('verified','verified'),('effect','effect'),('routeImplication','route implication')])}</section>
  <section><div class="head">Consumer Byte Checks</div>{table(payload['consumerByteChecks'], [('vaHex','va'),('bytes','bytes'),('matches','matches'),('label','label')])}</section>
  <section>
    <div class="head">Save/Load Restore Context</div>
    <div class="body">
      <p>세이브 로드/저장 함수 <code>{h(payload['saveLoaderRestoreContext']['functionVaHex'])}</code>는 <code>{h(payload['saveLoaderRestoreContext']['stateBlockVaHex'])}</code>, <code>{h(payload['saveLoaderRestoreContext']['descriptorBlockVaHex'])}</code>, <code>{h(payload['saveLoaderRestoreContext']['flagBlockVaHex'])}</code> 블록을 읽고/쓴다. 이후 <code>{h(payload['saveLoaderRestoreContext']['selectedRootRestoreVaHex'])}</code>에서 저장된 selector group/slot 바이트로 <code>{h(payload['saveLoaderRestoreContext']['selectedPointerGlobalHex'])}</code>을 재구성한다.</p>
      <p class="muted">{h(payload['saveLoaderRestoreContext']['interpretation'])}</p>
    </div>
    {table(payload['saveLoaderRestoreContext']['byteChecks'], [('vaHex','va'),('actualBytes','actual bytes'),('matches','matches'),('label','label')])}
  </section>
  <section><div class="head">Route Bridge Gates</div>{table(payload['routeBridgeGates'], [('gate','gate'),('status','status'),('evidence','evidence'),('blocksRouteProof','blocks route proof')])}</section>
  <section><div class="head">Decisions</div>{table(payload['decisions'], [('item','item'),('promotion','promotion'),('evidence','evidence'),('remainingGap','remaining gap')])}</section>
  <section><div class="head">Remaining Proofs</div><div class="body"><ul>{proofs}</ul></div></section>
</main>
<script>
window.HWANSE_SELECTED_SCENE_TEXT_ROOT_CONSUMER_READY = {{
  selectedPointerGlobalHex: "{h(s['selectedPointerGlobalHex'])}",
  consumerHandlerVaHex: "{h(s['consumerHandlerVaHex'])}",
  consumerReadVaHex: "{h(s['consumerReadVaHex'])}",
  consumerVerified: {str(bool(s['consumerVerified'])).lower()},
  routeConsumerProofFound: {str(bool(s['routeConsumerProofFound'])).lower()},
  routeBridgeProofFound: {str(bool(s['routeBridgeProofFound'])).lower()},
  sourceOrPredecessorCurrentRangeProducerCount: {int(s['sourceOrPredecessorCurrentRangeProducerCount'])},
  sourceToCurrentBridgeHitCount: {int(s['sourceToCurrentBridgeHitCount'])},
  predecessorToCurrentBridgeHitCount: {int(s['predecessorToCurrentBridgeHitCount'])},
  runtimeConsumerHitProbeCount: {int(s['runtimeConsumerHitProbeCount'])},
  saveLoaderRestoreConsumerContextGrounded: {str(bool(s['saveLoaderRestoreConsumerContextGrounded'])).lower()},
  saveLoaderRestoreConsumerRouteProducerPromoted: {str(bool(s['saveLoaderRestoreConsumerRouteProducerPromoted'])).lower()},
  selectedRootConsumerGrounded: true
}};
</script>
</body>
</html>
"""


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--selected-pointer-usage", type=Path, default=OUT / "save_selector_selected_pointer_usage.json")
    parser.add_argument("--opcode-paths", type=Path, default=OUT / "save_selector_selected_pointer_opcode_paths.json")
    parser.add_argument("--activation-windows", type=Path, default=OUT / "save_selector_opcode08_activation_windows.json")
    parser.add_argument("--execution-route", type=Path, default=OUT / "scene_event_vm_execution_route_review.json")
    parser.add_argument("--selected-external", type=Path, default=OUT / "selected_root_execution_external_proof_packet.json")
    parser.add_argument("--merge-bridge", type=Path, default=OUT / "save_selector_merge_bridge_matrix.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()

    payload = build(args)
    args.out_dir.mkdir(parents=True, exist_ok=True)
    write_json(args.out_dir / "selected_scene_text_root_consumer_review.json", payload)
    write_text(args.out_dir / "selected_scene_text_root_consumer_review.html", html_page(payload))
    write_text(WEB / "selected_scene_text_root_consumer_review.html", html_page(payload))
    print(f"wrote selected scene/text root consumer review -> {WEB / 'selected_scene_text_root_consumer_review.html'}")


if __name__ == "__main__":
    main()
