more docs

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

View file

@ -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/<game>/map-*/<hash>/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.

View file

@ -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'<MEMORY_SECTION NAME="\.text" START_ADDR="([^"]+)" LENGTH="([^"]+)"', xml):
print(m.group(0))

227
_tmp_valuebox_cache_scan.py Normal file
View file

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

View file

@ -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}

View file

@ -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` |

View file

@ -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 <arg>` 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 `<that root>/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).

View file

@ -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.

View file

@ -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`

View file

@ -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

View file

@ -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`

View file

@ -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 |

View file

@ -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.
- **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.

View file

@ -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, <copied token>, "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.

View file

@ -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 `<u16 raw_event_entry_word, u32 raw_code_offset>`
- 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.

View file

@ -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 <mission> [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 <mission> <x> <y> <z>` 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 <mission> [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.