#!/usr/bin/env python3
"""Build an external proof packet for the opcode 0x20 gate-base blocker."""
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"
SOURCE = "map1_01a"
TARGET = "map2_02d"


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


def proof_gate_rows(gate_base: dict) -> list[dict]:
    return [
        {
            "id": "localOpcode20CandidateOnly",
            "pass": False,
            "status": "local-candidate-not-proof",
            "detail": (
                f"only local base-affecting candidate is opcode 0x20 at "
                f"{gate_base.get('opcode20CandidateVaHex')}; runtime base path remains unproven"
            ),
        },
        {
            "id": "gateTimeContextA8Base",
            "pass": gate_base.get("gateTimeBaseProofFound") is True,
            "status": "missing" if gate_base.get("gateTimeBaseProofFound") is not True else "present",
            "detail": "context+0xa8 base at 0x005428c4/0x005428cc is not proven on the route path",
        },
        {
            "id": "descriptorSpecificRouteProof",
            "pass": gate_base.get("descriptorAllScriptSpecificGateBaseProven") is True,
            "status": "missing" if gate_base.get("descriptorAllScriptSpecificGateBaseProven") is not True else "present",
            "detail": (
                "descriptor+0/+4/+8 scripts have no field-map/current-frontier/gate row or "
                "encoded route-target proof"
            ),
        },
        {
            "id": "activeOrderRuntimeState",
            "pass": gate_base.get("activeOrderProofFound") is True,
            "status": "diagnostic-only" if gate_base.get("activeOrderOnlyProofEliminated") is True else "missing",
            "detail": "active order observations are diagnostic/public-predecessor only and do not prove selector 2:0",
        },
        {
            "id": "runtimeObjectTableState",
            "pass": gate_base.get("opcode20ContextF2SpecificRuntimeObjectPointerProven") is True,
            "status": "missing",
            "detail": "context+0xf2 object-table state is required and only diagnostic object-table evidence exists",
        },
        {
            "id": "predecessorStatePersistence",
            "pass": gate_base.get("predecessorPersistenceProofFound") is True,
            "status": "missing" if gate_base.get("predecessorPersistenceProofFound") is not True else "present",
            "detail": "predecessor 1:0 state persistence into current selector 2:0 is not proven",
        },
        {
            "id": "strictSourceHotspot",
            "pass": gate_base.get("strictHotspotProofFound") is True,
            "status": "missing" if gate_base.get("strictHotspotProofFound") is not True else "present",
            "detail": "strict map1_01a source hotspot or equivalent trigger remains absent",
        },
    ]


def source_report_rows(gate_base: dict) -> list[dict]:
    return [
        {
            "id": "gate-base-proof-gap",
            "proofFound": gate_base.get("gateBaseProofFound"),
            "failedGateIds": gate_base.get("failedGateBaseGateIds") or [],
            "missingEvidence": gate_base.get("missingEvidence") or [],
            "evidenceRefCount": gate_base.get("evidenceRefCount"),
        },
        {
            "id": "gate-offset-sources",
            "proofFound": gate_base.get("gateOffsetSourceProofFound"),
            "failedGateIds": gate_base.get("gateOffsetSourceFailedGateIds") or [],
            "evidenceRefCount": gate_base.get("gateOffsetSourceEvidenceRefCount"),
        },
        {
            "id": "gate-offset-patterns",
            "proofFound": gate_base.get("gateOffsetPatternProofFound"),
            "failedGateIds": gate_base.get("gateOffsetPatternFailedGateIds") or [],
            "evidenceRefCount": gate_base.get("gateOffsetPatternEvidenceRefCount"),
        },
        {
            "id": "gate-base-candidates",
            "proofFound": gate_base.get("gateBaseCandidateProofFound"),
            "failedGateIds": gate_base.get("gateBaseCandidateFailedGateIds") or [],
            "evidenceRefCount": gate_base.get("gateBaseCandidateEvidenceRefCount"),
        },
        {
            "id": "gate-sample-values",
            "proofFound": gate_base.get("gateSampleValueProofFound"),
            "failedGateIds": gate_base.get("gateSampleValueFailedGateIds") or [],
            "evidenceRefCount": gate_base.get("gateSampleValueEvidenceRefCount"),
        },
        {
            "id": "selection-buffer-bases",
            "proofFound": gate_base.get("selectionBufferBaseProofFound"),
            "failedGateIds": gate_base.get("selectionBufferBaseFailedGateIds") or [],
            "evidenceRefCount": gate_base.get("selectionBufferBaseEvidenceRefCount"),
        },
        {
            "id": "opcode20-object-base",
            "proofFound": gate_base.get("opcode20ObjectBaseProofFound"),
            "failedGateIds": gate_base.get("opcode20ObjectBaseFailedGateIds") or [],
            "evidenceRefCount": gate_base.get("opcode20ObjectBaseEvidenceRefCount"),
        },
        {
            "id": "opcode20-order-space",
            "proofFound": gate_base.get("opcode20OrderSpaceProofFound"),
            "failedGateIds": gate_base.get("opcode20OrderSpaceFailedGateIds") or [],
            "evidenceRefCount": gate_base.get("opcode20OrderSpaceEvidenceRefCount"),
        },
        {
            "id": "opcode20-slot-source",
            "proofFound": gate_base.get("opcode20SlotSourceProofFound"),
            "failedGateIds": gate_base.get("opcode20SlotSourceFailedGateIds") or [],
            "evidenceRefCount": gate_base.get("opcode20SlotSourceEvidenceRefCount"),
        },
        {
            "id": "opcode20-descriptor-writer",
            "proofFound": gate_base.get("opcode20DescriptorWriterProofFound"),
            "failedGateIds": gate_base.get("opcode20DescriptorWriterFailedGateIds") or [],
            "evidenceRefCount": gate_base.get("opcode20DescriptorWriterEvidenceRefCount"),
        },
        {
            "id": "opcode20-runtime-materializer",
            "proofFound": gate_base.get("opcode20RuntimeMaterializerProofFound"),
            "failedGateIds": gate_base.get("opcode20RuntimeMaterializerFailedGateIds") or [],
            "evidenceRefCount": gate_base.get("opcode20RuntimeMaterializerEvidenceRefCount"),
        },
    ]


def build_summary(out_dir: Path = OUT) -> dict:
    gate_base = load_json(out_dir / "save_selector_gate_base_proof_gap.json", {})
    gate_rows = proof_gate_rows(gate_base)
    return {
        "source": SOURCE,
        "target": TARGET,
        "promotionStatus": "blocked",
        "proofFound": gate_base.get("proofFound"),
        "opcode20GateBaseExternalProofFound": gate_base.get("gateBaseProofFound"),
        "failedOpcode20GateBaseExternalGateIds": gate_base.get("failedGateBaseGateIds") or [],
        "missingEvidence": gate_base.get("missingEvidence") or [],
        "currentWriterVaHex": gate_base.get("currentWriterVaHex"),
        "writerStreamStartHex": gate_base.get("writerStreamStartHex"),
        "firstGateVaHex": gate_base.get("firstGateVaHex"),
        "secondGateVaHex": gate_base.get("secondGateVaHex"),
        "opcode20CandidateVaHex": gate_base.get("opcode20CandidateVaHex"),
        "opcode20CurrentMode": gate_base.get("opcode20CurrentMode"),
        "opcode20CurrentModeIsNestedObjectPlus4": gate_base.get("opcode20CurrentModeIsNestedObjectPlus4"),
        "opcode20DirectContextA8SetterCountInNestedTable": gate_base.get(
            "opcode20DirectContextA8SetterCountInNestedTable"
        ),
        "gateWindowBaseSetterCandidateCount": gate_base.get("gateWindowBaseSetterCandidateCount"),
        "gateWindowOnlyOpcode20BaseCandidate": gate_base.get("gateWindowOnlyOpcode20BaseCandidate"),
        "localBaseAffectingRowCount": gate_base.get("localBaseAffectingRowCount"),
        "localDirectBaseSetterCount": gate_base.get("localDirectBaseSetterCount"),
        "localBaseAffectingRowsBeforeGate": gate_base.get("localBaseAffectingRowsBeforeGate") or [],
        "activeOrderProofFound": gate_base.get("activeOrderProofFound"),
        "gateTimeBaseProofFound": gate_base.get("gateTimeBaseProofFound"),
        "activeOrderOnlyProofEliminated": gate_base.get("activeOrderOnlyProofEliminated"),
        "activeOrderAloneSufficientForGateProof": gate_base.get("activeOrderAloneSufficientForGateProof"),
        "predecessorPersistenceProofFound": gate_base.get("predecessorPersistenceProofFound"),
        "strictHotspotProofFound": gate_base.get("strictHotspotProofFound"),
        "descriptorEvidence": {
            "script4SpecificGateBaseProven": gate_base.get("descriptorScript4SpecificGateBaseProven"),
            "script4GateWriterCount": gate_base.get("descriptorScript4GateWriterCount"),
            "script4GateReaderCount": gate_base.get("descriptorScript4GateReaderCount"),
            "script4FieldRecordCount": gate_base.get("descriptorScript4FieldRecordCount"),
            "script4CurrentFrontierDirectRefCount": gate_base.get("descriptorScript4CurrentFrontierDirectRefCount"),
            "script4EncodedTargetClassification": gate_base.get("descriptorScript4EncodedTargetClassification"),
            "script4EncodedTargetRawScalarCandidateCount": gate_base.get(
                "descriptorScript4EncodedTargetRawScalarCandidateCount"
            ),
            "script4EncodedTargetPromotingCandidateCount": gate_base.get(
                "descriptorScript4EncodedTargetPromotingCandidateCount"
            ),
            "script4ContextA8NonPointerSetterRowCount": gate_base.get(
                "descriptorScript4ContextA8NonPointerSetterRowCount"
            ),
            "allScriptSpecificGateBaseProven": gate_base.get("descriptorAllScriptSpecificGateBaseProven"),
            "allScriptGateWriterCount": gate_base.get("descriptorAllScriptGateWriterCount"),
            "allScriptGateReaderCount": gate_base.get("descriptorAllScriptGateReaderCount"),
            "allScriptSelectionOpcodeCount": gate_base.get("descriptorAllScriptSelectionOpcodeCount"),
            "allScriptFieldRecordCount": gate_base.get("descriptorAllScriptFieldRecordCount"),
            "allScriptCurrentFrontierDirectRefCount": gate_base.get("descriptorAllScriptCurrentFrontierDirectRefCount"),
            "allScriptEncodedTargetClassification": gate_base.get("descriptorAllScriptEncodedTargetClassification"),
            "allScriptEncodedTargetRawScalarCandidateCount": gate_base.get(
                "descriptorAllScriptEncodedTargetRawScalarCandidateCount"
            ),
            "allScriptEncodedTargetPromotingCandidateCount": gate_base.get(
                "descriptorAllScriptEncodedTargetPromotingCandidateCount"
            ),
        },
        "contextF2Evidence": {
            "referenceCount": gate_base.get("opcode20ContextF2ReferenceCount"),
            "readReferenceCount": gate_base.get("opcode20ContextF2ReadReferenceCount"),
            "writeReferenceCount": gate_base.get("opcode20ContextF2WriteReferenceCount"),
            "runtimeObjectTableReaderCount": gate_base.get("opcode20ContextF2RuntimeObjectTableReaderCount"),
            "directInitializerCount": gate_base.get("opcode20ContextF2DirectInitializerCount"),
            "copyWriterCount": gate_base.get("opcode20ContextF2CopyWriterCount"),
            "constantWriteCount": gate_base.get("opcode20ContextF2ConstantWriteCount"),
            "objectSelectorCount": gate_base.get("opcode20ContextF2ObjectSelectorCount"),
            "fixedStream2ObjectSelectorCount": gate_base.get("opcode20ContextF2FixedStream2ObjectSelectorCount"),
            "specificRuntimeObjectPointerProven": gate_base.get("opcode20ContextF2SpecificRuntimeObjectPointerProven"),
            "runtimeObjectTableStateRequired": gate_base.get("opcode20ContextF2RuntimeObjectTableStateRequired"),
            "diagnosticRouteSampleCount": gate_base.get("opcode20ContextF2DiagnosticRouteSampleCount"),
            "diagnosticPromotionStatus": gate_base.get("opcode20ContextF2DiagnosticPromotionStatus"),
            "promotionStatus": gate_base.get("opcode20ContextF2PromotionStatus"),
        },
        "runtimeObservationSummary": {
            "diagnosticActiveOrderEvidence": gate_base.get("diagnosticActiveOrderEvidence") or {},
            "diagnosticActiveOrderRecheckEvidence": gate_base.get("diagnosticActiveOrderRecheckEvidence") or {},
            "publicPredecessorActiveOrderEvidence": gate_base.get("publicPredecessorActiveOrderEvidence") or {},
            "publicPredecessorLeftOverrunActiveOrderEvidence": gate_base.get(
                "publicPredecessorLeftOverrunActiveOrderEvidence"
            )
            or {},
        },
        "supportProofRows": source_report_rows(gate_base),
        "proofGateCount": len(gate_rows),
        "proofGatePassCount": sum(1 for row in gate_rows if row.get("pass") is True),
        "proofGateBlockedCount": sum(1 for row in gate_rows if row.get("pass") is not True),
        "proofGatesAllBlocked": all(row.get("pass") is not True for row in gate_rows),
        "proofGateRows": gate_rows,
        "acceptedEvidenceChecklist": [
            {
                "requirement": "opcode 0x20 runtime descriptor/base path before 0x005428c4",
                "currentStatus": "missing",
                "acceptedSignal": "gateTimeBaseProofFound == true",
            },
            {
                "requirement": "normal selector 2:0 active order/count or equivalent runtime trace",
                "currentStatus": "missing",
                "acceptedSignal": "activeOrderProofFound == true on non-diagnostic selector 2:0",
            },
            {
                "requirement": "runtime context+0xf2 object pointer and context+0xa8 base selection",
                "currentStatus": "missing",
                "acceptedSignal": "opcode20ContextF2SpecificRuntimeObjectPointerProven == true",
            },
            {
                "requirement": "predecessor 1:0 state persistence into current 2:0 gate path",
                "currentStatus": "missing",
                "acceptedSignal": "predecessorPersistenceProofFound == true",
            },
            {
                "requirement": "strict map1_01a source hotspot or equivalent non-coordinate trigger",
                "currentStatus": "missing",
                "acceptedSignal": "strictHotspotProofFound == true",
            },
        ],
        "notAcceptedEvidence": [
            "opcode 0x20 is the only local base-affecting candidate, but local presence is not execution proof",
            "patched selector 2:0 active-order diagnostic observations",
            "public predecessor 1:0 active-order observations with no current-root or route-selector hit",
            "descriptor+4 script scans with no field-map/current-frontier/gate rows",
            "descriptor+0/+4/+8 all-script scans with 362 selection-buffer-shaped rows but no specific route base",
        ],
        "evidenceRefs": gate_base.get("evidenceRefs") or [],
        "evidenceRefCount": gate_base.get("evidenceRefCount"),
        "relatedReports": [
            "out/save_selector_gate_base_proof_gap.html",
            "out/save_selector_gate_offset_sources.html",
            "out/save_selector_gate_offset_patterns.html",
            "out/save_selector_gate_base_candidates.html",
            "out/save_selector_gate_sample_values.html",
            "out/save_selector_selection_buffer_bases.html",
            "out/save_selector_gate_pass_matrix.html",
            "out/save_selector_opcode20_nested_base_modes.html",
            "out/save_selector_opcode20_descriptor_scripts.html",
            "out/save_selector_opcode20_object_base_candidates.html",
            "out/save_selector_opcode20_order_space.html",
            "out/save_selector_opcode20_slot_sources.html",
            "out/save_selector_opcode20_slot_descriptor_writers.html",
            "out/save_selector_opcode20_sample_order_effects.html",
            "out/save_selector_opcode20_runtime_materializers.html",
            "out/save_selector_opcode20_context_f2_sources.html",
        ],
        "regenerateAndVerifyCommands": [
            "python3 tools/summarize_opcode20_gate_base_external_proof_packet.py",
            "python3 tools/verify_web_assets.py",
        ],
        "remainingProofs": gate_base.get("remainingProofs") or [],
        "conclusion": (
            "Opcode 0x20 gate-base proof remains blocked: active order and descriptor shape are "
            "diagnostic/static only until a route-path runtime base or equivalent proof is captured."
        ),
    }


def markdown(summary: dict) -> str:
    lines = [
        "# Opcode 0x20 Gate Base External Proof Packet",
        "",
        f"- route: `{summary['source']} -> {summary['target']}`",
        f"- promotion status: `{summary['promotionStatus']}`",
        f"- proof found: {summary.get('proofFound')}",
        f"- opcode20GateBaseExternalProofFound: {summary.get('opcode20GateBaseExternalProofFound')}",
        f"- current writer/gates: `{summary.get('currentWriterVaHex')}` -> `{summary.get('firstGateVaHex')}`, `{summary.get('secondGateVaHex')}`",
        f"- opcode20 candidate: `{summary.get('opcode20CandidateVaHex')}` / `{summary.get('opcode20CurrentMode')}`",
        f"- failed opcode20 gate-base external gates: `{', '.join(summary.get('failedOpcode20GateBaseExternalGateIds') or [])}`",
        f"- missing evidence count: {len(summary.get('missingEvidence') or [])}",
        f"- evidence refs: {summary.get('evidenceRefCount')}",
        "",
        "## Local Opcode 0x20 Gate Window",
        "",
        f"- only opcode20 base candidate: {summary.get('gateWindowOnlyOpcode20BaseCandidate')}",
        f"- base setter candidates: {summary.get('gateWindowBaseSetterCandidateCount')}",
        f"- local base-affecting rows: {summary.get('localBaseAffectingRowCount')}",
        f"- direct context+0xa8 setters: {summary.get('localDirectBaseSetterCount')}",
        f"- nested object +4 mode: {summary.get('opcode20CurrentModeIsNestedObjectPlus4')}",
        f"- nested-table context+0xa8 setter count: {summary.get('opcode20DirectContextA8SetterCountInNestedTable')}",
        "",
        "## Descriptor And Context Evidence",
        "",
    ]
    descriptor = summary.get("descriptorEvidence") or {}
    context = summary.get("contextF2Evidence") or {}
    lines.extend([
        f"- descriptor+4 field/frontier refs: {descriptor.get('script4FieldRecordCount')}/{descriptor.get('script4CurrentFrontierDirectRefCount')}",
        f"- descriptor+4 gate writer/reader: {descriptor.get('script4GateWriterCount')}/{descriptor.get('script4GateReaderCount')}",
        f"- descriptor+4 encoded classification: `{descriptor.get('script4EncodedTargetClassification')}`",
        f"- all-script selection rows: {descriptor.get('allScriptSelectionOpcodeCount')}",
        f"- all-script specific gate base proven: {descriptor.get('allScriptSpecificGateBaseProven')}",
        f"- context+0xf2 refs/read/write/object readers: {context.get('referenceCount')}/{context.get('readReferenceCount')}/{context.get('writeReferenceCount')}/{context.get('runtimeObjectTableReaderCount')}",
        f"- context+0xf2 specific runtime object pointer proven: {context.get('specificRuntimeObjectPointerProven')}",
        f"- context+0xf2 runtime object table state required: {context.get('runtimeObjectTableStateRequired')}",
        f"- diagnostic object-table route samples/status: {context.get('diagnosticRouteSampleCount')}/{context.get('diagnosticPromotionStatus')}",
        "",
        "## Runtime Observation Summary",
        "",
    ])
    runtime = summary.get("runtimeObservationSummary") or {}
    diag = runtime.get("diagnosticActiveOrderEvidence") or {}
    recheck = runtime.get("diagnosticActiveOrderRecheckEvidence") or {}
    pred = runtime.get("publicPredecessorActiveOrderEvidence") or {}
    lines.extend([
        f"- diagnostic active order sample/count/order: {diag.get('sampleCount')}/{diag.get('activeOrderCountHex')}/{diag.get('activeOrderHexes')}",
        f"- diagnostic descriptor/gate base proven: `{diag.get('firstDescriptorHex')}` / {diag.get('gateBaseProven')}",
        f"- diagnostic recheck route hits: {recheck.get('routeSelectorHitCount')} -> {recheck.get('recheckRouteSelectorHitCount')} / active-order {recheck.get('activeOrderRecheckRouteSelectorHitCount')}",
        f"- diagnostic active-order recheck values: `{recheck.get('activeOrderRecheckActiveOrderCountValues')}`",
        f"- public predecessor samples/sequences: {pred.get('sampleCount')}/{pred.get('sequenceCount')}",
        f"- public predecessor selectors/current/route: {pred.get('observedSelectors')} / {pred.get('reachedCurrentRoot')} / {pred.get('reachedRouteSelector')}",
        f"- public predecessor first descriptor/gate base proven: `{pred.get('firstDescriptorHex')}` / {pred.get('gateBaseProven')}",
        "",
        "## Proof Gates",
        "",
        f"- gate pass/block: {summary.get('proofGatePassCount')}/{summary.get('proofGateBlockedCount')}",
        f"- all blocked: {summary.get('proofGatesAllBlocked')}",
        "",
        "| gate | pass | status | detail |",
        "| --- | --- | --- | --- |",
    ])
    for row in summary.get("proofGateRows") or []:
        lines.append(
            f"| `{row.get('id')}` | {row.get('pass')} | `{row.get('status')}` | {row.get('detail')} |"
        )
    lines.extend(["", "## Support Proof Rows", "", "| report | proof found | failed gates | evidence refs |", "| --- | --- | --- | --- |"])
    for row in summary.get("supportProofRows") or []:
        lines.append(
            f"| `{row.get('id')}` | {row.get('proofFound')} | `{', '.join(row.get('failedGateIds') or [])}` | {row.get('evidenceRefCount')} |"
        )
    lines.extend(["", "## Accepted Evidence Checklist", "", "| requirement | current status | accepted signal |", "| --- | --- | --- |"])
    for row in summary.get("acceptedEvidenceChecklist") or []:
        lines.append(f"| {row.get('requirement')} | {row.get('currentStatus')} | {row.get('acceptedSignal')} |")
    lines.extend(["", "## Missing Evidence", ""])
    lines.extend(f"- {item}" for item in summary.get("missingEvidence") or [])
    lines.extend(["", "## Not Accepted Evidence", ""])
    lines.extend(f"- {item}" for item in summary.get("notAcceptedEvidence") or [])
    lines.extend(["", "## Evidence Refs", ""])
    lines.extend(
        f"- `{ref.get('path')}`: {', '.join(ref.get('fields') or [])}"
        for ref in summary.get("evidenceRefs") or []
    )
    lines.extend(["", "## Related Reports", ""])
    lines.extend(f"- `{item}`" for item in summary.get("relatedReports") or [])
    lines.extend(["", "## Regenerate And Verify", ""])
    lines.extend(f"- `{item}`" for item in summary.get("regenerateAndVerifyCommands") or [])
    lines.extend(["", summary.get("conclusion") or "", ""])
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    def esc(value: Any) -> str:
        return html.escape(str(value))

    descriptor = summary.get("descriptorEvidence") or {}
    context = summary.get("contextF2Evidence") or {}
    runtime = summary.get("runtimeObservationSummary") or {}
    diag = runtime.get("diagnosticActiveOrderEvidence") or {}
    recheck = runtime.get("diagnosticActiveOrderRecheckEvidence") or {}
    pred = runtime.get("publicPredecessorActiveOrderEvidence") or {}
    gate_rows = "".join(
        "<tr>"
        f"<td><code>{esc(row.get('id'))}</code></td>"
        f"<td>{esc(row.get('pass'))}</td>"
        f"<td><code>{esc(row.get('status'))}</code></td>"
        f"<td>{esc(row.get('detail'))}</td>"
        "</tr>"
        for row in summary.get("proofGateRows") or []
    )
    support_rows = "".join(
        "<tr>"
        f"<td><code>{esc(row.get('id'))}</code></td>"
        f"<td>{esc(row.get('proofFound'))}</td>"
        f"<td><code>{esc(', '.join(row.get('failedGateIds') or []))}</code></td>"
        f"<td>{esc(row.get('evidenceRefCount'))}</td>"
        "</tr>"
        for row in summary.get("supportProofRows") or []
    )
    checklist_rows = "".join(
        "<tr>"
        f"<td>{esc(row.get('requirement'))}</td>"
        f"<td>{esc(row.get('currentStatus'))}</td>"
        f"<td>{esc(row.get('acceptedSignal'))}</td>"
        "</tr>"
        for row in summary.get("acceptedEvidenceChecklist") or []
    )
    missing = "".join(f"<li>{esc(item)}</li>" for item in summary.get("missingEvidence") or [])
    not_accepted = "".join(f"<li>{esc(item)}</li>" for item in summary.get("notAcceptedEvidence") or [])
    evidence_refs = "".join(
        f"<li><code>{esc(ref.get('path'))}</code>: {esc(', '.join(ref.get('fields') or []))}</li>"
        for ref in summary.get("evidenceRefs") or []
    )
    related = "".join(f"<li><code>{esc(item)}</code></li>" for item in summary.get("relatedReports") or [])
    commands = "".join(f"<li><code>{esc(item)}</code></li>" for item in summary.get("regenerateAndVerifyCommands") or [])
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>Opcode 0x20 Gate Base External Proof Packet</title>",
        "  <style>body{margin:24px;background:#101010;color:#eee;font:14px system-ui,sans-serif}table{border-collapse:collapse;width:100%;margin:16px 0 28px}th,td{border:1px solid #333;padding:6px 8px;vertical-align:top}code{color:#f5d76e}</style>",
        "</head>",
        "<body>",
        "  <h1>Opcode 0x20 Gate Base External Proof Packet</h1>",
        f"  <p>route <code>{esc(summary['source'])}</code> -&gt; <code>{esc(summary['target'])}</code>; "
        f"writer <code>{esc(summary.get('currentWriterVaHex'))}</code>; gates "
        f"<code>{esc(summary.get('firstGateVaHex'))}</code>, <code>{esc(summary.get('secondGateVaHex'))}</code>; "
        f"candidate <code>{esc(summary.get('opcode20CandidateVaHex'))}</code>.</p>",
        f"  <p>proof found {esc(summary.get('proofFound'))}; opcode20GateBaseExternalProofFound "
        f"{esc(summary.get('opcode20GateBaseExternalProofFound'))}; failed opcode20 gate-base external gates "
        f"<code>{esc(', '.join(summary.get('failedOpcode20GateBaseExternalGateIds') or []))}</code>; "
        f"missing evidence count {esc(len(summary.get('missingEvidence') or []))}; "
        f"evidence refs {esc(summary.get('evidenceRefCount'))}.</p>",
        "  <h2>Local Opcode 0x20 Gate Window</h2>",
        f"  <p>only opcode20 base candidate {esc(summary.get('gateWindowOnlyOpcode20BaseCandidate'))}; "
        f"base setter candidates {esc(summary.get('gateWindowBaseSetterCandidateCount'))}; "
        f"local base-affecting rows {esc(summary.get('localBaseAffectingRowCount'))}; "
        f"direct context+0xa8 setters {esc(summary.get('localDirectBaseSetterCount'))}; "
        f"nested object +4 mode {esc(summary.get('opcode20CurrentModeIsNestedObjectPlus4'))}; "
        f"nested-table context+0xa8 setter count {esc(summary.get('opcode20DirectContextA8SetterCountInNestedTable'))}.</p>",
        "  <h2>Descriptor And Context Evidence</h2>",
        f"  <p>descriptor+4 field/frontier refs {esc(descriptor.get('script4FieldRecordCount'))}/{esc(descriptor.get('script4CurrentFrontierDirectRefCount'))}; "
        f"descriptor+4 gate writer/reader {esc(descriptor.get('script4GateWriterCount'))}/{esc(descriptor.get('script4GateReaderCount'))}; "
        f"encoded classification <code>{esc(descriptor.get('script4EncodedTargetClassification'))}</code>; "
        f"all-script selection rows {esc(descriptor.get('allScriptSelectionOpcodeCount'))}; "
        f"all-script specific gate base proven {esc(descriptor.get('allScriptSpecificGateBaseProven'))}.</p>",
        f"  <p>context+0xf2 refs/read/write/object readers {esc(context.get('referenceCount'))}/{esc(context.get('readReferenceCount'))}/{esc(context.get('writeReferenceCount'))}/{esc(context.get('runtimeObjectTableReaderCount'))}; "
        f"specific runtime object pointer proven {esc(context.get('specificRuntimeObjectPointerProven'))}; "
        f"runtime object table state required {esc(context.get('runtimeObjectTableStateRequired'))}; "
        f"diagnostic object-table route samples/status {esc(context.get('diagnosticRouteSampleCount'))}/{esc(context.get('diagnosticPromotionStatus'))}.</p>",
        "  <h2>Runtime Observation Summary</h2>",
        f"  <p>diagnostic active order sample/count/order {esc(diag.get('sampleCount'))}/{esc(diag.get('activeOrderCountHex'))}/{esc(diag.get('activeOrderHexes'))}; "
        f"diagnostic descriptor/gate base proven <code>{esc(diag.get('firstDescriptorHex'))}</code>/{esc(diag.get('gateBaseProven'))}; "
        f"diagnostic recheck route hits {esc(recheck.get('routeSelectorHitCount'))} -&gt; {esc(recheck.get('recheckRouteSelectorHitCount'))}; "
        f"active-order recheck route hits {esc(recheck.get('activeOrderRecheckRouteSelectorHitCount'))}; "
        f"active-order recheck values <code>{esc(recheck.get('activeOrderRecheckActiveOrderCountValues'))}</code>.</p>",
        f"  <p>public predecessor samples/sequences {esc(pred.get('sampleCount'))}/{esc(pred.get('sequenceCount'))}; "
        f"public predecessor selectors/current/route <code>{esc(pred.get('observedSelectors'))}</code> / "
        f"{esc(pred.get('reachedCurrentRoot'))}/{esc(pred.get('reachedRouteSelector'))}; "
        f"first descriptor <code>{esc(pred.get('firstDescriptorHex'))}</code>; gate base proven {esc(pred.get('gateBaseProven'))}.</p>",
        "  <h2>Proof Gates</h2>",
        f"  <p>gate pass/block {esc(summary.get('proofGatePassCount'))}/{esc(summary.get('proofGateBlockedCount'))}; "
        f"all blocked {esc(summary.get('proofGatesAllBlocked'))}.</p>",
        "  <table><thead><tr><th>gate</th><th>pass</th><th>status</th><th>detail</th></tr></thead>",
        f"  <tbody>{gate_rows}</tbody></table>",
        "  <h2>Support Proof Rows</h2>",
        f"  <table><thead><tr><th>report</th><th>proof found</th><th>failed gates</th><th>evidence refs</th></tr></thead><tbody>{support_rows}</tbody></table>",
        "  <h2>Accepted Evidence Checklist</h2>",
        f"  <table><thead><tr><th>requirement</th><th>status</th><th>accepted signal</th></tr></thead><tbody>{checklist_rows}</tbody></table>",
        "  <h2>Missing Evidence</h2>",
        f"  <ul>{missing}</ul>",
        "  <h2>Not Accepted Evidence</h2>",
        f"  <ul>{not_accepted}</ul>",
        "  <h2>Evidence Refs</h2>",
        f"  <ul>{evidence_refs}</ul>",
        "  <h2>Related Reports</h2>",
        f"  <ul>{related}</ul>",
        "  <h2>Regenerate And Verify</h2>",
        f"  <ul>{commands}</ul>",
        f"  <p>{esc(summary.get('conclusion'))}</p>",
        "</body>",
        "</html>",
        "",
    ])


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


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary(args.out_dir)
    write_outputs(summary, args.out_dir)
    print(f"wrote opcode20 gate-base external proof packet -> {args.out_dir / 'opcode20_gate_base_external_proof_packet.html'}")


if __name__ == "__main__":
    main()
