# Resource containers and the city layout

2026-09-18. Based on the supplied installation and the unpacked executable.
Executable addresses below are image offsets, excluding the MZ header.
Resource numbers such as `00/68` use decimal slot numbers; hexadecimal resource
IDs are explicitly shown as `0044` etc.

## Results

- All five DARKER.00–.04 files are now split and decompressed: **164 resources,
  8,956,602 decoded bytes**. Every resource matches the original x86 decoder.
- Ten **128 × 128 byte grids** describe city layouts, using reusable geometry
  types rather than storing a single enormous world mesh.
- Three geometry-bank directories have been parsed: **137, 80 and 174 type
  records**, respectively. These are not counts of unique buildings or meshes.
- The original loading routines confirm the parsed bank sections and the
  byte-map-to-word-map expansion.
- Audio resources identify **Sound Images Generation 2**, Tony Williams,
  version **1.11x**. This attribution was hidden in the archives, not the EXE.

Open the standalone [interactive map viewer](../analysis/city/index.html),
the [map atlas](../analysis/city/atlas.svg), or the
[large main-layout view](../analysis/city/main-city.svg).
Colours represent tile IDs; they are **not heights, materials, or game colours**.
File row order is preserved; compass orientation is not established.

## Where the archive directory lives

The files do not need their own directory headers. The EXE contains a pointer
table at `4534`. The resource reader at `410E` splits its BX resource ID into:

```text
archive = resource_id >> 9
slot    = resource_id & 0x01FF
entry   = archive_directory[archive] + 3 * slot
```

Each directory is an array of **three-byte little-endian boundary offsets**.
Resource length is the next boundary minus the current boundary. These are
offsets into the original compressed archive, not into decompressed data.

| Archive | EXE directory offset | Resources | Compressed bytes | Decoded bytes |
| --- | ---: | ---: | ---: | ---: |
| DARKER.00 | `0CB3` | 79 | 1,195,478 | 2,499,193 |
| DARKER.01 | `0DA3` | 10 | 1,337,498 | 1,864,413 |
| DARKER.02 | `0DC4` | 14 | 1,453,028 | 2,100,338 |
| DARKER.03 | `0DF1` | 45 | 1,402,901 | 2,258,740 |
| DARKER.04 | `0C7D` | 16 | 134,178 | 233,918 |

Every directory starts at zero and ends at the corresponding file size. The
DOS seek path at `3FAB–3FC1` masks the third byte with `3F`; no upper-bit flags
occur in these five observed directories. Do not generalize this to every
possible edition or virtual/cache resource ID.

`3F85–3FA7` builds the numeric filename extension, opens the appropriate archive,
and remembers its handle. The larger loader also caches compressed resources
through conventional/EMS/XMS memory paths. `40E7` invokes the decoder at `437C`.

## Compression

The archive decoder is closely related to the EXE's recovered LZ decoder:
MSB-first bits from little-endian words, a sentinel bit initialized to `4000`,
an initial literal, literal-word runs, overlapping back-references, and an
explicit end token. **Archive literals are plain bytes/words, without the EXE
loader's evolving XOR key.**

Each resource is an independent compressed stream. All 164 decode to the
explicit end marker while consuming exactly their directory-delimited input.
No standard ZIP/WAD-style container or standard mesh format is implied.

`tools/verify_resources.py` runs the original code at `437C` in Unicorn with
linear input/output memory, selecting that path through byte `43B4 = 1`.
This does not emulate the cache or DOS I/O. It compares the machine-code output
with both the Python decoder and the extracted files, and checks truncated and
trailing-byte rejection. The result is recorded in
[`verification.json`](../analysis/resources/verification.json).

## The city grids

Slots **68–77 in DARKER.00**, resource IDs **0044–004D**, each decompress to
exactly **16,384 bytes**. Each byte is a geometry-type ID. Zero selects no
geometry. The layouts are visibly structured and include a large dense city,
another substantial layout, and eight smaller layouts.

The loader at `BB56` reads the selected resource, and `BB59–BB6B` expands its
16,384 bytes into 16,384 words in the world-data segment:

```text
on disk:   uint8 type[128][128]
in memory: { uint8 type; uint8 state; } cell[128][128]
byte offset in runtime grid = 256 * row + 2 * column
```

The second byte is initially zero; subsequent initialization and gameplay code
set flags/variant state there. The optional initialization at `BB6D–BB8B` is
one example. It is not another stored height layer.

The reader at `2DE1–2E20` selects the cell, multiplies its type by eight to
index a geometry-definition record, and follows model pointers/variants. The
128-cell row stride is also visible in the renderer's neighboring-cell accesses
around `27D1–281B`. The tests execute the original expansion loop for all ten
maps, with the optional post-expansion flags disabled.

### Map and bank selection

`BC99–BCA1` loads geometry resource `001E + byte[6202]`. Later, `BCCB–BCD1`
selects map resource `geometry_resource + byte[BCCD] + 26h`.

The observed arrangement is:

| Map slots / IDs | Associated bank slot / ID |
| --- | --- |
| 00/68 (`0044`) | 00/30 (`001E`) |
| 00/69 (`0045`) | 00/31 (`001F`) |
| 00/70–77 (`0046–004D`) | 00/32 (`0020`) |

The mode/level setup around `BC50–BC66` supplies `6202`; `BB29–BB55` supplies
the additional layout selection. Every nonzero map ID is in range for the
associated bank. Exact mission names and the complete mode-to-map schedule
are not yet reconstructed. Ten map resources do **not** imply ten missions.

The first map has **5,454 nonzero cells using 125 distinct nonzero type IDs**.
Its compressed stream is only **5,037 bytes**. Its associated geometry-bank
resource is **48,002 compressed bytes**. Together those two resources occupy
53,039 bytes, excluding textures, other images, scripts, sound, and executable
code. This is useful evidence of how economically the city is represented;
it is not a claim that the entire city's presentation needs only 53 KB.

## Geometry-bank layout

The bank loader is `1C3E–1C93`. Its resource layout is:

```text
uint8  type_count
record type[type_count]       // 8 bytes each; IDs start at 1
uint8  special_count
uint16 special[special_count] // little-endian offsets into the model pool
uint16 model_pool_size
uint8  model_pool[model_pool_size]
uint8  trailing_data[...]     // exact end from resource length
```

| Bank | Type records | Distinct type pointers | Special pointers | Model-pool bytes | Trailing bytes |
| --- | ---: | ---: | ---: | ---: | ---: |
| 00/30 | 137 | 132 | 33 | 61,289 | 1,777 |
| 00/31 | 80 | 59 | 33 | 25,483 | 898 |
| 00/32 | 174 | 128 | 33 | 54,148 | 5,789 |

The first word of each eight-byte type record is an offset into the model
pool. The loader adds `0400` and installs records at
`world_segment:A000 + type_id * 8`. It copies the model pool to offset `0400`
in the segment held at `CS:3142`, and the trailing data to `world_segment:8000`.
The special offsets are also relocated by `0400` and written into executable
tables starting at `1926`, at a 24-byte stride.

Known type-record uses:

| Bytes | Evidence |
| --- | --- |
| `+0..1` | Model-pool offset, relocated by `0400` |
| `+2..3` | Unsigned fractional column/row offsets, in 1/256-cell units; verified at `2DF8` and `2E21` |
| `+4` | Collision/world-state marker; also subtracts `value << 8` from vertical placement unless `FF` (`2A29–2A30`) |
| `+5` | Meaning pending |
| `+6` | Used as a limit while cycling per-cell variants at `BC15` |
| `+7` | Meaning pending |

Multiple type records sharing the same model offset establish real geometry
reuse; do not equate the record count with unique buildings. Model records
also link to variants (`2E06–2E1D`). Distance-dependent rendering choices read
model-header fields in `2ECB–2F13`.

A subsequent pass decoded enough drawing bytecode to export **362 of the 391
type records**, plus 99 special-object directory entries, as static OBJ inspection meshes. See [model bytecode notes](model-bytecode.md)
and the [rotatable type atlas](../analysis/models/index.html). Animation,
textures, conditional rendering and variant selection remain incomplete; these
are inspection exports, not a faithful reconstruction of the game's renderer.
The trailing data still needs a complete interpretation.

`tools/verify_city_layout.py` executes the original loaders starting immediately
after resource I/O, supplies each decoded resource in memory, and checks all
three relocated directories, special-pointer tables, model pools and trailing
blocks. It also checks all ten grid expansions. See
[`city/verification.json`](../analysis/city/verification.json).

## Other concrete discoveries

### Named sound middleware

Resources 00/33–37 begin with x86-style callable entry code and contain explicit
driver banners identifying **Sound Images Generation 2 sound drivers**, credited
to **Tony Williams / Sound Images, 1992–94, v1.11x**. Their labels are:

| Slot | Embedded label |
| --- | --- |
| 33 | Soundblaster multi-channel |
| 34 | Roland SCC1 + Soundblaster multi-channel |
| 35 | Roland LAPC1 + Soundblaster multi-channel |
| 36 | Gravis UltraSound multi-channel |
| 37 | Soundblaster AWE32 |

This identifies the audio component from the supplied bytes themselves; it
does not identify the game's compiler or claim every driver has been tested.

### DARKER.04 and mission data

The code at `BB12–BB28` derives a resource ID in archive 04 from a saved stage
byte: decrement it, use the high part after division by eight for the resource
slot, and the low three bits for a sub-selection. `BAC1–BB55` loads sections
into the executable's `F000` area and sets script pointers/layout-selection
state. Thus archive 04 contains grouped stage/mission-related material; it is
not where the large static city grid and geometry banks reside. The complete
record and scripting format remains to be traced.

### Avalon search

A case-insensitive search found no
`Avalon` in the original installation files, unpacked executable data, or any
of the 164 decoded resource files. ASCII and UTF-16LE/BE byte forms were
checked. This does not rule out text drawn into images, fragmented strings,
or another encoding.

## Reproduction and output

```sh
python3 tools/extract_resources.py
python3 tools/inspect_city.py
# Optional independent checks, requiring unicorn==2.1.4:
python3 tools/verify_resources.py
python3 tools/verify_city_layout.py
```

- `analysis/resources/`: exact decoded binaries, IDs/offsets/sizes/hashes,
  original archive hashes and decoder-verification results.
- `analysis/city/index.html`: standalone interactive map viewer; no server or
  network required. Hover for coordinates/type/directory bytes; click to
  highlight repeated occurrences. All ten maps are available in the selector.
- `analysis/city/city.json`: maps, histograms and parsed geometry-bank metadata.
- `analysis/city/map_068.csv` through `map_077.csv`: exact byte grids.
- `analysis/city/main-city.svg` and `atlas.svg`: standalone inspection figures.

The recovered models are now assembled in the [3D city explorer](../analysis/city/3d.html).
The [placement report](city-scene.md) documents cell spacing, fractional offsets,
vertical placement and the column/local-B and opposite row/local-A mapping, verified against original
code. Per-cell runtime state and unresolved animated geometry remain separate.

## Cell-state refinement

The high byte of each runtime cell is not just a destroyed/not-destroyed flag:

- **Bit 0** is cleared and recomputed by neighbor-dependent visibility/render
  traversal (`287C–28EF` and adjacent loops). It is transient renderer state.
- **Bit 7 (`80`)** selects the relative model link at header `+2` (`2E06–2E0A`).
  Scripts can set it for lists of cells (`C178`, `C87A`) or toggle a supplied
  world byte (`C1A5`). Its complete gameplay meaning is still unresolved.
- **Damage-stage bits** select traversal of the relative link at header `+0`
  (`2E0E–2E1D`). `BCB6–BCBC` patches the test mask: bank 30 uses `20`, banks
  31/32 use `60`. The unpatched image alone concealed this distinction. Save/load routines `BB90/BBC6` pack/unpack two state bits for
  eligible cells; this is persistent state, not the full runtime byte.
- Low state bits also feed model-dependent animation/parameter lookup (`319E`)
  and initialization cycles through a per-type limit (`BC08–BC21`). Their use
  must be interpreted with the relevant object/model, not as a universal field.

The destruction path is now traced: `CDB9–CE1A` checks collision/weapon conditions
and a nonzero model link, then `67C1–67CA` adds `20` to the cell state without
changing the type byte. Thus destruction does select linked replacement models.
Calling every nonzero state “destroyed” would still lose renderer/animation state.
See [colours and damage states](model-colours.md) for original-code verification.

## Gameplay identifications

- Bank **30** / map **68**: main Delphi city.
- Bank **31** / map **69**: Halon city reached after level 99.
- Bank **32** / maps **70–77**: underground tunnel components/layouts.

The flight-control trace adds a concrete semantic type: **bank 31, type 3** is
the cell tested by automatic supply-pad landing (`C75A`). See the Skimma section
of [flight controls](flight-controls.md) for conditions and verification limits.
