setGlobals left the library's exports, but a test drives the page's
switches through it without a page (gexdev tests/page-switches). It is
now its own module, which app.js imports and the library does not
export.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
start()'s options, its handle, its errors, the Linux program's library,
and what is not the API (the test page's build option, the globals,
gex.mjs). The eject README's copy of the table, which had fallen behind
(no uid, wisp, pageHosts, glLazy, launch), now points here.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
A typo in an option name (onlog, contaner) was ignored, and a wrong
value (args as a string, size 'WxH' mistyped) failed later or not at
all. start() now rejects at once naming the option, with the nearest
name for a typo; resize() checks its size the same way. setGlobals is
no longer exported from the library: only start() and fromLaunch are.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The page-thread desktop (DomDesktop) used to read the whole scene and
reconcile it on every animation frame. Each frame built readScene's
records, a Set of live keys and a key string per surface. It now does
what the scene compositor already does: when no block has been claimed,
freed or published to since the last reconcile, every publish is acked,
nothing came from the page (a memory, a pixel buffer, an exit, a
resize), and every window got its pixels, the frame skips the reconcile.
A window whose pixels have not arrived yet keeps the next frame a full
one, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
Every net_status, net_avail, net_recv and net_peek used to be a round
trip to the page, once per socket per poll. Now each open stream gets a
receive ring in the network channel's shared memory (net-bridge.mjs
Rings, up to 64 slots of 256 KiB). The rings live in a growable
SharedArrayBuffer that starts empty and grows four rings at a time as
streams open, so a page whose guest never opens a socket holds none of
it. WispNetwork writes what arrives into the ring, keeps whatever does
not fit, and moves it in when a reader asks (REFILL). It also writes the
stream's state there. NetProxy answers status, poll, recv and peek from
the ring. A stream opened once every ring is taken, one already closed,
and every stream on an engine without growable shared memory still go
through the page. check-net-rings.mjs runs a WispNetwork on a stand-in
Wisp client against a NetProxy in a worker thread.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
An archive whose table sits at its end (a PBG4 .dat) reads its first
bytes and its last before the title. --rest-paths 'PATH HEAD tail' puts
the file's first HEAD MiB and its last, partial chunk in the bundle, and
the kernel holds both as fetched (tmpfs.rs seed_head), told apart from a
plain head by the copy's length.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
options.mjs holds each debug and tuning switch once: its start-message
name, global, URL key, environment variable and kind. Pages parse their
URL with pageOptions, runtime.js fills extra and opts with startOptions,
worker.mjs sets the globals with workerOptions and run-node.mjs reads the
environment with envOptions, in place of the copies each kept. A switch
is now added in one place. check-options.mjs follows every row from URL
or environment to a Worker's globals.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
GEX_RELAY_ALL (?relayall), GEX_EAGER_BLOCKS (?eagerblocks),
GEX_NO_MODULE_STORE, GEX_UNITS_EAGER, GEX_FORK_MODULES (?forkmods) and
GEX_PER_MISS (?permiss, the shim's gex_set_per_miss): nothing set them
but a person, and each kept a path besides the default alive. Small
modules are not relayed, dense modules wait for their first miss, the
module store is used where it can be, units are instantiated at their
first call, a fork hands its child its modules, and a miss takes up to 16
stored units.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
eject-site.mjs --rest-paths takes "PATH HEAD" lines (HEAD in MiB): the
file goes to the rest bundle whole, and its first HEAD MiB stay in the
bundle at its first-read place (site-bundle.mjs placeHeads), named in
lazy.json as the entry's sixth field. The page adds the lazy file over
the bundled head (gex.mjs), and the kernel takes a bundled copy that is
shorter than the lazy file and a whole number of chunks long as the
file's first chunks, already fetched (tmpfs.rs seed_head): what the boot
reads of a game's music, read at process start, comes from the bundle
and the rest bundle is not asked for it.
Tests: tests/eject/site-rest-heads.mjs; gex-kernel
a_bundled_head_seeds_a_lazy_file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
Since the compositor's channel has pixel buffers, WlProxy.publish copies
each surface's damaged rows into the block's buffer out of the memory it
reads the records from. hostx publishes the scene its Wayland server
module wrote into that module's memory, but the X windows' pixels are in
the X server's memory: every Xwayland window was copied from the wrong
memory, and drew black. The KDE desktop, and any X11 program in the
browser, showed nothing.
publish now takes the pixels' memory apart from the records' (the same by
default), and hostx passes the X server's. check-wl-pixels-memory.mjs
publishes a record from one memory whose pixels are in another.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The wine32 bundle's files go in the order the recorded run first read
them, so its download decodes in the order the start wants them; an
identical file goes with its first-read twin. --rest-paths FILE moves
read files to the rest bundle (a game's music, read by offset). The rest
options take values in the argument filter.
tests/eject/site-pack-order.mjs checks the order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
Every Worker loaded a zstd decoder before its kernel started, whatever
the site: an engine memory reservation each, on a site with no rest
bundle as well. A desktop session's Workers then ran out ("could not
allocate memory" as a program started, Konsole's shell among them).
The page now says when a lazy file is in a rest bundle, and only then
does a Worker without the lazy-file owner load a decoder.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
Each Worker loaded a zstd decoder of its own for the rest bundle before
its kernel started: an engine memory reservation per Worker. A desktop
session's Workers ran the page out of them, every decoder after that
failed to load, and no file in the rest bundle could be read (a program
started from the desktop never came up).
Where the page has its lazy-file owner (lazy-cache-worker.js, shared
memory and a Cache Storage that holds), a Worker asks it for a rest
bundle file as for any lazy file, and loads no decoder. The owner
fetches the frames by range, decodes them with its one decoder and
keeps the last few decoded, for every Worker. Without an owner, a
Worker decodes for itself as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The lazy files' list may give each a mode after its time; without it an
executable read on demand (a program only a later click starts) was
0644 and would not run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
(cherry picked from commit 8f929d92360f5ba46ee50de20f57b6aafd3f8e07)
The lazy files as one uncompressed tar, in read order then by path, its
stream cut into independent zstd frames of one size (--rest-frame MiB,
default 8) with an index (rest.bin.json); a lazy file is rest.bin#OFFSET,
read by fetching and decoding the frames over its range, the last few kept.
Untested end to end yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
(cherry picked from commit c71ddb40624f8af042754ee507b2cb6d83d3fcee)
A site's first bundle holds what the recorded run read up to its
first usable frame, in the order it read them, so the page can start on
the files it needs first. Each file's first use takes the next number of
one counter; read_set lists by it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
runtime.js fetches them as they are, the first time a program asks for a
64-bit memory; the page primes only the default runtime from pack.json, so a
packed copy of them was never read and the plain one was missing (a 404 at a
64-bit Wine page's first memory64 process).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
A recording whose loader is Wine's x86_64-unix one ejects as a wine64
project. It differs from a wine32 one in four ways:
- it ships the 64-bit NT trampolines' units, both the memory64 one and
the plain one, since a memory64 program runs beside Wine's own
processes;
- the runtime carries the memory64 shim and seed;
- the launch file says whether the page had the D3D device;
- resources default to whole.
The rest of the project, build and site is a wine32 one's.
Unit keys are matched for either guest memory width (rsrc key_with_mem,
the ABI hash per width), so identities and resource rekeying find
memory64 units. The launch file carries the bundle's user xattrs (the
memory64 mark on a program), which the site's rebuilt bundle drops; the
app library sets them with the tree's own. The recorder reads a Worker's
relayed lines ("[gex-console-error] ..."), so uncovered addresses are
no longer lost, and the session records the page's d3d.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The host map crosses postMessage as a Map; run-node parses
GEX_PAGE_HOSTS with page-host.mjs's own pageHosts, and says network and
tcp apart. Nothing after a request that is not answered (a POST, a bad
one) is read; a HEAD of a body of unknown length says no length. The
Node lane's files are streamed, one byte range if asked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
start({pageHosts: {host: path}}) names hosts the page answers itself,
instead of the network proxy, which cannot reach the page's own origin. A
guest speaks plain HTTP/1.1 to one: GET and HEAD, keep-alive and pipelined
requests, with each body streamed from fetch. Anything else, a POST or a
WebSocket upgrade, gets 405 and the connection ends. An ejected page can
serve its guest any files this way, such as a package repository beside it.
shims/browser/js/page-host.mjs is the stream, shaped like a Wisp client's.
WispNetwork holds one for a page-served host, and needs no proxy when there
are only those. So do a Worker's own network and the system Worker's. The
single-thread build shares the page's. NodeNetwork does the same for
run-node's GEX_PAGE_HOSTS=name=DIR,..., and with no GEX_NETWORK it refuses
every other host. check-page-host.mjs tests the HTTP side.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The channel carried a 64 MiB SharedArrayBuffer for each of its 32
blocks, made on the page whether a block was ever claimed: 2 GiB of the
page's data size for the few MiB a session's surfaces fill. Each guest's
proxy now makes its own buffer on its first surface with pixels, sized to
what its surfaces need with a quarter to spare, and a larger one when they
outgrow it (up to 64 MiB), posting each to the compositor ('wl-pixels').
A record names its buffer by number; the compositor draws a surface once
that buffer has come, drops the one it replaces, and drops a process's
buffers when it exits. Only the damage is copied when the buffer has the
surface's last commit, so two threads publishing one process's surfaces
each copy whole what the other published. The single-thread build is
unchanged: it has no pixel buffers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
A D3D program's calls reach the page as a stream of records through the GL
device's ring (its own magic and version). With GEX_MESA_DIRK=1,
web/build-mesa-wasm.sh fetches dxvk at Mesa's pinned commit
(third_party/mesa/subprojects/dxvk) and links its D3D11/DXGI front end on a
gex_wgpu screen into the replay (web/mesa/gexd3d-mesa.cpp). A present files
the frame under the client's window like a GL client's: the compositor shows
it, newest first, and the frame report counts D3D presents and how far behind
a window is. d3d=1 on the page turns the device on; a window with no surface
shows nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
Every animation frame read the whole scene (readScene's records, a Map
and Set of surfaces), reconciled it, and built the output's signature
string before finding that nothing had changed. A frame now first checks,
against what the last acked frame drew from, each block's token and
generation and whether its publishes are acked, whether a message came
from the page, and whether a GL frame was presented; with none of them it
returns at once. A surface whose pixels could not be read yet keeps the
next frame a full one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
compositor.js and dom-compositor.js each had the evdev key map, the
modifier bits and the button codes, and the copies had drifted: the scene
compositor dropped the back and forward buttons. Both now import
input-map.js. A back or forward press on the scene's canvas no longer lets
its release navigate the page's history.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The per-unit `[unit]` and `[cold]` lines and a link group's hold are
`GEX_LOG=unit` output; the page's start-up marks, the runtime rings and
Mesa's load time are `GEX_LOG=debug`. The fill's end and its late units
are still said by default. The wait census, whose report is debug output,
no longer runs a timer in every Worker otherwise.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
services.mjs was never wired to anything. The kernel import list was a
fallback for a caller without a shim Module, and every caller passes one.
#deliverFault, #guestView, DomDesktop.glStats and
MesaGlDevice.surfaceGeneration have no callers, and the per-block export
loop served a module shape no emitter produces.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The repository that builds the wasm32 Debian system and the desktop demo's
guest side is named debdemo, not gex-debian. The READMEs, the web build's
messages and the comments say so; where they named a script inside it,
they name the repository.
No key inputs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
V8 counts every wasm memory an isolate holds at its whole length in the
size its next full collection is scheduled from. Past about 8 GiB the
headroom stops growing, and the isolate collects every few megabytes it
allocates. The compositor worker held every guest process's memory to
read wl_shm pixels in place: a memory64 game's alone, a Wine process's
at about 2 GiB each, and the global kernel's. Between them it made a
full collection 25 times a second.
Each arena block now has a fixed SharedArrayBuffer for pixels. A plain
SAB is not counted, and writeTexture refuses a growable one. The guest's
WlProxy copies each shm surface's damaged rows into it as it publishes,
and nothing for a commit already copied. It then points the record's
address and stride there. The compositor (both kinds) reads only that
buffer and keeps no memory when the channel has pixel buffers.
wl-memory carries no process memory when the channel has pixel buffers,
so a process's threads no longer re-send it one per worker. The global
kernel's memory goes once a page (an arena flag). The compositor needed
the kernel only to notify a process's wait queue after an input event.
It now marks the block in the arena and wakes a relay on the page
thread, which holds the kernel anyway and does the same notify.
The single-thread build has no pixel buffers and works as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The NT launch's removal left its last line, the NT site's summary, at the
end of the file. It was never reached while wine32Site() was followed by an
exit; without that exit it ran after every site, and its names were gone
(ReferenceError: build is not defined), so eject-site.mjs failed after
writing a complete site.
No key inputs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
Comments named the test harness gex is developed with (its gate and bench
runner) and one still said a site's kind is w3; they say what they mean
without it.
No key inputs. wine32-dist.sh is in TOOLS_KEY and build-mesa-wasm.sh in
Mesa's stamp: the dist and Mesa rebuild.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The server took its xkb directories and xkbcomp's from the build prefix,
which put the build host's paths in Xwayland.wasm; they are fixed guest
paths now (the server reads only the keymap hostx.js writes, and runs no
xkbcomp). gexhostwl's panic messages named the tree's source paths; it is
built with the shims' --remap-path-prefix.
No key inputs. The script is in build-all.sh's Xwayland stamp, so both
Xwayland flavours rebuild.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598