From 83d019515ff47e7ec3be13b540441c02e55ffdd2 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Wed, 26 Aug 2026 09:41:46 +0000 Subject: [PATCH] re: confirm the cmesh<->model link via GameResourceID; world unit is oracle-blocked Two follow-ups on yesterday's^Wthis morning's CollisionSet write-up. 1. The _cmesh <-> render-model link, which I recorded as UNTESTED because matching stems against .xbg object names covered 4 of 158. The disc keeps only one build manifest, so that corpus was never going to answer it. The right corpus is the GameResourceID field of the DefTables / GP_MAIN_GAME records -- 480 distinct values. Against those, with a control that shuffles the characters of each stem: ship/mob stems prefixed by a real resource id 108/112 = 96.4% same stems, characters shuffled (control) 0/112 = 0.0% asteroid stems prefixed (expected none) 0/46 So a CollisionSet entry is [_]_cmesh. The 0/46 on asteroids matters as much as the 108/112: a test that fired on everything would be the bound-check hazard again. 2. The world unit. Sweeping every pak for a name carrying a kilometre figure returns mapmesh_box_500km.col/.rgn and nothing else -- 162 references, all to that one pair. The reading rests on a single filename with no corroborating instance anywhere in the data, so no static test can settle it; marking it blocked on the oracle rather than leaving it as an open static question. My objection's premise did survive: rou_e010 is a real GameResourceID and e010_ADAN_Attacker_S is in the stage tables, so the 133-unit mesh does belong to a craft the game calls an attacker. Whether the trailing _S means "small" is a further guess (there are _EX4 / _HF / _HF_Wayne variants), so it stays suggestive rather than evidence. --- docs/re/BACKLOG.md | 11 ++++- docs/re/data/collisionset.txt | 6 +++ docs/re/structures/collisionset.md | 47 +++++++++++++++--- tools/re-capture/collisionset.py | 76 +++++++++++++++++++++++++++++- 4 files changed, 130 insertions(+), 10 deletions(-) diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 6784c58c..393c4789 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -437,8 +437,15 @@ search cannot find a *schedule*. proxies (`_bdy`/`_brg`/`_eng`/`_wep`/`_sld`) plus 46 stage asteroids. ⚠️ **This downgraded the "1 unit = 1 m" claim to 🟑** β€” it implies a craft the tables call *small* is 133 m and the largest object 447 km (89 % of the arena). - 🟑 The `_cmesh`↔render-model name link is **untested, not confirmed**: only one - `.xbg` build manifest survives, so the check covers 4/158. + βœ… **The `_cmesh`↔model link is now CONFIRMED** (upgraded from 🟑 the same day): + the right corpus was the **`GameResourceID`** field, not the `.xbg` manifests β€” + **108/112** ship/mob stems are prefixed by one of the 480 resource ids, against + a **0/112** shuffled-character control, and correctly **0/46** on asteroids. A + `CollisionSet` entry is `[_]_cmesh`. + πŸ”΄ **The world unit is BLOCKED on the oracle.** Sweeping every pak for a + km-bearing name returns `mapmesh_box_500km` and **nothing else** (162 refs, all + that one pair) β€” the convention has no second instance, so no static test can + settle it. Needs a measured in-game traversal against the flight model. `tools/re-capture/collisionset.py verify|list|obj` ([`data/collisionset.txt`](data/collisionset.txt)). ❔ Still open: no material/surface-type field exists in the record, and the diff --git a/docs/re/data/collisionset.txt b/docs/re/data/collisionset.txt index e0649179..504bdcb2 100644 --- a/docs/re/data/collisionset.txt +++ b/docs/re/data/collisionset.txt @@ -169,3 +169,9 @@ PASS 158 meshes, 90836 triangles PASS +distinct GameResourceID values : 480 +ship/mob stems prefixed by a resource id: 108/112 = 96.4% (exact match 55) +same stems, characters shuffled (control): 0/112 = 0.0% +asteroid stems prefixed (expected none) : 0/46 + +PASS diff --git a/docs/re/structures/collisionset.md b/docs/re/structures/collisionset.md index db269876..da17db6f 100644 --- a/docs/re/structures/collisionset.md +++ b/docs/re/structures/collisionset.md @@ -67,10 +67,30 @@ weapon and shield β€” the same sub-part vocabulary as the `XBG7` render models, these are per-part collision proxies. The asteroid meshes carry exactly the stage prefixes that have an `_AsteroidVolume_wp` `MCOL`. -🟑 The name correspondence to the render models is **not demonstrated**. Only one -`XBG2` build manifest survives on the disc (`machines\rou_e104\…`), so matching -`_cmesh` stems against `.xbg` object names covers 4 of 158 β€” no coverage, and -therefore no evidence either way. It is not a refutation; it is an untested claim. +### βœ… The names *are* the game's own resource ids (upgraded from 🟑 the same day) + +The previous write-up recorded this link as **untested**, because matching +`_cmesh` stems against `.xbg` object names covered only 4 of 158 β€” the disc keeps +just one build manifest (`machines\rou_e104\…`), so that test had no power in +either direction. + +The right corpus is not the manifests, it is the **`GameResourceID` field** of +the `DefTables` / `GP_MAIN_GAME` records β€” 480 distinct values, e.g. +`GameResourceID = 'rou_e010'`. Against those, with a control that shuffles the +characters of each stem: + +| | | +|---|---| +| ship / mob stems prefixed by a real `GameResourceID` | **108 / 112 = 96.4 %** (55 exact) | +| same stems, characters shuffled (control) | **0 / 112 = 0.0 %** | +| asteroid stems prefixed (expected none β€” they are not resources) | **0 / 46** | + +So a `CollisionSet` entry is `[_]_cmesh`: the collision +proxy for one part of one game resource. The four unmatched stems are +`rou_e303_wep`, `rou_f301_body`, `rou_f302`, `rou_f303_body`. + +The 0 / 46 on asteroids is worth keeping: a test that fires on everything would +be the bound-check hazard again. It separates the two families correctly. ## 🟑 The world unit β€” the metre reading does not survive this file @@ -95,9 +115,22 @@ game's units really are metres and its ships are kilometre-scale, or `500km` in that filename names something other than the box's span. The 500 000 arithmetic stands; the *interpretation* does not have independent support. -An independent scale reference β€” the flight model's speed values against a -measured traversal time in the running game β€” would settle it, and has not been -run. +**And there is no second instance of the convention to check against.** Sweeping +every `.pak` on the disc for a name carrying a kilometre figure returns +`mapmesh_box_500km.col` / `.rgn` and nothing else β€” 162 references, all to that +one pair. So the reading rests on a single filename, with no corroborating +example anywhere in the data. That is not a refutation; it is the absence of any +static way to settle it. + +My objection's own premise did survive its check: `rou_e010` is a real +`GameResourceID`, and `e010_ADAN_Attacker_S` appears in the stage tables, so the +133-unit mesh does belong to a craft the game calls an attacker. (Whether the +trailing `_S` means "small" is a further guess β€” there are `_EX4`, `_HF` and +`_HF_Wayne` variants of the same unit β€” so it is suggestive, not evidence.) + +πŸ”΄ **Blocked on the oracle.** An independent scale reference β€” the flight model's +speed values against a measured traversal time in the running game β€” is the only +route left, and it needs an emulator run rather than the disc. Reproduce with `tools/re-capture/collisionset.py verify` / `list` ([`../data/collisionset.txt`](../data/collisionset.txt)); `collisionset.py obj` diff --git a/tools/re-capture/collisionset.py b/tools/re-capture/collisionset.py index aa28519f..faaf89fa 100755 --- a/tools/re-capture/collisionset.py +++ b/tools/re-capture/collisionset.py @@ -7,6 +7,7 @@ asteroids), separate from the per-map `MCOL` hull. ./collisionset.py list # the 158 meshes ./collisionset.py verify # the checks in the doc + ./collisionset.py link # cmesh names <-> GameResourceID ./collisionset.py obj A record is @@ -111,11 +112,84 @@ def cmd_obj(pak, want, out): raise SystemExit(f"no mesh named {want!r}") +def cmd_link(root): + """Are the mesh names the game's own resource ids? With a control.""" + import random + import re + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + from unitgroup import tag_hash + + grid = tag_hash("GameResourceID") + ids = set() + for pak in (os.path.join(root, "hidden", "DefTables.pak"), + os.path.join(root, "dat", "GP_MAIN_GAME_E.pak")): + for _, blob in pak_entries(pak): + p = _idxd(blob) + if not p: + continue + for key, _, voff in p["fields"]: + if key == grid: + v = _pool(p, voff) + if v: + ids.add(v) + + stems = [re.sub(r"_(cmesh|c)$", "", n) + for n, _, _, _ in meshes(os.path.join(root, "hidden", "MiscBin.pak"))[0]] + ship = [s for s in stems if s[:4] in ("rou_", "rob_", "mob_")] + rock = [s for s in stems if s not in ship] + + pre = sum(any(s.startswith(i) for i in ids) for s in ship) + exact = sum(s in ids for s in ship) + random.seed(3) + ctl = 0 + for s in ship: + t = list(s) + random.shuffle(t) + ctl += any("".join(t).startswith(i) for i in ids) + + print(f"distinct GameResourceID values : {len(ids)}") + print(f"ship/mob stems prefixed by a resource id: {pre}/{len(ship)} " + f"= {100*pre/len(ship):.1f}% (exact match {exact})") + print(f"same stems, characters shuffled (control): {ctl}/{len(ship)} " + f"= {100*ctl/len(ship):.1f}%") + print(f"asteroid stems prefixed (expected none) : " + f"{sum(any(s.startswith(i) for i in ids) for s in rock)}/{len(rock)}") + return pre / len(ship) > 0.9 and ctl == 0 + + +def _idxd(b): + if b[:4] != b"IDXD" or len(b) < 0x10: + return None + n = struct.unpack_from(">I", b, 4)[0] + if not 0 < n <= 200_000: + return None + recs, o = [(None,) + struct.unpack_from(">III", b, 0x0C)], 0x18 + if o + 16 * (n - 1) + 4 > len(b): + return None + for _ in range(n - 1): + recs.append(struct.unpack_from(">IIII", b, o)) + o += 16 + m = struct.unpack_from(">I", b, o)[0] + o += 4 + if o + 12 * m + 4 > len(b): + return None + return dict(recs=recs, + fields=[struct.unpack_from(">III", b, o + 12 * i) for i in range(m)], + pool=o + 12 * m + 4, raw=b) + + +def _pool(p, off): + b, base = p["raw"], p["pool"] + if base + off >= len(b): + return None + return b[base + off:b.index(b"\0", base + off)].decode("latin-1") + + if __name__ == "__main__": if len(sys.argv) < 3: raise SystemExit(__doc__) cmd, pak = sys.argv[1], sys.argv[2] - fn = {"list": cmd_list, "verify": cmd_verify}.get(cmd) + fn = {"list": cmd_list, "verify": cmd_verify, "link": cmd_link}.get(cmd) ok = cmd_obj(pak, sys.argv[3], sys.argv[4]) if cmd == "obj" else fn(pak) print("\nPASS" if ok else "\nFAIL") sys.exit(0 if ok else 1)