Dungeon Graphics Renderer - HoraceAndTheSpider/Bloodwych-68k GitHub Wiki
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 four columns are perspective-dependent rather than one universal N/E/S/W order. The seven left-hand view cells use N/W/S/E, the seven right-hand cells use N/E/S/W, and the five central cells use N/E/W/S. This asymmetry is required to expose the correct inner face of wooden walls at the left and right edges of the view.
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.flagscontributes wall faces which the cell can make visible;Dungeon_ViewCell_Occlusion.flagsremoves 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.
View cell 18 is the player's current map square. Its north/front wooden edge uses wall-face slot 27, the same source picture and position as the south edge of view cell 17 immediately ahead. Its east and west edges use slots 23 and 11; the south edge behind the player is not visible. This nineteenth cell also supplies the dedicated inside-cell positions for open large doors, stairs, pads, pits and ceiling holes.
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_SpriteTableselects the stone-wall picture for each slot;GFX_WallComponent_SpriteMirrorTableselects 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_DrawTransformFlagsis used for stone-wall faces;GFX_WallComponent_DrawTransformFlagsis 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.
Alternating floor, ceiling and main-wall pattern
Draw_DungeonViewport derives one temporary pattern state from the player position and facing:
pattern parity = (player X + player Y + facing) & 1
Player X, Y and facing are read from character-state offsets $16, $17 and $18. Moving one square or turning 90 degrees changes the parity; turning 180 degrees preserves it.
For parity 1, Draw_FloorAndCeiling copies FloorCeiling.gfx normally and the main-wall path selects its second group of perspective pictures through GFX_Main_Wall_SpriteTable, bit-reversing their rows. For parity 0, the floor and ceiling rows are horizontally bit-reversed and main-wall source indexes are the wall-face slot itself. Slots 24–27 are shared by both main-wall states.
The alternation applies to floor, ceiling and main stone-wall texture. Wooden walls, doors, wall overlays and centred dungeon features retain their normal render paths. The dispatcher at $00B074 is labelled Draw_MainWall_AlternatingPattern; $00B2A4 is the already-labelled Draw_MainWallFace lookup-selected/bit-reversed branch.
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.
Switch reference zero takes a special branch and leaves its complete replacement mask black. Non-zero switches select one of the eight GFX_Switches_Colours records using (map X + map Y) & 7.
Generated signs and wall scrolls use (map X + map Y) & 7 for their colour record. A reference-zero generated sign independently selects one of four emblems using (2 * map X - map Y) & 3. The four named tower signs retain their fixed emblem and colour records.
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 extracted resources are named for what is ultimately drawn:
Ceiling_Hole.gfxis the ceiling-hole artwork;Trigger_Pad.gfxis the recolourable trigger-pad template;Floor_Pit.gfxis the floor-pit artwork.
Floor pits and trigger pads share FloorPit_TriggerPad.offsets and
FloorPit_TriggerPad.positions. Ceiling holes use their own
Ceiling_Hole.offsets and Ceiling_Hole.positions.
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.
All three type-6 resources contain a nineteenth position record for the player's square. The centred-component lookup bypasses its ordinary sentinel and directly selects source picture 11, allowing the current-cell floor feature and ceiling hole to coexist.
Two historical extraction lengths omitted final inside-view source pixels:
| Resource | SPS 439 start | Correct size | Previous size |
|---|---|---|---|
Door_Open.gfx |
$02D2DC |
$1B68 (7016) |
$1B60 (7008) |
Ceiling_Hole.gfx |
$031BE4 |
$01B8 (440) |
$0190 (400) |
Each corrected resource ends exactly at the next labelled block. The additional bytes are graphic pixels, not separate lookup data.
Magic-location drawing
Map type 7 does not provide three independent graphic sheets:
- Firepath reuses the trigger-pad template and flashes between
$090C0B0Dand$090A0B0DfromGFX_Firepath_ColourMasksat$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. |
$00B1C6 |
Select_MainSwitch_ColourMask |
Selects the switch palette table and enters the coordinate-derived colour lookup. |
$00B1CC |
Calculate_WallOverlay_ColourIndex |
Calculates map X + map Y for generated sign, scroll and switch colouring. |
$00B1D4 |
Load_WallOverlay_ColourMask |
Masks the colour index to three bits and loads one four-byte palette record. |
$00B2DE |
Draw_Main_Door_Or_Stairs |
Selects open/closed door artwork and lock mask, or dispatches the shared stair path. |
$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: