Chaos Strikes Back — Reverse Engineering Notes
This page consolidates the reverse-engineering findings behind Firestaff’s CSB implementation: source module inventory, the Amiga IMG1 graphics container, the CSBWin DSA (Dynamic Scripting Architecture) system, the CSB save format, the viewport rendering path, and the reference boundary between ReDMCSB and CSBWin. For narrative status/progress see CSB-Technical-Reference and for the authenticated DSA/save contract see CSB-DSA-and-Save-Internals.
1. CSB-Specific Modules
Firestaff’s CSB implementation lives in src/csb/ (357 .c files) and
include/csb_v1_* / related headers (245+ files), plus redmcsb_* modules
that port ReDMCSB functions unmodified for CSB reuse.
ls src/csb/*.c | sort # 357 files
ls include/csb_v1_* | sort # 245 files
Module families (by prefix)
| Prefix | Purpose | Example files |
|---|---|---|
csb_v1_fNNNN_* |
Direct ReDMCSB function ports (F0089-F2605), one module per function or function range | csb_v1_f0267_move_result_pc34_compat.c, csb_v1_f0247_launcher_materialization_pc34_compat.c |
redmcsb_fNNNN_* |
ReDMCSB functions shared verbatim between DM1 and CSB (text, video primitives, IMG3, save headers) | redmcsb_f0684_blit_pc34_compat.c, redmcsb_f7061_save_header_pc34_compat.c |
csb_v1_viewport_* |
Per-element/per-depth viewport decomposition (D0-D3 walls, doors, ornaments, projectiles, custom backgrounds) | csb_v1_viewport_d2l_d2r_wall_pc34_compat.c, csb_v1_viewport_d1c_f0095_floor_ornament_pc34_compat.c |
csb_v1_csbgraphics_* / csb_v1_csbwin_* |
CSBWin-specific extensions: custom CSBgraphics.dat, XOR-padded saves, DSA runtime admission, save-loader boundary |
csb_v1_csbwin_dsa_runtime_admission_pc34_compat.c, csb_v1_csbwin_save_loader_boundary_pc34_compat.c |
csb_v1_atari_* / csb_v1_fmtowns_* / csb_v1_amiga_* / csb_v1_amg_* |
Original-platform ports (Atari ST saves/animation, FM Towns CD/ANM/portrait, Amiga IMG1 graphics, Amiga sound) | csb_v1_atari_save_decode_pc34_compat.c, csb_v1_fmtowns_cd.c |
csb_v1_startup_* |
Presentation chain: C001-C005 raster phases, SWSH, entrance, credits | csb_v1_startup_raster_present_pc34_compat.c, csb_v1_f0806_entrance_loop_runtime_handoff_pc34_compat.c |
csb_v1_save_* / csb_v1_utility_* |
Original save read/write, export/import, Utility Disk transaction path | csb_v1_save_export_import_pc34_compat.c, csb_v1_utility_save_transaction_pc34_compat.c |
csb_v2_* / csb_v22_* |
Modern presentation layer: filters, HUD overlay, minimap, dynamic lighting, VFX, touch controls (non-parity, opt-in) | csb_v2_filter_crt_scanlines_pc34.c, csb_v22_inplace_draw_pc34.c |
csb_hint_oracle_* / csb_p4_* |
Hint system (HTC files) and phase-4 lighting/VFX metadata | csb_hint_oracle_htc.c, csb_p4_lighting_metadata.c |
2. IMG1 Graphics Format (Amiga)
The original Amiga v3.1 graphic stream uses IMG1 nibble-RLE, wrapped in a
big-endian DMCSB2 container. It is deliberately kept distinct from the
optional CSBWin custom-graphics override format used only as a source-study
fixture.
Container header
| Field | Encoding | Notes |
|---|---|---|
| Marker | 0x8001 (BE) |
CSB_AMIGA_GRAPHICS_CONTAINER_WORD |
| Item count | uint16 BE |
Known variants: 749 items (Amiga 3.1/3.3 ML) |
| Size bounds | 300,000–500,000 bytes | CSB_AMIGA_GRAPHICS_MIN_SIZE / MAX_SIZE gate |
| Per-item header | BE width/height | precedes IMG1 nibble-RLE payload |
Known hash-identified variants (include/csb_v1_amiga_graphics_dat.h):
| MD5 | Variant | Item count |
|---|---|---|
61fbfd56887c94adc26888a9491c6611 |
Amiga 3.1/3.3 Multilanguage | 749 |
291e1bc6803e3dc4b974c60117ca5d68 |
Amiga 3.5 English | — |
cefaddfdf5651df2c91f61b5611a8362 |
Amiga 3.5 Multilanguage | — |
21197b1d4994fd835c403d5a33dcac2b |
Amiga X.X/3.1 English | — |
Nibble-RLE encoding
Each IMG1 graphic is decoded via a 4-bit (nibble) run-length scheme, distinct
from DM1’s byte-oriented IMG3 compressed-line format. Firestaff replaced its
ExpandGraphic byte-format decoder with a dedicated IMG1 nibble-RLE decoder
(csb_v1_amiga_graphics_dat.c, csb_v1_graphics_lzw_pc34_compat.c family)
because reusing the PC IMG3 path silently mis-decoded CSB Amiga-sourced art.
Two title/entrance graphics anchor correctness:
| Graphic | Size | Role |
|---|---|---|
| C001 | 320×153 | Title screen (“CHAOS STRIKES BACK”) |
| C004 | 320×200 | Dungeon entrance backdrop |
Both decode 100% correctly through the IMG1 path per
CSB-Technical-Reference.md.
IMG1 vs IMG3 — key differences
| Aspect | DM1 IMG3 (PC) | CSB IMG1 (Amiga-derived) |
|---|---|---|
| Byte order | Little-endian | Big-endian (DMCSB2 container) |
| Unit of RLE run | Byte | Nibble (4-bit) |
| Container | Flat PC GRAPHICS.DAT offsets |
0x8001 marker + BE item count wrapper |
| Reference decoder | ReDMCSB IMAGE3.C / F0689_Img3_Expand |
ReDMCSB IMAGE1.C nibble-RLE notes + Amiga corpus |
| Firestaff module | Internal compatibility/reference helper only; it is never a CSB PC/DOS media route | csb_v1_amiga_graphics_dat.c (primary CSB path) |
CSBWin’s own CSBgraphics.dat companion format (produced by CSBGraffer /
CSBWin Viewport Compiler) is a third, separate container — classified by
csb_v1_csbgraphics_dat_classify.c and bound at runtime via
csb_v1_csbgraphics_runtime_binding.c / csb_v1_csbgraphics_runtime_plan.c.
It must not be confused with Amiga CSBGRAPHICS.DAT or treated as evidence of
an original PC/DOS CSB release.
3. DSA (Dynamic Scripting Architecture)
CSBWin’s DSA system is a stack-based scripting VM used by custom dungeons (and embedded in the original CSB dungeon for some triggers). It is unique to the CSBWin PC port — ReDMCSB does not define it. Firestaff treats a loaded DSA action as authenticated source data, never a general-purpose scripting API: unsupported opcodes and malformed records fail closed rather than falling back to a generic interpreter.
| Property | Value |
|---|---|
| Unique opcodes covered | 117 |
| Test coverage | 12 files, 9255 lines (Q-CSB-01) |
| Reference header | CSBWin DSA runtime header, 998 lines, 264 CSBWin source references |
| Execution model | Scratch stack + candidate output state; publish only on complete action |
Record identity
Every DSA record is identified by the source tuple:
(absolute_dsa_id, state, ordinal, source_action_pointer)
The pointer identity is retained at runtime — a host-constructed or copied action with matching words cannot be substituted into an authenticated runner.
Actuator binding (type-47 DSAselector)
A type-47 DB3 actuator does not name a DSA directly; its selector bits resolve through the imported source level table:
selector = (DB3.word2 >> 7) & 0x1f
dsa_id = DSALevelIndex[current_level][selector]
Binding rejects on wrong actuator type, missing index, undefined ID, or an action absent from the staged extension — never a synthesized default.
Opcode families implemented
NOOP, EQUAL, QUESTION, plus STKOP families: Loc2AbsCoord,
FetchExCellFlg, BitCount, ParamFetch/ParamStore, GlobalFetch,
PartyDistance, TimeFetch, ThisDSAId, WhoHasTalent, CountInjury,
TalentsFetch, DisableSaves, ChPoss/MonPoss, ExamineCell, Copy,
CharFetch/CharStore, SwapCharacter, CausePoison, Mastery,
MissileInfoFetch/Store, MonsterFetch, PartyFetch, Override,
Message, Overlay, Palette, ExperiencePlus, JumpGear/GosubGear.
CausePoison and CountInjury double as combat integration points (Q-CSB-08
damage-character filter), not pure stack primitives.
LOAD/STORE, banks, and control transfer
- LOAD/STORE: source-shaped, operating on local
DSAVARSand owned global banks. Out-of-bank variables/globals reject. - JUMP/GOSUB: follow CSBWin
Executeselection rules —- state/column lookup picks the first exact file-order
(state, column)action match; JUMPtransfers only within the bounded execution frame;GOSUBrecords a one-frame nested transfer and preserves outer continuation;- a missing target ends selection without inventing a synthetic action;
- depth and transfer ceilings reject the action before publication.
- state/column lookup picks the first exact file-order
- World-mutating AMPERSAND forms, unsupported loads, malformed words, and unowned pointers all reject — a rejection leaves parameters, globals, filter receipt, and dungeon state unchanged (transactional publish).
Module map
| Concern | Module |
|---|---|
| DSA runtime admission gate | csb_v1_csbwin_dsa_runtime_admission_pc34_compat.c |
| Movement/multilevel/timer filters, trigger single-step | covered by test_csb_v1_dsa_trigger_single_step_pc34_compat, test_csb_v1_f0267_loaded_chain_pc34_compat |
| Combat opcodes | csb_v1_grey_lord_combat_pc34_compat.c, damage-character filter |
| 512-byte XOR pad classification (extended saves) | csb_v1_csbwin_512_xor_pad_classify.c |
Full opcode-by-opcode detail, admitted/restored timer bridge semantics, and
the real-package DSA receipt contract (firestaff_csb_v1_csbwin_extended_dsa_handoff_probe)
are documented in CSB-DSA-and-Save-Internals.
4. CSB Save Format
CSB save handling spans two layers: the original ReDMCSB save/dungeon-tail contract (shared with DM1) and CSBWin’s extended-save format for DSA/custom dungeons.
Original save (ReDMCSB-derived)
| Concern | Module |
|---|---|
| Save header build/read | redmcsb_f7061_save_header_pc34_compat.c, redmcsb_f7062_save_header_pc34_compat.c |
| Dungeon-part checksum | redmcsb_f7059_dungeon_part_checksum_pc34_compat.c |
| Dungeon stream | redmcsb_f7063_dungeon_stream_pc34_compat.c |
| Champion text / portraits | redmcsb_f7064_champion_text_pc34_compat.c, redmcsb_f7065_portrait_slots_pc34_compat.c, redmcsb_f7067_portrait_info_pc34_compat.c, redmcsb_f7088_portrait_transfer_pc34_compat.c |
| New-adventure creation | redmcsb_f7090_make_new_adventure_pc34_compat.c |
| Dungeon/save tail | redmcsb_f0434_dungeon_tail_pc34_compat.c, redmcsb_f0435_save_tail_pc34_compat.c |
GAMEBLOCK1/body import and extended tails
CSBWin extends the original save with GAMEBLOCK2, CHARDESC, ITEM16, timers,
game-info, DSA action records, level-index data, and an optional trace
bitmap. The extension loader stages all of these before a live profile
is changed — failure in a later record invalidates the whole candidate
rather than keeping an earlier partial import (csb_v1_csbwin_save_loader_boundary_pc34_compat.c).
- The CSBWin save loader boundary rejects malformed non-empty DB11/EXPOOL tails before atomic runtime staging.
csb_v1_csbwin_dungeon_tail.chandles the CSBWin-specific dungeon tail layout distinct from the ReDMCSBF0434tail.
F0435 native provenance
F0435 provenance is recorded only after a committed import, never on a
rejected candidate — this is the “did this save actually come from a real
CSB/CSBWin file” marker used elsewhere in the runtime to gate CSBWin-only
behavior (DSA execution, extended timers).
Export/import and Utility Disk
| Concern | Module |
|---|---|
| Export/import round trip | csb_v1_save_export_import_pc34_compat.c |
| Save import path | csb_v1_save_import_path_pc34_compat.c |
| Load | csb_v1_save_load_pc34_compat.c |
| Utility Disk transaction (import/edit/inventory/dialogs) | csb_v1_utility_save_transaction_pc34_compat.c, csb_v1_utility_flow_pc34.c, csb_v1_utility_import_pc34.c |
Test coverage: 32 save/Utility Disk files (Q-CSB-09), 15 of which are executable test binaries; all pass.
For a legacy CSBWin source artifact, csb_v1_runtime_export_csbwin_source_save_to_path()
is intentionally a provenance-checked byte copy with source-slot backup
rotation. csb_v1_runtime_export_csbwin_core_save_to_path() writes the
complete legacy body only while every live event still has its original source
receipt. The verified legacy path retains the exact TIMER-record width, raw
CSBWin queue order, and complete saved-dungeon tail separately from
DB11/EXPOOL state, after the tail prefix, DB0–DB15 spans, and terminal
checksum pass. A stock CSBGAME2.DAT round-trips through independent runtime
ownership and re-authenticates. ITEM16 ownership is restored without injecting
new C37 entries because CSBWin SaveGame.cpp restores ITEM16 and TIMER data
without calling ProcessMonstersOnLevel() in the resume branch.
This is not yet a user-facing F0433 writer after play: a changed timer, heap
position, dungeon record, or EXPOOL/DSA continuation needs a source-owned
writer receipt. M11 therefore still rejects Save and Play for a mutated
resumed CSBWin GAMEBLOCK instead of emitting an incomplete CSBGAME*.DAT.
Timer queue restart boundary
For a resumed CSBWin save, TIMER/TimerQueue state remains source-owned:
core export retains original array indexes, heap topology, sequence words,
and GAMEBLOCK2 timer counters only when every live timeline entry still
has an exact saved queue-slot receipt (time, function, priority,
coordinates, cell, effect). A fired, replaced, duplicated, or unmapped event
breaks the receipt and CSBWin export rejects rather than emit a
reconstructed queue.
5. Viewport and Rendering
CSB path
csb_v1_viewport_render_frame() [src/csb/csb_v1_viewport_pc34_compat.c]
│ builds DM1_Viewport3DState from CSB_V1_ViewportConfig
▼
dm1_viewport_3d_draw_frame() [shared DM1 V1 viewport engine]
csb_v1_viewport_render_frame(CSB_V1_ViewportConfig *cfg, int party_dir, int
party_x, int party_y) is the integration entry point (called from
csb_v1_boot.c). It is a documented passthrough to the shared DM1 V1
viewport engine (source: CSBWin Viewport.cpp F0128; ReDMCSB DUNVIEW.C
F0128), reusing the same 320×200 indexed pixel format and 224×136 viewport
sub-region at screen row 33 so DM1 draw primitives work unmodified. It is a
no-op when viewport_pixels is NULL (staged-integration guard).
CSB config supplies:
viewport_pixels+stride— target pixel bufferdungeon_grid/width/height— square-type data for element routingwall_set_index— selects whichGRAPHICS.DAT/CSBGRAPHICS.DATwall set
Callback-based wall ornament resolution
Wall ornament ordinals are resolved through
csb_v1_viewport_wall_ornament_ordinal_resolver_pc34_compat.c, which feeds
the shared DM1_ViewportWallOrnamentOrdinalCallback used by
dm1_viewport_3d_draw_frame — the same callback contract DM1’s CSB-style
path (as opposed to the M11 monolithic path) uses.
For the Amiga runtime, the F0107 callback applies the source MEDIA720 scale
selection and resolves placement from authentic Amiga Graphics.DAT item 696
(C696). It fails closed when the item or selected zone is invalid and uses the
full M615 source bitmap without PC34 source cropping. The real-media utility
check covers parsing and selected anchors only; matched-frame placement,
palette/light, alcove/inscription appearance and the floor-trapdoor path still
need independent verification. See the CSB evidence ledger.
Per-depth/per-element decomposition
Unlike DM1’s M11 path (a single large m11_draw_viewport with inline
dungeon-data access), CSB’s viewport is decomposed into dozens of focused
modules, one per depth (D0-D3) × side (center/left/right) × element type:
| Depth/element | Example modules |
|---|---|
| Walls | csb_v1_viewport_d1l_d1r_wall_pc34_compat.c, csb_v1_viewport_d2l2_d2r2_wall_pc34_compat.c, csb_v1_viewport_d3l2_wall_pc34_compat.c |
| Doors (incl. partly-open) | csb_v1_viewport_d0l2_d0r2_f0111_partly_open_door_pc34_compat.c, csb_v1_viewport_d3l2_door_pc34_compat.c |
| Floor/ceiling ornaments | csb_v1_viewport_d1c_f0095_floor_ornament_pc34_compat.c, csb_v1_viewport_d1c_f0108_floor_ceiling_ornament_pc34_compat.c |
| Center fields | csb_v1_viewport_d0c_center_field_pc34_compat.c … csb_v1_viewport_d3c_center_field_pc34_compat.c |
| Custom backgrounds (11 variants) | csb_v1_viewport_custom_backgrounds_room_slot_pc34_compat.c, csb_v1_viewport_custom_background_mask_apply_pc34_compat.c |
| Things/projectiles | csb_v1_viewport_d1c_f0115_thing_pass_pc34_compat.c, csb_v1_viewport_f0115_projectile_metadata_pc34_compat.c, csb_v1_viewport_d2l2_f0115_projectile_pc34_compat.c |
F0115 first-object native graphics use the G0209 weapon[46]/armour[58]/
junk[52]/potion[21] tables with C10 blit (conditional horizontal flip).
Creature groups use per-creature transparency (G0219
coordinateSet_transparentColor) and D2/D3 palette remap tables
(G0221/G0222).
Test coverage: 47 viewport tests (Q-CSB-06) covering walls D0-D3 all sides, doors, partly-open doors, ornaments, floor/ceiling, backgrounds, center fields, footprints, and projectile routing.
CSB vs DM1 M11 path
| Aspect | DM1 M11 path | CSB path |
|---|---|---|
| Entry point | m11_draw_viewport (m11_game_view.c, ~50k-line file) |
csb_v1_viewport_render_frame → dm1_viewport_3d_draw_frame |
| Data access | Inline dungeon-data access inside the monolithic function | Routed through CSB_V1_ViewportConfig + shared DM1 V1 engine |
| Wall ornament source | Direct resolution inside M11 | Callback (DM1_ViewportWallOrnamentOrdinalCallback) via dedicated resolver module |
| Decomposition | Single large function | ~50+ per-depth/per-element modules |
| Graphics container | PC IMG3 GRAPHICS.DAT |
IMG1 (Amiga) or optional CSBWin CSBgraphics.dat |
6. ReDMCSB vs CSBWin Reference Boundary
ReDMCSB and CSBWin answer different questions and are not interchangeable evidence.
| Area | Primary evidence | What the other reference cannot prove |
|---|---|---|
| Original timers and dungeon mutation | ReDMCSB TIMELINE.C, DUNGEON.C, GROUP.C |
CSBWin TIMER queue ownership and timerObj6/8 semantics are CSBWin-only |
| CSBWin DSA/custom dungeons | CSBWin DSA.cpp, data.cpp, SaveGame.cpp |
ReDMCSB has no EXPOOL, type-47 selector tables, opcode behavior, or DSA state — the DSA system does not exist in the original engine |
| Save interoperability | ReDMCSB DEFS.H, LOADSAVE.C + per-media corpus |
ReDMCSB cannot prove GAMEBLOCK2, ITEM16, extended tails, EXPOOL, or CSBWin continuation bytes |
| Mouse, audio, interrupt timing | target-media capture; CSBWin host sources for CSBWin paths | ReDMCSB’s vector-dispatched USIOSTUB.C/MUSCSTUB.C/interrupt timing establishes a dispatch path exists, not portable/SDL-equivalent behavior |
| Title/entrance/HUD pixels | hash-identified original assets + capture | Neither reference alone defines a universal palette, cadence, or renderer across MEDIA* branches |
ReDMCSB is the primary source for original CSB engine behavior, original EVENT/timeline structure, media branches, and original save-header contracts — it is explicitly not evidence for CSBWin extensions. CSBWin is the primary (and only) source for DSA, extended saves, and CSBWin-specific champion/input behavior.
The ReDMCSB archive itself contains platform dispatch and assembly segments
rather than a universal host spec: USIOSTUB.C forwards mouse/queued-input
through library vectors, MUSCSTUB.C forwards music calls, VBLANK.C
installs interrupt handlers, GRAPH21.C carries media-specific CPSE/fuzzy-
sector logic. These establish that a code path exists on the original
platform, not that Firestaff’s SDL3 port reproduces equivalent ordering,
sampling, mixing, or copy-protection results.
Required evidence tuples
For any claimed original save format:
(media/version hash, original save bytes, parser receipt, runtime capture)
For any CSBWin DSA/save claim:
(CSBWin save hash, timer queue slot, source timer index,
DSA/EXPOOL record identity, selected source action)
Absent these tuples, Firestaff fails closed or states the route is not yet
verified. The detailed gap-tracking queue is REDMCSB-CSB-GAP-001 through
REDMCSB-CSB-GAP-013 in TODO.md.
7. Parity Evidence
parity-evidence/ contains the cross-game pass corpus. The exact current
count is repository-derived rather than maintained as a narrative number. 28
are
CSB-specific (pass*csb* or explicit CSB-scope filenames), covering DSA
opcode receipts, save loader boundaries, viewport element routing, IMG1
graphics decode, and the CSBWin extended-save handoff contract.
ls parity-evidence | grep -ci csb # 28
ls parity-evidence | wc -l # 1071
For the full pass index and evidence-document conventions, see Parity-Evidence.
See also
- CSB-Technical-Reference — Q-CSB-01 through Q-CSB-10 status and verification commands
- CSB-DSA-and-Save-Internals — authenticated DSA/save/raster contract detail
- Architecture-Overview — project-wide module map