Block Foundation
The current block layer is a minimal serial domain executor over always-resident
world storage. It lives in include/tess/block/block.h and is exported by
tess/tess.h.
Public Surface
WritePolicyrecords intended write discipline:ReadOnly,UniquePerTile,UniquePerChunk, andUnsafe.is_valid_write_policy(policy)validates a runtime policy value against that enumerator set.ChunkDomainis a non-owningstd::span<const ChunkKey>wrapper.OwnedChunkDomainowns sorted chunk keys returned by allocating domain builders and can be adapted toChunkDomainwhile the owner lives.chunk_domain(span)adapts a prebuilt key span without allocation.explicit_chunk_domain(span)copies and sorts explicit keys in ascendingChunkKeyorder.dirty_chunk_domain(world, flags)andactive_chunk_domain(world, flags)return owning domains using the current always-resident metadata queries.BlockScratchowns caller-reusable temporary storage backed by a heapstd::byte[]buffer aligned forstd::max_align_t.reserve_bytes(bytes)grows the backing store when needed by allocating a fresh buffer: growth invalidates previously returned spans and does not preserve contents, whileused_bytes()accounting carries over.reset()rewinds the bump offset, andcapacity_bytes(),used_bytes(), andremaining_bytes()expose byte accounting. The class is move-only.BlockScratch::allocate<T>(count)returns an alignedstd::span<T>from the current bump offset. It does not allocate when existing capacity is sufficient. Zero-count requests, byte-count overflow, and capacity exhaustion all return an empty span and leaveused_bytes()unchanged.Tmust be trivially default-constructible and trivially destructible (implicit-lifetime), so the spans over the implicitly created objects in thestd::bytearray storage are well-defined.BlockDiagnosticsowns caller-reusable counters for serial block execution. It currently recordsscratch_allocation_failures, with explicitrecord_scratch_allocation_failure()andreset()calls.BlockCtx<World, Policy>is a non-owning serial execution context over a world,ChunkDomain, compile-timeWritePolicy, and optionalBlockScratchandBlockDiagnostics. Callers must keep the world, domain key storage, scratch storage, and diagnostics storage alive for the context lifetime.block_ctx<Policy>(world, domain)constructs a policy-typedBlockCtxwithout allocation.block_ctx<Policy>(world, domain, scratch)constructs a policy-typedBlockCtxwith a non-owning scratch pointer.block_ctx<Policy>(world, domain, diagnostics)constructs a policy-typedBlockCtxwith a non-owning diagnostics pointer.block_ctx<Policy>(world, domain, scratch, diagnostics)constructs a policy-typedBlockCtxwith both optional caller-owned facilities.BlockCtx::world(),domain(),policy(),size(), andempty()expose the context inputs and domain state.BlockCtx::scratch()returns the optional scratch pointer, andBlockCtx::reset_scratch()rewinds it when present. Context iteration does not reset scratch automatically; callers choose whether scratch lifetime is per domain, per chunk, or per algorithm.BlockCtx::diagnostics()returns the optional diagnostics pointer, andBlockCtx::reset_diagnostics()clears it when present. Scratch exhaustion is still reported explicitly by caller code afterallocate<T>returns an empty span.BlockCtx::chunk_view(key)returns an explicit chunk view for a chunk key.ReadOnlycontexts exposeChunkView<const World>even when the stored world object is mutable. Other current policies exposeChunkView<World>.BlockCtx::for_each_chunk(fn)walks the domain serially and invokesfn(view)with the policy-selected view type.for_each_chunk<Policy>(world, domain, fn)constructs a policy-typed context and walks the domain serially without allocation.for_each_chunk(world, domain, policy, fn)validates and dispatches the runtime policy.ReadOnlyinvokesfn(view)withChunkView<const World>for mutable worlds; other current policies invokeChunkView<World>. Because the policy is runtime but the callback type is compile-time, callbacks passed to this overload must accept the selected policy view type; selecting an invalid runtime policy value or incompatible callback/policy pair is a programmer error and fails fast. Preferfor_each_chunk<Policy>(world, domain, fn)orBlockCtx<World, Policy>when the policy is already known.- Parallel-ready ownership validation currently lives above the raw block API
in queued-operation phase planning.
plan_parallel_execution_phases(plan)accepts onlyReadOnlyandUniquePerChunkplanned operations, keeps same-chunk mutable work in separate phases, and rejectsUniquePerTileuntil tile subdomains exist. ChunkView<World>exposes the resolved page, metadata, key, chunk coordinate, chunk bounds, typed field spans throughChunkPage, and chunk-local tile helpers.ChunkView<World>::local_coord(LocalTileId)andChunkView<World>::local_tile_id(LocalCoord3)convert local tile positions using row-major chunk-local order.ChunkView<World>::local_bounds()returns the signed local candidate box{Coord3{0, 0, 0}, ShapeTraits<Shape>::chunk}.ChunkView<World>::contains_local(Coord3)andChunkView<World>::try_local_coord(Coord3)validate signed local candidate coordinates before converting them to unsignedLocalCoord3.ChunkView<World>::is_boundary(LocalCoord3)reports whether a valid local tile touches any non-degenerate chunk face, andChunkView<World>::is_interior(LocalCoord3)is its inverse for valid local coordinates. Axes with chunk extent1do not make every tile a boundary.ChunkView<World>::world_coord(LocalCoord3)andChunkView<World>::world_coord(LocalTileId)convert local positions to world coordinates for the current chunk.ChunkView<World>::world_coord(Coord3)converts signed local candidates, including one-step-out candidates, to world coordinates for the current chunk.ChunkView<World>::for_each_tile(fn)invokesfn(LocalTileId, LocalCoord3)for every local tile in ascendingLocalTileIdorder.
Iteration is deterministic when domains are produced by the provided builders.
The hot executor path does not allocate when passed a prebuilt ChunkDomain.
Policy-typed ReadOnly contexts enforce const page, metadata, and field span
access at compile time. Prebuilt BlockCtx iteration is also allocation-free,
including use of pre-reserved BlockScratch during chunk and tile iteration.
Scratch allocation can occur during reserve_bytes, but not during
allocate<T> when capacity is sufficient. Chunk-local tile iteration does not
materialize ranges or decode global TileKey values.
Boundary and local-candidate helpers only describe the current chunk. They do
not define movement legality, neighbor ordering, direction enums, halo loading,
transition providers, or cross-chunk field access. Future topology and path
systems can use signed local candidates plus contains_local to decide whether
a candidate remains inside the chunk or needs an explicit transition.
TDD Divergences
The historical block-kernel pipeline TDD describes a richer staged executor. This first M3 slice intentionally diverges:
- No planner, phase graph, barrier model, rich diagnostics reporting, planner-owned scratch arenas, worker pools, or external scheduler backend is implemented yet.
BlockCtxis only the current serial context. It does not yet provide scheduling, phase graphs, or planner state. Its scratch and diagnostics pointers are caller-owned and optional.- Only
ReadOnlyis enforced today, and only through policy-typed block contexts andfor_each_chunk<Policy>.UniquePerTile,UniquePerChunk, andUnsafestill record intended write discipline without ownership checks in raw block iteration. Queued-operation phase planning adds the first conservative parallel ownership check for plannedUniquePerChunkwork. - Execution remains serial only for both chunk and tile iteration; parallel chunk scheduling is deferred.
- Domains are chunk-key spans over always-resident storage only; sparse residency, tile subranges, and dynamic residency transitions are not covered.
- Field access stays on
ChunkPagespans instead of introducing kernel parameter binding or generated accessors.
These divergences keep the implemented API small while preserving a route to the larger TDD pipeline once domain semantics and benchmark baselines settle.