Walker algorithm
A walker is a point body with a radius, a desired speed, a trip, and a target. Time advances by a fixed dt. Each tick the sim first decides where the walker is going, then how it moves, then records what that did to the floor.
Social force is only the last step of how it moves. The rest of this page is the order the decisions compose. Formulas for the acceleration sum are in forces. Who appears, how they route, and when the force law is replaced are in behaviours. The exact call order, constants, and function names are in the tick note.
Layers
A walking body is steered by five layers. Each layer may replace the one below it.
| Layer | What it decides | When it is off |
|---|---|---|
| Goal | Which portal, store, seat, or rest spot the walker is bound for | Done, removed, in a store, in a lift |
| Route | A polyline of waypoints around walls, voids, full attractors, mouth aprons, formed queues, and the recent crowd | On a belt; while a queue or door-line aim has taken the heading |
| Local aim | A point on a queue axis, a door-line slot, a group place, or a curb stop | Sight and the waypoint drive the heading instead |
| Desired velocity | Speed along that heading: sampled speed, zone factor, headway, and every hold that scales v_des | Held still (loiter, seat, pick-up, zone stop); on a belt the belt speed wins |
| Acceleration | a_drive + a_wall + a_person, then a clamp | Held; on a belt or already in a store or lift. Sight yaws the heading and does not change speed |
Density, Fruin LOS, traffic counts, store visits, and crush flags are measurements. They do not enter the acceleration. Crowd does enter the route: the path cost of the last few seconds of standing and oncoming bodies, not the occupancy grid and not the LOS colour.
Lifecycle
| State | Motion |
|---|---|
| Walk | Integrated, or held at zero velocity |
| Teleport | Frozen until a lift car or a pair delay releases it |
| In store | Standing on a slot; no forces, not on the floor |
| Done / Removed | Stay in the frame so indices are stable; no forces, no spawn |
A trip is board (toward a platform or train door) or alight (toward the street). A portal with targets draws the walker's own exit at spawn; that draw is not changed by trip rules or a flip. A portal with a visit plan spawns a visitor instead: a short list of stores, optional browse and rest, optional group. The exit drawn at spawn is where they go once the plan is done.
Seed agents in the scenario JSON are already on the floor at load. Portals do the rest of the population.
One tick
Sim::step is n calls of tick. Walker-relevant work, in order:
- Release. Lifts run. Walkers whose teleport delay has elapsed are placed on the destination landing. Visitors whose store visit has elapsed step back out onto a clear spot past the door.
- Emit. Portals spawn. Tracks whose doors just opened split their alight count over the doors and emit from each door while it is open. Vehicles dock and couple: occupants step out as a party; pick-ups collect whoever is waiting.
- Plan. Visitors drain stamina and may seek rest. Group members copy the leader's plan. Attractors offer a stop. Walkers who still have to change floor pick a stair or escalator; walkers near a gate bank pick a gate. Queues, train-door lines, and crossing holds are written onto the walkers who need them.
- Snapshot. One body per agent: position, velocity, floor, heading, whether they are on a stair, and drawn height (tread height on a flight, else
z). - Accelerate. Pure function of the snapshot. Parallel when there are enough agents. Writes the three force terms.
- Move. Semi-implicit Euler for anyone not held and not on a belt. Speed is capped; a walker who wants to move cannot slide backward along its heading faster than 0.2 m/s. A step into a solid or onto a closed crossing is refused. Belts then advance, leader first; stairs clamp bodies inside the side walls.
zsnaps to the nearest floor. - Arrive. A floor change rebuilds the path. A walker that has made no progress for
unstick_srecovers. Interactions despawn into the target portal, enter a lift, enter a store, or advance the waypoint. A walked stair hands the walker to the other floor where they stand when they step past the landing line. - Repair. Every 0.5 s the current leg is re-checked against walls. Every 2 s the next 15 m of path is re-priced against the crowd; a large rise rebuilds it.
- Contacts. Overlapping walkers are pushed apart to their two radii, never through a wall.
- Measure. Occupancy grid, per-walker display density, crush disks, crowd map, clock +=
dt.
Walls are not rebuilt on later ticks. Navigation grids are cached on the scenario and cleared when geometry changes.
Spawn
A portal with mode spawn or both emits. count is a finite budget; rate_per_min spaces them. A rate of zero dumps the remaining count as fast as clear spots allow. Each spawn tries random points inside the polygon and rejects obstacles and anyone closer than two radii.
Desired speed is sampled once, from desired_speed × (1 ± speed_jitter). Radius scales slightly with that speed. p_impaired and p_disable are independent chances that the walker prefers an elevator. keep_left is the cultural side (Singapore / Indonesia).
Each walker also gets, from its spawn index so the rng stream is untouched:
- a lane share from −1 (right) to 1 (left), used to shift waypoints across a wide hall
- a wander field (if
wander> 0), used to bend the path - a preferred spot along a train, used when picking a door line
A visitor portal then draws group size from group_sizes. The first walker is the leader; members appear beside them and walk at the slowest member's speed. Spawn rates count parties, not people.
A train door is a portal the parser adds from the track. When the doors open, alight is split over the doors and each door lets a row off, one per 0.6 m lane, every 60 × lanes / alight_rate_per_min s while it is open, skipping lanes whose spot is taken (see behaviours). Whoever has not stepped off when the doors close stays on. Closing doors send released boarders back to their line.
Goal
On the same floor the target is a portal, a store door, a seat standing-spot, or a rest point. A different floor inserts a connector (below).
A portal (including a store door or train door) is a region, not a point. The route ends at the nearest part of it, 0.2 m inside its edge: the grid search stops at the first open cell touching the portal, guided by the distance to the portal's bounding box. Once the walker can see the portal (a clear line within 12 m), they steer for the nearest point 0.2 m inside it, recomputed each step. A crowd heading for a wide exit therefore fans out across its width instead of converging on its middle.
Ordinary walkers keep the exit drawn at spawn, or the portal named by target, or the next portal on the trip rotation.
Visitors keep that exit as exit_target and spend the trip on a plan:
- Draw how many stores to visit, uniformly in
stops. - Each next store is drawn among those not yet visited, by
weight × exp(−d / 40 m), withdthe straight distance to the door plus 2.5 m per metre of height. A store that would passcapacity(inside + already heading there + this party) is not drawn. - With chance
p_browse, first stand ~0.9 m out from another shopfront on the same floor for 3–8 s. - Walking drains stamina. Below
rest_belowthey look for a quiet seat or a standing rest spot out of the stream.rest_pis the chance of one planned rest after the first store, tired or not. - With no store left, they head for the exit.
The store is entered, not walked through. Inside, the walker is off the floor until dwell_s × 0.5–1.5 elapses. A full store at the door counts as visited and they move on.
Attractors (a kiosk, a retail front) offer a stop on entering the polygon, once, with probability p_affect. A seat is offered to a walker within 4 m who is walking toward it; they walk to a standing spot beside it, sit, and later stand up onto the same spot. A full attractor is a navigation obstacle for everyone else. Seats are not.
Group members do not pick. They copy the leader's target, store, and exit. While the leader is in a store they wait at the door; while the leader browses or rests they stand on a ring around that spot.
Route
The path is a walkable polyline on a 0.7 m grid. A straight cut between waypoints is kept only when the body radius stays clear of walls, voids, and full attractors, and when its sampled soft cost is no worse than the grid path it replaces.
The path is a centre line, not a rail. Each open-floor corner is shifted by the walker's lane share of that leg's slack (up to 1.5 m), so a crowd on one route walks abreast across a wide hall and passes a column on its open side. Slack is zero next to a wall and on a connector. The last 6 m into a mouth or the goal tapers the shift back onto the point.
Three soft costs sit on top of the wall term. None of them is a wall: if going round costs more than walking through, the walker walks through.
- Mouth aprons. Floor in front of every stair, escalator, and gate end. Crossing the front is expensive; walking straight in or out along the axis is cheap. Someone heading for that mouth ignores its apron.
- Crowds. Standing people and people walking toward the walker count in full; people walking the same way count a quarter. Felt density above the Fruin B/C boundary (0.35 ped/m²) adds cost, capped. The crowd at the walker's own goal does not count.
- Queue lines. A formed line behind a gate or escalator, and each column of people waiting at a train door, stamps a band. Walking through it is expensive. The walker's own queue costs nothing.
wander gives each walker its own smooth cost field (cells about 7 m) so a stream fans out. A wander path more than detour_max times the shortest path is discarded.
The path is rebuilt when:
- the destination, the held stair/escalator/gate, or the held door line changes
- the walker changes floor
- the current leg now clips a wall (every 0.5 s)
- the crowd cost of the next 15 m has grown by more than 1.5 m equivalent (every 2 s)
- the back of a belt queue they are walking to has moved more than 1.5 m
- they unstick
Walkers in a queue, on a door-line aim, waiting at a signal, captured, on a belt, or inside a connector do not replan around the crowd they are part of.
Choosing a connector or a gate
A walker on the stair route who still has to change floor weighs every stair and every escalator running the right way, in seconds: walk to the mouth (via the back of the line when one has formed), plus wait (people ahead × 1.3 s per lane, minus the walk, since that much of the queue clears on the way), plus crossing time, plus 6 s extra effort up a stair or 2 s down. They re-weigh about once a second and switch only for a saving of at least 3 s. Within 2 m of the held mouth the choice is final. An empty escalator wins when the walks are about the same; a long escalator queue spills onto the stair beside it.
A bank of fare gates is the same idea in metres: walk to the mouth plus 1.5 m per person already bound for it and closer, or riding. Side-by-side gates fill evenly. Within 2 m behind the mouth the choice is final.
Stairs are walked. Speed is desired_speed × speed_factor (the grade). On a stair at least 2 m wide that is not single file, each direction has a keep-left band whose share of the width follows the traffic on the flight and at the mouths (30–70 % each way). With nobody coming the other way the band is the whole flight. Sight yaw still works inside the band, so people go abreast and overtake. Off the flight, the first leg goes 1.8 m straight out of the mouth at the walker's own offset, then they turn.
Escalators and fare gates are belts. The walker walks onto the belt and boards near the start edge. The belt draws them onto the centreline, one file, held off the person ahead. Social forces are off. At the end they step off where the last step leaves them.
An elevator is a teleport pair with no walked zone. Two or more landings share the pair, one stop per floor. A walker who reaches the landing while the car stands there with room boards and stands still inside. The car leaves when it is full, or hold_s after the last boarding, and only for a called floor or a rider's destination. Riders bound for that floor step out a row at a time through a named door, or all at once across an open landing; the rest stay aboard.
Local aim
When a local aim is set, the acceleration step drives at that point instead of the current waypoint. Holding aims scale v_des down as the walker nears the slot, and switch sight yaw off, so they ease in and stand.
Gate and escalator queues. The line is the axis back from the mouth. Walkers heading for that mouth who stand in the strip are the queue, at any speed, so the line does not dissolve when its head steps on. Each person behind the head holds a spacing (0.75 m at a gate, 0.6 m at an escalator). Newcomers walk to the back first if they stand off to the side; they do not squeeze in from the side or wrap the head. Nobody is sent backward: a slot is never behind where they already stand. A sideways pull straightens a bent line. The head holds one spacing behind the last rider, then walks on without slowing.
Train door lines. Each door has two lines, one each side, starting beside the door edge and running in from the platform edge. A line that hits a wall, a void, a connector mouth, or the strip beside a stair well, or reaches six people, spills into a second and then a third column. Boarders pick the line of their train that costs least: half the walk to the tail, plus 4 m per person already in it, plus a pull toward their preferred spot along the train, plus a penalty when the tail stands in a mouth, plus a large penalty when the line is full. People off one stair therefore fan out instead of all taking the nearest doors. Each column is a file in arrival order. Alight-then-board: lines release only after that door has nobody left to step off. One column head per line per second.
Alighters first walk straight out from the door, past the deepest person waiting, between the two lines. On that stretch they do not sidestep. Other people walking along the platform route around the waiting columns.
Crossings. The path is planned through like open floor. The signal acts on walking. While flashing or don't-walk, a walker whose remaining path enters the crossing within 8 m eases to a stop 0.35 m short of the curb. The curb crowd fills in beside the front row rather than filing behind. Someone who stepped on during walk may finish, at 1.3 × desired speed. Nobody else can step onto a closed crossing: the step is refused like a wall. A zebra is open while every vehicle coming on its lanes could still stop.
Groups. On open floor a member near the leader steers for its place in a loose formation (two beside, then rows of three, or single file where that place is not open). A member who has fallen behind walks up to 25 % faster. The leader slows while any member is more than group_gap off. Groupmates push each other softly and do not take headway on each other.
Desired velocity
v_des starts as the sampled desired speed, then is multiplied or scaled, never added:
- zone
speed_factor(grade on a stair; belt speed replaces it on a conveyor) - queue, door-line, and lift-landing holds: a ramp to a stand a short distance short of the slot
- crossing curb: a ramp to a stand 0.35 m short, then a body-to-body gap to whoever is in front
- crossing hurry: × 1.3 while finishing a crossing they stepped onto during walk
- same-lane headway:
min(v_des, max(0, (gap − 0.08) / headway_t)), only for someone ahead in a ~0.55 m lateral band - queued
taustretches to 1.6 ×tau(0.64 s at the default, at most 0.85 s) when that gap is short, so the queue brakes more gently
A person stuck off to the side does not stop the stream. Sight does not cut speed.
Acceleration
For a walking body that is not held and not on a belt:
then clamped to ±12 m/s².
- Drive relaxes velocity toward
v_des * headingovertau(0.4 s). Backward speed against the heading is undone twice as fast.headingis the unit vector toward the current aim (waypoint, queue slot, door line, group place), then yawed by sight. - Wall is an exponential push from the nearest floor edge, hole, obstacle, or zone side, plus a tangent slide when the wall fights the drive. Corners push from the point, not from a distant edge normal.
- Person uses the nearest eight neighbours on the same floor or the same stair. The Helbing / Viswalk term is anisotropic (people ahead weigh more). Same-direction neighbours keep the lateral push and drop the component that would shove the queue backward. Oncoming neighbours, before contact, keep only 30 % of the backward push; 0.6 × the removed part becomes a sideways step away from them, to the cultural side when they are dead ahead. Soft
ped_a(1.15, not Viswalk’s 2.72) is deliberate: the stronger value splits a two-way walkway into two groups.
At the end of the tick, bodies are hard discs: overlapping walkers are pushed apart to their two radii, never through a wall, so a jam packs to contact and stops there.
Sight is a cone. It rewrites the unit heading and leaves speed alone:
- opposing traffic yaws toward the cultural side (
lane_m, small, so two-way flow stays one crowd) - a formed same-direction stream pulls the heading toward the side they already occupy, only once
follow_needpeople are in the cone - look-ahead avoidance yaws away from a predicted close approach in the next 3 s, so most head-on meetings never become a shove
- only people inside a 1.6 m corridor each side of the walker's line count for lane choice; someone several metres to the side is not in the way
On a stair, climbers and descenders see each other by drawn height, so the keep-left yaw starts before they meet at the mouth.
The defaults, clamps, and the three formulas are in forces.
When the force law is replaced
| Situation | What moves the body |
|---|---|
| Escalator or fare gate | Belt kinematics along the centreline, one file |
| Stair | Still the force law, clamped inside the side walls, heading aimed at the keep-side band |
| Elevator | Hidden; released onto the landing when the car arrives |
| In store | Standing on a slot; no forces |
Loitering / seated / waiting for a pick-up / zone stop_delay_s | Velocity held at zero |
| Door-line or queue aim | Force law, but drive heading is the slot and sight yaw is off once they are easing in |
Integration and collisions
v += a dt, cap speed at 1.8 × v_des (floors for a near-stopped body), trim backward speed along the drive heading, then x += v dt.
A step from open floor into a column, obstacle, or other solid_at is refused: the axis that stays clear is kept, that velocity component is zeroed. A walker already inside is not checked; contacts then eject them. An unstick mute does not skip this test. The same refusal keeps a walker without crossing_ok off a closed crossing.
A walked stair does not jump in plan. The far edge of a flight is a wall on the floor being left. Anyone who was on the flight and is now off it, within 0.6 m past the line, has stepped off.
Recovery
If a walking body makes no progress for unstick_s (default 3 s), or creeps in a hall for that long: on a stair they are pointed at the mouth they should use; on a belt the stuck timer is cleared; otherwise the nearest hole or obstacle is ignored for 1.6 s (soft wall force only), velocity is set sideways, and the path is rebuilt. An elevator shaft is never muted. The mute does not let them step into a solid.
Standing in a door line, waiting at a curb, waiting for a pick-up, holding a rest or browse spot, or a group member near its leader, is never stuck. This exists so a body pinned on a corner does not stand until the run ends. It is not a behavioural choice.
Measurement
After the bodies have moved, the tick writes:
- the occupancy grid — occupancy-time, mean speed, peak headcount, traffic (entries, not a step straight back into the cell just left), mean walking velocity
- per-walker density — headcount in a 1.5 m disk, same floor, for drawing. Not the grid, not a force
- the crowd map — a short-horizon average of standing and moving bodies, used the next time a path is built
- crush hotspots — LOS G (4 ped/m² on a 1 m disk) held for 2 s, merged within 3 m
LOS is Fruin’s 1971 walkway table. Stairs and queues are still coloured with that table; Fruin’s stair and queue tables are not implemented. G is not Fruin’s: it is the crush split at 4 ped/m². Units and the windowing rules are in outputs.
What the force law does not do
Escalators and fare gates do not steer with social forces. Sight does not reduce speed. Same-direction repulsion does not push the queue backward. Density and Fruin level of service do not feed back into the acceleration. Individual force terms are only on the selected walker (inspect), not in the crowd frame.