InteractionSystem

The rules of every moving and touchable object, behind an event and request boundary that keeps it free of Blupi's controller, the camera and graphics.

Location

Headersrc/GalaxyEggbert/Game/InteractionSystem.hpp
Sourcessrc/GalaxyEggbert/Game/InteractionSystem.cpp (with CratePush.hpp, TankShot.hpp, ObjectVerticalPlacement.hpp)
NamespaceGalaxyEggbert::Game
Engine dependencyNone for its logic. The header includes only ObjectiveHint.hpp, Sound.hpp, WorldRuntime.hpp and Def/BlupiAction.hpp. Through Sound it links against CNA's audio types, but it plays sounds only by channel number and its tests never load an audio device.

Purpose

Each Play tick, InteractionSystem::Update advances every object of the world and resolves its contact with Blupi, following the original game's Decor code. It works directly on the MobileObjSpec list in WorldRuntime and on Blupi's position as plain floats. It covers:

Which object type does what is not decided here but read from the capability flags of ObjectDefinitionRegistry (patrolMotion, genericContactHazard, standardPickupTouch, dynamiteDestructible, ...). Type-specific branches remain only for distinct state machines.

Collaborators

Uses

Used by

The event and request boundary

InteractionSystem never calls BlupiController, the camera, the HUD or the renderer. Anything that needs them is reported: Update() fills a list of Events that the host reads with EventsThisFrame() and applies. Facts about Blupi that it needs (crouching, ballooned, invincible, in which vehicle, whether he may receive a power, whether the action button was just pressed) come in as parameters computed by the host.

Event kindThe host then
BalloonTouched, BalloonPoppedcalls TriggerBalloon() / PopBalloon() on Blupi
SmallShakeTriggered, BigShakeTriggeredstarts a camera shake
TankFired, GlueDropStarted, GlueDroppedplays Blupi's FireTank / HelicoGlu pose, kicks the helicopter up
EnemyNearMissarms Blupi's Ouf4 reaction
ShieldGranted, PowerGranted, CloudGranted, HideGranted, InvertGranted, DynamiteTakengrants the power on Blupi (two-stage pickups carry their position for the item's return)
VoyageRequestedprojects the end points to the screen and calls BeginVoyage() (ResolvePendingVoyage)
DeathLockRequestedlocks Blupi in his death (ResolveDeathLock), then loses a life and respawns
Diedkept for tests; the live game uses DeathLockRequested

VoyageRequested and DeathLockRequested are requests: at most one of each exists per tick (the last wins, as in the original). Lift riding is reported through getters instead (IsRidingLift(), RideDeltaX(), RideStandY(), ...), which the host passes to Blupi before his next step.

Key members

A selection of the public API of InteractionSystem.hpp (verified 2026-11-10; the header is about 1 200 lines, much of it notes on the original's behaviour):

MemberSignatureWhat it does
Updatevoid Update(float dt, WorldRuntime& worldRuntime, float blupiX, float blupiY, float blupiZ, float blupiMoveDX, Sound& sound, bool blupiCrouching = false, bool blupiBallooned = false, ...)One tick of every object. About thirty parameters, almost all booleans describing Blupi's state and gates, with defaults so tests can pass only what they need.
EventsThisFrameconst std::vector<Event>& EventsThisFrame() const noexceptWhat happened this tick (see above).
UpdateCratePushCratePushOutput UpdateCratePush(float dt, WorldRuntime& worldRuntime, const CratePushInput& input)Push, hold and pull crates along X or Z at the original's speed; tells the host where Blupi's hands keep him.
CrateBeingPushedThisFrame, LastCratePushbool ...() const noexceptFor Blupi's Push animation and the crate sound.
SeedRandomvoid SeedRandom(std::uint32_t seed)Seeds the std::mt19937 used for the original's random choices; set per mission and recorded for resume and demos.
Lives, SetLives, LoseLife, GameOverCountint Lives() const noexcept, void LoseLife()The lives counter; the caller chooses the death sound.
TreasuresCollected, TotalTreasures, Key1Count ... Key3Count, LifeEggCount, DynamiteCount, BulletCountint ...() const noexceptCounters shown by the HUD.
ExitReached, ExitWasGoldenKeybool ...() const noexceptThe mission is won (the host plays the 40-tick win cheer first).
PlaceDynamite, TryPersobool PlaceDynamite(WorldRuntime&, float x, float y, float z, bool grounded, ...)Blupi's action button with dynamite or a decoy.
OpenDoorstatic void OpenDoor(WorldRuntime& worldRuntime, int gx, int gy, int gz, Sound& sound)Opens the door block at a raw cell: the block becomes air and a slide carries its icon up, as the original's Decor::OpenDoor(). Used for the hub doors when a hub loads.
BeginVoyage, VoyageActive, VoyageProgressvoid BeginVoyage(WorldRuntime&, VoyageKind kind, int iconId, bool isButtonChannel, ...)A reward or life icon flying between world and HUD; the reward is applied when it lands.
IsRidingLift, RideDeltaX/Z, RideStandY, LiftTopsNearbool / float getters, static int LiftTopsNear(...)Lift support for Blupi's next Step().
NearestObjective, NearestTreasureDistancestatic std::optional<ObjectiveHint> NearestObjective(...)The compass aid: nearest chest, then the exit.
MockeryDetect, BlupiTouchesstatic Def::BlupiAction MockeryDetect(const WorldRuntime&, float blupiX, ...)Pure queries: which taunt an enemy calls for, whether a type's contact box touches Blupi.
AdvanceEditorPlatformsstatic void AdvanceEditorPlatforms(WorldRuntime& worldRuntime, float dt)The editor's lift preview: the same patrol routine, no contacts, rewards or world writes.
CheatCleanAll, CheatAllTreasure, CheatFindExit, CheatAllKeys, CheatSetDynamitebool CheatCleanAll(WorldRuntime&), ...Entry points for the cheats.

How it is used

The host creates a fresh instance for every mission, keeping only the lives, and seeds it:

const int preservedLives = interaction_.Lives();
interaction_ = InteractionSystem();
interaction_.SetLives(preservedLives);
...
interaction_.SeedRandom(missionSeed_);

Each tick, after Blupi's step and the crate update, it calls interaction_.Update(simDt, worldRuntime_, ...), patches the terrain if tile reactions changed blocks, resolves a pending voyage, and then applies the events:

if (FindInteractionEvent(InteractionSystem::EventKind::SmallShakeTriggered))
{
    cameraShake_.Trigger(CameraShakeType::Small);
}
...
if (FindInteractionEvent(InteractionSystem::EventKind::EnemyNearMiss) && !blupi_.IsInVehicle() && ...)
{
    blupi_.ArmOuf(BlupiController::AnimState::Ouf4);
}

(from GalaxyEggbertGame::UpdateTickEvents, src/GalaxyEggbertCNA/GalaxyEggbertGameUpdate.cpp).

Rules and invariants

Tests

Related pages