Object Lifecycle
A MoveObject's path: world record → MobileObjSpec → InteractionSystem → drawn as a model or a billboard.
"Objects" are everything that is not terrain: treasure chests, eggs, keys, the exit, lifts,
crates, enemies, vehicles, power-ups, shots and short-lived effects. They use the original game's
ObjectType numbers (include/GalaxyEggbert/Def/ObjectType.hpp, 0-203), and
what each type can do is declared in the ObjectDefinitionRegistry.
Described as of 2026-11-10.
1. Stored: a type 1 record in the world
In a .vwr file an object is a 47-byte MoveObjectRecord
stored as sparse metadata type 1 on the cell under its start position: type, start and end points
in raw grid floats, four patrol timings in 20 Hz ticks, and an optional icon override. The
WorldEditor and tools/worldkit.py write them.
2. Loaded: MobileObjSpec in WorldRuntime
WorldRuntime::ResyncFromWorld() (run by every load and after editor changes) calls
CollectMoveObjects() and builds the live list returned by
GetMobileObjects() / GetMobileObjectsMutable():
- types the registry does not support, or whose
placementKindisNull, are skipped; - positions move to render space (X and Z minus 50, Y unchanged);
ForceOriginalTiming()applies the few per-type timings the original fixes (the large creature, type 54, rests 152 ticks at each end);current= start,active = true,phase = 0,patrolStep = 1.
MobileObjSpec field | Role |
|---|---|
type, visualIcon | What it is; the optional icon override. |
posStart*, posEnd* | The path's ends (render space). Never changed by play. |
currentX/Y/Z | Where it is now: patrols, pushed crates and homing followers move this. |
active | False once taken, destroyed or expired. The record stays in the list; some spawns reuse an inactive slot. |
phase | Its own animation clock, in 20 Hz ticks. |
stepAdvanceTicks, stepRecedeTicks, timeStopStartTicks, timeStopEndTicks, patrolStep, patrolTime | The patrol state machine (below). |
firedByBlupi | A glue ball Blupi fired himself never hits him. |
WorldRuntime::Update(dt) advances the world's 20 Hz animation tick and every object's phase by dt × 20.
3. Simulated: InteractionSystem::Update()
Once per tick in Play, after BlupiController::Step() has given Blupi his final position,
the host calls interaction_.Update(dt, worldRuntime_, blupiX, blupiY, blupiZ, ...) with
many flags describing Blupi's state (crouching, ballooned, invincible, in a vehicle, hidden, focused,
ghost...). InteractionSystem
(src/GalaxyEggbert/Game/InteractionSystem.{hpp,cpp}) has no access to the controller,
camera or graphics: it changes objects and world blocks, plays sounds, and reports
events (EventsThisFrame()) that the host applies to Blupi, the camera
and the HUD.
Patrols and lifts
Patrolling types (registry flag patrolMotion: 2, 3, 4, 20, 32, 33, 44, 54) and lifts
(1, 47, 48) follow the original's four-phase cycle in AdvancePatrolStep(): 1 rest at
start, 2 advance, 3 rest at end, 4 recede. A lift carries Blupi: the system reports
IsRidingLift(), RideDeltaX/Z() and RideStandY(), and the host
calls BlupiController::RideLift(); the conveyor lifts 47/48 add their nudge. In the
editor, AdvanceEditorPlatforms() previews the same motion without contacts.
Crates
Crates (type 12) are pushed and pulled by UpdateCratePush() (rules in
CratePush.hpp). Every tick the host hands their cells to the controller
(SetCrateCells()), so they are solid for Blupi (see Collision).
Contact with enemies and hazards
genericContactHazard(2, 3, 4, 16, 17, 20, 96, 97): touching one kills Blupi and destroys it; type 3 cannot hit a crouching Blupi. While ballooned, 3, 16, 96 and 97 pop the balloon instead (BalloonPopped).- The wasp (44) puts Blupi in a balloon (
BalloonTouched). The large creature (54) is lethal only while it pauses at an end. Followers (96 dormant, 97 awake) wake when he comes near and home in on him. - In the jeep, tank or skateboard, small enemies and mines are destroyed and he lives. Shield, Hide and the super-Blupi cheat spare him where the original does.
A kill does not respawn Blupi directly: the system raises DeathLockRequested, the host
starts the death animation lock, and the life is resolved when it ends (section 4).
Pickups
standardPickupTouch types (treasure, egg, keys, power-ups, the exit...) are taken on
contact. As in the original the object disappears at once but the reward is applied when its
little "voyage" to the HUD ends (BeginVoyage()). Some pickups need the action button
(lollipop, drink); some are reusable and come back where they were
(RespawnPickupItem()). Types 7 and 21 are level exits; ExitReached() starts
the win cheer.
Shots and dynamite
- The shooting enemies 32 and 33 fire glue balls (type 23) at fixed frames of their rest, along a grid ray to the next solid cell, never aimed. The tank and the helicopter fire Blupi's own glue (
firedByBlupi). - Dynamite:
PlaceDynamite()when Blupi stands with his hands free; it burns through the original's 100-tick fuse and its blast destroys everydynamiteDestructibleobject in reach. The blast is lethal to Blupi. - The decoy statue (200) placed with
TryPerso()destroys the bulldozer and shooters that meet it.
Retirement and effects
Destroyed objects go through Retire(): active = false, and some types
(bulldozer, crate, spider, fish, bird, invert potion, followers) fly away as rotating debris
(ByeByePieces()). Explosions, splashes, sparkles and similar effects are themselves
short-lived objects appended to the same list.
4. What resets objects
| Event | Objects | Blupi |
|---|---|---|
| Death with lives left | Unchanged: what was taken stays taken, what was destroyed stays gone, patrols continue. | After the death lock he reappears at his last safe position (GetValidX/Y/Z(), settled on the ground), and a life flies from the HUD to him. |
| Last life lost (Lost screen) | Unchanged. As in the original (DoorsLost()), the level is not reloaded; treasure and keys found are kept and lives are reset to 3. | Return puts him back at the world's start. |
LoadMission() (new mission, re-entry, Continue, "Original") | Rebuilt from the world's records; a fresh InteractionSystem (only lives carried over). | A fresh controller at the start or hub arrival. |
| Editor change | ResyncFromWorld() rebuilds the list from the edited world. | Unchanged. |
5. Drawn: model or billboard
The host turns the simulation into a draw list each frame
(src/GalaxyEggbertCNA/GalaxyEggbertGameScene.cpp fills a SceneFrame, which
WorldRenderer draws). For every active object:
ResolveObjectVisual(type, phase, visualIcon)gives the render mode (Hidden,Billboard,SolidCube), the sprite sheet (element.png,object-m.png,explo.png,blupi.png,blupi1.png) and the icon for the current animation phase (GetObjIcon()inGame/ObjectIcons.cpp). Patrolling enemies take their per-direction icon from their patrol state.ObjectModelTable::Find(sheet, icon)looks the icon up in the table read fromavatars3d/objects/*/icons.txt. If found, the object is drawn as that 3D model, posed at the listed time of its "Frames" clip, mirrored and turned as listed, and turned further by the cell's object-turn record (metadata type 5).- Otherwise the icon is drawn as a camera-facing billboard cut from its sheet. The game keeps choosing icons exactly as the original; the model table only decides how an icon is shown.
Lifts, conveyor lifts, parked vehicles and the clay bridge have dedicated model paths; Blupi himself uses his skinned model (Rendering).
Tests
tools/VerifyInteractionSystem.cpp(ctest): patrols, contacts, pickups, shots, dynamite, cheats and timing functions without a window.VerifyDefinitionRegistries: every object capability flag matches the expected type lists;VerifyGetObjIconandVerifyObjIconAgainstReferenceDoc: icon selection;VerifyObjectModelTable: the shippedicons.txtfiles.VerifyMoveObjectTypesCna:WorldRuntime's reader of the original game's text level files (a reference path, not used for play) accepts the object types those files contain;VerifyShippedWorlds: every shipped world yields its objects.- Autoplay playthroughs exercise all of it in real worlds (Testing).