Commit Graph
4054 Commits
Author SHA1 Message Date
swung0x48 7e76a98d36 [Fix] (DirectVulkan): keep one renderer per session across its surfaces, follow bind_context to each context's surface, and keep the wire resource ops registered while any session lives 2026-10-02 06:24:13 -04:00
swung0x48 15dbe78ce6 [Fix] (DirectVulkan): give each served session its own renderer and Magma state 2026-10-02 05:52:38 -04:00
swung0x48 168808d1a7 [Fix] (DirectVulkan): advertise the GLSL 1.00-era ARB strings KWin gates on, and let a debug property pick the Android backend 2026-10-02 05:42:14 -04:00
swung0x48 75e1da4d1b [Docs] (Anland): record the lock-screen corruption root cause 2026-10-02 05:07:59 -04:00
swung0x48 cd610934d7 [Fix] (DirectGLES): scope object deaths to their session, and keep container objects and staging rings per native context 2026-10-02 05:07:59 -04:00
swung0x48 335637af38 [Docs] (Anland): record the GLX, Chrome and server-config fixes 2026-10-02 03:09:18 -04:00
swung0x48 4236b42a57 [Fix] (Server+Transpiler): configure the server once per process, and enable blend_func_extended for index-1 outputs 2026-10-02 03:09:17 -04:00
swung0x48 0b6842ac8b [Fix] (EGL+GL): let ANGLE's GL backend run on MobileGL (GBM displays, foreign wl_display, precision query, ES-free GL_VERSION) 2026-10-02 02:58:55 -04:00
swung0x48 47db68d557 [Feature] (GLX): serve as a glvnd GLX vendor and present remote X windows 2026-10-02 02:11:36 -04:00
swung0x48 ce11c9accd [Docs] (Anland): record the Chrome/dma-buf feedback fix 2026-10-02 01:45:03 -04:00
swung0x48 7bb9d9829f [Docs] (Anland): record the Plasma desktop milestone and the remaining issues 2026-10-02 01:38:57 -04:00
swung0x48 ccf8eb6571 [Fix] (Client): serialize multi-threaded GL streams, follow Wayland window resizes, export the KHR image aliases 2026-10-02 01:38:21 -04:00
swung0x48 0b314ddae7 [Fix] (P14): serialize sessions on one backend turn and give each client surface its own native surface 2026-10-02 01:38:20 -04:00
swung0x48 d5acece005 [Docs] (Anland): record the Plasma bring-up state after the P14 server-state fixes 2026-10-02 00:04:01 -04:00
swung0x48 30fef23d52 [Fix] (P14): scope the server staged stores and the context-live flag to the calling session 2026-10-02 00:04:00 -04:00
swung0x48 cbb87b968a [Docs] (Anland): drop the stale-glslang note and record d554c87c and the landed tooling 2026-10-01 23:36:27 -04:00
swung0x48 d554c87c45 [Fix] (Loader+Texture): tolerate drivers without EXT swap-with-damage, and pair stencil-only storage correctly
- eglSwapBuffersWithDamageEXT is an extension entry point that Android's
  framework libEGL does not export through dlsym; requiring it was a
  bring-up FATAL on TB321FU. Resolve EXT, fall back to the KHR spelling
  (same function, interchangeable signature), then to plain
  eglSwapBuffers (a zero-rectangle damage call is defined to be exactly
  that). The GLES dlsym fallback gains a self-reference guard for
  MobileGL installed under a GLES soname, where dlsym would otherwise
  hand the backend the frontend's own entry points and recurse forever.
- NormalizePixelFormat maps GL_STENCIL_INDEX8/GL_STENCIL_INDEX to
  GL_STENCIL_INDEX/GL_UNSIGNED_BYTE instead of falling through to
  RGBA/UBYTE, which no driver can pair with stencil-only storage.
- Tests pin the EXT->KHR->none preference order and the stencil pair.
- The anland handoff is rewritten to the post-P14 landed state: device
  evidence (win-smoke green screen, Konsole on screen, eglinfo/glxinfo),
  the remote-only recovery procedure, and the known gaps (Plasma session
  bring-up, cursor scale, the no-CPU-blit requirement, resize/surface
  loss, AVC audit).
2026-10-01 23:16:33 -04:00
swung0x48 7a48521d37 [Fix] (DirectGLES): destroy stale window surfaces before creating a new one
A session that dies without its teardown (crash, SIGKILLed client) leaks
its native window surface; the server-owned display window is one
ANativeWindow for the process's lifetime, and Adreno refuses a second
eglCreateWindowSurface on it with EGL_BAD_NATIVE_WINDOW - on TB321FU this
made every session after the first die with 'MobileGL could not acquire
the app Surface 300b'. InitWindowSurface now finds and destroys any
surface still bound to the window it is about to use, and the session
registry records the window with each surface to make that possible.
2026-10-01 22:32:53 -04:00
swung0x48 6946ff3a5c [Docs] (Anland): refresh the handoff to the post-P14 landed state
The document still described the state before the state-ownership work
landed: it called the unified server design unfinished, listed the
KernelSU grant as unsolved, said the directory bind had not taken effect,
and presented MobileGLAppsService plus the per-app fork worker as pending
drafts. None of that is true now.

Rewritten against the current tree and the device:

- header: architecture landed per the user's correction, acceptance still
  open; the rejected fork-worker/AppsService path is removed and the
  unified server lives in the MobileGL library (design 11-state-ownership,
  commits d52c031c / 4ed1c9ce / 5c6399e2 / ff212061 / 5a0f13fd, plus the
  landed 6fb880cd / ebc552a0 that used to be listed as uncommitted)
- MobileGL: HEAD moved to 5a0f13fd; the state-ownership section now records
  what S1-S6 landed and the debts that remain (process-level unit shadows,
  g_backendContextGeneration, per-record ContextSlot, the glIsBuffer probe,
  the window half of pbuffer->window)
- anland: 2ffa1f5 and 33e496c pushed; the draft inventory is now a landed
  inventory plus the three uncommitted KWin-acceptance fixes
- device: KernelSU grant resolved (reinstall changed the uid to 10270), the
  clone restarted with the directory bind effective, the 5a0f13fd APK
  serving @anland-mobilegl with six completed client sessions, and the
  client library installed with a stale build stamp
- new: concurrency warning (parallel sessions have overwritten the dist and
  the source tar), the current desktop-session.service failure state, and a
  rewritten continuation order

Verified: doc citations resolve (scripts/check_doc_citations.py), device
facts checked against HA27Q3LQ at refresh time.
2026-10-01 20:41:54 -04:00
swung0x48 5a0f13fdfa [Fix] (DirectGLES): advertise the GLSL 1.00-era ARB strings KWin gates on
GL_ARB_shader_objects/vertex_shader/fragment_shader and
GL_ARB_texture_non_power_of_two are core since GL 2.0 and trivially true
for the 4.6 this backend claims, but KWin 6's EglContext::checkSupported()
requires exactly these strings before it brings up compositing. Found on
TB321FU: kwin_wayland --anland failed with 'Could not initialize rendering
context' after a successful context create + makeCurrent on the
server-owned window surface.
2026-10-01 20:21:35 -04:00
swung0x48 ff2120616d [Feature] (P14): key the backend twin tables by session and share group
The last piece of state ownership this phase owed: the eight DirectGLES twin
tables resolved a handle process-wide, so two contexts of one session that do
NOT share a share group - which have independent name spaces, so the same
{slot, gen} in both is legal - were handed the SAME twin, driver ids,
persistent map and pooled store included. Two contexts that DO share a group
were served by whichever of them twinned first. Design and per-slice shape:
docs/Disaggregated/design/11-state-ownership.md (S6).

Key: DirectGLES.h's TwinKey{SessionKey, ShareGroupKey} - the same two words
the applier's object records are filed under, with the group derived by
MGPipeApplierShareGroupKeyFor so a record and the twin describing it can never
land in different buckets. Session is in the key because a twin names a driver
id and no driver id crosses a connection.

Resolution: SessionRuntime.cpp's ResolveThreadTwinKey, installed by
InstallSessionRuntimeHooks beside the S2 PipeInputs probe, the S4 native
tuple key and the S5 applier key. It reads the ThreadSessionScope's session
and CurrentContextToken() - apply-thread state only, never MG_State, so the
frontend-keyed-lookup prohibition on the apply thread still holds. A thread
that belongs to no session answers {0, 0}, which is the process singleton this
replaces; the monolith and every unit case are unchanged by construction.

Shape: SlotTables.h's SlotTableRegistry holds one SharedPtr bucket per key,
with a thread-local last-key memo. It forwards every handle-keyed entry -
GetOrCreate, FindByHandle, LiveGenAt, HandleOf, StateForHandle,
NoteStateForHandle, ReleaseByHandle, ForEachLive, the counters - to the
CALLING THREAD's bucket, so the ~150 call sites in Managers.cpp and
DirectGLES.cpp are literally unchanged. The holder list stays process-wide: a
death notice is about an object, not a bucket, and each shared holder is one
bucket. ForEachLive now walks the calling thread's bucket (what a context can
see is its own group); the death and diagnostic walks take the new
ForEachBucket.

DropEveryTwinForEndedServerSession is session-scoped: it runs on the apply
thread of the session that is ending, while S2 lets neighbour sessions keep
rendering in the same process. No session on the thread keeps the old
process-wide drop verbatim. Managers.h's "a texture is owned by exactly one
context (share groups are not implemented)" is corrected: a texture belongs to
a share group and its sibling contexts legitimately mutate it.

Tests: MG_Test/Wire/TwinOwnershipTest.cpp, five cases driving the real twin
tables with no EGL and no wire - the same handle in two nonshared groups is
two twins and neither overwrites the other; two contexts of one group resolve
ONE twin; a per-session drop takes only that session's; a thread with no
session still gets the process-wide table; and the six-registry template
repeats the first three. Negative control run and reverted: making the
registry answer a single {0, 0} bucket turns four of the five red.

Full host ctest against a stashed baseline, compared by test name: 0 new
failures. The one name in the difference set
(LogForwardChannel.APeerThatStopsReadingCostsWarnLinesNotTheCallersTime) is a
pre-existing flake, failing on the baseline too with the identical assertion.
The 103-case acceptance set plus the five new cases is 108/108 green.

Remaining debts (documented): the unit shadows (g_activeTextureUnit,
g_boundTexturesCache, g_boundSamplersCache) are still process-global; the
driver-level glIsBuffer probe is still not usable on this host, so sharing is
proven at the twin layer (the twin IS the object holding the driver id); the
backend context/sync generations are still process-wide; the per-record
ContextSlot is still S1's edge declaration.
2026-10-01 20:11:53 -04:00
swung0x48 5c6399e240 [Docs] (Design): record S5 landed shape and remaining twin-key debt 2026-10-01 19:23:57 -04:00
swung0x48 4ed1c9cef2 [Feature] (P14): state ownership - multi-session, multi-context, share groups
Breaks the one process = one server = one session = one context equation
that the whole stack's correctness used to rest on. Design and per-slice
shape: docs/Disaggregated/design/11-state-ownership.md.

S1 wire: bind_context ring record (opcode 84) announces the current
context at the make-current edge; CreateContext/DestroyContext join the
SurfaceOp control frames; control revision 5 -> 6; the server keeps a
per-session token table and refuses unknown tokens by name.

S2 server: SessionRuntime per session with thread-scoped resolution
(singletons become fallbacks), per-thread gPipeInputs, per-session latch
domains, per-thread segment resolvers; the in-process unix supervisor
serves up to MOBILEGL_IPC_INPROC_MAX_SESSIONS (16) concurrent sessions;
surface mode is declared per session and the display lease refuses a
second on-screen session by name.

S3 frontend: ShareGroupState owns the shared object stores and name
generators (buffer/texture/renderbuffer/sampler/program), GLContext keeps
per-context bindings/errors/container objects; pGLContext becomes
thread-local with a process default context preserving the single-context
world verbatim; eglCreateContext builds a real GLContext and joins the
sharing context's group.

S4 backend: native EGL tuples keyed by (session, context token);
InitDisplayAndContext's unconditional teardown is gone - creating a
second context or surface no longer destroys the first; eglCreateContext
sharing is wired to the native driver; bind_context is the native switch
point.

S5 applier: MGPipeApplierState splits into MGPipeObjectRecords (per
share group) and MGPipeWorkingState (per context), resolved through a
{session, token} registry with a thread-local cached fast path.

Tests: MultiSessionTest (incl. two sessions x two contexts interleaved
with real clear/readback witnesses), ApplierOwnershipTest (with a
null-resolver negative control), ContextStateOwnershipTest,
ServerLoopEglTest native-context liveness; 99/99 of the acceptance set
green on the host build.

Known debts (documented in the design doc): twin registries still keyed
by handle alone, no per-record ContextSlot, process-global backend
generations, MakeCurrent dedup per current binding.
2026-10-01 19:16:41 -04:00
swung0x48 ebc552a048 [Fix] (WSI): report Wayland present failure and keep GLX drawables off XIDs
Wayland Present() failure now returns EGL_BAD_SURFACE instead of being
dropped. In spawn mode a GLX X drawable is backed by a remote pbuffer so
the XID is never mistaken for an Android ANativeWindow; X11 pixel present
and remote resize are still unimplemented.
2026-10-01 13:45:52 -04:00
swung0x48 6fb880cdf2 [Build] (Linux): alias libGL.so and libGL.so.1 to libMobileGL
GLX clients link against libGL; give the Linux build the same alias
treatment libEGL already had so glvnd-less clients can load MobileGL.
2026-10-01 13:45:51 -04:00
swung0x48 d52c031c50 [Docs] (Design): state ownership for multi-session/context/share-group (P14)
The whole stack assumes one process = one server = one session = one
context = one share group. The anland unified server needs that equation
broken in the library instead of fork-per-app workers. This document fixes
the four ownership layers (session, context, share group, thread current),
the wire changes (per-record context slot, CreateContext/DestroyContext
SurfaceOps, context-tagged reverse events), and the staged plan P14a-d
with acceptance tests.
2026-10-01 13:45:41 -04:00
swung0x48 4156ca6e36 [Docs] (Anland): hand off unified runtime work and record corrected state ownership requirements 2026-10-01 13:18:23 -04:00
swung0x48 2f64ede8c6 [Feature] (Remote): serve concurrent shared-memory clients in isolated workers 2026-10-01 12:53:27 -04:00
swung0x48 a5b463a5e8 [Feature] (Remote): expose a package-neutral embedded display server API 2026-10-01 12:31:57 -04:00
BZLZHH 32c79f4135 [Feature] (EGL): Wayland window surfaces, presented through wl_shm
A Wayland client's window is its own wl_surface, and what the compositor shows is the wl_buffer
attached to it - but the pixels of a MobileGL frame are drawn where the backend is, on a split
build in another process on another OS.  So far a wl_egl_window was taken for an X11 window and
its client-side address sent to the Android server (EGL_BAD_NATIVE_WINDOW at best), and under
glvnd a Wayland display never reached this vendor at all.

A window on a Wayland display - asked for as EGL_PLATFORM_WAYLAND, or a wl_display recognised by
its first word being wl_display_interface - is now a window surface in the EGL state at the
wl_egl_window's size with a pbuffer of that size behind it in the backend, whatever
MOBILEGL_IPC_SURFACE says.  At every swap the frame is read back out of the default framebuffer
through this library's own glReadPixels (the application's read framebuffer, pack buffer and pack
state put back as they were), its rows flipped and swizzled into a wl_shm buffer, and attached,
damaged and committed on the application's wl_surface.  Three buffers rotate on their release
events, dispatched on an event queue of the surface's own.  libwayland-client is loaded at run
time, so neither the build nor the library depends on it.  EGL_KHR_platform_wayland and
EGL_EXT_platform_wayland are announced on Linux.

Measured: glmark2-wayland runs its build scene at 13 FPS (400x300) and the window shows the frame
under kwin.  A window resize is not followed yet; the surface keeps its creation size.
2026-10-01 12:25:20 -04:00
gold f458924360 [Fix] (EGL): a second display is served, and glvnd gets its answers from one table
Two defects, both found by asking the client directly rather than by reading its log.

The backend refused to initialize any EGLDisplay but the first - "already bound to a different
EGLDisplay" - and the field it compared against has no reader anywhere else in the tree: the domain
rule was the whole of the rule.  What it cost was a compositor, which asks for the display it will
draw into AFTER probing another one.  The probe succeeded, the real display was refused, and the
caller reported the refusal one layer up as "no usable DRM render device" - a message that names
where it stopped, not why.

The second: glvnd asks a vendor for entry points from two places.  getProcAddress is how it builds
its own static EGL table, and getDispatchAddress is how it answers eglGetProcAddress for every name
it does not know itself - which is every extension entry point, the device queries among them.  This
side answered the second from a GL-only table, so an application asking for eglQueryDeviceStringEXT
got a null for a function the vendor implements.
2026-10-01 12:22:55 -04:00
gold 0dad3fcaa1 [Feature] (EGL): MobileGL serves EGL through libglvnd
On a system with libglvnd the libEGL.so.1 an application links is a dispatcher, not an
implementation, and the supported way to be the implementation it talks to is to be a vendor
library it loads and routes to.  On this machine it is also the ONLY way that reaches a caller which
does not link EGL directly: kwin goes through libepoxy, which dlopen()s libEGL.so.1 and dlsyms its
entry points at startup, so a library injected with LD_PRELOAD is invisible to it however loudly it
is named - measured, with the client in kwin's maps and not one call into it.

So this is the vendor half of that ABI: __egl_Main answers glvnd with the entry points by name, the
platforms this implementation serves, and the device it is associated with - the render node of the
machine, because that is what lets a compositor match its own node to the display it is about to
draw into, and because glvnd drops a vendor that does not answer at all.

Two details are the difference between a vendor that is loaded and a vendor that is used.  glvnd
requires setDispatchIndex to exist even when a vendor has nothing to record, which this one has -
its entry points are the implementation, they never re-enter glvnd.  And a vendor must hand over ITS
OWN entry points: with default visibility a reference inside this library resolves against the whole
process, where another libEGL that loaded first wins.  That is what -Bsymbolic-functions is for;
declaring the entry points protected says the same thing but does not compile against the platform
headers, which declare them default.
2026-10-01 12:22:55 -04:00
gold 75f83b20fc [Feature] (EGL): the platform and device extensions are one list, spelled once
A vendor string and an eglQueryString answer have to agree, and they are read by two different
parties: the system's libglvnd reads the first to decide which vendor a platform request should be
routed to, and an application reads the second before it decides to make that request at all.  Kept
apart they drift, and the drift has a signature - a library that is loaded, initialized, and never
called, while the caller reports that it found no provider for a function it never reached.

The list gains the device family for the same reason: a compositor asks which devices exist and
reads the DRM node off one before it asks for a display on it, so an implementation that answers
"no devices" is one it cannot use no matter how well the implementation draws.
2026-10-01 12:22:54 -04:00
gold 57781688ce [Chore] (glvnd): vendor the EGL vendor ABI headers
Two MIT headers from libglvnd, verbatim, because MobileGL is about to implement the vendor half of
glvnd's EGL ABI and the distribution ships the runtime without them.
2026-10-01 12:22:54 -04:00
swung0x48 7308668488 [Docs] (Anland): kgsl-as-server.md - read with the kernel as the server, routes A and B are about who owns the buffer (6.x + Mesa is A, 5.x is B), plus the split-level analogy to virtio-gpu native context vs virgl / venus; linked from the README 2026-09-30 13:00:11 -04:00
swung0x48 134791b4b9 [Docs] (Anland): compare today's container Mesa (freedreno / turnip over kgsl) with routes A and B - it is route A's no-change dma-buf tier with the renderer in the app process, Anland's dma-buf ingress was shaped around it, and 5.x's private protocol sat between A and B 2026-09-30 12:45:16 -04:00
swung0x48 c24926cafe [Docs] (Anland): add route B - Anland hands the window's Surface to the MobileGL server (the FCL shape: SC mode leaves the parent Surface's queue free, waylandbridge itself receives it over binder), with its MobileGL items B1-B4, the Anland extension, its limits, and the phasing reordered around it 2026-09-30 12:24:24 -04:00
swung0x48 c5fa722be7 [Docs] (Anland): correct the window model - a toplevel attaches on demand to at most one Android window and several can be attached at once (popups and subsurfaces composite into the parent); P12 does not fit because the Android window and its input belong to Anland, not because of the window count 2026-09-30 12:17:56 -04:00
swung0x48 b7aa8d2011 [Docs] (Anland): research note - what MobileGL and Anland would each need for MobileGL to render Anland's Linux apps (glibc split client in the container, bionic render server, native AHB ingress into waylandbridge), with the two source-audit reports, their briefs and the notes index entry 2026-09-30 11:57:57 -04:00
swung0x48 378020adf3 [Fix] (MG_Remote, P11 T0): the T0 POST also checks what the CPU writes through the held lock after the import and its first GPU use, in both directions - Mali-G1-Ultra passed the old single round (the import cleans the CPU cache) while its GPU saw 0/1024 post-import words, so T0 was granted and create-indirect-in-world-align1024 x Magma lost every Flywheel object on inproc/spawn (0.867/0.848); it now fails there and runs T2 (0.999984), and the Redmi passes both rounds and keeps T0 2026-09-30 10:21:46 -04:00
swung0x48 028cf4eb62 [Merge] (trace): take dev's create-indirect-in-world-align1024 fixture (a derived trace replacing the original create-indirect capture) and register its documentation-only derived_from key 2026-09-30 10:21:46 -04:00
swung0x48 08124c99f1 [Test] (trace): replace the create-indirect fixture with minecraft-1.21.1-neoforge-create-indirect-in-world-align1024, a derived trace and not the original capture - the desktop capture bound Flywheel's scatter lists at 16-byte SSBO offsets that Adreno 830 and Mali's Vulkan driver (64) reject, and fixture_tools/realign_buffer_ranges.cpp moves the 1,270 binds off a 1024-byte boundary onto 1024-aligned slots (3,812 calls inserted, target call 504631 -> 508443, host SSIM unchanged to 9 digits on both backends) 2026-09-30 10:00:26 -04:00
swung0x48 648800a2da [Docs] (P8): close the phase - ruling ID-P8-19 (the exit device check and the P12 cross-host rerun), the P8 README and PLAN, the roadmap row and header, MEASUREMENTS row 19, the status page and the last debt 2026-09-30 08:25:38 -04:00
swung0x48 b4bee9dd38 [Docs] (P8): CONTRACT-P8 (the rules P8 landed, each with its code anchor and its test), rulings ID-P8-14..18 (MD + MF and the create-indirect fixture's alignment, SE, SV, the dev merge, the wave 2 integration gate) and the debts wave 2 hands on 2026-09-30 07:55:50 -04:00
swung0x48 494305381e [Merge] (P8): integrate wave 2 package MF - the push flush ladder gets dev's Adreno 830 queued-ring-copy guard (FlushPendingRangesFrom, create-instancing x Espryt x monolith 0.870 -> 0.99998 on the Redmi), and both G5 scripts pin the two reviewed ladder bodies under ID-P8-14 2026-09-30 07:12:50 -04:00
swung0x48 fb106a682e [Docs] (P8-MF): the package note - the push-ladder mirror of dev f973008c, create-instancing Espryt monolith 0.870 -> 0.99998 on the Redmi with spawn unchanged, why spawn was unaffected (the same two partial maps behind queued ring copies, the copy already submitted by the server's intervening whole-buffer maps), the red-onces and the gate 2026-09-30 07:09:28 -04:00
swung0x48 c8c4397475 [Merge] (P8): integrate wave 2 package SV - Magma's 3D native mip blit uses one layer, the depth/stencil mip decline gets its tally row, 1D depth mips decline by name, and the wire arm issues glDispatchComputeIndirect natively
# Conflicts:
#	MobileGL/MG_IntegrationTest/CMakeLists.txt
2026-09-30 06:11:04 -04:00
swung0x48 9c24a14d2b [CI] (scripts, P8-MF): pin G5's tenth row as well and re-pin both flush ladders on the reviewed post-fix bodies (ID-P8-14) - dev f973008c moved FlushPendingRangesNow past every base ref CI passes (the Adreno 830 queued ring copy) and ac9ab078 mirrors it into FlushPendingRangesFrom; both scripts' self-tests now run the pin-precedence and one-token controls on both ladders 2026-09-30 05:45:10 -04:00
swung0x48 ac9ab078cc [Fix] (Espryt, P8-MF): the push arm's flush ladder keeps a partial range-invalidating map behind an upload-ring copy still queued on the store, as dev f973008c's pull arm does - FlushPendingRangesFrom's ring tier stamps ringCopyRetireSerial and InvalidateFlushAccessFor refuses tier 1 for a partial range while that copy is unretired (the whole-buffer orphan-map stays exempt); Adreno 830 loses the queued copy and create-instancing on the monolith arm drew terrain sections empty 2026-09-30 05:43:55 -04:00
swung0x48 377d90d9f1 [Merge] (P8): integrate wave 2 package SE - the spawn/tcp default framebuffer publishes its surface to the client, the CPU mip arm declines on a driver-written base, D's case 3 runs on the Espryt split arms, the unmigrated-emulation name list is linted against its call sites, and the P8 notes' citations point at their content again 2026-09-30 05:24:11 -04:00