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/regn-map-grid.md
Sylpheed RE agent f481ccbb5f re: REGN sections 0/1/2 have strides, and section 2 is a plane list
The three sections recorded as undecoded are fixed-stride arrays and counts[0..2]
are their record counts: 12, 96 and 48 bytes. Section 1's remainder is exactly 0
in 11/11 objects and section 2's exactly 96 in 11/11, which is what makes these
strides rather than a coincidence of division.

Section 0 is a point list: 13467 of 13467 records lie inside their object's own
header bounding box.

Section 2 is a plane list, 12 f32: four zeros, a unit normal (|n|=1 in
133573/133573), a signed distance, a point inside the bbox (133573/133573), and
a trailing 1.0 (133573/133573). The decisive check is algebraic -- n.p + d must
vanish for a real plane, and over all 133573 records the relative residual has a
median of 2.29e-08 and a maximum of 2.15e-07. That is float round-off, not a fit.

So a REGN object carries a point list and a plane list beside its uniform grid,
which fits collision or region-boundary geometry and sits next to MCOL.

Still open: section 1 (96 B, 60631 records), what queries the planes, the zeros
at [0..3], and the constant 96-byte tail.
2026-08-26 07:07:10 +00:00

8.8 KiB
Raw Blame History

REGN — a per-map spatial grid (and MCOL beside it)

Status: CONFIRMED for the header, which self-checks on all 11 objects on the disc. the four data sections are undecoded. New to this corpus — no document mentioned REGN, MCOL or hidden/MiscBin.pak before 2026-08-24.

Where it is

hidden/MiscBin.pak — 40 entries, none name-resolved, in three groups:

magic count sizes
REGN 11 49 KB … 3.1 MB
MCOL 11 37 KB … 81 KB
(other) 18

Eleven of each, which pairs them: one REGN and one MCOL per map. MCOL is untouched here; the name and the size range read like mesh collision.

The header, and why it is believable

0x00  char[4]  'REGN'
0x04  u32      size of the data area  (the POF0 table starts at 0x04-value + 16)
0x10  f32[4]   bbox min   (x, y, z, 1.0)
0x20  f32[4]   bbox max   (x, y, z, 1.0)
0x30  f32[4]   extent     (max - min)
0x40  f32[4]   cell size
0x50  u32[4]   grid dimensions
0x60  u16[6]   six counts
0x70  u32[4]   four section offsets (the first is always 0x80)

The check that makes this a decode rather than a guess — over all 11 objects:

  • extent == cell × dims holds 11 of 11, exactly;
  • counts[3] equals the cell count: 1000 on every 10×10×10 map and 125 on every 5×5×5 one.

Two independent fields reproducing the grid is what rules out coincidence.

The three map sizes on the disc

half-extent cell dims objects
250 000 50 000 10×10×10 2
50 000 10 000 10×10×10 6
25 000 10 000 5×5×5 3

So every map is a cube partitioned into 125 or 1 000 uniform cells — 10 km cells in a 100 km cube for the common case, and one pair of maps five times larger.

Section 3 is the cell index — and it self-checks 11/11

The fourth section is a one entry per cell table of 8-byte records (count, offset), immediately followed by the records those offsets point at:

index      = offsets[3] .. offsets[3] + cells*8
payload    = align16(index end) ..
record     = 32 bytes (offsets step by 0x20)

Checked over all 11 objects: the lowest offset any cell refers to equals align16(offsets[3] + cells × 8) — 11 of 11. On the six 10×10×10 maps cells × 8 is already 16-aligned and the payload butts straight up against the index; on the three 5×5×5 maps 125 × 8 = 1000 is not, and the payload starts 8 bytes later, which is what makes the alignment rule visible rather than assumed.

Two more invariants from the same sweep:

  • every occupied cell has count exactly 1 — total items equals occupied cells on all 11 objects, so this is "one record per cell", not a bucket list;
  • counts[4] = occupied cells + 2, exactly, on all 11 (e.g. 997/995, 880/878, 125/123). The +2 is unexplained — two sentinels, or two cells counted differently.

Most cells are occupied: 878998 of 1 000, 123 of 125.

It is a serialised object graph with a POF0 fixup table

Every one of the 11 objects contains the tag POF0, always near the tail, and its position is exactly header[0x04] + 16 — on 11 of 11:

e993b93e  header +0x04 = 0x0b100   POF0 at 45328 = 45312 + 16
e4155d94  header +0x04 = 0x235d70  POF0 at 2317696 = 2317680 + 16
… 11 of 11 identical relation

POF0 is a pointer-offset (fixup) table: the file is a serialised C++ object graph, and the loader patches the recorded slots into real pointers. That explains a detail that would otherwise be odd — the "offsets" inside the cell index are absolute file offsets, because that is what a fixup table rewrites.

So header[0x04] is the size of the data area, and everything past header[0x04] + 16 is relocation bookkeeping rather than content.

🔴 Two readings of the cell payload, both refuted by generalising

Both came from the smallest object and both died the moment they were checked against the other ten — recorded because the temptation to keep them was real:

  • "the f32 at record +0x1c is the grid's bounding-sphere radius." On e993b93e it is 86 689 against √3 × 50 000 = 86 603, a ratio of 1.001. On the other ten the ratio runs 0.13 0.27. Fitted to one sample.
  • "a record's (count, offset) pairs point at leaf arrays of count × 4 bytes." True for the first record of e993b93e; across the objects the offset deltas fail that rule on every object checked (0 of 11 clean).

What survives is only descriptive: the payload area is dominated by float data — the "strings" a printable-run scan finds are all byte patterns like 0x46/0x47 high bytes, i.e. medium-magnitude floats, not text.

The other three sections

Their offsets scale with the object, and counts[0..2] scale with them — (318, 1172, 2584) for the 350 KB map against (2936, 14967, 31155) for the 3.1 MB one, a roughly 1 : 4.5 : 10 ratio that holds across all eleven. The smallest object (49 KB) is nearly empty by comparison — (8, 6, 18) — which makes it the cheapest one to decode first.

Why this matters, stated without overclaiming

A mission's enemy count rises and falls as waves arrive and are destroyed, so somewhere there is a scheduler with parameters — what spawns, where, and on what trigger. A per-map uniform grid indexed by cell is exactly the structure such a thing is indexed by.

🔴 But nothing here shows spawn parameters yet. The header is a spatial partition and no more; the sections are unread. Treat this as the location of the world's spatial data, not as the wave table.

Update: REGN is a stage's MapPath

The per-stage definition record (see stage-definition-table.md) has a field MapPath = test.rgn, and name_hash("test.rgn") = 0x3506e972, which is one of the REGN objects in MiscBin.pak. The sibling field MapMesh = test.col hashes to 0x2cf7eb47, an MCOL object in the same pak.

So .rgn/REGN is stage navigation/path data referenced by the stage record, and .col/MCOL is the stage collision mesh. This does not by itself validate either of the two refuted cell-payload readings recorded above, but it does explain why the payload looks like a grid of route data.

Sections 0, 1 and 2 have record strides — and section 2 is a PLANE list

2026-08-26. The three sections the page called undecoded are fixed-stride record arrays, and counts[0..2] are their record counts. Dividing each section's byte span by its count over all 11 objects:

section stride evidence
0 12 bytes count × 12 fits with a remainder of 012 in 11 / 11
1 96 bytes count × 96 fits with a remainder of exactly 0 in 11 / 11
2 48 bytes count × 48 fits with a remainder of exactly 96 in 11 / 11

Section 1 landing on a zero remainder in every object, and section 2 on the same 96-byte tail in every object, is what makes these strides rather than a coincidence of division.

Section 0 is a point list

12 bytes is three f32. Over 13 467 records across all 11 objects, every one lies inside that object's own header bounding box — 13 467 / 13 467 (100 %). Values land on the box corners (±250 000) and inside.

Section 2 is a plane list — 48 bytes, and the plane equation closes

Read as twelve f32:

[0..3]   zero
[4..6]   unit normal          |n| = 1 ± 0.02 in 133 573 / 133 573 (100 %)
[7]      signed distance d
[8..10]  a point on the plane, inside the bbox in 133 573 / 133 573 (100 %)
[11]     1.0 exactly           in 133 573 / 133 573 (100 %)

The check that makes this a decode: for a genuine plane, n·p + d must be zero. Over all 133 573 records:

|n·p + d| / scale :  median 2.29e-08   p90 6.78e-08   max 2.15e-07

That is float round-off, not a fit — the relation holds to the last bits of a 32-bit float in every record on the disc. Three independent 100 % properties (unit normal, point in bbox, trailing 1.0) and an exact algebraic identity are well past what a wrong reading survives.

So a REGN object carries, alongside its uniform grid, a point list and a plane list — which is the shape of collision or region-boundary geometry, and consistent with MCOL sitting beside it.

Still open

  • Section 1 (96 bytes/record, 60 631 records disc-wide) is untouched. Its content is float-dominated with both large coordinates and mid-range values. 96 bytes is 24 floats — a plausible shape for a 4×4 matrix plus extras, or for two 48-byte plane records, but nothing is measured.
  • What the planes are for. "Collision or region boundary" is a reading of the shape; nothing here shows what queries them.
  • The zeros at [0..3] of every plane record, and the constant 96-byte tail after section 2, are unexplained.