findings(E-5,E-6): validation-round fixes — live e2e falsified two ported shapes

E-5: the module-identity bridge called installingModule._malloc, but this
build exposes _malloc only as a bare glue-closure export (Module._malloc is
absent) — every char/stat event entry threw TypeError, which also starved
the E-6 credit window (thrown dispatches never acked) and wedged the queued
bg-finished frame behind them. The bridge now uses the bare closure exports
(identity is still exact: the EM_JS body IS the installing module's closure;
the __ngspiceOnEvent self-disarm covers supersession).

E-6 (codex reference design corrected — its validation matrix never ran):
a FULL credit window was terminal (stopEventStream at 64 in-flight frames).
Under live e2e that killed a real simulation: bg-thread emissions proxy one
per task, so each line ships as its own frame and a normal transient outruns
a busy main thread. A full window now DEFERS into a bounded FIFO (512 events
/ 4 MiB) drained in order as acks free credit; only true overload or an
invalid ack is terminal. Retention stays bounded (8 MiB in flight + 4 MiB
deferred + 1 MiB open batch). And the service/harness mirror queue now acks
at ENQUEUE — placing a frame in the bounded pre-handler queue is taking
ownership; without that, a stream starting before the C++ handler installs
(the ngspice-probe page) starves the worker window forever.

Test updates: worker-batch reducer — new "a full credit window defers and
drains in order, never terminal" case pinning the regression; the storm case
now proves the deferred caps are the terminal edge. board-ready.ts gains the
owner-free openBoardProgrammatically (codex helper the ported occ-export
spec needs; the barrier-based waitForUiBoardReady was NOT taken).
occ-export.spec: domId is optional on this line's registry (coordinate
fallback is the supported path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Istvan Matejcsok 2026-08-19 17:53:11 +02:00
commit 06a46546cc
7 changed files with 180 additions and 21 deletions

View file

@ -68,8 +68,12 @@ async function findWxButton(page: Page, label: string): Promise<WxButtonTarget |
const el = registry.findAll({ visible: true })
.find((e) => (e.label === wanted || e.label === `&${wanted}`)
&& (e.typeName ?? '').includes('Button'));
// domId is present only for DOM-backed controls on lines that expose
// it; this line's registry may omit it — the coordinate fallback in
// clickWxButtonTarget is the supported path then.
const domId = (el as { domId?: number } | undefined)?.domId;
return el
? { x: el.centerX, y: el.centerY, domId: el.domId && el.domId > 0 ? el.domId : null }
? { x: el.centerX, y: el.centerY, domId: domId && domId > 0 ? domId : null }
: null;
}, label);
}
@ -256,8 +260,11 @@ test.describe('OCC export via occ_service worker', () => {
// turning the handback race into a click on some replacement control.
const retryExport = await findWxButton(page, 'Export');
expect(retryExport, 'the original parent Export button must remain registered').not.toBeNull();
expect(retryExport?.domId,
'the retry must target a stable DOM-backed wx button').toBeGreaterThan(0);
// On this line the export dialog's buttons may be canvas-rendered
// (domId null); clickWxButtonTarget's coordinate fallback is the
// supported path, so only the captured geometry must be sane.
expect(retryExport!.x, 'the retry target has stable geometry').toBeGreaterThan(0);
expect(retryExport!.y, 'the retry target has stable geometry').toBeGreaterThan(0);
expect(await clickWxButton(page, 'OK'), 'dismiss native export failure').toBe(true);