Skip to content

[M1-ECS-05] Deterministic iteration order - #21

Merged
offdev merged 2 commits into
masterfrom
m1-ecs-05-iter-order
Sep 14, 2026
Merged

offdev merged 2 commits into
masterfrom
m1-ecs-05-iter-order

Conversation

@offdev

@offdev offdev commented Sep 14, 2026

Copy link
Copy Markdown
Owner

M1-ECS-05 from roadmap/M1-heartbeat.md: the documented deterministic iteration contract over the World::each visit order, plus the convergence property test.

What changed

  • Contract documented (docs/api/iteration_order.md): archetypes in ascending archetype id (= the order a component set is first seen by a mutation — creation order), entities within an archetype in ascending slot id, empty query ascending slot id. The order is a pure function of the world state, never of the operation history.
  • Dense-id-order scheme documented: rows stay in ascending slot-id order through insert/remove (archetype.h invariants I1–I4), so two histories converging on the same state visit identically.
  • No unordered containers in the iteration path: only the fixed 256-record archetype table (id-ordered scan), packed slot columns, per-slot direct-index records, and membership-only 256-bit guard sets. The anticipated "one internal hash structure" (entity→archetype) is direct indexing — not even a hash, a stricter reading of the step allowance; the one hash structure in laige-sim (the component type-key index) is lookup-only, never iterated, and setup-path only.
  • Convergence property test (tests/laige-sim/iter_order_tests.cpp, CTest iter_order): two worlds whose operation sequences interleave create/destroy differently — A: serial per-entity scripts + end-phase dead destroys; B: the same create phase (LIFO requires it for the identical entity→id assignment) with PRNG-scattered dead-destroys, round-robin component steps in per-round PRNG permutations, and PRNG-placed scratch pairs — converge on the identical final state including the entity→id assignment and iterate identically for five queries, verified per-visit against an independent oracle. Component moves are structural throughout, pinning the dense-id scheme. A KAT pins the exact degenerate visit sequences; the greppable iter-order … fnv1a=0x… line is byte-identical across all instantiations, trees, and seeds.
  • Docs cross-refs (query.md, entity.md, archetype.md, component_registry.md, docs/README, sim README); roadmap checkbox + progress board (M1 5/25, total 27/193); change-log row in commit 2.
  • No public API added — laige-api.json unchanged (452 symbols, scanner rerun clean); no include-graph change; include lint OK.

Verification

  • ctest -R iter_order green (the step Verify command): build (g++), build-asan, build-tsan, build-clang, build-release
  • Full laige-sim_tests suite green on build (no regressions)
  • Visit hash 0x12fc225187f3b359 byte-identical across g++/Clang/ASan/release trees and across LAIGE_TEST_SEED overrides
  • Size: the step estimated ~100 lines + tests; the test suite is ~960 lines (the convergence construction + oracle dominate — same order as M1-ECS-04, whose ~250-line estimate landed at ~1450)

Untested: no new runtime system, so no benchmark/diagnostics scope; the no-unordered-containers half is verified by inspection (CI source scan lands in M1-DET-01).

The documented iteration contract over the World::each visit order:

- visit order pinned: archetypes in ascending archetype id (= the
  order a component set is first seen by a mutation — creation
  order), entities within an archetype in ascending slot id, empty
  query ascending slot id; the order is a pure function of the
  world state, never of the operation history
- dense-id-order scheme documented: rows stay in ascending slot-id
  order through insert/remove (binary search + memmove shift,
  archetype.h invariants I1-I4); two histories converging on the
  same state therefore visit identically
- no unordered containers in the iteration path: the iteration
  touches only the fixed 256-record archetype table (id-ordered
  scan), packed slot columns, per-slot direct-index records, and
  membership-only 256-bit guard sets — the entity->archetype map
  the step anticipated is direct indexing, not even a hash (a
  stricter reading of the allowance); the one hash structure in
  laige-sim (the component type-key index) is lookup-only and
  never iterated, and sits on the setup path, not any tick
- convergence property test: two worlds whose operation sequences
  interleave create/destroy differently (serial scripts + end
  destroys vs. PRNG-scattered dead-destroys + round-robin
  component steps + PRNG-placed scratch pairs) converge on the
  identical final state — including the entity->id assignment —
  and iterate identically for five queries, verified per-visit
  against an independent oracle (free-list simulation +
  first-seen archetype order); component moves are structural
  throughout (adds of absent / removes of present components), so
  the dense-id scheme is pinned, not assumed
- KAT test pins the exact degenerate visit sequences;
  machine-greppable iter-order lines record seed, PRNG pair count,
  visit counts, and the FNV-1a hash of the visit sequences —
  byte-identical across g++/Clang/ASan/TSan/release trees, default
  and LAIGE_TEST_SEED-overridden seeds

New: tests/laige-sim/iter_order_tests.cpp (IterOrder.* in the shared
laige-sim_tests executable), CTest entry iter_order (the step's
Verify command; added to the TSan property list); no public API
added — laige-api.json unchanged (452 symbols, scanner rerun
clean); no src include-graph change (comments only; include lint
clean). Docs: docs/api/iteration_order.md (the contract: order,
dense-id scheme, no-unordered-containers table, convergence
property, ARCH-010 determinism scope, Performance section per
DOC-004) + cross-refs in query.md/entity.md/archetype.md/
component_registry.md, docs/README.md, src/laige-sim/README.md;
roadmap checkbox + progress board updated.
@offdev
offdev merged commit 2ea8125 into master Sep 14, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant