BlockDefinitionRegistry

One BlockDefinition per block type 0-440: how it is drawn, which quarter-cells are solid, how it animates, and what role it plays in the game.

Location

Headerinclude/GalaxyEggbert/BlockDefinitionRegistry.hpp
Sourcesrc/GalaxyEggbert/BlockDefinitionRegistry.cpp
Data it readssrc/GalaxyEggbert/Game/DecorQuartTable.hpp (the original's quarter-cell solidity table), include/GalaxyEggbert/BlockTypes.hpp (icon names)
NamespaceGalaxyEggbert
CSV exporttools/DumpBlockDefinitions.cpp → tools/data/blocks.csv

Purpose

A Block's type is just a tile number from object-m.png. The registry says what that number means, in one place. The terrain renderer, Blupi's collision, the interaction system, the editor palette, the clipboard, the legacy saw upgrade, the 3D effects and the Python world checkers all read it; none of them keeps its own list of icons. If you change what a block does, change its entry here and let the consumers follow (see Tutorial: changing a block or object behaviour). The structure contains no CNA or cna-engine types.

Its sibling for MoveObjects is the ObjectDefinitionRegistry.

BlockDefinition fields

FieldTypeMeaning
typeuint16_tThe icon number itself.
textureSource, textureIconBlockTextureSource, uint16_tNone for air, otherwise ObjectM and the icon's own number.
renderModeBlockRenderModeMesh shape (below).
animationBlockAnimationDefinitionbaseIcon, up to 100 frames (-1 = not drawn), frameCount, divisor (20 Hz ticks per frame), IsAnimated().
collisionMaskuint16_tQuarter-cell solidity (below).
alphaBlendboolDrawn in the transparent pass (teleporter tips, the thin bar, hub signs, cross billboards).
doubleSidedCutoutboolOpen marker cubes (icons 30, 31) whose inner faces show.
mobileTransparentboolThe original's "empty mask" property, kept as the original decides it.
water, spring, temporary, bridge, switchBlockboolRole flags used by Blupi's controller and the interaction system.
grassTopboolIcons 107-109 get the separate grass overlay.
teleporterTipboolIcons 330-333.
decorboolPassable scenery: empty mask and no role at all, plus the standalone saw blade (which kills by its own disk test). Neither floor nor ceiling for Blupi.
hazardBlockHazardKindNone, Lava, Spike, Crusher, Saw, Blitz, Drip, Fan.
fanDirectionBlockFanDirectionNegativeX, PositiveX, NegativeZ, PositiveZ for the four fan bases.
doorKeyTypeint8_tKey object type (49, 50, 51) that opens this door, or -1.
teleporterIndexint8_t1-4 for the teleporters, or -1.
worldSelectIndexint16_tHub sign index as the original's Decor::IsWorld() reads it: 1-14, 199 for the return sign, -1 otherwise.

Render modes

BlockRenderModeShape
AirNothing (icon 0, and unsupported types).
UniformCubeThe default: a cube with the same tile on every face.
DirectionalCubeA cube whose faces use different tiles (DirectionalCubeTiles).
InnerPillarBoxA narrower box inside the cell (icons 76, 182, 183, 384, 385).
ThinBarIcon 202.
InnerFlatPlateA vertical plate through the cell, turnable by a plate-rotation record (grates, signs, many decorations).
TripleCrossBillboardThree crossed quads for small sprite parts (53-60, 63, 64).
SawBladeThe saw (378) and the stopped saw (379).

Many of these tiles are drawn as original 3D models instead when a model exists (BlockModels.hpp, Rendering); the render mode is the fallback and the geometry the editor and occlusion code reason about.

Collision masks

The original 2D game describes every tile by a 4 × 4 grid of quarter-cells (Tables::table_decor_quart, transcribed verbatim into DecorQuartTable.hpp). The registry packs that grid into 16 bits: bit row * 4 + col, row 0 at the top, 1 = solid. In 3D the mask is extruded through the Z axis. 0xFFFF is a full block, 0 is passable.

Two lists adjust the original's masks for 3D:

Animation

Lava, spike trap, crusher, saw, both waters, the four fans, the vanishing saucer (Temp), the marine plant, the lightning (Blitz) and the slime drip have frame tables copied from the original. Any non-base frame stored in a world (for example lava 69-72) resolves through its family's base definition. ResolveAnimationIconAt() adds a per-cell phase offset (13 x + 7 row + 11 z for lava, spike, drip, saw and the first water; a different offset and pace for the second water) so neighbouring cells do not animate in lockstep, and makes the spike trap snap while Blupi is not focused. The lightning uses a fixed shuffle instead of the original's random frame, so runs stay reproducible.

Public API

Verified in BlockDefinitionRegistry.hpp as of 2026-11-10.

MemberDescription
MaxObjectMIcon = 440, DefinitionCount = 441The supported range.
static const BlockDefinitionRegistry& Instance()The one immutable table, built on first use.
Get(type)The definition; types above 440 get an Air-rendered "unsupported" definition.
IsSupported(type)type <= 440.
ResolveAnimationIcon(type, rawTick)The icon to draw at a 20 Hz tick.
ResolveAnimationIconAt(type, rawTick, x, y, z, blupiFocused = true)The same with the per-cell offset.
GetBlockDefinition(type), IsSupportedBlockType(type), ResolveBlockAnimationIcon(...), ResolveBlockAnimationIconAt(...)Free inline shortcuts to Instance().
kSolidIn3DDecor, kSolidBigDecorIcons, kSolidBigDecorHeight, IsSolidBigDecorIcon()The 3D solidity lists described above.

Usage

Blupi's collision reads the mask bit of the quarter-cell a point falls in (BlupiController::IsPointSolid):

const int col = std::clamp(static_cast<int>(fracX * 4.0f), 0, 3);
const int row = std::clamp(static_cast<int>((1.0f - fracY) * 4.0f), 0, 3); // real row 0 = TOP
const std::uint16_t bit = static_cast<std::uint16_t>(1u << static_cast<unsigned>(row * 4 + col));
return (GalaxyEggbert::GetBlockDefinition(blockType).collisionMask & bit) != 0;

The legacy saw-floor upgrade picks a floor material only among full, still, harmless blocks (LegacySawFloors.hpp):

const auto& d = GetBlockDefinition(other);
if (!other || d.collisionMask != 0xFFFF || d.animation.IsAnimated() || d.hazard != BlockHazardKind::None) continue;

CSV export: tools/data/blocks.csv

The Python world tools cannot link C++, so DumpBlockDefinitions writes the registry as CSV, one row per icon 0-440 (442 lines with the header):

icon,render,collision_mask,solid,water,teleporter,fan,spring,bridge,door_key,hazard,world_select,hub_door,animated
68,1,0,1,0,0,0,0,0,-1,1,-1,0,1
378,7,0,0,0,0,0,0,0,-1,4,-1,0,1

render and hazard are the enum values; solid is standing-surface solidity as the controller decides it without sub-cell precision (any block except air, teleporters, fans, water and decor); hub_door is 1 for the hub door, 2 for the gold door. It is read by tools/levelcheck.py, tools/flightcheck.py and tools/watercheck.py.

DumpBlockDefinitions > tools/data/blocks.csv            # regenerate
DumpBlockDefinitions --check tools/data/blocks.csv      # PASS/FAIL, used by ctest
Generated file. Never edit blocks.csv by hand. The ctest VerifyBlockDefinitionsCsv runs the --check form and fails when the committed file no longer matches the C++ table; regenerate it after any registry change.

Tests

Related pages