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, mask)andactive_chunk_domain(world, mask)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::reserve_bytes_checked(bytes)returnsReserveStatusand leaves the object unchanged when rounded capacity would overflow. The existingreserve_bytes(bytes)retainsstd::bad_allocin enabled builds and fails fast for that deterministic error when exceptions are disabled.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.block_tiles(ctx)andblock_chunks(ctx)begin block-preserving lazy pipelines.filter,map, andflat_mapcompose at compile time; no intermediate collection is created.block_tilesemitsBlockTilevalues with the resolved chunk view, local id and coordinate, and world coordinate. A pipeline owns its cheapBlockCtxvalue, so a temporaryblock_ctx(...)is safe. The context still borrows the world, domain-key storage, scratch, and diagnostics, which must outlive the terminal.pipeline_from(span)applies the same lazy adapters to caller-owned sequences and frontiers.flat_mappreserves whether its mapper returned a range by reference and extends a returned temporary through the nested iteration. Mutating a referenced range through the terminal therefore affects the caller-owned range rather than an implicit copy.Pipeline::for_eachandPipeline::reduceare fused terminals.collect_intouses caller-owned bounded storage and reports both written and required counts throughPipelineCollectResult.to_sequence_allocatingis the deliberately explicit allocating terminal.PipelineDiagnosticsrecords blocks and items read, items filtered and emitted, explicit materializations, and bounded-capacity failures. It is optional and caller-owned.
Iteration is deterministic when domains are produced by the provided builders:
each sorts by ascending ChunkKey. That matters on sparse worlds, where the
underlying scans enumerate in residency order — a function of load and
eviction history rather than of world content — so a domain built from a raw
scan is not reproducible across runs and a non-commutative kernel would not
be either. The builders already allocate a vector and absorb the sort; the
scans stay unordered. dirty_chunks()/active_chunks() return a newly
allocated vector, so only the caller-owned
collect_dirty_chunks()/collect_active_chunks() avoid allocating, and
only when the output vector already has capacity.
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. Topology and path systems
use signed local candidates plus contains_local to decide whether a candidate
remains inside the chunk or needs an explicit transition.
Remaining TDD Differences
The historical block-kernel pipeline TDD describes a richer staged executor. The raw block layer intentionally remains smaller than the queued execution layer built above it:
BlockCtxis a serial resolved-chunk context. Planning, phase grouping, and worker-pool dispatch live in queued operations and simulation rather than in the raw view.- Scratch and diagnostic pointers are caller-owned and optional. The raw layer does not provide planner-owned arenas or cross-thread diagnostic reduction.
- Only
ReadOnlyis enforced, 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. - Direct block iteration remains serial. Planned
UniquePerChunkoperations can run through the production worker-pool phase executor. - 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.
Sparse block domains and tile subranges are not part of this layer. The shipped pipeline is deliberately an inlined serial composition layer over resolved block sources; worker ownership and phase scheduling remain in queued operations.