Skip to content

packages/world ​

Level data and the levels. Isomorphic: the server and every client build their colliders, scene and minimap from the same LevelData, and the AI navigates it.

FileWhat
level.tsLevelData (ground size, the meshes, their placements with a kind and a collider shape, polyline roads, spawn points, nail strips, range dummies, doors and containers (crates, lockers, items on the ground) with their items and lock tags, security cameras with their sweep, NPC spawners with their role, weapon, pockets and patrol, vehicle spawners (parking bays: classes, owner, lock, key, respawn) garages (bays with their prices) and districts (a name, a title, a satire theme, the pedestrians' roles it suggests, its areas)), Level (a level with its name) and the placement math both sides share (placePoint, placementVertices, meshBounds, roadPointAt, roadLength, roadAxes, inGarage, garageAt, inDistrict). The city (GAME_DESIGN §5.3–§5.6): spawn points with a role (SPAWN_ROLES: players, police, traffic, guards, hospital; spawnsOf), doors of a kind (DOOR_KINDS: door, gate, barrier), NPC spawners of the roles driver, target and VIP (a driver's vehicle, a target's refuge) with a schedule (ScheduleDef), and shops (ShopDef: a counter and the items it sells).
geometry.tsWhat the level's triangles say, baked into world space once per level and bucketed: surfaceHeight and groundSlope (the plane or the highest up-facing terrain triangle), isOpenGround (walls within a clearance, the inside of a solid, cliffs from below; decks high enough are passed under), insideSolid, supportHeight (what carries a road), trianglesNear and triangleBlocks for the navigation grid.
ground-probe.tsWhat a tyre rolls on: a road's surface and height first (dirt is loose), else the level's triangle there by its colour (isLooseColor: earth and grass loose, greys and paint paved); groundProbe is the level's own, built once. The client's tyre marks and dust read it, and the simulation's grip in the wet and the snow.
lamps.tsWhere the level is lit at night: its street lamps (isLampMesh, lampsOf, each with its placement's index; lampHead, where a lamp's mesh shines) and lampLightAt, a pool of light round each lamp's foot (full within 5 m, none past 9), on a grid built once per level; whoever asks says which lamps are out (knocked down). The AI's sight and the client's pools of light read it.
scents.tsWhat a dog stops to sniff at: the level's street furniture and plants by their pieces (isScentMesh: a lamp post, a hydrant, a bin, a bench, a sign, a tree, a bush), each where its placement stands (scentsOf), and the nearest one to a point (nearestScent), on a grid built once per level. The simulation's dogs read it.
pieces.tsThe pieces a level is built from in the editor: the eighteen blocks (BlockKind, their scripts, source meshes, parameters (numbers and texts), colours and unit meshes BLOCK_MESHES; the door, the container, the camera, the NPC, the vehicle spawner, the garage and the district's area among them, parseItemList reading a container's items, parsePatrol an NPC's patrol, spawnerKey a spawner's key tag, parseWeights a district's pedestrians) and kit pieces (PieceDef, PieceLibrary), and the mapping both ways: placementsToLevel() (deterministic order, every impossibility a BlockError naming the node) and levelToPlacements(). The city's parameters: a spawn's role, a door's kind, an NPC's vehicle, refuge and schedule (parseSchedule, formatSchedule: HH:MM post x z; HH:MM patrol x z x z…, a timed hour HH:MM/every+for), and the shop block (a counter, its items).
level-names.tsThe naming convention: test-<words> (a test level, run by the end-to-end tests) or main-<words> (a level that ships); levelKindOf, isValidLevelName, defaultLevelName (main-riverbend by name, DEFAULT_MAIN_LEVEL, else the first main-* in name order, else the first test-*).
levels.tsThe registry, the entry @foi/world/levels and the only module that carries the level files: loadLevel(name), listLevels(), isLevelName(), DEFAULT_LEVEL, read from the generated levels/index.ts. The server imports it to pick its level and sends it in the welcome message; the tests import it for the fixtures test-core-gameplay (the city) and test-ai-pathfinding (the roads and the obstacle course of the AI); the client never does.
schema.tsLevelDataSchema (zod) for level files and the welcome (every triangle within its mesh, every placement's mesh in the level, every item id and lock tag well formed), and lintLevel() for what a valid file can still get wrong (no spawn point, a spawn inside a wall, a road through a building or floating). The level's format across releases: LEVEL_SCHEMA_VERSION, LEVEL_STEPS (none yet) and migrateLevel, which the bundled levels, FOI_LEVEL files and artifacts go through.
road-graph.tsThe roads as a directed graph of lanes, what the AI drives on: buildRoadGraph() joins the roads whose centre lines touch or cross (crossings, T junctions, continuations), with a node at every bend of a polyline and one edge per lane with its offset to the right of travel (laneOffset); nearestRoadPoint() and laneCentre() read it.
road-route.tsA route over the road graph, as a car's navigator draws one: onto the nearest road, along the lanes in their direction of travel, off at the point nearest the goal (roadRoute, an A* over the nodes; routeLength). Pure, for the client's route line; the AI plans its own
pavements.tsThe pavements, where the pedestrians walk, laid from the roads alone: nodes beside every paved road on both sides (open ground at the road's height, off every carriageway, stepping round a lamp), linked along each side, a crossing over each arm of a junction, corners where roads meet, a door in front of each building facing a street; pavementsOf (built once per level), nearestPavementNode, pavementDistances and nextPavementNode (the shortest walk).
spots.tsThe spots that come with the furniture (SPOT_PIECES, each kit piece’s in its own frame): a bench’s seats, a bus stop’s queue (a shelter and its sign one stop), a vendor’s place beside a kiosk’s or a stall’s counter (and the counter: to), a cash machine’s user, a petrol station’s pumps; levelSpots lists them in the world, once per level object, the same everywhere.
signals.tsJunctions and traffic signals: findJunctions() (where three or more ways meet, with a box around each), buildSignals() (every traffic light piece within 20 m of a junction rules it; the approaches in two groups by axis; each light shows the group of the lane under its signal head, signalHead()), signalColour(), the fixed plan by tick (green 12 s, amber 3 s, all red 2 s, the other group's turn), the same on the server and every client. A junction switched by hand (SignalOverride: green for one way, red, flashing, until a tick): activeOverride, signalFlashing, signalColour honours it; axisGroup (the group of a way), nearestSignal.
nav-grid.tsThe navigation grid: 2 m cells with the ground height, blocked by every wall within a clearance and by the inside of a solid, slopes, roads, cliffs, near-wall cells; lineClear().
nav-layers.tsThe layered navigation: the navigation grid and the floors over its ground (storeys, decks, bridges, roofs), each node with its edge heights; forEachLink() (stairs onto floors, never off an edge), navNode() (the node someone stands on by the height of their feet).
checks.tsThe checks a level passes before it is played, as a library for the platform's builds: checkLevel() (lintLevel's rules, the LEVEL_BUDGET: triangles, pieces, roads, spawns, size, pieces a car knocks loose, and no road under water; the level gate npm run level:check runs it on every bundled level) and spawnReach() (whether a point can be reached from the spawn points, on foot over the layered navigation or along a road at its own height; a point given a height on its floor).
audit/The level audit (see its README): auditLevel(), the geometric rules a level passes with nobody looking at a picture of it (props in each other, a prop in a building, buildings in each other, pieces not resting on what is under them, pieces in a carriageway, z-fighting, paint over paint, roads apart), and repairLevel(), the repair a generation makes (a faulty prop of a shared kit dropped, paint under paint left out, never anything moved). Also the z-fighting search (coplanarOverlaps, levelZFighting) and the level's paint (levelPaint).
road-paint.tsroadPaint(): the paint on the paved roads as a function of the level (edge lines, centre lines, lane lines, in steps of a metre), clear of the junctions and giving way to the level's own paint (a zebra crossing, a stop line, a grate); the client draws what it returns.
testing.tsHelpers for the tests of every package (@foi/world/testing): a flat level, boxes and wedges placed as blocks, straight and polyline roads, a crossroads with a shop for the pedestrians.
levels/*.jsonThe exported level files, written by npm run level:export in a canonical format; never edited by hand.
levels/index.tsThe registry of those files, generated by npm run level:export and checked by npm run level:check; never edited by hand.

A placement is scaled, pitched around its own x axis, yawed and moved; triangles are wound counter-clockwise seen from outside. The ground is an implicit slab with its top at y = 0. A mesh with no draw group is a collider nobody sees (the slope under a flight of stairs); a placement with shape none is a visual nobody collides with (the treads). The editor side of the pipeline is apps/tools and apps/level-editor; the whole workflow is in the Levels guide.