Movement and navigation
Creatures move with the players’ integrator and find their way through a
world players are editing: they know cheaply whether they can reach a target,
search within a bounded budget on worker threads, follow a corridor, and
repair it locally when a block changes under it. The design and its rationale
are in Talos C (docs/superpowers/specs/2026-09-27-talos-c-movement-navigation-design.md);
this page is the current contract for what phases C0, C1, C2 (terrain
breadth), C3 (crowds: shared flow fields, ORCA, flocking and Reduced-LOD
integration), C3a (root motion for agents) and C4 (flight) built.
Source: manifold-nav (crates/manifold-nav/) (wasm-safe logic crate:
api always, the engine behind the default engine feature), the integrator
inputs in manifold_player::integrator::character (crates/manifold-player/src/integrator/character.rs),
the server wiring in manifold_server_state::nav (crates/manifold-server-state/src/nav/)
and the world change log in manifold_world::change_log (crates/manifold-world/src/change_log.rs).
Tests: crates/manifold-nav/tests/ (stored voxel fixtures under
tests/fixtures/; flight in flight.rs; doors, climbing, hazards,
swimmers, walker_large and surface groups in terrain.rs; flow fields
in flow.rs), the steering unit tests in manifold-nav’s steer.rs,
crates/manifold-player/tests/character_bit_identity.rs,
manifold-player’s integrator::climbing_tests,
crates/manifold-server-state/tests/motion_override_steps.rs and the
nav::tests module of manifold-server-state.
One integrator, three inputs
integrate_character_tick(state, input, motion, knock, frame_yaw, tick, sampler, params, dt)
is the one movement step for players and agents. state is
CharacterMotionState (position, velocity, grounded, mode, ground frame and
carry; Daedalus D §4.1):
| Input | Type | Players | Agents |
|---|---|---|---|
| Body constants | CharacterParams | CharacterParams::PLAYER, the old global constants | the profile’s box, step, jump velocity and walking speed |
| Drive | CharacterInput | Player(&MovementInput): keys, horizontal velocity from yaw | Agent(&DriveInput): analog world velocity, vertical, jump |
| Override | Option<&MotionOverride> | from MotionOverrides by the step’s client input tick | from MotionOverrides by the server tick |
With the player’s body and keys, no override, no knock and no carry it is today’s integrator bit for bit (a verbatim copy of the pre-C0 integrator is compared over seeded scenes; the canary hash is pinned).
Knock and carry (Daedalus D §6.6) are the integrator’s, for players and
agents alike. knock is a one-shot world Δv: its vertical part goes into
velocity (an upward knock clears on_ground), its horizontal part into
carry_velocity; a flyer carries the whole knock. Carry adds to the input’s
or drive’s velocity (not an override’s), decays 12 s⁻¹ grounded or flying and
0.5 s⁻¹ in the air, is capped at 80 m/s, and a contact no step-up took stops
it on that axis. The step also reports a landing or wall ImpactSample
(Daedalus D §10.2). An agent’s carry lives in its CharacterCarry
component, server-side; navigation keeps only the knockback lock.
Overrides. A MotionOverride is one integrated step’s displacement,
keyed by that step’s tick label: velocity (world frame for Ability(id),
character frame for RootMotion: −Z forward, rotated by the facing with
manifold-det), yaw_delta (agents turn their facing by it past the
turn-rate cap; players discard it), vertical: Some(v) (gravity suspended),
start_tick, ticks, stop and source. At most one applies to a step;
RootMotion beats Ability. While one applies the keys or drive and jump are
ignored and look still applies. Every applied step returns an
OverrideReport { requested, achieved, stopped } (stopped: an Ability
override with COLLISION hit something against its direction; movement ends
it). Reports collect per entity in MotionOverrideReports.
Writers push into the entity’s MotionOverrides component; the server player
loop and AgentMovementSystem look each step up and prune expired entries;
the client predictor looks up the local player’s and RingEntry records the
applied override, so replay after a correction reproduces it.
Root motion for agents (C3a, §5.7). Pygmalion’s AnimLogicSystem pushes
an agent’s RootMotion override for the server tick while an
Animation-authority play leads its root (see
animated characters (docs/architecture/animated-characters.md));
AgentMovementSystem applies it that tick. Its yaw_delta turns the facing
past the turn-rate cap, and the step reports
MotionSource::Scripted(ScriptedMotion::RootMotion { play, start_tick }):
the leading playback’s source (a machine key or 0x8000 | group) and its
start tick, from RootDelta::lead. B’s overrides report
ScriptedMotion::Ability { id, start_tick }. A play integrates every tick at
any LOD: the override wakes an agent at rest, and a Reduced agent never
strides over a tick with an override, so each step’s displacement lands
with its own dt. F presents a root-motion play by snapshots
(committed root-motion segments are F2’s): the publisher leaves segment
mode on the play’s first step with a handover snapshot. An authored charge
is such a play; code-driven Dash and Leap stay for simple motion.
A player entity without a live connection collides against the server’s own
loaded chunks (FrontierSampler over WorldSpace::is_chunk_loaded).
Climbing. A body whose climb_speed is positive and which overlaps a
climbable cell (CollisionSampler::climbable) climbs instead of falling:
DriveInput::climb Up/Down move at ±climb_speed, Hold stays, and
None slides at −0.15 · climb_speed, or climbs when the body presses into
a wall. Players keep climb_speed 0, so they never reach the branch (the
bit-identity tests pin it). CharacterIntegration::climbing reports it.
Agents
An agent is an entity with manifold-nav:locomotion (Tracked<Locomotion>,
server-only, persisted). Archetypes name its profile:
components: {
"manifold-sim:position": (), "manifold-sim:velocity": (),
"manifold-sim:yaw": (), "manifold-sim:pitch": (), "manifold-sim:motion-mode": (),
"manifold-nav:locomotion": (profile: "flagship-game:boar"),
},
Adding it adds MoveIntent, NavAgent, LocomotionState,
MovementLod, InWorldSpace and AgentMotion (F’s replication
marker). Agents never
carry ControlledBy and never read PendingInputs.
| Component | Written by | Read by |
|---|---|---|
MoveIntent | D’s tasks (desired_velocity, jump, sprint, look_at, follow: Path(handle), speed_percent) | the follower, the controller |
NavAgent | the follower (goal, corridor, pending splice or replan, progress watch) | F (Corridor::lookahead, Corridor::epoch) |
LocomotionState | AgentMovementSystem every integrated tick (actual velocity, edge, MotionSource, cruise speed, stop distance, landed, knockback carry) | F’s segments, D’s hearing; never animation |
MovementLod | D’s LOD pass (Full, or Reduced { k } with k = 2 or 4) | the follower (request priority), steering (ORCA eligibility), AgentMovementSystem (the stride) |
Locomotion.head | the controller | AgentMovementSystem, which copies the achieved head yaw into D’s AiPresentation.look_yaw (F’s agent view reads it) |
The body itself is the usual Position, Velocity, Yaw (the facing),
Pitch (head pitch), MotionMode and OnGround, written at the server tick.
Tick order
Time → HeldActionsApply → ResolveActionHandles → (D's AI block)
→ NavMaintenance → NavFollow → Steering
→ AnimBind → AnimLogic → PlayerMovement → AgentMovement
→ PhysicsInteraction → PhysicsStep → AnimHistory → …
The player movement system reads nothing navigation writes, and agents move after it, so players are byte-identical whether or not agents exist (tested).
Profiles
data/locomotion/*.ron entries of the manifold-nav:locomotion registry
(server side of the data stage), keyed by id: in the mod’s namespace:
class (manifold:walker_small, manifold:walker, manifold:walker_large,
manifold:swimmer, manifold:flyer), body, speeds, step, jump apex,
max_drop, gap_jump: Auto | Blocks(n), turn_rate_deg_per_s,
arrive_radius, strafe, can_open_doors, can_climb, per-path-type
malus (an integer or Impassable), drop_malus, surface_malus (block
names and a cost, below), search, avoidance and
knockback. A body larger than its class envelope fails the stage; a
profile that restricts its class is a warning, because its Yes answers
become searches. The flyer class requires can_fly: true, and
can_fly: true requires the flyer class; a flyer cruises at speed.walk
(sprint when sprinting). The swimmer class requires can_swim;
can_climb requires a climbing class (walker_small, walker) and a
positive speed.climb, which becomes the body’s climb_speed. Compilation
(manifold_nav::compile) derives the integer cost table, the jump velocity
sqrt(2 g jump), the gap reach floor(walk · 2v/g − 0.5) (0 in a class
without gap jumps) and the move rules. Door and climbable cells a profile
cannot use cost Impassable without making it restrict its class:
reachability gates them itself (terrain).
Navigation
| Layer | What | Where |
|---|---|---|
| 0 | One byte per cell per class: path type and floor in eighths, in 4³ bricks; classified against the integrator’s collision boxes, fluid::query::sample_body and the values’ traits (terrain); a surface-group plane where some cell has a group | classify, brick, blocks |
| 1 | Local components per chunk (symmetric moves; door cells are single-cell nodes), external and one-way edges per cell, and labels over the open graph and, when doors exist, the sealed graph: open islands, strong components, closures (≤ 512 components per island), island signatures, islands with climbable cells, and a negative cache | graph |
| 2 | Bounded, resumable A* on snapshots: integer costs, (f, h, offset key) tie-break, slices of 256 expansions, priority caps, partial paths | search |
| Corridor | String-pulled waypoints after the start cell, which it keeps as origin (the take-off of an action edge the path begins with; segments ≤ 8 blocks), per-tick revalidation of the next four segments, splices of ≤ 256 nodes to the next valid waypoints, replans on the slow timer | corridor, agent |
| Flight | The flyer class’s masks, the swept straight-line test, the greedy brick search and flight corridors (below) | flight |
The server’s ServerNav resource holds NavWorld and the corridor index
(brick → agents). NavMaintenanceSystem reads each space’s world change log
(WorldSpace::changes_since), reclassifies the dependency box of every edited
cell, recomputes Layer 1 for changed chunks and relabels, builds chunks within
three chunks of every awake agent, and pumps requests. Worker builds classify
from manifold_world::ChunkSnapshots (shared with physics’s terrain collider
builds) and are installed only if the chunk’s ChunkStamp (load serial and
generation) still matches the snapshot’s.
For D (brains).
NavWorld::reachable(profile, space, from, &goal) -> Reach:Yes,NoorUnknownwith a reason and the space’s nav epoch.Nois never wrong for the class; store the epoch with a give-up and re-ask oncenav_epoch(space)has advanced.nav::request(world, NavRequest) -> PathHandle(the server helper overNavWorld::requestandrequest_toward): never blocks; aNofails the handle at once without a search. Then putMoveIntent::follow_path(handle)on the agent.status(handle),result(handle),release(handle). The follower installs the result, repairs and replans by itself; if repair proves the goal unreachable the handle’s status becomesFailed(Unreachable(..)).NavGoal::Within { target: Entity(..), range }goals are chased: the search goes to the entity’s position at request time and the follower follows it as it moves (straight at it within 4 blocks, a replan when it leaves the corridor’s end by 2 blocks).line_walkable,is_standable,nearest_standable,estimate_travel,find_spawn_cells(&SpawnQuery, max, &dyn LineOfSight, out).NavEvents(a resource the follower refills each tick, read by the next AI block):NavEvent::DoorAhead { cell }when an agent stops before a closed door (doors). A task opens it withuse-block; profiles withcan_open_doorsdo so by default.
For F (presentation). LocomotionState::{source, edge, cruise_speed, distance_to_stop}, NavAgent.corridor with lookahead(from, points, len)
and epoch (bumped by installs, splices and teleport). While a segment
presents the agent, distance_to_stop is the segment’s: the path left to
its Stop at the tick (PathSegment::distance_to_stop_at_tick, the integer
evaluation clients share), inside arrive_radius, else 0; otherwise the
follower’s remaining distance. No binding source reads it yet.
For everyone. nav::teleport(world, agent, pos, yaw) places an agent’s
body instead of writing Position (and zeroes its carry). Knockback is
Daedalus D’s character::CharacterImpulses: AgentMovementSystem takes an
agent’s entry at its step (a pending knock wakes an agent at rest), the
integrator applies it, and the controller starts a knockback lock during
which the stuck watch is suspended. Agents’ landings and wall hits join the
CharacterImpacts stream.
Crowds
Phase C3 (§10, §13, §5.5).
Shared flow fields (flow (crates/manifold-nav/src/flow.rs)).
A field is keyed by (space, profile, goal): the goal’s entity for a
Within { Entity } goal, else its anchor cell. Two ways in:
- Promotion.
NavWorld::request(andrequest_toward) counts requests toward each goal; once 8 agents of one profile hold requests toward one goal (NavSettings::flow_promote_agents, 0 turns it off), a new request returns a path handle backed by the goal’s field (NavWorld::flow_of(handle)):statusisSearchinguntil the field’s first build lands, thenReadywith no waypoints, and D’s tasks follow it unchanged. Flyers andAwaygoals are never promoted. - Explicit (E’s waves):
nav::request_flow(world, FlowRequest { space, profile, goal, cover })returns aFlowFieldHandle(an entity goal is resolved and followed); members followMoveIntent { follow: Some(Follow::Flow(handle)), .. };nav::release_flowwhen done.NavWorld::flow_status(h)isBuilding,Ready { version },Failed(..)orReleased.
A build (FlowJob (crates/manifold-nav/src/flow.rs), on the nav
workers, in slices of 4,096 cells) covers the region around its users,
its goal and E’s cover points grown by 16 blocks (at most 160 across
from the goal): every standable cell, its Layer 2 moves reversed, an
integer Dijkstra from the goal cells popping by (cost, cell index) (so a
cell’s cost equals the cheapest Layer 2 path’s inside the region, on every
host), a line-of-sight flag for cells on the goal’s level with a straight
walkable line to it within 16 blocks, and each cell’s successor and edge
kind, stored per chunk. A field rebuilds (at most 4 Hz, the old one in use
until the new lands) when a Layer 0 change lands in its region, when a
follower stands outside it, or when its goal moves more than 2 blocks from
the anchor; the server moves entity goals each tick in
NavMaintenanceSystem. A field nobody holds is dropped 200 ticks later.
Following a field. The follower traces 24 cells along the field from the agent’s cell (a line-of-sight cell goes straight to the goal cell), string-pulls them into the agent’s corridor and follows that like a path’s, so F’s segments, the corridor index, doors and the stuck watch work unchanged. It re-traces when the trace runs short (on its last waypoint, unless that is the goal), the field is rebuilt, the trace is cut, or stuck recovery would splice or replan; field followers never search. A re-trace bumps the corridor epoch only when the waypoints ahead changed: F reissues the agent’s segment on a new epoch, so re-tracing the same route sends nothing. An agent outside the region heads straight for the goal when the line is clear (and holds otherwise) until the next build covers it.
Steering (steer (crates/manifold-nav/src/steer.rs)).
SteeringSystem separates every agent; a profile’s avoidance adds more:
avoidance | What |
|---|---|
Separation (default) | Reynolds separation only |
Orca | RVO2’s ORCA (1 s horizon, ≤ 10 neighbours within 4 blocks) while the agent is at Full LOD, not knocked, within 16 blocks of a player and has ≥ 4 bodies within 3; separation otherwise. Other ORCA agents share the avoidance; players, knocked agents and agents that only separate are obstacles it avoids alone |
Flock | separation, alignment and cohesion with agents of the same profile within flock.radius, weighted by the profile’s flock: (separation, alignment, cohesion, radius) (defaults 1, 0.3, 0.2, 4 m); 3D for flyers |
Reduced LOD (§5.5). D’s LOD pass writes MovementLod. A Reduced { k }
agent follows every tick, only separates, and caps its own requests at
Low; it integrates every k-th tick (staggered by its durable id) with
dt the ticks since its last step, but only while F’s publisher presents
it through a path segment (AgentMotionState::mode == Segment) and the
step is plain walking: grounded, no override (so an Animation-authority
play integrates every tick), no knock or carry, no action edge or jump
ahead, not swimming or climbing, and facing within one tick’s turn of its
heading. Otherwise it integrates every tick. LocomotionState::step_tick
is the tick the body’s state stands for; the publisher judges a held
agent’s segment only on its step ticks, and clients never see the held
pose (they evaluate the segment).
Flight
A flyer is a profile of class manifold:flyer (envelope 0.98 × 1.95). Its
Layer 0 byte is Open (the envelope, feet on the cell’s bottom and
centred, touches no collision box and no fluid a body cannot swim),
Walkable (also directly above a collision top: a hover-clearance cell),
HazardFluid or Blocked. Per 4³ brick, NavChunk.flyer
(FlyerMasks (crates/manifold-nav/src/flight.rs)) says blocked,
open (every cell free, none a hover cell), or mixed with a free and a hover
u64 (bit x + 4y + 16z within the brick). Edits refresh the bricks whose
bytes changed. There is no Layer 1 for flyers.
- Straight line (
flight::segment_clear,NavWorld::line_flyable, andline_walkablefor a flyer profile): the body’s box swept from feetato feetb. An Amanatides–Woo walk over bricks skips stretches whose swept box lies in all-free bricks and walks cells only where it does not; every foot cell under the swept box must be free. Conservative: it never passes a line that touches geometry. - Reachability:
Yes(StraightLine) when the straight line from the start’s cell to a free goal cell is clear, otherwiseUnknown(FlightSearch). NeverNo; no negative cache. - Search (a flyer’s request;
SearchJob::with_flight): the straight line first, which gives a one-waypoint corridor without a search. Otherwise a greedy search whose nodes are open bricks and, in other bricks, free cells, six neighbours each, one unit per node plus one per hover cell,f = g + 4·h(Manhattan, in cells), with the ground search’s bounds, caps, arena and tie-break. Its node path is emitted as axis-aligned runs through free cells and string-pulled with the swept test (segments ≤ 16 blocks). Every waypoint’s edge isFly, its feet on its cell’s bottom. - Following: arrival as on the ground, the next four waypoints’ cells each tick, every remaining segment on a corridor-index hit (the index samples a flight segment over the body’s two foot rows, from its start) and once at install when planned against an older epoch; the look-ahead steers at the furthest waypoint within four blocks that a clear segment reaches; chasing flies straight at a target within four blocks.
- Movement: a flyer integrates in
MotionMode::Fly(the agent step sets it); the controller clamps the 3D desired velocity and drives its vertical part throughDriveInput::vertical, never jumps, and when horizontally blocked slides vertically toward the target’s side. A hovering flyer can rest. A knock goes wholly into carry; its lock ends by ticks alone.
Terrain
Phase C2: doors, climbing, hazards and slow cells, walker_large, swimmers
and surface groups.
What values are. Classification keys on Enki A’s traits and family
roles, which do not exist yet. manifold_nav::blocks::NavBlockTraits states
them in Enki A’s vocabulary over today’s block names, and NavBlocks is the
per-value table built from them for the block registry
(ServerNav::set_block_traits, or NavWorld::set_block_traits; a change
rebuilds the space’s navigation):
| Enki A | The seam |
|---|---|
manifold:climbable | member(CLIMBABLE, block) |
manifold:nav_slow | member(NAV_SLOW, block) |
manifold:nav_hazard (fire, other) | value(NAV_HAZARD, block, "fire") |
family roles door, fence_gate, trapdoor and the property open | openable(role, closed_block, open_block) (today two block types) |
When Enki A lands, NavBlocks::build reads its trait bitsets, family roles
and open instead, and nothing downstream changes. The flagship declares
none of these yet, so its worlds classify as before.
Hazards and slow cells. A hazard value in a cell the body occupies (or
stands on) makes the cell DamageFire/DamageOther, impassable unless a
profile prices it; within one cell of the body’s cells a walkable cell is
DangerFire/DangerOther (base cost 8). A slow value in the body’s cells or
under it makes a walkable cell Slow (base cost 8). Fire outranks other.
Doors
- Layer 0. A cell is a door cell when a door value lies in the body’s
own cells:
DoorClosedwhen the envelope fits once every closed door there is swapped for its open value,DoorOpenwhen it is open already. Doors never support a body. - Moves. Door cells are entered only by a level or step move along an
axis, as a
Dooredge (no diagonals, jumps, drops or gap jumps into or diagonally out of them). Everyone may stand inDoorOpen;DoorClosedonly profiles withcan_open_doors(base cost 8). - Layer 1. Door cells are single-cell components, so a door never merges
the rooms on either side, and opening or closing one changes only Layer 0.
Profiles that open doors answer from the open graph; others from the
sealed graph (door nodes removed), where a goal reachable only through a
door is
Unknown(DoorGated) and the search decides. - Following. When the next corridor edge enters a closed door cell, the
follower stops within
1 + w/2of it, raisesDoorAheadonce, and holds (the stuck watch rebased) until Layer 0 shows the cell open. After 40 ticks the cell joinsNavAgent::avoidfor 600 ticks (cost 2,048 per entry) and the agent replans; every request made for the agent throughnav::requestcarries its avoided cells. use-block(Talos G op 27) is one Enki B transaction (block transactions).nav::block_use_transaction(spaces, blocks, &BlockUseRequest { space, pos, expect, actor, owner })builds it: requesterNative("talos"), oneblockssection that expects the seen value over every cell the use changes and fills them with the door’s value withopenflipped (the cell and the run of the same value straight above and below it, so a two-high door’s halves flip together or not at all).NavFollowsubmits it withTxnEngine::submitthrough its declaredTxnEnginewrite; exclusive code usestransaction::submit. Outcomes come fromtransaction::take_results(world, &Requester::Native("talos")):Committed,RejectedorConflict(Changed)when the door changed first. The use applies at B’s slot after the tick, and maintenance sees the door open the next tick. The default door behaviour acts asRule("ai.brain").
Climbing
A climbable cell is one whose body overlaps a climbable value while
unsupported (Climbable); a supported cell in front of a ladder stays
walkable, so profiles that cannot climb keep it. Climbing classes
(walker_small, walker) get Climb edges up into and down out of
climbable cells, and a rise out of one onto a ledge is a Climb, not a
jump. The controller climbs toward a Climb edge’s waypoint (onto a ledge:
up until the feet clear its lip), and the server’s sampler answers
climbable from the value table (blocks::TraitSampler). A profile that
cannot climb is restricted only in islands with climbable cells: there its
Yes becomes Unknown (ProfileRestricted). Climbable values should not
collide: a ladder panel inside the cell blocks the class envelope.
walker_large and swimmers
walker_large(1.98 × 2.95, a 2 × 2 footprint, step and jump one block, drops of four, no gap jumps). Its foot cell is the footprint’s low corner, so its body centre is(0.5, 0, 0.5)from the foot cell’s centre (LocomotionClass::footprint_offset):NavWorldqueries and the follower shift positions into foot-cell space, while corridor waypoints (andCorridor::lookahead) are foot-cell feet.- Swimmers stand only in
Watercells, where the whole envelope (feet on the cell’s bottom) lies in a swimmable fluid, and move level and vertically between them. The controller rises or dives toward the depth of the waypoint it steers to.
Surface groups
surface_malus masks name blocks (Enki A’s masks later), checked against
the world’s blocks at load
(data stage). NavBlocks
sorts values by the set of masks they match into surface groups (u8, at
most 255) and keeps each profile’s cost per group; a chunk stores a surface
plane only when some cell has a group (a standing cell takes its support’s
group, a water cell its fluid’s). Search adds the entered cell’s surface
cost; a negative one lowers the heuristic’s step cost, and every step still
costs at least 4.
Workers and determinism
Builds and searches run on the global rayon pool (at most workers in flight:
2 native, 1 in the browser’s server worker) and synchronously where there are
no threads. The executor is manifold_world::exec, shared with physics’s
terrain collider builds. A worker build classifies a snapshot of the chunk and its 26
neighbours; an edit meanwhile reclassifies nothing in a chunk not yet
installed. So a build of a chunk edited since the snapshot is classified
again from the current blocks, and one whose neighbour was edited, loaded or
unloaded since is installed and then has the band facing that neighbour
reclassified like an edit.
Results apply in request order per space. NavSettings::lockstep()
completes every job at dispatch and delivers it two ticks later, for tests and
determinism checks. Costs, tie-breaks and island logic are integers, facing
is a 16-bit binary angle with manifold-det trigonometry, and hash maps are
lookup-only; tests/determinism_guard.rs scans the sources.
Limits
- No digging (C5). Traits, door roles and surface masks come from
NavBlockTraitsuntil Enki A; masks name blocks only. - Flow fields are one integration per region (no per-portal tile sharing between fields, no incremental repair; an edit rebuilds the field), are keyed by profile, and do not serve flyers (Talos C §28, issues 46–48).
- Nothing drains Talos’s transaction results yet (navigation watches the door); the ring keeps at most 256 until Talos G’s results step.
- String pulling ignores surface costs, so a corridor segment can cut a corner across a costlier surface the search went round.
- Flyers have no component graph: an unreachable flight goal costs a capped search per request (Talos C §28, issue 32).
- Layer 1 relabels the whole class graph when anything changed, and an edit recomputes its chunk’s components; an edit costs about 1 ms rather than the 40 µs budget.
- At-rest wake-ups from bodies are not built. Reduced-LOD agents integrate coarsely only while a segment presents them (issue 50).