#!/usr/bin/env python3 """Export first-milestone source evidence for the Cocos 4 rebuild. The extractor consumes the already decoded source-first inventory from the original Cocos package. It does not read Godot output and does not infer layout from screenshots. """ from __future__ import annotations import json from datetime import datetime, timezone from pathlib import Path from typing import Any PROJECT = Path(__file__).resolve().parents[1] SOURCE_ROOT = Path("/Users/hy/Documents/dev/qq_farm_godot_rebuild_20260616") SOURCE_INVENTORY = SOURCE_ROOT / "source_first/out/main_scene_elements.json" OUT = PROJECT / "source_export" def read_json(path: Path) -> Any: with path.open("r", encoding="utf-8") as fh: return json.load(fh) def write_json(path: Path, value: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def write_text(path: Path, value: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(value, encoding="utf-8") def find_default_records(config_assets: list[dict[str, Any]]) -> list[dict[str, Any]]: for item in config_assets: if item.get("id") == "skin_cfg": return item.get("default_records", []) return [] def only_paths(elements: list[dict[str, Any]], *needles: str) -> list[dict[str, Any]]: return [item for item in elements if any(needle in str(item.get("full_path")) for needle in needles)] def build_stage1(data: dict[str, Any]) -> dict[str, Any]: sprite_prefab = data["prefabs"]["farm_scene_v3_sprite"] mount_prefab = data["prefabs"]["farm_scene_v3"] main_ui = data["prefabs"]["main_ui_v2"] land_candidates = [ item for item in sprite_prefab["elements"] if item.get("category") == "visual_land_cell" ] background_nodes = [ item for item in sprite_prefab["elements"] if item.get("category") == "world_background" or str(item.get("full_path")).endswith("/Scene/bg") or "BgLayer" in str(item.get("full_path")) ] bottom_menu_nodes = only_paths( main_ui["elements"], "main_ui_v2/Menu", "main_ui_v2/Menu/btn_layout", "main_ui_v2/Menu/Node_task", "main_ui_v2/Menu/Node_Friend", ) default_skin_records = find_default_records(data.get("config_assets", [])) house_records = [ item for item in default_skin_records if item.get("skin_type") in {1, 5} ] doghouse_nodes = only_paths(sprite_prefab["elements"], "dogHouse") + only_paths(mount_prefab["elements"], "dogHouse") return { "module": "main_scene_first_milestone", "generated_at": datetime.now(timezone.utc).isoformat(), "source_inventory": str(SOURCE_INVENTORY), "design_resolution": data["summary"]["design_resolution"], "scope": [ "Canvas", "main Camera", "root scene", "background layers", "visible land plots", "house", "doghouse", "bottom menu buttons", ], "startup_scene": data["startup_scene"], "prefab_sources": { "farm_scene_v3_sprite": sprite_prefab["source"], "farm_scene_v3": mount_prefab["source"], "main_ui_v2": main_ui["source"], }, "canvas_and_cameras": data["startup_scene"]["elements"], "background_nodes": background_nodes, "land_candidates": { "source_count": len(land_candidates), "visible_count_required_by_spec": 24, "selection_status": "blocked_missing_runtime_visible_land_rule", "nodes": land_candidates, }, "house_and_doghouse": { "skin_cfg_default_records": house_records, "doghouse_nodes": doghouse_nodes, }, "bottom_menu_nodes": bottom_menu_nodes, "asset_inputs": { "backgrounds": data.get("source_assets", {}).get("backgrounds", []), "config_assets": data.get("config_assets", []), }, "blockers": [ { "id": "visible_land_count", "status": "blocked", "detail": "Static source prefab has 36 visual grid cells. The first-milestone spec requires 24 visible land plots, but the runtime rule selecting those 24 is not yet decoded.", }, { "id": "background_texture_binding", "status": "blocked", "detail": "Background ImageAsset entries are located, but final texture/SpriteFrame binding for Cocos 4 import is not yet resolved from metadata.", }, { "id": "cocos4_cli_build", "status": "blocked", "detail": "Cocos 4 alpha CLI reaches engine init, then fails because node_modules/gl/build/Release/webgl.node requires NODE_MODULE_VERSION 140 while local Node 24 provides 137.", }, ], } def main() -> None: data = read_json(SOURCE_INVENTORY) stage1 = build_stage1(data) sprite_prefab = data["prefabs"]["farm_scene_v3_sprite"] mount_prefab = data["prefabs"]["farm_scene_v3"] main_ui = data["prefabs"]["main_ui_v2"] write_json(OUT / "source_nodes.json", stage1) write_json(OUT / "source_prefabs.json", { "farm_scene_v3_sprite": { "source": sprite_prefab["source"], "node_count": len(sprite_prefab["elements"]), "category_counts": sprite_prefab.get("category_counts", {}), }, "farm_scene_v3": { "source": mount_prefab["source"], "node_count": len(mount_prefab["elements"]), "category_counts": mount_prefab.get("category_counts", {}), }, "main_ui_v2": { "source": main_ui["source"], "node_count": len(main_ui["elements"]), "category_counts": main_ui.get("category_counts", {}), }, }) write_json(OUT / "source_assets.json", stage1["asset_inputs"]) write_json(OUT / "source_spriteframes.json", { "status": "metadata_manifest_available_but_stage1_bindings_unresolved", "manifest": str(SOURCE_ROOT / "downloads/spriteframes/sprite_manifest.json"), }) write_json(OUT / "source_spine.json", { "status": "default house and doghouse reference Spine assets through SkinCfg; Cocos 4 import binding not generated yet", "manifest": str(SOURCE_ROOT / "downloads/spine_regions/spine_region_manifest.json"), "records": stage1["house_and_doghouse"]["skin_cfg_default_records"], }) write_json(OUT / "source_animations.json", { "status": "not_in_first_stage_scope", "animation_clips": data.get("source_assets", {}).get("animation_clips", []), }) runtime_notes = """# Source Runtime Notes - Startup scene source has Canvas/root/ui/scene and two cameras. - `farm_scene_v3_sprite` contains 36 static visual land cells under `Scaled/Rotate/GridOrigin`. - First-stage spec requires 24 visible land plots, but the runtime rule selecting visible or unlocked plots is not decoded yet. - Default house and doghouse come from `SkinCfg` records `201001` and `205001`, not only from the static visual prefab. - Bottom menu source nodes are under `main_ui_v2/Menu`; feature logic for warehouse/shop/friend/task is outside first-stage scope. """ write_text(OUT / "source_runtime_notes.md", runtime_notes) blocker_lines = ["# Source Blockers", ""] for item in stage1["blockers"]: blocker_lines.append(f"- `{item['id']}`: {item['detail']}") blocker_lines.append("") write_text(OUT / "source_blockers.md", "\n".join(blocker_lines)) print(f"wrote {OUT / 'source_nodes.json'}") print(f"land candidates: {stage1['land_candidates']['source_count']}") print("visible land selection: blocked_missing_runtime_visible_land_rule") if __name__ == "__main__": main()