#!/usr/bin/env python3
"""Build a compact field-vs-battle render pipeline model review.

This report does not introduce a new speculative renderer.  It consolidates
already-grounded DirectDraw, field tile redraw, and battle helper/object evidence
into one active artifact so later CNS rect/source-rect work can share the same
mental model.
"""
from __future__ import annotations

import json
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"


def read_json(name: str) -> dict[str, Any]:
    path = OUT / name
    if not path.exists():
        return {"missing": True, "path": f"out/{name}"}
    return json.loads(path.read_text(encoding="utf-8"))


def artifact_status(name: str, payload: dict[str, Any]) -> dict[str, Any]:
    return {
        "path": f"out/{name}",
        "exists": not payload.get("missing"),
        "kind": payload.get("kind"),
        "status": payload.get("status") or payload.get("promotionStatus"),
    }


def build_report() -> dict[str, Any]:
    directdraw = read_json("directdraw_flow.json")
    redraw = read_json("map_animation_redraw_consumer_review.json")
    draw_transform = read_json("map_animation_draw_tile_transform_review.json")
    battle_object = read_json("battle_effect_object_review.json")
    battle_lifecycle = read_json("battle_child_lifecycle_draw_order_review.json")

    directdraw_surfaces = directdraw.get("surfaces") or {}
    directdraw_verification = directdraw.get("verification") or {}
    redraw_summary = redraw.get("summary") or {}
    transform_summary = draw_transform.get("summary") or {}
    battle_object_summary = battle_object.get("summary") or {}
    battle_lifecycle_summary = battle_lifecycle.get("summary") or {}
    directdraw_calls = directdraw.get("vtableCalls") or []
    directdraw_verified_calls = [row for row in directdraw_calls if row.get("verified") is True]

    return {
        "kind": "hwanse-render-pipeline-model-review",
        "status": "field-battle-render-model-grounded",
        "source": [
            "out/directdraw_flow.json",
            "out/map_animation_redraw_consumer_review.json",
            "out/map_animation_draw_tile_transform_review.json",
            "out/battle_effect_object_review.json",
            "out/battle_child_lifecycle_draw_order_review.json",
            "docs/map3_active_object_overlay_notes.md",
            "docs/DIRECTDRAW_NOTES.md",
        ],
        "summary": {
            "displayResolution": "640x480x8",
            "sharedApiSurface": "DirectDraw surface/palette copy path",
            "fieldDrawUnit": "37x21 visible tile grid plus dirty/occlusion buffers",
            "battleDrawUnit": "active object/sprite/effect display objects over battle background",
            "surfaceWrapperTable": directdraw_surfaces.get("surfaceTableBaseHex") or "0x0055abd8",
            "directDrawVerifiedCallCount": len(directdraw_verified_calls),
            "directDrawTotalCallCount": len(directdraw_calls),
            "fieldAnimatedFlag0x40Meaning": "confirmed redraw invalidation consumer; visible frame producer still unbound",
            "fieldDrawTimeTileTransformFound": transform_summary.get("drawTimeVisibleMotionTransformFound"),
            "battleHelperCoverage": {
                "helpers": battle_object_summary.get("helpers"),
                "helpersWithSpawnTree": battle_object_summary.get("helpersWithSpawnTree"),
                "helpersWithMotionStep": battle_object_summary.get("helpersWithMotionStep"),
                "totalDirectSpawnEvents": battle_lifecycle_summary.get("totalDirectSpawnEvents"),
            },
            "colorKeyConclusion": (
                "Transparency must be tracked as draw-call/context state.  A source CNS transparent color alone is not sufficient "
                "unless the Blt/BltFast path requests source color key handling."
            ),
        },
        "artifactStatuses": [
            artifact_status("directdraw_flow.json", directdraw),
            artifact_status("map_animation_redraw_consumer_review.json", redraw),
            artifact_status("map_animation_draw_tile_transform_review.json", draw_transform),
            artifact_status("battle_effect_object_review.json", battle_object),
            artifact_status("battle_child_lifecycle_draw_order_review.json", battle_lifecycle),
        ],
        "sharedDirectDrawModel": {
            "evidenceStatus": "grounded-call-surface",
            "surfaceWrapperTable": directdraw_surfaces.get("surfaceTableBaseHex") or "0x0055abd8",
            "primarySlots": directdraw_surfaces.get("primarySurfaceSlots") or [],
            "confirmedMethods": {
                "SetDisplayMode": "640, 480, 8",
                "CreateSurface": "primary/offscreen/backbuffer wrappers",
                "CreatePalette/SetPalette/SetEntries": "256-entry palette path",
                "Flip/Blt": "fullscreen and copy/windowed presentation paths",
            },
            "boundary": (
                "This proves the Win32/DirectDraw presentation surface, not the full game-engine draw ordering. "
                "Field and battle diverge before these DirectDraw calls."
            ),
        },
        "fieldPipeline": {
            "evidenceStatus": "tilemap-dirty-render-consumer-grounded",
            "drawUnit": "tile row renderer over 37x21 viewport",
            "globals": {
                "dirtyVisibleGrid": "0x005957d0 + y * 37 + x",
                "liveLayer1Flags": "0x0058d7d0 + index * 2",
                "liveLayer0TileIds": "0x00595af0 + index * 2",
                "viewX": "0x004576dc",
                "viewY": "0x004576de",
                "mapWidth": "0x00595ada",
            },
            "confirmedSteps": [
                {
                    "vaHex": "0x00425403",
                    "meaning": "37x21 viewport dirty scan wrapper.",
                },
                {
                    "vaHex": "0x00425494",
                    "meaning": "Reads layer1 flags and tests low byte bit 0x40.",
                },
                {
                    "vaHex": "0x004254a5",
                    "meaning": "Marks matching visible dirty cell with bit 0x01.",
                },
                {
                    "vaHex": "0x00425163",
                    "meaning": "Dirty row renderer reads live layer0 tile ids.",
                },
                {
                    "vaHex": "0x004175d3",
                    "meaning": "Packs draw descriptor into temporary object +0x28 and x/y into +0x1c/+0x20.",
                },
                {
                    "vaHex": "0x004199a0",
                    "meaning": "Converts low-word tile/frame index to source rect using wrapper width/height/columns.",
                },
            ],
            "wrapperFields": {
                "+0x2e": "sprite/tile cell width",
                "+0x30": "sprite/tile cell height",
                "+0x32": "columns",
                "+0x34": "group table candidate",
            },
            "negativeEvidence": {
                "drawTimeUsesLayer1Flag0x40": transform_summary.get("usesLayer1AnimatedFlag0x40"),
                "drawTimeUsesFrameCounter": transform_summary.get("usesFrameCounter"),
                "drawTimeUsesPerMapAnimationSequence": transform_summary.get("usesPerMapAnimationSequence"),
                "alternateTileSelectionFoundInRedrawConsumer": redraw_summary.get("alternateTileSelectionFound"),
                "visibleMotionProducerFoundInRedrawConsumer": redraw_summary.get("visibleMotionProducerFound"),
            },
            "currentConclusion": (
                "Field animation flag 0x40 is a redraw invalidation/input to dirty rendering.  The visible motion source is still not "
                "proven in the generic tile converter; if motion is tile-id based, a producer must mutate 0x00595af0 before redraw."
            ),
        },
        "battlePipeline": {
            "evidenceStatus": "object-sprite-helper-model-grounded",
            "drawUnit": "active object / battle actor / helper child display object",
            "globalsAndFields": {
                "currentObject": "0x0055a800",
                "surfaceWrapperTable": "0x0055abd8 + slot * 4",
                "object+0x28": "packed draw-source selector; low word frame/source index, high word surface slot",
                "object+0x1c": "projected x, fixed 16.16",
                "object+0x20": "projected y, fixed 16.16",
                "object+0x2c": "draw/object flags candidate; exact bit roles still need focused proof",
            },
            "confirmedSteps": [
                {
                    "vaHex": "0x00416ce2",
                    "meaning": "Common active-object rect/draw-source path.",
                },
                {
                    "vaHex": "0x00416ceb..0x00416cf6",
                    "meaning": "Uses object+0x28 low word as source rect/frame selector.",
                },
                {
                    "vaHex": "0x00416cf9..0x00416d07",
                    "meaning": "Uses object+0x28 high word as surface slot and indexes 0x0055abd8.",
                },
                {
                    "vaHex": "0x00416d11..0x00416d3d",
                    "meaning": "Uses object+0x1c/+0x20 as projected draw coordinates.",
                },
            ],
            "helperEvidence": {
                "helpers": battle_object_summary.get("helpers"),
                "helpersWithDirectFrameScript": battle_object_summary.get("helpersWithDirectFrameScript"),
                "helpersWithSpawnTree": battle_object_summary.get("helpersWithSpawnTree"),
                "helpersWithRandomRange": battle_object_summary.get("helpersWithRandomRange"),
                "helpersWithMotionStep": battle_object_summary.get("helpersWithMotionStep"),
                "frameScriptSpriteCounts": battle_object_summary.get("frameScriptSpriteCounts"),
                "rendererReadinessCounts": battle_lifecycle_summary.get("rendererReadinessCounts"),
                "drawOrderStatusCounts": battle_lifecycle_summary.get("drawOrderStatusCounts"),
            },
            "currentConclusion": (
                "Battle rendering is not a visible tile grid pass.  It composes a battle background and object/helper frame streams, "
                "with source selection routed through object+0x28 and surface wrapper metadata."
            ),
        },
        "nextFrontier": [
            {
                "area": "field animation producer",
                "work": "Search for live layer0 0x00595af0 mutation tied to map tick/frame counter, or prove a specialized draw path outside 0x004199a0.",
            },
            {
                "area": "wrapper validation",
                "work": "Dump wrapper +0x2e/+0x30/+0x32/+0x34 for loaded CNS slots and compare against the CNS rect/source-rect catalog.",
            },
            {
                "area": "draw flags / color key",
                "work": "Decode object+0x2c and Blt/BltFast flag consumers to separate opaque copy, source-color-key copy, and palette/context effects.",
            },
            {
                "area": "battle object placement",
                "work": "Keep battle actor/helper position opcodes separate from field tilemap placement; do not reuse map_*3 assumptions for battle effects.",
            },
        ],
    }


def main() -> int:
    OUT.mkdir(exist_ok=True)
    report = build_report()
    (OUT / "render_pipeline_model_review.json").write_text(
        json.dumps(report, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    print("render_pipeline_model_review ok")
    print(json.dumps(report["summary"], ensure_ascii=False, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
