Commit Graph
2 Commits
Author SHA1 Message Date
swung0x48 8bb6309a1c [scripts, CI, docs] (P7): the 184-symbol link-closure ratchet lands as a gate
CONTRACT-P6.md 12.1 has carried "a ratchet recomputes the 184 in CI and asserts it only
ever falls" as an obligation since P6. The roadmap handed it to P6.5; the P6.5 line never
received it, and scripts/ci/ has never had anything that computes this number. ID-P7-7
gives it to P7 wave 0. This is it.

scripts/link_ratchet.py mechanises a6's hand experiment: partition the per-object output
of the single MobileGL target into SERVER / FRONTEND / SHARED, then compute
(SERVER undefined) - (SERVER defined) - (SHARED defined) intersect (FRONTEND defined),
which is the undefined-symbol list a real link of a server-only image would print. The
partition is an explicit table with a rationale per row, because it IS the module boundary
the build system does not have (SOURCE_FILES is one flat list; boundaries are P13's work).
An object matching no row is a hard failure, as is a build whose CMakeCache lacks the three
disaggregated options, or an empty role set - every way this gate could go green without
having measured anything is an explicit exit 2.

Measured on 78b7d6be: 186 symbols. The three backend buckets reproduce a6's hand-made
101 / 14 / 60 exactly, as do its per-object referrer and definer distributions
(UniformManager 90, VulkanRenderer 60, Managers 53, DirectGLES 51; TextureObject 59,
SamplerObject 30, BufferObject 18, FramebufferObject 16, PipeFill 9). Nothing from a6's
184 has been cleared; the +2 are both P6.5's:
  Client::ClientSession::StartSpawned()                 - fork-per-session
  GLState::ProgramArtifactsSchemaFingerprint()          - archive v2 caps

The baseline stores the LIST, not a count (ID-P7-7): a count-only ratchet goes green on the
day one symbol is cleared and another added. --assert-monotone fails only on symbols absent
from the baseline and names each one's defining object and every referring object; symbols
that disappeared are reported with an instruction to re-baseline in the same commit, and
never fail.

The CI step goes in build-linux-split, not build-linux: build-linux takes the three options
from MOBILEGL_CI_DISAGGREGATED, which is ON only on feat/disaggregated and
codex/fix-github-ci, so on any other branch it builds a monolith and the gate would measure
a SERVER set with no transport half. The JSON is uploaded even when the gate fires.

Two findings for the integrator, both written up in the note:
  - a6's four published bucket totals sum to 181 against its own headline of 184. These
    buckets are a strict partition and always sum to the headline; the difference is in
    a6's core bucket, which it listed selectively.
  - 5 of the 14 "P3b/P4b" symbols are MG_Pipe slot-allocator and apply-thread plumbing
    that sits under MG_Impl/Pipe/ by file location alone. P3b/P4b cannot clear them by
    migrating Espryt functionality; they need P13's module move. That bucket's floor is
    5, not 0, so the plan's 0.5-day Tier 1 estimate for "D12's 14 symbols" should be
    re-cut against 9. All 7 symbols from that object carry a `# P13` annotation.

red-once (R-16) is recorded verbatim in the note. Worth knowing: the plan's own example
(SamplerObject::GetMinFilter) does NOT turn this gate red, because it is already in the
baseline - the ratchet reddens on new symbols, so a negative control has to pick one that
is not in the 186. A reach to GetBorderColorForm() from DirectGLES/Utils.cpp does, naming
the symbol, its definer and its referrer, exit 1; reverted and green again.
2026-09-22 14:26:04 -04:00
swung0x48 9c773182bb [Feat] (MGPipe): land the interface skeleton - the complete call catalogue, the payload PODs and the seven generators
- Plan B section 4 makes the frontend/backend boundary explicit and gives the backend its
  own state machine. This is the P0 deliverable of section 11: the whole catalogue exists
  from day one, placeholders included, because the wire opcode is a call's position in
  PipeCalls.def and record numbering must never churn.
- MG_Pipe/PipeCalls.def is the single source of truth: 68 unique calls as
  X(Name, Payload, Class, Flags). Its header reconciles that number with the plan's
  headline counts (section 4.4, appendix A), which double count - "CSO 15" names
  bind_sampler_states and set_sampler_views that the "set_* 17" list also names, "screen
  14" tabulates the query family that section 4.3 assigns to the context, and the
  "transfer 12" row enumerates 11 calls. Each reconciliation is written down next to the
  count rather than resolved silently.
- MGPipeHandles.h: the 8-byte {slot, gen} pair, dense per-kind slots, the reserved null
  and default-framebuffer handles, and the ShaderCso composite band (sections 4.2, 5.6.3).
  The two generations are documented as strictly separate, with the interface rule that no
  call may require the client to know MGGen.
- MGPipeTypes.h: every payload of section 4.5 as a flat POD with explicit padding, a
  trivial-copyability assertion and an exact sizeof assertion, because the wire records are
  memcpy'd and a field silently changing width is a protocol break no test would see.
  MGPCaps embeds DynamicBackendParameters by inclusion so a caps field added there needs no
  second edit here; its assertion is stated as a composition because that struct still
  carries SizeT. ResidualValueBlock is pinned at MGL_RESIDUAL_BLOCK_SIZE 1248, the ratchet
  that only ever goes down and reaches static_assert(... == 0) in P13 (section 6.3).
- MGPipeHostSpan.h keeps the one shape that changes with the transport isolated behind one
  predictable branch, with the kFromServerIndexMirror sentinel D-B7 needs.
- MGPipeCallbacks.h names the reverse channel as ten callbacks plus the forward terminator
  in the context table, replacing 95 poke sites across 17 methods (section 7.1).
- scripts/gen_pipe.py runs G1-G7 off those .def files. G1 asserts each table is EXACTLY its
  call count of function pointers; G3 pads every wire record to the stream's 8-byte
  granularity and checks size >= sizeof && size <= remaining && size % 8 == 0 before
  dispatch, fatally; G4 compares field by field (padding excluded, floats by bits) because
  a comparator with false positives is one nobody reads - DirectGLES.cpp says the same
  thing about its own memcmp of RenderStateParameters; G5 turns the accessor list into
  per-verb poison generations rather than a written-once bitmap, which is the only version
  that can see a field left over from the previous draw (section 6.2.2); G6 joins the 477
  read points of the vendored backend_read_inventory.md against Coverage.def and reports
  0 UNMAPPED (299 to a call, 167 signatures that become handle parameters, 6 reverse
  channel, 5 client-resolved); G7 pins the pipeline subset BY MEMBER NAME from what
  VulkanRenderer::ComputePipelineStateHash hashes today, computing no offsets in python.
- The generated files are committed so the build never depends on python; CI regenerates
  and diffs them.
2026-09-05 20:16:49 -04:00