Rooms and Regions
Rooms are the locations players explore. Regions are logical groupings of related rooms organized as single files.
Creating Rooms
import { WorldModel, EntityType, RoomTrait, IdentityTrait } from '@sharpee/world-model';
const kitchen = world.createEntity('kitchen', EntityType.ROOM);kitchen.add(new RoomTrait({ exits: {}, isDark: false }));kitchen.add(new IdentityTrait({ name: 'Kitchen', description: 'A small kitchen with copper pots hanging from the ceiling.', properName: true, article: 'the',}));Every room needs two traits:
RoomTrait— exits, darkness, outdoor statusIdentityTrait— name, description, aliases
Room Properties
| Property | Type | Description |
|---|---|---|
isDark | boolean | Requires light source to see |
isOutdoors | boolean | Outdoor location |
exits | object | Direction-to-destination mapping |
Dark Rooms
const cellar = world.createEntity('cellar', EntityType.ROOM);cellar.add(new RoomTrait({ exits: {}, isDark: true }));cellar.add(new IdentityTrait({ name: 'Cellar', description: 'A damp cellar with stone walls.', properName: true, article: 'the',}));Players need a light source (like a lantern with LightSourceTrait and isOn: true) to see in dark rooms.
Connecting Rooms
Connections are set directly on the RoomTrait.exits property:
import { Direction } from '@sharpee/world-model';
// Direct assignmentkitchen.get(RoomTrait)!.exits[Direction.NORTH] = { destination: diningRoom.id };diningRoom.get(RoomTrait)!.exits[Direction.SOUTH] = { destination: kitchen.id };Helper Function
For convenience, define a setExits helper:
import { DirectionType } from '@sharpee/world-model';
function setExits(room: IFEntity, exits: Partial<Record<DirectionType, string>>): void { const trait = room.get(RoomTrait); if (trait) { for (const [dir, dest] of Object.entries(exits)) { trait.exits[dir as DirectionType] = { destination: dest! }; } }}
// UsagesetExits(kitchen, { [Direction.NORTH]: diningRoom.id, [Direction.DOWN]: cellar.id, [Direction.OUT]: garden.id,});Available Directions
Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WESTDirection.UP, Direction.DOWN, Direction.IN, Direction.OUTDirection.NORTHEAST, Direction.NORTHWESTDirection.SOUTHEAST, Direction.SOUTHWESTExits Through Doors
Connect rooms through a door that can be opened/closed:
import { DoorTrait, OpenableTrait } from '@sharpee/world-model';
const door = world.createEntity('oak-door', EntityType.DOOR);door.add(new IdentityTrait({ name: 'oak door', description: 'A heavy oak door with iron bands.',}));door.add(new OpenableTrait({ isOpen: false }));door.add(new DoorTrait());world.moveEntity(door.id, hallway.id);
// Connect through the doorhallway.get(RoomTrait)!.exits[Direction.NORTH] = { destination: study.id, via: door.id, // Must open door first};One-Way Exits
// Cliff edge — can go down but not back upsetExits(cliffTop, { [Direction.DOWN]: ravine.id });// Don't add UP exit from ravineOrganizing into Regions
For any story beyond a handful of rooms, organize by region. Each region is a single file containing all rooms, objects, and internal connections for that area:
src/regions/├── village.ts # Village square, tavern, shop├── forest.ts # Paths, clearing, grove├── dungeon.ts # Underground rooms and items└── castle.ts # Castle rooms and treasuresRegion Pattern
import { WorldModel, IFEntity, EntityType, RoomTrait, IdentityTrait, Direction } from '@sharpee/world-model';
export interface ForestRooms { clearing: IFEntity; path: IFEntity; grove: IFEntity;}
export function createForest(world: WorldModel): ForestRooms { // Create rooms const clearing = world.createEntity('clearing', EntityType.ROOM); clearing.add(new RoomTrait({ exits: {}, isDark: false, isOutdoors: true })); clearing.add(new IdentityTrait({ name: 'Forest Clearing', description: 'Sunlight filters through the canopy above.', properName: true, article: 'the', }));
const path = world.createEntity('forest-path', EntityType.ROOM); path.add(new RoomTrait({ exits: {}, isDark: false, isOutdoors: true })); path.add(new IdentityTrait({ name: 'Forest Path', description: 'A winding path through dense trees.', properName: true, article: 'the', }));
const grove = world.createEntity('grove', EntityType.ROOM); grove.add(new RoomTrait({ exits: {}, isDark: false, isOutdoors: true })); grove.add(new IdentityTrait({ name: 'Sacred Grove', description: 'Ancient oaks form a natural cathedral.', properName: true, article: 'the', }));
// Internal connections clearing.get(RoomTrait)!.exits[Direction.EAST] = { destination: path.id }; path.get(RoomTrait)!.exits[Direction.WEST] = { destination: clearing.id }; path.get(RoomTrait)!.exits[Direction.NORTH] = { destination: grove.id }; grove.get(RoomTrait)!.exits[Direction.SOUTH] = { destination: path.id };
// Objects in this region const mushroom = world.createEntity('mushroom', EntityType.ITEM); mushroom.add(new IdentityTrait({ name: 'red mushroom', description: 'A bright red mushroom with white spots.', })); world.moveEntity(mushroom.id, clearing.id);
return { clearing, path, grove };}Connecting Regions
Wire regions together in your story’s initializeWorld:
initializeWorld(world: WorldModel): void { const forest = createForest(world); const castle = createCastle(world);
// Cross-region connections forest.path.get(RoomTrait)!.exits[Direction.NORTH] = { destination: castle.gate.id }; castle.gate.get(RoomTrait)!.exits[Direction.SOUTH] = { destination: forest.path.id };
const player = world.getPlayer(); world.moveEntity(player.id, forest.clearing.id);}Best Practices
- Use meaningful IDs:
'forest-clearing'not'room1' - One region file per area: Keep rooms, objects, and connections together
- Return typed interfaces:
ForestRoomsmakes cross-region wiring type-safe - Test navigation: Create transcript tests for critical paths
- Consider light: Dark rooms add puzzle opportunities