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
| Header | include/GalaxyEggbert/BlockDefinitionRegistry.hpp |
|---|---|
| Source | src/GalaxyEggbert/BlockDefinitionRegistry.cpp |
| Data it reads | src/GalaxyEggbert/Game/DecorQuartTable.hpp (the original's quarter-cell solidity table), include/GalaxyEggbert/BlockTypes.hpp (icon names) |
| Namespace | GalaxyEggbert |
| CSV export | tools/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
| Field | Type | Meaning |
|---|---|---|
type | uint16_t | The icon number itself. |
textureSource, textureIcon | BlockTextureSource, uint16_t | None for air, otherwise ObjectM and the icon's own number. |
renderMode | BlockRenderMode | Mesh shape (below). |
animation | BlockAnimationDefinition | baseIcon, up to 100 frames (-1 = not drawn), frameCount, divisor (20 Hz ticks per frame), IsAnimated(). |
collisionMask | uint16_t | Quarter-cell solidity (below). |
alphaBlend | bool | Drawn in the transparent pass (teleporter tips, the thin bar, hub signs, cross billboards). |
doubleSidedCutout | bool | Open marker cubes (icons 30, 31) whose inner faces show. |
mobileTransparent | bool | The original's "empty mask" property, kept as the original decides it. |
water, spring, temporary, bridge, switchBlock | bool | Role flags used by Blupi's controller and the interaction system. |
grassTop | bool | Icons 107-109 get the separate grass overlay. |
teleporterTip | bool | Icons 330-333. |
decor | bool | Passable 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. |
hazard | BlockHazardKind | None, Lava, Spike, Crusher, Saw, Blitz, Drip, Fan. |
fanDirection | BlockFanDirection | NegativeX, PositiveX, NegativeZ, PositiveZ for the four fan bases. |
doorKeyType | int8_t | Key object type (49, 50, 51) that opens this door, or -1. |
teleporterIndex | int8_t | 1-4 for the teleporters, or -1. |
worldSelectIndex | int16_t | Hub sign index as the original's Decor::IsWorld() reads it: 1-14, 199 for the return sign, -1 otherwise. |
Render modes
BlockRenderMode | Shape |
|---|---|
Air | Nothing (icon 0, and unsupported types). |
UniformCube | The default: a cube with the same tile on every face. |
DirectionalCube | A cube whose faces use different tiles (DirectionalCubeTiles). |
InnerPillarBox | A narrower box inside the cell (icons 76, 182, 183, 384, 385). |
ThinBar | Icon 202. |
InnerFlatPlate | A vertical plate through the cell, turnable by a plate-rotation record (grates, signs, many decorations). |
TripleCrossBillboard | Three crossed quads for small sprite parts (53-60, 63, 64). |
SawBlade | The 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:
kSolidIn3DDecor(12 icons: 49, 50, 51, 76, 77, 199, 200, 201, 235, 236, 300, 398) are decorations Blupi walks in front of in 2D but that would stand in his way in 3D (columns, posts, girder, machine pieces, trestle, slime column, balustrade). Their mask is forced to0xFFFF. Secret passages stay passable.kSolidBigDecorIcons(35explo.pngicons: palms, trees, plants, houses, machines, toys, palace pieces, the Blupi statue, the green columns and vase) mark which BigDecor pictures are solid.IsSolidBigDecorIcon(icon)tests membership. Their shape comes from the measured 3D model; without one, a columnkSolidBigDecorHeight = 2cells high over the anchor. See Collision.
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.
| Member | Description |
|---|---|
MaxObjectMIcon = 440, DefinitionCount = 441 | The 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
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
tools/VerifyDefinitionRegistries.cpp(ctest, built with the CNA target): every icon keeps its id, render modes match the expected classification, cross billboards are alpha-blended, every mask equals the original table except the solid-in-3D list,mobileTransparentand hazards agree withBlockTypes, icon 441 is unsupported, the saw/stopped-saw animations; and the same for the object registry.VerifyBlockDefinitionsCsv(the CSV gate above).tools/VerifyTerrainAnimDivisor.cpp: animation divisors per tile.- Consumers' tests exercise it too:
VerifyBlupiMovement(collision),VerifyTileFaces,VerifySceneEffects,VerifyUltra.