Dynamic images and repaint
Stage 73 production source is frozen and necessary CPU/GPU and compilation checks passed. Matching Native/WASM/JNI, the same public host script, and the SDK archive passed local acceptance. Website HTTP/application Git are the final delivery steps; external platforms and formal publication remain incomplete.
map.addImage(id, image, options) takes onAdd(map, id), render(), and onRemove() in its second image argument. The third argument remains static image options. Callback this is the original image object. Only synchronous CPU RGBA callbacks are in scope; Promise, arbitrary GPU handles, and renderWithWebGL are unsupported.
An original fixture against official MapLibre GL JS 6.14.0 established that initial display uses the registered input snapshot: onAdd mutations are not immediately uploaded. Returning true from render submits pixels; false or undefined preserves the old display. True does not create continuous animation. Explicit map.triggerRepaint() requests the next frame. Multiple icon/pattern consumers call one image provider once per actual frame; unused/all-hidden images do not call it, and repeated repaint requests coalesce. updateImage retains the provider and updates its data.
Explicit removeImage calls onRemove. The reference did not invoke it for full/null style replacement or map.remove. The project still silently releases internal references and late tokens. Context loss differs from permanent removal: a restored provider resumes after onRemove without another onAdd. Checked pixels, failed frame candidates, and recovery follow an explicit project contract, without claiming rollback of arbitrary user JavaScript effects.
The shared core manages provider identity, actual demand batches, and pixel replies. Functions remain in their creating host. DLL/SO use the actual image JSON request/reply protocol below. The retained Native runtime, actual APP UI pump, SDK main/Worker, and web script host use the same algorithm. Hosts independently retain successful registration identities so interrupted onAdd still permits cleanup; local public-host and matching-artifact acceptance passed. One-shot scripts do not promise callbacks surviving completion.
Native QuickJS and an isolated script Worker can use actual interrupt/termination mechanisms. SDK main-thread user JavaScript cannot be forcibly preempted by that same thread: elapsed time can only be checked after return, followed by refusal of excessive results and owner cancellation. Post-call timing is not protection against an arbitrary infinite loop. This difference must follow each actual host connection.
Existing image/atlas and joint pattern limits are preserved: 128 demanded providers per actual frame, 16 MiB raw dirty pixels, one in-flight batch, and one coalesced pending repaint. Existing map limits remain 128 images, 512 pixels per edge, 1 MiB per image, 16 MiB total raw RGBA, and a 2048-square atlas. SDK scopes are limited to 32; whole-map JSON remains bounded at 8 MiB. External platforms/devices, mobile GIS/iOS, full style/vector/DEM support, formal publication, and deployment remain incomplete.
The frozen native host protocol reuses sqsm_map_image_json, its {method,args} envelope, and existing owned-output allocation/free rules. Atomic sqs.dynamicImage.add checks the initial image and provider binding together, returning opaque epoch, providerId, and imageInstanceId. Runtime function pointers never enter the map core. This is a one-pixel illustration; the actual fixture uses 32×32 RGBA.
{"method":"sqs.dynamicImage.add","args":["pulse",{"width":1,"height":1,"data":[0,255,0,255]},{},"host:1"]}
The host polls sqs.dynamicImage.requests for actual demand, receiving null or {epoch,token,frame,images}. Each image contains id, imageInstanceId, providerId, width, and height. Reading an unanswered batch again returns the same batch. After synchronous callbacks in its own runtime, the host submits sqs.dynamicImage.reply with {token,updates,errors}; each update is {id,imageInstanceId,image:{width,height,data}}. Pixels change only after the joint checked candidate succeeds. Late tokens return accepted:false; a replacement image with the same name never accepts the previous identity.
repaint coalesces requests for another frame; state exposes pending/inFlight. release accepts provider IDs, silently detaching them on script stop while retaining the last successful pixels. The existing register also requires the expected imageInstanceId, preventing delayed binding to a different same-name image. A 16 MiB raw dirty-pixel bound does not override the existing 8 MiB JSON request limit: encoding may reach the limit first and is rejected explicitly. epoch/token/imageInstanceId/providerId are strings; frame is a numeric observation. Match replies by token, not by a frame number treated as an exact identity. The public C ABI fixture passed against the matching library; evidence appears below.
flush({render:true}) continues to confirm one actual future frame. A finite animation subscribes before triggering and waits for real idle; flush does not drain an animation chain. External hosts draw when pendingRender or animation requires it. An explicit render call itself requests a new frame, so repeatedly forcing renders cannot prove automatic looping.
Context notifications describe image-provider callback lifetime. They do not claim that the entire map automatically rebuilds after an underlying wgpu device loss. Actual rendering-device recovery retains the map core’s existing supported boundary.
Execution limits: Native QuickJS has a 32 MiB heap and 512 KiB stack per VM, with a real two-second deadline shared by all VMs in a whole-map broker batch. APP tasks have a 32 MiB heap, 256 KiB stack, and at most two seconds per callback turn; tasks run concurrently, so this is not a two-second whole-map aggregate CPU claim. SDK frame replies have a two-second deadline; direct main-thread code is checked only after return. Browser script RPC has a two-second deadline and the existing five-second Worker watchdog terminates unresponsive owners. Existing 256 ordinary calls/32 pending RPCs remain unchanged; internal callbacks use a separate bounded channel.
Rejected replies drop the entire dirty candidate set, retain the last checked pixels, and terminate that batch without an automatic retry. A fresh explicit triggerRepaint permits recovery. Callback-requested repaint waits for an actual presentation receipt, never a UI-turn count.
Complete HTML/CSS/JS example · Same portable script. The matching SDK includes the raw C ABI example at native/examples/dynamic-images.py.
Shared fill/line patterns retain their existing 2..512 power-of-two edge constraint. Public fixtures use original 32×32 images.
Core ddfb4f29b73fa597bcf9c4f946b5e069dd58d669, development SDK 0.3.0-dev.ddfb4f29. Shared synchronous StyleImageInterface and triggerRepaint passed local public acceptance. The same 51-check/12-phase script passed Native, main-thread, and Worker at DPR1/2, with 216 observed frames. Actual C ABI calls checked the shared provider batch, twenty coalesced requests, rejected candidates preserving the last pixels and terminating the batch, explicit recovery, late replies, and replacement identity. Complete HTML passed four real buttons, five display phases, and an original ID73 click (15 frames). Comprehensive CPU/serial real GPU and necessary source checks are recorded in core-results.json; software GPU evidence is not device acceptance.
Archive SqsMap-SDK-0.3.0-dev.ddfb4f29-resumed.tar.gz: 620 inventory files plus bundle.json, 38104788 bytes, SHA256 55a1c058bdd73c3ae03db30c9691f920e71a9529019e1db6df38e95355558617. The tar was independently reopened to verify every size/SHA. Native, core/application/script WASM and Android API24 ARM64/JNI match this core, including the canonical JavaScript build input. Production HTTP and four-repository Git delivery are final steps; external Windows/macOS machines, phone devices, mobile GIS/iOS, full style/vector/DEM, formal publication, and deployment remain incomplete.
The shared capability is commonDynamicStyleImages. Scripts query await map.sqs.getCapabilities(); the standalone SDK also exposes map.capabilities. The actual APP UI path is connected and compiles. Local public Native acceptance uses the standalone library; it does not claim Windows/macOS desktop-window or phone-device acceptance.