Parent: https://github.com/craft-native/craft/issues/393 Related owned-emulator cancellation source: https://github.com/craft-native/craft/issues/434
Observable defect
The maintained public craft.vulkan_surface.Surface.resize(160,120) returns 256x192 after a real Android SurfaceView resize. The callback, View measured size, SurfaceHolder frame, retained ANativeWindow and freshly acquired current-Surface window all report 160x120, and the native window identity remains the same. The shared consumer then rejects the actual extent mismatch.
Craft selects every non-sentinel currentExtent as mandatory. Android WSI defines it as the consumer preferred size and permits image extents within the surface min/max image bounds. AOSP obtains currentExtent from cached default dimensions, while live native-window dimensions query the producer. A controlled diagnostic retained the original failure: one legitimate 256x192 presentation plus complete GPU readback refreshed that cache, and one subsequent observation returned 160x120. This diagnostic is not an acceptance recovery and will not be retained as a retry.
Primary contracts:
- Android WSI
- Swapchain extent bounds, VUID 07781
- AOSP cached default dimensions
- AOSP queue-cache refresh
Owning source change
Select requested swapchain pixel dimensions clamped to the driver's min/max image extents. Fixed desktop min=max bounds still force the native desktop extent. Preserve all buffer/image limits, exact error propagation, creating-thread ownership and 1000ms command timeout. No polling, reconnect workaround or test-only resize retry.
Add the missing maintained Android Activity/JNI/SurfaceHolder consumer of the unchanged public source package. Exercise actual Choreographer frames, native control input, real View resize, Home/background and foreground surface recreation, and two complete Activity sessions.
Acceptance
- Pure extent-policy regression fails before the repair; fixed desktop bounds, flexible/stale preferred extents and lower/upper clamping pass afterward.
- Actual Android software-Vulkan run draws at least 370 GPU-checked frames across at least 4 real native sessions, with two native pointer events and complete disposal.
- Five actual compositor captures match every shared-scene pixel and independent literal atlas/triangle/glyph/changed-frame/input anchors; blank rendering cannot pass.
- Preserve 256x192 then 160x120 View extents, 61-frame/30-second stage limits and 1000ms native command budgets.
- Rerun the same strict candidate on Android 35 Google APIs revision 9 and default revision 2; retain truthful separate outcomes. The original default image's external-fence wait worker completes promptly but outer driver wait often returns about 1 second later, and remains a separate compatibility/qualification gate.
- Maintain CI invocation/path filters/original artifacts, exact source/compiler/NDK/Gradle/emulator/image/APK/library/capture identities and owned process cleanup.
- Run owner checks and independent review. Release changed source through
bun run release:patch, verify published artifacts and coordinate downstream adoption.
Retained initial evidence
Inspected baseline 31d8891; Android NDK 27.2.12479018, Gradle 8.11.1, pinned Zig 0.18.0-dev.146+35accc0, Android emulator 37.2.13 build 16740729, hardware CPU virtualization and software Vulkan llvmpipe LLVM 21.1.4.
Local original receipts: /tmp/craft-vulkan-android-acceptance/run-BvFcVv (all resize dimensions) and run-2uJmh0 (diagnostic queue/cache observation, deliberately failed). The canonical retained CI receipts and implementation/release SHAs will replace local-only evidence during delivery.
This issue is a narrow source/native-host slice. It does not establish physical GPU/device execution, full native game rendering, Gold fidelity or completion of parent #393.