Skip to content

Dungeon Graphics Renderer

HoraceAndTheSpider edited this page Jul 22, 2026 · 5 revisions

Dungeon graphics renderer data

This page describes the SPS 439 source data used to turn dungeon map locations into the 128×76 first-person display. It is concerned with the game data and drawing routines, rather than any particular editor or preview implementation.

View cells and wall-face slots

The renderer does not calculate dungeon perspective from a general 3D formula. It uses two fixed coordinate systems:

  • 19 player-relative view cells describe the map locations which may contribute to the current view;
  • 28 wall-face slots describe the possible projected wall surfaces within the 128×76 viewport.

Dungeon_ViewCell_RelativeCoordinates.positions contains four groups of 19 signed X/Y pairs, one group for each player facing. Each word stores signed X in its high byte and signed Y in its low byte.

For each visible map cell, Dungeon_ViewCell_WallFaces.lookup supplies four bytes. These are the N/E/S/W candidate wall-face slots for that cell; $FF means that the face is unavailable from that view cell.

The source first scans all 19 view cells. It then traverses the cells from farthest to nearest to combine two 32-bit masks per cell:

  • Dungeon_ViewCell_VisibleFaces.flags contributes wall faces which the cell can make visible;
  • Dungeon_ViewCell_Occlusion.flags removes faces hidden by an opaque location.

The accumulated visible mask is finally ANDed with the accumulated occlusion mask. The surviving view cells are then drawn in the source traversal order. This is why a collection of individually plausible wall sprites can still be wrong if the view-cell masks or face order are ignored.

Dungeon_ViewCell_CentredSlots.lookup provides one centred projected slot for features such as pillars, beds, pits and pads. Nineteen bytes are used; the twentieth byte is spare/padding. $FF means that no centred sprite is available in that view cell.

Stone and wooden walls

A type-1 stone-wall location tests all four of its candidate faces. The visibility and occlusion masks decide which faces survive. Its direction field does not limit the stone block to one wall surface; that direction selects the one face on which a shelf, sign, switch or socket can appear.

A type-2 wooden-wall location is different. Its first map byte contains four independent two-bit N/E/S/W states:

Value Result
0 no wooden wall or door
1 solid wooden wall
2 open wooden doorway
3 closed wooden door

The renderer therefore draws only the sides actually present in those four fields.

Sprite selection, mirroring and perspective transforms

The 28 wall-face slots are translated to source pictures by two byte tables:

  • GFX_Main_Wall_SpriteTable selects the stone-wall picture for each slot;
  • GFX_WallComponent_SpriteMirrorTable selects wall-component, wooden-wall, door and stair pictures. Bit 7 requests the mirrored path and the low seven bits select the picture.

Centred dungeon features use GFX_CentredDungeonComponent_SpriteMirrorTable. Its first 19 entries correspond to the 19 view cells; bit 7 again selects the mirrored path. The twentieth byte is not read by the 19-cell traversal.

Main stone-wall rows always pass through the game's bit-reversal lookup and are written right-to-left. The raw ST-format artwork therefore needs this horizontal reversal even though GFX_Main_Wall_SpriteTable contains ordinary picture numbers. Omitting it reverses the masonry perspective on the near left and right walls.

Two separate 28-byte flag tables select the ordinary writer or the perspective edge/cropping path:

  • GFX_Main_Wall_DrawTransformFlags is used for stone-wall faces;
  • GFX_WallComponent_DrawTransformFlags is used for wall components, wooden walls, doors and stairs.

The tables are nearly, but not completely, identical: slots 6 and 18 differ. They should therefore remain separate resources. Bits 0 and 2 request the extra edge pass before or after the centre, while bit 1 selects the perspective centre path. The low three-bit value indexes the corresponding GFX_*_PerspectiveTrimLookup; that lookup returns 0, 1 or 2 source word-columns to trim while drawing the perspective edge.

For the nearest central component slots, source pictures 12–15 represent one half of the finished image. The renderer draws that half and then its reflected partner at 128 - x - width.

Graphic resource companions

Dungeon artwork is not self-describing. A drawable resource commonly consists of:

Resource Purpose
.gfx Raw Atari ST four-plane pixels.
.offsets Big-endian byte offsets selecting pictures within the .gfx block.
.positions Packed X, Y, width and height records. For these wall resources each four-byte record is x / 2, y, width in 16-pixel words minus 1, height minus 1.
.colours Optional palette/remapping data used by the component.

Gem sockets are a useful example. Main_Slots.gfx supplies the pixels, Main_Slots.offsets selects the socket picture, Main_Slots.positions positions and sizes it for each wall-face view, and Main_Slots.colours supplies its colour choices. All four resources are already identified in segments.xlsx.

The shared face tables described above do not replace the socket position table. They determine which projected wall face is being drawn and which transformation that face uses; Main_Slots.positions then provides the component's exact rectangle within that projected face. The same arrangement applies to signs and switches.

Wall-component colours and states

Wall signs, switches and sockets use four-byte colour-remapping entries. The map data selects the entry and also supplies the component state:

  • an empty socket retains the socket artwork but clears the final replacement-colour byte, removing the inserted gem;
  • a dim/used switch transforms its selected mask from [a,b,c,d] to [0,d,0,c];
  • sign references 1–4 select the Serpent, Dragon, Moon and Chaos emblem sheets respectively;
  • sign references 5 and above use the wall-scroll path instead of an emblem overlay.

The eight socket colour entries correspond to Serpent green, Chaos yellow, Dragon red, Moon blue, unused grey, bluish teleport, unused brown and tan teleport gems.

Pits, trigger pads and ceiling holes

Type-6 floor and ceiling features are independent. Bit 2 of the first map byte is tested first, allowing a ceiling hole to coexist with either a floor pit or a trigger pad.

The historical extracted filenames do not directly describe what is ultimately drawn:

  • Pad_Trigger.gfx is the ceiling-hole artwork;
  • Pad_Pit_High.gfx is the recolourable trigger-pad template;
  • Pad_Pit_Low.gfx is the floor-pit artwork.

Set_TriggerPad_ColourMask at $0094DC loads the normal trigger-pad mask $01050406. This value is embedded in an instruction rather than stored as a separate table, and could later be represented as TriggerPad_ColourMask EQU $01050406.

Magic-location drawing

Map type 7 does not provide three independent graphic sheets:

  • Firepath reuses the trigger-pad template and flashes between $090C0B0D and $090A0B0D from GFX_Firepath_ColourMasks at $0094D4;
  • Mindrock conditionally follows the ordinary stone-wall draw path;
  • Formwall follows the solid stone-wall draw path.

The two Firepath longwords must remain as eight bytes of addressable table data because the code selects one with an indexed load. They may nevertheless be named as Firepath_ColourMask_Frame1 and Firepath_ColourMask_Frame2 in a source representation.

Proposed SPS 439 shared-table extracts

These shared rules belong under gfx-data/. Whether they are editable is independent of whether they replace source data: tables with an exact source label should use the normal blank data_action, allowing Inspect / Data to replace their dc.* blocks with INCBIN resources.

The coordinate and two mask tables are one contiguous source region. They should use data_start followed by two data_append rows, all anchored at the existing adrEA00B8AE label. This produces three named external files without requiring new source labels at $00B946 and $00B992.

Memory address SPS 439 file position Size Proposed label Extracted file data_action
$0094D4 $09150 $08 GFX_Firepath_ColourMasks gfx-data/Firepath.colours blank
$0095C0 $0923C $14 GFX_CentredDungeonComponent_SpriteMirrorTable gfx-data/Dungeon_CentredComponents.lookup blank
$00B2BA $0AF36 $1C GFX_Main_Wall_SpriteTable gfx-data/Dungeon_MainWall_SpriteSelection.lookup blank
$00B43C $0B0B8 $1C GFX_WallComponent_SpriteMirrorTable gfx-data/Dungeon_WallComponents.lookup blank
$00B4C4 $0B140 $1C GFX_WallComponent_DrawTransformFlags gfx-data/Dungeon_WallComponent_DrawTransform.flags blank
$00B558 $0B1D4 $08 GFX_WallComponent_PerspectiveTrimLookup gfx-data/Dungeon_WallComponent_PerspectiveTrim.lookup blank
$00B64A $0B2C6 $1C GFX_Main_Wall_DrawTransformFlags gfx-data/Dungeon_MainWall_DrawTransform.flags blank
$00B6F2 $0B36E $08 GFX_Main_Wall_PerspectiveTrimLookup gfx-data/Dungeon_MainWall_PerspectiveTrim.lookup blank
$00B8AE $0B52A $98 Dungeon_ViewCell_RelativeCoordinates gfx-data/Dungeon_ViewCell_RelativeCoordinates.positions data_start
$00B946 $0B5C2 $4C Dungeon_ViewCell_OcclusionMasks gfx-data/Dungeon_ViewCell_Occlusion.flags data_append
$00B992 $0B60E $4C Dungeon_ViewCell_VisibleFaceMasks gfx-data/Dungeon_ViewCell_VisibleFaces.flags data_append
$00B9DE $0B65A $14 Dungeon_ViewCell_CentredSlots gfx-data/Dungeon_ViewCell_CentredSlots.lookup blank
$00B9F2 $0B66E $4C Dungeon_ViewCell_WallFaceSlots gfx-data/Dungeon_ViewCell_WallFaces.lookup blank

The data_append rows repeat adrEA00B8AE in the spreadsheet label column. Their unique relabel values become the generated labels before their respective INCBIN statements.

Existing labels $00B98E and $00B9DA identify the final longword of the two mask blocks and are referenced by the reverse traversal. Before replacing the group, those labels should be converted to Dungeon_ViewCell_OcclusionMasks+$48 and Dungeon_ViewCell_VisibleFaceMasks+$48 using the spreadsheet's _offset_..._0x48 convention. This preserves the code references without retaining labels inside either included file.

Door_Lock_Colours at memory $00B2D6 / SPS 439 file position $0AF52 is already extracted as data/Door_Lock.colours; it is adjacent renderer data but does not need a duplicate row.

Relevant routine responsibilities

The proposed routine names describe responsibilities confirmed from the SPS 439 code:

Source address Proposed label Responsibility
$0090D4 Draw_DungeonViewport Scans the 19 relative cells, builds the global masks and draws the surviving cells.
$00921E Draw_DungeonViewCell Resolves and draws one player-relative view cell.
$009388 Draw_DungeonLocation_ByType Dispatches map types and iterates their candidate wall faces.
$0094BC Select_Firepath_ColourMask Randomly selects one of the two Firepath masks, then enters the shared pad/pit draw path.
$0094DC Set_TriggerPad_ColourMask Loads the ordinary trigger-pad colour mask $01050406.
$0094E6 Draw_PitPadAndCeilingHole Resolves the centred view slot, draws an optional ceiling hole, then selects the floor pit or trigger-pad artwork.
$0095D4 Resolve_DungeonWallFaceDirection Converts the current face slot and player facing to N/E/S/W.
$00B2A4 Draw_MainWallFace Selects and draws one stone-wall face.
$00B3D8 Draw_WoodenWallOrDoorFace Selects solid wall, open frame and optional closed-door overlay.
$00B410 Draw_WallComponentFace Selects the wall-component picture and normal, mirrored or two-half path.
$00B458 Draw_WallComponent_TwoHalves Draws one source half plus its reflected partner.
$00B474 Select_MainWallGraphicTables Selects the main-wall .gfx, .offsets and .positions tables.
$00B486 Prepare_WallSpriteDraw Resolves picture offset and packed position into source/destination pointers and dimensions.
$00B4E0 Draw_WallComponent_Transformed Applies wall-component transform flags and centre trimming.
$00B560 Draw_WallComponent_EdgeTransform Draws the component's perspective edge pass.
$00B666 Draw_MainWall_Transformed Applies main-wall transform flags, reversal and centre trimming.
$00B6FA Draw_MainWall_EdgeTransform Draws the main wall's reversed perspective edge pass.
$00B76E Draw_WallSprite_BitReversed Writes normal wall rows after horizontal bit reversal.

See also:

Clone this wiki locally