DARKER Research archive

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

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.

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

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:

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

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.