Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Design: mesh visibility

Provisional answer to #772: freeze section-wide and per-cell face decisions before worker dispatch, expose raw immutable section input, and invalidate by captured revisions and epochs. Ari's side posts the decision on the issue; this note implements no Rust API. It extends the section meshing plan for #1508 and shares its input, budgets and publication boundary with #864, rather than introducing another meshing context or worker event bus.

Vanilla and NeoForge 26.3

Verified vanilla using python3 tools/rea_vanilla.py method net.minecraft.client.renderer.chunk.SectionCompiler compile and callers for that method from /workspace/horizon. Compile visits 4096 cells, marks solid cells in VisGraph, collects block entities, emits fluids separately from block models, sorts translucent output and resolves section connectivity. The sole reported caller is SectionRenderDispatcher.RenderSection.CompileTask.doTask. These are separate geometry and connectivity decisions; hiding geometry must also remove its occlusion contribution to avoid invisible walls hiding terrain.

The issue explicitly identifies no NeoForge counterpart. Exact 26.3 absence cannot be independently proved from the available public references. The public model documentation is labelled 26.1 and describes directional culling and model data, not this contract. The attempted 26.3 event Javadoc was inaccessible; exact 26.3 behavior remains unverified. No NeoForge source, patches or other project's source informed this proposal.

Proposed contract

Keep the existing state visibility registration as the shared default recipe; add section and face inputs at the same preparation moment, with one canonical visibility plan. Final public naming awaits approval; no parallel legacy hook or compatibility alias. Resolve subscriptions to dense state slots at load. Client-lib captures input, game dispatches with ClientHost on the client world-owning thread, then validates and applies in one transaction. Refuse changed preconditions and reschedule; never recompute inside apply. Workers consume concrete frozen arrays only, with no new mod dispatch, live Host or world access. #1508's explicitly declared worker-safe geometry/tint capability is separate and does not authorize visibility callbacks on workers.

A section decision is Keep or HideTerrain. HideTerrain emits zero block-model, fluid and #864 terrain-contribution quads and contributes no occlusion. It does not suppress independently drawn block entities, entities or collision. Keep permits per-position decisions: block-model visibility, fluid visibility and a six-bit directional face mask, ordered down, up, north, south, west, east. Default mask is 63. Suppression composes by intersection in dependency order; no listener can restore another listener's hidden geometry. Section suppression wins. Unculled decorative quads follow the position's model visibility, not an invented directional classification. #864 unassociated section contributions follow section visibility; position-associated quads follow their position and declared cull face. Replacement and append retain #1508's conflict rules.

Face bits filter eligible geometry after normal culling; they do not force normally occluded faces to appear. A wholly hidden cell is nonoccluding, as in the existing hidden-state behavior. A partially masked cell retains full-face occlusion only on retained, explicitly full occluding faces. Recompute section connectivity conservatively from effective occlusion; uncertainty must allow visibility rather than incorrectly cull terrain. Translucent faces remain in the translucent sorting path and never become opaque occluders. Fluid masks filter emitted fluid directions without changing the captured fluid simulation.

Raw means API-owned state ids and immutable captured values before visibility substitution, not palette internals, mutable chunks, pointers or GPU buffers. Reuse #1508's 18 cubed snapshot: 5832 cells including edges and corners, with light, biome, availability, section coordinates and deterministic seeds. Reads are bounded to local [-1,16] on each axis; outside reads refuse. Missing is distinct from air but acts nonoccluding/nonmatching for decisions; outside world height is known air. Hidden ore remains present in raw input. No access to unloaded terrain, block-entity mutable state or larger-radius live queries.

Invalidation and verified owners

Inspected at base 8b736c3cbc; paths relative to crates/. Existing owners below are integration points, not claims that the proposed epochs already exist.

RouteVerified file:lineRequired behavior
Initial mesh and remeshhorizon-client-render/src/streaming.rs:2237, :1142Prepare before scheduling, use frozen input in worker
State write and neighbour bordershorizon-client-render/src/world.rs:385Dirty all radius-one dependent sections, including diagonals and vertical borders
Column replacement, biome/light changeshorizon-client-render/src/streaming.rs:3455, :3476Invalidate halo dependencies even if centre state is unchanged
Visibility togglehorizon-client/src/game/modding/native_render_visibility.rs:13Explicit refresh increments policy epoch and remeshes affected loaded sections; conservative all-loaded refresh initially
Resource reloadhorizon-client-render/src/streaming.rs:1277, :1283Replace context, rebuild plans and sections
Renderer recovery and pending uploadshorizon-client/src/game.rs:14166, :14178Clear pending old uploads and use shared reload invalidation
Unloadhorizon-client-render/src/streaming.rs:516, :1394Cancel queued jobs, invalidate lifetime, remove terrain, dirty surviving halo dependents
Stale workershorizon-client-render/src/streaming.rs:1887Extend sequence/column checks to all captured generations and recycle refused output
Existing mask consumptionhorizon-client-render/src/mesher.rs:1766Preserve built-in behavior and raw input separately

Chunk arrival also invalidates surviving sections that captured Missing; use the streaming column installation boundary shared with replacement. Every plan, job and pending upload carries section lifetime/revision, halo revisions, resource, mod and visibility-policy epochs (and #1508 tint generation). Check again immediately before upload. Unload/reload at identical coordinates has a new lifetime; cancellation alone is insufficient. Old resources stay leased until old jobs finish, but old results publish zero uploads. Arbitrary mod state changes require explicit visibility refresh; no per-tick polling.

Trust boundary, targets and cost

Native and wasm receive the same generated bounded Wire snapshot and return identical integer masks; no borrowed game representation crosses either ABI. Reject invalid tags, unknown/stale ids, duplicate coordinates, masks above 63, and positions outside [0,15]. No floating-point visibility values are accepted. Use at most 4096 position records and 64 KiB encoded visibility output per section, checked before allocation; charge decoded storage against #1508's 32 MiB section and 128 MiB in-flight ceilings. Admission waits at the global limit. Snapshot sharing counts storage once, leases remain charged until drop.

A bad result refuses that registration's entire section answer atomically. Native unwind panics and wasm traps/fuel exhaustion disable the registration for the generation, emit one diagnostic and invalidate dependent plans; rebuild using valid defaults and remaining listeners. No partially applied masks. Native abort cannot be recovered; native callbacks require the supported unwind boundary and cannot be forcibly preempted. Wasm gets a finite batch fuel budget. Teardown invalidates mod generation and drains leases before unloading code.

No subscriptions selects the existing mesher once at dispatch: zero extension snapshot allocations, callbacks, listener scans or new per-face dynamic dispatch. Subscribed decisions are batched once per section; inner loops read data only. Benchmark acceptance targets less than 1% no-mod throughput regression; no performance measurement is claimed by this documentation change.

Acceptance matrix

Future implementation tests, not results of this note:

InputExpected value
Isolated opaque cube, Keep, mask 636 model quads
Same cube, mask 61 (up removed)5 model quads
Two adjacent opaque cubes, default10 exterior quads
One cube wholly hidden beside another0 hidden quads, 6 remaining cube quads
HideTerrain with fluid and one #864 quad0 terrain quads; independent block-entity draw retained
Hidden state in raw snapshotOriginal state id unchanged
Border neighbour Missing, arrives, unloadsExposed cube faces 6, then 5, then 6
Write at local (15,15,15), neighbours loadedExactly 8 radius-one sections dirtied
Resource A completes after B; unload/reload same coordinate0 stale uploads in both cases
Two translucent quads at distances 2 and 52 translucent quads, distance 5 sorted first
Mask 64, duplicate position, 4097 records, panic or trapWhole faulty answer refused; default cube 6 faces
Native and wasm same snapshot/answersIdentical masks, occlusion and face counts
No subscribersBaseline mesh, 0 extension allocations and callbacks

Proposed stages

File none: do not file implementation issues until Ari posts the decision. First approve granularity, raw input and occlusion semantics. Then implement shared snapshot/epoch infrastructure with #1508, followed by generated native and wasm preparation adapters together. Finally integrate #864 contribution masking and exercise the route matrix, counters and throughput benchmark.

Open questions

  • Does section hiding include independently drawn block entities? Recommend no: preserve R2 lifecycle ownership and make a separate explicit lifecycle choice.
  • Should masks force faces visible? Recommend suppression only; whole-cell hiding already exposes neighbours and avoids contradictory occlusion declarations.
  • Can mods read beyond one block of halo? Recommend no, matching #1508; a future radius extension requires explicit dependency and admission budgets.
  • Are the proposed output limits sufficient? Recommend 4096 records/64 KiB as initial ceilings and representative native/wasm measurements before raising them.
  • What exact public name and refresh receiver should ship? Recommend one section visibility preparation contract and a natural client-world handle refresh, finalized with #1508 rather than a broad new Host service.