Repository navigation
Serialization
This document is the Trailblazer-specific reference for how the runtime currently uses the Chronicler serialization layer.
If you only need the high-level architecture, read Overview.md.
If you need the reusable Chronicler API details, check out that library's README.md.
If you need the runtime systems currently covered by this Trailblazer implementation, read:
Navigator.mdNavSteering.mdNavTurning.mdNavMotor.md
The code referenced here lives primarily in:
src/Trailblazer/Navigation/Navigator/Navigator.Serialization.cssrc/Trailblazer/Navigation/Steering/NavSteering.Serialization.cssrc/Trailblazer/Navigation/Steering/Serialization/*src/Trailblazer/Navigation/Turning/NavTurning.Serialization.cssrc/Trailblazer/Navigation/Motor/NavMotor.Serialization.cssrc/Trailblazer/Navigation/Motor/Locomotion/*
The current Trailblazer serialization coverage is intentionally limited to:
NavigatorNavigatorHeightmapGroundingSettingsNavSteeringNavTurningNavMotorLocomotionHandlerMoveLocomotionPlatformLocomotionJumpLocomotionFallLocomotionSlideLocomotionWaterLocomotionFlyLocomotionClimbLocomotion
In practice that means the current covered branch handles:
- navigator transform and motion state
- navigator steering session state
- navigator turning configuration
- navigator motor-owned traversal state
- navigator-owned heightmap grounding opt-in settings
- locomotion configuration
- locomotion resumable runtime state
- moving-platform attachment state inside the motor branch and guided steering branch
The current implementation loads into existing initialized instances.
This is a deliberate constraint.
The current load path assumes:
- the host has already created the runtime object
- the runtime object has already been bound to the correct
TrailblazerWorldContext - the runtime object has already been initialized enough to own its nested controllers
- a Chronicler transport such as
JsonRecordSerializer.Populate(...)orMemoryPackRecordSerializer.Populate(...)then overwrites supported state
This means the current system is designed for:
- restoring state into a known runtime shell
- deterministic snapshot/restore experiments
- proving the shape of the API before broadening scope
Trailblazer intentionally does not treat Chronicler as a construct-from-data runtime factory.
Context is host binding, not serialized state. Hosts must recreate or look up the correct
TrailblazerWorldContext, bind each navigator shell to it, and then populate state. This keeps save
payloads engine-agnostic and prevents one snapshot from silently attaching to the wrong world.
Another important rule applies across the Trailblazer RecordData(...) implementations:
-
RecordValues.Look(...)defaults must be canonical defaults for that field, not the instance's current runtime value - value entries equal to their declared default are omitted during save
- missing value entries restore to the declared default during load
- deep record paths are split intentionally: reference-owned nested objects use
RecordDeep, non-nullable structs useRecordDeepStruct, and optional structs useRecordNullableDeep
This keeps payloads smaller and makes partial payloads deterministic.
It also means omitted entries do not preserve whatever value happened to already be present in the target runtime shell. If a field is absent from the payload, load assigns that field's declared default.
That means this runtime is not designed for:
- constructing arbitrary controller graphs from serialized data alone
- polymorphic navigator creation
- full save-game bootstrap of the entire framework
Navigator.RecordData(...) currently persists:
PositionLastPositionRotationVelocitySpeedAccelerationSizeFootPositionAdjust- guided-request defaults configured through
ConfigureForGuidedTraversal(...), includingGuidedPathMode, transition fallback, and the built-in chart request tuning values - heightmap grounding settings configured through
ConfigureHeightmapGrounding(...), including mode, preferred layer name, active layer name, ground offset, and snap tolerance GlobalIdOccupantGroupIdIsLockedOnStuckThresholdSpeedIsGuideded_frameCondition_frameRequest- guided traversal settings that must survive frame-request resets during active guided movement
SteeringTurningMotor
On load it also:
- rebuilds
ForwardfromRotation - clears frame deltas
- refreshes steering radius-derived tolerances from the loaded navigator size
- clears and recomputes occupancy
- reinitializes
Turningif present
Heightmap samples, compressed heightmap data, and context registrations are not serialized through
Navigator. Those are host/context-owned world resources. After loading a navigator with heightmap
grounding enabled, hosts must register the required heightmap layers on the already-bound
TrailblazerWorldContext before calling the navigator's concrete heightmap grounding hook. If a
restored navigator references a missing layer, grounding fails cleanly and does not construct or
hydrate heightmap data from the navigator payload.
NavSteering.RecordData(...) currently persists:
- movement-session flags such as
ShouldMove,IsStuck,IsAtDestination, andHasLineOfSightPath - destination, requested destination, target direction, and distance-to-target state
- steering tuning such as stop multiplier, group/avoid factors, behavior weights, and braking power
- cooldowns, stuck counters, repath counters, and auto-stop state
- movement-group id and current travel mode
- the active request shape through a serializable request record
The request record currently captures:
- request type (
AStar,FlowField,Volume, or internalHybrid) - origin
- target position
- unit size
- walkability override
- max search range
- request-type-specific settings such as heuristic, max climb height, extra flood range, or volume traversal mode
- whether an active guide existed
- current waypoint index when the guide is waypoint-based
On load it also:
- rebuilds the active
IPathRequestfrom the recorded request data - reacquires a guide from the request's owning context when the session was actively guide-driven
- restores waypoint progress for
AStarGuide,VolumeGuide, and internalHybridGuidewhen the guide is waypoint-based - clears any stale guide or movement-group session state already attached to the existing runtime shell
- preserves per-session movement-group intent while treating the context-owned coordinator as rebuild-only runtime state
- allows hosts to prewarm grouped sessions explicitly through
Navigator.PrewarmMovementGroup()orNavSteering.PrewarmMovementGroup(...) - falls back to a new path request on the next steering tick if a guide could not be reacquired immediately
NavTurning.RecordData(...) currently persists:
CanTurnTurnRate
On load it intentionally rebuilds buffered runtime state instead of restoring it directly.
That means load resets:
- the current turn target
- any pending buffered turn request
- one-shot interpolation overrides
- collision-triggered auto-turn buffering
This keeps turn configuration durable while treating active turn execution as frame-local runtime state that is safer to rebuild.
NavMotor.RecordData(...) currently persists:
Handler- current
TrekCondition - previous
TrekCondition IsInitialized
On load it also:
- rebuilds
CurrentState - clears pending traversal-finalization state
- clears frame-local cached output such as
_forceOutput - resets
FrameSlopeAngle
LocomotionHandler.RecordData(...) currently persists:
IsInControl-
installedKindsfor the currently installed built-in locomotion set - the required
MoveandFalllocomotions throughRecordDeep - optional built-in locomotions through named
RecordDeepslots when installed
Each locomotion persists its configuration plus its resumable runtime state.
Examples:
-
MoveLocomotion: speed/acceleration/gravity configuration, core slope limit, passive water drag, plusFrameVelocity -
JumpLocomotion: jump configuration plus jump/cooldown state -
FallLocomotion: fall configuration plus fall start/end state -
SlideLocomotion: slide-specific configuration plusIsSliding -
WaterLocomotion: liquid-medium configuration plus swim/dive/underwater timers -
FlyLocomotion: flight speed/acceleration/gravity-compensation configuration plusIsFlying -
ClimbLocomotion: climb speed/acceleration/gravity-compensation configuration plus active climb/mantle state -
PlatformLocomotion: platform configuration plus active/previous/hold platform state, movement-transfer state, local attachment state, and tracked platform velocities
The current Trailblazer serialization branch intentionally does not serialize the full live navigation stack.
Out of scope today:
- inline serialization of live
IGuideinstances - movement-group coordinator caches and memberships
- chart registrations, path caches, or context-owned pathing state
- full voxel occupancy maps
Some state is also intentionally rebuilt instead of persisted directly:
Navigator.Forward-
NavTurningbuffered turn state NavMotor.CurrentState- frame-local delta accumulators
- temporary traversal caches such as
FrameSlopeAngle
The core now also supports immediate and deferred stable external links through ChronicleContext and RecordLinks, but the current Trailblazer runtime does not need direct RecordLinks usage in its core navigation branch at this time.
Trailblazer's current serialization boundary is:
- JSON and MemoryPack Chronicler backends
- explicit type-owned
RecordData(...)behavior - load-into-existing-instance semantics
- navigator, steering, turning, and motor branches
This gives Trailblazer a workable starting point without overcommitting to a full save-game architecture too early.
Current tradeoffs of this boundary:
- it is safe and explicit, but not yet turnkey for whole-world persistence
- it rebuilds live guides from recorded requests instead of serializing guide instances directly
- it keeps deterministic state local to the types that understand it
- it still needs broader coverage before it becomes a complete framework-level persistence solution
When extending this system, keep these rules in mind:
- prefer explicit record code over implicit serializer behavior for runtime systems
- serialize authoritative state, not convenience caches
- rebuild derived values on load where practical
- avoid serializing live references when a stable identifier or reconstructed state is safer
- use canonical declared defaults in
RecordValues.Look(...), never the current live field value - treat omitted payload entries as "load the declared default," not "keep the current in-memory value"
- keep deterministic ordering and fixed-point state intact
- keep load behavior obvious and testable
This is especially important in Trailblazer because pathing and navigation are sensitive to:
- simulation order
- traversal-state transitions
- platform attachment state
- cache invalidation
- frame-local intermediate values
Focused round-trip coverage was added for:
NavTurningNavMotor-
Navigatorplus its steering, turning, and motor branches
The current Trailblazer-focused tests verify that:
- supported navigator state round-trips through the JSON and MemoryPack chroniclers
- guided A* and flow-field steering sessions survive round-trip
- path-request configuration and A* waypoint progress survive round-trip
- guides are reacquired after load instead of being serialized inline
- partial payload loads preserve omitted branches on existing initialized runtime shells
- payloads missing current fields fall back to declared defaults
- invalid request payloads fail closed and clear active steering sessions instead of leaving broken live state
- disabled locomotions clear transient runtime state on load even when serialized payloads still carry stale transient values
- unresolved deferred links throw explicit load errors for both transports
- turning configuration survives round-trip while active turn runtime state is rebuilt on load
- navigator heightmap grounding settings survive round-trip without serializing context-owned heightmap data
- moving-platform state survives round-trip
- locomotion runtime state survives round-trip
- grouped steering can be explicitly prewarmed after load, and the runtime can lazily rejoin groups when needed
- rebuild-oriented load behavior matches across the JSON and MemoryPack transports
- a restored navigator remains usable for later simulation
The Trailblazer-specific branch is in solid shape now, but a few non-blocking follow-ups are still valuable:
- Add an end-to-end host workflow guide or sample that shows batch save/load for multiple navigators, including chart prerequisites, restore ordering, and optional
PrewarmMovementGroup()calls. - Decide whether any additional Trailblazer-owned context state should ever expose optional snapshot surfaces, or whether pathing and navigation services remain rebuild-only by design.
- Expand
RecordData(...)coverage into other Trailblazer branches only as those runtime surfaces stabilize and prove they contain authoritative state worth persisting.
When adding RecordData(...) to more types:
- Start with the authoritative state.
- Skip or rebuild frame-local caches unless they are truly required for restore correctness.
- Prefer
RecordDeepfor nested runtime-owned objects. - Use
RecordLinksfor stable references to external or runtime-owned objects. - Use value fields for small deterministic structs and enums.
- Add a focused round-trip test in the same change.
- Verify the type still behaves correctly after load, not just that the raw values match.
That approach is what the current Navigator -> NavMotor -> LocomotionHandler -> Locomotions branch follows.