Appearance
How to Build: Checkpoint System
Imagine you've been grinding through levels, climbing higher and higher, and then you want to go back and farm an earlier area for gold. Without checkpoints, you'd have to fight your way back from the beginning every time. That's not idle-friendly at all.
Checkpoints solve this with two features: level selection (pick which level to run from the Checkpoint Map) and fast travel (teleport to any previously visited level to farm). Each level gets a checkpoint marker at its start. Walk up to it, interact, and it's unlocked forever.
One important note: death does not respawn the player at a checkpoint. Death randomizes the level seed and generates a fresh level. Checkpoints are for intentional travel, not respawn safety nets.
Checkpoint Traits
We need two traits — one for individual checkpoint entities, one for world-level progression tracking:
typescript
const IsCheckpoint = trait()
const CheckpointData = trait({
levelNumber: 0,
unlocked: false,
})And on the world itself (defined in the level structure system):
typescript
const PlayerProgression = trait({
highestUnlockedLevel: 1,
unlockedCheckpoints: [] as number[],
})IsCheckpoint is a tag for queries. CheckpointData carries the level number and lock state. PlayerProgression is the source of truth for which checkpoints the player has unlocked — this is what gets saved to disk.
Spawning Checkpoints
Each level spawns a checkpoint marker at its starting position:
typescript
function spawnCheckpoint(
world: World,
levelNumber: number,
position: THREE.Vector3
): EntityWhen spawning, check PlayerProgression.unlockedCheckpoints to see if this checkpoint was already unlocked in a previous session. Set the initial unlocked state accordingly. This way, loading a save correctly restores checkpoint visual states.
Unlocking a Checkpoint
When the player walks near a checkpoint and interacts with it:
typescript
function unlockCheckpoint(world: World, checkpoint: Entity): voidRead the CheckpointData. If already unlocked, do nothing. Otherwise, set unlocked: true on the entity and update PlayerProgression — add the level number to unlockedCheckpoints and update highestUnlockedLevel if this is a new high.
Notice how the unlock is both local (entity state) and global (world trait). The entity state drives the visual, the world trait drives the save data and the Checkpoint Map UI.
Teleporting
From the Checkpoint Map panel, the player picks a level and hits "Go":
typescript
function teleportToLevel(world: World, levelNumber: number): voidThis calls into the level manager to start the selected level. The player's position resets to the level's starting point, enemies respawn, and the road path resets. It's effectively "begin this level from scratch" — but the player keeps all their gear, gold, and experience points.
The Checkpoint Map UI
Accessible from the HUD's "Map" button. This is one of the five menu panels:
typescript
function CheckpointMap(): JSX.ElementRead PlayerProgression using useWorldTrait. Render a sorted list of unlocked checkpoints, each with a level number and a "Go" button that calls teleportToLevel. Locked levels don't appear — there's nothing to tease with since level numbers are sequential.
Keep the UI simple: a vertical list is fine for the prototype. A visual map with nodes and paths would be a nice upgrade later, but it's not necessary to prove the mechanic works.
Visual Markers
Checkpoint entities need an in-world visual so the player knows they exist:
typescript
function CheckpointRenderer(): JSX.ElementQuery all entities with IsCheckpoint and CheckpointData. Render each one as a simple 3D marker on the terrain — a pillar, a beacon, a glowing crystal, whatever fits the art style. Unlocked checkpoints should look different from locked ones (different color, glow effect, and so on) so the player gets visual confirmation that the interaction worked.
File Structure
src/core/actions/checkpoints.ts # spawnCheckpoint, unlockCheckpoint, teleportTo
src/features/ui/menus/CheckpointMap.tsx # Level select / teleport UI
src/features/checkpoints/
└── CheckpointRenderer.tsx # In-world checkpoint marker visualWhat This System Depends On
- Level Structure — level numbers and the
PlayerProgressiontrait - Player Entity — reposition player on teleport
We now have a way for players to revisit earlier content and choose their challenge level. But none of this matters if it all disappears when they close the browser tab. Time to build persistence.