Extending the Game
Where each kind of change goes, which source of truth it starts from, and what to re-run afterwards.
Decor.cpp is the authority for numbers and timing),
or be recorded as a small, deliberate 3D adaptation (M3D-NNNN) or an owner decision
(OR-, MR-, Q-). New media need approval. "It seems plausible" is not
a reason. Read the ledgers in plans/audit/ before you start: the question may already be
answered.A block behaviour: the registry first
A block's type number is its icon in Content/icons/object-m.png (0-440). Everything the game
knows about a type (render mode, collision mask, animation frames and divisor, hazard kind, water, spring,
bridge, switch, door key, teleporter, world-sign index, passable decor) is one
BlockDefinition in src/GalaxyEggbert/BlockDefinitionRegistry.cpp (header
include/GalaxyEggbert/BlockDefinitionRegistry.hpp). The collision mask is derived from the
original's quarter-cell table; deliberate 3D exceptions are named lists in the header, such as
kSolidIn3DDecor and kSolidBigDecorIcons.
- Change the registry entry (or its named list). Do not add a parallel
switchon icon numbers in a consumer. - The consumers follow by themselves:
BlupiController(collision, standing),InteractionSystem(hazards and tile reactions),TerrainRendererand its helpers (render mode, cut-outs, animation), the editor (palette, validation). - Update the expectations in
tools/VerifyDefinitionRegistries.cpp, add a movement or interaction test for the new behaviour, and refreshtools/data/blocks.csv, which the Python checkers read (VerifyBlockDefinitionsCsvfails while it is stale). - Re-run the world checks: a tile that becomes solid or passable can open or close a route.
A worked example is the tutorial A Block or Object Behaviour.
An object behaviour
Moving and interactive objects are the original's Def::ObjectType values (0-203,
include/GalaxyEggbert/Def/ObjectType.hpp). Their per-type facts live in
src/GalaxyEggbert/Game/ObjectDefinitionRegistry.cpp: render mode and texture sheet, placement
and semantic kind, patrol timing, and declarative capability flags (dynamiteDestructible,
genericContactHazard, balloonPoppableHazard, standardPickupTouch,
smallEnemy, levelExit...).
- If the change is "type X is treated like these others", flip a flag in the registry and update its
expectation in
VerifyDefinitionRegistries. - If the type has a state machine of its own (lifts, crates, vehicles, the big creature), the logic lives
in
InteractionSystemorBlupiController.InteractionSystemreports what happened as events (EventsThisFrame()) and the host applies what needs the controller, camera or HUD; keep it free of those dependencies. - Animation frames per type and phase are
GetObjIconinsrc/GalaxyEggbert/Game/ObjectIcons.cpp, locked byVerifyGetObjIconand cross-checked byVerifyObjIconAgainstReferenceDoc. - Creature patrol timing is the original's per type (BD-0016); the same table drives
CREATURE_TIMINGintools/worldkit.py, so change both or neither.
A sound
The game plays the original's 93 sound channels (Content/sounds/sound000.wav to
sound092.wav, Def::SoundChannel) with the original's per-channel volume and pitch
table (kVolumePitch in src/GalaxyEggbert/Game/Sound.cpp). There are no new sound
files: "adding a sound" means playing an existing channel at a moment where the original plays it, with
Sound::Play or, positioned in the world, Sound::PlayAt. An example from the
history: feat(audio): the lightning's zap (channel 69) under its emitter, as Decor::BlitzActif,
which plays channel 69 at each lightning emitter on the cycle ticks the original uses. Music tracks are
rendered from the original's MIDI by tools/music/render_music.py; a world's track is stored in
its header. See Audio & Music.
A model: through the converters, never by hand
Blupi, his vehicles, the objects and the terrain reliefs are converted from the original Ray Dream scenes
(the sibling data repository speedyblupi-data) and are generated files.
- An object: add a line to
tools/rd4_to_gltf/objects.txt(name, sprite sheet, icon range, scene files, options), build withtools/rd4_to_gltf/build_object_library.py, export withexport_objects.py(it runs CNA's own avatar converter, so it needs the CNA checkout). The result isavatars3d/objects/<name>_<scene>/with anicons.txtsaying which sprite icon each pose replaces; icons not listed stay billboards. The exact commands are inavatars3d/objects/README.md. - Blupi:
match_icons.pyandbuild_clip_library.pybuild a clip perBlupiController::AnimState; commands inavatars3d/blupi/README.md. Rebuilding him is expensive; compare clip by clip before replacing anything. - Terrain reliefs:
tools/terrain_relief/(tiles.txt,build_relief.py), commands inavatars3d/terrain/README.md.
Check the match against the sprites (each model's comparison sheet, tools/model_parity.py),
run VerifyObjectModelTable, refresh plans/inventory/assets.md with
python3 tools/asset_inventory.py, and look at it live (world998, the gallery, shows
every object). See Assets & Tools.
A world
Campaign worlds are Python generators in tools/worlds/; the .vwr files in
worlds3d/ are their committed output. Change the script, never the file. The full walk-through is
the tutorial Authoring a World. A new numbered
campaign world beyond the 78 is not a routine task: the hubs, HubDoors,
campaigncheck.py and the autoplay chains encode the original's structure. Players' own worlds
are made in the 3D World Editor.
An editor feature: UX only
The editor (src/GalaxyEggbert/Editor/, namespace GalaxyEggbert::Editor) is the one
place where new UX may be invented. It must not add gameplay: it places the game's existing blocks and
objects, applies the original's creature timing, and validates through the same registries. Its palette
follows the Eggbert 2 editor's order where the original specifies one; parity findings are
ED-NNNN rows in plans/audit/editor_parity.md. Keep editor logic testable without a
window and extend VerifyGEWorldEditor, VerifyGEEditorPalette,
VerifyGEEditorStorage, VerifyGEEditorObjects or VerifyGEEditorDevices;
for flows that need the screen, add or replay a step file in tools/qa_steps/.
What to re-run afterwards
| You changed | Re-run |
|---|---|
| a block or object behaviour | ctest (registries, CSV, movement and interaction verifiers), golden trace, the affected missions' autoplay scripts, tools/verify_demos.sh |
| anything in the simulation | the playthroughs and the demo replays. If the shipped demos no longer replay as recorded, bump kDemoSimulationRevision in DemoReplay.hpp and record them again (tools/record_demo.sh) |
| a sound cue | ctest (VerifyBlupiMovement checks one-shot sound cues), a live listen with QA_SOUND=1 |
| a model | VerifyObjectModelTable, the parity sheet, golden frames if a model in world999 changed |
| a world | the generator, the Python checkers, VerifyShippedWorlds, the world's playthrough |
| the editor | the editor verifiers and the relevant tools/qa_steps/editor_*.txt |
Details for each check: Testing & QA. Record what you found in the ledger
row it belongs to, and update CURRENT.md when the verified state or a limitation changes.