# C-Pod hand-over: long filenames **and HD art**

**Audience:** whoever updates **C-Pod** (the POD packer / surgery tool).
**Status:** long-name contract written 2026-08-11; **HD-art section added 2026-08-12**; **stem
contract tightened 20 → 18 on 2026-08-31** to reserve room for derived `_N` / `_AO` / `_MASK` / `_DTL`
names (§1) — this is now
the single C-Pod document, covering two separate contracts.
**Companions:** `FILENAME_CONVENTIONS.md` (the 22 derivation sites, measured from 200 shipped PODs),
`ENGINE_LIMITS.md` (content caps + the new box types), `TRACK_VERSIONING.md` (`.TXV` / `.SI2`),
`MONVERT_HANDOVER.md` (the new `.bin` records, for model exporters).

> **Reading order if you are short of time:** §8 (HD art) is what changed most recently and is where
> a packer can now get things wrong in a way that produces silently missing art. The long-name
> contract in §1–§7 is unchanged since 8-11.

> ⛔ **THERE IS NO NEW POD FORMAT. The container is POD1, unchanged:** `int32 n`,
> `char volumeName[80]`, then `n` directory entries of **40 bytes** — `char name[32]` (31 chars + NUL),
> `int32 size`, `int32 offset`. Nothing in this document widens that entry, and nothing about it
> changed with the 64-bit port (`int` is still 32-bit on x64). "Long names" here is a **naming**
> contract that lives *inside* the 31-character field.
>
> The **64-byte** names in §9 (`MRGL_TEXTURE64`, `MRGL_KEYFRAME64`) are records **inside `.bin` model
> files** — never the pod directory. On 2026-09-09 a third-party tool conflated the two and wrote a
> "POD64" with 72-byte entries (`name[64]` + size + offset — the exact shape of `MRGL_TEXTURE64`), and
> the engine rejects it at mount. The full byte layout, a reference writer and an engine-equivalent
> validator are in **`POD1_FORMAT_HANDOVER.md`**.

---

## The one sentence

> **A filename stem of up to 18 characters must survive a pack/unpack/rebuild round trip
> unchanged — byte for byte — and must never be truncated silently.**

Everything below is that sentence with its corners nailed down.

---

## 1. Why 18, and why it is not arbitrary

POD1's directory entry has a **31-character** name field, so the format has always had room.
What constrains us is not the container — it is the **~22 places the engine BUILDS one filename
out of another**, catalogued in `FILENAME_CONVENTIONS.md`. A texture `FOO.RAW` implies
`art\FOO.act`, `art\FOO.PNG`, `data\FOO.tty`; a track stem implies 17 `DATA\<stem>.*` siblings, a
`FOG\<stem>.MAP`, and `UI\<stem7>S.BMP`. Every one of those needs the prefix, the stem and the new
extension to fit the engine buffer doing the composing.

Measured across 200 shipped PODs: the **longest stored name anywhere is 20 characters**. So that is
where the real content already sits.

⭐ **18, NOT 20, AND THE TWO SPARE CHARACTERS ARE DELIBERATE (user, 2026-08-31).** The container
would take more — this is a reservation, not a limit. Art files gain **derived siblings whose names
are the stem plus a suffix**, and those have to fit too:

| derived from `<stem>` | suffix cost | what it is |
|---|---|---|
| `<stem>_N` | 2 | normal map (`AUTHORING_HD_ART.md`) |
| `<stem>_AO` | 3 | ambient occlusion, packed into the `_N`'s blue at ingest |
| `<stem>_MASK` | 5 | terrain detail mask (`TERRAIN_DETAIL_MASK.md`) |
| `<stem>_DTL` | 4 | supplied terrain detail normal |

An 18-character stem leaves every one of those inside the 31-character entry once the directory
prefix and extension are counted. A 20-character stem does not.

⛔ **This is why `_DTL` is spelled that way.** It began life as `_DETAIL` and was renamed on
2026-08-31 after Traxx reported it overrunning on real track names. The suffixes are abbreviated to
buy stem characters back — do not "improve" one into something more readable without redoing this
arithmetic.

⛔ **And the failure is SILENT.** An overrunning derived name does not error: the file simply stops
resolving and the feature it belongs to quietly does not appear. That is what makes a reservation
worth more than a warning.

⚠ **18 is the STEM, not the entry.** `MODELS\LONGTREENAME01.BIN` is a 20-char stem inside a
31-char entry. Budget accordingly — and note that stem is already past the 18 this document now asks
for, precisely because it has no derived siblings. Stems longer than 20 are the
*extended-directory* tier and are **out of scope for C-Pod today** — do not invent support for them.

---

## 2. What C-Pod must do

1. **Preserve names verbatim.** No upper/lower-casing, no 8.3 folding, no truncation to any legacy
   width, on pack, unpack, list, or rebuild. If C-Pod has an internal `char[13]`, `char[16]` or
   `char[9]` anywhere a POD entry name lands, that is the bug.
2. **Bound every copy.** A wide buffer with an unbounded `strcpy` is still a defect. See §4 — this
   exact pattern cost the engine a crash on 2026-08-11.
3. **Refuse loudly, never truncate silently.** If a name cannot be represented, say so with the
   name and the limit. A truncated name still *looks* valid and then fails to match at load time,
   which is far harder to diagnose than a refusal.
4. **Preserve unknown entries.** In particular `DATA\*.TXV` (the track version record) must survive
   pack/unpack/rebuild untouched — never filtered as "unrecognised". Same for anything else new.
5. **Accept `.SI2` everywhere `.SIT` is accepted** — track detection, listing, validation, and any
   "is this a track pod?" heuristic. See `TRACK_VERSIONING.md`; `.SI2` is how a track hides itself
   from a 1998 install that would render it wrong.
6. **Keep sibling families together.** When C-Pod moves, renames or rebuilds a track, the stem is
   shared by ~21 files (§3). Renaming one without the others silently breaks the track.

---

## 3. The sibling families — what shares a stem

From `FILENAME_CONVENTIONS.md`, verified against shipped PODs. If C-Pod ever renames a stem, **all
of these move together or the track breaks**:

| family | members |
|---|---|
| Track `<S>` | `WORLD\<S>.SIT` (or `.SI2`) · `LEVELS\<S>.LVL` · **17×** `DATA\<S>.*` (`.ANI .CL0 .CL1 .CL2 .CLR .LTE .PUP .RA0 .RA1 .RA2 .RA3 .RA4 .RA5 .RAW .TEX .TTY .TXT`) · `FOG\<S>.MAP` · optional `SOUND\<S>.WAV` + `.KLP` |
| Track UI | `UI\<S7>S.BMP` and `UI\<S7>L.BMP`, where **`S7` = the stem truncated to 7 characters** + a suffix letter |
| Texture `<T>` | `ART\<T>.RAW` + `ART\<T>.ACT` (palette) · optional HD `ART\<T>.PNG` / `.TGA` · optional `DATA\<T>.TTY` |
| Sound `<W>` | `SOUND\<W>.WAV` + optional `SOUND\<W>.KLP` (loop points) |

---

## 3a. 🚩 STRUCTURAL NAME SCHEMES — the hardest part, and NOT a buffer problem

**Widening a buffer does nothing for a derivation that encodes meaning in a POSITION or a SUFFIX
LETTER.** Several engine schemes do exactly that, and they break at 20 characters in ways that are
invisible until content collides. These are shared contracts: the engine derives, Traxx generates,
C-Pod packs — **all three must move together or the pair breaks.**

| scheme | how it breaks at a 20-char stem |
|---|---|
| `UI\<S7>S.BMP` / `UI\<S7>L.BMP` — stem **truncated to 7** + suffix letter | 🚩 **COLLISION.** `AlpineValleyNorth` and `AlpineValleySouth` both derive `AlpineV*` and silently share UI bitmaps. Rare at 8-char stems; likely at 20. |
| `UI\<logobase>M.BMP` — logo name **minus a trailing T/S, plus M** | A stem legitimately ENDING in `T` or `S` loses a real character. |
| Truck LOD models — the detail **digit is written AT OFFSET 7** | Caps truck model stems at **7**, regardless of buffer width. Algorithmic, not a resize. |
| Steering wheel `ART\<wheel>{L35..L05,C00,R05..R35}.RAW` | Suffix-letter family: the stem plus 3 characters plus `.RAW` must fit **and not alias** another stem. |
| Shifter `ART\<shifter>{P,R,N,1,2,3}.RAW` | Same shape. |
| Backdrop weather substitution by **EXACT string match** (`circ1drp`→`ee4drop`) | Hardcoded table — a longer stem simply never matches and the substitution **silently does nothing**. |

**Rank these by whether the failure is SILENT.** A collision that makes two tracks share a logo, or
a substitution table that quietly stops matching, is far worse than something that errors — those
are the ones that reach players looking like a content bug.

**What C-Pod should do about it:**

> ### 🔒 DECIDED 2026-08-12 (user): the `UI\` 7-character scheme STAYS. Warn, do not redesign.
>
> C-Pod raised the collision and the answer is to keep it. Redesigning the derivation would break
> the ~200 pods already in circulation and every pod-zipped distribution built around them, which is
> a far larger cost than the collision it would prevent. Long stems make the collision **easy to
> avoid by authoring** — the first 7 characters simply have to differ — so this moves to the
> authoring rules rather than the code. It is documented author-side in
> `AUTHORING_HD_ART.md` §7. **Do not reopen it as a tool fix.**

- ⛔ **Do not redesign a scheme unilaterally.** These are contracts shared with the engine and
  Traxx; changing one side breaks the pair. If a scheme genuinely cannot carry 20 characters, raise
  it — it is a joint decision, not a tool fix.
- ✅ **Validate and warn.** At minimum: **warn when two track stems in one pod share their first 7
  characters** (the `UI\` collision). A silent collision is indistinguishable from working.

  > ### ✅ ANSWERED 2026-08-13 — the trailing `T`/`S` check should NOT fire on load. C-Pod's read is right.
  >
  > C-Pod exposed the check but declined to run it on load, on the grounds that a huge number of real
  > stems end in those letters (`SUNSET`, `HILLS`) and warning on all of them would be noise. **Agreed,
  > and here is the engine-side reason it is safe:** the trailing letter is dropped only to derive the
  > **info image**, `UI\<logo minus trailing T/S>M.BMP` (`MenuShell.cpp`, `trackInfoStem`). That
  > derivation is **opportunistic** — the image decides whether the info button appears at all, so a
  > stem that loses a real character simply looks for a file that does not exist and the track quietly
  > gets no info button. Nothing is corrupted, nothing is overwritten, and no other asset is affected.
  >
  > So the warning earns its keep **only where a tool generates or rewrites a logo name**, which is
  > what C-Pod has kept it for. ⛔ Do not promote it to a load-time check.
- ⚠ `.ACT` / `.TTY` / `.MAP` derivations split on the **FIRST dot**, so a stem containing a dot
  truncates. Treat "no dots inside a stem" as an authoring rule and validate it.

---

## 4. The failure mode this is written to prevent

On 2026-08-11 the engine crashed and rendered garbage geometry because of this, in `SIMMODEL.H`:

```c
char sceneryModelNameList[MAX_SCENERY_MODEL][16];   // 16 bytes
strcpy(sceneryModelNameList[i], name);              // UNBOUNDED
```

A 17-character model name overran into the next entry's name. That entry's lookup then **missed**,
so its model was loaded again into a fresh slot; each load advanced a ring cache that `freeModel`d
the slot it landed on — **while the display list still pointed at it**. Dangling model →
screen-filling garbage triangles → crash.

Three lessons C-Pod should inherit:

- **A missed lookup is not cosmetic.** Truncation is not a display bug; it makes name matching fail,
  and downstream systems assume a name that matched is resident.
- **Widening alone is insufficient.** The copy must be bounded *and* must report when it would have
  truncated, or the same bug returns at a longer name.
- ⛔ **Do not audit by grepping for `[16]` and declaring it clean.** A sweep two days earlier had
  pronounced that area done and missed this buffer. **Read the buffer, not the doc.**

---

## 5. What C-Pod must NOT do

- ⛔ **Do not widen any structure that is a direct on-disk overlay.** In the engine, `textureStruct`
  (`name[16]`) is overlaid on raw `.bin` bytes and its `sizeof` **is** the record stride — widening
  it changes how every model ever authored parses. The long-name path there is a *new opcode*,
  `MRGL_TEXTURE64` (62, `name[64]`), not a bigger old record. If C-Pod parses `.bin` contents,
  the same rule applies.
- ⛔ **Do not rename anything on import.** Pre-authored files keep their names at any length and
  pass through untouched. Name *generation* rules apply only to files the tool itself creates.
- ⛔ **Do not assume `.SIT`.** Derive the extension; a track may be `.SI2`.

---

## 6. Acceptance checklist

- [ ] Pack → unpack → repack a pod containing an **18-char stem** in each family (model, texture,
      sound, track): every entry name byte-identical, whole-pod hash stable across the round trip.
- [ ] A name too long to represent produces a **named refusal**, not a truncation.
- [ ] `DATA\*.TXV` survives pack/unpack/rebuild.
- [ ] A `.SI2` track is detected, listed and validated exactly as a `.SIT` one.
- [ ] Two stems sharing their first 7 characters produce a `UI\` collision **warning**.
- [ ] A legacy 1998 pod round-trips **byte-identical** — the regression guard that matters most.

---

## 7. Open engine-side issues C-Pod should know about

From `FILENAME_CONVENTIONS.md`, still unfixed at the time of writing (an engine audit is in
progress; re-check before relying on these):

- `data\<texture>.tty` composition: `filename[16]` with an unbounded `strcpy` from `texture[40]` —
  a live overflow.
- ~~`RACE.CPP` `readName()`: unbounded `strcpy` into `logoBMP[32]` / `mapBMP[32]`.~~ **FIXED
  2026-08-09** — `readName()` reads through a local and `strncpy`s bounded by the destination size
  (`RACE.CPP:108-120`). A long logo entry in a `.SIT` now truncates instead of scribbling over the
  `STrack` fields that follow.
- `.ACT` / `.TTY` / `.MAP` derivations use **first-dot** replacement, so a stem containing a dot
  truncates. **Advise authors: no dots inside a stem.**
- Truck LOD names write the detail digit **at offset 7**, an algorithmic 8.3 rule that caps truck
  model stems at **7** characters regardless of buffer width. That one is a format decision, not a
  resize.

---

# 8. HD ART — PNG and TGA in pods (added 2026-08-12)

The engine gained true-colour art. This changes **what a pod may legitimately contain**, and there
is one mistake that produces silently missing textures — so this section exists mostly to stop it.

## 8.1 The one sentence

> **`.PNG` and `.TGA` are ordinary `ART\` entries, the `.RAW`/`.ACT` pair is now OPTIONAL, and a
> texture name is a STEM — the extension recorded anywhere is only a hint.**

## 8.2 ⛔ The mistake to avoid: filing HD art outside `ART\`

The engine resolves a texture by probing, **in this order**:

```
ART\<stem>.PNG   →   ART\<stem>.TGA   →   ART\<stem>.RAW
```

It looks **only in `ART\`**. A `.PNG` packed into the pod root, or into any other folder, is never
found — and nothing reports it, because "no HD source" is a normal, silent answer. The texture just
renders from its legacy `.RAW`, or not at all.

**BinEdit had exactly this bug** (fixed 2026-08-12): an explicitly added `.PNG` fell through its
unknown-extension routing into the pod root. If C-Pod has a similar "what folder does this extension
belong in?" table, **`.PNG` and `.TGA` must map to `ART\`.**

## 8.3 A pod may have art with NO `.RAW` — this is legal, not corruption

An author may deliberately ship HD art only. C-Pod must therefore **not**:

- treat a missing `.RAW` for a `.PNG` as an error, a warning, or a repair opportunity;
- synthesise a `.RAW` from the `.PNG`;
- drop a `.PNG` because "it is not a texture";
- drop a `.PNG` because no `.ACT` sits beside it.

**Why an author does this:** the legacy pair is typically **3–11× the size of the HD source** and can
be ~90% of a texture-heavy pod. Traxx and BinEdit both offer the choice at save time, defaulting to
writing the fallback.

⚠️ **The cost is real and differs by pod type**, so do not "helpfully" warn about the wrong one:

| pod contains | omitting the legacy pair means |
|---|---|
| a **track** (`.SIT` → must be `.SI2`) | the track is **invisible** to a 1998 install — deliberate, see `TRACK_VERSIONING.md` |
| a **model / truck** | a 1998 engine reads it and draws **garbage textures**. There is no `.SI2` equivalent for model pods |

## 8.4 The `.ACT` belongs to the `.RAW`, never to the PNG

`<stem>.ACT` is the palette of `<stem>.RAW`. A `.PNG`/`.TGA` carries its own colour and has **no**
`.ACT` sibling.

So: **drop the `.ACT` with the `.RAW`, keep it with the `.RAW`, and never look for one beside HD
art.** A texture that ships PNG-only having no `.ACT` is correct, not incomplete.

## 8.5 A texture name is a STEM — do not normalise names inside files

Since 2026-08-12 the engine, Traxx and BinEdit all resolve **stem-based**: a `.bin` may record
`FOO`, `FOO.PNG`, `FOO.TGA` or `FOO.RAW` and every one resolves to whatever art is present.

⛔ **So C-Pod must not rewrite texture names inside models to "fix" their extension.** It was never
required, and a model is not C-Pod's to edit. (Older engines *do* read the recorded name literally,
which is why tools still prefer to WRITE `.RAW` — but that is the writer's choice, not something a
packer should impose after the fact.)

⚠️ **If C-Pod scans models to gather dependencies, probe all three extensions.** A model recording
`FOO.RAW` may legitimately be accompanied by `FOO.PNG` on disk and no `FOO.RAW` at all. Gathering
only the literal name will silently omit the author's real art.

## 8.6 Sizes and names

- HD art is **square, power of two, 32–1024 px**. A 1024×1024 PNG is a normal entry; pods get bigger.
- **Name budgets are unchanged** (§1–§2). `.PNG`, `.TGA` and `.RAW` are all 3-character extensions,
  so **a stem that fits `ART\` as `.RAW` fits as `.PNG`** — no new length arithmetic.
- **Case still matters.** Mixed case has caused pod-lookup failures before; upper-case is reliable.

## 8.7 Acceptance checklist for the HD half

- [ ] `.PNG` / `.TGA` are filed into **`ART\`**, never the pod root.
- [ ] A pod containing `FOO.PNG` and **no** `FOO.RAW`/`FOO.ACT` packs, unpacks and rebuilds unchanged.
- [ ] A pod containing **both** keeps both, and the `.ACT` travels with the `.RAW`.
- [ ] No texture name inside any `.bin` is rewritten by a pack/unpack/rebuild round trip.
- [ ] Dependency scanning finds `FOO.PNG` for a model that records `FOO.RAW`.
- [ ] Nothing warns about a missing `.RAW` or a missing `.ACT` beside HD art.

## 8.8 ⛔ POD SURGERY CAN MAKE THE `.TXV` LIE — the one that needs real care

§2 rules 4 and 5 tell C-Pod to **preserve** `DATA\*.TXV` and to accept `.SI2` everywhere `.SIT` is
accepted. That is right for pack/unpack/rebuild. **It is not sufficient the moment C-Pod EDITS what
a pod contains** — which is exactly what a surgery tool is for.

`DATA\<stem>.TXV` is a small text record that **asserts things about the pod around it**
(`TRACK_VERSIONING.md`):

```
formatVersion=1        the format generation
legacyFallback=0       0 = HD only.  1 = the .raw/.act siblings were packed too
hdTextures=1           at least one texture larger than 256x256
```

Traxx computes those from **what it actually packed**. So:

> **If C-Pod adds or removes art, the `.TXV` and the pod can disagree — and nothing will notice.**

| what C-Pod does | what silently becomes false |
|---|---|
| strips `.RAW`/`.ACT` to shrink a pod | `legacyFallback=1` — and the situation file must also become **`.SI2`**, or a 1998 install loads a track whose textures are gone |
| adds a legacy pair back | `legacyFallback=0` — the track hides from engines that could now render it fine |
| adds a 512/1024 texture | `hdTextures=0` |
| removes the last large texture | `hdTextures=1` |

**What C-Pod should do — in preference order:**

1. **Update the `.TXV` to match what was actually written.** Same rule Traxx follows: the record
   describes the pod, so whoever last wrote the pod owns the record.
2. **Or refuse the edit** and say why, naming the field that would go stale.
3. ⛔ **Never leave a contradiction in place silently.** A `.TXV` that disagrees with its pod is
   worse than none: readers trust it, and the failure surfaces later on somebody else's machine as
   missing art or a track that will not appear.

⚠️ **`legacyFallback` and the situation extension are ONE decision, not two.** `WORLD\<stem>.SIT`
with `legacyFallback=0` is self-contradictory — the whole point of `.SI2` is that a 1998 install
never sees a track it would render without textures. If C-Pod changes one it must change the other.

⚠️ **Model and truck pods have no `.TXV` and no `.SI2`** (§8.3). Do not invent one for them, and do
not warn about its absence — the version record is a track-pod thing.

---

# 9. ⚠️ NEW `.bin` OPCODES — only if C-Pod parses models

**Skip this section entirely if C-Pod treats `.bin` files as opaque blobs.** It matters only if
C-Pod walks the MRGL stream — most likely to gather texture dependencies for packing.

Four record types were added in August 2026. A walker that does not know them **does not merely miss
a name — it desynchronises**: it advances by the wrong number of bytes, and every opcode after that
point is read out of the middle of the previous record. The failure looks like corruption anywhere
downstream, not like an unknown opcode.

| opcode | name | size | carries a filename? |
|---|---|---|---|
| **62** | `MRGL_TEXTURE64` | `4 + 4 + 64` = **72 bytes** | ⭐ **YES** — a texture name up to 63 chars |
| **63** | `MRGL_MATERIAL` | **48 bytes**, fixed | no |
| **64** | `MRGL_MATFACET` | same stride as a normal textured facet (`type, n, a,b,c,d`, then 3 verts × 3 ints) | no |
| **65** | `MRGL_KEYFRAME64` | **4376 bytes**, fixed | ⭐ **YES** — 64 model names × 64 bytes |

⛔ **These widths are `.bin` record layouts only.** A 64-byte name field belongs inside a model file;
the pod that *contains* the model still uses 40-byte POD1 entries with `name[32]`. Applying this
table to the pod directory is the "POD64" mistake described at the top of this document.

⭐ **The two that affect dependency gathering are 62 and 65.**

- **`MRGL_TEXTURE64` (62)** is the long-name form of `MRGL_TEXTURE` (13). Its `type` and `slot`
  fields sit at the **same offsets**, so only the trailing name width differs — 64 bytes instead of
  16. A model may legitimately contain **both kinds**; each record stands alone. Miss it and you
  lose that texture from the pod *and* desync the walk.
- **`MRGL_KEYFRAME64` (65)** is the long-name form of `MRGL_KEYFRAME` (32) and names up to 64
  **models**. If C-Pod resolves model dependencies, those names live here.

⚠️ **`MRGL_MATERIAL` (63) and `MRGL_MATFACET` (64) carry no filenames** — they only need skipping by
the right stride. `MATFACET` is deliberately shaped exactly like an existing textured facet, so if
C-Pod's walker already has a "textured facet" stride arm, that arm handles it unchanged.

**Live values are in `engine\3D.H`** — if anything here disagrees with that header, the header is
right. `MONVERT_HANDOVER.md` documents 62/63/64 in full for tools that WRITE them.
