world.bend

bendvy-ecs-core@0.1.0.3 · 401 lines · 82 definitions

1import Base
2import bendvy-ecs-core@0.1.0.3/change_tick.bend as Tick
3import bendvy-ecs-core@0.1.0.3/clock_state.bend as Clock
4import bendvy-ecs-core@0.1.0.3/execution_lifecycle_notices.bend as Notice
5import bendvy-ecs-core@0.1.0.3/trace_facts.bend as TraceFacts
6import bendvy-ecs-core@0.1.0.3/deferred_command.bend as Deferred
7import string_equality.bend as SE
8import entity_key.bend as K
9import entity_allocator.bend as A
10
11# Framework-owned metadata. Independent factory roots are not globally unique.
12# Schema-branded detached identity containing World namespace and full entity key
13# (index plus generation). Extracting its index does not validate it; raw construction
14# is trusted setup, not evidence that a handle belongs to a live entity.
15type Handle<Schema: Data> is Data:
16  Handle{namespace: U32, key: K.Key}
17# Affine namespace sequence for raw World creation. Keep one actual sequence for
18# worlds that must distinguish each other; independently reset roots can collide.
19# Application code normally uses world-io creation rather than fabricating this value.
20type Factory is Type:
21  Factory{nextNamespace: U32}
22# Detached registration id/name/ordered access metadata. It is not the registered
23# runner, its affine Local/capture owner or a capability derived from a string.
24type RegistrationMeta is Data:
25  RegistrationMeta{id: U32, name: String, access: List<&2,String>}
26# Explicit low-level refusal reasons for entity, capacity, namespace or system-id
27# admission. Callers must inspect their operation result rather than infer success.
28type WorldError is Data:
29  ReservationUnauthorized{}
30  MissingEntity{}
31  CapacityExceeded{}
32  NamespaceExhausted{}
33  SystemIdExhausted{}
34# Affine engine storage/resource owner with allocator and administrative state.
35# Raw field construction/destructuring is trusted framework provisioning, not the
36# opaque gameplay interface. Schema branding alone does not validate forged fields.
37type World<Schema: Data,Store: Type,Resource: Type,Event: Data> is Type:
38  World{namespace: U32,allocator: A.Allocator,store: Store,resource: Resource,events: List<&2,Event>,pending:List<Deferred.Command<World<Schema,Store,Resource,Event>>>,registrations: List<&2,RegistrationMeta>,nextSystemId: U32,clock: Clock.State,lifecycle:Notice.Journal<Schema>}
39# Raw creation returns the advanced Factory and World on success; rejection
40# returns the actual Factory and incoming resource owner without constructing C.
41type Creation<Schema: Data,Store: Type,Resource: Type,Event: Data> is Type:
42  Created{factory: Factory,world: World<Schema,Store,Resource,Event>}
43  CreateRejected{factory: Factory,resource: Resource}
44# Reservation admission result. Reserved provides a full handle but does not
45# activate it or populate components; rejection includes the allocator refusal.
46type Reservation<Schema: Data> is Data:
47  Reserved{handle: Handle<Schema>}
48  ReservationRejected{error: WorldError}
49# Low-level operation decision; returned World is separate. Accepted describes
50# that operation, not success of an entire transaction or deferred command queue.
51type Status is Data:
52  Accepted{}
53  Rejected{error: WorldError}
54# Metadata-registration result; id allocation is distinct from owning or running
55# a system. Exhaustion returns RegistrationRejected without a new registration.
56type Registration is Data:
57  Registered{id: U32}
58  RegistrationRejected{error: WorldError}
59
60# Return administrative time without advancing it. Full epoch is required by readers.
61def clock(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E> & Tick.Tick:
62  match world:
63    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,+clock,lifecycle}:(World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},Clock.now(clock))
64
65# Observe this execution's captured tick without advancing administrative time.
66def execution_clock(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E> & Tick.Tick:
67  match world:
68    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,+clock,lifecycle}:(World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},Clock.current(clock))
69
70def clock_returned(~S:Data,~C:Type,~R:Type,~E:Data,namespace:U32,allocator:A.Allocator,store:C,resource:R,events:List<&2,E>,pending:List<Deferred.Command<World<S,C,R,E>>>,registrations:List<&2,RegistrationMeta>,nextSystemId:U32,changed:Clock.State & Tick.Tick,lifecycle:Notice.Journal<S>) -> World<S,C,R,E> & Tick.Tick:
71  (clock,tick)=changed
72  (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},tick)
73
74# Explicit direct/deferred time boundary; this is not system admission.
75def advance_clock(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E> & Tick.Tick:
76  match world:
77    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}:clock_returned(~S,~C,~R,~E,namespace,allocator,store,resource,events,pending,registrations,nextSystemId,Clock.advance(clock),lifecycle)
78
79# Stamp a canonical write with its captured execution tick or a new direct-edit tick.
80def write_clock(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E> & Tick.Tick:
81  match world:
82    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}:clock_returned(~S,~C,~R,~E,namespace,allocator,store,resource,events,pending,registrations,nextSystemId,Clock.write(clock),lifecycle)
83
84# Enter only after registration admission; capture one tick for all run writes.
85def enter_run(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E> & Tick.Tick:
86  match world:
87    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}:clock_returned(~S,~C,~R,~E,namespace,allocator,store,resource,events,pending,registrations,nextSystemId,Clock.enter(clock),lifecycle)
88
89# Exit an executed run on success or failure, preserving administrative progress.
90def leave_run(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E>:
91  match world:
92    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}:World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,Clock.leave(clock),lifecycle}
93
94def ensure_returned(~S:Data,~C:Type,~R:Type,~E:Data,namespace:U32,allocator:A.Allocator,store:C,resource:R,events:List<&2,E>,pending:List<Deferred.Command<World<S,C,R,E>>>,registrations:List<&2,RegistrationMeta>,nextSystemId:U32,result:Clock.State & Bool,lifecycle:Notice.Journal<S>) -> World<S,C,R,E> & Bool:
95  (clock,owned)=result
96  (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},owned)
97# A direct query owns a new scope; a query inside a system reuses that scope.
98def ensure_run(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E> & Bool:
99  match world:
100    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}:ensure_returned(~S,~C,~R,~E,namespace,allocator,store,resource,events,pending,registrations,nextSystemId,Clock.ensure(clock),lifecycle)
101def finish_run(~S:Data,~C:Type,~R:Type,~E:Data,owned:Bool,world:World<S,C,R,E>) -> World<S,C,R,E>:
102  match world:
103    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}:World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,Clock.finish(owned,clock),lifecycle}
104
105# Extract only the slot index for trusted storage adapters. Generation and World
106# namespace are omitted; this projection must not replace handle validation.
107def handle_index(~S:Data,handle:Handle<S>) -> U32:
108  match handle:
109    case Handle{_,key}:K.index(key)
110# Extract the complete index/generation key, without checking namespace or liveness.
111def handle_key(~S:Data,handle:Handle<S>) -> K.Key:
112  match handle:
113    case Handle{_,key}:key
114
115# Start a raw namespace sequence at one. This pure constructor is not a global
116# uniqueness service; ordinary independently created Worlds use world-io.bend.
117def factory() -> Factory:
118  Factory{1}
119def create_checked(~S:Data,~C:Type,~R:Type,~E:Data,~empty:Unit -> C,available:Bool,+namespace:U32,resource:R) -> Creation<S,C,R,E>:
120  match available:
121    case False{}:CreateRejected{Factory{namespace},resource}
122    case True{}:Created{Factory{(namespace + 1 : U32)},World{namespace,A.empty(),empty(Unit{}),resource,[],[],[],1,Clock.empty(),Notice.empty(~S,namespace)}}
123# Consume the raw Factory and resource owner to construct an empty World, advancing
124# the namespace sequence on success. Namespace exhaustion returns both inputs via
125# CreateRejected. Trusted empty supplies C; use world-io for application creation.
126def create(~S:Data,~C:Type,~R:Type,~E:Data,~empty:Unit -> C,factory:Factory,resource:R) -> Creation<S,C,R,E>:
127  match factory:
128    case Factory{+namespace}:create_checked(~S,~C,~R,~E,~empty,U32.is_lt(namespace,4294967295),namespace,resource)
129def reserve_result(~S:Data,~C:Type,~R:Type,~E:Data,+namespace:U32,store:C,resource:R,+events:List<&2,E>,pending:List<Deferred.Command<World<S,C,R,E>>>,+registrations:List<&2,RegistrationMeta>,+nextSystemId:U32,+clock:Clock.State,allocator:A.Allocator,reservation:A.Reservation,lifecycle:Notice.Journal<S>) -> World<S,C,R,E> & Reservation<S>:
130  match reservation:
131    case A.Exhausted{}:(World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},ReservationRejected{CapacityExceeded{}})
132    case A.Reserved{key}:(World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},Reserved{Handle{namespace,key}})
133def reserve_join(~S:Data,~C:Type,~R:Type,~E:Data,+namespace:U32,store:C,resource:R,+events:List<&2,E>,pending:List<Deferred.Command<World<S,C,R,E>>>,+registrations:List<&2,RegistrationMeta>,+nextSystemId:U32,+clock:Clock.State,reserved:A.Allocator & A.Reservation,lifecycle:Notice.Journal<S>) -> World<S,C,R,E> & Reservation<S>:
134  (allocator,reservation) = reserved
135  reserve_result(~S,~C,~R,~E,namespace,store,resource,events,pending,registrations,nextSystemId,clock,allocator,reservation,lifecycle)
136# Reserve a full allocator key, returning World in both success and refusal.
137# Reservation consumes allocator progress and is not yet visible as a live entity.
138def reserve_id(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E> & Reservation<S>:
139  match world:
140    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}:reserve_join(~S,~C,~R,~E,namespace,store,resource,events,pending,registrations,nextSystemId,clock,A.reserve(allocator),lifecycle)
141def checked_join(~S:Data,~C:Type,~R:Type,~E:Data,namespace:U32,store:C,resource:R,events:List<&2,E>,pending:List<Deferred.Command<World<S,C,R,E>>>,registrations:List<&2,RegistrationMeta>,nextSystemId:U32,clock:Clock.State,checked:A.Allocator & Bool,lifecycle:Notice.Journal<S>) -> World<S,C,R,E> & Bool:
142  match checked:
143    case (allocator,valid):(World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},valid)
144def checked_namespace(~S:Data,~C:Type,~R:Type,~E:Data,allowed:Bool,mode:U32,world:World<S,C,R,E>,key:K.Key) -> World<S,C,R,E> & Bool:
145  match allowed world:
146    case False{} world:(world,False{})
147    case True{} World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}:checked_join(~S,~C,~R,~E,namespace,store,resource,events,pending,registrations,nextSystemId,clock,A.checked(allocator,key,mode),lifecycle)
148def checked(~S:Data,~C:Type,~R:Type,~E:Data,mode:U32,world:World<S,C,R,E>,handle:Handle<S>) -> World<S,C,R,E> & Bool:
149  match world handle:
150    case World{+namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle} Handle{ns,key}:checked_namespace(~S,~C,~R,~E,U32.is_eq(namespace,ns),mode,World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},key)
151# Trusted structural check of namespace and current allocator key metadata, without
152# requiring a live entity. Not a gameplay substitute for valid.
153def metadata_valid(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,handle:Handle<S>) -> World<S,C,R,E> & Bool:
154  checked(~S,~C,~R,~E,0,world,handle)
155# Return World with namespace/full-generation/live validation. Foreign or stale
156# handles fail before allocator slot access for an unmatched namespace.
157def valid(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,handle:Handle<S>) -> World<S,C,R,E> & Bool:
158  checked(~S,~C,~R,~E,1,world,handle)
159# Return World with validation of actual Pending authority for this full handle.
160# An arbitrary reserved key does not by itself establish that pending authority.
161def pending_valid(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,handle:Handle<S>) -> World<S,C,R,E> & Bool:
162  checked(~S,~C,~R,~E,2,world,handle)
163def phase_checked(~S:Data,~C:Type,~R:Type,~E:Data,phase:A.Phase,key:K.Key,checked:World<S,C,R,E> & Bool) -> World<S,C,R,E> & Status:
164  match checked:
165    case (world,False{}):(world,Rejected{MissingEntity{}})
166    case (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},True{}):(World{namespace,A.write_phase(allocator,key,phase),store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},Accepted{})
167# Trusted lifecycle phase change after metadata validation; returns World and
168# Status. This does not construct components or execute a pending populate closure.
169def activate(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,+handle:Handle<S>) -> World<S,C,R,E> & Status:
170  phase_checked(~S,~C,~R,~E,A.Live{},handle_key(~S,handle),metadata_valid(~S,~C,~R,~E,world,handle))
171# Opaque storage is retained; this operation cannot make its slot reusable.
172# Mark a metadata-valid entity DeadRetained, returning Status and World. Payloads
173# remain in storage; schema cleanup must precede slot release/reuse.
174def deactivate(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,+handle:Handle<S>) -> World<S,C,R,E> & Status:
175  phase_checked(~S,~C,~R,~E,A.DeadRetained{},handle_key(~S,handle),metadata_valid(~S,~C,~R,~E,world,handle))
176def discard_status(~S:Data,~C:Type,~R:Type,~E:Data,observed:World<S,C,R,E> & Status) -> World<S,C,R,E>:
177  match observed:
178    case (world,_):world
179# Structural bookkeeping for an actually staged spawn. Metadata validation gates
180# the phase update; this helper alone does not enqueue or populate an entity.
181def mark_pending(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,+handle:Handle<S>) -> World<S,C,R,E>:
182  discard_status(~S,~C,~R,~E,phase_checked(~S,~C,~R,~E,A.Pending{},handle_key(~S,handle),metadata_valid(~S,~C,~R,~E,world,handle)))
183def owner_change(~S:Data,~C:Type,~R:Type,~E:Data,release:Bool,+key:K.Key,checked:World<S,C,R,E> & Bool) -> World<S,C,R,E>:
184  match release checked:
185    case _, (world,False{}):world
186    case False{}, (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},True{}):World{namespace,A.abandon(allocator,key),store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}
187    case True{}, (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},True{}):World{namespace,A.release(allocator,key),store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}
188# Abandon actual pending authority without rewinding reserved allocator progress.
189# Used by spawn rollback; does not pretend a reserved id was never consumed.
190def clear_pending(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,+handle:Handle<S>) -> World<S,C,R,E>:
191  owner_change(~S,~C,~R,~E,False{},handle_key(~S,handle),pending_valid(~S,~C,~R,~E,world,handle))
192# Only schema-owned cleanup calls this after all payloads and stamps are cleared.
193# Trusted cleanup boundary: release only after schema-owned payload/stamp clearing.
194# Metadata validation is not proof that arbitrary caller-supplied storage is empty.
195def release_cleared(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,+handle:Handle<S>) -> World<S,C,R,E>:
196  owner_change(~S,~C,~R,~E,True{},handle_key(~S,handle),metadata_valid(~S,~C,~R,~E,world,handle))
197def command_live(~S:Data,~C:Type,~R:Type,~E:Data,handle:Handle<S>,checked:World<S,C,R,E> & Bool) -> World<S,C,R,E> & Bool:
198  match checked:
199    case (world,True{}):(world,True{})
200    case (world,False{}):pending_valid(~S,~C,~R,~E,world,handle)
201# Allow a live or actual Pending handle, preserving namespace/generation checks.
202# This permits ordered deferred spawn/despawn workflows without making Pending
203# entities readable through ordinary live-component access.
204def command_valid(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,+handle:Handle<S>) -> World<S,C,R,E> & Bool:
205  command_live(~S,~C,~R,~E,handle,valid(~S,~C,~R,~E,world,handle))
206
207# Transfer one affine command to the reverse pending accumulator. It is not run
208# until a barrier; arbitrary command bodies are trusted engine adapters.
209def enqueue_command(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,command:Deferred.Command<World<S,C,R,E>>) -> World<S,C,R,E>:
210  match world:
211    case World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}:World{namespace,allocator,store,resource,events,command <> pending,registrations,nextSystemId,clock,lifecycle}
212# Wrap a raw World-to-World closure as a deferred command. No immediate effect
213# or automatic transaction inverse is supplied for the callback.
214def enqueue(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,command:World<S,C,R,E> -> World<S,C,R,E>) -> World<S,C,R,E>:
215  enqueue_command(~S,~C,~R,~E,world,Deferred.Raw{command})
216def drain_fifo(~S:Data,~C:Type,~R:Type,~E:Data,commands:List<Deferred.Command<World<S,C,R,E>>>,world:World<S,C,R,E>) -> World<S,C,R,E>:
217  match commands:
218    case []:world
219    case command <> rest:Deferred.run_disabled(~World<S,C,R,E>,command,drain_fifo(~S,~C,~R,~E,rest,world))
220def drain_apply(~S:Data,~C:Type,~R:Type,~E:Data,command:Deferred.Command<World<S,C,R,E>>,result:World<S,C,R,E> & TraceFacts.State) -> World<S,C,R,E> & TraceFacts.State:
221  match result:
222    case (world,trace):Deferred.run(~World<S,C,R,E>,command,world,trace)
223def drain_fifo_traced(~S:Data,~C:Type,~R:Type,~E:Data,commands:List<Deferred.Command<World<S,C,R,E>>>,world:World<S,C,R,E>,trace:TraceFacts.State) -> World<S,C,R,E> & TraceFacts.State:
224  match commands:
225    case []:(world,trace)
226    case command <> rest:
227      drain_apply(~S,~C,~R,~E,command,drain_fifo_traced(~S,~C,~R,~E,rest,world,trace))
228# Detach the current pending batch, execute it in FIFO enqueue order, and return
229# World plus operation trace state. Commands enqueued during that drain remain
230# pending for a later barrier; arbitrary callback effects are not transactional.
231def barrier_completed(~S:Data,~C:Type,~R:Type,~E:Data,result:World<S,C,R,E> & TraceFacts.State) -> World<S,C,R,E> & TraceFacts.State:
232  (world,trace)=result
233  (leave_run(~S,~C,~R,~E,world),trace)
234def barrier_entered(~S:Data,~C:Type,~R:Type,~E:Data,trace:TraceFacts.State,entered:World<S,C,R,E> & Tick.Tick) -> World<S,C,R,E> & TraceFacts.State:
235  match entered:
236    case (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},_):barrier_completed(~S,~C,~R,~E,drain_fifo_traced(~S,~C,~R,~E,pending,World{namespace,allocator,store,resource,events,[],registrations,nextSystemId,clock,lifecycle},trace))
237# Explicit deferred-application boundary; all commands in this batch share its tick.
238def barrier_traced(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>,trace:TraceFacts.State) -> World<S,C,R,E> & TraceFacts.State:
239  barrier_entered(~S,~C,~R,~E,trace,enter_run(~S,~C,~R,~E,world))
240# Apply the current pending command batch in FIFO order with tracing disabled.
241# Returns World; it neither invents a transaction rollback nor recursively drains
242# new work queued by those commands. See commands.bend for lifecycle adapters.
243def barrier(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E>:
244  Deferred.world_only(~World<S,C,R,E>,barrier_traced(~S,~C,~R,~E,world,TraceFacts.Off{}))
245
246# Immediate whole-resource swap returning the displaced actual R owner. This raw
247# administrative operation supplies neither projection restrictions nor undo.
248def replace_resource(~S: Data,~C: Type,~R: Type,~E: Data,world: World<S,C,R,E>,replacement: R) -> World<S,C,R,E> & R:
249  match world:
250    case World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}: (World{namespace,allocator,store,replacement,events,pending,registrations,nextSystemId,clock,lifecycle},resource)
251
252def event_append_loop(~E: Data,reversed: List<&2,E>,+output: List<&2,E>) -> List<&2,E>:
253  match reversed:
254    case []: output
255    case head <> rest: event_append_loop(~E,rest,head <> output)
256
257def event_append(~E: Data,+left: List<&2,E>,+right: List<&2,E>) -> List<&2,E>:
258  event_append_loop(~E,List.reverse(&2,E,left),right)
259
260def event_publish_nonempty(~S: Data,~C: Type,~R: Type,~E: Data,world: World<S,C,R,E>,+published: List<&2,E>) -> World<S,C,R,E>:
261  match world:
262    case World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}: World{namespace,allocator,store,resource,event_append(~E,events,published),pending,registrations,nextSystemId,clock,lifecycle}
263
264# An empty publication changes no field and needs no traversal of retained events.
265# Append detached Data events in list order and return World. Empty input preserves
266# World. This does not synchronize typed event Runtime batches/readers or run systems.
267def event_publish(~S: Data,~C: Type,~R: Type,~E: Data,world: World<S,C,R,E>,+published: List<&2,E>) -> World<S,C,R,E>:
268  match published:
269    case []: world
270    case head <> rest: event_publish_nonempty(~S,~C,~R,~E,world,head <> rest)
271
272# Return World and its copied Data event list without consuming or acknowledging
273# reader positions. Affine Type event logs use their separate resource ownership.
274def events_view(~S: Data,~C: Type,~R: Type,~E: Data,world: World<S,C,R,E>) -> World<S,C,R,E> & List<&2,E>:
275  match world:
276    case World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}: (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},events)
277
278def observe_resource_finish(~S: Data,~C: Type,~R: Type,~E: Data,~V: Type,observed: R & V,+namespace: U32,allocator: A.Allocator,store: C,+events: List<&2,E>,pending:List<Deferred.Command<World<S,C,R,E>>>,+registrations: List<&2,RegistrationMeta>,+nextSystemId: U32,+clock: Clock.State,lifecycle:Notice.Journal<S>) -> World<S,C,R,E> & V:
279  (resource,value) = observed
280  (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},value)
281
282# Trusted resource-owner adapter: project returns the actual R with an Out owner
283# or value, then World is rebuilt. The signature does not prove projection purity
284# or allow duplicating affine resource contents.
285def observe_resource(~S: Data,~C: Type,~R: Type,~E: Data,~V: Type,~project: R -> R & V,world: World<S,C,R,E>) -> World<S,C,R,E> & V:
286  match world:
287    case World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}: observe_resource_finish(~S,~C,~R,~E,~V,project(resource),namespace,allocator,store,events,pending,registrations,nextSystemId,clock,lifecycle)
288
289def register_checked(~S: Data,~C: Type,~R: Type,~E: Data,available: Bool,world: World<S,C,R,E>,+name: String,+access: List<&2,String>) -> World<S,C,R,E> & Registration:
290  match available world:
291    case False{} world: (world,RegistrationRejected{SystemIdExhausted{}})
292    case True{} World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}: (World{namespace,allocator,store,resource,events,pending,RegistrationMeta{nextSystemId,name,access} <> registrations,(nextSystemId + 1 : U32),clock,lifecycle},Registered{nextSystemId})
293
294# Allocate a system metadata id/name/access entry, returning World and Registration.
295# System-id exhaustion preserves World. Metadata alone does not install a runner
296# or validate a gameplay body; ordinary registered-system setup performs that join.
297def register(~S: Data,~C: Type,~R: Type,~E: Data,world: World<S,C,R,E>,+name: String,+access: List<&2,String>) -> World<S,C,R,E> & Registration:
298  match world:
299    case World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}: register_checked(~S,~C,~R,~E,U32.is_lt(nextSystemId,4294967295),World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},name,access)
300
301# Exact ordered declaration comparison; no normalization grants capabilities.
302def access_equal(xs: List<&2,String>,ys: List<&2,String>) -> Bool:
303  match xs ys:
304    case Nil{} Nil{}: True{}
305    case Nil{} Con{_,_}: False{}
306    case Con{_,_} Nil{}: False{}
307    case Con{x,xs} Con{y,ys}: Bool.and(SE.equal(x,y),access_equal(xs,ys))
308
309def matching_access(sameName: Bool,metaAccess: List<&2,String>,wantedAccess: List<&2,String>) -> Bool:
310  match sameName:
311    case False{}: False{}
312    case True{}: access_equal(metaAccess,wantedAccess)
313
314def matching_id(sameId: Bool,metaName: String,wantedName: String,metaAccess: List<&2,String>,wantedAccess: List<&2,String>) -> Bool:
315  match sameId:
316    case False{}: False{}
317    case True{}: matching_access(SE.equal(metaName,wantedName),metaAccess,wantedAccess)
318
319def registration_meta_matches(meta: RegistrationMeta,+id: U32,+name: String,+access: List<&2,String>) -> Bool:
320  match meta:
321    case RegistrationMeta{mid,mname,maccess}: matching_id(U32.is_eq(mid,id),mname,name,maccess,access)
322
323def registration_list_walk(registrations: List<&2,RegistrationMeta>,+id: U32,+name: String,+access: List<&2,String>,found: Bool) -> Bool:
324  match registrations found:
325    case _ True{}: True{}
326    case [] False{}: False{}
327    case meta <> rest False{}: registration_list_walk(rest,id,name,access,registration_meta_matches(meta,id,name,access))
328
329def registration_list_matches(registrations: List<&2,RegistrationMeta>,+id: U32,+name: String,+access: List<&2,String>) -> Bool:
330  registration_list_walk(registrations,id,name,access,False{})
331
332
333# Return World and its namespace for engine admission checks, not global provenance.
334def namespace(~S: Data,~C: Type,~R: Type,~E: Data,world: World<S,C,R,E>) -> World<S,C,R,E> & U32:
335  match world:
336    case World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}: (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},namespace)
337
338# Check retained id, name and exact ordered access strings while returning World.
339# No sorting/normalization creates extra authority or verifies arbitrary callbacks.
340def registration_matches(~S: Data,~C: Type,~R: Type,~E: Data,world: World<S,C,R,E>,+id: U32,+name: String,+access: List<&2,String>) -> World<S,C,R,E> & Bool:
341  match world:
342    case World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}: (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},registration_list_matches(registrations,id,name,access))
343
344def with_store_finish(~S: Data,~C: Type,~R: Type,~E: Data,~Out: Type,observed: C & Out,+namespace: U32,allocator: A.Allocator,resource: R,+events: List<&2,E>,pending:List<Deferred.Command<World<S,C,R,E>>>,+registrations: List<&2,RegistrationMeta>,+nextSystemId: U32,+clock: Clock.State,lifecycle:Notice.Journal<S>) -> World<S,C,R,E> & Out:
345  (store,value) = observed
346  (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},value)
347
348# Trusted storage adapter threading actual C and returning Out with World. This is
349# not a gameplay capability or a universal guarantee that effect preserves storage.
350def with_store(~S: Data,~C: Type,~R: Type,~E: Data,~Out: Type,effect: C -> C & Out,world: World<S,C,R,E>) -> World<S,C,R,E> & Out:
351  match world:
352    case World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}: with_store_finish(~S,~C,~R,~E,~Out,effect(store),namespace,allocator,resource,events,pending,registrations,nextSystemId,clock,lifecycle)
353
354def pending_prepend(~S: Data,~C: Type,~R: Type,~E: Data,command: World<S,C,R,E> -> World<S,C,R,E>,counted: List<(World<S,C,R,E> -> World<S,C,R,E>)> & U32) -> List<(World<S,C,R,E> -> World<S,C,R,E>)> & U32:
355  (commands,count) = counted
356  (command <> commands,(count + 1 : U32))
357
358def pending_length_acc(~S: Data,~C: Type,~R: Type,~E: Data,commands:List<Deferred.Command<World<S,C,R,E>>>,reversed: List<Deferred.Command<World<S,C,R,E>>>,count: U32) -> List<Deferred.Command<World<S,C,R,E>>> & U32:
359  match commands:
360    case []: (List.reverse(&1,Deferred.Command<World<S,C,R,E>>,reversed),count)
361    case command <> rest: pending_length_acc(~S,~C,~R,~E,rest,command <> reversed,(count + 1 : U32))
362
363def pending_length(~S: Data,~C: Type,~R: Type,~E: Data,commands:List<Deferred.Command<World<S,C,R,E>>>) -> List<Deferred.Command<World<S,C,R,E>>> & U32:
364  pending_length_acc(~S,~C,~R,~E,commands,[],0)
365
366def pending_count_join(~S: Data,~C: Type,~R: Type,~E: Data,+namespace: U32,allocator: A.Allocator,store: C,resource: R,+events: List<&2,E>,+registrations: List<&2,RegistrationMeta>,+nextSystemId: U32,+clock: Clock.State,counted: List<Deferred.Command<World<S,C,R,E>>> & U32,lifecycle:Notice.Journal<S>) -> World<S,C,R,E> & U32:
367  (pending,count) = counted
368  (World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},count)
369
370# Count pending affine command closures without executing them, returning World
371# with the original command order and owners retained.
372def pending_count(~S: Data,~C: Type,~R: Type,~E: Data,world: World<S,C,R,E>) -> World<S,C,R,E> & U32:
373  match world:
374    case World{+namespace,allocator,store,resource,+events,pending,+registrations,+nextSystemId,+clock,lifecycle}: pending_count_join(~S,~C,~R,~E,namespace,allocator,store,resource,events,registrations,nextSystemId,clock,pending_length(~S,~C,~R,~E,pending),lifecycle)
375
376# Event runtime retention replaces only its owned event domain.
377# Administrative replacement of the Data event domain only. It does not execute
378# pending work, rebuild registrations or reset external event reader owners.
379def events_replace(~S: Data,~C: Type,~R: Type,~E: Data,world: World<S,C,R,E>,+retained: List<&2,E>) -> World<S,C,R,E>:
380  match world:
381    case World{+namespace,allocator,store,resource,_,pending,+registrations,+nextSystemId,+clock,lifecycle}: World{namespace,allocator,store,resource,retained,pending,registrations,nextSystemId,clock,lifecycle}
382
383def key_handles(~S:Data,+namespace:U32,keys:List<&2,K.Key>) -> List<&2,Handle<S>>:
384  match keys:
385    case []:[]
386    case key <> rest:Handle{namespace,key} <> key_handles(~S,namespace,rest)
387def handles_join(~S:Data,~C:Type,~R:Type,~E:Data,+namespace:U32,store:C,resource:R,events:List<&2,E>,pending:List<Deferred.Command<World<S,C,R,E>>>,registrations:List<&2,RegistrationMeta>,nextSystemId:U32,clock:Clock.State,observed:A.Allocator & List<&2,K.Key>,lifecycle:Notice.Journal<S>) -> World<S,C,R,E> & List<&2,Handle<S>>:
388  match observed:
389    case (allocator,keys):(World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle},key_handles(~S,namespace,keys))
390# Return World and currently visible live full handles in allocator-visible order.
391# Reserved/Pending handles are not an ordinary query membership list yet.
392def handles(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E> & List<&2,Handle<S>>:
393  match world:
394    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}:handles_join(~S,~C,~R,~E,namespace,store,resource,events,pending,registrations,nextSystemId,clock,A.visible(allocator),lifecycle)
395
396# Bevy ad678262 bevy_app/sub_app.rs runs the schedule before clear_trackers.
397# The public completed App boundary owns this call, including handled errors;
398# bare system invocations and barriers preserve the current retention window.
399def maintain_lifecycle(~S:Data,~C:Type,~R:Type,~E:Data,world:World<S,C,R,E>) -> World<S,C,R,E>:
400  match world:
401    case World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,lifecycle}:World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock,Notice.maintain(~S,lifecycle)}