QVAC-23489 feat[api]: add ABot-World interactive world sessions to the SDK - #3812
Draft
donriddo wants to merge 7 commits into
Draft
QVAC-23489 feat[api]: add ABot-World interactive world sessions to the SDK#3812donriddo wants to merge 7 commits into
donriddo wants to merge 7 commits into
Conversation
…e SDK Exposes @qvac/diffusion-cpp/world through the sdcpp-generation plugin as `modelConfig.mode: 'world'`, with two streaming operations: worldCreateScene builds a world from a prompt and a first frame, and worldStep walks it one generated block at a time, streaming decoded frames as they arrive. A walk session does not fit the plugin load contract as-is, so the plugin wraps it: - Activation is deferred when no scene pack exists yet, because the caller is about to build one. Supplying modelConfig.sceneSrc activates eagerly at loadModel so a bad pack fails fast like any other model. - Teardown waits for in-flight native work. The addon's unload() is synchronous down to a thread join, so entering it mid-job would block the worker's event loop — every model and every RPC on it — until that job finished on its own. - Scene packs are server-managed and ephemeral. They live at a path derived from a hash of the model id, so a caller-supplied id cannot steer the write, and are deleted on unload, on failed load, and on worker shutdown. Callers persist a world by keeping the bytes worldCreateScene returns and passing them back as sceneSrc. - Replacing a world stages the new pack and promotes it with an atomic rename, so a failed generation leaves the previous world intact rather than leaving the model with none. Cancellation follows the engine's real semantics rather than an idealised one. A walk cancel is block-granular: the current block finishes internally, delivery stops, and the step rejects instead of resolving with a silently truncated block, since the DiT has already committed that block to session history. Scene creation takes no abort predicate at all, so its concurrency slot is held until the native encode settles even when the caller disconnects. One job runs per model. A second step, or a scene creation arriving mid-walk, is rejected rather than queued: a walk is driven by live key input, so a backlog of stale keypresses is worse for the caller than a refusal it can drop. World sessions are bound to the worker holding the GPU. A delegated load with mode: 'world' is refused at loadModel rather than succeeding and failing on the first step, because the world operations have no delegated route.
…lk in e2e Registers worldStepStream and worldSceneStream with the operation-metrics profiler so a walk reports the same gauges every other streaming op does — without this the world ops are the only inference path invisible to profiling. Step gauges cover the per-block and cumulative timings plus frame counts; scene creation reports its encode time. Regenerates the Python client, which the contract change had made stale. The new wire schemas add their request, response and stats models to the generated bindings. Adds the SDK e2e definitions and a desktop executor for the walk, at the 448x256 low-VRAM tier so the tests fit the shared GPU desktop runners rather than needing the 20 GB the 832x480 tier does. Coverage: scene creation returning a safetensors pack, the 9-then-12 frame counts, frame dimensions, the W+L action mask, stepping before a world exists, invalid keys and dimensions, overlapping requests, and cancel-then-reload. The cancellation case accepts either the typed error or a clean resolve, because a cancel arriving after the block finished legitimately succeeds and asserting otherwise would flake. Mobile and Electron skip the walk, matching how both already skip diffusion: the model set is 13.3 GB and the session needs a dedicated GPU.
The executor declared no `pattern`, so BaseExecutor could not route any world- test to it, and its handler map used the wrong generic arity for HandlerFn/ExtractTest. Neither showed up until the e2e package's own dependencies were installed, since without them every file in that package fails to resolve @tetherto/qvac-test-suite and the real errors are buried.
Contributor
License compliance — findings detected (warn-only)Critical: 0 · High: 1 · Medium: 0
How to resolve a blocking finding:
Warn-only (shadow) mode — this check does not block merges yet. Updated automatically by the canonical license compliance workflow. NOTICE presence (advisory)Missing NOTICE (advisory, does not block):
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Contributor
Review StatusCurrent Status: ❌ PENDING Pending reviews: Needs 1 Management or Team Lead, and 1 more from Management, Team Lead, or Member. |
…ition real The test asserted that stepping fails before a world exists, but the world resource is shared through ensureLoaded, so an earlier test in the batch had already created one on that model and the step legitimately succeeded. It evicts first now — unload deletes the managed pack, so the reload is a genuinely world-less session rather than one that depends on test ordering. Found by running the suite: 7/8 before, 8/8 after.
The walk-key enum generates a member named `l` (keyboard "L"), which trips ruff's ambiguous-name rule and failed the sdk-python Generate + test job. The name is fixed by the RPC contract and cannot be renamed, and the file is generated rather than written, so the rule has nothing to act on. Scoped to _generated so E741 still applies to hand-written code, mirroring how E501 is handled for black-formatted output. Verified with the pinned toolchain the workflow uses (datamodel-code-generator 0.68.0, black 26.5.1, ruff 0.15.21): generate.py --check, ruff and black all clean.
Review found two of the eight tests passing for the wrong reason. world-concurrent-step-rejected matched errorContains: 'world', but the model id is abot-world-0-5b-lf-dit-q8_0.gguf, so almost any server error naming the model satisfied it — including "No world exists", a different failure entirely. It now matches the policy rejection specifically, and proves the first step really held the slot by requiring it to have delivered a full block. Admission is checked after the fact rather than before: world generates a whole block before emitting any frame, so waiting for one would release the slot and let the overlap through legitimately. world-cancel-then-reload discarded both the resolved value and the rejection, so it would have passed with cancel() removed. It now asserts which branch ran and fails on a rejection that is not a cancellation. Either branch is still accepted, because a cancel landing after the block finished legitimately resolves. Also: the idle block asserts actionMask 0, so a silent coercion to some default key set can no longer pass on frame count alone; scene creation asserts sceneCreateMs and the dimension round-trip, which the new profiler entry reads; the sceneCreateMs metric guard no longer drops a legitimate 0; and the two client-side validation tests declare dependency 'none' instead of dragging the 13.3 GB model set for assertions that reject before any RPC. Verified: 8/8 on GPU. The concurrency test failed under a badly-timed first attempt at the same fix, which is what showed the assertion now depends on the policy rather than on a substring.
Comment on lines
+10
to
+22
| import { | ||
| SCENE_HEIGHT, | ||
| SCENE_WIDTH, | ||
| worldTests, | ||
| worldCancelThenReload, | ||
| worldConcurrentStepRejected, | ||
| worldCreateSceneReturnsPack, | ||
| worldFirstBlockFrames, | ||
| worldInvalidDimensionsRejected, | ||
| worldInvalidKeyRejected, | ||
| worldSecondBlockFrames, | ||
| worldStepBeforeSceneFails | ||
| } from '../../world-tests.js' |
Prettier was not re-run after the last executor edit, so the SDK pod format check failed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎯 What problem does this PR solve?
@qvac/diffusion-cpp0.19.0 (addon QVAC-21981 feat[api]: ABot-World interactive world sessions (walk + native scene creation) #3352), but nothing in@qvac/sdkexposed it — the capability was reachable only byrequire('@qvac/diffusion-cpp/world')from a bare/Node app.load()reads a scene pack that may not exist yet, and its native teardown is synchronous down to a thread join.📝 How does it solve it?
modelConfig.mode: 'world'on the existing sdcpp-generation plugin (same addon package, following thevideo/upscalemode precedent), with two streaming ops:worldCreateScenebuilds a world from a prompt + first frame,worldStepwalks it one generated block at a time and streams frames as they decode.sceneSrcthe session activates atloadModelso a bad pack fails fast; without it, activation waits for the firstworldStepafter a world exists — because the caller is about to build one.unload()is synchronous down toprocessingThread_.join(), so entering it mid-job blocks the worker's event loop — every model and every RPC on it — until that job finishes. Measured: a mid-blockunloadModeltook 779 ms with a worst event-loop gap of 76 ms; entering the join would have stalled it for the full 779 ms.worldCreateScenereturns and passing them back assceneSrc. Replacement is staged and promoted with an atomicrename, so a failed generation leaves the previous world intact rather than leaving the model with none.mode: 'world'is refused atloadModelrather than succeeding and failing on the first step, since the world ops have no delegated route.🧪 How was it tested?
Static —
typecheck,lint --max-warnings=0,format,contract:export+contract:check, andtscoverpackages/sdk/e2e.Suites — unit 114/114 files; bare 131/131 tests / 523/523 asserts;
test:securityandtest:security:bare.New coverage
test/bare/sdcpp-world-ops.test.ts— drives the ops through an injected native session:run()follows the response to terminal state (not just dispatch), teardown is not entered while a job is tracked, abort never emitsdone: true, and the model slot is held until settle even when the consumer abandons the stream.test/bare/sdcpp-world-plugin.test.ts— companion resolution, world-only field rejection, eager vs deferred activation, staged replacement and rollback, ephemeral cleanup, and a traversal-shaped model id not steering the scene path.test/unit/world-{schemas,client,concurrency}.test.ts— config accept/reject matrix, key normalisation across array/object/mask forms, both client result factories including error paths, and the installed reject-on-overlap admission policy.Hardware (RTX 5080, 448x256 low-VRAM tier) —
loadModel→worldCreateScene→worldStepxN →unloadModel:Job cancelled; session reloaded and walked againSDK E2E (desktop consumer, RTX 5080, real models via the registry) — 8/8 passing.
npx qvac-test run:local:desktop --filter world, covering scene creation, first/second block frame counts, step-before-scene, invalid key and dimensions, overlap rejection and cancel-then-reload. Peak RSS across the batch 3,791 MB.Running them found a defect in the tests themselves:
world-step-before-scene-failspassed only when it happened to run before any world was created, because the resource is shared throughensureLoaded. It evicts first now, so the precondition is real rather than dependent on test ordering (7/8 → 8/8).🔌 API Changes
keysalso accepts a key-state object ({ W: true }) or a raw 8-bit mask, so a keyboard handler can pass its state straight through.📋 Open items
mac-mini-m4-gpu, addon lane), and the ABot set is 13.3 GB. A Darwin skip looks warranted, but the desktop consumer currently has no platform-conditional logic at all, so this would be the first — flagging rather than introducing that pattern unilaterally.completionStreamhas a delegated route today, so the mobile delegation guidance insdcppConfigSchemacannot work as written.