Two tests. Counts modulo 3 are spread across all three residues (639/2063/1786), so the u16 array is not a triangle list. And a B record is reached through a specific cell, so a point it references should lie in that cell -- referenced points score 0.79% against a 0.48% random-point control. Chance. The contrast is the point. One section earlier the same u16 entries scored 18379/18379 (100%) on 'are these valid point indices'. I flagged that at the time as the weak bound-check and recorded it as consistent rather than as a finding. The caution was right: the powered version of the same question now returns chance, and had the 100% been written up as the decode this page would carry a confident false statement about MCOL's geometry. Fifth appearance of the pattern across REGN and MCOL and the first time both halves have been run on the same field, so the page now states it plainly: a bound-check asks whether something could be an index, and the answer is set by the size of the target collection rather than by the field's meaning. Datum for the next attempt: the maximum u16 is consistently about 0.75x the point count (923/1232, 1019/1360, 1163/1552, 59/80), too consistent to be coincidence and not explained.
285 lines
13 KiB
Markdown
285 lines
13 KiB
Markdown
# `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.
|
||
|
||
### ✅ A → B is one-to-one, and every count is 1
|
||
|
||
Filtering array A by the cell-index criteria (so only genuine A records) and
|
||
following each one's pointer, over all **11** objects:
|
||
|
||
| | |
|
||
|---|---|
|
||
| every A record points at a **distinct** B record | **11 / 11** |
|
||
| every A record's count field is **exactly 1** | **11 / 11** |
|
||
|
||
Per object the A-record count is the number of **occupied cells** — 110, 118,
|
||
117, 488, 488, 575, 514, 468, 492, 546, 572 — and the total of the count fields
|
||
equals it exactly.
|
||
|
||
That is the *same* design `REGN` uses: this corpus already records for `REGN`
|
||
that "every occupied cell has count exactly 1 — total items equals occupied
|
||
cells". Two sibling formats, same cell-index convention. It is a further
|
||
independent confirmation of the A-record reading, since the filter and the
|
||
cardinality are unrelated criteria.
|
||
|
||
### ❌ Array B resisted a first pass, and the tests I reached for were bad ones
|
||
|
||
I could not read B's record layout this iteration, and both attempts failed in
|
||
ways worth recording rather than retrying:
|
||
|
||
* **The boundary was off by 8 again.** Dumping from `pointer − 8` (as A's layout
|
||
needed) produced records that begin with what is plainly the *tail* of the
|
||
previous structure — a zero and `0x47295092`, the same radius value A's first
|
||
record carries. Same mistake as the `0x74` check two iterations ago.
|
||
* **The `u16`-index test had no power.** The `0x5C` block holds ~1 232 points, so
|
||
"is this `u16` below the point count" is satisfied by almost any small value —
|
||
and duly reported 100 % at seven different offsets. That is the *fourth* time
|
||
in this pair of formats that a bound-check against a large collection has
|
||
produced a meaningless 100 %.
|
||
|
||
❔ So B's layout is open. **What would have power**: B records are 1:1 with
|
||
occupied cells and A already carries the cell's bounding sphere, so a candidate
|
||
field in B can be tested by whether it is spatially consistent with *that
|
||
specific cell* — the same design that worked for `REGN`'s faces, and the same
|
||
design that these two bound-checks lack.
|
||
|
||
## ✅ Array B decoded — `{count, u16 index array}`, and the chain closes
|
||
|
||
**2026-08-26.** Two corrections got there.
|
||
|
||
**Where the other relocations live.** `MCOL` has 9 020 relocated words and only
|
||
4 488 are A-record pointers. I assumed the rest sat at `B+8`, mirroring A —
|
||
**refuted, 0 / 4 488**. Measuring their offset from the nearest preceding B
|
||
record instead:
|
||
|
||
at B + 4 4 488 (99.0 %)
|
||
before the first B 44 (1.0 %)
|
||
|
||
and those 44 are exactly the four header pointers × 11 objects. **Nothing
|
||
unaccounted for.**
|
||
|
||
**So B is `{u32 count, pointer}`** — pointer at `+4`, not `+8`. Read that way:
|
||
|
||
B@0x7720 count 0x2B ptr 0x84D0
|
||
B@0x7740 count 0x1C ptr 0x8526 0x8526 − 0x84D0 = 0x56 = 2 × 0x2B
|
||
B@0x7760 count 0x02 ptr 0x855E 0x855E − 0x8526 = 0x38 = 2 × 0x1C
|
||
B@0x7780 count 0x02 ptr 0x8562 = 2 × 2
|
||
|
||
The pointers advance by exactly twice the count — a **packed `u16` array**, no
|
||
padding. Over all 11 objects:
|
||
|
||
| | |
|
||
|---|---|
|
||
| consecutive B pointers differ by **exactly `2 × count`** | **4 477 / 4 477 (100.00 %)** |
|
||
| the `u16` entries are valid `0x5C` point indices | 18 379 / 18 379 |
|
||
|
||
The first row is the load-bearing one: an exact arithmetic identity over 4 477
|
||
consecutive pairs. (The second is the same weak bound-check flagged above — the
|
||
point block is large, so almost any `u16` passes. It is consistent, not
|
||
evidence.)
|
||
|
||
### The chain
|
||
|
||
position → cell → A record {cell index, count 1, →B, bounding sphere}
|
||
→ B record {count n, →u16[n]}
|
||
→ n indices into the 0x5C point block
|
||
|
||
which is the same shape as `REGN`'s `cell → item → refs → geometry`, as the two
|
||
formats' shared header and shared count-1 convention already suggested.
|
||
|
||
❔ Still open: the `0x54` and `0x58` blocks (neither is reached by this chain).
|
||
What the `u16` entries index is **not** the point block — see immediately below.
|
||
|
||
## ❌ The `u16` entries do NOT index the point block — and the weak test said they did
|
||
|
||
**2026-08-26.** Two tests, and the contrast between them is the point of this
|
||
section.
|
||
|
||
**Not a triangle list.** If the `u16` array held triangle corners, every count
|
||
would be divisible by 3. Counts modulo 3 across all objects:
|
||
|
||
n % 3 == 0 639 n % 3 == 1 2 063 n % 3 == 2 1 786
|
||
|
||
Spread across all three residues. Refuted.
|
||
|
||
**Not cell-local either.** A B record is reached *through* a specific cell, so a
|
||
point it references should lie in that cell. With a random-point control:
|
||
|
||
| | |
|
||
|---|---|
|
||
| referenced point inside the cell that reached it | 142 / 17 871 = **0.79 %** |
|
||
| a random point inside that cell (control) | 85 / 17 871 = **0.48 %** |
|
||
|
||
Chance. So whatever the `u16`s address, it is not the `0x5C` point block in any
|
||
spatially meaningful way.
|
||
|
||
### ⚠️ This is the bound-check hazard caught in the act
|
||
|
||
One section above, the same `u16` entries scored **18 379 / 18 379 (100 %)** on
|
||
"are these valid `0x5C` point indices". I flagged that at the time as the weak
|
||
bound-check rather than evidence, and recorded it as *consistent* rather than as
|
||
a finding. **That caution was correct**: the powered version of the same question
|
||
now returns chance.
|
||
|
||
This is the fifth appearance of the pattern across `REGN` and `MCOL`, and the
|
||
first time both halves have been run side by side on the same field, so it is
|
||
worth stating exactly:
|
||
|
||
> A bound-check asks *"could this be an index?"*. Almost always, yes — the answer
|
||
> is set by how large the target collection is, not by whether the field means
|
||
> anything. The powered version asks *"does the thing it points at make sense
|
||
> where it was reached from?"*, and only that version can be wrong.
|
||
|
||
Had the 100 % been written up as the decode, this page would now carry a
|
||
confident and false statement about `MCOL`'s geometry.
|
||
|
||
❔ What the `u16`s index is open. A datum for the next attempt: the maximum
|
||
`u16` is consistently ≈ **0.75 ×** the point count (923/1 232, 1 019/1 360,
|
||
1 163/1 552, 59/80) — too consistent to be coincidence, and not explained. |