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, the map atlas, or the large main-layout view. 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:
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.
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:
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:
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 and the rotatable type atlas. 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.
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
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.csvthroughmap_077.csv: exact byte grids.analysis/city/main-city.svgandatlas.svg: standalone inspection figures.
The recovered models are now assembled in the 3D city explorer. The placement report 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–28EFand 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–BCBCpatches the test mask: bank 30 uses20, banks 31/32 use60. The unpatched image alone concealed this distinction. Save/load routinesBB90/BBC6pack/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 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 for conditions and verification limits.