7.4 KiB
Crusader Map Rendering Workbench
Purpose
This note starts a dedicated lane for offline Crusader map extraction and PNG rendering from the shipped data files in this workspace.
Current implementation entry point:
tools/render_crusader_map.py
Current supported data roots:
STATICfor No RemorseSTATIC_REGRETfor No Regret
Current asset note:
STATIC_REGRETin this workspace now includesFIXED.DAT- the renderer still accepts
--fixed-datso alternate map copies can be tested without changing the rest of the static asset path
The immediate goal is practical and narrow: load a fixed map, expand glob placements, decode the required shapes from SHAPES.FLX, apply GAMEPAL.PAL, and render a deterministic PNG.
Source Cross-Checks Used
The first renderer is grounded in the overlapping parts of three sources rather than in ad hoc guesses.
-
Pentagram Crusader shape/map loaders
convert/crusader/ConvertShapeCrusader.cppgraphics/Shape.cppgraphics/ShapeFrame.cppworld/Map.cppworld/MapGlob.cppgraphics/Palette.cppgraphics/TypeFlags.cpp
-
ScummVM Ultima8 Crusader paths
gfx/shape_archive.cppgfx/type_flags.cppworld/map.cppworld/glob_egg.cppworld/coord_utils.hworld/item_sorter.cppworld/sort_item.cpp
-
Local workspace evidence
docs/scummvm-crusader-reference.mddocs/pentagram-crusader-reference.mddocs/raw-0007-rendering.mdcrusader-disasm/shapedata.txtcrusader-disasm/mapdump/mapdump.py
File Formats Used By The First Tool
FIXED.DAT
The map container is treated as a header plus a map table:
- map count at file offset
0x54 - map table at file offset
0x80 - each table row is
<u32 offset, u32 size>
Each map payload is read as packed 16-byte item records:
x: u16y: u16z: u8shape: u16frame: u8flags: u16quality: u16npc_num: u8map_num: u8next: u16
Crusader-specific coordinate adjustment matches the Pentagram and ScummVM runtime loaders:
- world
x = disk_x * 2 - world
y = disk_y * 2
GLOB.FLX
GLOB.FLX is handled as a normal FLEX archive, not as a one-off format.
Each non-empty glob object contains:
- object count:
u16 - repeated entries of
x:u8 y:u8 z:u8 shape:u16 frame:u8
Glob expansion matches the Crusader GlobEgg::enterFastArea() rule in ScummVM/Pentagram:
coordmask = ~0x3ffcoordshift = 2offset = 2itemx = (parent_x & coordmask) + (glob_x << 2) + 2itemy = (parent_y & coordmask) + (glob_y << 2) + 2itemz = parent_z + glob_z
The first renderer expands glob contents and skips drawing the source glob egg itself.
SHAPES.FLX
World shapes use the Crusader shape layout documented by Pentagram/ScummVM:
- shape header: 6 bytes
- 4 bytes unknown
- 2-byte frame count
- frame header table: 8 bytes per frame
- 3-byte frame offset
- 1 unknown byte
- 4-byte frame length
- frame body header: 28 bytes
- 8 unknown bytes
- 4-byte compression flag
- 4-byte width
- 4-byte height
- 4-byte x offset
- 4-byte y offset
- then
height4-byte line offsets - then per-line RLE data
The current decoder follows the runtime line walker used in ShapeFrame::getPixelAtPoint():
- each line is a series of skip/run pairs
- compressed runs use the low bit to choose literal versus repeated-color mode
- pixels absent from the RLE stream are treated as transparent
GAMEPAL.PAL
GAMEPAL.PAL is read as 768 bytes of VGA-style palette data.
Each component is promoted from 0..63 to 0..255 using the same scaling used by Pentagram:
rgb8 = (rgb6 * 255) / 63
TYPEFLAG.DAT
The renderer currently uses Crusader's 9-byte records to extract:
- family id
- shape footpad dimensions (
x,y,z) - editor flag
This is enough for:
- skipping known egg families in the first pass
- expanding
SF_GLOBEGG - documenting future work toward a better sorter
The current tool does not yet use the footpad values for full ItemSorter-equivalent overlap resolution.
Current Projection And Painting Rules
The renderer anchors each shape at the same world-to-screen bottom point used by the runtime shape painter:
screen_x = \frac{x - y}{4}
screen_y = \frac{x + y}{8} - z
Frame placement then follows the shape-frame offsets used by the runtime sorter:
- unflipped:
left = screen_x - xoff - flipped:
left = screen_x + xoff - width top = screen_y - yoff
The renderer now uses a ScummVM/Pentagram-style dependency graph sorter rather than a plain scalar key.
The current implementation ports the crucial parts of SortItem and ItemSorter:
- footpad-derived world boxes from
TYPEFLAG.DAT - screen-diamond overlap and containment checks
below()ordering rules for flat pieces, tall pieces, roofs, translucent items, and Crusader inventory-item families- dependency expansion so overlapping items are painted only after everything behind them
This is materially better than the initial z / x+y heuristic and is the main path for reducing wall and prop overdraw artifacts, though it still omits some of the engine's more specialized runtime-only cases.
Command Examples
Render No Remorse map 0:
c:/Users/Maddo/.PYENV/PYENV-WIN/versions/3.14.3/python.exe tools/render_crusader_map.py --game remorse --map 0 --output out/map0-remorse.png
Render No Regret map 0 and emit metadata:
c:/Users/Maddo/.PYENV/PYENV-WIN/versions/3.14.3/python.exe tools/render_crusader_map.py --game regret --fixed-dat K:/path/to/REGRET/FIXED.DAT --map 0 --output out/map0-regret.png --metadata out/map0-regret.json
Render a bounded world-space region only:
c:/Users/Maddo/.PYENV/PYENV-WIN/versions/3.14.3/python.exe tools/render_crusader_map.py --game remorse --map 0 --world-rect 0 0 4096 4096 --output out/map0-quarter.png
Current Deliberate Limits
This tool is a start, not a complete engine clone.
Current gaps:
- It renders
FIXED.DATonly. It does not yet merge save-state orNONFIXED.DATstyle movable items. - It expands globs, but it does not yet emulate broader fast-area/runtime-driven materialization behavior.
- It skips several egg-family placements instead of trying to visualize their hidden runtime helpers.
- It now implements the core dependency graph sorter, but it still omits experimental occlusion grouping and some runtime-only sprite/highlight cases.
- It does not yet consume
ANIM.DAT,DAMAGE.FLX,DTABLE.FLX,WPNOVLAY.DAT, or palette transforms such asXFORMPAL.DAT. - It uses
GAMEPAL.PALdirectly and does not yet model alternate or transformed palettes. - It writes a plain RGBA PNG using only the standard library; there is no zoomed viewer, tile atlas exporter, or sprite manifest yet.
Immediate Follow-Ups
- Validate and tune the dependency sorter against representative Remorse and Regret rooms, especially tall wall seams and dense prop clusters.
- Add optional atlas export for all shapes touched by a chosen map.
- Add a second path for movable/dynamic content once the relevant Crusader save/runtime files are pinned down for both games.
- Compare a few rendered regions against known in-game screenshots to tighten projection and ordering errors.
- Add optional per-item manifest output with
(shape, frame, x, y, z, source)rows for debugging bad composites. - Revisit raw
0007rendering notes and the live executable only if the current Pentagram/ScummVM overlap model proves insufficient for specific remaining errors.