Intentional Leakage and Permeability — ADR-091
Intentional Leakage and Permeability — ADR-091
Date: 2026-08-02 · Written in English per the 2026-08-01 convention change.
Provenance: an Epic review, not an audit round. This came out of the review of
epic_presentation_smooth_fog.md, where moving audio occlusion to the server (Section D) exposed a mechanic the architecture had no vocabulary for: a closed door is an unrevealed room, but a player can put an ear against it. The corpus had a binary occluder and a rule that visibility is server-owned, and nothing in between.This file continues the
R-numbered sequence for filing purposes only. As with R6, the title names where the ruling came from rather than implying a seventh review round.
ADR-091 — Intentional Leakage Is a Server-Side Mechanic, and Permeability Is Fixed-Point, Quantised and Rate-Bounded
Status: Accepted · Date: 2026-08-02 · Amends: 034, 038 · Depends-on: 017 (fixed-point determinism contract), 035 (explored-chunk persistence model) · interacts with 073
Context
Table-top play depends on information permeability. A closed door conceals a room, but an investigator listens at it; light shows under it; a growl carries through a thin wall. These are not leaks in the security sense — they are the mechanic, and a system that cannot express them cannot run the game.
The architecture had no vocabulary for this. OccluderSet is binary: a segment
blocks or it does not. ADR-034 rules that visibility has exactly one producer and it
is on the server. Between “fully concealed” and “fully visible” there was nothing.
The obvious implementation is the alternative ADR-034 already rejected. Send the sound to the client with the monster’s position and let the browser apply a low-pass filter. ADR-034 rejected client-side culling of hidden occluders because “a culling shader is one line away from being disabled”; a low-pass filter is one line away from being bypassed. It is also strictly worse than the channel ADR-034 closed: the visibility side channel was an inference from correction timing, whereas here the monster’s position is sitting in client memory as a plain value. That is direct disclosure wearing the costume of a presentation effect.
A second proposal arrived alongside it — that every entity including AI NPCs holds
its own Explored and Visible map, so NPC behaviour is not omniscient. The
motivation is correct and the mechanism collides with measured arithmetic: the
registry’s reference room fits inside Q-015a only because mask sets are per
party rather than per viewer, and “fifty independent masks were never affordable at
any tier.”
Decision
-
Audibility and leaked illumination are server-computed authoritative results, never client filters. The server decides whether a sound is audible and at what attenuation, and whether light leaks and how much. It dispatches already attenuated parameters positioned at the aperture — the door, the crack, the thin wall — or it dispatches nothing. The client renders them blindly and is never told the true source. This extends ADR-034 clause 1 from visibility to audibility and to leaked illumination: the producer is the server for all three.
-
Permeability is two independent fixed-point parameters on the occluder. Never one, and never floating point. Acoustic and optic permeability are separate because materials are: a curtain stops light and not sound, a glass window the reverse. Both are
Fx32. ADR-017’s determinism contract is not relaxed — a float attenuation is bit-exact on neither host and takes the parity corpus with it. -
Leaked values are quantised, and the quantisation is the channel bound. Attenuation and leaked intensity are emitted as one of
Q-072/Q-073ordinal levels, never as a continuous value. A continuous attenuation encodes distance-behind-the-aperture to whatever precision the number carries, and a player who can read it can triangulate. Bucketing makes the recoverable information a stated number instead of an artefact of the float width. -
The event rate is bounded, because frequency is a channel that amplitude bounding does not cover (
Q-074). A growl emitted on every movement reports activity even when every individual event is maximally muffled. The channel’s capacity is the productlog₂(levels) × rate, not either factor, soQ-072/Q-073andQ-074may not be decided independently — the same couplingQ-051/Q-052have, for the same reason. -
Permeability’s disclosure asymmetry is a lower bound only. This is the third asymmetry. From a
Disclosure::Partialset, accumulated attenuation may soundly conclude≥ Xand never≤ X: an undisclosed occluder can only add attenuation, never remove it. The fail-safe direction is therefore muffle more, or emit nothing.OccluderSetreturns a lower bound orIncompletefrom a partial set and never an exact value — the same shape assegment_visibilityreturningOk(Clear)only from aCompleteset.Sound from a PartialsetNever sound Visibility BlockedClearPathfinding UnreachableFoundPermeability attenuation ≥ Xattenuation ≤ X -
Per-entity LOS, yes. Per-entity FOW mask, only where a client consumes one. An NPC needs immediate behavioural context — can I see the investigator now — which is LOS rays at 0.4 µs, not an explored map at 29.37 ms per advance. That is a ~73,000× difference, and it is the whole ruling. Twenty NPCs holding mask sets at even
Q-071’s Dormant tier costs 294 ms, the entirety ofQ-015a, before a single player mask exists; and because explored chunks are persisted and branch-keyed (ADR-035), per-NPC explored state multiplies FOW storage by the NPC count as well. A mask set exists for a party that receives one. An NPC gets rays and a last-known-position scalar. -
The advance cost must be re-measured before permeability ships (
Q-075). Today a ray terminates on its first blocking hit; accumulating attenuation means continuing through every permeable occluder on the path.Q-015a’s entire derivation — 300 ms ÷ 29.37 ms = 10 advances per room-second — andQ-068…Q-071stacked on top of it, rest on that 29.37 ms. The ladder is re-derived from the new measurement rather than assumed to have survived it. -
A leaked value is occluder-derived by construction, so it needs a reviewed exemption rather than a silent pass. ADR-038 fails the build on any ephemeral payload field whose provenance resolves to an occluder or visibility computation, and a pre-attenuated audio event is definitionally that. ADR-038 gains one exemption category: a field may be occluder-derived if it is server-computed, quantised under clause 3, rate-bounded under clause 4, and recorded in the schema registry naming this ADR. The exemption is per field and reviewed. There is no blanket “leakage” class, because a blanket class is how the next occluder-derived field gets in without anyone looking at it.
Alternatives Considered and Why Rejected
- Send raw events and muffle client-side. Rejected — it is ADR-034’s already rejected alternative in a different sense, and worse: the concealed datum is in client memory as a plain value rather than inferable from timing. “A culling shader is one line away from being disabled” applies unchanged to a filter node.
- Continuous attenuation, trusting the client not to read it. Rejected — the value is the channel, and precision is capacity. This is not a case where a bound can be added later: clients that shipped reading a continuous field will still read it.
- Per-entity masks for everything, with smaller chunks to pay for it. Rejected —
Q-015acounts advances, not cells. Halving the chunk resolution does not reduce the number of mask sets, which is the term that actually grows. - A single permeability scalar. Rejected — it forces curtains and glass to be the same material, and that is discovered by a level designer mid-campaign rather than by a test.
- Derive leakage on the client from disclosed geometry only. Rejected — leakage is interesting precisely where the source is undisclosed, so the client would need the thing being concealed in order to compute what escapes it.
Consequences (including negative)
- Negative, and the largest:
Q-015a’s derivation is re-opened. Clause 7 is not bookkeeping. Shipping permeability without re-measuring would leave the token bucket admitting advances against a per-advance cost that no longer holds, and the failure would appear as tick overruns in rooms with heavy permeable geometry — far from its cause. - Negative: quantisation is player-visible. A muffled growl steps between levels
rather than sliding, and a sound designer will ask for more levels.
Q-072is a security bound wearing an aesthetic hat, and the pressure to raise it will not arrive labelled as a security change. Recorded here so that the next person to weigh it knows what they are weighing. - Negative: audibility becomes ruleset-dependent. The crate computes attenuation;
the cartridge decides perception (a CoC 7e
Listenroll). This keeps the crate rules-agnostic as ADR-017 requires, but it creates a second place where “may this player know this” is answered, and the two must not drift. - Negative: three asymmetries instead of two, and the third is the least intuitive. “More muffled is the safe answer” reads backwards to anyone who thinks of attenuation as information loss rather than as a bound.
- Positive: the mechanic table-top play actually needs becomes expressible with no client-side filter anywhere in it — which is what makes it survivable when a client is hostile.
Enforcement
Descent.DisclosureTestsgains a third class asserting aPartialoccluder set never returns an exact attenuation, alongside the visibility and pathfinding asymmetries it already holds.- The parity corpus’s adversarial case class extends to permeable occluders: viewer pose identical, occluder sets differing by one undisclosed permeable occluder, asserting the client build refuses rather than answering differently.
- ADR-038’s schema registry: every leakage field names its clause-8 exemption and this ADR, and a leakage field without one fails the build.
- A benchmark produces
Q-075, and theQ-068…Q-071ladder is re-derived from it before permeability ships. Until then those four remain derived from the impermeable 29.37 ms and say so. - Registry rule 4:
Q-072…Q-075are registered before any of them is written into the whitepaper body.
§11 Index Line
ADR-091: Intentional leakage is a server-side mechanic — audibility and leaked illumination are server-computed and dispatched pre-attenuated at the aperture, never filtered client-side; permeability is two fixed-point occluder parameters whose partial-set conclusion is a lower bound only (the third disclosure asymmetry); leaked values are quantised and rate-bounded and their product is the channel capacity; NPCs get LOS rays and a last-known-position scalar, never mask sets. (Amends ADR-034, ADR-038.)