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