[MG_Pipe, MG_Impl, MG_Test, docs] (P7): D-K2's dependency rule in one table, with the case that fails when the two readers disagree

P3b/P4b R-5 (and R-3's cheap half), landing under the P7 program (PLAN-PH-P34B-P7.md
1.2, wave 2-D package D2).

The rule had SIX statements and nothing compared them, and two of the four prose ones
were wrong in the same way: MG_Pipe/MGPipe.h and MG_Backend/DirectGLES/Managers.h both
said "THREE OF THEM HAVE A DEPENDENCY" (there are six rows) and both said "the mirror
pairs (10 without 11, ...) are all fine" - when 10-without-11 is precisely D-K2's FOURTH
row (ID-14/ID-15), refused by the server and withheld by the client, with PipeFill.cpp
saying so out loud: "The brief's original 'bit 10 without 11 is fine' is WITHDRAWN for
P4a as built." Neither wrong comment could fail anything; Managers.h's copy sits in the
header of the file that implements the refusal.

The rows, once, in MG_Pipe/SubsystemDeps.def (X-macro, FatalFamilies.def's convention),
expanded by MGPipe.h into kMGPipeSubsystemDependencies[] / MGPipeSubsystemRequires() /
MGPipeSubsystemDependenciesAreSet() with four static_asserts. Six rows: 8->7, 9->10,
10->{7,11}, 11->10, 12->none (a ROW, not an absence), 13->7. Bits 8 and 13 are the two
the old comment did not know about; bit 13's row lived fifteen lines further down in the
same header, which is how "THREE OF THEM" survived two phases.

THE TWO READERS DO NOT YET READ IT, deliberately: MG_Impl/Pipe/PipeFill.cpp is the
contract package's file for the whole phase (TextureEmit.h states that rule) and
MG_Backend/DirectGLES/Managers.cpp belongs to wave 2-D package D1 while it is in flight.
Switching them is two one-line changes for the integrator. The anti-drift property does
not wait: MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp is the first thing in the tree
that calls BOTH readers at the same mask - sweeping the phase constants, the two
documented refusal lanes and one mask per row - and compares each against the .def. It
lives in that binary because that binary links both sides.

Both comparisons are one-directional, each for a measured reason. The server resolvers
also fold in the family's own bit and D-C3's wire-caps refusal, so a resolver may say NO
for its own reasons but never YES where the table refuses. MGPipeP4aFamilyEmits is a
QUADRUPLE and the first version of this case asserted equality and reddened at 0x1fff on
the sampler and program families because a bare unit binary registers no consumer - the
assertion being wrong, not the product, and recorded in the note because the temptation
to assert equality here is strong.

Red-once (R-16), twice: dropping the fourth row from the .def fails at COMPILE time
(MGPipe.h's static_asserts); making the server's ResolveSamplerSubsystemArm stop checking
bit 10 (throwaway, reverted) reds the runtime case and names the mask - 0x9ff, which is
exactly the lane CMakeLists.txt already pins for G12.

R-3's cheap half: CONTRACT-P5.md claimed the image-access, sampler-view-target and
draw-buffer encodings as contract by pointing at package-header LINE RANGES, and all
three had drifted (ImageEmit.h:146-159 no longer holds the encoding at all). All three
rows now state the encoding by enumerator and function name, and the two confusable
siblings are named. The remaining half - MGPSamplerView::Target still has no Count bound,
no ...IsValid and no Fatal for an out-of-range value - is stated in the row rather than
closed, being an MG_Pipe change outside this package's footprint.

Managers.h:690-695's copy of the wrong prose is NOT corrected here: it is package D1's
file, and its two defects are named in notes/p34b/espryt-d2.md so the integrator can take
them in one line.

G1 after this change: pull .text 10822147 = 0xa52203, nm --defined-only identical to
~/w7/p7-before/pull-syms.txt (30570 symbols) - the new inline constexpr table does not
reach the pull build.
This commit is contained in:
2026-09-22 17:31:51 -04:00
parent 779ff21b3e
commit 519445f946
8 changed files with 568 additions and 22 deletions
+9 -3
View File
@@ -61,9 +61,15 @@ namespace MobileGL::MG_Pipe {
// 47 DirectGLES cases at ID-41). With them the four families emit NOTHING in that state and
// the legacy pull path runs exactly as it does on a pull build.
//
// D-K2's TABLE IS IN PipeFill.cpp, ONCE: bit 9 requires bit 10, bit 10 requires bits 7 and
// 11, bit 11 requires bit 10, bit 12 depends on nothing - the client mirror, bit for bit, of
// the four `Resolve<Family>SubsystemArm()` refusals in DirectGLES/Managers.cpp.
// D-K2's ROWS ARE IN MG_Pipe/SubsystemDeps.def, ONCE (P3b/P4b R-5), and this comment no
// longer restates them - restating them here is half of how the rule ended up with six
// statements, two of which were wrong. PipeFill.cpp's kMGPipeP4aFamilyDependencies is still
// the client's own copy and is still the bit-for-bit mirror of the four
// `Resolve<Family>SubsystemArm()` refusals in DirectGLES/Managers.cpp; switching it to read
// the .def is the integrator's one-line change (PipeFill.cpp is the contract package's file
// for the whole phase, so the emitter packages do not edit it).
// MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp drives BOTH readers at every interesting
// mask and compares them against the .def, so the two cannot drift in the meantime.
//
// It is exported for the unit gate and for no other caller: the gate itself is
// FamilyIsLive() inside PipeFill.cpp, every birth hook and every `wants()` row resolves
+4 -3
View File
@@ -100,9 +100,10 @@ namespace MobileGL::MG_Pipe {
// THE FLIP IS THE WHOLE SWITCH AND NOTHING ELSE MOVES: PipeFill.cpp ORs this constant into
// kMGPipeWiredSubsystems, static_asserts it is 0-or-its-own-bit, gates every birth hook on
// FamilyIsLive(kMGPipeSubsystemTextureResources, this), and gates DrainTextureSubData on
// the same OR. Bit 9 (framebuffer) REQUIRES bit 10 (D-K2), because every MGPSurface::Res
// names a texture or renderbuffer handle the applier must hold a record for - so this
// package may never be integrated with only one of the two constants set.
// the same OR. D-K2's rows - including bit 9's dependency on this family - are in
// MG_Pipe/SubsystemDeps.def, once (P3b/P4b R-5), and are not restated here; what this
// comment still has to say is the consequence for INTEGRATION ORDER, which no table can
// carry: this package may never be integrated with only one of the two constants set.
inline constexpr Uint64 kMGPipeWiredTextureSubsystem = kMGPipeSubsystemTextureResources;
static_assert(kMGPipeWiredTextureSubsystem == 0 ||
kMGPipeWiredTextureSubsystem == kMGPipeSubsystemTextureResources,
+79 -13
View File
@@ -112,17 +112,30 @@ namespace MobileGL::MG_Pipe {
// path that regressed are four different findings, and clearing one must not disarm the
// other three.
//
// THREE OF THEM HAVE A DEPENDENCY and it is diagnosed at the first use, never half-run -
// one Resolve<Family>SubsystemArm per family beside the backend's existing
// ResolveResourceSubsystemArm, modelled on the bit-8-requires-bit-7 refusal it already
// ships, and lazy rather than at bring-up because a pre-flight child dying on a signal
// makes a whole lane SKIP green: bit 11 requires bit 10 because
// every MGPBoundView::Texture and MGPImageView::Res names a Texture handle and only bit 10
// puts one in the slot table; bit 9 requires bit 10 because MGPSurface::Res does; and bit
// 10 requires bit 7 because a buffer texture's BufferForTexBuffer names a Buffer handle.
// The mirror pairs (10 without 11, 10 without 9, 7 without 10) are all fine, and are
// stated as such because an unreachable branch that says something different is how the
// reachable one drifts. Bit 12 depends on nothing.
// FOUR OF THE SIX FAMILIES CARRY A DEPENDENCY and it is diagnosed at the first use, never
// half-run - one Resolve<Family>SubsystemArm per family beside the backend's existing
// ResolveResourceSubsystemArm, and lazy rather than at bring-up because a pre-flight child
// dying on a signal makes a whole lane SKIP green.
//
// THE ROWS ARE IN SubsystemDeps.def, ONCE (P3b/P4b R-5), and this comment no longer
// restates them. It used to, and it was WRONG in two ways that nothing could fail: it said
// "THREE OF THEM" when there are six rows, and it said "the mirror pairs (10 without 11,
// 10 without 9, 7 without 10) are all fine" when 10-without-11 is precisely D-K2's FOURTH
// row (ID-14/ID-15) - refused by the server and withheld by the client, with PipeFill.cpp
// saying so out loud: "The brief's original 'bit 10 without 11 is fine' is WITHDRAWN for
// P4a as built." The audit found the same pair of defects repeated in
// MG_Backend/DirectGLES/Managers.h's header, i.e. in the header of the file that
// implements the refusal.
//
// THE MIRROR PAIRS THAT REALLY ARE FINE, said out loud rather than left as an absence,
// because a table is only trustworthy if what it does NOT contain was decided:
// - bit 10 set, bit 9 clear: FINE. The legacy FBO sync reaches the texture twin through
// SyncTextureObjectToBackend, which dispatches to the handle arm by itself.
// - bit 11 set, bit 9 clear: FINE, for the same reason - a sampler view names a texture,
// never a framebuffer.
// - bit 7 set, bit 10 clear: FINE, and it is P3a's shipped configuration.
// - bit 12 set with any or none of 9/10/11: FINE, per its own row.
// - bit 10 set, bit 11 clear and its mirror are NOT fine; that is the fourth row.
inline constexpr Uint64 kMGPipeSubsystemFramebuffer = 1ull << 9; // set_framebuffer_state
inline constexpr Uint64 kMGPipeSubsystemTextureResources = 1ull << 10; // texture + renderbuffer
// resource_*, set_texture_params
@@ -137,8 +150,9 @@ namespace MobileGL::MG_Pipe {
// path that regressed and a buffer path that regressed are different findings, and
// clearing one must not disarm the other.
//
// BIT 13 REQUIRES BIT 7, and for bit 11's reason: every MGPBufferRange::Res names a Buffer
// handle, and only bit 7 puts one in the slot table.
// Its dependency row is in SubsystemDeps.def with the other five (P3b/P4b R-5). It used to
// be stated here instead, fifteen lines away from the block that stated the other three -
// which is how "THREE OF THEM" above stayed wrong through two phases.
inline constexpr Uint64 kMGPipeSubsystemBufferBindings = 1ull << 13;
// bits 14..62 reserved for the later phases, allocated in ROADMAP order.
// NOT a subsystem, a BEHAVIOUR: turn OFF client-side content addressing of CSOs, so
@@ -166,6 +180,58 @@ namespace MobileGL::MG_Pipe {
(kMGPipeSubsystemsMigratedAtP4a | kMGPipeSubsystemBufferBindings),
"the P5e phase constant and P5e's subsystem bit have drifted");
// ---- D-K2's dependency rule, in ONE place (P3b/P4b R-5) --------------------------------
//
// The rows, their reasons and the argument for a table rather than six comments are in
// SubsystemDeps.def. This is the expansion both readers are meant to end up on; the
// anti-drift property is already live through
// MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp, which drives the client check and the
// server gate at every interesting mask and compares both against these rows.
#include "SubsystemDeps.def"
struct MGPipeSubsystemDependencyRow {
Uint64 Family; // exactly one kMGPipeSubsystem* bit
Uint64 Requires; // the bits MOBILEGL_PIPE_PUSH must ALSO carry; 0 is a row, not a gap
const char* Why;
};
#define MGL_SUBSYSTEM_DEPENDENCY_ROW(family, requires_, why) {family, requires_, why},
inline constexpr MGPipeSubsystemDependencyRow kMGPipeSubsystemDependencies[] = {
MGL_SUBSYSTEM_DEPENDENCY_LIST(MGL_SUBSYSTEM_DEPENDENCY_ROW)};
#undef MGL_SUBSYSTEM_DEPENDENCY_ROW
// The bits `subsystem` additionally needs, or 0 for a family with no row. A family with no
// row and a family whose row says 0 are the same answer ON PURPOSE: both mean "nothing else
// is required", and the table covers its families exhaustively so the two cannot be
// confused for "I forgot to look".
inline constexpr Uint64 MGPipeSubsystemRequires(Uint64 subsystem) {
Uint64 required = 0;
for (const MGPipeSubsystemDependencyRow& row : kMGPipeSubsystemDependencies) {
if ((subsystem & row.Family) != 0) required |= row.Requires;
}
return required;
}
// NOT TRANSITIVE, and that is load-bearing: each reader tests the required BITS against
// MOBILEGL_PIPE_PUSH alone and never the other family's liveness, because Espryt's
// resolvers classify their arms from that mask alone (PipeFill.cpp's own argument;
// TextureEmitTest.cpp pins it). A transitive closure here would make the two sides disagree
// at some mask - the failure this table exists to prevent.
inline constexpr Bool MGPipeSubsystemDependenciesAreSet(Uint64 subsystem, Uint64 pushMask) {
const Uint64 required = MGPipeSubsystemRequires(subsystem);
return (pushMask & required) == required;
}
static_assert(MGPipeSubsystemRequires(kMGPipeSubsystemTextureResources) ==
(kMGPipeSubsystemResources | kMGPipeSubsystemSamplers),
"D-K2's fourth row (bit 10 requires bit 11) has gone missing again");
static_assert(MGPipeSubsystemRequires(kMGPipeSubsystemPrograms) == 0,
"bit 12 depends on nothing, and that is a row rather than an absence");
static_assert(!MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemTextureResources, 0x7ffull),
"0x7ff is the mask whose texture family Espryt refuses; the table must agree");
static_assert(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemTextureResources, 0x1fffull),
"0x1fff is P4a's own arm and every row of it must be satisfied");
// The catalogue itself. Only macros, so it is safe to expand inside the namespace, and
// consumers (the unit test, later the transport) get MGP_CALL_LIST from this header.
#include "PipeCalls.def"
+73
View File
@@ -0,0 +1,73 @@
// MobileGL - MobileGL/MG_Pipe/SubsystemDeps.def
// Copyright (c) 2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
//
// D-K2's SUBSYSTEM DEPENDENCY RULE, IN ONE PLACE (P3b/P4b R-5).
//
// WHAT WAS WRONG. The rule "bit X requires bit Y" was stated in SIX places, and the audit found
// two of them WRONG in the same direction:
//
// * MG_Pipe/MGPipe.h's comment said "THREE OF THEM HAVE A DEPENDENCY" (there are six rows) and
// "The mirror pairs (10 without 11, 10 without 9, 7 without 10) are all fine" - but 10
// without 11 is exactly D-K2's FOURTH row, refused by the server at
// Managers.cpp ResolveTextureResourceSubsystemArm and withheld by the client at
// PipeFill.cpp's table. PipeFill.cpp says so out loud: "The brief's original
// 'bit 10 without 11 is fine' is WITHDRAWN for P4a as built.";
// * MG_Backend/DirectGLES/Managers.h's header comment repeats both defects, in the header of
// the file that IMPLEMENTS the refusal.
//
// Neither wrong comment could fail anything, which is the whole argument for a table: the two
// EXECUTABLE statements of the rule are in different link units with no shared header, and the
// four PROSE statements drifted because nothing compared them to anything.
//
// WHAT THIS FILE IS. The rule, once, as rows. It is a preprocessor-only table like
// MG_Remote/FatalFamilies.def - no generator reads it - and it carries pure bit constants, so
// including it from MGPipe.h keeps check_include_closure.py's value-header rule satisfied.
//
// WHO READS IT. MGPipe.h turns it into kMGPipeSubsystemDependencies[] and
// MGPipeSubsystemRequires(). The two executable statements of the rule are:
//
// CLIENT dependency check MG_Impl/Pipe/PipeFill.cpp's kMGPipeP4aFamilyDependencies /
// P4aFamilyDependenciesAreSet (bits 9-12), plus P5eFamilyIsLive's
// hand-coded bit-13 row.
// SERVER consumer gate MG_Backend/DirectGLES/Managers.cpp's four
// Resolve<Family>SubsystemArm() over PipeSubsystemDependencyMissing
// (bits 9-12), plus ResolveVertexInputSubsystemArm (bit 8) and
// DirectGLES.cpp's ResolveBufferBindingSubsystemArm (bit 13).
//
// THEY ARE NOT SWITCHED OVER IN THIS COMMIT, deliberately: PipeFill.cpp is the contract
// package's file for the whole phase (TextureEmit.h states that rule) and Managers.cpp belongs
// to wave 2-D package D1 while it is in flight. The anti-drift property does not wait for that,
// because MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp drives BOTH readers at every
// interesting mask and compares them against these rows - a change to either side alone reds.
// Switching the two readers to read this table is two one-line changes for the integrator.
//
// THE ROWS ARE NOT TRANSITIVE, and that is load-bearing rather than an oversight: each reader
// tests the required BITS against MOBILEGL_PIPE_PUSH alone, never the other family's liveness,
// because Espryt's resolvers classify their arms from that mask alone. Making the rows
// transitive here would make the two sides disagree at some mask, which is the failure the
// table exists to prevent. PipeFill.cpp:1576-1581 argues this at length and
// TextureEmitTest.cpp:1278-1283 pins it.
//
// THE MIRROR PAIRS THAT STAY FINE are stated in MGPipe.h beside the table rather than here,
// because an absence is not a row.
// clang-format off
// X(Family, Requires, Why)
// Family - exactly one kMGPipeSubsystem* bit
// Requires - the bits MOBILEGL_PIPE_PUSH must ALSO carry for that family to be live; 0 is a
// ROW and not an absence, so the table covers its families exhaustively
#define MGL_SUBSYSTEM_DEPENDENCY_LIST(X) \
X(kMGPipeSubsystemVertexInput, kMGPipeSubsystemResources, "a vertex buffer binding names a Buffer handle and only bit 7 puts one in the slot table (P3a's shipped shape; ResolveVertexInputSubsystemArm)") \
X(kMGPipeSubsystemFramebuffer, kMGPipeSubsystemTextureResources, "every MGPSurface::Res names a Texture or Renderbuffer handle and only bit 10 populates those two slot tables (ResolveFramebufferSubsystemArm)") \
X(kMGPipeSubsystemTextureResources, kMGPipeSubsystemResources | kMGPipeSubsystemSamplers, "a buffer texture's BufferForTexBuffer names a Buffer handle (bit 7); and MGPTextureParams::BuiltinSampler is a SamplerCso HANDLE only bit 11 mints, whose null the applier treats as Fatal{ProtocolCorruption} rather than a decline - D-K2's fourth row, ID-14/ID-15, which WITHDRAWS the brief's 'bit 10 without 11 is fine'") \
X(kMGPipeSubsystemSamplers, kMGPipeSubsystemTextureResources, "every MGPBoundView::Texture and MGPImageView::Res names a Texture handle and only bit 10 populates that slot table; with the row above this is SYMMETRIC - bits 10 and 11 are one arm with two switches (ResolveSamplerSubsystemArm)") \
X(kMGPipeSubsystemPrograms, 0, "a ShaderCso handle names no texture and no buffer, the archive rides beside the record as a companion pointer, and the server's extra specialisation inputs come from state the backend already holds (ResolveProgramSubsystemArm)") \
X(kMGPipeSubsystemBufferBindings, kMGPipeSubsystemResources, "every MGPBufferRange::Res names a Buffer handle and only bit 7 puts one in the slot table, so without it every server-side EnsureBufferResourceForHandle would mint a twin with no record behind it (P5e sb; ResolveBufferBindingSubsystemArm)")
// clang-format on
+3 -3
View File
@@ -67,9 +67,9 @@ P2/P3a; everything else that is not a plain scalar is here.
| **`MGPCaps::CallMask` layout** | bits **0..8** = `MGPCapBit`, unchanged. bits **9..31** reserved. bits **32..47** = the **consumer mask**: bit `32+n` means "the server has a consumer for MGPipe subsystem bit `n`". bits **48..63** reserved. | a clear consumer bit = "this server does not consume that family; emit nothing for it". | `CapsMirror::ServerConsumes` — the **only** legal client-side source | c0's ruling, and the thing that makes R-8 implementable at all: R-8 says the client's liveness gates must read the `CallMask` mirror, but `CallMask` as declared has only nine feature bits and no per-family bit. Constants and the two fold/test helpers are in `MG_Remote/CapsCodec.h`; `CapsCodec.cpp` asserts the block does not collide with `MGPCapBit` and that P4a's `0x1fff` fits sixteen bits. |
| **ABI agreement** | `Hello`/`Welcome` assert both peers agree on `sizeof(DynamicBackendParameters)`, `sizeof(MGPCaps)`, `sizeof(GLFunctionsTable)` and `buildFingerprint`. A mismatch is `Fatal{AbiMismatch}` and **never** a downgrade. | — | s1 | The compositional assertion above means the caps block's literal size is ABI-dependent. P6's spawn is same-machine, same-binary and inherits this unchanged. A fixed-width rewrite of `DynamicBackendParameters` is **P7's** account, not P5's. |
| **`MGPSubData::Target`** | Packed: **low byte = `MGPipeResourceTarget`, high byte = the cube-face upload target**. Read only through `MGPipeSubDataResourceTargetOf` / `MGPipeSubDataUploadTargetOf`. Whole field `== 0` is the **buffer** half; a low byte naming `Buffer`, `Renderbuffer` or `>= MGPipeResourceTarget::Count` is `Fatal{ProtocolCorruption}`. | whole field 0 = buffer upload | applier | P4a ID-12. Already settled; copied here because a decoder that open-codes it is the class-1 defect. |
| **`MGPImageView::Access`** | The three-value encoding at `ImageEmit.h:146-159`, verbatim. | — | decoder, applier | **This encoding has only ever lived in a package header** (P4a R-3). This table is its first wire reader, so it is now contract. |
| **`MGPSamplerView::Target`** | The encoding at `SamplerEmit.h:900`, verbatim. | — | decoder, applier | Same status as the row above. |
| **`MGPFramebufferState::DrawBuffers[8]`** | The `-1` / default-token narrowing at `FramebufferEmit.h:146-163`, verbatim. A draw-buffer entry naming a colour attachment outside the record's own array is `Fatal{ProtocolCorruption}` (the applier already says so). | `-1` = "no attachment" | decoder, applier | Same status. |
| **`MGPImageView::Access`** | `0 = GL_READ_ONLY`, `1 = GL_WRITE_ONLY`, `2 = GL_READ_WRITE`. Named by `kMGPipeImageAccess{ReadOnly,WriteOnly,ReadWrite}` and validated by `MGPipeImageAccessIsValid` / `MGPipeDecodeImageAccess`, all in `MG_Pipe/MGPipeValueTypes.h`. Any other value is `Fatal{ProtocolCorruption, "ImageView.Access"}` at the READER. **Not** `MGPImageBind::Access`, which carries the raw GL token (`0x88B8`/`0x88B9`/`0x88BA`) and is a different field. | — | decoder, applier | **This encoding only ever lived in a package header** (P4a R-3); P5e ruling 16 moved it to `MGPipeValueTypes.h`, which is where both roles now read it. P3b/P4b R-3 replaced this row's line-range citation with the ENUMERATOR NAMES: the old pointer (`ImageEmit.h:146-159`) had drifted off the encoding entirely, which is the failure mode this table's own `MGPSubData::Target` row warns about. |
| **`MGPSamplerView::Target`** | `MobileGL::TextureTarget` cast to `Uint8` (`SamplerEmit.h`'s `m_lastView.Target = static_cast<Uint8>(texture.GetTarget())`), 11 values. **Not** `SamplerEmit.h`'s `SamplerTarget[]`, which is a DIFFERENT, biased encoding of the same enum (`TextureTarget + 1`, `0` = "this program samples nothing here") living 200 lines away and client-internal. | — | decoder, applier | P3b/P4b R-3: the old line citation had drifted by six lines. **Known gap, stated rather than closed here**: unlike `MGPipeResourceTarget` this encoding has no `Count` bound, no `…IsValid` predicate and no `Fatal{ProtocolCorruption}` for an out-of-range value — the decoders at `DirectGLES.cpp` cast straight back. Giving it the `MGPipeImageAccess` treatment in `MGPipeValueTypes.h` is the remaining half of R-3 and is not this package's file. |
| **`MGPFramebufferState::DrawBuffers[8]`** | An index INTO THIS RECORD'S OWN `Color[]` array; `-1` = NONE. The four default-framebuffer tokens map to `0`. Narrowed by `MGPipeDrawBufferIndex` and bounded by `MGPipeDrawBufferIsInsideTheWireWidth` (`MG_Impl/Pipe/FramebufferEmit.h`); the applier re-checks `< -1 \|\| >= kMGPipeMaxColorAttachments` and raises `Fatal{ProtocolCorruption}` (`MG_Pipe/PipeApply.cpp`). `kMGPipeMaxColorAttachments == MobileGL::kMGMaxDrawBuffers` is `static_assert`ed. | `-1` = "no attachment" | decoder, applier | P3b/P4b R-3: the old line citation (`FramebufferEmit.h:146-163`) was off by one and is replaced by the FUNCTION NAMES, which move with the code. |
| **`MGPReplySlot::Id`** | **= the record's sequence number** (R-3). No new id space, no allocator. The server writes the answer into `SEG_REPLY[seq % slots]` and **stamps `seq` back into the slot header** so a wrong-slot read is detectable rather than plausible. | seq is 1-based; `0` = "no record / not encoded" | client barrier wait | `ARCHITECTURE.md:124`: the wire carries no per-record seq field, so seq *is* the ordinal. `MGPReplySlot` exists (`MGPipeTypes.h:78-81`) and **no payload of the ten `kReplySlot` calls contains one** — which is exactly why the id must be derived rather than carried. P9 generalises this to "seq is the id's initial value", which extends the rule rather than overturning it. |
| **reply slot header** | `{Uint64 Seq; Int32 Status; Uint32 Size;}` — 16 bytes, then the payload. `Status`: **0 = OK, 1 = DECLINED, 2 = ERROR**. | — | client | **`DECLINED` is a real answer, not a failure.** It is how `MapPersistent` says `nullptr` (R-6) and how the four `Bool` acceptance entry points say `false` (R-5). A client that treats DECLINED as an error re-creates ID-39's 66 lost uploads from the other side. |
| **which rows own a reply slot** | **14, and `kMGPipeCallFlags` is the single source of truth** (R-16). The ten always-declared answers plus the **four acceptance rows** — `ResourceCreate`, `ResourceRespecify`, `ResourceSubData`, `SetTextureParams`. | a row without `kReplySlot` has no slot and must never be posted to | s1 sizes the pool from it; w1 posts against it | The four are exactly the `MGPipeApply*` entry points returning `Bool` (`PipeApply.h:820`, `:868`, `:897`, `:1023`); `MapPersistent`'s `void*` is the fifth answer and was already declared. They carried no flag because in monolith the answer is a direct call's return value and there was nothing to declare — but this table already said DECLINED is how they answer, so the catalogue and the contract could not both stand. Pinned by `PipeCatalogueTest`: 14 by count **and** the four by name, so a row cannot lose the flag while another gains one. |
@@ -53,6 +53,14 @@ add_executable(ContextEpochTest ContextEpochTest.cpp)
target_include_directories(ContextEpochTest PRIVATE ${MGL_ROOT}/include ${MGL_ROOT}/MobileGL)
target_link_libraries(ContextEpochTest PRIVATE GTest::gtest_main ${LINK_LIBRARIES})
gtest_discover_tests(ContextEpochTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
# P3b/P4b R-5: D-K2's dependency rule had six statements and nothing compared them. This binary
# is where the comparison can live, because it is the one that links BOTH the client check
# (MG_Impl/Pipe, through MG_Pipe) and the server gate (MG_Backend/DirectGLES's resolvers).
add_executable(SubsystemDepsTest SubsystemDepsTest.cpp)
target_include_directories(SubsystemDepsTest PRIVATE ${MGL_ROOT}/include ${MGL_ROOT}/MobileGL)
target_link_libraries(SubsystemDepsTest PRIVATE GTest::gtest_main ${LINK_LIBRARIES})
gtest_discover_tests(SubsystemDepsTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
gtest_discover_tests(EsslShaderPassTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
gtest_discover_tests(BaseInstanceInjectionTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
gtest_discover_tests(ViewportIndexRoutingTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
@@ -0,0 +1,247 @@
// MobileGL - MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp
// Copyright (c) 2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
//
// D-K2's SUBSYSTEM DEPENDENCY RULE: THE CASE THAT FAILS WHEN THE TWO SIDES DISAGREE (P3b/P4b
// R-5).
//
// THE RULE HAD SIX STATEMENTS AND NOTHING COMPARED THEM. Two of the four prose ones were
// WRONG - MG_Pipe/MGPipe.h and MG_Backend/DirectGLES/Managers.h both said "THREE OF THEM" when
// there are six rows, and both said "the mirror pairs (10 without 11, ...) are all fine" when
// 10-without-11 is D-K2's FOURTH row. Neither wrong comment could fail anything. The two
// EXECUTABLE statements are in different link units with no shared header:
//
// CLIENT dependency check MG_Impl/Pipe/PipeFill.cpp's kMGPipeP4aFamilyDependencies, reached
// through MGPipeP4aFamilyEmits().
// SERVER consumer gate MG_Backend/DirectGLES/Managers.cpp's four
// Resolve<Family>SubsystemArm(), over PipeSubsystemDependencyMissing.
//
// This file is the first thing in the tree that calls BOTH at the same mask and compares them,
// and it compares each against MG_Pipe/SubsystemDeps.def - the one place the rule is now
// written. A change to either reader alone reds here; a change to the .def alone reds here
// twice.
//
// WHY THE TWO READERS DO NOT YET READ THE .def THEMSELVES. PipeFill.cpp is the contract
// package's file for the whole phase (MG_Impl/Pipe/TextureEmit.h states that rule in full) and
// Managers.cpp belongs to wave 2-D package D1 while it is in flight. Switching them is two
// one-line changes for the integrator; the anti-drift property does not wait for it, because
// this file supplies it from the outside.
//
// THE SERVER RESOLVERS ARE CALLED DIRECTLY, NOT THROUGH THEIR LATCHED ACCESSORS. Managers.h
// wraps each one in a `static const Bool enabled = Resolve...()` that runs once per process;
// calling the resolver itself is what lets this case sweep masks. That is also why the case
// restores MG_Config::Features.PipePush before it returns: a latched accessor evaluated later
// in the same binary would otherwise read a mask this test left behind.
#include <gtest/gtest.h>
#include <Config.h>
#include <MG_Backend/DirectGLES/Managers.h>
#include <MG_Pipe/MGPipe.h>
#if MOBILEGL_PIPE_PUSH
#include <MG_Impl/Pipe/PipeFill.h>
#endif
using namespace MobileGL;
using namespace MobileGL::MG_Pipe;
namespace {
struct PushMaskScope {
Uint64 Saved = MG_Config::Features.PipePush;
explicit PushMaskScope(Uint64 mask) { MG_Config::Features.PipePush = mask; }
~PushMaskScope() { MG_Config::Features.PipePush = Saved; }
};
// Every mask the rule can be interestingly asked about: the phase constants, the two
// documented refusal lanes (0x7ff withholds bit 11, 0x5ff withholds bit 9), and one mask per
// row with exactly that row's required bits knocked out.
Vector<Uint64> InterestingMasks() {
Vector<Uint64> masks{0ull,
kMGPipeSubsystemsMigratedAtP2,
kMGPipeSubsystemsMigratedAtP3a,
kMGPipeSubsystemsMigratedAtP4a,
kMGPipeSubsystemsMigratedAtP5e,
0x7ffull,
0x5ffull,
0x9ffull};
for (const MGPipeSubsystemDependencyRow& row : kMGPipeSubsystemDependencies) {
masks.push_back(kMGPipeSubsystemsMigratedAtP5e & ~row.Requires);
masks.push_back(kMGPipeSubsystemsMigratedAtP5e & ~row.Family);
}
return masks;
}
String Describe(Uint64 mask) {
char buffer[32];
std::snprintf(buffer, sizeof(buffer), "0x%llx", static_cast<unsigned long long>(mask));
return String(buffer);
}
} // namespace
// ---------------------------------------------------------------------------------------------
// 1. The table itself is exhaustive and well-formed.
// ---------------------------------------------------------------------------------------------
TEST(SubsystemDepsTest, EveryRowNamesExactlyOneFamilyAndNoRowRequiresItself) {
Uint64 seen = 0;
for (const MGPipeSubsystemDependencyRow& row : kMGPipeSubsystemDependencies) {
EXPECT_NE(row.Family, 0ull) << "a row with no family is not a row";
EXPECT_EQ(row.Family & (row.Family - 1), 0ull)
<< "a row must name EXACTLY ONE bit; " << Describe(row.Family) << " names several, and "
<< "MGPipeSubsystemRequires ORs every matching row - a multi-bit family would make a "
"query for one of its bits answer for all of them";
EXPECT_EQ(row.Family & row.Requires, 0ull)
<< Describe(row.Family) << " requires itself, which no reader can ever satisfy";
EXPECT_EQ(seen & row.Family, 0ull)
<< "two rows name " << Describe(row.Family) << "; MGPipeSubsystemRequires would OR "
<< "them and the duplicate would be invisible";
seen |= row.Family;
EXPECT_NE(row.Why, nullptr);
EXPECT_NE(row.Why[0], '\0') << "a row with no reason is how the rule drifted in the first "
"place";
}
// The families the rule is about. Bit 8 and bit 13 are in the table too, which is the half
// MGPipe.h's old "THREE OF THEM" comment was missing.
EXPECT_EQ(seen, kMGPipeSubsystemVertexInput | kMGPipeSubsystemFramebuffer |
kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers |
kMGPipeSubsystemPrograms | kMGPipeSubsystemBufferBindings);
}
TEST(SubsystemDepsTest, TheFourthRowIsPresentAndTheWithdrawnMirrorPairIsNotFine) {
// ID-14/ID-15. This is the row two comments in the tree denied existed, and the reason this
// case is written as an assertion rather than a paragraph.
EXPECT_NE(MGPipeSubsystemRequires(kMGPipeSubsystemTextureResources) & kMGPipeSubsystemSamplers,
0ull)
<< "bit 10 no longer requires bit 11. If that is intended, the withdrawal recorded in "
"PipeFill.cpp and in BRIEF-P4A's amendment has been reversed and Espryt's "
"ResolveTextureResourceSubsystemArm must stop refusing 0x7ff in the same commit";
EXPECT_FALSE(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemTextureResources, 0x7ffull));
EXPECT_FALSE(MGPipeSubsystemDependenciesAreSet(
kMGPipeSubsystemSamplers,
kMGPipeSubsystemsMigratedAtP5e & ~kMGPipeSubsystemTextureResources))
<< "bit 11 without bit 10 must not be live - the symmetric half of the fourth row";
// ...and the mirrors that really are fine.
EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemPrograms, 0ull))
<< "bit 12 depends on nothing";
EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(
kMGPipeSubsystemTextureResources,
kMGPipeSubsystemResources | kMGPipeSubsystemSamplers | kMGPipeSubsystemTextureResources))
<< "bit 10 with 7 and 11 and WITHOUT bit 9 is fine and is not a row";
}
// ---------------------------------------------------------------------------------------------
// 2. THE CLIENT DEPENDENCY CHECK never emits a family the table refuses.
//
// ONE-DIRECTIONAL, AND FOR A MEASURED REASON. MGPipeP4aFamilyEmits is a QUADRUPLE: the
// operator's bit, the family's wired constant, a backend having registered the consumer, and
// the dependency rule (PipeFill.cpp's `wants()`). Only the fourth conjunct is this file's
// subject, and the other three are false in a bare unit binary with no backend registered - the
// first version of this case asserted equality and reddened at 0x1fff on the sampler and program
// families for exactly that reason, which is the assertion being wrong rather than the product.
//
// So the direction that IS this rule's: the client may withhold a family for reasons of its own,
// but it may never EMIT one whose dependency bits are unset. That is the direction in which a
// disagreement means records crossing to a server whose matching handle arm did not come up.
// ---------------------------------------------------------------------------------------------
TEST(SubsystemDepsTest, TheClientNeverEmitsAFamilyTheTableRefuses) {
#if MOBILEGL_PIPE_PUSH
const Uint64 families[] = {kMGPipeSubsystemFramebuffer, kMGPipeSubsystemTextureResources,
kMGPipeSubsystemSamplers, kMGPipeSubsystemPrograms};
int emitted = 0;
for (const Uint64 mask : InterestingMasks()) {
for (const Uint64 family : families) {
if (!MGPipeP4aFamilyEmits(family, mask)) continue;
++emitted;
EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(family, mask))
<< "at MOBILEGL_PIPE_PUSH=" << Describe(mask) << " the CLIENT emits family "
<< Describe(family)
<< " while MG_Pipe/SubsystemDeps.def says its dependency bits are not all set. "
"Those records reach a server whose matching handle arm refused to come up - "
"the half-run subsystem D-K2 exists to make impossible. The two statements of "
"the rule have drifted; whichever moved, the other has to move with it.";
EXPECT_NE(mask & family, 0ull)
<< "the client emitted a family whose own bit is clear at " << Describe(mask);
}
}
// NOT VACUOUS-PROOF, but a loud marker: a unit binary registers no backend consumer, so
// MGPipeP4aFamilyEmits may legitimately be false everywhere here. Recorded rather than
// asserted, because asserting it would make this case depend on whether the binary happens
// to have pulled in a consumer registration.
RecordProperty("client_emitting_combinations", emitted);
#else
GTEST_SKIP() << "the client dependency check is compiled only under MOBILEGL_PIPE_PUSH";
#endif
}
// ---------------------------------------------------------------------------------------------
// 3. THE SERVER CONSUMER GATE agrees with the table at every interesting mask.
//
// The resolvers answer "did this family's handle arm come up", which is the dependency rule AND
// the family's own bit AND, for the framebuffer one, D-C3's separate wire-caps refusal. So the
// comparison is one-directional where it has to be: a resolver may say NO for a reason of its
// own, but it may never say YES at a mask the table refuses - that is the direction in which a
// disagreement is a half-run subsystem rather than an extra refusal.
// ---------------------------------------------------------------------------------------------
TEST(SubsystemDepsTest, TheServerConsumerGateNeverArmsAFamilyTheTableRefuses) {
using namespace MobileGL::MG_Backend::DirectGLES;
struct Resolver {
const char* Name;
Uint64 Family;
Bool (*Resolve)();
};
const Resolver resolvers[] = {
{"ResolveFramebufferSubsystemArm", kMGPipeSubsystemFramebuffer,
&ResolveFramebufferSubsystemArm},
{"ResolveTextureResourceSubsystemArm", kMGPipeSubsystemTextureResources,
&ResolveTextureResourceSubsystemArm},
{"ResolveSamplerSubsystemArm", kMGPipeSubsystemSamplers, &ResolveSamplerSubsystemArm},
{"ResolveProgramSubsystemArm", kMGPipeSubsystemPrograms, &ResolveProgramSubsystemArm},
};
for (const Uint64 mask : InterestingMasks()) {
PushMaskScope scope(mask);
for (const Resolver& resolver : resolvers) {
const Bool armed = resolver.Resolve();
if (!armed) continue;
EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(resolver.Family, mask))
<< "at MOBILEGL_PIPE_PUSH=" << Describe(mask) << " the SERVER's " << resolver.Name
<< " ARMED the handle arm for family " << Describe(resolver.Family)
<< " while MG_Pipe/SubsystemDeps.def says its dependency bits are not all set. "
"The client withholds the family at this mask, so the server is running a "
"handle arm nobody is feeding - which is the half-run subsystem D-K2 exists "
"to make impossible.";
EXPECT_NE(mask & resolver.Family, 0ull)
<< resolver.Name << " armed with its own bit clear at " << Describe(mask);
}
}
}
// ---------------------------------------------------------------------------------------------
// 4. ...and the refusal really happens at the two documented lanes, so that test 3 is not
// vacuously green because every resolver said NO for an unrelated reason.
// ---------------------------------------------------------------------------------------------
TEST(SubsystemDepsTest, TheTwoDocumentedRefusalLanesStillRefuseAndTheFullMaskStillArms) {
using namespace MobileGL::MG_Backend::DirectGLES;
{
PushMaskScope scope(kMGPipeSubsystemsMigratedAtP5e);
EXPECT_TRUE(ResolveTextureResourceSubsystemArm())
<< "the texture family's handle arm does not come up at its own phase mask, so every "
"refusal assertion in this file is vacuous";
EXPECT_TRUE(ResolveSamplerSubsystemArm());
EXPECT_TRUE(ResolveProgramSubsystemArm());
}
{
// 0x7ff: bits 0..10, i.e. the texture family WITHOUT the sampler family - D-K2's fourth
// row, and the mask CMakeLists.txt's refusal lane pins.
PushMaskScope scope(0x7ffull);
EXPECT_FALSE(ResolveTextureResourceSubsystemArm())
<< "0x7ff is the mask whose texture family Espryt refuses by name; if it now arms, "
"D-K2's fourth row has been dropped on the server side while the client and the "
".def still carry it";
}
}
+145
View File
@@ -195,3 +195,148 @@ probe hits (`MGITEST_PIPE_CLIENT_TEXTURE_UPLOAD_EMITTER_PRESENT=1` is in every l
lands"). The `else()` arm is KEPT — it is what makes the arming decision falsifiable in both
directions — but it is unreachable on this tree and now says so.
* `notes/p4a/p4a-results/gates-v1.md`'s re-run table
---
## Slice 3 — the memo purity gate
`scripts/ci/espryt_memo_purity.py`, a sibling of `link_seam_purity.py`, wired into `test.yml`
beside it. Scoped to the BRACES of the families the plan names (`ResolvedTextureBindingMemo`,
`SamplerPassMemo`, `UnitSamplerLookupMemo`, `TwinLookupMemo`, `StateBackendObjectRegistry`) plus
the image sweep's seven file statics, because these two files pass frontend pointers around
legitimately in hundreds of places and a file-wide grep would be a list of exceptions.
Guard-awareness is a small conditional walker, not a preprocessor: it recognises
`#if MOBILEGL_PIPE_LEGACY_MEMOS`, `#if !MOBILEGL_BUILD_DISAGGREGATED` and the `#else` of
`#if MOBILEGL_BUILD_DISAGGREGATED`, which is every spelling these files use.
**Six allow-list entries, each with its reason.** The two that matter:
`StateBackendObjectRegistry`'s `UnorderedMap<StateObject*, Entry>` (`Managers.h:330`, and the
`m_entries` member behind it) and `ResolvedTextureBindingMemo`'s type-erased `const void* program`
are the PULL build's only arms — deleting them moves pull `.text` and fails G1, so they retire
with the pull path (P13). The other four are memo VALUES rather than keys.
**Red-once (R-16)**: 7 negative controls in `--self-test`, 4 positive controls (a
`LEGACY_MEMOS` arm, the `#else` of a `DISAGGREGATED` arm, the allow-listed type-erased key, and a
frontend pointer outside every memo family) — plus one taken against the real tree: injecting
`MG_State::GLState::ProgramObject* regressionKey` into the actual `ResolvedTextureBindingMemo`
reddens the gate and names `DirectGLES.cpp:7266`.
**What the sweep found that is NOT in scope**: seven unguarded frontend-pointer-keyed containers
under `MG_Backend/DirectVulkan` (`VkTextureManager`'s five `TextureIdentity`-keyed containers,
`VkClearManager`'s two, `VkRenderPassManager`'s two raw `RenderbufferObject*` maps, and
`VulkanRenderer::VaoDrawMemo`'s `vaoKey`+`vaoLifetimeId` pair sitting beside the correctly fenced
handle key). Magma's key inventory is P7 wave 2's, so the gate does not look there; recorded here
so the next pass meets a known list.
---
## Slice 4 — R-5 (D-K2's dependency rule in one place) and R-3
### R-5
The rule had **six** statements and nothing compared them, and **two of the four prose ones were
wrong in the same way**: `MG_Pipe/MGPipe.h:115-125` and `MG_Backend/DirectGLES/Managers.h:690-695`
both said "THREE OF THEM HAVE A DEPENDENCY" (there are six rows) and both said "the mirror pairs
(10 without 11, ...) are all fine" — when 10-without-11 is precisely D-K2's FOURTH row
(ID-14/ID-15), refused by the server and withheld by the client, with `PipeFill.cpp` saying so out
loud: "The brief's original 'bit 10 without 11 is fine' is WITHDRAWN for P4a as built." Neither
wrong comment could fail anything. `Managers.h`'s copy is in the header of the file that
implements the refusal.
**The rows, once**, in `MG_Pipe/SubsystemDeps.def` (X-macro, `FatalFamilies.def`'s convention),
expanded by `MGPipe.h` into `kMGPipeSubsystemDependencies[]` / `MGPipeSubsystemRequires()` /
`MGPipeSubsystemDependenciesAreSet()`, with four `static_assert`s over it:
| family | requires |
|---|---|
| bit 8 VertexInput | bit 7 |
| bit 9 Framebuffer | bit 10 |
| bit 10 TextureResources | bit 7 **and bit 11** |
| bit 11 Samplers | bit 10 |
| bit 12 Programs | — (a ROW, not an absence) |
| bit 13 BufferBindings | bit 7 |
Bits 8 and 13 were the two the old comment did not know about; bit 13's row lived fifteen lines
further down in `MGPipe.h`, which is how "THREE OF THEM" survived two phases.
**The two readers do NOT yet read it, deliberately.** `MG_Impl/Pipe/PipeFill.cpp` is the contract
package's file for the whole phase (`TextureEmit.h` states that rule) and
`MG_Backend/DirectGLES/Managers.cpp` belongs to package D1 while it is in flight. Switching them
is two one-line changes for the integrator. **The anti-drift property does not wait for it**:
`MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp` is the first thing in the tree that calls BOTH
readers at the same mask, sweeping the phase constants, the two documented refusal lanes and one
mask per row, and compares each against the `.def`. It is in that binary because that binary is
the one that links both sides.
Both comparisons are **one-directional**, and each for a measured reason:
* the server resolvers also fold in the family's own bit and (for the framebuffer one) D-C3's
separate wire-caps refusal, so a resolver may say NO for a reason of its own but may never say
YES at a mask the table refuses;
* `MGPipeP4aFamilyEmits` is a QUADRUPLE — the operator's bit, the wired constant, a registered
consumer, and the dependency rule — and the first version of this case asserted equality and
reddened at `0x1fff` on the sampler and program families because a bare unit binary registers no
consumer. That was the assertion being wrong, not the product; it is recorded because the
temptation to assert equality here is strong and wrong.
**Red-once (R-16), twice:**
1. drop D-K2's fourth row from the `.def` → **compile-time** failure, `MGPipe.h:225` and `:230`
("D-K2's fourth row (bit 10 requires bit 11) has gone missing again").
2. make the SERVER's `ResolveSamplerSubsystemArm` stop checking bit 10 (throwaway patch to
`Managers.cpp`, reverted) → `TheServerConsumerGateNeverArmsAFamilyTheTableRefuses` reds and
names the mask: "at MOBILEGL_PIPE_PUSH=0x9ff the SERVER's ResolveSamplerSubsystemArm ARMED the
handle arm for family 0x800 while MG_Pipe/SubsystemDeps.def says its dependency bits are not
all set." `0x9ff` is exactly the lane `CMakeLists.txt` already pins for G12.
Stale prose corrected in `MGPipe.h` (both blocks), `MG_Impl/Pipe/PipeFill.h` and
`MG_Impl/Pipe/TextureEmit.h`. **`MG_Backend/DirectGLES/Managers.h:690-695` is NOT corrected here**
— it is package D1's file. Its two defects are named above so the integrator can take them in one
line.
### R-3 (the cheap half)
`MG_Remote/CONTRACT-P5.md` already claimed the image-access, sampler-view-target and draw-buffer
encodings as contract, but did so **by pointing at package-header line ranges, and all three had
drifted** — `ImageEmit.h:146-159` no longer holds the encoding at all, `SamplerEmit.h:900` is off
by six lines, `FramebufferEmit.h:146-163` by one. That is the exact failure the table's own
`MGPSubData::Target` row warns about.
All three rows now state the encoding **by enumerator and function name** rather than by line, and
the two confusable siblings are named out loud: `MGPImageBind::Access` carries the raw GL token
(not this encoding), and `SamplerEmit.h`'s `SamplerTarget[]` is a different, biased encoding of the
same enum living 200 lines from the wire one.
**The remaining half of R-3 is stated, not closed**: `MGPSamplerView::Target` has no `Count`
bound, no `...IsValid` predicate and no `Fatal{ProtocolCorruption}` for an out-of-range value,
unlike `MGPipeResourceTarget`. Giving it the `MGPipeImageAccess` treatment in
`MGPipeValueTypes.h` is a `MG_Pipe` change outside this package's declared footprint.
---
## Slice 5 — the raw-depth-fetch sampler, step 1 only
Step 1 done: `ContextEpochTest` gains a case that drives `GetRawDepthFetchSampler()` on BOTH arms
in one process and asserts they are two different `BackendSamplerObject`s — the observable form of
`DirectGLES.cpp:334-336`'s prose claim that the legacy pair "is not constructed or consulted by
the server".
**Step 2 NOT done, and the package's own condition is why.** Every deletion candidate — the two
file-static `SharedPtr`s (`DirectGLES.cpp:82-83`), the monolith arm (`:361-371`),
`NeedsRawDepthFetchSampler` (`:393-399`) and the pre-handle sampler-pass branch (`:8015-8024`) —
is UNCONDITIONAL code. None sits behind `#if !MOBILEGL_BUILD_DISAGGREGATED`; the pull build
(`DISAGGREGATED=OFF`, `PIPE_PUSH=OFF`) compiles and links all of it, and the monolith arm is the
ENTIRE body `GetRawDepthFetchSampler` has there. Deleting any of it moves pull `.text` and fails
G1's 0/0/0/0 — `ARCHITECTURE.md:340` states that rule for exactly this class of
retired-but-compiled code. Filed as **P13**, beside the existing S7 entry in
`notes/p5f/f0-statics.md`.
---
## Slice 6 — the census
`notes/p34b/split-scenario-census.md` carries the table. Twelve scenarios newly gating in the
three GLES split arms; `ProgramPipelineScenario`'s nine non-storage-block cases deliberately not
registered (ScenarioFixture's armed-lane rule); DirectVulkan arms deliberately none, with the
tier-2 informational replay named as the reason it is not a coverage hole.