Skip to content

Doors

Doors extend the static .cub map with runtime entities. The map only marks where a door exists; the bonus subsystem owns the mutable state used by interaction, collision, raycasting, rendering, and minimap feedback.

Interactive doors

A runtime state machine shared by collision, raycasting, and rendering

Door tiles are discovered from the map, but animation and passability are driven by runtime door state under app->bonus.doors.

01
map-level representation

Door symbols mark where runtime door entities must be created.

A D BONUS_DOOR_SET
02
runtime door state

Static map text is not rewritten; mutable behavior lives in door structs.

int x, y
int state
double open_progress // [0..1]
double timer
03
state machine

Interaction and timers drive deterministic transitions every frame.

CLOSED progress = 0
OPENING progress += speed * dt
OPEN progress = 1
CLOSING progress -= speed * dt
if (state == OPENING)
  open_progress += DOOR_SPEED * delta_time

if (state == CLOSING)
  open_progress -= DOOR_SPEED * delta_time
interactive passability simulation

Use the interaction button like key E. The same progress value drives visual opening and passability.

State: CLOSED · progress: 0.00

!
passability query

Movement and ray traversal stay coherent because both ask the door subsystem.

// offset = hit fraction inside the door tile [0..1]
return (offset < door->open_progress);
runtime invariants

These properties make doors safe to defend during evaluation.

one runtime entry per door tilemap.grid stays immutable after parsingcollision and DDA use the same passability queryrendering reads open_progress from runtime state

Door-capable tiles come from BONUS_DOOR_SET, currently A and D. During bonus initialization, each matching map tile is converted into one runtime door entry under app->bonus.doors.

The original map.grid is not rewritten every frame. Instead, each door keeps its own map position, state, open_progress, and timers. This separation keeps the static map stable while still allowing animated movement and passability.

Door interaction is triggered from the bonus input path, typically with E. When a valid door is found in interaction range, the door state machine changes state and progresses over time using delta_time.

The state flow is:

  • closed becomes opening
  • open_progress increases until it reaches 1.0
  • open can later become closing
  • open_progress decreases back to 0.0
  • the door returns to closed

Collision and ray traversal do not rely on the raw map character alone. They query the door subsystem to decide whether the current door slice is blocking or passable.

This is the key invariant: movement, DDA hits, visual door opening, and minimap feedback all derive from the same runtime door progress. That prevents cases where the player can walk through a visually closed door or where the raycaster draws a door differently from the collision system.

The door subsystem is responsible for:

  • discovering door tiles from the validated bonus map
  • allocating one runtime state entry per door
  • handling interaction requests
  • updating state/progress every frame
  • exposing passability queries to collision and raycasting
  • releasing door state during shutdown or level reload

At runtime, door handling preserves:

  • one runtime state entry per valid door tile
  • deterministic state transitions
  • collision and raycasting based on current open_progress
  • safe no-op behavior when no interactable door is in range
  • stable static map content after parsing
  • srcs_bonus/doors/doors_api.c
  • srcs_bonus/doors/doors_logic.c
  • srcs_bonus/doors/doors_query.c
  • srcs_bonus/doors/doors_utils.c
  • srcs/render/raycast.c
  • srcs/render/raycast_draw.c
  • include/structs_bonus.h
  • include/defines_bonus.h