diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index aca9929..7443b44 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -16,4 +16,5 @@ applyTo: '**' - Prefer small, focused changes. - Validate reverse-engineering and tooling changes with the narrowest relevant check. - Keep read-only analysis separate from any explicit writable workflow. +- For authored map-placement or link investigation in Crusader-Map-Viewer, prefer decompressed `.cache/scene-cache//map-*//scene.json` over `site/data`; the cache scenes preserve direct item objects and world coordinates. - If the shell becomes stuck on multiline input or otherwise unhealthy, you could try to press esc in the shell to see if it gets unstuck, otherwise immediately use `vscode_askQuestions` to ask the user to fix the terminal state, then continue the task once the user confirms it is fixed. \ No newline at end of file diff --git a/Crusader.rep/idata/01/~00000015.db/change.data.gbf b/Crusader.rep/idata/01/~00000015.db/change.data.gbf index 26fb920..e257852 100644 Binary files a/Crusader.rep/idata/01/~00000015.db/change.data.gbf and b/Crusader.rep/idata/01/~00000015.db/change.data.gbf differ diff --git a/Crusader.rep/idata/01/~00000015.db/change.map.gbf b/Crusader.rep/idata/01/~00000015.db/change.map.gbf index 3bc6051..50162f3 100644 Binary files a/Crusader.rep/idata/01/~00000015.db/change.map.gbf and b/Crusader.rep/idata/01/~00000015.db/change.map.gbf differ diff --git a/Crusader.rep/idata/01/~00000015.db/db.39.gbf b/Crusader.rep/idata/01/~00000015.db/db.40.gbf similarity index 99% rename from Crusader.rep/idata/01/~00000015.db/db.39.gbf rename to Crusader.rep/idata/01/~00000015.db/db.40.gbf index d2e7c02..84de55a 100644 Binary files a/Crusader.rep/idata/01/~00000015.db/db.39.gbf and b/Crusader.rep/idata/01/~00000015.db/db.40.gbf differ diff --git a/Crusader.rep/idata/01/~00000015.db/db.38.gbf b/Crusader.rep/idata/01/~00000015.db/db.41.gbf similarity index 99% rename from Crusader.rep/idata/01/~00000015.db/db.38.gbf rename to Crusader.rep/idata/01/~00000015.db/db.41.gbf index 9f0d50e..02cb60a 100644 Binary files a/Crusader.rep/idata/01/~00000015.db/db.38.gbf and b/Crusader.rep/idata/01/~00000015.db/db.41.gbf differ diff --git a/Crusader.rep/user/00/~00000008.db/db.31.gbf b/Crusader.rep/user/00/~00000008.db/db.33.gbf similarity index 99% rename from Crusader.rep/user/00/~00000008.db/db.31.gbf rename to Crusader.rep/user/00/~00000008.db/db.33.gbf index 99a64f8..d6661a5 100644 Binary files a/Crusader.rep/user/00/~00000008.db/db.31.gbf and b/Crusader.rep/user/00/~00000008.db/db.33.gbf differ diff --git a/Crusader.rep/user/00/~00000008.db/db.32.gbf b/Crusader.rep/user/00/~00000008.db/db.34.gbf similarity index 98% rename from Crusader.rep/user/00/~00000008.db/db.32.gbf rename to Crusader.rep/user/00/~00000008.db/db.34.gbf index 57a795c..b6efd2b 100644 Binary files a/Crusader.rep/user/00/~00000008.db/db.32.gbf and b/Crusader.rep/user/00/~00000008.db/db.34.gbf differ diff --git a/_tmp_find_text_section.py b/_tmp_find_text_section.py new file mode 100644 index 0000000..7159f0f --- /dev/null +++ b/_tmp_find_text_section.py @@ -0,0 +1,5 @@ +import re +from pathlib import Path +xml = Path('exports/CRUSADER.EXE.xml').read_text(encoding='utf-8', errors='ignore') +for m in re.finditer(r' 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", "")] += 1 + + for dist2, other in nearby: + shape_id = other.get("shapeDefId", "") + 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() \ No newline at end of file diff --git a/_tmp_valuebox_cache_scan_output.txt b/_tmp_valuebox_cache_scan_output.txt new file mode 100644 index 0000000..1634ad9 --- /dev/null +++ b/_tmp_valuebox_cache_scan_output.txt @@ -0,0 +1,329 @@ +VALUEBOX frame-0 counts by map +remorse map-19 count=57 +remorse-101 map-19 count=57 +remorse-jp map-19 count=57 +remorse map-29 count=32 +remorse-101 map-29 count=32 +remorse-jp map-29 count=32 +remorse map-62 count=28 +remorse-101 map-62 count=28 +remorse-jp map-62 count=28 +regret map-15 count=27 +remorse map-47 count=27 +remorse-101 map-47 count=27 +remorse-jp map-47 count=27 +remorse map-25 count=25 +remorse map-69 count=25 +remorse-101 map-25 count=25 +remorse-101 map-69 count=25 +remorse-jp map-25 count=25 +remorse-jp map-69 count=25 +remorse map-141 count=24 +remorse-101 map-141 count=24 +remorse-jp map-141 count=24 +remorse map-10 count=22 +remorse map-26 count=22 +remorse-101 map-10 count=22 +remorse-101 map-26 count=22 +remorse-jp map-10 count=22 +remorse-jp map-26 count=22 +regret map-16 count=16 +regret map-13 count=14 +regret map-14 count=14 +regret map-8 count=11 +regret map-28 count=10 +regret map-5 count=10 +regret map-7 count=10 +regret map-200 count=9 +regret map-201 count=9 +remorse map-15 count=9 +remorse-101 map-15 count=9 +remorse-jp map-15 count=9 + +Top nearby shapes per game + +[regret] +shape:248 226 shape_00f8 +shape:249 76 shape_00f9 +shape:1232 71 shape_04d0 +shape:253 49 shape_00fd +shape:573 47 shape_023d +shape:16 42 shape_0010 +shape:1061 42 shape_0425 +shape:534 38 shape_0216 +shape:1365 28 shape_0555 +shape:246 22 shape_00f6 +shape:1278 21 shape_04fe +shape:567 20 shape_0237 +shape:1201 19 CMD_LINK +shape:1142 19 shape_0476 +shape:1364 19 shape_0554 +shape:247 18 shape_00f7 +shape:1363 16 shape_0553 +shape:1369 15 shape_0559 +shape:1347 15 shape_0543 +shape:1366 14 shape_0556 + +[regret-demo] +shape:248 8 shape_00f8 +shape:99 6 shape_0063 +shape:249 6 shape_00f9 +shape:574 5 shape_023e +shape:1232 5 shape_04d0 +shape:431 5 shape_01af +shape:250 4 shape_00fa +shape:1142 3 shape_0476 +shape:16 3 shape_0010 +shape:246 2 shape_00f6 +shape:252 2 shape_00fc +shape:534 1 shape_0216 +shape:949 1 shape_03b5 +shape:593 1 shape_0251 +shape:1377 1 shape_0561 +shape:1593 1 Roof_Regret_Level1 +shape:253 1 shape_00fd +shape:1201 1 CMD_LINK + +[remorse] +shape:248 444 shape_00f8 +shape:249 132 shape_00f9 +shape:569 77 shape_0239 +shape:534 62 shape_0216 +shape:16 60 shape_0010 +shape:253 58 shape_00fd +shape:563 49 shape_0233 +shape:564 49 shape_0234 +shape:1253 49 shape_04e5 +shape:572 46 shape_023c +shape:486 42 shape_01e6 +shape:250 40 shape_00fa +shape:43 40 shape_002b +shape:573 39 shape_023d +shape:716 36 shape_02cc +shape:252 35 shape_00fc +shape:631 35 shape_0277 +shape:246 33 shape_00f6 +shape:15 33 shape_000f +shape:547 30 shape_0223 + +[remorse-101] +shape:248 444 shape_00f8 +shape:249 132 shape_00f9 +shape:569 77 shape_0239 +shape:534 62 shape_0216 +shape:16 60 shape_0010 +shape:253 58 shape_00fd +shape:563 49 shape_0233 +shape:564 49 shape_0234 +shape:1253 49 shape_04e5 +shape:572 46 shape_023c +shape:486 42 shape_01e6 +shape:250 40 shape_00fa +shape:43 40 shape_002b +shape:573 39 shape_023d +shape:716 36 shape_02cc +shape:252 35 shape_00fc +shape:631 35 shape_0277 +shape:246 33 shape_00f6 +shape:15 33 shape_000f +shape:547 30 shape_0223 + +[remorse-jp] +shape:248 444 shape_00f8 +shape:249 132 shape_00f9 +shape:569 77 shape_0239 +shape:534 62 shape_0216 +shape:16 60 shape_0010 +shape:253 58 shape_00fd +shape:563 49 shape_0233 +shape:564 49 shape_0234 +shape:1253 49 shape_04e5 +shape:572 46 shape_023c +shape:486 42 shape_01e6 +shape:250 40 shape_00fa +shape:43 40 shape_002b +shape:573 39 shape_023d +shape:716 36 shape_02cc +shape:252 35 shape_00fc +shape:631 35 shape_0277 +shape:246 33 shape_00f6 +shape:15 33 shape_000f +shape:547 30 shape_0223 + +Interesting nearby controller families + +[regret] +shape:251 226 VALUEBOX +shape:357 32 MONITEW +shape:258 21 MONITNS +shape:1246 20 WATCHEW +shape:1019 11 SECURNS +shape:1085 6 SECUREW +shape:871 6 WALLMNS +shape:1086 1 WALLMEW + +[regret-demo] +shape:251 6 VALUEBOX +shape:258 2 MONITNS +shape:1246 2 WATCHEW + +[remorse] +shape:251 456 VALUEBOX +shape:357 90 MONITEW +shape:258 65 MONITNS +shape:1246 18 WATCHEW +shape:1019 1 SECURNS + +[remorse-101] +shape:251 456 VALUEBOX +shape:357 90 MONITEW +shape:258 65 MONITNS +shape:1246 18 WATCHEW +shape:1019 1 SECURNS + +[remorse-jp] +shape:251 456 VALUEBOX +shape:357 90 MONITEW +shape:258 65 MONITNS +shape:1246 18 WATCHEW +shape:1019 1 SECURNS + +Top QLo values per game + +[regret] +QLo 0 -> 170 +QLo 23 -> 1 + +[regret-demo] +QLo 0 -> 7 + +[remorse] +QLo 0 -> 299 +QLo 60 -> 3 + +[remorse-101] +QLo 0 -> 299 +QLo 60 -> 3 + +[remorse-jp] +QLo 0 -> 299 +QLo 60 -> 3 + +Top QHi values per game + +[regret] +QHi 0 -> 171 + +[regret-demo] +QHi 0 -> 7 + +[remorse] +QHi 0 -> 299 +QHi 3 -> 3 + +[remorse-101] +QHi 0 -> 299 +QHi 3 -> 3 + +[remorse-jp] +QHi 0 -> 299 +QHi 3 -> 3 + +Representative examples + +[regret] +{"coords": [57662, 5790, 0], "id": "item:1425:fixed:251:0:57662:5790:0", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [58302, 5790, 0], "dist2": 409600, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4501, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58590, 7070, 0], "dist2": 2499584, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4229, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 4523, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [58302, 5790, 0], "id": "item:1430:fixed:251:0:58302:5790:0", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [57662, 5790, 0], "dist2": 409600, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4523, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58590, 7070, 0], "dist2": 1721344, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4229, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58590, 7358, 0], "dist2": 2541568, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4080, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 4501, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [58590, 7070, 0], "id": "item:1621:fixed:251:0:58590:7070:0", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [58590, 7358, 0], "dist2": 82944, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4080, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58302, 5790, 0], "dist2": 1721344, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4501, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57662, 5790, 0], "dist2": 2499584, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4523, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 4229, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [58590, 7358, 0], "id": "item:1710:fixed:251:0:58590:7358:0", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [58590, 7070, 0], "dist2": 82944, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4229, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58302, 5790, 0], "dist2": 2541568, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4501, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 4080, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [59358, 11486, 0], "id": "item:1991:fixed:251:0:59358:11486:0", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [59230, 11486, 0], "dist2": 16384, "frame": 0, "mapNum": 0, "name": "shape_00fd", "nextItem": 5364, "npcNum": 0, "quality": 0, "shape": "shape:253"}, {"coords": [59390, 11262, 0], "dist2": 51200, "frame": 4, "mapNum": 0, "name": "shape_023d", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:573"}, {"coords": [59390, 11262, 0], "dist2": 51200, "frame": 0, "mapNum": 0, "name": "shape_0010", "nextItem": 3701, "npcNum": 0, "quality": 2651, "shape": "shape:16"}, {"coords": [59390, 11262, 0], "dist2": 51200, "frame": 2, "mapNum": 0, "name": "shape_0237", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:567"}, {"coords": [59390, 11262, 0], "dist2": 51200, "frame": 2, "mapNum": 0, "name": "shape_0239", "nextItem": 3719, "npcNum": 0, "quality": 0, "shape": "shape:569"}, {"coords": [59422, 11262, 0], "dist2": 54272, "frame": 1, "mapNum": 0, "name": "shape_0239", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:569"}, {"coords": [59486, 11262, 0], "dist2": 66560, "frame": 1, "mapNum": 0, "name": "shape_023c", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:572"}, {"coords": [59390, 11774, 0], "dist2": 83968, "frame": 4, "mapNum": 0, "name": "shape_023d", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:573"}], "nextItem": 5365, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [58334, 9214, 96], "id": "item:5998:fixed:251:0:58334:9214:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [57758, 9022, 96], "dist2": 368640, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3949, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 9630, 96], "dist2": 504832, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3819, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 10110, 96], "dist2": 1134592, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 3957, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [57758, 9022, 96], "id": "item:6001:fixed:251:0:57758:9022:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [58334, 9214, 96], "dist2": 368640, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3957, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 9630, 96], "dist2": 369664, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3819, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 10110, 96], "dist2": 1183744, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 3949, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [57758, 10110, 96], "id": "item:6046:fixed:251:0:57758:10110:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [57758, 9630, 96], "dist2": 230400, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3819, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58334, 9214, 96], "dist2": 1134592, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3957, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 9022, 96], "dist2": 1183744, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3949, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [57758, 9630, 96], "id": "item:6051:fixed:251:0:57758:9630:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [57758, 10110, 96], "dist2": 230400, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 9022, 96], "dist2": 369664, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3949, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58334, 9214, 96], "dist2": 504832, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3957, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 3819, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [37406, 28222, 96], "id": "item:6414:fixed:251:0:37406:28222:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [36958, 28222, 96], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2040, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [37144, 29232, 128], "dist2": 1089768, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1983, "npcNum": 0, "quality": 18972, "shape": "shape:357"}, {"coords": [35902, 28478, 128], "dist2": 2328576, "frame": 0, "mapNum": 22, "name": "shape_043d", "nextItem": 2071, "npcNum": 164, "quality": 18, "shape": "shape:1085"}], "nextItem": 2043, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [36958, 28222, 96], "id": "item:6416:fixed:251:0:36958:28222:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [37406, 28222, 96], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2043, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [37144, 29232, 128], "dist2": 1055720, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1983, "npcNum": 0, "quality": 18972, "shape": "shape:357"}, {"coords": [35902, 28478, 128], "dist2": 1181696, "frame": 0, "mapNum": 22, "name": "shape_043d", "nextItem": 2071, "npcNum": 164, "quality": 18, "shape": "shape:1085"}], "nextItem": 2040, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [32414, 33310, 96], "id": "item:6666:fixed:251:0:32414:33310:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [33694, 33310, 96], "dist2": 1638400, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1605, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1660, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} + +[regret-demo] +{"coords": [48158, 54750, 8], "id": "item:2223:fixed:251:0:48158:54750:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [48158, 55486, 8], "dist2": 541696, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 475, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 642, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [50206, 54750, 8], "id": "item:2250:fixed:251:0:50206:54750:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [50652, 54868, 56], "dist2": 215144, "frame": 0, "mapNum": 167, "name": "MONITNS", "nextItem": 564, "npcNum": 139, "quality": 43, "shape": "shape:258"}, {"coords": [50206, 55486, 8], "dist2": 541696, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 442, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 638, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [54430, 55230, 8], "id": "item:2280:fixed:251:0:54430:55230:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [53982, 55070, 8], "dist2": 226304, "frame": 0, "mapNum": 102, "name": "WATCHEW", "nextItem": 492, "npcNum": 24, "quality": 282, "shape": "shape:1246"}, {"coords": [54366, 54750, 8], "dist2": 234496, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3670, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 534, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [54366, 54750, 8], "id": "item:2285:fixed:251:0:54366:54750:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [54430, 55230, 8], "dist2": 234496, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 534, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [53982, 55070, 8], "dist2": 249856, "frame": 0, "mapNum": 102, "name": "WATCHEW", "nextItem": 492, "npcNum": 24, "quality": 282, "shape": "shape:1246"}], "nextItem": 3670, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [48158, 55486, 8], "id": "item:2295:fixed:251:0:48158:55486:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [48158, 54750, 8], "dist2": 541696, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 642, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 475, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [50206, 55486, 8], "id": "item:2306:fixed:251:0:50206:55486:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [50206, 54750, 8], "dist2": 541696, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 638, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [50652, 54868, 56], "dist2": 583144, "frame": 0, "mapNum": 167, "name": "MONITNS", "nextItem": 564, "npcNum": 139, "quality": 43, "shape": "shape:258"}], "nextItem": 442, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [26526, 28670, 96], "id": "item:3506:fixed:251:0:26526:28670:96", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [26590, 28670, 96], "dist2": 4096, "frame": 0, "mapNum": 0, "name": "shape_00f9", "nextItem": 4941, "npcNum": 0, "quality": 0, "shape": "shape:249"}, {"coords": [26622, 28670, 96], "dist2": 9216, "frame": 2, "mapNum": 0, "name": "shape_0063", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:99"}, {"coords": [26622, 28670, 96], "dist2": 9216, "frame": 0, "mapNum": 0, "name": "shape_0010", "nextItem": 4933, "npcNum": 0, "quality": 347, "shape": "shape:16"}, {"coords": [26622, 28670, 96], "dist2": 9216, "frame": 1, "mapNum": 0, "name": "shape_00f8", "nextItem": 4940, "npcNum": 0, "quality": 0, "shape": "shape:248"}, {"coords": [26398, 28670, 96], "dist2": 16384, "frame": 0, "mapNum": 0, "name": "shape_00fd", "nextItem": 4943, "npcNum": 0, "quality": 0, "shape": "shape:253"}, {"coords": [26494, 28894, 136], "dist2": 52800, "frame": 0, "mapNum": 200, "name": "shape_0476", "nextItem": 4827, "npcNum": 128, "quality": 28425, "shape": "shape:1142"}, {"coords": [26558, 28958, 96], "dist2": 83968, "frame": 0, "mapNum": 239, "name": "shape_0476", "nextItem": 4824, "npcNum": 64, "quality": 9, "shape": "shape:1142"}, {"coords": [26462, 28382, 96], "dist2": 87040, "frame": 8, "mapNum": 111, "name": "CMD_LINK", "nextItem": 4935, "npcNum": 97, "quality": 3334, "shape": "shape:1201"}], "nextItem": 4942, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} + +[remorse] +{"coords": [22782, 11326, 0], "id": "item:2327:fixed:251:0:22782:11326:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [23262, 10302, 112], "dist2": 1291520, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2988, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 1676864, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 2726, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 2839, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [25534, 12254, 0], "id": "item:2408:fixed:251:0:25534:12254:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 11710, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2753, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1857792, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2883, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2754, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [25534, 11710, 0], "id": "item:2411:fixed:251:0:25534:11710:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 12254, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2754, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1074432, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2883, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25534, 10302, 112], "dist2": 1995008, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2898, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25790, 10270, 112], "dist2": 2151680, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2800, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 2205248, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 2726, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 2753, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [22398, 13374, 0], "id": "item:2550:fixed:251:0:22398:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [21950, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2542, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2545, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [21950, 13374, 0], "id": "item:2554:fixed:251:0:21950:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [22398, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2545, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2542, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [20670, 15422, 0], "id": "item:2768:fixed:251:0:20670:15422:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [20350, 15294, 24], "dist2": 119360, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 2530, "npcNum": 0, "quality": 14906, "shape": "shape:357"}, {"coords": [19998, 14558, 72], "dist2": 1203264, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 2531, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 2424, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [30910, 17502, 0], "id": "item:2988:fixed:251:0:30910:17502:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [31070, 16734, 40], "dist2": 617024, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 2224, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 2049, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [38462, 19518, 0], "id": "item:3216:fixed:251:0:38462:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38078, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1738, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 217152, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1924, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 293952, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1925, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38014, 19166, 136], "dist2": 343104, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1923, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38704, 18592, 152], "dist2": 939144, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1208576, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1879, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [39742, 20382, 24], "dist2": 2385472, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 3339, "npcNum": 0, "quality": 0, "shape": "shape:258"}], "nextItem": 1745, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [38078, 19518, 0], "id": "item:3225:fixed:251:0:38078:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38014, 19166, 136], "dist2": 146496, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1923, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38462, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1745, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 684096, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1924, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 760896, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1925, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38704, 18592, 152], "dist2": 1272456, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1650944, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1879, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1738, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [32414, 20542, 0], "id": "item:3354:fixed:251:0:32414:20542:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [32926, 19998, 0], "dist2": 558080, "frame": 0, "mapNum": 0, "name": "shape_04de", "nextItem": 1767, "npcNum": 0, "quality": 14362, "shape": "shape:1246"}], "nextItem": 3361, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [36062, 23614, 0], "id": "item:3782:fixed:251:0:36062:23614:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [36478, 23710, 32], "dist2": 183296, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1177, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [35966, 24030, 32], "dist2": 183296, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 1176, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36926, 23710, 16], "dist2": 755968, "frame": 2, "mapNum": 0, "name": "MONITNS", "nextItem": 1099, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36030, 25054, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 962, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1130, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [36030, 25054, 0], "id": "item:3932:fixed:251:0:36030:25054:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [35966, 24030, 32], "dist2": 1053696, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 1176, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36478, 23710, 32], "dist2": 2008064, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1177, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36062, 23614, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1130, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 962, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} + +[remorse-101] +{"coords": [22782, 11326, 0], "id": "item:2326:fixed:251:0:22782:11326:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [23262, 10302, 112], "dist2": 1291520, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 533, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 1676864, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 752, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 707, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [25534, 11710, 0], "id": "item:2402:fixed:251:0:25534:11710:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 12254, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 782, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1074432, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 664, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25534, 10302, 112], "dist2": 1995008, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 622, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25790, 10270, 112], "dist2": 2151680, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 639, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 2205248, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 752, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 783, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [25534, 12254, 0], "id": "item:2410:fixed:251:0:25534:12254:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 11710, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 783, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1857792, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 664, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 782, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [22398, 13374, 0], "id": "item:2536:fixed:251:0:22398:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [21950, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 940, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 937, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [21950, 13374, 0], "id": "item:2545:fixed:251:0:21950:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [22398, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 937, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 940, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [20670, 15422, 0], "id": "item:2753:fixed:251:0:20670:15422:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [20350, 15294, 24], "dist2": 119360, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1039, "npcNum": 0, "quality": 14906, "shape": "shape:357"}, {"coords": [19998, 14558, 72], "dist2": 1203264, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 1038, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 1217, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [30910, 17502, 0], "id": "item:2982:fixed:251:0:30910:17502:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [31070, 16734, 40], "dist2": 617024, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1396, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 1566, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [38462, 19518, 0], "id": "item:3225:fixed:251:0:38462:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38078, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1987, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 217152, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1810, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 293952, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1809, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38014, 19166, 136], "dist2": 343104, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1811, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38638, 18602, 152], "dist2": 893136, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1759, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1208576, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1778, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [39742, 20382, 24], "dist2": 2385472, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 2104, "npcNum": 0, "quality": 0, "shape": "shape:258"}], "nextItem": 1980, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [38078, 19518, 0], "id": "item:3229:fixed:251:0:38078:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38014, 19166, 136], "dist2": 146496, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1811, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38462, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1980, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 684096, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1810, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 760896, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1809, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38638, 18602, 152], "dist2": 1175760, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1759, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1650944, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1778, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1987, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [32414, 20542, 0], "id": "item:3347:fixed:251:0:32414:20542:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [32926, 19998, 0], "dist2": 558080, "frame": 0, "mapNum": 0, "name": "shape_04de", "nextItem": 1924, "npcNum": 0, "quality": 14362, "shape": "shape:1246"}], "nextItem": 2182, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [36062, 23614, 0], "id": "item:3771:fixed:251:0:36062:23614:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [35966, 24030, 32], "dist2": 183296, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 2765, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36478, 23710, 32], "dist2": 183296, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 2764, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36926, 23710, 16], "dist2": 755968, "frame": 2, "mapNum": 0, "name": "MONITNS", "nextItem": 2844, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36030, 25054, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2960, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2748, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [36030, 25054, 0], "id": "item:3950:fixed:251:0:36030:25054:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [35966, 24030, 32], "dist2": 1053696, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 2765, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36478, 23710, 32], "dist2": 2008064, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 2764, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36062, 23614, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2748, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2960, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} + +[remorse-jp] +{"coords": [22782, 11326, 0], "id": "item:2327:fixed:251:0:22782:11326:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [23262, 10302, 112], "dist2": 1291520, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2988, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 1676864, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 2726, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 2839, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [25534, 12254, 0], "id": "item:2408:fixed:251:0:25534:12254:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 11710, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2753, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1857792, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2883, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2754, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [25534, 11710, 0], "id": "item:2411:fixed:251:0:25534:11710:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 12254, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2754, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1074432, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2883, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25534, 10302, 112], "dist2": 1995008, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2898, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25790, 10270, 112], "dist2": 2151680, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2800, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 2205248, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 2726, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 2753, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [22398, 13374, 0], "id": "item:2550:fixed:251:0:22398:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [21950, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2542, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2545, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [21950, 13374, 0], "id": "item:2554:fixed:251:0:21950:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [22398, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2545, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2542, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [20670, 15422, 0], "id": "item:2768:fixed:251:0:20670:15422:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [20350, 15294, 24], "dist2": 119360, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 2530, "npcNum": 0, "quality": 14906, "shape": "shape:357"}, {"coords": [19998, 14558, 72], "dist2": 1203264, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 2531, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 2424, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [30910, 17502, 0], "id": "item:2988:fixed:251:0:30910:17502:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [31070, 16734, 40], "dist2": 617024, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 2224, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 2049, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [38462, 19518, 0], "id": "item:3216:fixed:251:0:38462:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38078, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1738, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 217152, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1924, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 293952, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1925, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38014, 19166, 136], "dist2": 343104, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1923, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38704, 18592, 152], "dist2": 939144, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1208576, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1879, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [39742, 20382, 24], "dist2": 2385472, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 3339, "npcNum": 0, "quality": 0, "shape": "shape:258"}], "nextItem": 1745, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [38078, 19518, 0], "id": "item:3225:fixed:251:0:38078:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38014, 19166, 136], "dist2": 146496, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1923, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38462, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1745, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 684096, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1924, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 760896, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1925, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38704, 18592, 152], "dist2": 1272456, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1650944, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1879, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1738, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [32414, 20542, 0], "id": "item:3354:fixed:251:0:32414:20542:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [32926, 19998, 0], "dist2": 558080, "frame": 0, "mapNum": 0, "name": "shape_04de", "nextItem": 1767, "npcNum": 0, "quality": 14362, "shape": "shape:1246"}], "nextItem": 3361, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [36062, 23614, 0], "id": "item:3782:fixed:251:0:36062:23614:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [36478, 23710, 32], "dist2": 183296, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1177, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [35966, 24030, 32], "dist2": 183296, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 1176, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36926, 23710, 16], "dist2": 755968, "frame": 2, "mapNum": 0, "name": "MONITNS", "nextItem": 1099, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36030, 25054, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 962, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1130, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [36030, 25054, 0], "id": "item:3932:fixed:251:0:36030:25054:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [35966, 24030, 32], "dist2": 1053696, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 1176, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36478, 23710, 32], "dist2": 2008064, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1177, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36062, 23614, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1130, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 962, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} + +Nonzero payload examples + +[regret] +{"coords": [4926, 14302, 96], "id": "item:5149:fixed:251:0:4926:14302:96", "map": "map-29", "mapNum": 0, "nextItem": 1596, "npcNum": 0, "qhi": 0, "qlo": 23, "quality": 23} + +[remorse] +{"coords": [53118, 30558, 96], "id": "item:10711:fixed:251:0:53118:30558:96", "map": "map-141", "mapNum": 0, "nextItem": 799, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828} +{"coords": [64702, 34814, 96], "id": "item:12590:glob:251:0:64702:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [65438, 34814, 96], "id": "item:12594:glob:251:0:65438:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [53118, 30558, 96], "id": "item:11657:fixed:251:0:53118:30558:96", "map": "map-25", "mapNum": 0, "nextItem": 1078, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828} +{"coords": [64702, 34814, 96], "id": "item:13546:glob:251:0:64702:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [65438, 34814, 96], "id": "item:13550:glob:251:0:65438:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [53118, 30558, 96], "id": "item:10397:fixed:251:0:53118:30558:96", "map": "map-26", "mapNum": 0, "nextItem": 279, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828} + +[remorse-101] +{"coords": [53118, 30558, 96], "id": "item:10711:fixed:251:0:53118:30558:96", "map": "map-141", "mapNum": 0, "nextItem": 799, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828} +{"coords": [64702, 34814, 96], "id": "item:12590:glob:251:0:64702:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [65438, 34814, 96], "id": "item:12594:glob:251:0:65438:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [53118, 30558, 96], "id": "item:11664:fixed:251:0:53118:30558:96", "map": "map-25", "mapNum": 0, "nextItem": 1561, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828} +{"coords": [64702, 34814, 96], "id": "item:13546:glob:251:0:64702:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [65438, 34814, 96], "id": "item:13550:glob:251:0:65438:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [53118, 30558, 96], "id": "item:10397:fixed:251:0:53118:30558:96", "map": "map-26", "mapNum": 0, "nextItem": 279, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828} + +[remorse-jp] +{"coords": [53118, 30558, 96], "id": "item:10711:fixed:251:0:53118:30558:96", "map": "map-141", "mapNum": 0, "nextItem": 799, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828} +{"coords": [64702, 34814, 96], "id": "item:12590:glob:251:0:64702:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [65438, 34814, 96], "id": "item:12594:glob:251:0:65438:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [53118, 30558, 96], "id": "item:11657:fixed:251:0:53118:30558:96", "map": "map-25", "mapNum": 0, "nextItem": 1078, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828} +{"coords": [64702, 34814, 96], "id": "item:13546:glob:251:0:64702:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [65438, 34814, 96], "id": "item:13550:glob:251:0:65438:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0} +{"coords": [53118, 30558, 96], "id": "item:10397:fixed:251:0:53118:30558:96", "map": "map-26", "mapNum": 0, "nextItem": 279, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828} diff --git a/crusader_decompilation_notes.md b/crusader_decompilation_notes.md index df96ea3..12ae566 100644 --- a/crusader_decompilation_notes.md +++ b/crusader_decompilation_notes.md @@ -4,6 +4,14 @@ This file is an index. Detailed notes have been split into the `docs/` folder by Active live analysis target is now `CRUSADER.EXE`. Existing `CRUSADER-RAW.EXE` notes remain in scope as cross-reference evidence and should be cited alongside live NE addresses when they support a rename, variable role, or behavior claim. +Recent map-renderer egg-link follow-up: [docs/map_renderer/egg-identification.md](docs/map_renderer/egg-identification.md) now closes the old No Regret map-`3` destination-egg `102` gap. Current best read is that Regret uses a second elevator family at `shape:400` (`0x0190`) in addition to the earlier Remorse-focused `shape:542` rule: recovered Regret `ELEVATOR::gotHit` accepts `QLo >= 100`, treats `QLo < 0x00c8` as the generic same-map lane, and map `3` contains a concrete source object `item:664:fixed:400:0:44030:9662:0` with `quality 614` (`QLo 102`) that resolves the previously unexplained destination egg `102`. + +Recent map-renderer editor-object follow-up: [docs/map_renderer/trigger-usecode-links.md](docs/map_renderer/trigger-usecode-links.md) and [docs/map_renderer/editor-object-survey.md](docs/map_renderer/editor-object-survey.md) now promote a Regret-only controller cluster that had still been sitting in the unresolved editor bucket. Current best read is that `0x04c6` / `0x04de` are `WATCHNS` / `WATCHEW` secret-door watcher controllers, `0x0510` is their nearby `SECRET_DOOR_POST` target keyed by shared `QLo`, `0x05e1` is `CRYOBOX`, and `0x05df` / `0x05e0` are the paired pressure-barrier faces it drives by shared `QLo`. The same batch also closes `0x0451` / `0x05ae` as `CRAZYEW` / `CRAZYNS` hit-driven NPC wake-up relays and `0x056d` as `VIDEOBOX`, then promotes cautious local viewer arrows for `WATCH* -> 0x0510` and `CRYOBOX -> 0x05DF/0x05E0` in the map renderer. + +Recent map-renderer control-pad follow-up: [docs/map_renderer/egg-identification.md](docs/map_renderer/egg-identification.md), [docs/map_renderer/trigger-usecode-links.md](docs/map_renderer/trigger-usecode-links.md), and [docs/map_renderer/editor-object-survey.md](docs/map_renderer/editor-object-survey.md) now tighten the Regret read on `0x0318` / `0x0366` using the decompressed `.cache` scenes rather than the packed site export. Current best read is that `0x0318` is `CRUMORPH`, a control-transfer pad whose `equip` body scans nearby NPCs for a local-`QLo` key before bracketing `TRIGGER.slot_20`, while `0x0366` remains `NPC_ONLY`, a hit-driven NPC-only trigger pad keyed by an internal actor field. The viewer now promotes cautious same-`QLo` local `CRUMORPH -> 0x04B1` and `NPC_ONLY -> 0x04B1` arrows where authored matches exist, but still keeps `NPC_ONLY -> actor` out of the overlay. + +Recent actor-key follow-up: the same map-renderer notes now make the current blocker explicit instead of leaving it as an implied missing export. Current best read is that the hidden actor-side value behind `CRUMORPH` / `NPC_ONLY` is mutable actor field `0x63`, not a stable DTABLE row: sampled Regret DTABLE rows still read as zero at record byte `0x63`, while recovered `TRIGGER.slot_29` / `slot_2B` lanes can rewrite actor field `0x63` on nearby matched NPCs after load. The same pass also widens the sibling-family set that uses this mechanism: `WATCHNS` / `WATCHEW`, `THRMBCKN` / `THRMBCKE`, and `SURCAMNS` / `SURCAMEW` all compare controller-local bytes against actor field `0x63` in recovered lanes, so the viewer now documents a broader actor-key controller family while still withholding speculative actor-target arrows. + Recent verified PSX pre-alpha batch: [docs/psx/prealpha.md](docs/psx/prealpha.md) now records a focused Ghidra pass on `/psx/prealpha/SLUS_002.68` plus a disc-tree comparison against the released PlayStation `Crusader: No Remorse` build. Current best read is that this pre-pre alpha still looks much more like a trimmed early No Remorse PSX branch than a clearly rebranded `Crusader 2` executable: it still carries direct `Crusader: No Remorse` save/quit text, the renamed `wdl_resource_bundle_load_by_index` still embeds the full retail `\LSET1\L` through `\LSET7\L` prefix table and the same `10/20/30/40/50/60` threshold ladder, and the mission/passcode UI scaffolding is still present with the same visible `15` mission briefing strings and consonant/digit passcode alphabet. The main concrete differences in this batch are the heavily reduced shipped content (`3` level bundles, `1` XA, no `.STR` movies) and the surviving architectural leftovers that no longer match the current disc literally, especially the missing-file `\AUDIO\TALK1.XA;1` path and the `LoadExec` helper for `MENU.EXE` / `ENGINE.EXE` / `PSX.EXE`. Recent verified PSX executable batch: [docs/psx/psx.md](docs/psx/psx.md) now records a focused Ghidra pass on `SLUS_002.68` for mission/map inventory, passcode handling, and catalog text. Current best read is that the PSX loader hardcodes seven `\LSETn\L` folder prefixes and the extracted disc ships `62` level bundles (`L0..L58`, `L62..L64`) with a real gap at `L59..L61`, while the executable still exposes only `15` plain-text `Mission Briefing ^Mission N` strings. The same pass closes the visible passcode-generation side too: mission-complete flow synthesizes `4`-character passcodes from the alphabet `BCDFGHJKLMNPQRSTVWXZ0123456789`, and the executable preserves direct ammo/item/weapon name tables. The hidden password-screen cheat codes remain less direct: public PSX references point to `XXXX` and `L0SR`/`L0SER`, but those values are not stored as plain ASCII in `SLUS_002.68`, so the compare path still looks numeric or transformed rather than table-driven. @@ -38,10 +46,14 @@ Latest `-u` deep dive: new note [docs/usecode-startup-override.md](docs/usecode- Latest `-u` token-shape follow-up: the same [docs/usecode-startup-override.md](docs/usecode-startup-override.md) note now tightens the argument semantics materially. In live retail `CRUSADER.EXE`, `startup_apply_u_override_if_present` does not pass the copied argv token through as an arbitrary final filename. It loads the mutable filename template `eusecode.flx` from `1478:07a0` via the far pointer at `1478:06d6/06d8`, forces the first byte to `'e'`, and calls `Filespec_GetFullPath(0, s_usecode, "eusecode.flx", 0)`. Current safest read is therefore `path/root override for the standard EUSECODE archive family`, not `free-form arbitrary filename switch`. The same note now also separates the stock-path status more cleanly: the raw-side VM bootstrap is strongly cross-referenced, but the exact live-NE writer that seeds `1478:6611/6613` without `-u` is still not directly closed. +Latest `-u` practical follow-up: that same [docs/usecode-startup-override.md](docs/usecode-startup-override.md) and [docs/command-line-parameters.md](docs/command-line-parameters.md) notes now make the immediate user-facing consequence explicit. Failed attempts like `-u USECODE/FLICTEST.FLX` and `-u FLICTEST.FLX` fit the live helper badly because retail No Remorse still appends the fixed filename template `EUSECODE.FLX`; the copied argv token is only the path/root component. Current safest experiment shape is therefore `directory passed to -u, complete replacement archive named EUSECODE.FLX inside that directory`, not `free-form archive basename override`. + Latest `-u` loader-layout follow-up: the same [docs/usecode-startup-override.md](docs/usecode-startup-override.md) note now records the direct constructor/loader pair behind the override in the live NE session. `1420:1499` is now renamed `entity_vm_runtime_create` and currently reads as a `0x1319`-byte runtime-object constructor with a `0x1300`-byte front region that behaves like `0x80` stride-`0x26` slot/runtime records plus tail metadata at `0x1300..0x1318`. `1430:0000` is now renamed `entity_vm_runtime_owner_resource_create` and currently reads as the compact `0x14`-byte file-backed helper allocated from the resolved `eusecode.flx` path and attached to the runtime object at `+0x1315/+0x1317`. Latest doc-reconciliation batch: [docs/ne-segment1.md](docs/ne-segment1.md) now has a combined hidden-debugger component table that explicitly separates the seg109/raw-reference UI wrappers (`000b:9a86`, `000b:9c0d`, `000b:b3b1`, `000b:b62c`, `000b:2882`) from the live seg1408 breakpoint-state helpers (`1408:0000`, `1408:0053`, `1408:00dd`, `1408:029e`, `1408:03b0`, `1408:03f7`, `1408:0419`, `1408:0432`, `1408:0444`) and the interpreter hook at `1418:04aa..04b5`. Current best read remains `two connected layers of one hidden usecode debugger`, not `conflicting address claims for the same function family`. +Latest hidden-debugger floor pass: [docs/retail-debugger-patch-attempts.md](docs/retail-debugger-patch-attempts.md) now records a fresh live-Ghidra constraint check on the smallest viable retail unlock patch. Current best read is tighter than the earlier "there must be one tiny jump" idea: retail still has no recovered writer for `1478:659c/659e`, the constructor still seeds only the inert shared callbacks `1478:65ab -> 1408:046f` / `1478:65af -> 1408:0474`, and the interpreter-side gate at `1418:049e..04b5` only checks for a non-null debugger object before handing off to `1408:0053`. That makes the current O/P interpreter-callsite-retarget family the smallest structurally defensible executable patch shape so far, because smaller one-site ideas lose either object bootstrap, one-shot deferred gating, or wrapper-argument sanitation. + Follow-up cheat-key correction pass: [docs/ne-segment1.md](docs/ne-segment1.md) now also records a live NE cleanup of several folklore keyboard-cheat claims. `~` is a real runtime cheat-latch toggle at `13e8:203d`, `Ctrl+C` is wrong for this build and should be `Ctrl+L` for the coordinate popup at `13e8:255e`, and the third F7-family overlay really does exist as a separate `Ctrl+F7` path at `13e8:1a20` alongside the other two cheat-gated F7 overlay toggles. That same note now also separates `~` from `jassica16` more cleanly: `jassica16` is the raw scan-code unlock path that toggles both `1478:0844` and `1478:6045` and sets the extra post-sequence latch `1478:8c52`, while `~` is only the later translated logical-`0x7e` hotkey that flips `1478:6045` after `1478:0844` is already on. The F7-family clarification is tighter too: `Ctrl+F7` is best read as an egg-hatcher trigger-range overlay rather than a third generic background grid. @@ -90,5 +102,6 @@ Latest F7 overlay follow-up: new note [docs/f7-overlays.md](docs/f7-overlays.md) | [docs/usecode-jelyhack-analysis.md](docs/usecode-jelyhack-analysis.md) | Focused analysis of exported `JELYHACK` / `JELYH2` pseudocode, the tiny shared `use` stub, and why the current best model remains `referent anchor + neighboring event-bearing attachment` | | [docs/usecode-equipment-system.md](docs/usecode-equipment-system.md) | Evidence-backed note on Crusader's surviving `equip` / `unequip` event system, including live compiled-side dispatcher proof, corpus-wide slot counts, actor/turret/environment examples, and the current best model of `equip` as a generalized inherited Ultima-style item event | | [docs/usecode-alarmhat-analysis.md](docs/usecode-alarmhat-analysis.md) | Focused analysis of exported `ALARMHAT::equip`, the nearby `shape 0x04D0` equip loops, alarm-family comparisons, and the current gameplay-facing read of `ALARMHAT` as a local alarm-state driver | +| [docs/usecode/flictest-investigation.md](docs/usecode/flictest-investigation.md) | Focused investigation of `FLICTEST`: class/body structure, Remorse versus Regret differences, confirmed caller scripts, and the stronger Regret finding that a hidden `KEYPADNS` `VIDEO PLAYER` route appears to have matching shipped placement evidence | | [docs/usecode/windsurf-regret-vs-remorse.md](docs/usecode/windsurf-regret-vs-remorse.md) | Side-by-side comparison of `WINDSURF` in Regret and No Remorse, including shared slot behavior, helper-family drift, body-size differences, and the current best read of `WINDSURF` as a directional wind-force helper used by vent scripts | | [docs/removed_items.md](docs/removed_items.md) | Evidence summary for suspicious removed item shapes in old No Remorse maps: grenade-family leftovers `0343/034E/034F/0350`, the inventory-labeled `0548` `Invalid` item, and unresolved non-pickup shapes `0110/0112` | diff --git a/docs/command-line-parameters.md b/docs/command-line-parameters.md index 1234d16..59421f5 100644 --- a/docs/command-line-parameters.md +++ b/docs/command-line-parameters.md @@ -151,14 +151,76 @@ What is now materially tighter: - it uses the token as the `path` component to `Filespec_GetFullPath` - it uses the mutable filename template at `1478:07a0`, which is `eusecode.flx`, as the fixed `filename` component - it forces that template's first byte to `'e'` before the existence probe and final load call +- the path builder uses DOS-style backslashes via the literal `"\\"` string from `FILE\\FILESPEC.C` +- the existence probe goes through the DOS file-search path in `File_Exists`, so the override must resolve inside the game's DOS-visible filesystem +- the parser copies the token into the `0x1e`-byte buffer at `1478:065a`, so the practical maximum is `29` visible characters plus the terminator So the safest current retail read is: - `-u ` expects a directory/resource-root style path argument for the standard `eusecode.flx` archive family - it does **not** currently look like a free-form arbitrary filename override +- the safest token spelling uses DOS-style backslashes, not forward slashes + +That gives a direct explanation for failed attempts like: + +- `-u USECODE/FLICTEST.FLX` +- `-u FLICTEST.FLX` +- `-u C:\MADDOCODE` when that path only exists on the Windows host and not inside the guest DOS drive mapping + +Those forms fit the recovered helper badly because the token is used as the `Filespec_GetFullPath` path component while the filename remains the fixed template `eusecode.flx`. Current safest interpretation is therefore: + +- pass a directory or resource-root to `-u` +- put the replacement archive at `/EUSECODE.FLX` +- do not expect `-u` to open `FLICTEST.FLX` directly as the final archive name + +The current safest syntax examples are: + +- `-u MADDOCODE` +- `-u .\MADDOCODE` +- `-u \MADDOCODE` +- `-u C:\MADDOCODE` + +But for real DOS-facing installs, the safer practical examples are the `8.3`-safe variants: + +- `-u MADDOC~1` +- `-u .\MADDOC~1` + +with these current best path interpretations: + +- `MADDOCODE` = relative to the game's current DOS working directory +- `.\MADDOCODE` = explicit relative DOS path +- `\MADDOCODE` = root-relative path on the current DOS drive +- `C:\MADDOCODE` = absolute DOS path on drive `C:` as seen by the DOS game, not automatically the Windows host path + +In the current GOG install used for this investigation, Windows reports the short alias: + +- `MADDOCODE` -> `MADDOC~1` + +That makes `MADDOC~1` the highest-probability next token for this specific setup. + +Current safest negative guidance: + +- avoid forward slashes for `-u` +- avoid long path tokens because silent truncation at `1478:065a` can change the real lookup target +- do not use the archive filename itself as the `-u` argument +- prefer `8.3`-safe directory names or explicit DOS short aliases when testing under DOSBox-like environments But the important uncertainty is now only `exact naming rules`, not `whether the switch is real`. In the regular non-Japanese `CRUSADER.EXE`, the switch is clearly still live. +## Expected Console Output For `-u` + +Current best answer: probably none. + +The recovered `-u` parser case does not print a banner, and the startup helper that probes and swaps the override path also has no recovered `ConsolePrintf` call. So unlike `-debug`, `-warp`, `-skill`, `-mapoff`, `-egg`, or `-demo`, `-u` does not currently appear to advertise itself on the console before the game loads. + +The safest current expectation is: + +- success: no dedicated `-u` console message +- missing path/file: no dedicated `-u` console message, likely silent fallback to stock usecode +- malformed but existing replacement: no dedicated `-u` status line from the parser/helper path; later behavior depends on the deeper loader/runtime path + +This also sharpens the practical diagnosis for the current `MADDOCODE` setup. In the tested GOG install, only `EUSECODE.FLX` differs between `USECODE` and `MADDOCODE`; the sidecar files are byte-identical. So if replacing `USECODE\EUSECODE.FLX` with the hacked file produces the expected obvious no-usecode-style breakage, but `-u MADDOCODE` behaves like stock gameplay instead, the strongest current read is that the override path was probably not accepted and startup silently fell back to the stock usecode root. + This also aligns with the already-stronger JP Win32 result, where the matching `-u` lane was recovered as the same kind of usecode override. For the deeper runtime-side investigation of whether `-u` replaces or augments the stock usecode root, and what game systems that replacement feeds, see [docs/usecode-startup-override.md](docs/usecode-startup-override.md). diff --git a/docs/map_renderer/editor-object-survey.md b/docs/map_renderer/editor-object-survey.md index 3f989ee..a052649 100644 --- a/docs/map_renderer/editor-object-survey.md +++ b/docs/map_renderer/editor-object-survey.md @@ -10,9 +10,29 @@ This pass widened the renderer research beyond egg and NPC spawner objects and f - `0x005A`, `0x005B`, `0x005C`, `0x005D`, `0x0066`-`0x0069`: invisible/editor wall objects - `0x01B8`: `camera` - `0x0290`, `0x0336`: `LIGHT_BRIDGE_*` - - `0x0251`, `0x0318`, `0x0337`, `0x0361`: placeholder cubes and placeholder UI/editor markers + - `0x0337`, `0x0361`: placeholder cubes and placeholder UI/editor markers - `0x0108`, `0x0113`, `0x01B9`, `0x01BA`, `0x025F`, `0x0260`, `0x02F0`, `0x0373`, `0x0399`, `0x03A1`, `0x04C8`: `wallgun_shape_*` helper cluster +## `0x0251` Frame `0`: `VALUEBOX` + +- The recovered usecode corpus now closes `0x0251` directly as `VALUEBOX` in both retail games, not as a generic placeholder cube. +- Current safest read is `local payload box`, not `world prop`: the object stores numeric or text-selection data for nearby authored controllers instead of behaving like a visible gameplay pickup. +- The caller side is much clearer than `VALUEBOX.slot_20(...)` itself. Checked bodies that explicitly scan nearby `shape=0x0251` items include: + - `MONITNS` (`0x0102`) and `MONITEW` (`0x0165`) + - `WALLMNS` (`0x0367`) and Regret `WALLMEW` (`0x0436`) + - Regret `SECURNS` (`0x03FB`) and `SECUREW` (`0x043D`) + - Regret `WATCHNS` (`0x04C6`) and `WATCHEW` (`0x04DE`) + - `KEYPAD` (`0x0A0E` in Remorse, `0x0A0D` in Regret) +- Those families usually match the box on shared `QLo`, then call `VALBOX.slot_20(valueBox)` to recover the stored value. Monitor, wall-display, and security-terminal paths also feed `Item.getQHi(valueBox)` into `TEXTFILE.slot_23(...)`, so `QHi` behaves like a second text/value selector rather than dead metadata. +- `VALUEBOX::cachein` also hints at a small self-initialization lane: when the decoded value is zero it calls `FREE.slot_20(0x0383)` and writes a replacement through `VALUEBOX.slot_20(...)`. The slot-20 decompilation is still weak, so the conservative read is `possible value initialization`, not a fully closed random-number claim. +- Decompressed `.cache` scenes back the helper role strongly: + - Remorse retail caches currently expose `299` frame-0 placements. The strongest nearby controller families in the local scan are `MONITEW` (`90` hits), `MONITNS` (`65`), and `WATCHEW` (`18`). + - Regret retail caches currently expose `171` frame-0 placements. The strongest nearby controller families are `MONITEW` (`32`), `MONITNS` (`21`), `WATCHEW` (`20`), `SECURNS` (`11`), `SECUREW` (`6`), and `WALLMNS` (`6`). + - Most frame-0 boxes still carry `quality = 0`, which is consistent with the common `QLo == 0` default-link path in the callers. + - Rare authored payloads do exist: Regret map `29` has a `VALUEBOX` with `quality 23` (`QLo 23`), and Remorse maps `25`, `26`, and `141` all contain a nonzero example at `53118,30558,96` with `quality 828` (`QLo 60`, `QHi 3`, `npcNum 2`). + - Many placements occur in small chained local clusters through `nextItem`, which fits a backing-store/helper role better than a standalone scene-prop interpretation. +- Practical renderer implication: `0x0251` should be labeled `VALUEBOX`, should keep `QLo`, `QHi`, and `nextItem` visible in tooltips, and should be treated as a local controller payload object rather than as a generic placeholder cube. + ## What This Means For The Renderer - A lot of the useful information is already present without more reverse-engineering. The main problem was presentation, not raw data availability. @@ -46,7 +66,26 @@ The tooltip now exposes generalized metadata for editor/helper objects instead o - Add dedicated filters or list views for helper subfamilies such as invisible walls, camera markers, light bridges, and placeholder cubes. - Add shape-family frequency summaries so repeated helper markers can be audited across a map. -- Decode more shape-specific field semantics for the still-unresolved editor objects, especially the remaining non-promoted invisible-wall, camera/helper, and music-controller families, and keep folding any new results back into the dedicated USECODE-link note. +- Decode more shape-specific field semantics for the still-unresolved editor objects, especially the remaining non-promoted invisible-wall, camera/helper, music-controller, and secret-door-switch families, and keep folding any new results back into the dedicated USECODE-link note. - Find the No Regret replacement for the Remorse `0x024F` monster-egg workflow instead of assuming the same shape is reused. +## Newly Promoted Regret-Only Controllers + +- `0x0318` is now promoted as `CRUMORPH`, not a blank placeholder cube. The recovered `equip` body scans nearby NPCs for a shared internal control key derived from the item's `QLo`, temporarily transfers player control to the first live match, and then brackets `TRIGGER.slot_20` with success or failure lanes. +- `0x0366` remains `NPC_ONLY`, but the latest decompressed `.cache` sweep tightens its practical viewer behavior: actor-target arrows are still not justified, while cautious local `NPC_ONLY -> 0x04B1` same-`QLo` arrows are now strong enough to expose. +- `0x04c6` / `0x04de` are now promoted as `WATCHNS` / `WATCHEW`, not generic editor leftovers. Their recovered `slot_20` bodies scan nearby `0x0510` posts by shared `QLo` and then bracket `TRIGGER.slot_20` around a watcher-specific follow-up lane. +- `0x0510` is now better treated as a `SECRET_DOOR_POST` helper target rather than an unresolved standalone controller. The strongest current viewer behavior is a cautious local arrow from `WATCHNS` / `WATCHEW` plus tooltip decoding of its `QLo`/`QHi` bytes. +- `0x05e1` is closed as `CRYOBOX`, and nearby `0x05df` / `0x05e0` pressure-barrier faces are now promoted out of the unresolved bucket as local arrow targets keyed by shared `QLo`. +- `0x0451` / `0x05ae` are now closed as `CRAZYEW` / `CRAZYNS`, small Regret-only hit-driven NPC wake-up relays rather than vague contextual map labels. +- `0x056d` is now closed as `VIDEOBOX`, a gated controller with a direct `equip` body, even though its higher-level gameplay meaning is still less explicit than the watcher and cryobox lanes. + +## Actor-Key Family Follow-Up + +- The latest actor-link follow-up did not justify exporting a stable `NPC_ONLY -> actor` or `CRUMORPH -> actor` overlay from static map/cache data alone. +- Current best read is that the compared value is mutable actor field `0x63`, not a DTABLE row and not a field that the current scene export already carries. +- A direct Regret DTABLE row check at byte offset `0x63` is not enough to rescue that idea: sampled rows still read as zero there, so the actor key is not simply a persistent `NPCDat` attribute that can be copied through the existing preview pipeline. +- The same corpus also shows why the field is unstable: `TRIGGER.slot_29` / `slot_2B` can rewrite actor field `0x63` on nearby matched NPCs, so the relevant actor-side link id can change after startup. +- The broader family using this same hidden actor-key mechanism is now clearer though: `CRUMORPH`, `NPC_ONLY`, `WATCHNS`, `WATCHEW`, `THRMBCKN`, `THRMBCKE`, `SURCAMNS`, and `SURCAMEW` all compare controller-local bytes against actor field `0x63` in one of their recovered lanes. +- Practical renderer stance stays conservative: keep only the already-evidenced local helper arrows in the overlay, and surface the actor-key behavior in metadata/tooltips until a runtime or spawn-time export can close field `0x63` directly. + `0x04B1` now has a stable `CMD_LINK -> TRIGGER.slot_20` viewer target, and `0x04E3` is already promoted as `SKILLBOX::equip`, so they no longer belong in the unresolved editor-object bucket here. \ No newline at end of file diff --git a/docs/map_renderer/egg-identification.md b/docs/map_renderer/egg-identification.md index 33cdaa9..fe87db1 100644 --- a/docs/map_renderer/egg-identification.md +++ b/docs/map_renderer/egg-identification.md @@ -223,12 +223,15 @@ Scene evidence lines up with that lane in No Remorse map `1`: So the public renderer now treats same-map `shape:542` frame-`0` placements as elevator-link sources when their checked `QLo` values map to a local destination egg id. -## Current Elevator Gap +No Regret now closes one additional elevator lane that the earlier Remorse-only rule missed. -- No Regret map `3` destination egg `102` does not sit on the same currently verified `ELEVATOR` (`shape:542`) or `LIFT` (`shape:307`) lanes. -- Current scene exports for that map show no nearby `shape:542` or `shape:307` objects at all. -- A nearby editor/helper record `item:1056:fixed:1201:0:41758:33694:0` carries `mapNum 102`, but there is not yet enough executable-side evidence to promote that into a viewer arrow rule. -- The map renderer therefore leaves egg `102` unresolved for now instead of hardcoding a speculative elevator source pattern. +- Regret also ships a separate elevator family at `shape:400` (`0x0190`), which the current shape catalog leaves unnamed but the recovered usecode closes as `ELEVATOR` through `ELEVATOR::slot_20`'s local `nearby_items(shape=0x0190, origin=global[0x001e])` scan. +- In Regret `ELEVATOR::gotHit`, the gate is different from the Remorse `shape:542` body: the object must have `QLo >= 100`, and the generic local-elevator lane is `QLo < 0x00c8`. +- That generic Regret lane dispatches `ELEVATOR::slot_20` with the current map and the source object's `QLo` as the destination egg id, while `QHi` still carries the direction/state argument. +- Regret map `3` now has a concrete same-map source for destination egg `102`: `item:664:fixed:400:0:44030:9662:0` with `quality 614` (`QLo 102`, `QHi 2`). +- Current scene exports for that map still show no `shape:542` or `shape:307` objects at all near destination egg `102`, and the nearby `0x04B1` cmd-link helpers instead carry unrelated local keys such as `7`, `9`, `41`, and `60`. + +So the old Regret map-`3` egg-`102` gap is now closed: it is a same-map Regret `shape:400` elevator destination, not an unresolved `shape:542`/`shape:307` case and not a `0x04B1` helper lane. ## Adjacent Editor Objects: `0x04E3` And `0x04B1` @@ -601,9 +604,9 @@ Current best read: - `0x0366` frame `0` is an idle NPC-only trigger pad - `QLo` is the author-selected NPC-group key, but the executable compares it against actor field `0x63`, not against the scene-export `npcNum` or a simple DTABLE row - current scene exports do not expose that actor-side field directly, so there is not yet enough evidence to draw trustworthy `NPC_ONLY -> actor` arrows in the renderer -- a follow-up scene-cache sweep also failed to produce a convincing generic `NPC_ONLY -> 0x04B1` helper pattern; shared-`QLo` local matches look incidental rather than like a dedicated authored link lane +- a decompressed `.cache` scene sweep does show repeated nearby same-`QLo` `0x04B1` cmd helpers in authored Regret maps, so the renderer can now safely expose cautious `NPC_ONLY -> cmd QLo` arrows while still keeping `NPC_ONLY -> actor` arrows out of the overlay -That means the safe editor improvement here is naming and field emphasis, not a target-arrow rule yet. +That means the safe editor improvement here is `NPC_ONLY` naming plus local `cmd QLo` arrows only; the actor-target lane still remains tooltip-only. ### `0x0403` frame `0`: `FLAMEBOX` diff --git a/docs/map_renderer/trigger-usecode-links.md b/docs/map_renderer/trigger-usecode-links.md index 36cbfbf..c6727fe 100644 --- a/docs/map_renderer/trigger-usecode-links.md +++ b/docs/map_renderer/trigger-usecode-links.md @@ -13,6 +13,7 @@ The implementation uses extracted `class_event_index.tsv` results plus existing | `MONITNS` (`0x0102`) | `MONITNS::use` (`slot 0x01`) | Existing gameplay notes tie shape `258` / `0x0102` to a live monitor/computer-adjacent use handler, making it a strong non-editor first-view script target. | | `MONITEW` (`0x0165`) | `MONITEW::use` (`slot 0x01`) | Disasm crosswalks shape `0x0165` to the east-west monitor variant, which keeps the same live computer-adjacent use handler family. | | `PANELNS` (`0x00A1`) | `PANELNS::use` (`slot 0x01`) | Verified panel-switch wrapper for the same nearby trigger-helper chain. | +| `CRUMORPH` (`0x0318`) | `CRUMORPH::equip` (`slot 0x0A`) | Recovered control-transfer pad body scans nearby NPCs for a local-`QLo` control key match, temporarily hands control to the first live hit, and then dispatches `TRIGGER.slot_20` lane `0` or `1`. | | `NPCTRIG` (`0x0363`) | `NPCTRIG::equip` (`slot 0x0A`) | Crosswalked shape/class match; the compact slot-`0x0A` body is still the strongest active-event frontier for this trigger family. | | `CRUZTRIG` (`0x0365`) | `CRUZTRIG::gotHit` (`slot 0x06`) | Disasm crosswalks shape `0x0365` to CRUZTRIG, and `gotHit` is the recovered live body for this trigger/helper family. | | `VMAIL` (`0x0367`) | `VMAIL::slot_0a` (`slot 0x0A`) | Disasm crosswalks shape `0x0367` to VMAIL; slot `0x0A` is the active helper body even though its final semantic label is still weaker than the slot number. | @@ -27,7 +28,7 @@ The implementation uses extracted `class_event_index.tsv` results plus existing | `TIMER` (`0x04C9`) | `TIMER::enterFastArea` (`slot 0x0F`) | Fast-area timer helper; the first active body arms slot `0x20` from qHi enter/leave flags and the packed `mapNum:npcNum` delay payload. | | `SPECIAL` (`0x04CA`) | `SPECIAL::enterFastArea` (`slot 0x0F`) | Fast-area phase helper; the active entry body reads `mapNum` / `npcNum` as phase bytes and `qHi` as the delay byte before fanning out through `TRIGGER.slot_20` and `SPECIAL.slot_21`. | | `TRIGPAD` (`0x04CD`) | `TRIGPAD::gotHit` (`slot 0x06`) | Occupancy/surface-gated trigger-pad logic lives in the recovered `gotHit` body. | -| `NPC_ONLY` (`0x0366`) | `NPC_ONLY::gotHit` (`slot 0x06`) | Active hit-driven helper lane from the extracted class/event table. | +| `NPC_ONLY` (`0x0366`) | `NPC_ONLY::gotHit` (`slot 0x06`) | Active hit-driven helper lane; the body gates on an NPC-only actor key, then brackets `TRIGGER.slot_20` lane `0` / `1` from the pad itself. | | `FLAMEBOX` (`0x0403`) | `FLAMEBOX::equip` (`slot 0x0A`) | Recovered flame-controller body scans nearby flame helper shapes by shared `QLo` and can swap helper markers into live flame actors. | | `SFXTRIG` (`0x04E2`) | `SFXTRIG::slot_0a` (`slot 0x0A`) | Disasm crosswalks shape `0x04E2` to the compact event-bearing SFXTRIG helper; slot `0x0A` is the stable active body even though a precise semantic label is still weaker than the slot number. | | `DEATHBOX` (`0x04E7`) | `DEATHBOX::slot_0a` (`slot 0x0A`) | The recovered helper body matches death-link `QLo` and forwards NPC death events into `TRIGGER` lanes, so opening the helper body is now more useful than leaving the shape unmapped. | @@ -52,8 +53,26 @@ That is why the viewer opens `TRIGGER.slot_20` for pinned `0x04B1` helpers inste - Pinned controller objects and the small set of promoted gameplay objects now expose a `USECODE` action in the tooltip. - The action switches the workspace to the USECODE tab. - The USECODE viewer resolves the exact class/slot target against the generated cache index instead of relying on fuzzy filename search. +- `CRUMORPH` and `NPC_ONLY` now also participate in the same cautious local `... -> cmd QLo ...` overlay rule used for other `TRIGGER.slot_20` controller families, but only for nearby `0x04B1` helpers that actually share the source object's low `quality` byte. - `0x0011` usecode-trigger eggs now decode their `npcNum` nibble-packed X/Y ranges, resolve `QLo` into the authored family-4 class, open the matching subtype body in the USECODE tab, and draw arrows only for the narrower subtype families whose local target scans are actually recovered. +## Actor-Key Family Blocker + +- The current static scene/cache export still cannot support trustworthy `controller -> actor` arrows for the Regret actor-key family. +- The strongest current reason is that the compared value is mutable actor field `0x63`, not a stable DTABLE row or an already-exported scene field. +- A direct Regret DTABLE byte check on record offset `0x63` is not enough to close that gap: sampled rows are still zero there, so the actor key is not just a plain `NPCDat` byte copied into the runtime actor. +- The same recovered corpus shows why the value is unstable: `TRIGGER.slot_29` / `slot_2B` can rewrite actor field `0x63` on nearby matched NPCs, which means the practical link id can change after the map loads. +- Current safest viewer stance is therefore: keep actor-key families named and tooltip-decoded, allow only the already-evidenced local helper arrows, and leave actor-target arrows disabled until a runtime or spawn-time export closes field `0x63` directly. + +### Known Actor-Key Families + +- `CRUMORPH` (`0x0318`) compares nearby actor field `0x63` against the pad `QLo` before transferring control and bracketing `TRIGGER.slot_20`. +- `NPC_ONLY` (`0x0366`) compares the incoming NPC-like source's actor field `0x63` against the pad `QLo` before bracketing `TRIGGER.slot_20` lane `0` / `1`. +- `WATCHNS` / `WATCHEW` (`0x04c6` / `0x04de`) have a stronger current local `0x0510` post lane in the viewer, but their deeper watcher body also checks nearby actor field `0x63` against controller `QLo`. +- `THRMBCKN` / `THRMBCKE` (`0x0566` / `0x0567` classes) compare nearby Thermatron actor field `0x63` against controller `QLo`. +- `SURCAMNS` / `SURCAMEW` also scan nearby NPCs by actor field `0x63` and controller `QLo` in their camera/control lane. +- `TRIGGER.slot_29` / `slot_2B` are part of the same ecosystem because one subcommand explicitly rewrites actor field `0x63` on matched nearby NPCs. + `0x04F8` remains intentionally outside the `USECODE` target list for now. The current evidence says it is a destroyable-door helper scanned by `DOOR.slot_23`, not a proven standalone usecode class the viewer should open directly. ## Newly Decoded Field Notes @@ -157,14 +176,24 @@ No currently unresolved Remorse-only editor rows remain in this note after the ` |---|---|---| | `0x00cf` | `HAND` | Needs examination for usecode-link integration | | `0x01d6` | `MUTANT_HOOK_CONTROL` | Needs examination for usecode-link integration | -| `0x0451` | `GIMP_DISPENSER` | Needs examination for usecode-link integration | -| `0x0510` | `SECRET_DOOR_POST` | Needs examination for usecode-link integration | +| `0x0451` | `CRAZYEW` | Integrated: `CRAZYEW::gotHit` as a Regret-only NPC wake-up relay; tooltip now treats it as a hit-driven controller rather than a generic editor placeholder. | +| `0x0510` | `SECRET_DOOR_POST` | Integrated as a local arrow target for nearby `WATCHNS` / `WATCHEW` controllers that match it by `QLo`; no separate direct usecode body promoted yet. | | `0x0548` | `SECRET_DOOR_SWITCH` | Needs examination for usecode-link integration | -| `0x056d` | `STEAM_COLLISION_SWITCH` | Needs examination for usecode-link integration | -| `0x05ae` | `VOLCANO_CONTROLLER` | Needs examination for usecode-link integration | -| `0x05df` | `PRESSURE_BARRIER_V` | Needs examination for usecode-link integration | -| `0x05e0` | `PRESSURE_BARRIER_H` | Needs examination for usecode-link integration | -| `0x05e1` | `PRESSURE_BARRIER_SWITCH` | Needs examination for usecode-link integration | +| `0x056d` | `VIDEOBOX` | Integrated: `VIDEOBOX::equip` as the recovered Regret-only gated controller body. | +| `0x05ae` | `CRAZYNS` | Integrated: `CRAZYNS::gotHit` as a Regret-only NPC wake-up relay; tooltip now treats it as a hit-driven controller rather than a generic editor placeholder. | +| `0x05df` | `PRESSURE_BARRIER_V` | Integrated as a local arrow target for nearby `CRYOBOX` controllers that match it by `QLo`. | +| `0x05e0` | `PRESSURE_BARRIER_H` | Integrated as a local arrow target for nearby `CRYOBOX` controllers that match it by `QLo`. | +| `0x05e1` | `CRYOBOX` | Integrated: `CRYOBOX::equip` plus local `QLo` arrows to nearby `0x05DF` / `0x05E0` pressure-barrier faces. | + +### Regret-Only Batch: Watchers, Cryobox, And Wake-Up Relays + +- `0x04c6` and `0x04de` are no longer anonymous shared editor rows in Regret scenes. The recovered corpus names them `WATCHNS` and `WATCHEW`, and both `slot_20` bodies scan nearby `shape=0x0510` placements before bracketing `TRIGGER.slot_20` around their watcher-specific follow-up lane. +- The scene-cache cross-check supports a cautious viewer arrow rule here. Across Regret maps `1`, `10`, `13`, `14`, `15`, `16`, `18`, `200`, `201`, `215`, `29`, `30`, and others, placed `WATCHNS` / `WATCHEW` objects repeatedly sit within local helper range of `0x0510` posts and share the same low quality byte even when the raw 16-bit quality differs. +- `0x0510` therefore belongs in the editor as a local secret-door post/helper target, not as an unresolved generic editor placeholder. The recovered watcher body only treats `qHi == 0` posts as the text/door-side lane, so the current viewer promotion stays conservative and only adds the local arrow plus tooltip decoding. +- `0x05e1` is now closed as `CRYOBOX`, not a vague pressure-barrier switch. Its `equip` body matches nearby `0x05DF` and `0x05E0` shapes by shared `QLo`, then hands off into `slot_20` / `slot_21` worker lanes that wait on animation state, flip `ITEM` control slots, and spawn the steam worker path. +- The paired faces `0x05DF` and `0x05E0` remain useful human-facing labels as `PRESSURE_BARRIER_V` and `PRESSURE_BARRIER_H`, but they no longer belong in the unresolved bucket. In the viewer they are now arrow targets of nearby `CRYOBOX` controllers instead of unlabeled editor debris. +- `0x0451` and `0x05AE` are now closed as `CRAZYEW` and `CRAZYNS`. The recovered `gotHit` bodies are small but concrete: when the incoming hit source is an actor handle (`>= 0x00FF`), they check `NPC.slot_2A` and, unless the target is already in activity `12`, spawn `NPC.slot_2C` to wake or re-arm that actor. That is enough to classify them as hit-driven NPC wake-up relays rather than dispensers or volcano-only map art. +- `0x056D` is also no longer an unresolved `STEAM_COLLISION_SWITCH`. The recovered class is `VIDEOBOX`, and its `equip` body is a thin global-latch gate that either falls straight into `ITEM.slot_21` or runs a short scripted helper loop first. That is enough for a direct usecode-view target even though the higher-level gameplay meaning is still thinner than the `WATCH*` and `CRYOBOX` lanes. ## Remaining Steps @@ -173,14 +202,14 @@ The next map-viewer USECODE passes should stay evidence-backed and prioritize it ### Highest Priority 1. Extend the `0x0011` subtype table beyond the currently promoted `TRIGEGG` / `ONCEEGG`, `FLOOR1`, `MHATCHER`, `DOOREGG`, `MISS1*`, and `VIDEOEGG` lanes only when the recovered pseudocode justifies a reusable viewer target or arrow rule. -2. Do the `SURCAMNS` / `SURCAMEW` placement crosswalk so the renderer can decide whether placed `0x04c6` / `0x04de` objects deserve a direct USECODE jump or should remain callback-holder-only families. +2. Revisit the remaining Regret-only door-side helpers around `WATCHNS` / `WATCHEW`, especially `0x0548`, to decide whether they form a second stable secret-door lane beyond the now-promoted `0x0510` post targets. 3. Finish the remaining `CMD_LINK` field write-up: the current tooltip now decodes `quality`, `mapNum`, and `npcNum`, but `nextItem` still lacks a stable standalone semantic beyond appearing in authored controller records. ### Catalog And Viewer Cleanup 1. Sweep the remaining shared editor/controller shapes in the catalog table and promote the next solid names instead of leaving `(unnamed)` placeholders where the disasm or extracted corpus already gives a stable class anchor. 2. Revisit the `0x05b2`-`0x05be` music-controller cluster and decide whether those shapes belong in the USECODE viewer backlog, a scene-audio note, or both. -3. Recheck the Regret-only controller shapes (`HAND`, `MUTANT_HOOK_CONTROL`, `GIMP_DISPENSER`, `SECRET_DOOR_*`, `STEAM_COLLISION_SWITCH`, `VOLCANO_CONTROLLER`, `PRESSURE_BARRIER_*`) against the extracted class/event table before adding any direct links. +3. Recheck the remaining unresolved Regret-only controller shapes (`HAND`, `MUTANT_HOOK_CONTROL`, `SECRET_DOOR_SWITCH`) against the extracted class/event table before adding any direct links. ### Gameplay Coverage Extensions diff --git a/docs/removed_items.md b/docs/removed_items.md index a7aa777..3104091 100644 --- a/docs/removed_items.md +++ b/docs/removed_items.md @@ -137,8 +137,8 @@ The broader Remorse catalog does contain more unused-looking or obviously non-re ### Placeholder cube family -- The strongest additional item-adjacent candidates from the catalog/scene pass are placeholder cube entries: - - `0x0251` = `PLACEHOLDER_KEY_CUBE` +- `0x0251` no longer fits the generic placeholder bucket well. Current best read is `VALUEBOX`, a local data/helper box used by monitors, watcher panels, security displays, and keypads. +- The strongest remaining placeholder-cube entries from the catalog/scene pass are: - `0x0318` = `PLACEHOLDER_CUBE` - `0x0337` = `PLACEHOLDER_CUBE_BIG` - `0x0361` = `PLACEHOLDER_CUBE_RED_BLACK` diff --git a/docs/retail-debugger-patch-attempts.md b/docs/retail-debugger-patch-attempts.md index 7b9cfc9..19c1199 100644 --- a/docs/retail-debugger-patch-attempts.md +++ b/docs/retail-debugger-patch-attempts.md @@ -26,6 +26,76 @@ Purpose: | Constructor writes `0x65ab` to object `+0` | `1408:0024..0028` | | `1478:65ab` is method 0 and `1478:65af` is method 1 of the same vtable | `CALLF [BX]` and `CALLF [BX+4]` dispatch paths | +## Minimum Viable Patch Floor (2026-04-03) + +Fresh live-Ghidra re-checks tighten the lower bound on what an executable-side debugger-unlock patch must do. + +What the live binary still proves: +- `1408:0000` is only a constructor. It allocates/initializes the seg1408 debugger object, seeds object `+0` with `0x65ab`, and returns the far pointer in `DX:AX`; it does **not** store that pointer into `1478:659c/659e`. +- current instruction searches still show reads of `1478:659c/659e` in seg13a0 and seg1418, but no recovered retail writer that seeds those globals before the interpreter hook runs. +- raw debugger-adjacent data at `1478:65ab/65af` still resolves to the two inert retail callbacks `1408:046f` and `1408:0474`; there is no hidden already-live UI-opening target sitting behind the stock vtable. +- the interpreter-side pre-call guard at `1418:049e..04b5` only checks whether `1478:659c/659e` is non-null before calling `1408:0053`. The one-shot breakpoint/step gating lives inside `1408:0053`, not at the callsite itself. +- both UI wrappers still inherit caller words at `13a0:008f` / `13a0:024a`, so any deferred entry that reuses those wrappers from a non-native caller still needs wrapper-argument sanitization. + +That rules out the common "there must be one tiny direct jump" theories: +- a constructor-only retarget is insufficient because the returned far pointer still has to be stored into `1478:659c/659e` somewhere. +- a vtable-dword-only patch is insufficient because the retail callbacks at `1478:65ab/65af` are inert shared stubs, and the previously tested shared-slot rewrites already crashed at startup. +- a direct `1418:04b5 -> 13a0:020d/0086` retarget is insufficient because once `1478:659c/659e` becomes non-null, that callsite would fire on every eligible interpreter pass unless a private one-shot stub preserves the original `1408:0053` gating semantics. + +Current best conclusion: +- the smallest structurally defensible patch family is still the current interpreter-callsite-retarget design (Candidates O/P): one embedded `13e8:230d..232d` body that lazily creates/stores/arms the debugger object, one retarget of `1418:04b5` into the private stub, and one wrapper-argument sanitization site (`13a0:024a` or `13a0:008f`). +- anything smaller than that is currently missing at least one required behavior: object creation, global pointer seeding, one-shot deferred gating, or safe wrapper arguments. + +## Practical Candidate Byte Maps + +These are the current live-candidate edits in practical patching terms. + +Important NE note: +- for internal far calls, the on-disk opcode bytes stay as placeholder `FF FF 00 00` +- the real old/new target change is in the NE fixup entry, not in the immediate bytes themselves +- so for those sites below, `raw bytes` may be unchanged while the `fixup target` changes + +### Common edits shared by Candidate O and Candidate P + +| File offset | Live address | What changes | Old value | New value | +|-------------|--------------|--------------|-----------|-----------| +| `0x0C970D` | `13e8:230d` | Replace the retail `0x410` body with the corrected private bootstrap/stub body | `A0 4F 60 B4 00 F7 D8 1B C0 40 A2 4F 60 80 3E 4F 60 00 74 47 6A FF 6A FF C4 1E D0 4C 26 8A 47 05 50 1E 68 D2 60 6A 00 6A 00 83 EC 06 C7 86 76 FF 00 00 8B 86 76 FF F7 D0 89 86 78 FF C6 86 7A FF 00 6A 00 6A 00 9A FF FF 00 00 83 C4 14 52 50 9A FF FF 00 00 83 C4 08 5F 5E C9 CB 6A FF 6A FF C4 1E D0 4C 26 8A 47 05 50 1E 68 EE 60 6A 00 6A 00 83 EC 06 C7` | `A1 9C 65 0B 06 9E 65 74 10 C4 1E 9C 65 C6 47 75 00 C6 47 74 01 5F 5E C9 CB 6A 00 6A 00 E9 25 00 55 8B EC A1 9C 65 39 46 06 75 16 A1 9E 65 39 46 08 75 0E C4 5E 06 C7 47 74 00 00 6A 00 6A 00 EB 0E 5D CB 90 90 9A FF FF 00 00 83 C4 04 EB 0A 9A FF FF 00 00 83 C4 04 5D CB 0B C2 74 13 A3 9C 65 89 16 9E 65 89 C3 8E C2 C6 47 75 00 C6 47 74 01 5F 5E C9 CB` | +| `0x0C9753` | `13e8:2352` | First reused far-call fixup inside the patched `0x410` body | fixup target `1350:0046` | fixup target `1408:0000` | +| `0x0CFAB5` | `1418:04b5` | Interpreter debugger callsite retarget | raw bytes `9A FF FF 00 00`; fixup target `1408:0053` | raw bytes `9A FF FF 00 00`; fixup target `13e8:232d` | + +### Candidate O only + +| File offset | Live address | What changes | Old value | New value | +|-------------|--------------|--------------|-----------|-----------| +| `0x0C975D` | `13e8:235c` | Second reused far-call fixup inside the patched `0x410` body | raw bytes `FF FF 00 00`; fixup target `1320:1588` | raw bytes `FF FF 00 00`; fixup target `13a0:020d` | +| `0x0B9C48` | `13a0:0248` | Zero inherited modal-wrapper caller words while preserving the leading local default `PUSH 0` | `6A 00 FF 76 08 FF 76 06` | `6A 00 6A 00 6A 00 90 90` | + +Practical meaning: +- the patched `0x410` body now creates/stores the debugger object if needed, or reuses the live one and arms break-next +- the interpreter callback at `1418:04b5` no longer enters `1408:0053` directly; it enters the private stub at `13e8:232d` +- that private stub eventually uses the repointed second far-call slot at `13e8:235c` to open `usecode_debugger_open_modal` + +### Candidate P only + +| File offset | Live address | What changes | Old value | New value | +|-------------|--------------|--------------|-----------|-----------| +| `0x0C975D` | `13e8:235c` | Second reused far-call fixup inside the patched `0x410` body | raw bytes `FF FF 00 00`; fixup target `1320:1588` | raw bytes `FF FF 00 00`; fixup target `13a0:0086` | +| `0x0B9A8D` | `13a0:008d` | Zero inherited current-unit-wrapper caller words while preserving the leading mode byte `PUSH 1` | `6A 01 FF 76 08 FF 76 06` | `6A 01 6A 00 6A 00 90 90` | + +Practical meaning: +- same bootstrap and interpreter-callsite retarget as Candidate O +- the only behavioral difference is the final UI target: this one routes to `usecode_debugger_open_for_current_unit` instead of the generic modal wrapper + +### Restore values for the current live-candidate family + +If reverting O/P back to retail, these are the practical targets: +- `0x0C970D` / `13e8:230d`: restore the original retail `0x410` body bytes shown above +- `0x0C9753` / `13e8:2352`: restore fixup target `1350:0046` +- `0x0CFAB5` / `1418:04b5`: restore fixup target `1408:0053` +- `0x0C975D` / `13e8:235c`: restore fixup target `1320:1588` +- `0x0B9C48` / `13a0:0248`: restore `6A 00 FF 76 08 FF 76 06` if Candidate O was active +- `0x0B9A8D` / `13a0:008d`: restore `6A 01 FF 76 08 FF 76 06` if Candidate P was active + ## Attempt Log | ID | Patch shape | Mechanical result | Runtime result | Verdict | diff --git a/docs/usecode-roundtrip-ir.md b/docs/usecode-roundtrip-ir.md index 13a8f4f..3050ca4 100644 --- a/docs/usecode-roundtrip-ir.md +++ b/docs/usecode-roundtrip-ir.md @@ -842,4 +842,12 @@ That gets to a reversible editor sooner than waiting for a full semantic VM reco - **Renderer Fix:**: [src/lib/usecode-decompiler.js](k:/ghidra/crusader_map_viewer/map_renderer/src/lib/usecode-decompiler.js) now recognizes that stacked-shape whitelist selector and emits readable loops such as `for roof in nearby_items(shapes=[...], distance=(100 * 32), origin=arg_06)` instead of collapsing back to `while (condition)`. - **Readability Impact:**: The cached Remorse and Regret `CHANGER.slot_07` pseudocode bodies now expose the actual nearby-roof selector inputs directly: the hardcoded roof-shape whitelist, the recovered `3200`-unit range, and the egg-origin scan. That makes the later `Item.getQLo(...) == eggId` destroy branch legible without a raw-byte fallback pass. - **Editor Impact:**: The same selector close justified promoting Regret `QLo 8 -> CHANGER` from tooltip-only metadata into the map editor overlay. The viewer can now expose the same local roof-target lane for Regret that was already proven for Remorse, using the recovered Regret whitelist and the same `3200`-unit scan distance. -- **Regression Coverage:**: [scripts/test-usecode-structuring.mjs](k:/ghidra/crusader_map_viewer/map_renderer/scripts/test-usecode-structuring.mjs) now adds one synthetic regression for the `loopscr 0x24/0x4c` stacked-shape selector and one real-data regression for Regret `CHANGER.slot_07`, so future renderer changes fail if this selector family falls back to opaque loop output again. \ No newline at end of file +- **Regression Coverage:**: [scripts/test-usecode-structuring.mjs](k:/ghidra/crusader_map_viewer/map_renderer/scripts/test-usecode-structuring.mjs) now adds one synthetic regression for the `loopscr 0x24/0x4c` stacked-shape selector and one real-data regression for Regret `CHANGER.slot_07`, so future renderer changes fail if this selector family falls back to opaque loop output again. + +## **Recent Renderer Work (2026-04-03, switch literal normalization)** + +- **Root Cause Closed:**: `FLICTEST.slot_20` exposed a literal-formatting seam in the JS renderer. The compare ladder crosses the byte/word immediate boundary at `127 -> 128`, so the decompiler was structuring the ladder into one `switch` but was preserving the raw operand spelling from each compare. That produced mixed case labels such as `case 127:` followed by `case 0x0080:` and `case 0x0081:` even though the selector is one small ordinal lane. +- **Renderer Fix:**: [src/lib/usecode-decompiler.js](k:/ghidra/crusader_map_viewer/map_renderer/src/lib/usecode-decompiler.js) now normalizes switch case labels after selector-chain recovery. When every case label is a non-negative integer in the small ordinal range (`0..255`), the renderer canonicalizes the labels to decimal before emitting the final `switch`. +- **Heuristic Boundary:**: The normalization is intentionally narrow. It applies only to structured `switch` output, and only when every case label parses as a small ordinal integer. Larger identifier-like constants such as shape ids (`0x053a`) are left in hex so the renderer does not erase domain-significant formatting. +- **Readability Impact:**: FLICTEST-style keypad/movie dispatch ladders now render as consistent ordinal switches across the byte/word immediate boundary, so cases `128+` appear as `128`, `129`, and so on rather than width-leaking `0x0080`, `0x0081`, etc. +- **Regression Coverage:**: [scripts/test-usecode-structuring.mjs](k:/ghidra/crusader_map_viewer/map_renderer/scripts/test-usecode-structuring.mjs) now includes one synthetic regression for the mixed `127/0x0080/0x0081` selector ladder and one guard that keeps larger ID-style switch cases in hex. \ No newline at end of file diff --git a/docs/usecode-startup-override.md b/docs/usecode-startup-override.md index be435fd..00f327c 100644 --- a/docs/usecode-startup-override.md +++ b/docs/usecode-startup-override.md @@ -53,6 +53,145 @@ The remaining uncertainty is narrower now: - whether it can also be a loader alias/resource root that `Filespec_GetFullPath` understands - whether relative, absolute, and CD-backed forms are all accepted equally +What is now materially tighter from the live `Filespec_GetFullPath` and `File_Exists` implementations: + +- the path is interpreted with DOS file APIs, not Windows host-path APIs +- the builder uses the literal separator string `"\\"`, not `"/"` +- the parser-side `s_usecode` buffer is only `0x1e` bytes long, so the raw `-u` token is truncated to at most `29` visible characters plus the terminator + +That makes the safest current syntax guidance: + +- prefer DOS-style backslashes, not forward slashes +- prefer short path tokens +- assume the path must exist inside the game's DOS-visible filesystem view + +If the game is running under DOSBox or a similar DOS environment, `C:\MADDOCODE` refers to the guest DOS `C:` drive, not automatically to the Windows host `C:` drive. + +One more practical constraint is now strong enough to call out explicitly: + +- the `-u` existence probe uses an old DOS find-first style path, so `8.3`-safe directory names are the safest override roots + +For the current GOG install used in this investigation, Windows reports the short alias: + +- `MADDOCODE` -> `MADDOC~1` + +So the highest-probability next token for this specific setup is: + +- `-u MADDOC~1` + +or the explicit relative form: + +- `-u .\MADDOC~1` + +## Why `-u FLICTEST.FLX` Fails + +The live helper shape is now tight enough to explain the failed `FLICTEST` attempts directly. + +What retail `CRUSADER.EXE` does at `1420:0cf0..0d33` is: + +- read the copied argv token from `1478:065a` +- load the fixed filename template through `1478:06d6/06d8 -> 1478:07a0` +- use that template as `eusecode.flx` +- call `Filespec_GetFullPath(0, , "eusecode.flx", 0)` + +The raw data bytes at `1478:079a` confirm the fixed template directly: + +- `65 75 73 65 63 6f 64 65 2e 66 6c 78 00` +- ASCII = `eusecode.flx\0` + +So these example invocations do **not** mean `load this exact archive file`: + +- `-u USECODE/FLICTEST.FLX` +- `-u FLICTEST.FLX` + +They instead mean, in the current safest static interpretation: + +- use `USECODE/FLICTEST.FLX` as the `path` component and still append the fixed filename `eusecode.flx` +- or use `FLICTEST.FLX` as the `path` component and still append the fixed filename `eusecode.flx` + +That makes both attempts wrong for a direct single-file override. The game is not being told to open your `FLICTEST.FLX` archive as the final filename at all. + +## Safest Current Experiment Shape + +The current safest static-model experiment is therefore: + +1. create a directory that will act as the override root +2. place a structurally valid replacement archive in that directory +3. name that archive `EUSECODE.FLX` +4. pass the directory path to `-u`, not the archive filename + +Examples that now fit the recovered helper better are: + +- `-u USECODE` +- `-u .\\USECODE` +- `-u \USECODE` +- `-u C:\USECODE` +- `-u MOD_UC` + +with the important condition that the target directory must contain `EUSECODE.FLX`, not `FLICTEST.FLX`. + +The safest current path interpretations for those examples are: + +- `-u MADDOCODE` -> tries `MADDOCODE\EUSECODE.FLX` relative to the game's current DOS working directory +- `-u .\MADDOCODE` -> tries `.\MADDOCODE\EUSECODE.FLX` +- `-u \MADDOCODE` -> tries `\MADDOCODE\EUSECODE.FLX` on the current DOS drive +- `-u C:\MADDOCODE` -> tries `C:\MADDOCODE\EUSECODE.FLX` on DOS drive `C:` + +The current safest negative guidance is: + +- do not use forward slashes like `USECODE/FLICTEST.FLX` +- do not pass the archive filename itself as the `-u` token +- do not assume a long absolute host path will survive the `0x1e`-byte parser buffer intact +- do not assume a directory name longer than classic `8.3` DOS spelling will be accepted exactly as typed + +## Expected Visible Output + +There is currently no evidence that retail `-u` prints a success or failure banner before the game loads. + +What the live code shows: + +- the `-u` parser case at `1048:0a46` only copies the token into `1478:065a` +- unlike `-debug`, `-warp`, `-skill`, `-mapoff`, `-egg`, and `-demo`, that parser case does **not** call `ConsolePrintf` +- `startup_apply_u_override_if_present` also does not call `ConsolePrintf` +- if `File_Exists` says the constructed path is missing, the helper just returns and startup continues + +So the safest current user-facing expectation is: + +- no dedicated `-u` console line on success +- no dedicated `-u` console line on file-not-found failure +- if the override path is wrong, the game most likely falls back silently to stock usecode + +That means `no message appeared before the game loaded` is currently normal and does **not** prove the override worked. + +## Strongest Current Diagnosis For The `MADDOCODE` Test Case + +The current install-side evidence makes this case much tighter than a generic `-u` experiment. + +What is now verified in the GOG install: + +- `MADDOCODE` contains `EUSECODE.FLX`, `OVERLOAD.DAT`, `UNKCOFF.DAT`, and `UNKDS.DAT` +- `OVERLOAD.DAT`, `UNKCOFF.DAT`, and `UNKDS.DAT` in `MADDOCODE` are byte-identical to the working copies in `USECODE` +- only `EUSECODE.FLX` differs between the stock `USECODE` folder and `MADDOCODE` + +That makes the A/B interpretation unusually clean. + +If: + +- replacing `USECODE\EUSECODE.FLX` with the hacked file produces the expected obvious gameplay breakage +- but launching with `-u MADDOCODE` produces stock gameplay instead + +the strongest current read is **not** `the alternate root loaded but behaved differently`. The stronger current read is: + +- the alternate root was probably never selected +- startup most likely fell back silently to stock usecode after the override path probe failed + +Given the recovered code and the current install layout, the leading practical failure mode is now: + +- the DOS-side file probe did not accept `MADDOCODE` exactly as typed +- the short alias `MADDOC~1` is the next highest-probability working token + +This is still a static-analysis conclusion rather than a runtime-tested acceptance matrix, so the exact relative-path and alias forms remain open. But the filename side is no longer the main uncertainty: the recovered helper is strongly telling us `directory/root override for EUSECODE.FLX`, not `arbitrary filename override`. + External engine code reinforces the fixed-filename part of this read. Both Pentagram and ScummVM build the default Crusader main usecode path as `usecode/` + language letter + `usecode.flx`, and for Remorse/Regret the language-usecode letter is `'e'`, which matches the retail helper's forced `eusecode.flx` template. ## Why This Looks Like Replacement, Not Addition @@ -284,6 +423,8 @@ For current tooling planning, the defensive assumption should be: - prepare a structurally complete replacement source that satisfies the loader's normal expectations - do not assume the game will merge one partial class file into the stock runtime for us +That also means a standalone archive like `FLICTEST.FLX` is currently a poor first experiment even if its internals are valid. The live retail loader path is much more likely to accept a complete replacement `EUSECODE.FLX` rooted under a directory passed to `-u`. + ## Best Current Usefulness For Decompilation Work This path is interesting for the current usecode-decompilation lane for two reasons. diff --git a/docs/usecode/flictest-investigation.md b/docs/usecode/flictest-investigation.md new file mode 100644 index 0000000..ed4a051 --- /dev/null +++ b/docs/usecode/flictest-investigation.md @@ -0,0 +1,402 @@ +# FLICTEST usecode investigation + +## Verdict + +`FLICTEST` is real shipped usecode, and it is absolutely movie-related. + +The strongest current reading is: + +- `FLICTEST` is a movie playback helper/jukebox class, not a normal authored world-object class that maps place directly on maps. +- It is reachable through other shipped usecode classes. +- Reachability differs by game: Remorse keeps a much larger movie-browser helper, while Regret keeps a smaller helper and uses it through a handful of specific caller scripts. +- In Regret specifically, the strongest surviving route is not just a generic helper call. It sits behind a hidden `KEYPADNS` menu path that explicitly exposes both a `Cheaters Menu` branch and a `VIDEO PLAYER` branch. + +I did not find evidence that any placed map item or egg resolves directly to the `FLICTEST` class itself. The evidence instead points to `FLICTEST` being spawned as a helper from other classes. + +## Core class evidence + +Remorse extracted class metadata: + +- `USECODE/EUSECODE_extracted/class_event_index.tsv` +- `entry_index = 402`, `object_index = 0xA22`, `class_id = 0x0A20`, `class_name_hint = FLICTEST` +- only slots `0x20` and `0x21` are populated +- slot `0x20`: body `0x00E0..0x279F`, length `9919` +- slot `0x21`: body `0x279F..0x4E4C`, length `9901` + +Regret extracted class metadata: + +- `USECODE/REGRET/REGRET_USECODE_extracted/class_event_index.tsv` +- `entry_index = 456`, `object_index = 0xA0C`, `class_id = 0x0A0A`, `class_name_hint = FLICTEST` +- only slots `0x20` and `0x21` are populated +- slot `0x20`: body `0x00E0..0x0CA4`, length `3012` +- slot `0x21`: body `0x0CA4..0x0CAF`, length `11` + +That already looks less like an ordinary object family and more like a dedicated helper with anonymous high-slot entry points. + +## What the bodies do + +### Remorse + +`map_renderer/.cache/usecode/remorse/EUSECODE/pseudocode/FLICTEST/slot_20_slot_20.txt` is a keypad-driven movie browser. + +- It calls `KeypadGump.showKeypad(0)`. +- It switches on the entered number. +- Nearly every case calls `playFlic(...)` with a short movie name such as `01a`, `04h`, `m03`, `mva01`, `o01`, `t02`, `test`, `z1`, or `wec`. +- The helper includes special names such as `hideoutx`, many `mva*` clips, and an explicit `test` clip. + +`map_renderer/.cache/usecode/remorse/EUSECODE/pseudocode/FLICTEST/slot_21_slot_21.txt` is a second movie dispatcher that takes the movie selector from `arg_10` instead of reading a keypad itself. In other words, Remorse ships both: + +- an interactive movie-picker entry point in `slot_20` +- a parameter-driven dispatcher in `slot_21` + +That is consistent with a general movie test or movie browser utility. + +### Regret + +Regret still ships `FLICTEST`, but the helper is cut down. + +- `slot_20` remains a real body. +- `slot_21` is only: + +```c +function flictest_slot_21() +{ + set_info(1, *(arg_06)); + return; +} +``` + +So Regret preserves the helper class but no longer keeps the large second dispatch body that Remorse has. + +## Where it is used + +### Remorse: `EVENT::equip` + +The clearest Remorse use is inside the shipped `EVENT` controller family. + +Evidence: + +- `USECODE/EUSECODE_extracted/class_event_index.tsv` shows `EVENT` slot `0x0A` as the single large active body. +- `docs/map_renderer/trigger-usecode-links.md` already treats `EVENT` as a shipped controller family and maps shape `0x0361` to `EVENT::equip`. +- `map_renderer/Catalogs/usecode_shape_catalog_remorse.csv` names shape `0x0361` as `EVENT_CONTROLLER`. + +Direct caller snippet from `map_renderer/.cache/usecode/remorse/EUSECODE/pseudocode/EVENT/slot_0A_equip.txt`: + +```c +case 0x00fa: + spawn FLICTEST.slot_20(pid, flicMan); + suspend; + return; +``` + +This is strong evidence that Remorse's event-controller ecosystem can launch `FLICTEST` as a helper, using an event-local `flicMan` value as the selector. + +Current best read: Remorse uses `FLICTEST` as a generic movie-playback utility behind at least some `EVENT`-driven scripted situations. + +### Remorse: current access status + +The newer comparison pass makes Remorse look much less recoverable than Regret. + +- The only confirmed Remorse `spawn FLICTEST.slot_20(...)` call found in the extracted pseudocode is the `EVENT::equip` `case 0x00fa` branch above. +- Remorse `KEYPADNS::use` is only the standard keypad wrapper. It has no `VIDEO PLAYER` or `Cheaters Menu` string path. +- Remorse `DATALINK::use` does not spawn `FLICTEST`. Instead it plays the campaign MVA clips directly with `playFlicsomething(...)` and then shows mission-objective text. + +That means the current best No Remorse answer is: + +- `FLICTEST` exists and its keypad browser body is real. +- But I did not find a shipped player-facing keypad, monitor, or datalink route that opens that browser directly. +- The only current workspace evidence for launching the browser in Remorse is the generic `EVENT` controller case `0x00fa`, and I do not yet have a verified placed world route for that case. + +The latest map pass strengthens that answer with actual `KEYPADNS` placement data: + +- In the generated Remorse scene export, the only placed `shape:1099` (`0x044b`, `KEYPADNS`) records I found are all on map 1. +- Those three records are ordinary `frame 0` keypad objects with full quality values `267`, `832`, and `41997`. +- Their local low bytes are therefore `0x0b`, `0x40`, and `0x0d`, not the Regret-style hidden selector values `0` / `1`. +- Remorse `KEYPADNS::use` has no `VIDEO PLAYER` or `Cheaters Menu` branch anyway, so these map-1 placements currently read as normal gameplay keypads rather than hidden `FLICTEST` terminals. +- A broader exported-map scan also found no placed `shape:1236` (`0x04d4`, `DATALINK`) records in the rendered Remorse map exports, which fits the usecode-side picture that the shipped datalink path is inventory/startup-facing rather than a placed world terminal route. +- The exported `shape:865` (`0x0361`, `EVENT_CONTROLLER`) placements I found are also all on map 1, and sampled controllers are not colocated with the three `KEYPADNS` objects above. + +So, unlike Regret, No Remorse currently looks like `movie-browser helper present in data, but no confirmed shipped user-facing access path`. + +## Remorse datalink hack-in patch + +The minimal reversible Remorse hack is a 2-byte `EUSECODE.FLX` edit, not an executable rewrite. + +### Why the patch moved out of `CRUSADER.EXE` + +The first executable-side `Item_Use` hook was functionally plausible, but it changed too many bytes in a relocation-sensitive path and the user reported that build crashed on startup. + +That made the right bar much stricter: + +- avoid `CRUSADER.EXE` entirely if possible +- avoid far-call rewrites +- change the smallest stable token already present in shipped data + +The better target turned out to be the tail of Remorse `DATALINK::use` inside `USECODE\EUSECODE.FLX`. + +### Why I did not use a direct class-event row swap + +I checked the raw Remorse `EUSECODE.FLX` class-event rows directly. + +- `DATALINK slot 0x01 use` row bytes are `B4 13 01 00 00 00` +- `FLICTEST slot 0x20` row bytes are `BF 26 01 00 00 00` + +Those are not global cross-class code pointers. The extracted parser and raw headers show they are class-local metadata: + +- each row is parsed as `` +- the code body is resolved relative to the current class chunk +- copying a `FLICTEST` row into `DATALINK` would still resolve inside the `DATALINK` chunk + +So a row swap would not produce a real `DATALINK -> FLICTEST` redirect. + +### Final patch shape + +The validated patch site is the final spawned callee token in Remorse `DATALINK::use`. + +Pseudocode evidence from the extracted Remorse cache: + +```c +spawn TEXTFILE.slot_20(pid, textFile, arg_06); +suspend; +``` + +Raw file evidence from the installed retail `USECODE\EUSECODE.FLX` near the end of the `DATALINK` body: + +```text +04 59 41 FE 40 06 57 02 02 17 0A 20 00 65 00 6E FE 5E 54 01 01 12 53 5C +``` + +The important token is the class id in the final spawn: + +- `17 0A` = `TEXTFILE` (`0x0A17`) +- `20 0A` = `FLICTEST` (`0x0A20`) + +So the effective patch is simply: + +- file: `USECODE\EUSECODE.FLX` +- installed retail offset resolved by signature scan: `0x1FF55` +- retail bytes: `17 0A` +- patched bytes: `20 0A` + +That preserves the existing `slot_20` dispatch and only swaps the target class. + +So the effective behavior becomes: + +- normal Remorse datalink tail: `spawn TEXTFILE.slot_20(...)` +- patched Remorse datalink tail: `spawn FLICTEST.slot_20(...)` + +This exposes the shipped keypad-driven `FLICTEST` movie browser with only a 2-byte usecode-file change. + +### Raw hex patch + +At the resolved installed retail site: + +```text +Offset 0x1FF55 +Retail : 17 0A +Patched: 20 0A +``` + +Or with surrounding context: + +```text +Retail : 57 02 02 17 0A 20 00 65 00 6E FE +Patched: 57 02 02 20 0A 20 00 65 00 6E FE +``` + +### Patcher integration + +I updated `Crusader-Map-Viewer/patch_crusader_map_load.ps1` so the Remorse-only movie-browser option now patches `USECODE\EUSECODE.FLX` instead of the executable. + +- CLI enable: `-Choice flictest-enable` +- CLI restore: `-Choice flictest-default` +- interactive enable: `6` +- interactive restore: `7` + +The script resolves the site by scanning for the full DATALINK tail signature, then applies only the 2-byte token swap. It refuses to write if the current bytes are neither the expected retail bytes nor the already-enabled bytes. + +### Current validation state + +The rewritten PowerShell script was copied into the installed No Remorse folder and validated with `-Choice status`. + +Current retail detection from the live install: + +- `CRUSADER.EXE` startup selector resolved normally +- both editor-visibility sites resolved normally +- Remorse datalink movie-browser site resolved at `USECODE\EUSECODE.FLX` offset `0x1FF55` +- current state reported as `Retail default` + +So the signature-scan and offset-resolution logic for the minimal patch is now confirmed against the installed retail files. + +### Regret: `DATALINK::use` + +Regret has a shipped, clearly reachable `DATALINK` path into `FLICTEST`. + +Direct caller snippet from `map_renderer/.cache/usecode/regret/EUSECODE/pseudocode/DATALINK/slot_01_use.txt`: + +```c +else { + spawn FLICTEST.slot_20(pid, global[0x0001], local_02); + suspend; +} +``` + +This branch sits at the tail of the normal `DATALINK::use` body, after the map-specific mission/objective text handling. + +Why this matters for reachability: + +- `docs/scummvm-crusader-reference.md` notes that `games/start_crusader_process.cpp` seeds inventory with shape `0x4d4` `datalink` at game start. +- That means `DATALINK` is not hypothetical leftover content. It is part of the live shipped startup flow. + +So even if the exact branch condition still needs fuller runtime narrowing, `FLICTEST` is attached to a definitely shipped and definitely player-facing object family in Regret. + +### Regret: `KEYPADNS::use` + +Regret also has an even more explicit movie-browser route through `KEYPADNS`. + +This is also where the strongest hidden/debug-style evidence lives. Unlike Remorse, whose `KEYPADNS::use` is just the normal keypad wrapper that either spawns `KEYPAD.slot_20` or forwards to `TRIGGER.slot_20`, Regret adds two special map-gated branches ahead of the ordinary keypad logic. + +Direct caller snippet from `map_renderer/.cache/usecode/regret/EUSECODE/pseudocode/KEYPADNS/slot_01_use.txt`: + +```c +local_06 = "VIDEO PLAYER^_____________^^Mission video 0-72^Mission MVAs 73-89^Game Flicks 90-102"; +ComputerGump.readComputer(local_06); +local_02 = KeypadGump.showKeypad(0); +... +spawn FLICTEST.slot_20(pid, local_02, local_04); +``` + +This is the single strongest semantic clue in the whole pass. `KEYPADNS` literally presents a `VIDEO PLAYER` menu and then hands the user-selected number to `FLICTEST.slot_20`. + +The same body also contains a second literal branch: + +```c +local_06 = "Cheaters Menu^_____________^^Select a mission^number (1-10)^and Col. Shepherd^will assign it to you."; +``` + +That changes the interpretation materially. In Regret, `KEYPADNS` is not just a generic keypad helper that happens to call a movie routine. It preserves an overt hidden-menu path with both mission-cheat and movie-browser behavior. + +That makes `FLICTEST` look less like a purely abandoned developer test and more like a retained hidden utility that still sits behind shipped object logic, at least for this Regret keypad/computer path. + +#### Recoverability status + +This is where the evidence gets better than the first pass. + +- Remorse contrast: `map_renderer/.cache/usecode/remorse/EUSECODE/pseudocode/KEYPADNS/slot_01_use.txt` has no `Cheaters Menu` or `VIDEO PLAYER` branch. It is only the standard keypad wrapper. +- Regret demo persistence: `map_renderer/.cache/usecode/regret-demo/EUSECODE/pseudocode/KEYPADNS/slot_01_use.txt` still contains both the `Cheaters Menu` string and the `VIDEO PLAYER` string. So this was not a one-build accident unique to one retail extract. +- Shape alignment: Regret extracted metadata gives `KEYPADNS` class id `0x044b`, and the Regret shape catalog also has a placed but unnamed base entry at `0x044b`. +- Map placement: a structured scan of the Regret scene cache found an actual placed `shape:1099` (`0x044b`) object on map 26 at world position `(32746, 32798, 48)` with `quality = 1` and `frame = 1`. +- Stable locator: in the viewer, that map-26 object resolves to fixed scene id `fixed:123` and runtime id `item:757:fixed:1099:1:32746:32798:48`. +- Adjacent-state persistence: the same visible keypad also exists on Regret map 27 at the same world coordinates with the same `quality = 1` / `frame = 1` signature, so this is not unique to a single cache variant. + +That last point matters because the hidden movie-browser branch checks for the exact pair `Actor.getMap(...) == 26` and `Item.getQLo(arg_06) == 1` before showing `VIDEO PLAYER` and spawning `FLICTEST.slot_20`. + +So the movie-browser route is no longer just semantically plausible. There is concrete shipped placement evidence for the exact object signature that the branch expects. + +The remaining gap is narrower now: + +- I did not find a matching base `shape:1099` object with `quality = 0` in the map-26 scene cache, so the `Cheaters Menu` branch is still not proven to be exposed by a shipped interactable in the same way. +- I did find a `shape:1099` helper-geometry record with `quality = 0` on map 26, but it is flagged invisible in the scene cache, so it is not strong enough to claim a player-facing cheat terminal without more runtime or map-authoring evidence. + +#### Practical locator + +For the visible Regret movie keypad route, the current best locator set is: + +- Map 26 placed item: `fixed:123` +- Map 26 runtime id: `item:757:fixed:1099:1:32746:32798:48` +- World coordinates: `(32746, 32798, 48)` +- Disk coordinates used by the built-in warp path: `(16373, 16399, 48)` +- Direct debug warp into map 26: `-warp 0 16373 16399 48 -mapoff 26` + +Because map 27 is a base mission map in the executable warp table and carries the same keypad placement, there is also a second practical route: + +- Map 27 runtime id: `item:755:fixed:1099:1:32746:32798:48` +- Direct mission warp into the same keypad position on the adjacent state: `-warp 14 16373 16399 48` + +Current best access read: + +- `VIDEO PLAYER` / `FLICTEST` keypad: plausibly reachable from shipped content, and now backed by a visible placed keypad record. +- `Cheaters Menu` keypad: branch definitely exists, but I have not found a matching visible `map 26 + QLo 0` placement. The only current map-26 `QLo 0` sibling is invisible helper geometry, so normal-player access to the cheat branch is still unproven. +- If someone wants to force-test the cheat path, the cleanest current experiment would be to duplicate or retag the visible `0x044b` keypad so its local quality byte is `0` instead of `1`, then use that object on map 26. + +#### Runtime confirmation + +A live runtime test now closes the practical behavior: + +- Warping to map 26 and using the visible `QLo 1` keypad does open the `FLICTEST` movie browser. +- Talking to the nearby mission-giver NPC instead produces cheat-menu instructions and accepts mission numbers for the mission-assignment warp behavior. +- Warping to map 27 does not reproduce that movie-browser route: the matching keypad placement there is nonfunctional for `FLICTEST`, and the nearby cheat-menu interaction is absent in that state. + +So the Regret hidden branch is no longer only static-analysis evidence. The visible movie keypad behavior is runtime-confirmed, and the map-27 comparison now suggests that the hidden menu path is state-gated rather than broadly enabled on every neighboring camp variant. + +#### Is map 26 reachable without warp codes? + +Current best answer: probably not through the normal campaign flow. + +The strongest reasons are: + +- Regret map 26 scene metadata marks the map as `offset-only-or-unmapped`, while map 25 and map 27 are the neighboring campaign-facing camp states. +- The live No Regret mission table uses base maps `0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 40`; map 26 is absent from that table. +- The local map-25, map-26, and map-27 scene files all expose the same two local teleport-destination eggs (`teleport-id` `30` and `250`), but I did not find any teleporter helper or destination record in those scenes that explicitly names map 26 as the player-facing destination. +- A broader Regret scene-cache search for `mapNum 26` does return many hits, but sampled hits resolve mostly to family-4 usecode-trigger egg records whose `labelId` happens to be `26`, not to a confirmed cross-map destination lane into map 26. + +That does not prove there is no obscure script-only handoff anywhere in the executable, but the current workspace evidence favors `debug or offset-only camp variant` rather than `normally entered campaign map`. + +### Regret: `ELEVATOR::slot_20` + +Regret also contains a smaller scripted launch from an elevator body. + +Direct caller snippet from `map_renderer/.cache/usecode/regret/EUSECODE/pseudocode/ELEVATOR/slot_20_slot_20.txt`: + +```c +if (arg_12 == 6) { + ... + setUnkFlagA4(); + spawn FLICTEST.slot_20(pid, 19, local_15); + suspend; + clearUnkFlagA4(); +} +``` + +This path fades to black, stops SFX, sets a temporary flag, and then spawns `FLICTEST`. That looks like a cutscene or scripted transition, not a debug-only leftover. + +## What I did not find + +- No direct evidence that map placements instantiate the `FLICTEST` class itself. +- No direct shape-catalog or egg-subtype crosswalk that points a placed object straight at `FLICTEST`. +- No ScummVM engine-side special case named `FLICTEST`; it looks like data-driven usecode, not a hardcoded engine mode. +- The remaining uncertainty is now concentrated in the `Cheaters Menu` side of Regret `KEYPADNS`, not the `VIDEO PLAYER` side. The movie-browser branch has stronger placement evidence than the cheat branch. + +That absence matters. The current evidence is much stronger for `helper class spawned by other scripts` than for `standalone world object that level designers placed directly`. + +## Engine-side movie context + +The ScummVM Crusader reference supports the general movie interpretation. + +- `docs/scummvm-crusader-reference.md` notes that Crusader intro playback requires the `FLICS` directory. +- The same note lists `MovieGump::I_playMovieOverlay` among the Crusader-side USECODE bridge intrinsics. +- It also notes that Crusader movie playback uses assets under `flics/`. + +That does not prove every `playFlic` signature detail in the original DOS binary, but it does support the high-level claim that `FLICTEST` is using the normal Crusader movie subsystem rather than a separate hidden debugger subsystem. + +## Current best answer + +Yes: `FLICTEST` really is a movie-test/movie-browser style usecode class. + +More specifically: + +- In Remorse it looks like a fairly full-featured movie selector/dispatcher helper. +- In Regret it survives in reduced form, but one surviving route is a clearly hidden `KEYPADNS` menu that still advertises `VIDEO PLAYER` and `Cheaters Menu` behaviors. +- It is not obviously placed directly on maps as its own authored class. +- It is used indirectly by shipped content. +- The hidden `VIDEO PLAYER` route in Regret is plausibly recoverable from shipped content because map 26 contains a placed `0x044b` object with the exact local quality value the branch checks. +- The hidden `Cheaters Menu` route is still only partially proven. The branch exists, but I did not yet find a matching player-facing `map 26 + QLo 0` object. + +Most defensible live uses found in this pass: + +- Remorse: `EVENT::equip` can spawn `FLICTEST.slot_20`. +- Regret: `DATALINK::use` can spawn `FLICTEST.slot_20`. +- Regret: `KEYPADNS::use` exposes a literal `VIDEO PLAYER` UI and then spawns `FLICTEST.slot_20`. +- Regret: `ELEVATOR::slot_20` has at least one cutscene-style spawn of `FLICTEST.slot_20`. + +So the answer is not just `dead test script`. The better reading is `retained movie helper that still participates in shipped scripted flows`, even if its original name probably came from development/test usage. \ No newline at end of file diff --git a/plan-mid.md b/plan-mid.md index 3efd6cb..bd302b3 100644 --- a/plan-mid.md +++ b/plan-mid.md @@ -63,16 +63,18 @@ Detailed completed analysis belongs in the files under `docs/`, not in this plan - That same warp-table lane is now exact across both retail DOS executables too. Byte checks against `CRUSADER.EXE` and `REGRET.EXE` now show matching 17-word `-warp mission` base-map tables (`0,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,40`) at `1478:0488` and `1480:075c`, each followed by a `0,0` terminator. The public map renderer now also has a dedicated mission-table extractor and generated JSON cache, so scene metadata no longer has to treat mission/base-map usage as an unknown ownership question. - That same startup lane is now tighter at the argument level too. Current best parser/control-flow read in `REGRET.EXE` is `-warp [x y z]`, with X/Y/Z carried as positional argv tokens after the mission number rather than as separate recovered switches. The corresponding runtime branch in `Game_RunNewGameFlow` is also clearer: nonnegative `-egg` overrides beat the coordinate path, while the real eggless-map workaround is `-warp ` plus `-mapoff` with `-egg` omitted so the game falls into direct `NPC_Teleport` instead of the teleporter-egg lookup. - The matching No Remorse cross-check is now closed too. Live `CRUSADER.EXE` `HandleCommandlineArgs` at `1048:0adc` uses the same positional `-warp [x y z]` parser shape, and `Game_Start` at `1020:029e` / `1020:02d0` uses the same runtime precedence: direct coordinates only win when the egg override is still negative, otherwise the code falls back to `Teleporter_CreateProcessDirect`. The parameter-only eggless-map workaround is therefore shared across both retail games, not Regret-specific. -- The public map-renderer link lane is tighter again. Cross-game `0x01DB` support now covers both the earlier frame-`1` teleporter-light helpers and the remaining Regret map `3` frame-`0` telepad helper placements that carry destination ids `27/28` in `quality`. The same pass also adds the checked same-map `ELEVATOR` lane: frame-`0` `shape:542` sources now link to local teleport-destination eggs by verified `QLo` rules (`1..0x0f -> same egg id`, `0x10 -> egg 4`). Current best gap is still Regret map `3` egg `102`, which does not sit on the verified `shape:542` / `shape:307` elevator lanes yet. +- The public map-renderer link lane is tighter again. Cross-game `0x01DB` support now covers both the earlier frame-`1` teleporter-light helpers and the remaining Regret map `3` frame-`0` telepad helper placements that carry destination ids `27/28` in `quality`. The same pass also adds the checked same-map `ELEVATOR` lane: frame-`0` `shape:542` sources now link to local teleport-destination eggs by verified `QLo` rules (`1..0x0f -> same egg id`, `0x10 -> egg 4`). A new Regret follow-up closes the remaining map-`3` egg-`102` gap too: Regret `shape:400` (`0x0190`) is a second `ELEVATOR` family, `ELEVATOR::gotHit` uses a generic same-map lane for `QLo >= 100 && < 0x00c8`, and map `3` contains concrete source `item:664:fixed:400:0:44030:9662:0` with `quality 614` (`QLo 102`) linking to destination egg `102`. A second Regret-only editor-object follow-up now also promotes `WATCHNS`, `WATCHEW`, `CRYOBOX`, `CRAZYEW`, `CRAZYNS`, `VIDEOBOX`, and the `SECRET_DOOR_POST` / `PRESSURE_BARRIER_*` target shapes into the viewer, including cautious local arrows for `WATCH* -> 0x0510` and `CRYOBOX -> 0x05DF/0x05E0`. - The map-13 wall-jump follow-up is tighter too. The suspicious nearby start tile `fixed:4767` is now closed as `FFFLOOR` (`shape 0x0135` / `shape:309`) rather than as an editor/helper collision override: decoded reference data still marks it as ordinary `terrain` (`4 x 4 x 0`, `solid`, `fixed`, `land`), but the extracted EUSECODE corpus shows `FFFLOOR::gotHit/equip/unequip` as an environmental hazard/sensor lane that toggles nearby same-shape floors and pushes standing actors through `NPC.slot_2d` into the normal hit/damage path. The nearest same-level trigger companion in the retail cache is family-4 egg `fixed:4770`, which currently resolves to `CHANGER`, so the local map-13 setup now reads better as a small scripted floor/trigger cluster than as proof that the rare jump-through wall has an authored per-instance non-solid flag. - That same map-13 trigger companion is tighter now too. The nearby family-4 egg `fixed:4770` is no longer just a subtype label: retail Remorse clearly uses `QLo 4 -> CHANGER`, and the extracted `CHANGER::hatch` body plus the local decoded scene now line up as a keyed roof-destruction trigger (`mapNum` egg id `37`, nearby roof tiles with `QLo 37`). Current safest local read is `FFFLOOR hazard tile plus CHANGER roof-removal cluster` on the same upper platform, still not a direct wall-solidity override. -- The editor-helper overlay lane is tighter too. A broader exported-scene sweep now shows that `BRO_BOOT` (`0x04FE`) really does form a repeatable local helper lane into nearby same-`QLo` `SPANEL` items, with concrete Remorse examples on maps `9`, `10`, `11`, `21`, `23`, `160`, and `246`, so the renderer now promotes `BRO_BOOT -> SPANEL` alongside the existing cmd-link, alarm, steam, door, and flame helper arrows. The same follow-up kept two tempting false positives out of the overlay: `NPC_ONLY -> 0x04B1` and `DEATHBOX -> 0x04B1` still read better as incidental local overlap than as a dedicated helper-source relationship. The latest tooltip pass also upgrades `0x04B1` from a mostly structural decode to concrete operation notes: helper dispatch via nearby `0x0476`, direct target mutation, timed pulses through `TRIGGER.slot_22` / `DOOR.slot_21`, verified link rewrites, and a create-and-drop lane. +- The editor-helper overlay lane is tighter too. A broader exported-scene sweep now shows that `BRO_BOOT` (`0x04FE`) really does form a repeatable local helper lane into nearby same-`QLo` `SPANEL` items, with concrete Remorse examples on maps `9`, `10`, `11`, `21`, `23`, `160`, and `246`, so the renderer now promotes `BRO_BOOT -> SPANEL` alongside the existing cmd-link, alarm, steam, door, and flame helper arrows. A later decompressed `.cache` pass tightens two Regret trigger families as well: `NPC_ONLY` (`0x0366`) and `CRUMORPH` (`0x0318`) now promote cautious local `-> 0x04B1` same-`QLo` arrows where authored matches exist, while `NPC_ONLY -> actor` and `DEATHBOX -> 0x04B1` still stay out of the overlay. The latest tooltip pass also upgrades `0x04B1` from a mostly structural decode to concrete operation notes: helper dispatch via nearby `0x0476`, direct target mutation, timed pulses through `TRIGGER.slot_22` / `DOOR.slot_21`, verified link rewrites, and a create-and-drop lane. +- The same Regret controller lane is now better separated into `static helper links` versus `runtime actor-key links`. Current best read is that the withheld actor-target arrows (`CRUMORPH`, `NPC_ONLY`, and the wider sibling family) depend on mutable actor field `0x63`, not on a stable DTABLE export: sampled Regret DTABLE rows still show `0x00` at record byte `0x63`, while recovered `TRIGGER.slot_29` / `slot_2B` can rewrite field `0x63` on nearby matched NPCs after startup. The newly identified sibling families using that same hidden actor-key mechanism are `WATCHNS` / `WATCHEW`, `THRMBCKN` / `THRMBCKE`, and `SURCAMNS` / `SURCAMEW`. Current viewer/tooling stance remains `metadata + cautious helper arrows only` until a runtime or spawn-time export can close actor field `0x63` directly. - The skill-controller lane is tighter too. Shape `0x0120` is now closed as `FASTSKIL`, distinct from `SKILLBOX`: `enterFastArea` waits briefly, only runs while map-array is clear, uses frame `0/1` as difficulty thresholds for `TRIGGER.slot_20` lane `0` versus `1`, and uses frame `2` as an explicit `QLo/+1/+2` difficulty router. The renderer now exposes that decode in tooltips and adds conservative local `FASTSKIL -> 0x04B1` helper arrows, with frame-`2` variants for the recovered `QLo + 1` and `QLo + 2` lanes. - The switch/pad clarification lane is tighter too. Shape `0x0080` now closes as `BOX_EW`, and sampled exported scenes are strong enough to promote a conservative `BOX_EW frame 0 -> nearby same-QLo 0x04B1` helper arrow rule. Shape `0x04CD` now closes as `TRIGPAD`, but its broader occupancy/elevator behavior and the negative scene sweep keep it metadata-only instead of promoting a generic cmd-link overlay. Shape `0x033A` now reads best as a tiny `NUMBERS` readout/display helper family clustered with nearby `0x0501/0x0502/0x0503/0x0505/0x0507` pieces, so it also stays label-only. -- The readable-usecode viewer lane is tighter too. New note `docs/map_renderer/trigger-usecode-links.md` records the evidence-backed class/event mapping now used for pinned controller tooltips: `BOX_EW`, `PANELNS`, `CARD_NS`, and `SPANEL` open `use`; `FASTSKIL` opens `enterFastArea`; `SKILLBOX`, `EVENT`, `ALARMHAT`, and `ALRMTRIG` open `equip`; `TRIGPAD` and `NPC_ONLY` open `gotHit`; and the `0x04B1` cmd helper jumps directly to `TRIGGER.slot_20`, the shared high-slot fan-out lane recovered from the extracted corpus and existing trigger notes. +- The readable-usecode viewer lane is tighter too. New note `docs/map_renderer/trigger-usecode-links.md` records the evidence-backed class/event mapping now used for pinned controller tooltips: `BOX_EW`, `PANELNS`, `CARD_NS`, and `SPANEL` open `use`; `CRUMORPH`, `SKILLBOX`, `EVENT`, `ALARMHAT`, and `ALRMTRIG` open `equip`; `FASTSKIL` opens `enterFastArea`; `TRIGPAD` and `NPC_ONLY` open `gotHit`; and the `0x04B1` cmd helper jumps directly to `TRIGGER.slot_20`, the shared high-slot fan-out lane recovered from the extracted corpus and existing trigger notes. - The command-line lane is tighter around `-u` now too. In live non-Japanese `CRUSADER.EXE`, the parser case at `1048:0a46` copies the following token into `1478:065a`, and the renamed `startup_apply_u_override_if_present` at `1420:0cdf` later consumes that buffer to resolve/load an alternate usecode/EUSECODE source into `1478:6611/6613`, mark `1478:6615`, and rebuild the cumulative slot-base words at `1478:8c7c..8c82`. Current best read is `real retail startup usecode override`, not `JP-only` and not `dead string-table residue`; the paired consequence is that the older CRUSADER-side `-setver` attribution should now be treated as reopened until its exact retail consumer is isolated directly. - That same `-u` lane is now tighter at the runtime-scope level too. The follow-up note `docs/usecode-startup-override.md` now records that retail `-u` appears to replace the single live usecode root at `1478:6611/6613`, not add a sidecar table: `startup_apply_u_override_if_present` overwrites that root directly, rebuilds the cumulative slot-base words, and later consumers including `Usecode_ItemCallEvent`, `UsecodeProcess_CreateProcess`, `Interpreter_NextUsecodeOp`, and `Item_GetDamaged` all read the same replacement root. Current safest tooling implication is `runtime swap for the existing Crusader usecode VM`, which makes `-u` a potentially important future validation hook for round-tripped/custom usecode archives once the accepted source format is nailed down. - The same `-u` lane is tighter at the token-shape level now too. Live `1420:0cdf` does not use the copied argv token as an arbitrary final filename; it treats `1478:065a` as the `Filespec_GetFullPath` path component while loading the fixed mutable filename template `eusecode.flx` from `1478:07a0` through `1478:06d6/06d8` and forcing the first byte to `'e'` before both the existence probe and the final load call. Current safest read is therefore `path/root override for standard EUSECODE archive naming`, not `free-form filename override`. The stock bootstrap side is also better scoped: `1478:6611/6613` starts zero in the live NE image and the only currently recovered explicit writer there is the `-u` helper, so the normal non-`-u` seed remains only cross-referenced through the verified raw-side VM bootstrap note rather than fully live-NE-closed. +- The same `-u` lane is tighter at the practical experiment level now too. The live helper shape plus direct bytes at `1478:079a` make the fixed filename template concrete as `eusecode.flx`, which materially weakens the older `maybe it accepts arbitrary archive filenames` hope. Current safest user-facing read is now `pass a directory/root to -u and place a complete replacement EUSECODE.FLX there`; failed forms like `-u USECODE/FLICTEST.FLX` and `-u FLICTEST.FLX` are best explained as path/root misuse rather than as evidence that the `-u` switch itself is dead. - The same override lane now has a concrete live-NE constructor pair too. `1420:1499` is now renamed `entity_vm_runtime_create` and currently reads as a `0x1319`-byte runtime-object allocator that zeroes a `0x1300`-byte front region behaving like `0x80` stride-`0x26` slot/runtime records before storing an attached helper pointer at `+0x1315/+0x1317`. `1430:0000` is now renamed `entity_vm_runtime_owner_resource_create` and currently reads as the compact `0x14`-byte file-backed helper that opens the resolved `eusecode.flx` path, queries entry count through vtable `+0x04`, allocates a backing buffer at `+0x10/+0x12`, and materializes indexed owner/resource records through vtable `+0x0c`. Current safest implication is that `-u` swaps the live VM runtime object graph, not just a raw archive handle. - The USECODE/VM owner/resource/runtime lane now has a workable partial model, a named sequencer entry, paired external file-family loader evidence, and supporting extraction/reporting tooling. - The USECODE/VM tooling lane now also has a concrete near-term implementation path: a Pentagram-derived proof-of-concept parser can reuse opcode decoding while swapping in the locally verified owner-loaded class and slot arithmetic, with a hybrid Ghidra comment/bookmark import path instead of a premature custom processor module. @@ -147,6 +149,7 @@ Detailed completed analysis belongs in the files under `docs/`, not in this plan - Candidate O = interpreter callsite retarget -> `13a0:020d`, with `13a0:024a` zeroed inherited modal-wrapper words - Candidate P = interpreter callsite retarget -> `13a0:0086`, with `13a0:008f` zeroed inherited current-unit-wrapper words Both apply/restore cleanly on a disposable retail copy and are the next runtime tests. +- Fresh live-Ghidra re-checks now tighten the lower bound on this patch family too. Retail still shows no recovered writer that seeds `1478:659c/659e`, the constructor at `1408:0000` still only returns the debugger object and seeds the inert shared callbacks `1478:65ab -> 1408:046f` / `1478:65af -> 1408:0474`, and the interpreter pre-call guard at `1418:049e..04b5` still only checks for a non-null debugger pointer before handing off to `1408:0053`. Current best implication: there is still no evidence-backed one-site direct jump that can safely load the hidden debugger. Candidate O/P are now the smallest structurally defensible executable patches because they are the first design that preserves all four required pieces together: object bootstrap, global pointer seeding, one-shot deferred entry, and sanitized wrapper arguments. - Full chronology for this patch line now lives in `docs/retail-debugger-patch-attempts.md`, including the failed global callback rewrite, direct wrapper call, single-step `65af` build, break-next `65af` build, guarded `0474` trampoline, shared `046f` method patch, and the current private-vtable candidate. - The hidden-menu orphan model is now materially stronger too. New live renames in seg1408 (`usecode_debugger_break_state_create`, `usecode_debugger_maybe_break_on_current_line`, `usecode_debugger_breakpoint_insert_sorted`, `usecode_debugger_has_breakpoint`, `usecode_debugger_callstack_push_entry`, `usecode_debugger_callstack_pop_entry`, `usecode_debugger_enable_single_step`, `usecode_debugger_clear_step_state`, `usecode_debugger_current_entry_get_unit_name`) line up with the seg109 UI in a way the cheat-only hook never did. The concrete interpreter-side handoff at `1418:04aa..04b5` now calls `usecode_debugger_maybe_break_on_current_line` whenever the far pointer at `0x659c/0x659e` is non-null, and that helper checks `(file,line)` breakpoints before callbacking through the debugger-state object's vtable. Current best read is therefore that the retail orphan happened one layer earlier than the cheat/event experiments: the seg109 current-unit debugger UI likely used to be entered from this seg1408 breakpoint object, but retail no longer appears to instantiate/store that object at `0x659c/0x659e`. That makes the breakpoint callback lane a stronger original-entry candidate than direct event `0x103` retargeting. - The follow-up doc reconciliation is now closed too. `docs/ne-segment1.md` no longer presents the seg109/raw-reference UI addresses (`000b:*`) and the live seg1408 breakpoint-state addresses (`1408:*`) as if they were competing versions of one table; it now uses one combined component map that makes the layering explicit and preserves the interpreter callback at `1418:04aa..04b5` as the bridge between them.