Collision System

AABB voxel collision — ResolveY, ResolveXZ, and block solidity rules.

AABB Shape

Blupi's hitbox is an axis-aligned bounding box (AABB):

Width  = 2 * kHalfW = 0.70 units   (X and Z)
Height = 2 * kHalfH = 1.40 units   (Y)

The pivot point (pos) is at the bottom centre of the AABB (i.e., feet position).

Block Solidity

bool IsSolid(int wx, int wy, int wz):
    if wx < 0 || wy < 0 || wz < 0: return true   // treat out-of-bounds-low as solid floor
    if wx >= 100 || wy >= 100 || wz >= 100: return false  // out-of-bounds-high is air
    block = world_->getBlock(wx + kWCX, wy, wz + kWCZ)
    return !block.isAir()

Note: kWCX = kWCZ = 50 translates gameplay coordinates to world storage coordinates. The world Y axis is not offset.

ResolveY — Vertical Collision

Called after applying gravity and Y velocity displacement each frame:

ResolveY(pos):
  footY = floor(pos.y)               // block row that feet are in
  headY = floor(pos.y + 2*kHalfH)

  // Falling — check floor
  if vel_.y <= 0:
      if IsSolid(round(pos.x), footY, round(pos.z)):
          pos.y = footY + 1          // snap feet to top of solid block
          vel_.y = 0
          onGround_ = true
  else:
      onGround_ = false

  // Rising — check ceiling
  if vel_.y > 0:
      if IsSolid(round(pos.x), headY, round(pos.z)):
          pos.y = headY - 2*kHalfH  // push head below block
          vel_.y = 0

ResolveXZ — Horizontal Collision

Called after applying X and Z displacement each frame:

ResolveXZ(pos):
  bodyWY = round(pos.y)         // which Y block row the body occupies

  // +X face
  if IsSolid(round(pos.x + kHalfW), bodyWY, round(pos.z)):
      pos.x = round(pos.x + kHalfW) - kHalfW
  // -X face
  if IsSolid(round(pos.x - kHalfW), bodyWY, round(pos.z)):
      pos.x = round(pos.x - kHalfW) + kHalfW
  // +Z face
  if IsSolid(round(pos.x), bodyWY, round(pos.z + kHalfW)):
      pos.z = round(pos.z + kHalfW) - kHalfW
  // -Z face
  if IsSolid(round(pos.x), bodyWY, round(pos.z - kHalfW)):
      pos.z = round(pos.z - kHalfW) + kHalfW
Design note: Horizontal collision only checks the body centre row (bodyWY), not a full swept AABB. This is consistent with the original mobile-eggbert approach and sufficient for the game's block-scale geometry.

Hazard Tiles

Lava (typeId=68) and Spike (typeId=373) blocks are solid for movement but also trigger blupiHit_ when stood on. This check happens in Blupi::Update() after ResolveY(): if onGround_ and the block below is a hazard type, the hit flag is set. (Needs verification — exact implementation.)

Fall Death

// After position update and collision resolution:
if pos.y < -10.0f:
    fallDeath_ = true
    pos = spawnPoint_
    vel_ = Vector3::ZERO

GalaxyEggbertGame::UpdatePlay() checks blupi_->WasFallDeath() each frame and deducts a life.

Object Collision

Blupi↔object collision is handled in Decor::CheckContact() using a simple sphere test (radius 0.85 units), not the full AABB. See Object Lifecycle.