Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ removed no sooner than the next major (see `docs/API_STABILITY.md`).

## [Unreleased]

- Settle camera/sample startup promises after Stop, replacement, failure or the
preparation deadline even while camera acquisition or video playback remains
pending. Preserve late-response cleanup and replacement ownership; native
browser work is not cancelled. Add exact-deadline and real-browser controls.

- Reuse the shared native GPU runtime in renderer tests while keeping renderer
ownership and parallel execution. Add synchronized shared-runtime/independent
renderer coverage and three Linux CI processes that require actual GPU
Expand Down
30 changes: 30 additions & 0 deletions benchmarks/camera-startup-completion-plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"schema": "spatialrust.camera-startup-completion-plan.v1",
"slice": "146AQ",
"parent_merge": "fde55df103a72aaea0b557dccfe2557c83d11362",
"purpose": "Settle cancelled camera/sample start calls even when native acquisition or presenter startup remains pending, retaining late-resource cleanup and replacement-session ownership.",
"contract": {
"completion": "Each run owns a cancellation completion. Stop, failure and the unchanged startup deadline release the application start wait with false; successful startup returns true. Cancellation applies both before worker/camera readiness and while awaiting onReady.",
"ownership": "Native getUserMedia and video.play cannot be cancelled by settling the application wait. Their eventual completion/rejection remains observed; late acquired streams stop all tracks, and presenter epoch checks retain replacement ownership. No claim of native cancellation or physical memory bounds across restarts.",
"verification": "Reproduce parent start waits remaining unsettled after Stop with a ready worker and pending acquisition, and with pending video.play. Deterministic unit controls exercise Stop, exact startup timeout, failure, replacement, late success/rejection and synchronous failure. Browser controls hold real fake-camera acquisition or video playback, require application completion before release, and verify cleanup plus replacement sample progress. Retain all existing real WASM/model, geometry, capture/result/progress, CSP and privacy assertions.",
"preservation": "No Rust/core/GPU/dependency change or compilation. Preserve scientific JSON, frozen native outputs, prior camera and renderer evidence, resolved lockfile and release WASM. Reuse installed runtime/browser, matching bindgen and pinned vendor hardlinks."
},
"resources": {
"build_root": "E:/SpatialRustBuild/camera-startup-completion-v1",
"cargo_cache": "E:/SpatialRustBuild/maturity-80-target",
"build_max_bytes": 100663296,
"positive_cache_growth_max_bytes": 8388608,
"combined_new_and_positive_cache_growth_max_bytes": 109051904,
"minimum_free_bytes": 21474836480,
"workers": 1,
"command_timeout_seconds": 300,
"sampling_seconds": 5,
"asset_download_max_bytes": 0,
"sensor_downloads": 0,
"new_sensor_payload_copies": 0,
"strategy": "Fresh E: site with hash-checked vendor hardlinks and reused release WASM. All local command inputs are source-bound; preserve previous outputs and failures.",
"public_verification_stream_max_bytes": 33554432,
"asset_download_scope": "No new local SDK/model acquisition. Bounded public HTTPS verification streams without retaining vendor payloads."
},
"remaining_80_percent": "Canonical calibration/timing, independent real motion, full-room accuracy, qualifying physical Linux hardware and integrated operational acceptance remain open."
}
53 changes: 53 additions & 0 deletions crates/spatialrust-web/web/camera_controller.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,59 @@ function harness(acquire = async () => ({ getTracks: () => [] }), options = {})
return { session, workers, states, errors, results, send };
}


// Completion must not depend on releasing either native operation.
for (const phase of ["acquire", "presenter"]) for (const action of ["stop", "timeout", "failure", "replacement"]) {
test(`${action} settles startup while ${phase} stays pending`, async (t) => {
t.mock.timers.enable({ apis: ["setTimeout"] });
const gate = deferred(), entered = deferred();let stopped = 0;
const stream = { getTracks: () => [{ stop() { stopped++; } }] };
const h = harness(() => {
if (phase === "acquire") { entered.resolve();return gate.promise; }
return stream;
}, { onReady: (run) => { if (phase === "presenter" && run.mode === "camera") { entered.resolve();return gate.promise; } } });
const pending = h.session.start("camera");h.send(h.workers[0], "ready");await entered.promise;
let settled = false, value;pending.then((result) => { settled = true;value = result; });
t.mock.timers.tick(4999);await Promise.resolve();assert.equal(settled, false);
let replacement;
if (action === "stop") h.session.stop();
else if (action === "timeout") t.mock.timers.tick(1);
else if (action === "failure") h.send(h.workers[0], "error", { message: "startup failed" });
else { replacement = h.session.start("sample");h.send(h.workers[1], "ready");assert.equal(await replacement, true); }
await new Promise((resolve) => setImmediate(resolve));
assert.equal(settled, true, "cancelled start remained pending");assert.equal(value, false);
assert.equal(h.workers[0].terminated, true);assert.equal(h.workers[0].disposed, true);
assert.equal(h.errors.length, ["timeout", "failure"].includes(action) ? 1 : 0);
const current = h.session.run;gate.resolve(phase === "acquire" ? stream : undefined);
await new Promise((resolve) => setImmediate(resolve));
assert.equal(stopped, 1);assert.equal(h.session.run, current);assert.equal(await pending, false);
if (action === "replacement") { assert.equal(current.started, true);assert.equal(h.workers[1].terminated, false); }
else assert.equal(h.session.run, null);
h.session.stop();t.mock.timers.tick(30000);
assert.equal(h.errors.length, ["timeout", "failure"].includes(action) ? 1 : 0);
});
}

for (const phase of ["acquire", "presenter"]) test(`late ${phase} rejection is consumed after cancelled startup settles`, async () => {
const gate = deferred(), entered = deferred();
const h = harness(() => {
if (phase === "acquire") { entered.resolve();return gate.promise; }
return { getTracks: () => [] };
}, { onReady: (run) => { if (phase === "presenter" && run.mode === "camera") { entered.resolve();return gate.promise; } } });
const pending = h.session.start("camera");h.send(h.workers[0], "ready");await entered.promise;h.session.stop();
let settled = false;pending.then(() => { settled = true; });await new Promise((resolve) => setImmediate(resolve));assert.equal(settled, true);
const next = h.session.start("sample");h.send(h.workers[1], "ready");assert.equal(await next, true);
gate.reject(new Error("late native rejection"));await new Promise((resolve) => setImmediate(resolve));
assert.equal(h.errors.length, 0);assert.equal(h.session.run.started, true);assert.equal(h.workers[1].terminated, false);h.session.stop();
});

test("synchronous worker creation failure settles startup and allows a fresh run", async () => {
const h = harness(), create = h.session.createWorker;
h.session.createWorker = () => { throw new Error("worker creation failed"); };
assert.equal(await h.session.start("camera"), false);assert.equal(h.session.run, null);assert.equal(h.errors.length, 1);
h.session.createWorker = create;const next = h.session.start("sample");h.send(h.workers[0], "ready");assert.equal(await next, true);h.session.stop();
});

test("sample mode never requests a camera", async () => {
let requested=0;const h=harness(async () => { requested++; throw new Error("camera"); });
const p=h.session.start("sample",false);h.send(h.workers[0],"ready",{backend:"OFF"});assert.equal(await p,true);assert.equal(requested,0);h.session.stop();
Expand Down
5 changes: 5 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ Result timeout reuses cleanup and offers explicit restart without acquisition.
Each bitmap creation also owns a separate 5000ms wait deadline. Session Stop
aborts that wait; completion, rejection, abort and timeout clear its timer and
listener. A bitmap arriving after cancellation is closed without submission.
Start calls also observe a per-run cancellation completion through both worker/
camera readiness and presenter startup. Stop, failure and the unchanged startup
deadline return false without requiring a native acquisition/play promise to
settle. Late continuations remain observed and retain their epoch/track cleanup;
this does not cancel native browser work or dismiss permission prompts.
Native browser bitmap creation itself is not cancellable, so this policy does
not claim cancellation or a physical memory bound for unfinished native work.
This experimental browser boundary preserves core and vision algorithms; physical
Expand Down
35 changes: 35 additions & 0 deletions docs/MOBILE_CAMERA_DEMO.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ after awaiting `video.play()`. A late completion after Stop or replacement canno
restore the old canvas, label, size or backend. Stream/worker cleanup remains
owned by the existing session controller.

Each start also owns an application cancellation completion. Stop, startup
failure, replacement and the existing 45-second preparation deadline settle
`CameraSession.start()` with `false`, even when the worker is already ready and
camera permission or `video.play()` remains pending. Successful startup retains
`true`. Both readiness and presenter waits observe the same run cancellation;
settling the start releases its cancellation callback. Late native rejection
stays observed, and a late acquired stream stops every track without changing
the replacement session.

This settles the application promise; it does not cancel native `getUserMedia`
or `video.play()`, promise that the browser dismisses a permission prompt, or
bound physical memory for unfinished native work across repeated restarts.

During capture the page derives its processing size from current intrinsic video
dimensions. It retains a 256-pixel long edge and admits rounded short sides of at
least 64 pixels. Unsupported extreme ratios fail with an explanation instead of
Expand Down Expand Up @@ -454,3 +467,25 @@ runner's temporary directory with the workflow run ID and attempt in its name.
The fresh-output invariant remains enforced; previous cached evidence is kept.
Application code and the successful local geometry controls are unchanged by
this CI path correction.

## Startup completion evidence (146AQ)

The [startup receipt](receipts/2026-10-21_camera_startup_completion.json) binds
seven terminal local commands and both reproduced parent-browser failures. A
ready worker plus pending camera acquisition, and a pending native playback
completion, each left the old `start()` promise unsettled after Stop. The final
checks pass **77 JS**, **six asset** and **73 browser** controls. Eleven new unit
cases cover cancellation, the exact startup deadline, failure, replacement,
late success/rejection and synchronous creation failure. Browser checks hold
real fake-camera operations, require `false` before releasing them, and verify
late-track cleanup and replacement sample progress. All existing model,
geometry, capture/result/progress, timing and CSP checks remain.

The fixed contract is
`C:/Users/rsasa/Workspace/SpatialRust/benchmarks/camera-startup-completion-plan.json`.
Reuse the existing release WASM and **41,087,603** hard-linked vendor bytes,
with no Rust compilation or new SDK/model/sensor acquisition. Preserve **613**
prior camera/renderer files, 493 scientific JSON, 42 frozen native artifacts
and the resolved lockfile. Physical phone/Safari behavior, native operation
cancellation and operational 80% qualification remain open. Exact-source CI,
merge and the matching public Pages checks are verified after the local receipt.
1 change: 1 addition & 0 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,7 @@ evidence, and fail-closed acceptance gates live in
| 146AN | Complete implementation: bounded worker result ownership; physical qualification open | Give each camera/sample submission its own 15000ms result deadline, accept only matching session/frame results, clear resources on timeout and permit explicit restart | Both parent missing-result failures reproduced; 56 JS, six asset and 59 real WASM/model browser controls, including slow healthy input, duplicates, obsolete frames/sessions and restart. Reuse WASM/vendor assets and preserve all prior evidence; `docs/MOBILE_CAMERA_DEMO.md`. Physical phone/Safari, blocked-main-thread timing and operational 80% remain open |
| 146AO | Complete implementation: bounded bitmap capture wait; native cancellation open | Give each camera/sample bitmap request a 5000ms deadline, abort its wait on session Stop/failure and close eventual late bitmaps without submitting or changing a replacement capture | Both parent capture stalls reproduced; 66 JS, six asset and 69 real WASM/model browser controls. Verify exact timers, listener cleanup, late resolution/rejection, restart and explicit Stop; preserve prior evidence and reuse WASM/vendor assets. Native operation cancellation/memory bounds, physical phone/Safari and operational 80% remain open; `docs/MOBILE_CAMERA_DEMO.md` |
| 146AP | Complete implementation: shared native render test runtime; driver cause open | Reuse shared initialization while keeping renderer ownership and four-thread execution; require all GPU cases in three Linux processes without automatic retries | Windows parent five pass, candidate six pass in each of three processes, four GPU completion markers per process, related Clippy/base/format checks; preserve the transient inventory-stop record and all scientific/native/camera evidence. Verify exact-head Linux repeat logs before merge. Production constructors/transfers unchanged; prior SIGSEGV cause, physical Linux hardware and operational 80% remain open; `docs/RENDER_TEST_RUNTIME.md` |
| 146AQ | Complete implementation: cancelled startup completion; native cancellation open | Settle start calls with false on Stop, failure, replacement or preparation timeout through readiness and presenter waits; retain late native cleanup and replacement ownership | Both parent pending-start failures reproduced; 77 JS, six asset and 73 real WASM/model browser controls. Reuse WASM/vendor assets and preserve 613 prior camera/renderer files plus all frozen scientific/native evidence. Native operation cancellation, physical phone/Safari and operational 80% remain open; `docs/MOBILE_CAMERA_DEMO.md` |

The operational maturity target is 80% on the explicitly requested real-data,
multi-GPU/OS and public-release criteria. Historical engineering estimates in
Expand Down
Loading
Loading