cocos-farm/tools/extract_source_evidence.py

200 lines
7.8 KiB
Python

#!/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()