Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@ jobs:
- name: Compile controller for wasm32
run: zig build wasm-check

# Prepared endpoint integration depends on engine#916 until its release.
# Prepared endpoint integration depends on engine#917 until its release.
# Pin the exact tested commit rather than following an unreleased branch.
- name: Checkout engine position revision API
run: |
git init ../labelle-engine
git -C ../labelle-engine remote add origin https://github.com/labelle-toolkit/labelle-engine.git
git -C ../labelle-engine fetch --depth 1 origin b93dafd1295fe5324be70c6249fc4687a78b8fe3
git -C ../labelle-engine fetch --depth 1 origin 21ea60c67038416c007e308814c137358104353d
git -C ../labelle-engine checkout --detach FETCH_HEAD

- name: Run engine integration tests
run: zig build test --build-file tests/integration/build.zig --summary all

- name: Compile serial and threaded WASM snapshot executor
working-directory: ../labelle-engine
run: zig build snapshot-worker-check
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// v3.0.0 — repurposed: the ECS navigation Controller (promoted from
// flying-platform) is now the headline API; the standalone algorithm core
// moves under `.algo`; the old position-owning engine is dropped.
.version = "4.2.0",
.version = "4.3.0-rc.2",
.fingerprint = 0x15f62ccc39d14685,
.minimum_zig_version = "0.16.0",
.dependencies = .{
Expand Down
29 changes: 29 additions & 0 deletions rfc/prepared-endpoint-implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,32 @@ mutation and participant audits, generation-qualified graph/query freshness,
remaining CMN writers/known-node updates, frame-order placement, game query migration
and normal-retry handling for active jobs/raids. In particular, this phase's graph
checks do not constitute a query-time guarantee after arbitrary later mutations.

## Prepared queries and graph dependency freshness

`preparedDistance(game, from, to)` reads only current published endpoints and the
existing FW table. Ordinary entities require a ready CMN with matching world,
entity, position and graph stamps. MovementNode endpoints use the current live
entity registration directly, without a self-CMN. A stale/absent/off-graph or
disconnected endpoint, dirty table, or changed graph dependency returns null.
There are no allocations, parent walks, nearest-node scans, repairs or Euclidean
fallbacks in this query.

Graph construction watches MovementNode/MovementStair/MovementLink mutations and
registered node positions through the engine dependency group. Any subsequent
watched mutation invalidates all prepared queries immediately. Full graph input
validation remains in advance/preparation, outside queries. Direct backend writes
must use the engine invalidation companion; command-level node removal also
invalidates the table. Component-pointer writes are not magically observable.

`endpointPreparationComplete` is a separate lifecycle gate: pending/stale is
false, completed off-graph is true. It does not change the matching null contract.
It lets an active job or raid retry preparation before concluding that no usable
candidate exists. Destroyed targets still need the consumer's existing lifetime
policy.

`publishKnownEndpoint` supports explicit docking/placement. It validates actual
world-space position against the registered node, snap radius and Y filter, then
publishes the current stamp. It rejects MovementNode recipients and cannot repair
a stale graph. Opted-in `requestRepath` invalidates for the bounded phase instead
of scanning synchronously. Legacy navigation commands remain available separately.
16 changes: 14 additions & 2 deletions src/nav/components.zig
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub const ClosestMovementNode = struct {
entity_id: u64 = 0,
entity_generation: u64 = 0,
position_revision: u64 = 0,
position_slot: u32 = @import("std").math.maxInt(u32),
graph_revision: u64 = 0,

pub fn valid(self: Stamp) bool {
Expand All @@ -59,16 +60,26 @@ pub const ClosestMovementNode = struct {

pub fn eql(a: Stamp, b: Stamp) bool {
return a.world_revision == b.world_revision and a.entity_id == b.entity_id and a.entity_generation == b.entity_generation and
a.position_revision == b.position_revision and a.graph_revision == b.graph_revision;
a.position_revision == b.position_revision and a.position_slot == b.position_slot and a.graph_revision == b.graph_revision;
}
};
pub const Status = enum { pending, ready, off_graph };
pub const Invalidation = enum { none, position, ancestor, parent, graph, world };
pub const Resolution = struct { node_entity: u64, node_id: u32, distance: f32 };
/// Conservative nearest-node certificate, created only by full preparation.
/// It stays centred at the original sample even across repeated reuse.
pub const ReuseRegion = struct {
x: f32 = 0,
y: f32 = 0,
radius: f64 = 0,
min_y: f64 = 0,
max_y: f64 = 0,
};
pub const Resolution = struct { node_entity: u64, node_id: u32, distance: f32, reuse: ReuseRegion = .{} };

node_entity: u64 = 0,
node_id: u32 = 0,
distance: f32 = 0.0,
reuse: ReuseRegion = .{},
status: Status = .pending,
stamp: Stamp = .{},
invalidation: Invalidation = .none,
Expand Down Expand Up @@ -99,6 +110,7 @@ pub const ClosestMovementNode = struct {
self.node_entity = r.node_entity;
self.node_id = r.node_id;
self.distance = r.distance;
self.reuse = r.reuse;
self.last_ready_stamp = snapshot;
}
self.stamp = snapshot;
Expand Down
Loading
Loading