Commit Graph
204 Commits
Author SHA1 Message Date
swung0x48 d83fc6643a [CI, android-plugin] (P6): CI starts a second MobileGL process on a device
The Android retrace matrix gains a third lane, spawn-acceptance, beside
monolith-control and inproc-acceptance. Until now nothing in CI had ever
started a second MobileGL process on a device at all.

The apk.yml retrace job already carried a `transport` matrix dimension and
already branched on it, so the lane itself is three edits: a row in
github_transport_matrix, an elif in the retrace step, and a summary to render.
What needed thought was the gate.

THE SPAWN LANE IS APK-ONLY, and that is a statement about where evidence is
missing rather than about what spawn can do. Linux already has
test.yml's `retrace-split`, which is {backend, case, transport} over inproc AND
spawn with the two negative controls that make it mean anything. A spawn row in
the Linux transport matrix would pay for a second full sweep proving nothing -
and test.yml's `retrace` job stages no server image, so the lane would not
merely be redundant, it would be red. self_test_transport_matrices now asserts
the absence in both directions, because an accidental removal of the `if apk`
has to be caught in the self-test rather than in CI.

--require-spawn REQUIRES TWO SENTENCES, and the second is the one that matters.
ConfigLoader's marker proves the VALUE RESOLVED - it exists only in the Spawn
arm, which exists only under MOBILEGL_BUILD_DISAGGREGATED - but that is a
statement about a parser. `spawn ARMED - the server role runs in pid N` is
written only after the launch, the connect and the handshake have all
succeeded, and the pid in it is the SERVER's. It is the only line in the log a
same-process run cannot produce.

Both halves red once, measured:
  * a monolith run demanded to prove spawn: REFUSED, transport_resolution_marker
    false, server_pid null - while the retrace itself scored ssim=1.000000,
    which is exactly the trap. OpenRA matches its golden under monolith too, so
    the picture has never been the evidence.
  * the same run's real spawn log with the one ARMED line deleted: REFUSED,
    transport_resolution_marker TRUE and server_pid null. That is the
    "resolved the transport and then never left the process" case, isolated.

THE PROOF USES `any` WHERE THE inproc GATE USES `all`, and the difference is
load-bearing rather than a relaxation. Under spawn two processes append to one
MOBILEGL_LOG_FILE_PATH, and the launcher deliberately scrubs every
MOBILEGL_IPC_* from the child's environment (anti-recursion catch (b)) - so the
server's own `Config: IPC` line reads strict=0 role-split-state=0 BY
CONSTRUCTION. Demanding every line match would red every spawn run for the one
thing the design requires.

--use-pbuffer is not optional on this lane: until P12 a spawned server cannot be
handed a window, because an ANativeWindow* is a pointer into the CLIENT's
process (Rule H). MOBILEGL_IPC_SERVER_PATH is deliberately NOT passed -
nativeLibraryDir carries an install-time hash that nothing off the device can
spell, and the Activity resolves it.

The local runner now passes the same knobs and the same --require-* flag CI
does, so "it works locally" can no longer mean "it ran monolith locally". That
change immediately caught its own omission: the first local run with the gate
armed failed on client_ipc_line_present=false because the runner had been
sending MOBILEGL_TRANSPORT alone.

Verified on device 2f7cbe2e: OpenRA.DirectGLES spawn, ssim=1.000000,
mismatchPixels=0, "real spawn transport, server role in pid 9885, and zero
Fatal records confirmed".
2026-09-21 18:30:18 -04:00
swung0x48 dc5bd935de [MG_Remote, CI] (P6): the split retrace lane becomes the whole CI matrix, both backends, both transports
It was one case on one backend under one transport: OpenRA, DirectGLES,
inproc. It is now every CI case, both backends, inproc AND spawn - 154 CI
entries, 156 local ctest entries - because every defect the spawn work turned
up was a question about the SERVER's process and not about any one trace or
either backend.

THE DirectVulkan ARM. tools/trace_replay/CMakeLists.txt carried "a
DirectVulkan arm would be measuring a server nobody has written yet". True
when P5 wrote it; false since P5f/P7 landed Magma's server consumers and its
run-ahead readiness gate. Leaving it would have shipped a split shape whose
second backend nothing ever ran - the same class of hole as a lane that runs
monolith under a name that says split. Magma now brings up a server process
of its own and OpenRA retraces across it at ssim=1.000000, which is only
possible because this branch also taught the child to load its config: before
that, a DirectVulkan session brought up a DirectGLES server and the two
halves disagreed about which backend the capability mask describes.

`split` BECOMES AN OPT-OUT. It began as an opt-in naming one case, because
one case was the work. Marking 39 cases `"split": true` would carry no
information and would reinstate the hole the key was written to close, only
mirrored: a case added later would get the monolith arms and be silently
absent from the split ones, and absent is the one state nothing reds on. A CI
case is now in the subset unless it says `"split": false`, which is a
reviewable claim that a trace cannot cross a role boundary.

THE CI MATRIX GAINS A THIRD DIMENSION. The job took {backend, case} and
hard-coded MOBILEGL_TRANSPORT=inproc in its own run block, so `spawn` had no
way into CI at all; the .SPAWN ctest entries existed only for a local build.
It is {backend, case, transport} now, the backends are the case's own
ci_backends rather than a hard-coded DirectGLES, and the artifact names carry
the transport or the two arms of one {backend, case} would overwrite each
other's uploads.

inproc STAYS AS THE CONTROL. Same library, same codec, same applier, same
picture; the only difference is which process the server role runs in.
Running only spawn would leave "did the transport break it?" and "did the
phase break it?" answerable only together - which is the question every
failure in this phase has turned on, including the SIGSEGV that D1 explained.

BOTH NEGATIVE CONTROLS FOLLOW THE ARM. retrace_pull_library_control.sh and
retrace_drop_draw_control.sh each exported MOBILEGL_TRANSPORT=inproc
themselves, so on a spawn job they would have gone on proving something about
the other arm while spawn ran unguarded. They read SPLIT_TRANSPORT now, and
default to inproc so a caller that sets nothing behaves exactly as before.
scripts/ci/control_smoke_test.sh: 22 passed, 0 failed against the stubbed
ctest, including the two modes that matter most - a red whose SSIM never fell
and a knob that armed and dropped nothing.

The spawn arm needs a second binary and the inproc arm needs none, so the job
places libMobileGLServer.so only on the spawn arm and a missing one is a
named failure there rather than a LaunchServer refusal 78 jobs deep.

Measured locally as this lands: 39 of the 78 SPAWN entries complete, all
green. The rest are still running.
2026-09-21 12:31:49 -04:00
swung0x48 eae947050f [CI] drop the APK disaggregated-proof step
The step located the Android CMake identity by reading CMakeCache.txt next
to each compile_commands.json, but the trace flavor's build also produces
.cxx/tools/<flavor>/<abi>/compile_commands.json copies whose cache lives in
the real per-ABI build dir, so the proof failed on a file-layout assumption
rather than on missing evidence. Remove the step and its artifact upload.
2026-09-21 03:36:45 -04:00
swung0x48 e4ae19643d [CI] (Disaggregated): P7's packed-float mip gate flips to green, by name
5310ff62 generates the R11F_G11F_B10F chain with a real GPU shader blit and gives
F1WireScenario.GenerateMipmapPackedFloatPixels pixel assertions, so the entry now
passes under strict split state and `Magma:mipmap-native-format@P7` survives in no
source file. The old arm would therefore have failed its own `test rc -ne 0`.

Both halves of the arm are retired in one commit, because either alone is a lie:
CMakeLists' fm loop drops the special case that gave this slot the
`integration-magma-p7-known-red` label, so the entry is discovered by the ordinary
`integration-magma-split` lane - the same label, environment and pixels assertion
as GenerateMipmapPixels and GenerateMipmapDepthPixels next to it, and the same
label the dual-block census lane already re-runs.

The lane membership alone would not be the old gate's equal: a lane counts its
entries' exit codes, not their absence. So the CI step keeps its shape and inverts
its expectation - the same single named entry, discovered and run by name under
MOBILEGL_IPC_STRICT_ERRORS=1 and MOBILEGL_IPC_ROLE_SPLIT_STATE=1, now asserted
through split_log_paths.py's require_green, which rejects a skip, a missing entry
and a failure alike. No assertion is skipped away or weakened: the pixels are
checked by the test, and the named-refusal check the log carried is what the GPU
blit retired.
2026-09-20 20:34:35 -04:00
swung0x48 5cbf29b041 [CI] retain failed retrace-split artifacts and read full inventories before cleanup
The remove-artifact-clutter jobs deleted trace fixtures and results while
reading job/artifact inventories through unchecked process substitution, so
a failed GitHub API page looked like 'no failed cases' and fixtures for a
red retrace could be deleted. test.yml cleanup also did not wait for the
retrace-split lane even though those jobs download the same fixtures.

Read both paginated inventories into files before any deletion so an API
failure fails the step, add retrace-split to the cleanup dependencies, and
retain fixtures plus result artifacts for failed cases in all three lanes.
2026-09-20 18:16:44 -04:00
swung0x48 347bb90c4a [CI] make migrated client arrays and full inproc status hard gates 2026-09-20 12:07:52 -04:00
swung0x48 97ce032f7f [CI] make full inproc GPU and trace acceptance primary on disaggregation branches 2026-09-20 11:46:54 -04:00
swung0x48 bb3a51995c [CI, Android] (Disaggregated): run full inproc trace acceptance beside monolith controls 2026-09-20 10:43:06 -04:00
swung0x48 7e82ea0fbf [CI, Android] (Disaggregated): ship and verify inproc APKs on disaggregation branches 2026-09-20 10:16:54 -04:00
swung0x48 ac1fbcdd7b [CI] validate Magma cached object lifetime scenarios with Vulkan layers 2026-09-20 09:04:01 -04:00
swung0x48 112da7ca5f [CI] (Disaggregated): enforce run-ahead negative control and Vulkan synchronization validation 2026-09-20 05:18:22 -04:00
swung0x48 c1960fdfaa [MG_IntegrationTest] (Disaggregated): prove real Magma queued state and present credit 2026-09-20 04:02:44 -04:00
swung0x48 38919d453b [MG_Backend, MG_IntegrationTest] (Disaggregated): rotate native Magma window blits through server-owned shaders 2026-09-20 03:06:26 -04:00
swung0x48 31dc1a7304 [CI, MG_Test] (Disaggregated): enforce real Magma buffer consumers and complete pixel coverage 2026-09-20 02:44:59 -04:00
swung0x48 c42577a42a [CI, MG_IntegrationTest] (Disaggregated): P5f close - reject incomplete or skipped exit evidence 2026-09-20 00:53:07 -04:00
swung0x48 4667e13be6 [Docs, CI] (Disaggregated): P5f wave 2 - record green integration and remaining phase gates 2026-09-19 23:26:46 -04:00
swung0x48 03620744c0 [CI] (Disaggregated): P5f fm - require named execution of the native format negative probe 2026-09-19 22:49:38 -04:00
swung0x48 911cc74a57 [MG_Backend, MG_Impl, MG_Remote, CI] (Disaggregated): P5f fm - connect family records and preserve GPU texture writes 2026-09-19 22:18:24 -04:00
swung0x48 cfcc12f73a [MG_Backend, MG_Impl, MG_Remote, MG_Test, MG_IntegrationTest, CI] (Disaggregated): P5f f1 - the dual-block rehearsal: per-role PipeInputs, the MOBILEGL_IPC_ROLE_SPLIT_STATE knob, and the named-Fatal arm that makes the red a census 2026-09-19 09:59:33 -04:00
swung0x48 f286548010 [MG_Pipe, MG_Impl, MG_IntegrationTest, CI, Docs] (Disaggregated): P5e ra2 - ID-133's escalation, ID-134's lane, and the contract sentence that could not be checked
- ID-133: escalation (iii) in MGPipeBarriered - a draw_vbo with NumDraws > 1 that is NOT
  kDrawIsIndirect is barriered. Espryt's indirect multi-draw tiers reach
  MultiDrawImpl::RunIndirect, whose apply reads the client's GL_DRAW_INDIRECT_BUFFER binding
  (BoundDrawIndirectBufferId, MultiDraw.cpp:87) - GetBufferBindingSlot, retiring in P8 - and
  unbarriered that is Fatal{UnmigratedPipeInput, "GetBufferBindingSlot@DrawArrays"} on 18 lane
  entries. Backtrace of the aborting thread, verbatim frames: CountBarrierPull <-
  MultiDrawImpl::RunIndirect <- MultiDrawImpl::DrawElementsBatch <- ServerVerbSink::OnDrawVbo
  <- PipeWireDecoder::DecodeAndApply <- PipeApplier::ApplyOne.
- TWO PREMISE CORRECTIONS, both measured, both in the report and in the contract. (a) There is
  no indirect multi-draw OP: all twenty draw entry points collapse onto draw_vbo, and the tier
  is resolved on the server per batch (ResolveTierForBatch), so the escalation cannot be keyed
  on the tier and its cost is NOT confined to the opt-in tier lanes - a plain glMultiDraw* now
  waits on the auto -> ext default too. NumDraws > 1 is the narrowest wire fact that contains
  the reaching set; kDrawIsIndirect is excluded so Sodium's indirect draw, which resolves by
  handle and pulls nothing, keeps running ahead. (b) The read is a SAVE/RESTORE of a GL binding
  name around the tier's own scratch buffer, not a data dependency - so it can be retired
  outright by giving BoundDrawIndirectBufferId the handle arm its neighbour
  ResolveBoundIndexBuffer already has, and this escalation withdrawn. That is three lines in
  MultiDraw.cpp, which ID-113 gives to mv, so it is reported rather than edited here.
- The client's half is verb-keyed because the fill decision is asked before the record exists:
  {MultiDrawArrays, MultiDrawElements, MultiDrawElementsBaseVertex} is exactly the set whose
  record can carry NumDraws > 1 without kDrawIsIndirect. The inclusion goes that way round on
  purpose - a record the server calls barriered whose fields the client did not fill would be
  an ADMITTED pull of a stale value, which is worse than the abort.
- ID-134: the two ClientVertexArrayScenario entries get the label integration-clientarrays-split
  and an expected-red CI step that asserts the MARKER, shaped like gl's Magma lane. ID-131 heeded
  by COUNTING, not by assuming: -L integration-split 181 -> 179, -L integration-gpu 1359 -> 1357,
  the new label selects exactly 2. The step branches on whether run-ahead is actually ARMED,
  because kMGPipeP5eRunAheadReady is a BUILD constant: on a head where it is false the refusal is
  unreachable and the entries are legitimately green, so an unconditional demand for red would
  fail for the one reason that is not a defect. The client says which world it is in in its own
  log line; delete the green branch when the flip lands for good.
- CONTRACT-P5E §3.5 amended to the code: "outside a barriered fill" rested on the caller's claim
  about the FUTURE, and a guard whose exemption cannot be false on the class it exists for is not
  a guard - it never fired on any of the 70 red entries. The landed rule is "not while the applier
  is inside, barriered fill or not", with the fill sites taking §2.5's forced wait first, twice
  per filling verb because the block is written either side of the emitters.
- AND §3.2 IS MARKED UNLANDED, which nobody had noticed: "the server's stamp is server-private
  … never into the shared block" describes storage the applier owns, and
  MGPipeServerStampVerbBoundary still writes m_filled / m_currentVerb / m_serverStampedVerb into
  gPipeInputs itself. That gap is what made the race possible. Splitting the stamp is the P11
  item - but per-role stamps WITHOUT versioning the BARRIER_PULLED values would be worse than
  today, because it turns a loud Fatal into a silent stale read.
- §2.1 carries escalation (iii) as a row, per its own rule that a new escalation is a ruling and
  a row in the contract rather than a condition at a call site.
- Gate on the flipped head: unit 2256/2256, isplit 179/179 HARD GREEN, gens rc=0, doc citations
  rc=0 (the three remaining are CONTRACT-P5.md's and pre-date this branch).
2026-09-18 13:56:51 -04:00
swung0x48 664cbf3ef8 [MG_IntegrationTest, CI, Docs] (Disaggregated): P5e - Magma's named-blit entries get a lane of their own
- Both DirectVulkan.Split.NamedBlit entries carried LABELS "integration-split" and abort under
  strict on GetFramebufferBindingSlot@Clear (VulkanRenderer.cpp:7648). P5e does not unbarrier that
  field on Magma - it retires there in P7 - so the Espryt lane could not go hard green with them
  in it.
- ARGUED RATHER THAN ASSUMED, because the brief allowed either reading: admitting the row instead
  is the WORSE option. FieldOwnership.def carries ONE retiring-phase string for both backends
  ("P5e (Espryt unbarriered), P7 (Magma)"), so a rule that admitted the pair for Magma would admit
  it for Espryt too - and an Espryt Clear reading the frontend's binding slot again is exactly the
  regression fb's handle arm exists to prevent. The lane split costs two entries; the admission
  would cost the gate.
- THE LABEL IS `integration-magma-split`, NOT `integration-split-magma`, and the spelling is
  load-bearing: `ctest -L` takes a REGEX, so `-L integration-split` matches a label spelled
  `integration-split-magma` as a substring. Measured with the obvious name first: both DirectVulkan
  entries were still listed by `ctest -N -L integration-split`, i.e. the split looked done in every
  listing and had not happened.
- Its CI step asserts the run is RED **and** that a private log carries the named marker, so
  "Magma is expected red" cannot decay into "Magma did not run". Measured: 0/2 under strict with
  two logs carrying Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@Clear"}, and 2/2 green
  without the knob. integration-split is 117 and integration-magma-split is 2.
- CONTRACT-P5E section 7 states the split and the reason it is not an allowlist entry.
2026-09-18 12:32:39 -04:00
swung0x48 bc6e24ff30 [MG_IntegrationTest, MG_Util, MG_Backend, CI, Docs] (Disaggregated): P5e ID-119/ID-115 - the lane reads every log, counts, and has a positive control
Three measured defects in the strict lane, one commit each in spirit and one in fact because
they are the same step.

1. THE LOG SET CAME FROM A DIRECTORY. The CI step grepped MG_IntegrationTest/split-logs/, which
   holds 90 of the lane's entries. The ones it could not see were the F1. readback block, both
   NamedBlit pairs, the four Ct. entries and PersistentMapArm - precisely the readback population
   the allowlist is ABOUT, plus the only DirectVulkan entries in the lane. split_log_paths.py
   gains a `markers` mode that walks ctest's own {entry: MOBILEGL_LOG_FILE_PATH} map and
   classifies every Fatal{ and Admitted{ line. Measured on this head: 113 of 113 entries read.
   The lane is now a TWO-SIDED RATCHET - an unadmitted marker fails, an admitted pair not in
   Harness/strict-expected-markers.txt fails, AND an expected pair that no longer appears fails,
   so the lane cannot rot green by outliving its own debts.

2. THE `rsp` CHECK GREPPED FOR A COUNTER NO SPLIT ENTRY EMITS, and the pin behind it was vacuous:
   CountBarrierPull's unbarriered arm is [[noreturn]] and runs BEFORE ++g_residualPulls, so
   "rsp = 0 on unbarriered records" was true of every possible implementation. Replaced by an
   assertion where the counter actually is - one entry, DirectGLES.Split.StrictArming., with
   MOBILEGL_PIPE_STATS=1, MOBILEGL_PIPE_STATS_PERIOD=1 and a private log path of its own (a
   whole-lane env flip races under -j: the library opens that path "w"). It asserts
   rsp == 0 || rsp >= draws, which is the device evidence's shape - rsp ~= the draw count under
   inproc, 0 under monolith - so the draw path's retirement is visible as rsp ceasing to scale.

3. THE LANE HAD NO POSITIVE CONTROL (ID-115). Of the seven entries that passed strict, three were
   monolith transport - where nothing stamps a verb boundary, so the whole mechanism is
   structurally unreachable - two self-skipped, one was a death test and one was a Python check.
   Not one was a record-carrying split GL scenario, so "green" and "strict was never armed" were
   the same observation. New push-only counter `vbs` (PipeStats::ServerVerbBoundaries), counted
   at MGPipeServerStampVerbBoundary behind Enabled(), and the same entry asserts vbs > 0 on a
   frame that drew. It is deliberately NOT rsp: rsp reaches zero when the phase SUCCEEDS, so a
   control built on it would start failing on the day the debt is paid.

- Measured on this head (base 7c6f6886, before pa and mv): isplit 119/119; under strict the
  census is Fatal{ GetProgramForDraw@DrawArrays 52, GetBoundVertexArray@DrawArrays 9,
  GetProgramForDispatch@DispatchCompute 7 - exactly what pa and mv owe - and Admitted{ on ten
  pairs, three of them ADMITTED-ESCALATED. The expected-marker file carries all ten and says in
  its header that the wave-3 merge retires three of them.
- CONTRACT-P5E §7 rewritten around what is now checkable: the derived allowlist and its three
  disjuncts, the two-sided ratchet, the restated rsp pin, and the positive control.
2026-09-18 12:30:10 -04:00
swung0x48 faf19a6aaa [MG_Pipe, MG_Test, CI] (Disaggregated): P5e ID-125 - admission gains the retiring-phase disjunct
- ID-116's static rule failed on a row no package in this phase may touch. pa retired the
  per-draw program pull, which unmasked GetTransformFeedbackProgram@DrawArrays (11 entries):
  BARRIER_PULLED, on kWaitNone draw_vbo, and ruled out of P5e entirely by CONTRACT-P5E section
  5.7, with a retiring phase of "P3b/P4b (Espryt), P7 (Magma)". The rule was wrong, not the code.
- A pull is now admitted if the field is BARRIER_PULLED, the verb has a stamp row, the field is
  in the verb class's may-read mask, and EITHER the verb's op is statically barriered OR the
  field's retiring phase does not name P5e. This table is only ever consulted on a record the
  server already stamped BARRIERED - CountBarrierPull's unbarriered arm is [[noreturn]] and
  fires first - so the pull is legal by construction and the only open question is whose debt it
  is. The retiring-phase column is exactly that answer and was already in the table.
- A verb with NO stamp row still admits nothing under either disjunct: the server stamps only
  MGP_VERB_OP_LIST's ops, so any other bit would be a row nothing can exercise. 111 -> 174 pairs.
- The phase column is matched as a TOKEN, not a substring, so a "P5" or a future "P5e2" cannot
  answer for "P5e".
- All six of ID-125's survivors checked: GetFramebufferBindingSlot@ReadPixels and
  GetTextureUnitObject@CopyTexImage2D by disjunct 1; GetTransformFeedbackProgram@DrawArrays
  (P3b/P4b), GetTextureObject@CopyImageSubData (P7) and ValidateProgramName@ShaderStorageBlockBinding
  (P9) by disjunct 2. GetTextureObject@CopyImageSubData holds by BOTH, asserted separately.
  GetProgramForDraw@DrawArrays, GetBoundVertexArray@DrawArrays and
  GetProgramForDispatch@DispatchCompute stay rejected.
- Magma's GetFramebufferBindingSlot@Clear is NOT admitted and cannot be: the retiring-phase
  column is one string for both backends and it names P5e for Espryt. The DirectVulkan entries
  get their own lane label instead (item 7), which keeps the Espryt allowlist about Espryt.
- StrictErrorsAlsoPromotesTheStickyForwards is RENAMED to
  StrictErrorsReachTheStickyForwardsAndNameThemByField and restated. Not one of the seven sticky
  forwards names P5e (P7, P7/P9, P7/P13, P9), so on a stamped verb they are all admitted now.
  The claim that still has teeth is that the detector REACHES them - asserted by rsp moving and
  by the marker naming the field and its phase, so "did not abort" cannot pass for "never
  detected". The Fatal half keeps its own case next door on GetBoundVertexArray, a P5e row.
- Six new derivation controls, including one that makes GetTransformFeedbackProgram this phase's
  debt and requires it to leave the unbarriered verb's allowlist - without which disjunct 2
  could be a constant `true` and every other control would still hold.
2026-09-18 12:12:08 -04:00
swung0x48 a211756aec [MG_Pipe, MG_Test, CI] (Disaggregated): P5e ID-116 - the section 7 allowlist is generated, not hand-kept
- ra landed a hand-kept 13-row copy of CONTRACT-P5E section 7 in .github/workflows/test.yml, and
  it was wrong in BOTH directions at once: it omitted GetFramebufferBindingSlot@ReadPixels (21
  entries, the lane's largest survivor) and carried GetTextureObject@CopyImageSubData, which
  resource_copy_region's kWaitNone makes a genuine unbarriered read of client memory. Neither
  error is visible by reading the list. Both are obvious once the list is derived.
- gen_pipe_field_ownership.py now joins the three tables ID-84 already states the rule in -
  kMGPipeFieldOwnership, kMGPipeWaitClasses (generated/PipeWire.inc) and kMGPipeClassFieldMask /
  kMGPipeVerbClass (generated/PipeFillPoints.inc) - and emits kMGPipeAdmittedPullMask plus
  MGPipeBarrierPullAdmitted(field, verb): 101 admitted pairs across 12 barriered verbs.
- The three inputs are READ, never restated, so the allowlist moves with the code: retiring a
  verb's wait class narrows it with no edit anywhere, and a new BARRIER_PULLED row widens it only
  on verbs that wait. A verb with no stamp row admits nothing - only MGP_VERB_OP_LIST's ops can
  ever be the record a pull is reported against.
- --print-admitted gives the CI shell the SAME table the C++ answers from, which is what makes
  the hand copy unnecessary rather than merely corrected.
- Three new negative controls (each source renamed away must stop the script - a missing source
  yields a silently EMPTY allowlist, which reads as rigour and is blindness) and five derivation
  controls that move an input and read the allowlist back.
- Red-once: dropping the wait-class conjunct from build_admitted fails the self-test by name -
  "3 admission-derivation control(s) did not hold: unbarriering ReadPixels' op removes every
  @ReadPixels pair; unbarriering ResourceCopyRegion removes every @CopyImageSubData pair; no pair
  is admitted on an unbarriered verb (DrawArrays)" - and the new unit case fails with
  "GetBoundVertexArray@DrawArrays is admitted on a verb no barriered op stamps".
2026-09-18 11:54:28 -04:00
swung0x48 6ee4c0979b [CI, Docs] (Disaggregated): P5e ra - the strict lane becomes one step with two arms, and §11/§14/§17 say what landed
- CONTRACT-P5E §7. integration-split-strict was an EXPECTED-RED control: every entry
  aborts on its first BARRIER-PULLED read, so the step gated on the run failing for
  that reason. After the flip it is a HARD GREEN GATE - an unbarriered record's pull is
  Fatal whatever the knob says (§3.3), so strict adds only the barriered rows, and those
  are a short named allowlist of <field>@<verb>. A revert of any handle arm in
  vi/sb/pg/tx2/fb then goes red HERE, by field and verb, instead of in a picture.
- THE ARM IS CHOSEN BY THE TREE. The step greps kMGPipeP5eRunAheadReady out of
  MG_Backend/Init.cpp, which is the one constant the integration commit flips and is
  exactly the fact the lane's expectation depends on. A lane whose flip is a separate
  human step is a lane that is wrong for as long as that step is outstanding - and the
  pre-flip arm is unchanged, so this commit moves no CI verdict today.
- The allowlist names the package that retires each row: readbacks and the pack PBO
  (P8/P9), the CopyTex endpoint and GenerateMipmap (tx2), set_storage_block_binding
  (pg), XFB (P3b/P4b/P9), resource_copy_region's GL names (the declared P5b debt). A
  row leaves it by deletion, never by widening. The lane also warns on a non-zero rsp,
  which after P5e can only be a barriered row.
- The allowlist is built with printf rather than a heredoc: a quoted heredoc's
  terminator has to start at column 0, which inside a YAML block scalar would end the
  scalar instead of the heredoc.
- ARCHITECTURE §11.6: the wait rule is the generated WaitClass column now, not the call
  class, and it REPLACES the BatchWaits skip; glFinish stops being a no-op under
  run-ahead; the forced waits (EGL forwarders, the map/readback family) and glGetError's
  named window are listed. §11.7: the P9 plan is code - the producer blocks, the drain
  clears AND rings, the client's parks break on the latch, and two named refusals remain
  for the impossible cases. §14: the present credit is client-minted, 1-based, one per
  swap, paid before the encode. §17.7: what P5e is, in one section - the predicate, rule
  F, gPipeInputs' ownership, the latch and the control arms.
- 附 A gains MOBILEGL_IPC_RUN_AHEAD and moves MOBILEGL_IPC_PRESENT_CREDIT out of the
  "P6+" line: a knob listed as not-yet-effective while it paces the frame is how an A/B
  stops being one.
2026-09-18 08:22:49 -04:00
swung0x48 a0865b00f4 [MG_State, MG_Backend, MG_Remote, MG_Impl, CI] (Disaggregated): P5c gt - the role guards close the phase
Layer 1 arms the texture object surfaces at MipmapStorage (the one point every concrete
texture class and view forwards through): AllocateStorage, TruncateMipmapLevels,
UpdateMipmapSubData, MapMipmapData, MarkStorageDirty(Region), IsStorageDirty,
GetStorageDirtyRegion(s) take Fatal{RoleViolation, texture-legacy-arm} from the apply
thread with an active transport. What the lane then exposed is fixed, not exempted: the
completeness gate answers from the staged store's defined-ness, the two logging reads go
through the descriptor macro, and the remint replay loop keeps its named
Fatal{UnmigratedEmulation} ahead of the guard. Magma's texture sync read path is the one
named exemption (MGPipeTextureLegacyArmScope, P7). Layer 2 wires InBarrierWait as the
client half of the gPipeInputs single-writer rule (RefusePipeInputsTouchWhileApplierOwnsIt:
applier owns it AND this thread not in a barrier wait => Fatal{RoleViolation,
gPipeInputs}; the barrier-off negative control is deferred to its own Fatal), and stale
reads of RECORD-SUPPLIED / APPLIER-DERIVED fields under a server stamp now report
RoleViolation with the field named. The RemoteGuards suite pins all nine texture surfaces
plus HasDefinedContent by name, and a negative control for the fill window. CI gains the
integration-split-strict job: unit lane as the hard gate under MOBILEGL_IPC_STRICT_ERRORS=1,
the scenario lane expected-red with the marker asserted (rewritable to plain green once P7
retires the object-class rows). rsp is per-frame in the stats line and measured on the
locally available traces (bsl in-world 948.5/frame avg over 123 frames, complementary
1591.9, iterationrp 286 until its named remint-pull Fatal); value-class pulls are zero by
rv's pinned list, what remains is the object-class fifteen.

Evidence: unit 2187/2187 (also under STRICT_ERRORS=1); integration-split 111/111; red-once
per layer (the texture guard and the gPipeInputs guard each turned their named death test
red and restored green). CONTRACT-P5C sections 5.4, 6.
2026-09-17 11:20:44 -04:00
swung0x48 6fa6a925bd [Fix] (CI): make split controls mandatory and isolate runner evidence 2026-09-16 12:34:33 -04:00
swung0x48 37fc4fdb0f [Merge] (MGPipe, P5): land x2 via x2m - the E2 draw-drop control, R-10 maxrec, the small-ring wrap counters (ID-65) 2026-09-16 11:32:23 -04:00
swung0x48 bd11ab189f [Merge] (MGPipe, P5): land x2 - the E2 draw-drop control, R-10 maxrec and the small-ring wrap counters, resolved against j0 (ID-65) 2026-09-16 11:30:12 -04:00
swung0x48 94ce2ee778 [CI] (apk): pin cmdline-tools and drop the removed 'tools' package so Setup Android SDK stops failing before the build 2026-09-16 11:28:37 -04:00
swung0x48 579118a1b3 [Test, CI] (scripts/ci, workflows): E2's draw-drop control requires the SSIM red AND the library's dropped-record count, E3(a) requires its private-log line as well as the pixels, and the smoke red-check finally perturbs the split control (ID-65) 2026-09-16 11:09:21 -04:00
swung0x48 68d8ef9f34 [Fix] (IntegrationTest): gate private split logs and repair control red-checks 2026-09-16 09:23:27 -04:00
swung0x48 0a22f363ff [Doc] (workflows): correct the two G5 self-test descriptions to b1 round 2 - the eleventh row is pinned in the script at 3dadd4c1 and compared against the reviewed body, p3a runs eight canned controls and p4a nine negative ones 2026-09-16 06:58:58 -04:00
swung0x48 2e2b41ab49 [Fix, Test] (workflows, scripts/ci): make the three CI negative controls assert their own failure reason instead of accepting any non-zero ctest exit, count the arming baseline by PASSED rather than by not-skipped, and put both control bodies in files a smoke test can drive against a stubbed ctest 2026-09-16 06:58:34 -04:00
swung0x48 bfaf5f9d0e [Merge] (MGPipe, P5): integrate package t1 2026-09-11 15:57:13 -04:00
swung0x48 949ed1f037 [Test, CI] (tools/trace_replay, workflows): give retrace-split the negative control it was cloned without, match ConfigLoader's whole INFO sentence rather than a KEY=VALUE an env dump also prints, read the three-arm names file the identity step was writing and discarding, and refuse an unknown manifest key that silently empties a CI matrix 2026-09-11 15:30:17 -04:00
swung0x48 3aa48bccdc [Test] (CI): run the field-ownership generator's --check and --self-test in pipe-gates - R-7.1 rests on a generated table that nothing in CI diffed, and the build assertion only catches an unclassified row 2026-09-11 14:53:39 -04:00
swung0x48 f265f0a8e1 [CI] (workflows): add build-linux-split, integration-split and retrace-split - the first configurations in this workflow that compile MG_Remote at all, each asserting by nm that the option really took 2026-09-11 14:00:14 -04:00
swung0x48 4714561f85 [Fix, CI] (Pipe): order the byte-identity listing, prove every protected region reaches its closing brace, and pin the include-closure probe count 2026-09-08 19:08:12 -04:00
swung0x48 0ba404caec [CI] (Pipe): gate that the unpack ring, the attachment permutation, the depth-stencil sampling core and the format caveat did not move 2026-09-08 19:08:12 -04:00
swung0x48 9ba5d7ba1e [CI] (scripts, test.yml, DirectGLES): hash FlushPendingRangesFrom as G5's eleventh row - a push build compiles no FlushPendingRangesNow, so the tenth row protected text the shipping build never sees 2026-09-08 06:54:25 -04:00
swung0x48 8d3cd67b94 [CI] (Pipe): select the P3a subsystem and map-persistent lanes in the only job that unpacks a push build 2026-09-08 06:02:36 -04:00
swung0x48 4412cef00c [CI] (Pipe): gate that the buffer pool, the deferred-release drain and the three rings did not move 2026-09-08 06:02:36 -04:00
swung0x48 b1c37699b1 [Fix] (Bench, Trace, CI): let only the profile answer for itself, name an unreadable profile, and describe the CI step by the mechanism the tree has
- The verified-profile guard read the process environment as well as the profile: the test ran
  after the source, so PROFILE_VERIFIED=1 exported in an operator's shell re-opened the fail-open
  hole for every profile that says nothing. Both scripts now set PROFILE_VERIFIED=0 immediately
  before sourcing, so the file is the only thing that can answer.
- A --device path that cannot be sourced was diagnosed as an unverified profile, because both
  scripts cd to their own directory first and neither checked readability. The path is now also
  tried relative to the directory the script was invoked from (which is what a repo-root-relative
  --device means), and an unreadable one is reported as unreadable, naming both places tried.
- Verified: exported PROFILE_VERIFIED=1 + an unverified profile -> rc 2; exported 1 + a profile
  with no key -> rc 2; a repo-root-relative path -> resolved, then refused for its own reason;
  a missing file -> "cannot read the device profile"; odinlite.env -> past the guard;
  --allow-unverified-profile -> the three warnings, then proceeds.
- test.yml's new step described a mechanism the tree does not have. G6's and G10's entries are
  registered in the pull build too - they must be, for G2's name-for-name comparison - and skip
  inside their bodies. The step's value is unchanged and its comment now says the true thing: the
  `test` job runs those names as a column of skips, and this is the first CI job that unpacks a
  build which compiled the assertions.
- trace_benchmark takes the wall baseline before the CPU baseline, the order OnFrameBoundary
  already reads them in, so frame 0 stops reporting a CPU delta biased upward against its own
  wall delta; and it includes <time.h> rather than <ctime> for the POSIX names it uses.
2026-09-07 23:18:10 -04:00
swung0x48 af20dba6db [Fix] (Trace, Bench, CI): compute p50 by the device's own median rule, fail the profile guard closed, and give the new control step its sibling's environment
- format_benchmark printed a p50 taken with the nearest-rank rule beside a medianFrameCpuMs the
  device computes as the average of the two middle frames, and documented the two as one rule; on
  an even window they differ (the pre-flight printed p50=8.261ms next to medianCpuMs=271.766).
  p50 now goes through series_median, which is SummarizeSeries' rule transcribed; p95 and p99 stay
  nearest rank, which is the device's rule for p95 and the honest extension of it for the p99 the
  device does not compute at all
- require_verified_profile treated a profile that simply omits PROFILE_VERIFIED as verified, which
  is the fail-open default a profile written by copying another one inherits - exactly the case the
  guard exists for. It defaults to unverified now, odinlite.env carries PROFILE_VERIFIED=1
  explicitly (it is the one profile that earned it), and the refusal says "says 0, or says nothing"
- the two new profiles claimed profile.sh refuses an unverified profile; it has no such check and
  needs none - it records a simpleperf profile and pins nothing. The claim is corrected in both
  profiles and in the README rather than a guard added where there is nothing to guard
- the handle-ABA / CSO control step in test.yml set only MOBILEGL_ITEST_REQUIRE_GPU while its
  sibling verify step sets the three MOBILEGL_MAGMA_* fixes and arms core dumps. It runs the same
  DirectVulkan binary on the same runner, so a crash there left no core; it now carries both
2026-09-07 23:18:09 -04:00
swung0x48 ce9f44a24c [CI] (Pipe): make the dirty-surface report a gate and run the push-only unit tests on the verify runtime
- pipe-gates stops printing gen_pipe_dirty_surface.py --summary and runs --check && --self-test.
  --check fails both directions - a scanned mutator with no row in MG_Pipe/DirtySurface.def, and
  a row naming a mutator the scan no longer finds - so a deleted mutator cannot leave a stale row
  behind claiming coverage. --self-test is what keeps --check honest: a completeness check that
  silently stopped checking is indistinguishable from a complete mapping, so two canned negative
  controls must both trip. Same shape as gen_pipe.py --self-test next to it.
- integration-verify gains `ctest -L unit`. G6's chunk-table walk and G10's residual assertions
  live in MG_Test/Pipe, compiled only under MOBILEGL_PIPE_PUSH, and the `test` job builds the
  PULL library - so before this those tests ran in no CI job at all. The artifact already carries
  them (the packaging step tars MobileGL/MG_Test whole), so the whole cost is the run: ~14 s for
  ~1490 entries, measured locally on this tree.
- integration-verify also runs the two always-on negative controls by name. They are labelled
  integration-gpu rather than integration-verify - they are about the handle key and the CSO
  switch, not the comparator - and this is the only CI job that unpacks a push build, which
  CsoContentAddressingScenario needs because both counters and the cso[] bracket are
  #if MOBILEGL_PIPE_PUSH.
- build-linux-verify's arming check accepts the per-verb entry point under either of its two
  names. P2 renames MGPipeFillForVerb to MGPipeValidateForVerb, and a check that named only the
  old one would go red on the rename for a reason unrelated to what it tests. What it tests is
  unchanged: the artifact has a per-verb entry point, and it still fails when there is none.
2026-09-07 23:18:09 -04:00
swung0x48 4826806881 [CI] (Workflows): run the test and apk lanes on every push to feat/disaggregated
- the MGPipe phases land as a series of pushes and each one needs the full lane; dispatching
  by hand after every merge is a step that gets forgotten
- both entries carry a remove-before-merging-to-dev note: dev's trigger set is what ships
2026-09-06 06:32:37 -04:00
swung0x48 97b997d5da [CI] (Pipe): read the verify library's static symtab, grep only the arming lane's log, and stop the two retrace lanes overwriting each other's evidence
- both `nm -D --defined-only ... | grep -q MGPipe...` gates could never pass: the library
  is built CXX_VISIBILITY_PRESET hidden in every non-Debug configuration and the MGPipe
  entry points carry no export attribute, so the dynamic table holds none of them (0 of
  11930 exported symbols on this tree, while `nm --defined-only` finds MGPipeFillForVerb
  as a local `t`). build-linux-verify, and with it integration-verify and every
  retrace-verify entry, would have been red forever for a reason unrelated to the
  comparator. Both now read the static table, name what a miss means, and refuse a
  stripped artifact instead of reporting its silence as a missing symbol
- "Every verify process really armed" grepped one shared per-lane log that holds only the
  LAST process of 406 - and the last ambient entry is the poison control's parent, which
  forks, execve()s and waits without ever issuing a verb, so the step would have red a
  healthy lane while proving nothing about the other 405. It now greps the two
  VerifyArming. lanes' own logs, requires one per backend, and says what that establishes
- remove-artifact-clutter kept fixtures for failed `retrace (` jobs only, so a failed
  `retrace verify (` case lost the fixture needed to reproduce it; both prefixes now count
- the retrace negative control replayed OpenRA into the same case directory, so
  "Upload actual image" shipped the deliberately corrupted run's output under the good
  run's name. The verified output is put aside and restored before the verdict
- build-linux-verify regains build-linux's "Show installed toolchain" step, and its
  deliberate divergence (Release even under ACTIONS_STEP_DEBUG - a Debug build would flip
  visibility and arm the poison through a different #if arm) is written down
- the lane's scope is stated where it is run: every integration ENTRY under the
  comparator, not every configuration - `integration`'s second
  MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1 pass (186 entries here) is not affordable at
  the 5-10x the comparator costs, so that tier stays covered unverified by `integration`
2026-09-06 04:41:28 -04:00
swung0x48 0fe7bf82d2 [CI] (Pipe): the third CI mode - a verify build, its integration and retrace lanes, and the two negative controls as always-on steps
- build-linux-verify is a second Release/INFO build with -DMOBILEGL_PIPE_VERIFY=ON,
  because the comparator is a compile-time option and does not exist in the shipped
  library. It refuses to ship an artifact whose libMobileGL.so does not export
  MGPipeVerifyInputs and MGPipeFillForVerb: a typo'd -D is not an error in CMake,
  and every lane below would then be green having compared nothing.
- integration-verify runs the suite with the comparator armed and then proves it
  armed twice over: --no-tests=error reds a build whose verify entries were never
  registered, and a step greps every pipe-verify-*.log for the arming line.
- The two negative controls are steps of that job, not a manual exercise: a gate
  that can only be shown to work by someone remembering to break it has already
  stopped working. Each passes when ctest FAILS, and each first counts its own
  selection - an empty selection also exits non-zero under --no-tests=error, and a
  control that passed because it ran nothing would be worse than no control.
- Control B targets PoisonOmissionScenario.WithoutOmissionCompletes, the only
  integration entry in the tree that calls glGenerateMipmap at all; the case no
  longer skips itself when the omission knob is set, precisely so that the control
  has a green entry to turn red.
- retrace-verify replays the eight "verify": true cases against the verify library,
  copied over build-linux/libMobileGL.so because build-retrace freezes that absolute
  path into every case, with an nm check that the swap happened and an inverted
  OpenRA step that must go red under MOBILEGL_PIPE_VERIFY_CORRUPT.
  remove-artifact-clutter now waits for it: it deletes the trace fixtures these jobs
  download.
- monolith-symbol-report is G1 as a job: two pull builds with identical flags and
  LTO off, the baseline named by a workflow_dispatch input, symbol_report.py with
  both hard gates, and a refusal of any MG_Remote symbol in the monolith. It is
  dispatch-only because its answer is about a baseline, not about this push.
- pipe-gates gains the two --self-test steps. Regenerating and diffing cannot see a
  structural check that silently stopped checking; a broken gate and a clean tree
  produce the same green. Its dirty-surface comment now says P2, which is where
  ROADMAP.md:18 puts the first mapping round.
2026-09-06 04:41:28 -04:00
swung0x48 087685d19b [CI] (Purity): install libx11-dev for the include-graph-check job
- Includes.h defines VK_USE_PLATFORM_XLIB_KHR before vulkan.h, so the clang-mode probes and
  the negative controls need X11/Xlib.h on the runner; run 34008829271 failed on exactly that
2026-09-05 23:27:10 -04:00