Skip to content
Merged
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
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ knowledge base — see § Quick links spec.
|---|---|
| Phase | 1 (Etch ↔ ECS) |
| Current milestone | (none — between milestones) |
| Last released tag | `v0.10.7-cross-file-import` |
| Last released tag | `v0.10.8-const-private-test` |
| Active branch | `main` |
| Next planned milestone | M1.0.8`const` top-level + `private` + `test` graduation (the remaining `non_s3_keywords` reserve list; `import` graduated in M1.0.7). M1.0.9 (extension hook `on_attach`/`on_detach` execution, text-vs-bytecode decision) follows. |
| Next planned milestone | M1.0.9extension hook `on_attach`/`on_detach` execution (the text-vs-bytecode serialization decision; the interp is compile-once from the AST with no runtime text-execution surface yet). `override` stays the last reserved `non_s3_keywords` member (waits for a Tier-1 overridable module). |

## Tags

Expand Down Expand Up @@ -45,6 +45,7 @@ knowledge base — see § Quick links spec.
| `v0.10.5-scene-load` | 2026-06-27 | M1.0.5 — Runtime `.scene.bin` loader → ECS | Runtime loader `src/core/scene/loader.zig` reusing `accessor.zig` verbatim: `openVerified` (magic/version + `verifyHash` → `CorruptScene`) + `buildSchemaRemap` (Schema-Registry index → runtime `ComponentId` via `idOf`, size/alignment-validated → `SchemaMismatch`/`UnknownComponent`) + per-entity `spawnDynamicWithValues` instantiation + UUID(16 B)→handle map + two-phase `on_spawned` (`ObserverRegistry.dispatchOnSpawned` + `World.dispatchOnSpawned`, all-entities-exist-first ordering) + resource loading (POD + `string` fields interned into the Tier-0 persistent heap, owned by `LoadResult`). `loadFromBytes` (byte-level core) + `loadScene(path)` (mmap). New `error.MalformedScene` (structure invalid, distinct from `CorruptScene` = hash mismatch). Persistent heap moved `src/etch/persistent.zig` → `src/core/memory/persistent.zig` (Tier 0). Bench median ~1.05 ms / 10k entities (M4 Pro, ReleaseFast). |
| `v0.10.6-prefabs-crossrefs-extensions` | 2026-06-28 | M1.0.6 — prefabs + entity→entity cross-references + extension activation | `.prefab.bin` cook (standalone + `of` variants) + `instance of` flattening at scene cook (byte-identical to hand-authored). Entity→entity cross-refs via new `FieldKind.entity_` (8 B `EntityId`, component-only, default `dead`=`0xFF`) — by **name** (like `parent:`), resolved at load (`resolveCrossRefs`, bounds-checked → `MalformedScene`). Extension activation: `extensions:` grammar clause (entity + instance) + Entity Extensions Table + dedup Prefab ID Table + hooks sub-section in the `extensions_offset` region (shape A); `extends` cook (components + `requires` + `on_attach`/`on_detach` rendered as **text**). Load `applyExtensions`: resolve by name → `addComponentDynamic` (conflict → `ExtensionComponentConflict`) → fire Tier-0 `on_attach` seam (`registerOnAttach`/`dispatchOnAttach`; loader never touches the VM). **`format_version` 1→2** (region restructured; v1 → `BadVersion`, re-cook). Hook **execution** re-scoped → M1.0.9. |
| `v0.10.7-cross-file-import` | 2026-06-29 | M1.0.7 — Cross-file `import` (resolver pass-1) | `import` graduated parser-up (lexer `kw_import` out of `non_s3_keywords`; `ImportDecl` AST + arena slabs; `parseImportDecl` — the 4 forms, items accept IDENT **and** TYPE_IDENT, D-D). `root.validateProject` builds the module dependency graph from `ProjectFile.name` (module path under `src/`), topo-sorts it (deps-first `checkProject` order), and detects cycles → **`E0108 ImportCycle`** (D-B: NOT E0101; E0101 stays DuplicateSymbol). **Per-module** byte-keyed exports index (`ExportEntry {kind, visibility, arena_index, item_id}` — NOT a flat global index; two modules exporting the same name never collide) extends the M0.9 `ProjectContext`. `bindImports` resolves each file's imports: selective items enter scope under their local name; module aliases record an `imported_alias` binding (qualified `m.Type` resolution deferred — D-F); diagnostics `E0103 NotAModule` / `E0104 UnknownExport` / `E0107 ImportPrivateItem` (wired-but-dormant until `private`, D-G). `checkComponentInstance` resolves an imported component **cross-arena** (decl fetched from its defining arena, field names compared by **bytes**) → **unblocks the E1793 false positive**: a `.prefab.etch` importing its components validates clean; E1793 fires only for a genuinely-undeclared component. Cross-arena field-TYPE check (E1795) is builtin-typed-only; named foreign field types are a documented residual. |
| `v0.10.8-const-private-test` | 2026-06-29 | M1.0.8 — `const` top-level + `private` + `test` graduation | The last three `non_s3_keywords` graduate parser-up (`override` stays reserved). Lexer: `kw_const`/`kw_private`/`kw_test` added to `s3_keywords`, removed from the reserve list (identifier→keyword logic unchanged). AST: `ConstDecl`/`TestDecl` side-slabs + `Visibility {public, private}` field on the `Item` node (`itemVisibility`/`setItemVisibility`). Parser: `parseConstDecl` (`const ( IDENT \| TYPE_IDENT ) : type = const_expr`, top-level only — `parseStmt` untouched, so `const` in a block is a parse error per part1 §4.5); `parseTestDecl` (`test STRING block`, reuses `parseBlockExpr`, no execution); `private` prefix in `parseOneTopLevel` (after annotations, before dispatch; rejects `private import/const/type`; sets the item `.private`). Lockstep set extended {dispatch, `recoverToTopLevel` stop-set, the single error-message enumeration} — `private` adds no stop-set member. Resolver: `SymbolKind` += `const_`/`test_`; `pass1Collect` registers both, `checkConstValue` reuses the field-default surface (`E1101 NotConstEvaluable` + `E0200 TypeMismatch`); tests registered but not exported. `buildExports` exports `const_decl` and reads `Item.visibility` per decl → **activates the dormant `E0107 ImportPrivateItem`** check. **Cleared M1.0.7 debts**: cross-file `const` resolves; selectively importing a `private` item emits `E0107`. |

## Hypotheses validated by spikes

Expand Down Expand Up @@ -75,6 +76,7 @@ knowledge base — see § Quick links spec.
- **`format_version` 1→2 (correction to the L68 day-1 prediction)**: the cross-references section was genuinely additive (no bump at E4, count-0 back-compatible), but the Entity Extensions Table's full structure (vs M1.0.4's bare `[0]` count-placeholder) is **not** count-0 back-compatible → required `format_version` 1→2. The `FieldKind` dispatch prediction held (`entity_` added as a new variant). `.scene.bin`/`.prefab.bin` are re-cookable Phase-1 artifacts (deterministic cook) → no v1 back-compat.
- **`FieldKind.entity_` realizes `Entity` (M1.0.6)**: 8 B/8-align `EntityId`, default `dead` (`@memset 0xFF`, not `{0,0}` = a live handle to entity 0), component-only (gated to `reg_kind == .component`, mirror of resource-only `string_`/`enum_`).
- **M1.0.7 scope boundary (cross-file import)**: `import` graduated parser-up (the only `non_s3_keywords` member to leave; `const`/`private`/`test`/`override` stay reserved for M1.0.8). **Validated approach**: a **per-module** byte-keyed exports index (`{name bytes → {kind, arena_index, item_id}}`) extends the M0.9 byte-keyed `ProjectContext` pattern (StringIds are per-arena) — NOT a flat global index, so two modules exporting the same name never collide; the imported-component cross-arena resolution (decl fetched from its defining arena, field names compared by bytes) **unblocks the E1793 false positive** for `.prefab.etch`. **Deferred-but-pre-wired**: module-alias qualified `m.Type` resolution (D-F — the `imported_alias` binding is recorded at E5, so the descending `Path` walk is purely additive later); `E0107 ImportPrivateItem` (D-G — wired through the exports `visibility` flag, dormant until `private` graduates M1.0.8). **Not debt — moot**: the cross-arena field-TYPE check (E1795) resolves builtin types; this is COMPLETE for components because `validateFieldsInDecl(.component_like)` admits only builtin-POD field types (named struct/enum/string rejected on components) — the named-type branch is unreachable for a valid component (forward-compat headroom only). The cross-file `const`-import acceptance test is deferred to M1.0.8 (`const` is not parseable until it graduates) — cross-file resolution is covered by the imported-component type test + the prefab unblock.
- **M1.0.8 scope boundary (`const`/`private`/`test` graduation)**: the last three `non_s3_keywords` graduate parser-up; `override` stays reserved (waits for a Tier-1 overridable module). **Top-level `const` only** — `parseStmt` is deliberately NOT extended, so a block-level `const` is a parse error; the tri-document drift (`const_stmt` under `etch-grammar.md §4.1` statements vs §4.5 "top-level only" vs `local_const` in `etch-resolver-types.md §2.1`) is a PREEXISTING cross-doc inconsistency deferred to a KB-audit conversation (NOT resolved here). **`private` is direct export-visibility + `E0107` only** — visibility inheritance (`etch-resolver-types.md §10.2`) and `W0902 PrivateTypeInPublicImpl` stay additive/deferred; `private` is parsed as a prefix on a `declaration_body` (rejects `private import/const/type`) and adds no `recoverToTopLevel` stop-set member. **`test` is parse + validate + symbol registration only** — no execution surface exists (same blocker family as M1.0.9); tests register a `test_` symbol but are never exported. **Residual**: a string-named `test "X"` registers under the byte sequence `X` via `registerSymbol`, so it shares the name namespace with identifier-named symbols (a `test "Foo"` collides with `component Foo` → `E0101`); acceptable for M1.0.8, revisit when the M1.0.9 test-runner formalizes test identity. **Cross-file tests** live in `tests/etch/import_resolve_test.zig` (the `validateProject` harness), not inline in `types.zig` (which cannot reach `validateProject` — a tier-up dependency).

## Non-negotiable rules

Expand Down
Loading