more docs

This commit is contained in:
Maddo 2026-04-05 09:55:21 +02:00
commit a70ec15899
21 changed files with 1357 additions and 25 deletions

227
_tmp_valuebox_cache_scan.py Normal file
View file

@ -0,0 +1,227 @@
import json
from collections import Counter, defaultdict
from pathlib import Path
ROOT = Path(r"e:\disasm\Crusader-Map-Viewer\map_renderer\.cache")
SCENE_ROOT = ROOT / "scene-cache"
REF_ROOT = ROOT / "reference-data"
TARGET_SHAPE = "shape:251"
MAX_DISTANCE = 1600
MAX_NEIGHBORS_PER_ITEM = 8
INTERESTING_SHAPES = {
"shape:251": "VALUEBOX",
"shape:258": "MONITNS",
"shape:357": "MONITEW",
"shape:871": "WALLMNS",
"shape:1086": "WALLMEW",
"shape:1019": "SECURNS",
"shape:1085": "SECUREW",
"shape:1214": "WATCHNS",
"shape:1246": "WATCHEW",
"shape:2573": "KEYPAD",
"shape:2574": "KEYPAD?",
}
def load_shape_names(game: str) -> dict[str, str]:
ref_path = REF_ROOT / game / "reference-data.json"
with ref_path.open("r", encoding="utf-8") as handle:
data = json.load(handle)
shape_names = {}
for entry in data.get("shapeDefinitions", []):
shape_id = entry.get("id")
if not shape_id:
continue
name = (
entry.get("catalog", {}).get("label")
or entry.get("displayName")
or shape_id
)
shape_names[shape_id] = name
return shape_names
def squared_distance(a: dict, b: dict) -> int:
a_world = a.get("world") or {}
b_world = b.get("world") or {}
dx = int(a_world.get("x", 0)) - int(b_world.get("x", 0))
dy = int(a_world.get("y", 0)) - int(b_world.get("y", 0))
dz = int(a_world.get("z", 0)) - int(b_world.get("z", 0))
return dx * dx + dy * dy + dz * dz
def main() -> None:
shape_names_by_game = {
"remorse": load_shape_names("remorse"),
"regret": load_shape_names("regret"),
}
summary = []
per_game_shape_counts = defaultdict(Counter)
per_game_qlo = defaultdict(Counter)
per_game_qhi = defaultdict(Counter)
interesting_links = defaultdict(Counter)
examples = defaultdict(list)
nonzero_examples = defaultdict(list)
for game_dir in sorted(SCENE_ROOT.iterdir()):
if not game_dir.is_dir():
continue
game_name = game_dir.name
base_game = "regret" if game_name.startswith("regret") else "remorse"
shape_names = shape_names_by_game[base_game]
for map_dir in sorted(game_dir.iterdir()):
if not map_dir.is_dir() or not map_dir.name.startswith("map-"):
continue
map_name = map_dir.name
for hash_dir in sorted(map_dir.iterdir()):
scene_path = hash_dir / "scene.json"
if not scene_path.exists():
continue
with scene_path.open("r", encoding="utf-8") as handle:
scene = json.load(handle)
items = scene.get("items", [])
valueboxes = [item for item in items if item.get("shapeDefId") == TARGET_SHAPE and item.get("frame") == 0]
if not valueboxes:
continue
for item in valueboxes:
quality = int(item.get("quality", 0))
qlo = quality & 0xFF
qhi = (quality >> 8) & 0xFF
per_game_qlo[game_name][qlo] += 1
per_game_qhi[game_name][qhi] += 1
nearby = []
for other in items:
if other is item:
continue
dist2 = squared_distance(item, other)
if dist2 <= MAX_DISTANCE * MAX_DISTANCE:
nearby.append((dist2, other))
nearby.sort(key=lambda pair: pair[0])
for _, other in nearby[:MAX_NEIGHBORS_PER_ITEM]:
per_game_shape_counts[game_name][other.get("shapeDefId", "<none>")] += 1
for dist2, other in nearby:
shape_id = other.get("shapeDefId", "<none>")
if shape_id in INTERESTING_SHAPES:
interesting_links[game_name][shape_id] += 1
if len(examples[game_name]) < 12:
world = item.get("world") or {}
example_row = {
"map": map_name,
"id": item.get("id"),
"coords": [world.get("x"), world.get("y"), world.get("z")],
"quality": quality,
"qlo": qlo,
"qhi": qhi,
"mapNum": item.get("mapNum"),
"npcNum": item.get("npcNum"),
"nextItem": item.get("nextItem"),
"nearby": [
{
"shape": other.get("shapeDefId"),
"name": shape_names.get(other.get("shapeDefId", ""), other.get("shapeDefId", "")),
"frame": other.get("frame"),
"quality": other.get("quality"),
"mapNum": other.get("mapNum"),
"npcNum": other.get("npcNum"),
"nextItem": other.get("nextItem"),
"coords": [
(other.get("world") or {}).get("x"),
(other.get("world") or {}).get("y"),
(other.get("world") or {}).get("z"),
],
"dist2": dist2,
}
for dist2, other in nearby
if other.get("shapeDefId") in INTERESTING_SHAPES
][:MAX_NEIGHBORS_PER_ITEM]
or [
{
"shape": other.get("shapeDefId"),
"name": shape_names.get(other.get("shapeDefId", ""), other.get("shapeDefId", "")),
"frame": other.get("frame"),
"quality": other.get("quality"),
"mapNum": other.get("mapNum"),
"npcNum": other.get("npcNum"),
"nextItem": other.get("nextItem"),
"coords": [
(other.get("world") or {}).get("x"),
(other.get("world") or {}).get("y"),
(other.get("world") or {}).get("z"),
],
"dist2": dist2,
}
for dist2, other in nearby[:MAX_NEIGHBORS_PER_ITEM]
],
}
examples[game_name].append(example_row)
if (qlo or qhi or item.get("mapNum") or item.get("npcNum")) and len(nonzero_examples[game_name]) < 12:
nonzero_examples[game_name].append(example_row)
elif (qlo or qhi or item.get("mapNum") or item.get("npcNum")) and len(nonzero_examples[game_name]) < 12:
world = item.get("world") or {}
nonzero_examples[game_name].append(
{
"map": map_name,
"id": item.get("id"),
"coords": [world.get("x"), world.get("y"), world.get("z")],
"quality": quality,
"qlo": qlo,
"qhi": qhi,
"mapNum": item.get("mapNum"),
"npcNum": item.get("npcNum"),
"nextItem": item.get("nextItem"),
}
)
summary.append({"game": game_name, "map": map_name, "count": len(valueboxes)})
print("VALUEBOX frame-0 counts by map")
for row in sorted(summary, key=lambda entry: (-entry["count"], entry["game"], entry["map"]))[:40]:
print(f"{row['game']:12} {row['map']:8} count={row['count']}")
print("\nTop nearby shapes per game")
for game_name, counter in sorted(per_game_shape_counts.items()):
print(f"\n[{game_name}]")
for shape_id, count in counter.most_common(20):
base_game = "regret" if game_name.startswith("regret") else "remorse"
shape_name = shape_names_by_game[base_game].get(shape_id, shape_id)
print(f"{shape_id:10} {count:5} {shape_name}")
print("\nInteresting nearby controller families")
for game_name, counter in sorted(interesting_links.items()):
print(f"\n[{game_name}]")
for shape_id, count in counter.most_common():
print(f"{shape_id:10} {count:5} {INTERESTING_SHAPES[shape_id]}")
print("\nTop QLo values per game")
for game_name, counter in sorted(per_game_qlo.items()):
print(f"\n[{game_name}]")
for value, count in counter.most_common(20):
print(f"QLo {value:3} -> {count}")
print("\nTop QHi values per game")
for game_name, counter in sorted(per_game_qhi.items()):
print(f"\n[{game_name}]")
for value, count in counter.most_common(20):
print(f"QHi {value:3} -> {count}")
print("\nRepresentative examples")
for game_name, rows in sorted(examples.items()):
print(f"\n[{game_name}]")
for row in rows:
print(json.dumps(row, sort_keys=True))
print("\nNonzero payload examples")
for game_name, rows in sorted(nonzero_examples.items()):
print(f"\n[{game_name}]")
for row in rows:
print(json.dumps(row, sort_keys=True))
if __name__ == "__main__":
main()