Phase 1 / Etch / Execute extension hooks#37
Open
guysenpai wants to merge 16 commits into
Open
Conversation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parse a bare statement-run fragment (the canonical text a cooked extension hook body carries: statements joined by "; ", no enclosing braces) into a fresh AstArena, exposing the body statement range (body_start/body_len) in the same encoding rule/fn bodies use. Reuses the existing parseStmt via a new parseStmtFragment loop that skips one optional .semicolon between statements (the parser otherwise only consumes ; inside a fill-array literal). New entry point parseStmtBlock + StmtBlockResult, not a new statement grammar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
world.zig gains the on_detach dispatch seam (ExtensionDetachFn + detach_hook + registerOnDetach/dispatchOnDetach), a mirror of the M1.0.6 on_attach pair — fired by the runtime deactivate path before removing an extension's components. Plus a per-entity active-extension side-table (entity_extensions: EntityId -> owned name slices) with addEntityExtension/removeEntityExtension/ hasEntityExtension/entityExtensions, populated by the shared activate path and freed in deinit. Backs the interpreter's has_extension/active_extensions. Inline tests cover the detach seam (register/dispatch/no-op) and the side-table (add/has/remove/order + leak-freedom). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The M1.0.6 on_attach seam now runs the cooked Etch text. interp.zig gains execHookText: parse the hook statement-run (parseStmtBlock) into a transient AstArena, rebind self.ast to it, bind the implicit entity, run via execStmtRun, and route deferred structural changes through the world's shared observer- deferred buffer (mirrors runObserverBody). bindToWorld registers the real on_attach/on_detach trampolines (ctx = *Interpreter); dispatchMethodOnValue's entity arm gains activate_extension/deactivate_extension/has_extension/ active_extensions. The Bridge holds an optional ExtensionResolver (setExtensionResolver) for name-only runtime activation. loader.zig: shared activateExtension now records the active extension (addEntityExtension) before firing on_attach; runtimeActivate/runtimeDeactivate are the runtime entries (deactivate fires on_detach first, then removes the extension's components); the load sequence drains hook-issued deferred commands after the activation pass, before on_spawned. ecs_bridge.zig: ext_resolver field. Decision frozen at scoping: TEXT re-parse, not bytecode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Headline: a cooked scene activating CombatModule runs on_attach at load (Health.max 100->150). Plus activate/deactivate/has_extension/active_extensions via Etch methods (single-entity rules keep the immediate structural mutation safe), and a deferred-command drain-before-on_spawned test via a Tier-0 stand-in attach callback (the interpreter has no entity.add/spawn in bodies, so a cooked hook cannot itself issue a deferred structural change). These live here, not in interp.zig, because they need the cook pipeline (circular import otherwise) — the M1.0.8 tier-dependency precedent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that the on_attach/on_detach seam executes the cooked Etch text via the bridge's registered callback, the doc comments on ExtensionAttachFn, dispatchOnAttach, applyExtensions, and the M1.0.6 seam test no longer frame execution as a future M1.0.9 deferral. Part of the §3.6.1 closing audit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Current-state table (last released tag v0.10.9, next milestone M1.1.0), a new Tags row for v0.10.9-extension-hooks, the M1.0.6 text-vs-bytecode decision closed (text re-parse), a new M1.0.9 scope-boundary open-decision entry (surface findings + recorded deviations), and the Last updated date. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reopened ACTIVE for the merge-blocker round-trip. FROZEN delta: E3 gains the B1 deferred-command requirement (Etch activate/deactivate enqueue, applied at the flush boundary after iteration — runtimeActivate/runtimeDeactivate stay for load + direct paths); E4 gains the B2 type-checker recognition of the four entity methods; two acceptance tests added; Recorded deviations B1 + B2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
iterateArchetype walks arch.chunks LIVE, so an Etch activate_extension/ deactivate_extension doing an immediate add/removeComponentDynamic migrated the entity's archetype mid-iteration and corrupted the walk. dispatchMethodOnValue now ENQUEUES a deferred command (PendingExtension, mirror of pending_tags; extension bytes resolved at the call), drained at the tick boundary by flushPendingExtensions (after iteration, snapshot to avoid recursive drain), which calls the loader's bytes-taking activateExtension / new deactivateExtension (both pub now) — components added/removed + the Tier-0 seam fired. The immediate runtimeActivate/runtimeDeactivate stay for the load + direct-programmatic paths. +1 test: a multi-entity rule activates each matched entity with no corruption. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
synthMethodCall's entity arm (dispatchMethodOnType, types.zig) emitted type_mismatch "no method on an Entity" for any non-inherent/trait method, so a real rule body calling activate_extension/deactivate_extension/has_extension/ active_extensions failed weld check — the API was unusable from type-checked Etch. The entity arm now recognizes the four builtin methods before the trait lookup (falling through for anything else): activate/deactivate_extension(string) -> unit (unknown, statement-use), has_extension(string) -> bool, active_extensions() -> [string], with arg-count/type validation (checkExtensionNameArg). +1 test: a checked program calling all four passes clean; a wrong-typed arg is still rejected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Journal B1+B2 implementation; reconcile Closing notes (the two first-close flags are now RESOLVED — B1 defers the Etch activate/deactivate, B2 type-checks the four methods); +14 tests; Status CLOSED. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The M1.0.9 scope-boundary items (2) immediate-mutation and (4) interpreter-level- only are now superseded by the round-trip: B1 defers the Etch activate/deactivate, B2 type-checks the four methods. Tags row notes B1+B2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Milestone
Brief:
briefs/M1.0.9-extension-hooks.md— Status CLOSED.M1.0.9 founds the runtime text-execution surface M1.0.6 deferred: a cooked extension
on_attach/on_detachhook is re-parsed and walked against the live world. Decision frozen at scoping: TEXT re-parse, not bytecode (the VM is Phase 2). The last Etch milestone.Deliverables (Scope E1–E5)
parser.parseStmtBlock: parse a cooked hook statement-run ("; "-joined, no braces) into a transientAstArena(reusesparseStmtviaparseStmtFragment, skipping one optional.semicolon).interp.execHookText: transient-arenaself.astrebind + implicitentitybind +execStmtRun+ observer-deferred routing;bindToWorldregisters the realon_attach/on_detachtrampolines so the loader'sdispatchOnAttachreaches execution.world.zigon_detachseam (mirror) +loaderruntime activate/deactivate +dispatchMethodOnValueactivate_extension/deactivate_extension.has_extension/active_extensionsEtch methods.CLAUDE.md§3.4.✅ B1 + B2 round-trip (the two merge blockers — fixed)
Recorded as round-tripped FROZEN deltas in the brief (
docs(brief): record B1+B2 round-trip).iterateArchetypewalksarch.chunkslive; the Etchactivate_extension/deactivate_extensionpreviously did an immediateadd/removeComponentDynamic→ archetype migration mid-walk → corruption. NowdispatchMethodOnValueENQUEUES a deferred op (pending_extensions, mirror ofpending_tags, extension bytes resolved at the call), drained at the tick boundary byflushPendingExtensions(after iteration; snapshot → no recursive drain) via the loader's bytes-takingactivateExtension/ newdeactivateExtension(bothpub), which fire the Tier-0on_attach/on_detachseam. The immediateruntimeActivate/runtimeDeactivatestay for the load + direct-programmatic paths (outside iteration). Realization note (Recorded deviations): the deferred queue is interp-side, not the Tier-0CommandBuffer, because the ECS command-buffer apply path cannot reachscene/loader(theecs → scenelayering forbids it); the interpreter is the correct tier to own + flush it. +1 test: a multi-entity rule activates every matched entity with no corruption, effects applied after the flush.dispatchMethodOnType's entity arm (types.zig) rejected any non-inherent/trait method on anEntity, so a real rule body calling the methods failedweld check. The entity arm now recognizesactivate_extension(string) -> unit,deactivate_extension(string) -> unit,has_extension(string) -> bool,active_extensions() -> [string](arg-validated) before the "no method" error. This supersedes the first-close "interpreter-level only" position — tests no longer skip the checker. +1 test: a checked program calling all four passes clean; a wrong-typed arg is still rejected.The two B-flags are resolved. The only remaining point is surface-adaptation #1 (Recorded deviations): the "on_attach structural command drained before on_spawned" acceptance test uses a Tier-0 stand-in attach callback, NOT a cooked Etch hook issuing
entity.add(...)/spawn— because the interpreter has noentity.add/spawnin bodies (S4 boundary) and tag mutation is not cookable, so a cooked hook cannot itself issue a deferred structural change today. The drain mechanism is built + correct (and now also exercised by B1's deferred extension ops). Happy to round-trip if the literal form is wanted (it needs interp structural-mutation support — a separate milestone). Four further surface-adaptations (tests location, trampolines location,{ source: entity }, §30.5 warning out of scope) are documented in the brief.Closing notes
;-separator (cooked text is"; "-joined; the parser only consumes;inside fill-arrays →parseStmtBlockneeds its own separator-skipping loop), the brief's correct call thatself.astis rebindable (the rebind is required — the executor resolves identifiers viaself.ast.strings), and the two-EntityIdduality.execHookTextmirrorsrunObserverBody; B1'spending_extensionsmirrorspending_tags— both inherit proven discipline.test-extensions14/14, zero build warnings,fmt/lintclean.entity.add/spawn(drain wiring future-proofs it); §30.5 compile-time warning deferred; two out-of-scope "M1.0.9" doc-comment refs left untouched (ast.zig:647,prefab_integration_test.zig:6).Validation (Étape 4, re-run after B1+B2)
@exclusive_with; no multi-entity; noweld extension reapply; no re-type-check of the fragment)CombatModule→Health.max100→150 at load; multi-entity activate round-tripzig build(zero warnings),zig build test,zig fmt --check,zig build lintgreen;commit-msggreen on every commit; pre-push (build + test + test-release + test-tsan-wayland) greenCLAUDE.mdupdated (§3.4 + B1/B2 reconciliation) and committed on the branchStatus:CLOSED,Closed:2026-06-30docs: reconcile CLAUDE.md for B1+B2 (M1.0.9)Merge + tag (
v0.10.9-extension-hooks) left to you.🤖 Generated with Claude Code