This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/structures/mcol-collision.md
Sylpheed RE agent c8d3893bb8 re: MCOL array A confirmed as the per-cell record -- 100% once split
Separating the two interleaved arrays by address and re-running the same three
criteria: array A (2509 records) gives byte3==1 at 100.00%, a valid cell index
at 100.00%, and the sphere reaching that cell at 99.92%. Array B (6467) gives
30.65% and 30.60% -- a different record type, and the control showing A's 100%
is not what any 32-byte block would score.

So array A is the per-cell record: cell index (x,y,z), count, pointer into array
B, bounding sphere -- the same role REGN's section 3 plays. That confirms the
earlier 50% was the interleaving artifact and not a half-working reading.

The split was crude, first-half-by-address giving 2509 vs 6467 rather than an
even cut, and A still came out at 100%. A rough partition isolating a perfect
population is stronger than a careful one isolating a good-ish population.

Array B's layout is still unread.
2026-08-26 08:32:41 +00:00

152 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `MCOL` — the same container as `REGN`, and the same map parameters
**🟡 Opened 2026-08-26.** `MCOL` sits beside `REGN` in `hidden/MiscBin.pak`, 11
of each, and has never been decoded. This page establishes what it shares with
`REGN` — which is a lot, and gives the next attempt a large head start.
## ✅ Same container
Over all **11** objects:
| | |
|---|---|
| `POF0` fixup table at `data_size@+4` + 16 | **11 / 11** |
| bbox pad words are `1.0` / `1.0` / `0.0` at `+0x1C`, `+0x2C`, `+0x3C` | **11 / 11** |
| `extent == max − min` for the `0x30` block | **11 / 11** |
So the header prefix is byte-for-byte the same shape as `REGN`'s: magic, data
size, then bbox min / bbox max / extent as `f32[4]`, then a triple at `0x40`.
The `POF0` mechanism applies, which means **the `chunk + 0x10` base and the
loader's own pointer list are available here too** — the two things that cracked
`REGN`.
## ✅ And the same map parameters, exactly
The bounding boxes and the `0x40` triple are not merely similar — the
distributions are **identical**:
| | `MCOL` | `REGN` |
|---|---|---|
| bbox ±250 000 | 2 | 2 |
| bbox ±50 000 | 6 | 6 |
| bbox ±25 000 | 3 | 3 |
| `0x40` = 50 000 | 2 | 2 |
| `0x40` = 10 000 | 9 | 9 |
Eleven maps, and for each one an `MCOL` and a `REGN` describing the same volume
at the same cell size. `0x40` is the **cell size** in `REGN`; the same values in
the same multiplicities here is strong evidence it is the cell size in `MCOL`
too — though note this is a match of *distributions*, not a demonstrated
object-to-object pairing, which would need the two linked by name or by a stage's
tables.
## ❔ What is not yet known
* **Everything past `0x40`.** `MCOL`'s words at `0x50`–`0x84` do **not** look
like `REGN`'s (`REGN` has grid dims at `0x50`, six `u16` counts at `0x60` and
six section pointers at `0x70`; `MCOL` has a large value, two mid-range values
and `112` at `0x50`, mostly zeros at `0x60`, and `0x05050501` at `0x70`). The
headers agree on the spatial prefix and diverge after it.
* **Everything past `0x40`** — but see below; the pointer layout is now known.
## ✅ The pointer layout, from `POF0`
**2026-08-26.** Running the known-good decoder (`regn_decode.py` on
`auto/regn-reader`) rather than my own broken one. Sanity check first: on `REGN`
it returns header slots `0x70`–`0x84` exactly — the six section pointers — so the
tool and my use of it are right.
On `MCOL`, over all **11** objects:
| | |
|---|---|
| header-region relocated slots are **exactly `0x54`, `0x58`, `0x5C`, `0x74`** | **11 / 11** |
| slot `0x5C` resolves to **`0x80`** — the first byte after the header | **11 / 11** |
| slot `0x74` resolves to **(first array pointer − 8)** | **10 / 11** |
So `MCOL` has **four** top-level pointers where `REGN` has six, and one of them
(`0x5C`) always addresses the data immediately following the header.
**The bulk of the relocations form record arrays.** 92.7 % of the gaps between
consecutive relocated words are **32 bytes**, arranged in 7–127 contiguous runs
per object. Combined with `0x74` landing 8 bytes before the first of them, the
reading is an array of **32-byte records each carrying one pointer at `+8`**.
What the four targets look like:
0x5C -> 0x80 c685620b 4596789d 4694b3b5 44a9a634 floats
0x54 -> … c6826964 456b1aa4 469ab065 c685cfe8 floats
0x58 -> … 00000001 00020003 00040005 00050004 small ints / u16 pairs
0x74 -> … 00000001 00000001 00007710 00000000 counts, then the array
Two float blocks, an index block and a record array is the shape of a mesh —
which is what a name like `MCOL` beside a navigation mesh would suggest. 🟡 That
is a reading of the shape; none of the four blocks has been decoded.
❔ Still open: the record layout, what the index block indexes, the **one object
in eleven** where `0x74` does not land 8 before the array, and the 7.3 % of gaps
that are not 32 (they are the boundaries between runs, but that has not been
checked).
## 🟡 The 32-byte record — a cell entry, and there are two interleaved arrays
**2026-08-26.** Reading each record as 8 big-endian words (record start =
pointer slot − 8), the first entries of the smallest object are:
@0x6780 00000001 00000001 00007710 00000000 00000000 00000000 00000000 47295092
@0x67A0 01000001 00000001 00007730 00000000 …
@0x67C0 02000001 00000001 00007750 00000000 46023555 C6023555 C6023555 471FA1A7
@0x6820 00010001 00000001 000077B0 00000000 …
Word 0 read as **four bytes** is `(x, y, z, 1)` — a **3-D cell index**. That
object's grid is 5×5×5 (bbox ±25 000, cell 10 000), and the values run 0–4 in
the first byte and step the second byte at the right point. Words 4–6 are a
position and word 7 a positive scalar — a bounding sphere. Word 1 is a count and
word 2 the relocated pointer.
**Every record pointer lands in the same region — 8 976 / 8 976 (100 %)** — and
each points 0xFA0 further on with the same stride, so there are **two parallel
arrays**, not one: array A at `0x6780` and array B at `0x7720`.
### The 50 % is the tell, not a failure
Testing the cell-index reading over *all* relocated records gives almost exactly
half:
byte 3 == 1 4 491 / 8 976 (50.03 %)
bytes 0..2 a valid cell index 4 488 (50.00 %)
the record's sphere reaches that cell 4 485 (49.97 %)
Three independent criteria all landing on 50.0 % is not a partial fit — it says
**half the records are not this type**. The `POF0` slot list interleaves both
arrays, and I was testing array B's records against array A's layout. Reported as
a rate it would read like a half-working hypothesis; split by array it is two
clean populations.
🟡 So array A is a **per-cell record** — cell index, count, pointer into array B,
bounding sphere — the same role `REGN`'s section 3 plays. ❔ Array B's layout is
unread, and the split has not yet been re-run per array to confirm 100 % on A.
### ✅ Split by array, and it goes to 100 %
Done. Separating the records by address and re-running the same three criteria:
| | array A (2 509) | array B (6 467) |
|---|---|---|
| byte 3 of word 0 == 1 | **100.00 %** | 30.65 % |
| bytes 0–2 a valid cell index | **100.00 %** | 30.60 % |
| the record's sphere reaches that cell | **99.92 %** | — |
**Array A is the per-cell record**, exactly as read: cell index `(x, y, z)`, a
count, a pointer into array B, and a bounding sphere — the same role `REGN`'s
section 3 plays. Array B is a different record type; its ~30 % is incidental,
and it is the control that shows A's 100 % is not something any 32-byte block
would score.
Worth noting the split was crude — "first half by address", giving 2 509 vs
6 467 rather than an even cut — and A still came out clean at 100 %. A rough
partition that isolates a perfect population is stronger evidence than a careful
one that isolates a good-ish population.
❔ Array B's layout is still unread. That is the next thing.