tiny.sdfii.Engine
Defined in tiny.sdfii.
API (128)
Actions
Public operations.
CurrentPublishedScene.ptrUiCapacity.deriveactiveWorldactiveWorldConstadvanceUiInputapplyPlayBackbindAcousticExecutorbindCurrentScenebindShapeAssetbindShapeParambindUiRootToInputSessioncaptureLastFramecaptureWorldclearFrameHistoryclearRenderOutputcommandSinkcommitShapeAssetReloadcurrentPublishedScenecurrentQueryRevisiondeinitenterPlayexitPlayframeHistoryAtframeHistoryCountframeHistoryLatestframeStepinitinitFallibleinstallRenderPaletteinvalidateSceneDerivationsinvalidateWorldBoundaryDerivationsisCompileDirtyplayWorldpreflightPhysicsStepprepareShapeAssetReloadpublishAudioCommandpublishInputCommandpublishPhysicsSteppublishScenepublishSceneEditpublishUiCommandpublishUiSurfacereadAudioAssetreadAudioEmitterAcousticsreadAudioListenerPosereadAudioOutputreadAudioPlaybackStatusreadAudioRevisionreadAudioRuntimereadAudioTransportreadBehaviorRuntimereadClientInputreadPhysicsRuntimereadPhysicsScratchreadPointerRayreadRenderFramereadRenderRuntimereadSceneAuthoringGapreadSceneBackgroundreadSceneCamerareadSceneDefaultMaterialreadSceneEditPublishedreadSceneLightCountreadSceneMetadatareadSceneRuntimereadSceneSettingsreadUiActivationEventsreadUiPublishedFramereadUiStorageStatusreadUiSurfaceStatereadUiTextEditEventsreadUiTextPointEventsrestoreWorldsceneEditRevisionsetRenderOptionssetRenderStyleunbindUiRootFromInputSessionupsertEntityMaterialworldStep
Types and contracts
Public types and contracts.
AcousticExecutorBindingAudioAssetAudioAssetKindAudioCommandAudioEmitterAcousticsSnapshotAudioListenerPoseAudioOutputAudioOutputKindAudioPlaybackModeAudioPlaybackSnapshotAudioPlaybackStateAudioRuntimeSnapshotAudioTransportBehaviorRuntimeSnapshotClientInputSnapshotCurrentPublishedSceneDIRTY_COL_EDITSDIRTY_COL_MATERIALDIRTY_COL_SCENE_RESOURCEDIRTY_COL_SHAPEHandlerRegistryInputCommandLimitsPhysicsRuntimeSnapshotPhysicsScratchCapacityPhysicsScratchLimitsPhysicsScratchStatusPhysicsStepContextPointerRayRenderFrameRgba8StateRenderFrameViewRenderRuntimeSnapshotSceneAuthoringGapScenePublicationSceneRuntimeSnapshotUiActivationEventUiCapacityUiCommandUiLimitsUiPointerEventKindUiPublishedFrameUiStorageExhaustionUiStorageStatusUiSurfaceUiSurfaceStateUiSurfaceTreeUiTextEditEventUiTextPointEventUiTextSelectionUpdate
Source
Source: fun/sdfii/src/engine/system/ui/command.zig:20
pub const ActivationEvent = struct { sequence: u64, frame_index: u64, root_id: u64, widget_id: u64, action: []const u8,};Source: fun/sdfii/src/engine/system/ui/command.zig:101
pub const Command = union(enum) { publish_surface: SurfacePublish, publish_native_surface: NativeSurfacePublish, retract_surface: u64, pointer_event: PointerEvent, scroll_event: ScrollEvent, key_event: KeyEvent, text_event: TextEvent, text_selection_event: TextSelectionUpdate,};Source: fun/sdfii/src/engine/system/ui/command.zig:8
pub const PointerEventKind = enum(u8) { move, down, up,};Source: fun/sdfii/src/engine/system/ui/command.zig:28
pub const TextEditEvent = struct { sequence: u64, frame_index: u64, root_id: u64, widget_id: u64, kind: TextEditKind, text: []const u8,};Source: fun/sdfii/src/engine/system/ui/command.zig:37
pub const TextPointEvent = struct { sequence: u64, frame_index: u64, root_id: u64, widget_id: u64, kind: PointerEventKind, x: f32, y: f32, local_x: f32, local_y: f32, anchor_x: f32, anchor_y: f32, anchor_local_x: f32, anchor_local_y: f32,};Source: fun/sdfii/src/engine/system/ui/command.zig:53
pub const TextSelectionUpdate = struct { root_id: u64, widget_id: u64, cursor_visible: bool, cursor_byte_offset: usize, selection_active: bool, selection_anchor_byte_offset: usize, selection_focus_byte_offset: usize,};Source: fun/sdfii/src/engine/system/ui/ownership/capacity.zig:49
pub const Capacity = struct { surfaces: usize, placements: usize, surface: surface.Capacity, entries_offset: usize, entries_bytes: usize, placements_offset: usize, placements_bytes: usize, clone_offset: usize, clone_stride: usize, clone_bytes: usize, storage_bytes: usize, pub fn derive(limits: Limits) DeriveError!Capacity { if (limits.surfaces == 0) return error.InvalidSurfaceLimit; const placement_count = std.math.add(usize, limits.surfaces, 1) catch return error.CapacityOverflow; const surface_capacity = try surface.Capacity.derive(limits.surface); const entries = try placed(Entry, 0, limits.surfaces); const placements = try placed(surface.Placement, entries.end, placement_count); const clone_offset = try aligned(placements.end, surface.storage_alignment); const clone_stride = try aligned(surface_capacity.storage_bytes, surface.storage_alignment); const clone_bytes = std.math.mul(usize, placement_count, clone_stride) catch return error.CapacityOverflow; const storage_bytes = std.math.add(usize, clone_offset, clone_bytes) catch return error.CapacityOverflow; return .{ .surfaces = limits.surfaces, .placements = placement_count, .surface = surface_capacity, .entries_offset = entries.start, .entries_bytes = entries.bytes, .placements_offset = placements.start, .placements_bytes = placements.bytes, .clone_offset = clone_offset, .clone_stride = clone_stride, .clone_bytes = clone_bytes, .storage_bytes = storage_bytes, }; }};Source: fun/sdfii/src/engine/system/ui/ownership/capacity.zig:27
pub const Limits = struct { surfaces: usize = default_surface_count, surface: surface.Limits = .{ .nodes = default_nodes_per_surface, .runs = default_runs_per_surface, .styles = default_styles_per_surface, .images = default_images_per_surface, .image_pixels = default_image_pixels_per_surface, .semantic_bytes = default_semantic_bytes_per_surface, },};Source: fun/sdfii/src/engine/system/ui/ownership/capacity.zig:14
pub const SurfaceState = struct { revision: u64, tree: *const gui.model.UiSurfaceTree, images: gui.model.UiImageSet,};Source: fun/sdfii/src/engine/system/ui/ownership/store.zig:12
pub const Status = struct { capacity: capacity_mod.Capacity, surface_count: usize, free_surfaces: usize, free_placements: usize,};Source: fun/sdfii/src/engine/system/ui/ownership/store.zig:10
pub const StoreError = surface.CloneError || error{SurfaceStoreFull};Source: fun/sdfii/src/engine/system/world/dirty.zig:8
pub const edits_column: u32 = 803;Source: fun/sdfii/src/engine/system/world/dirty.zig:6
pub const material_column: u32 = 801;Source: fun/sdfii/src/engine/system/world/dirty.zig:7
pub const scene_resource_column: u32 = 802;Source: fun/sdfii/src/engine/system/world/dirty.zig:5
pub const shape_column: u32 = 800;Source: fun/sdfii/src/engine/engine.zig
const std = @import("std");const Self = @This();const WorldStore = @import("sdfii_world").store;const WorldSave = @import("sdfii_world").save;const sdfii_assets = @import("sdfii_assets");const AssetRegistry = sdfii_assets.registry;const ShapeAsset = sdfii_assets.ShapeAsset;const asset_reload = sdfii_assets.hot_reload;const SceneArtifact = @import("sdfii_scene").artifact;const Compiler = @import("sdfii_scene").compiler;const ScenePublish = @import("sdfii_scene").publish;const SceneRuntime = @import("sdfii_scene").runtime;const SceneOwnership = @import("sdfii_scene").ownership;const QueryService = @import("sdfii_query").service;const Raymarch = @import("sdfii_raymarch");const BackendExecutor = @import("sdfii_backend").executor;const BackendRuntime = @import("sdfii_backend").runtime;const Router = @import("sdfii_backend").router;const BehaviorRuntime = @import("system/root.zig").behavior.runtime;const Schema = @import("sdfii_world").schema;const WorldCommands = @import("sdfii_world").commands;const types = @import("sdfii_ffi").types;const runtime_shape = @import("system/root.zig").shape;const shape_api = runtime_shape.api;const shape_binding = runtime_shape.binding;const runtime_world = @import("system/root.zig").world;const world_dirty = runtime_world.dirty;const world_session = runtime_world.session;const WorldPlaySession = world_session.PlaySession;const PhysicsRuntime = @import("sdfii_physics").runtime;const PhysicsWorld = @import("sdfii_physics").world;const curve = @import("rig").curve;const AnimationRuntime = @import("system/root.zig").animation.runtime;const ControllerComponent = @import("system/root.zig").animation.controller;const InputRuntime = @import("system/root.zig").input.runtime;const UiInputBindings = @import("system/root.zig").input.binding.Bindings;const Ui = @import("system/root.zig").ui;const AudioRuntime = @import("system/root.zig").audio.runtime;const CommandSink = @import("command/root.zig").sink;const frame_mod = @import("sdfii_frame");const FrameClock = frame_mod.time;const InputState = frame_mod.input;const frame_system = @import("system/root.zig").frame;const runtime_render = @import("system/root.zig").render;const render_config = runtime_render.config;const step = @import("step.zig");const PhysicsScratch = @import("system/root.zig").physics.scratch.Store;pub const HandlerRegistry = BehaviorRuntime.HandlerRegistry;pub const ScenePublication = SceneRuntime.Publication;pub const InputCommand = InputRuntime.Command;pub const ClientInputSnapshot = InputRuntime.ClientSnapshot;pub const RenderRuntimeSnapshot = BackendRuntime.Snapshot;pub const RenderFrameView = BackendRuntime.FrameView;pub const RenderFrameRgba8State = BackendRuntime.Rgba8State;pub const SceneRuntimeSnapshot = SceneRuntime.Snapshot;pub const SceneAuthoringGap = SceneRuntime.AuthoringGap;pub const UiPointerEventKind = Ui.PointerEventKind;pub const UiSurface = Ui.UiSurface;pub const UiSurfaceTree = Ui.UiSurfaceTree;pub const UiCommand = Ui.Command;pub const UiPublishedFrame = Ui.UiPublishedFrame;pub const UiActivationEvent = Ui.ActivationEvent;pub const UiTextEditEvent = Ui.TextEditEvent;pub const UiTextPointEvent = Ui.TextPointEvent;pub const UiTextSelectionUpdate = Ui.TextSelectionUpdate;pub const UiLimits = Ui.Limits;pub const UiCapacity = Ui.Capacity;pub const UiStorageStatus = Ui.StorageStatus;pub const UiStorageExhaustion = Ui.StorageExhaustion;pub const UiSurfaceState = Ui.SurfaceState;pub const AudioCommand = AudioRuntime.Command;pub const AudioAssetKind = AudioRuntime.AssetKind;pub const AudioPlaybackMode = AudioRuntime.PlaybackMode;pub const AudioPlaybackState = AudioRuntime.PlaybackState;pub const AudioOutputKind = AudioRuntime.OutputKind;pub const AudioListenerPose = AudioRuntime.ListenerPose;pub const AudioAsset = AudioRuntime.Asset;pub const AudioTransport = AudioRuntime.Transport;pub const AudioOutput = AudioRuntime.Output;pub const AudioRuntimeSnapshot = AudioRuntime.Snapshot;pub const AudioPlaybackSnapshot = AudioRuntime.PlaybackSnapshot;pub const PhysicsScratchLimits = struct { program_nodes: usize,};pub const Limits = struct { entity_capacity: u32, physics_scratch: PhysicsScratchLimits, ui: UiLimits = .{},};pub const PhysicsScratchCapacity = PhysicsScratch.Capacity;pub const PhysicsScratchStatus = @import("system/root.zig").physics.scratch.Status;pub const AudioEmitterAcousticsSnapshot = AudioRuntime.EmitterAcousticsSnapshot;pub const PhysicsStepContext = struct { frame_index: u64 = 0, dt_s: f64 = 0, collision_revision: u64 = 0, collision_published: ?*const Compiler.PublishedRevision = null,};pub const PhysicsRuntimeSnapshot = struct { synced_revision: u64 = 0, fixed_step_count: u32 = 0, step_ns: u64 = 0, body_pose_count: usize = 0, contact_count: usize = 0,};pub const BehaviorRuntimeSnapshot = struct { step_ns: u64 = 0, executed_component_count: usize = 0, counter_after: i64 = 0,};pub const DIRTY_COL_SHAPE: u32 = world_dirty.shape_column;pub const DIRTY_COL_MATERIAL: u32 = world_dirty.material_column;pub const DIRTY_COL_SCENE_RESOURCE: u32 = world_dirty.scene_resource_column;pub const DIRTY_COL_EDITS: u32 = world_dirty.edits_column;pub const CurrentPublishedScene = struct { value: Compiler.PublishedRevision = .{}, has_value: bool = false, pub fn ptr(self: *const CurrentPublishedScene) ?*const Compiler.PublishedRevision { return if (self.has_value) &self.value else null; }};allocator: std.mem.Allocator,artifact_allocator: std.mem.Allocator,world: WorldStore,assets: AssetRegistry = .{},compiler: Compiler = .{},queries: QueryService = .{},backend: Router = .{},renderer: BackendRuntime.Runtime,scene: SceneRuntime.Runtime,input: InputRuntime.Runtime = .{},ui_input_bindings: UiInputBindings = .{},ui: Ui.Runtime,audio: AudioRuntime.Runtime,compiled_artifact: ?SceneArtifact.CompiledArtifact = null,handler_registry: HandlerRegistry = .{},pixel_buffer: ?[]types.FramePixel = null,graphics_buffer: ?[]u8 = null,pixel_width: u32 = 0,pixel_height: u32 = 0,render_preference: types.BackendPreference = .auto,requested_backend: types.BackendKind = .none,render_style: BackendExecutor.RenderStyle = .{},render_palette: []BackendExecutor.PaletteColor = &.{},graphics_requested: bool = false,last_compile_tick: u64 = 0,last_diagnostics: types.FrameDiagnostics = .{},last_frame_context: frame_mod.FrameContext = .{},frame_history: frame_mod.history.History = .{},frame_allocations: frame_mod.allocator.Telemetry = .{},play_session: ?WorldPlaySession = null,scene_compile: SceneOwnership.CompileDomain,collision_compile: SceneOwnership.CompileDomain,artifact_arena: std.heap.ArenaAllocator,physics_scratch: PhysicsScratch,clock: FrameClock = .{},last_input: InputState = .{},behavior: BehaviorRuntime = .{},physics: PhysicsRuntime = .{},animation: AnimationRuntime = .{},physics_snapshot: PhysicsRuntimeSnapshot = .{},behavior_snapshot: BehaviorRuntimeSnapshot = .{},last_task_run: step.TaskRun = .{},phase_count: usize = 0,pub fn init(allocator: std.mem.Allocator, limits: Limits) Self { return initFallible(allocator, limits) catch unreachable;}pub fn initFallible(allocator: std.mem.Allocator, limits: Limits) !Self { var world = try WorldStore.init(allocator, limits.entity_capacity); errdefer world.deinit(); var physics_scratch = try PhysicsScratch.init(allocator, .{ .bodies = world.entities.capacity, .program_nodes = limits.physics_scratch.program_nodes, }); errdefer physics_scratch.deinit(allocator); var ui_runtime = try Ui.Runtime.init(allocator, limits.ui); errdefer ui_runtime.deinit(); var self = Self{ .allocator = allocator, .artifact_allocator = allocator, .world = world, .backend = Router.initWithAllocator(allocator), .renderer = BackendRuntime.Runtime.init(allocator), .scene = SceneRuntime.Runtime.init(allocator), .ui = ui_runtime, .audio = AudioRuntime.Runtime.init(allocator), .scene_compile = SceneOwnership.CompileDomain.init(allocator), .collision_compile = SceneOwnership.CompileDomain.init(allocator), .artifact_arena = std.heap.ArenaAllocator.init(allocator), .physics_scratch = physics_scratch, }; self.physics_scratch.activate(); self.world.markEngineOwned(); self.initDirtyTracking(); return self;}pub fn deinit(self: *Self) void { render_config.deinit(self); self.audio.deinit(); self.ui.deinit(); self.ui_input_bindings.deinit(self.allocator); self.scene.deinit(); self.renderer.deinit(); self.handler_registry.deinit(self.allocator); self.queries.deinitPushed(self.allocator); self.backend.deinit(); self.artifact_arena.deinit(); self.animation.deinit(self.allocator); self.physics.deinit(self.allocator); self.behavior.deinit(self.allocator); if (self.play_session) |*session| { session.deinit(); self.play_session = null; } self.physics_scratch.deinit(self.allocator); self.collision_compile.deinit(); self.scene_compile.deinit(); self.assets.deinit(self.allocator); self.world.deinit();}pub fn bindShapeAsset(self: *Self, entity: WorldStore.EntityId, asset_id: []const u8) !void { try shape_api.bindAsset(self, entity, asset_id);}pub fn bindShapeParam(self: *Self, entity: WorldStore.EntityId, param_name: []const u8, value: f32) !void { try shape_api.bindParam(self, entity, param_name, value);}pub fn prepareShapeAssetReload( self: *Self, asset: ShapeAsset, options: asset_reload.PrepareOptions,) !asset_reload.PreparedShapeAsset { return shape_api.prepareReload(self, asset, options);}pub fn commitShapeAssetReload( self: *Self, prepared: asset_reload.PreparedShapeAsset,) !u64 { return shape_api.commitReload(self, prepared);}pub fn setRenderOptions(self: *Self, request: types.RenderPlanRequest) void { render_config.setOptions(self, request);}pub fn setRenderStyle(self: *Self, request: types.RenderStyleRequest) void { render_config.setStyle(self, request);}pub fn installRenderPalette(self: *Self, colors: []BackendExecutor.PaletteColor) void { render_config.installPalette(self, colors);}pub fn commandSink(self: *Self) CommandSink { return CommandSink.bind(self.allocator, self.activeWorld());}pub fn enterPlay(self: *Self) !void { try world_session.enterPlay(self);}pub fn exitPlay(self: *Self) void { world_session.exitPlay(self);}pub fn playWorld(self: *Self) ?*WorldStore { return world_session.playWorld(self);}pub fn applyPlayBack( self: *Self, batch: *WorldCommands.Batch, spawned_ids: ?[]WorldStore.EntityId,) !u32 { return try world_session.applyBack(self, batch, spawned_ids);}pub fn captureWorld(self: *Self, allocator: std.mem.Allocator) !WorldSave.Package { return world_session.captureWorld(self, allocator);}pub fn restoreWorld(self: *Self, package: *const WorldSave.Package) !void { try world_session.restoreWorld(self, package);}pub fn activeWorld(self: *Self) *WorldStore { return world_session.active(self);}pub fn activeWorldConst(self: *const Self) *const WorldStore { return world_session.activeConst(self);}fn initDirtyTracking(self: *Self) void { world_dirty.initTracking(self);}pub fn isCompileDirty(self: *const Self) bool { return world_dirty.isCompileDirty(self);}pub fn worldStep(self: *Self, commands: types.CommandBatch, input: types.FrameInput) types.StepResult { return step.world(self, commands, input);}pub fn frameStep(self: *Self, input: types.FrameStepInput) types.FrameStepResult { return step.frame(self, input);}pub fn publishSceneEdit( self: *Self, nodes: []const SceneArtifact.NativeNodeDesc, program_nodes: []const ScenePublish.ProgramNode, root_index: u32,) !void { try self.scene.publishEdit(&self.compiler, nodes, program_nodes, root_index); self.invalidateSceneDerivations();}pub fn sceneEditRevision(self: *Self) u64 { return if (self.scene.editPublished()) |published| published.revision else 0;}pub fn publishScene(self: *Self, publication: ScenePublication) !usize { const result = try self.scene.apply(&self.compiler, publication); self.invalidateSceneDerivations(); return result;}pub fn invalidateSceneDerivations(self: *Self) void { self.invalidateSceneConsumers(); self.scene.releaseOwnedActivePublication(&self.compiler); _ = self.artifact_arena.reset(.free_all);}pub fn invalidateWorldBoundaryDerivations(self: *Self) void { self.invalidateSceneConsumers(); self.compiler.resetWorldState( self.scene_compile.activeAllocator(), self.collision_compile.activeAllocator(), ); self.scene.invalidateWorldBoundary(&self.compiler); self.scene_compile.clear(); self.collision_compile.clear(); _ = self.artifact_arena.reset(.free_all);}fn invalidateSceneConsumers(self: *Self) void { self.queries.invalidateBinding(); self.backend.invalidateBindings(); self.renderer.invalidateScene(); self.clearRenderOutput(); self.compiled_artifact = null;}pub fn clearRenderOutput(self: *Self) void { self.pixel_buffer = null; self.graphics_buffer = null; self.pixel_width = 0; self.pixel_height = 0;}pub fn publishInputCommand(self: *Self, command: InputCommand) !void { try self.input.apply(command);}pub fn readClientInput(self: *Self, session_id: u64) ?ClientInputSnapshot { return self.input.read(session_id);}pub fn bindUiRootToInputSession(self: *Self, root_id: u64, session_id: u64) !void { const prepared = try self.ui_input_bindings.prepareBind(self.allocator, root_id); try self.input.admitUiCaptureSession(session_id); const previous = self.ui_input_bindings.commitBind(prepared, session_id); if (previous) |previous_session_id| { if (previous_session_id != session_id) self.input.clearUiCaptured(previous_session_id); } self.ui_input_bindings.syncCapture(&self.ui, &self.input);}pub fn unbindUiRootFromInputSession(self: *Self, root_id: u64) !bool { const session_id = self.ui_input_bindings.unbind(root_id) orelse return false; self.input.clearUiCaptured(session_id); self.ui_input_bindings.syncCapture(&self.ui, &self.input); return true;}pub fn advanceUiInput(self: *Self, frame_index: u64) !void { try self.ui.advanceInput(frame_index); self.ui_input_bindings.syncCapture(&self.ui, &self.input);}pub fn publishPhysicsStep(self: *Self, context: PhysicsStepContext) !void { const start_ns = @import("sdfii_timing").nowNs(.frame); const admission = try self.preparePhysicsStep(context); const plan = try self.physics.planFixedSteps( admission.scratch, admission.active_world, admission.collision, context.dt_s, ); const outputs = self.physics.collectPublicSummary( admission.active_world, admission.collision, plan.fixed_step_count, ); self.physics_snapshot = .{ .synced_revision = outputs.status.synced_revision, .fixed_step_count = plan.fixed_step_count, .step_ns = @import("sdfii_timing").sinceNs(.frame, start_ns), .body_pose_count = outputs.body_pose_count, .contact_count = outputs.contact_count, };}pub fn preflightPhysicsStep(self: *Self, context: PhysicsStepContext) !void { _ = try self.preparePhysicsStep(context);}const PhysicsStepAdmission = struct { active_world: *WorldStore, collision: ?*const Compiler.PublishedRevision, scratch: PhysicsRuntime.StepScratch,};fn preparePhysicsStep(self: *Self, context: PhysicsStepContext) !PhysicsStepAdmission { const collision = if (context.collision_published) |published| if (published.revision == context.collision_revision) published else null else null; const active_world = self.activeWorld(); const scratch = try self.physics_scratch.frame( PhysicsWorld.physicsBodyCount(active_world, collision), PhysicsWorld.physicsPlaneCount(collision), ); return .{ .active_world = active_world, .collision = collision, .scratch = scratch, };}pub fn readPhysicsRuntime(self: *Self) PhysicsRuntimeSnapshot { return self.physics_snapshot;}pub fn readPhysicsScratch(self: *const Self) PhysicsScratchStatus { return self.physics_scratch.status();}pub fn readBehaviorRuntime(self: *Self) BehaviorRuntimeSnapshot { return self.behavior_snapshot;}pub fn readRenderRuntime(self: *Self) RenderRuntimeSnapshot { return self.renderer.snapshot(&self.backend);}pub fn readRenderFrame(self: *Self) RenderFrameView { return self.renderer.frameView();}pub const PointerRay = struct { origin: [3]f32, direction: [3]f32,};pub fn readPointerRay(self: *Self, pixel_x: f32, pixel_y: f32) ?PointerRay { const view = self.renderer.frameView(); if (!view.valid) return null; if (!view.camera.valid) return null; if (view.width == 0 or view.height == 0) return null; if (!std.math.isFinite(pixel_x) or !std.math.isFinite(pixel_y)) return null; const camera = Raymarch.SdfiiCameraDesc.from(view.camera.camera); const ray = switch (view.camera.projection) { .scene_camera => Raymarch.pixelRayPerspective( camera, view.width, view.height, pixel_x, pixel_y, ), .orthographic => Raymarch.pixelRayOrthographic( camera, view.camera.orthographic_height, view.width, view.height, pixel_x, pixel_y, ), }; return .{ .origin = .{ ray.origin.x, ray.origin.y, ray.origin.z }, .direction = .{ ray.direction.x, ray.direction.y, ray.direction.z }, };}pub fn readSceneRuntime(self: *Self) SceneRuntimeSnapshot { return self.scene.snapshot();}pub fn readSceneAuthoringGap(self: *const Self) ?SceneAuthoringGap { return self.scene.authoring_gap;}pub fn readSceneMetadata(self: *const Self) Compiler.SceneMetadata { return self.scene.metadata();}pub fn readSceneEditPublished(self: *const Self) ?*const Compiler.PublishedRevision { return self.scene.editPublished();}pub fn readSceneCamera(self: *const Self) Compiler.CameraDesc { return self.scene.camera;}pub fn readSceneDefaultMaterial(self: *const Self) Compiler.MaterialDesc { return self.scene.default_material;}pub fn readSceneBackground(self: *const Self) @import("linear").Vec3 { return self.scene.background;}pub fn readSceneSettings(self: *const Self) Compiler.SettingsDesc { return self.scene.settings;}pub fn readSceneLightCount(self: *const Self) usize { return self.scene.lights.items.len;}pub fn currentPublishedScene(self: *const Self) CurrentPublishedScene { const published = self.scene.activePublished(&self.compiler) orelse return .{}; return .{ .value = published.*, .has_value = true, };}pub fn currentQueryRevision(self: *const Self) !u64 { const published = self.scene.activePublished(&self.compiler) orelse return error.NoRevisionBound; if (!self.queries.isBoundTo(published.revision)) return error.NoRevisionBound; return published.revision;}pub fn bindCurrentScene(self: *Self) !u64 { const published = self.scene.activePublished(&self.compiler) orelse return error.NoRevisionBound; try self.queries.bindPublishedRevision(published.*); return published.revision;}pub fn publishUiCommand(self: *Self, command: UiCommand) !void { try self.ui.apply(command);}pub fn publishUiSurface( self: *Self, root_id: u64, revision: u64, surface: UiSurface,) UiStorageExhaustion!void { try self.ui.publishNativeSurface(.{ .root_id = root_id, .revision = revision, .surface = surface, });}pub fn readUiStorageStatus(self: *const Self) UiStorageStatus { return self.ui.storageStatus();}pub fn readUiSurfaceState(self: *const Self, root_id: u64) ?*const UiSurfaceState { return self.ui.surfaceState(root_id);}pub fn readUiPublishedFrame(self: *Self) *const UiPublishedFrame { return self.ui.frameView();}pub fn readUiActivationEvents(self: *Self) []const UiActivationEvent { return self.ui.activationEvents();}pub fn readUiTextEditEvents(self: *Self) []const UiTextEditEvent { return self.ui.textEditEvents();}pub fn readUiTextPointEvents(self: *Self) []const UiTextPointEvent { return self.ui.textPointEvents();}pub fn publishAudioCommand(self: *Self, command: AudioCommand) !void { try self.audio.apply(command);}pub fn readAudioRevision(self: *Self) u64 { return self.audio.revision;}pub fn readAudioRuntime(self: *Self) AudioRuntimeSnapshot { return self.audio.snapshot();}pub fn readAudioPlaybackStatus(self: *Self, playback_ref: u64) ?AudioPlaybackSnapshot { return self.audio.playback(playback_ref);}pub fn readAudioAsset(self: *Self, asset_ref: u64) ?AudioAsset { return self.audio.asset(asset_ref);}pub fn readAudioEmitterAcoustics(self: *Self, emitter_ref: u64) ?AudioEmitterAcousticsSnapshot { return self.audio.emitter(emitter_ref);}pub fn readAudioListenerPose(self: *Self) ?AudioListenerPose { return self.audio.listenerPose();}pub fn readAudioTransport(self: *Self) ?AudioTransport { return self.audio.transport();}pub fn readAudioOutput(self: *Self) ?AudioOutput { return self.audio.output();}fn backendAvailability(self: *const Self) types.BackendAvailability { return self.backend.liveAvailability();}pub const AcousticExecutorBinding = struct { executor: BackendExecutor.Executor, backend_kind: types.BackendKind, scene_version: u64,};pub fn bindAcousticExecutor(self: *Self) !AcousticExecutorBinding { const artifact = self.compiled_artifact orelse return error.NoCompiledArtifact; if (artifact.summary.compiled_revision == 0 or artifact.summary.node_count == 0 or !types.boolFromFlag(artifact.summary.supports_field_queries)) { return error.NoCompiledArtifact; } const summary = types.CompiledArtifactSummary.from(artifact.summary); const availability = self.backendAvailability(); const selection = self.backend.selectBackend(.{ .summary = summary, .availability = availability, .preference = @backingInt(self.render_preference), .requested_backend = @backingInt(self.requested_backend), .existing_backend = @backingInt(self.backend.selected), }); const selected_backend = types.backendKindFromRaw(selection.selected_backend); if (selected_backend == .none) return error.NoSupportedBackend; const executor = self.backend.wosQueryExecutor(summary, availability) orelse return error.NoSupportedBackend; const query_backend = executor.capabilities().kind; if (query_backend == .none) return error.NoSupportedBackend; const upload_plan = self.backend.upload.plan(query_backend, summary, availability); try executor.bind(&artifact, upload_plan); self.backend.upload.commit(query_backend, summary); return .{ .executor = executor, .backend_kind = query_backend, .scene_version = if (artifact.summary.scene_version != 0) artifact.summary.scene_version else artifact.summary.compiled_revision, };}pub fn captureLastFrame(self: *Self, scratch: std.mem.Allocator) !frame_mod.capture.FrameCapture { return frame_system.captureLastFrame(self, scratch);}pub fn frameHistoryCount(self: *const Self) usize { return frame_system.historyCount(self);}pub fn frameHistoryLatest(self: *const Self) ?frame_mod.history.FrameRecord { return frame_system.historyLatest(self);}pub fn frameHistoryAt(self: *const Self, offset_from_oldest: usize) ?frame_mod.history.FrameRecord { return frame_system.historyAt(self, offset_from_oldest);}pub fn clearFrameHistory(self: *Self) void { frame_system.clearHistory(self);}fn encodeMaterialBlob(desc: Compiler.MaterialDesc) [48]u8 { var blob = @as([48]u8, @splat(0)); blob[0] = 1; @memcpy(blob[4..48], std.mem.asBytes(&desc)); return blob;}pub fn upsertEntityMaterial( self: *Self, entity: WorldStore.EntityId, desc: Compiler.MaterialDesc,) !void { var batch = try WorldCommands.Batch.init(self.allocator, 1); defer batch.deinit(); const blob = encodeMaterialBlob(desc); try batch.pushUpsertComponent( entity, @backingInt(Schema.ComponentSchema.material), &blob, ); _ = try self.world.submit(&batch, null);}fn encodeLightsBlob(lights: []const Compiler.LightDesc) [232]u8 { var blob = @as([232]u8, @splat(0)); const count: u32 = @intCast(@min(lights.len, 8)); @as(*align(1) u32, @ptrCast(&blob[0])).* = count; for (lights[0..count], 0..) |light, i| { const offset = 8 + i * @sizeOf(Compiler.LightDesc); @memcpy(blob[offset .. offset + @sizeOf(Compiler.LightDesc)], std.mem.asBytes(&light)); } return blob;}fn seedShapeEntity(engine: *Self) !WorldStore.EntityId { var batch = try WorldCommands.Batch.init(engine.allocator, 4); defer batch.deinit(); try batch.pushSpawn(null, null, .{ .pos_x = 0.0, .pos_y = 0.0, .pos_z = 0.0, .rot_axis_x = 0.0, .rot_axis_y = 1.0, .rot_axis_z = 0.0, .rot_angle = 0.0, .scale = 1.0, }); var ids: [1]WorldStore.EntityId = undefined; _ = try engine.world.submit(&batch, &ids); try batch.pushUpsertComponent( ids[0], @backingInt(Schema.ComponentSchema.shape), "{\"asset_id\":\"sphere\",\"params\":{\"radius\":1.0}}", ); _ = try engine.world.submit(&batch, null); return ids[0];}const CommittedFrameProducts = struct { published_revision: u64, collision_revision: u64, compiler_revision: u64, compiler_collision_revision: u64, artifact_summary: SceneArtifact.CompiledArtifactSummary, artifact_nodes_ptr: usize, artifact_nodes_len: usize, artifact_materials_ptr: usize, artifact_material_count: usize, active_nodes_ptr: usize, query_revision: u64, query_nodes_ptr: usize, render_projection_revision: u64, frame_published_revision: u64, frame_pixels_ptr: usize, frame_pixel_count: usize, frame_width: u32, frame_height: u32, context_frame_index: u64, history_count: usize,};fn captureCommittedFrameProducts(engine: *Self) !CommittedFrameProducts { const scene = engine.readSceneRuntime(); const compiler_published = engine.compiler.published() orelse return error.TestUnexpectedResult; const collision_published = engine.compiler.collisionPublished() orelse return error.TestUnexpectedResult; const artifact = engine.compiled_artifact orelse return error.TestUnexpectedResult; const active_scene = (engine.scene.activePublished(&engine.compiler) orelse return error.TestUnexpectedResult).scene orelse return error.TestUnexpectedResult; const query_nodes = engine.queries.nodes orelse return error.TestUnexpectedResult; const render = engine.readRenderRuntime(); const frame = engine.readRenderFrame(); if (!frame.valid or frame.pixels.len == 0) return error.TestUnexpectedResult; return .{ .published_revision = scene.published_revision, .collision_revision = scene.collision_revision, .compiler_revision = compiler_published.revision, .compiler_collision_revision = collision_published.revision, .artifact_summary = artifact.summary, .artifact_nodes_ptr = @intFromPtr(artifact.sdfProgram().?.nodes.ptr), .artifact_nodes_len = artifact.sdfProgram().?.nodes.len, .artifact_materials_ptr = if (artifact.metadata.materials.len == 0) 0 else @intFromPtr(artifact.metadata.materials.ptr), .artifact_material_count = artifact.metadata.materials.len, .active_nodes_ptr = @intFromPtr(active_scene.nodes.ptr), .query_revision = try engine.currentQueryRevision(), .query_nodes_ptr = @intFromPtr(query_nodes.ptr), .render_projection_revision = render.render_projection_revision, .frame_published_revision = render.frame_published_revision, .frame_pixels_ptr = @intFromPtr(frame.pixels.ptr), .frame_pixel_count = frame.pixels.len, .frame_width = frame.width, .frame_height = frame.height, .context_frame_index = engine.last_frame_context.frame_index, .history_count = engine.frame_history.count, };}fn expectCommittedFrameProductsUnchanged(engine: *Self, expected: CommittedFrameProducts) !void { const actual = try captureCommittedFrameProducts(engine); try std.testing.expectEqualDeep(expected, actual); try std.testing.expect(engine.queries.isBoundTo(expected.query_revision));}fn expectedLiveRenderBackend(engine: *const Self) []const u8 { const report = engine.backend.capabilityReport(); return if (report.accy.available) "accy" else "zig_cpu";}test "frameStep recompiles on world scene resources and ignores demoted performance resources" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 16, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); _ = try seedShapeEntity(&engine); const initial = engine.frameStep(.{ .frame_index = 0, .dt_s = 1.0 / 60.0, .input_event_count = 0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u8, 1), initial.recompiled); var batch = try WorldCommands.Batch.init(std.testing.allocator, 8); defer batch.deinit(); const camera = Compiler.CameraDesc{ .position = .{ .x = 1.0, .y = 2.0, .z = 6.0 }, .target = .{ .x = 0.0, .y = 0.5, .z = 0.0 }, .up = .{ .x = 0.0, .y = 1.0, .z = 0.0 }, .fov = 35.0, }; const background = @import("linear").Vec3{ .x = 0.2, .y = 0.3, .z = 0.4 }; const default_material = Compiler.MaterialDesc{ .ambient = 0.4, .diffuse = 0.5, .specular = 0.6, .shininess = 24.0, .color = .{ .x = 0.7, .y = 0.1, .z = 0.2 }, .rim_color = .{ .x = 0.0, .y = 0.2, .z = 0.4 }, .rim_exponent = 5.0, }; const lights = [_]Compiler.LightDesc{ .{ .direction = .{ .x = 0.0, .y = -1.0, .z = -0.5 }, .color = .{ .x = 0.8, .y = 0.9, .z = 1.0 }, .intensity = 2.5, }, }; const lights_blob = encodeLightsBlob(&lights); const default_material_blob = encodeMaterialBlob(default_material); try batch.pushUpsertResource(@backingInt(Schema.ResourceSchema.camera), std.mem.asBytes(&camera)); try batch.pushUpsertResource(@backingInt(Schema.ResourceSchema.background), std.mem.asBytes(&background)); try batch.pushUpsertResource(@backingInt(Schema.ResourceSchema.default_material), &default_material_blob); try batch.pushUpsertResource(@backingInt(Schema.ResourceSchema.lights), &lights_blob); try batch.pushUpsertResource( @backingInt(Schema.ResourceSchema.scene_options), "{\"ao_steps\":4,\"shadow_steps\":24,\"fog_density\":0.05}", ); _ = try engine.world.submit(&batch, null); const updated = engine.frameStep(.{ .frame_index = 1, .dt_s = 1.0 / 60.0, .input_event_count = 0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u8, 1), updated.recompiled); try std.testing.expect(updated.published_revision > initial.published_revision); const published = engine.compiler.published().?.scene.?; try std.testing.expectEqual(camera.fov, published.camera.fov); try std.testing.expectApproxEqAbs(@as(f32, 1.0), published.camera.position.x, 1e-6); try std.testing.expectApproxEqAbs(@as(f32, 0.2), published.background.x, 1e-6); try std.testing.expectApproxEqAbs(@as(f32, 0.4), published.default_material.ambient, 1e-6); try std.testing.expectEqual(@as(usize, 1), published.lights.len); try std.testing.expectApproxEqAbs(@as(f32, 2.5), published.lights[0].intensity, 1e-6); try std.testing.expectEqual(@as(u32, 4), published.settings.ao_steps); try std.testing.expectEqual(@as(u32, 24), published.settings.shadow_steps); try std.testing.expectApproxEqAbs(@as(f32, 0.05), published.settings.fog_density, 1e-6); try batch.pushUpsertResource( @backingInt(Schema.ResourceSchema.performance_preset), "\"high\"", ); _ = try engine.world.submit(&batch, null); try std.testing.expect(!engine.isCompileDirty()); const performance_only = engine.frameStep(.{ .frame_index = 2, .dt_s = 1.0 / 60.0, .input_event_count = 0, .render_width = 0, .render_height = 0, }); _ = performance_only; const performance_scene = engine.compiler.published().?.scene.?; try std.testing.expectEqual(camera.fov, performance_scene.camera.fov); try std.testing.expectEqual(@as(u32, 4), performance_scene.settings.ao_steps); try std.testing.expectApproxEqAbs(@as(f32, 0.05), performance_scene.settings.fog_density, 1e-6);}test "metadata-only scene change preserves execution subversions and query cleanliness" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const entity = try seedShapeEntity(&engine); const first = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u32, 0), first.diagnostic_flags); const first_summary = (engine.compiled_artifact orelse return error.TestUnexpectedResult).summary; const first_collision_index = engine.collision_compile.active_index; const first_collision = engine.compiler.collisionPublished() orelse return error.TestUnexpectedResult; const first_collision_nodes = (first_collision.scene orelse return error.TestUnexpectedResult).nodes.ptr; const camera = Compiler.CameraDesc{ .position = .{ .x = 1.0, .y = 2.0, .z = 6.0 }, .target = .{ .x = 0.0, .y = 0.5, .z = 0.0 }, .up = .{ .x = 0.0, .y = 1.0, .z = 0.0 }, .fov = 35.0, }; var batch = try WorldCommands.Batch.init(std.testing.allocator, 1); defer batch.deinit(); try batch.pushUpsertResource( @backingInt(Schema.ResourceSchema.camera), std.mem.asBytes(&camera), ); _ = try engine.world.submit(&batch, null); const changed = engine.frameStep(.{ .frame_index = 1, .dt_s = 0.0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u32, 0), changed.diagnostic_flags); try std.testing.expectEqual(@as(u8, 1), changed.recompiled); const changed_artifact = engine.compiled_artifact orelse return error.TestUnexpectedResult; const changed_summary = changed_artifact.summary; try std.testing.expectEqual(first_summary.compiled_revision + 1, changed_summary.compiled_revision); try std.testing.expectEqual(first_summary.scene_version + 1, changed_summary.scene_version); try std.testing.expectEqual(first_summary.execution_ir_version, changed_summary.execution_ir_version); try std.testing.expectEqual(first_summary.native_payload_version, changed_summary.native_payload_version); try std.testing.expectEqual(@as(u32, 0), changed_summary.dirty_region_count); try std.testing.expectEqual(@as(usize, 0), changed_artifact.contract.dirty_region_count); try std.testing.expectEqual( @as(usize, 0), engine.compiler.published().?.query_state.dirtyRegionCountForRevision(changed.published_revision), ); try std.testing.expectEqual(first_collision_index, engine.collision_compile.active_index); try std.testing.expectEqual(first_collision.revision, engine.compiler.collisionPublished().?.revision); const unchanged = engine.frameStep(.{ .frame_index = 2, .dt_s = 0.0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u32, 0), unchanged.diagnostic_flags); try std.testing.expectEqual(@as(u8, 0), unchanged.recompiled); try std.testing.expectEqual(changed.published_revision, unchanged.published_revision); try std.testing.expectEqualDeep(changed_summary, engine.compiled_artifact.?.summary); const before_direct = engine.compiled_artifact.?.summary; _ = try engine.publishScene(.{ .background = .{ .x = 0.2, .y = 0.3, .z = 0.4 } }); try std.testing.expect(engine.compiler.published() == null); try std.testing.expect(engine.compiled_artifact == null); const direct = engine.frameStep(.{ .frame_index = 3, .dt_s = 0.0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u32, 0), direct.diagnostic_flags); try std.testing.expectEqual(@as(u8, 1), direct.recompiled); const direct_artifact = engine.compiled_artifact orelse return error.TestUnexpectedResult; const direct_summary = direct_artifact.summary; try std.testing.expectEqual(before_direct.compiled_revision + 1, direct_summary.compiled_revision); try std.testing.expectEqual(before_direct.scene_version + 1, direct_summary.scene_version); try std.testing.expectEqual(before_direct.execution_ir_version, direct_summary.execution_ir_version); try std.testing.expectEqual(before_direct.native_payload_version, direct_summary.native_payload_version); try std.testing.expectEqual(@as(u32, 0), direct_summary.dirty_region_count); try std.testing.expectEqual(@as(usize, 0), direct_artifact.contract.dirty_region_count); try std.testing.expectEqual( @as(usize, 0), engine.compiler.published().?.query_state.dirtyRegionCountForRevision(direct.published_revision), ); try std.testing.expectEqual(first_collision_index, engine.collision_compile.active_index); const direct_collision = engine.compiler.collisionPublished() orelse return error.TestUnexpectedResult; try std.testing.expectEqual(first_collision.revision, direct_collision.revision); try std.testing.expectEqual( @intFromPtr(first_collision_nodes), @intFromPtr((direct_collision.scene orelse return error.TestUnexpectedResult).nodes.ptr), ); var transform = engine.world.transforms.getDesc(entity.index) orelse return error.TestUnexpectedResult; transform.pos_x = 2.0; try engine.world.transforms.insert(entity.index, transform); const geometry = engine.frameStep(.{ .frame_index = 4, .dt_s = 0.0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u32, 0), geometry.diagnostic_flags); const geometry_summary = engine.compiled_artifact.?.summary; try std.testing.expect(geometry_summary.compiled_revision > direct_summary.compiled_revision); try std.testing.expect(geometry_summary.scene_version > direct_summary.scene_version); try std.testing.expect(geometry_summary.execution_ir_version > direct_summary.execution_ir_version); try std.testing.expect(geometry_summary.native_payload_version > direct_summary.native_payload_version); try batch.pushUpsertResource( @backingInt(Schema.ResourceSchema.scene_options), "{\"max_steps\":96}", ); _ = try engine.world.submit(&batch, null); const settings = engine.frameStep(.{ .frame_index = 5, .dt_s = 0.0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u32, 0), settings.diagnostic_flags); const settings_artifact = engine.compiled_artifact orelse return error.TestUnexpectedResult; const settings_summary = settings_artifact.summary; try std.testing.expect(settings_summary.compiled_revision > geometry_summary.compiled_revision); try std.testing.expect(settings_summary.scene_version > geometry_summary.scene_version); try std.testing.expect(settings_summary.execution_ir_version > geometry_summary.execution_ir_version); try std.testing.expect(settings_summary.native_payload_version > geometry_summary.native_payload_version); try std.testing.expectEqual(@as(u32, 0), settings_summary.dirty_region_count); try std.testing.expectEqual(@as(usize, 0), settings_artifact.contract.dirty_region_count);}test "compiled fallback lights survive runtime light storage replacement" { var moving = std.testing.FailingAllocator.init(std.testing.allocator, .{ .resize_fail_index = 0 }); var engine = Self.init(moving.allocator(), .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); _ = try seedShapeEntity(&engine); _ = try engine.publishScene(.{ .add_light = .{ .direction = .{ .y = -1.0 }, .color = .{ .x = 1.0, .y = 0.5, .z = 0.25 }, .intensity = 1.0, } }); try engine.scene.lights.shrinkAndFreePrecise(engine.allocator, engine.scene.lights.items.len); const first = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u32, 0), first.diagnostic_flags); const first_scene = (engine.compiler.published() orelse return error.TestUnexpectedResult).scene orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(usize, 1), first_scene.lights.len); try std.testing.expect(@intFromPtr(engine.scene.lights.items.ptr) != @intFromPtr(first_scene.lights.ptr)); const runtime_lights_ptr = engine.scene.lights.items.ptr; _ = try engine.publishScene(.{ .add_light = .{ .direction = .{ .x = 1.0 }, .color = .{ .x = 0.25, .y = 0.5, .z = 1.0 }, .intensity = 2.0, } }); try std.testing.expect(@intFromPtr(runtime_lights_ptr) != @intFromPtr(engine.scene.lights.items.ptr)); const second = engine.frameStep(.{ .frame_index = 1, .dt_s = 0.0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u32, 0), second.diagnostic_flags); try std.testing.expectEqual(@as(u8, 1), second.recompiled); try std.testing.expect(second.published_revision > first.published_revision); const second_scene = (engine.compiler.published() orelse return error.TestUnexpectedResult).scene orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(usize, 2), second_scene.lights.len); try std.testing.expectEqual(@as(f32, 1.0), second_scene.lights[0].intensity); try std.testing.expectEqual(@as(f32, 2.0), second_scene.lights[1].intensity);}test "shape asset reload prepare failure preserves resident registry and commit republishes frame" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 16, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const initial_asset = ShapeAsset.init("hot.ship", 1).withAnalyticProgram(.{ .root = 0, .node_count = 2, .payload_hash = 0x1111, }); const initial_prepared = try engine.prepareShapeAssetReload(initial_asset, .{}); _ = try engine.commitShapeAssetReload(initial_prepared); const first = engine.frameStep(.{ .frame_index = 1, .dt_s = 1.0 / 60.0, .input_event_count = 0, .render_width = 0, .render_height = 0, }); try std.testing.expectEqual(@as(u64, 1), first.published_revision); try std.testing.expectEqual(@as(?u32, 1), engine.assets.versionOf("hot.ship")); const first_collision = engine.compiler.collisionPublished() orelse return error.TestUnexpectedResult; const first_collision_index = engine.collision_compile.active_index; const bad_asset = ShapeAsset.init("hot.ship", 2); try std.testing.expectError( error.InvalidReloadProgram, engine.prepareShapeAssetReload(bad_asset, .{}), ); const resident_after_failure = engine.assets.get("hot.ship").?; try std.testing.expectEqual(@as(u32, 1), resident_after_failure.version); try std.testing.expectEqual(@as(u32, 0x1111), resident_after_failure.analytic_program.payload_hash); try std.testing.expectEqual(first.published_revision, engine.compiler.published().?.revision); try std.testing.expectEqual(first_collision.revision, engine.compiler.collisionPublished().?.revision); const updated_asset = ShapeAsset.init("hot.ship", 2).withAnalyticProgram(.{ .root = 0, .node_count = 3, .payload_hash = 0x2222, }); const prepared = try engine.prepareShapeAssetReload(updated_asset, .{}); try std.testing.expect(prepared.artifact_revision != initial_prepared.artifact_revision); _ = try engine.commitShapeAssetReload(prepared); const resident_after_commit = engine.assets.get("hot.ship").?; try std.testing.expectEqual(@as(u32, 2), resident_after_commit.version); try std.testing.expectEqual(@as(u32, 0x2222), resident_after_commit.analytic_program.payload_hash); try std.testing.expect(engine.compiler.collisionPublished() == null); const second = engine.frameStep(.{ .frame_index = 2, .dt_s = 1.0 / 60.0, .input_event_count = 0, .render_width = 0, .render_height = 0, }); try std.testing.expect(second.published_revision > first.published_revision); try std.testing.expectEqual(@as(usize, 1), engine.compiler.published().?.view.asset_count); try std.testing.expect(engine.compiler.collisionPublished().?.revision > first_collision.revision); try std.testing.expectEqual(first_collision_index ^ 1, engine.collision_compile.active_index);}test "readPointerRay is null before any rendered frame and hits the rendered scene after" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); try std.testing.expectEqual(@as(?PointerRay, null), engine.readPointerRay(0.0, 0.0)); const sdf_types = @import("sdfii_sdf").types; const nodes = [_]SceneArtifact.NativeNodeDesc{ .{ .kind_id = @backingInt(sdf_types.NodeKind.sphere), .f0 = 1.0, }, }; try engine.publishSceneEdit(&nodes, &.{}, 0); _ = try engine.publishScene(.{ .camera = .{ .position = .{ .x = 0.0, .y = 0.0, .z = 6.0 }, .target = .{ .x = 0.0, .y = 0.0, .z = 0.0 }, .up = .{ .x = 0.0, .y = 1.0, .z = 0.0 }, .fov = 45.0, } }); const width: u32 = 33; const height: u32 = 33; const result = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, .input_event_count = 0, .render_width = width, .render_height = height, }); try std.testing.expectEqual(@as(u8, 1), result.render_valid); const center: f32 = 16.0; const ray = engine.readPointerRay(center, center) orelse return error.TestUnexpectedResult; try std.testing.expectApproxEqAbs(@as(f32, 0.0), ray.direction[0], 0.01); try std.testing.expectApproxEqAbs(@as(f32, 0.0), ray.direction[1], 0.01); try std.testing.expectApproxEqAbs(@as(f32, -1.0), ray.direction[2], 0.01); const hit = try engine.queries.queryRaycast(@import("linear").Vec3.fromArray(ray.origin), @import("linear").Vec3.fromArray(ray.direction)); try std.testing.expect(hit.hit); try std.testing.expectApproxEqAbs(@as(f32, 1.0), hit.point.z, 0.05); const view = engine.readRenderFrame(); const center_pixel = view.pixels[@as(usize, center) * width + @as(usize, center)]; try std.testing.expect(center_pixel.flags != 0); const expected_depth = hit.t; try std.testing.expectApproxEqAbs(expected_depth, center_pixel.depth, 0.1);}test "frameStep keeps rendering an edit scene when the compiled world is empty" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const sdf_types = @import("sdfii_sdf").types; const nodes = [_]SceneArtifact.NativeNodeDesc{ .{ .kind_id = @backingInt(sdf_types.NodeKind.sphere), .f0 = 1.0, }, }; try engine.publishSceneEdit(&nodes, &.{}, 0); const result = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, .input_event_count = 0, .render_width = 32, .render_height = 16, }); try std.testing.expectEqual(@as(u8, 1), result.render_valid); try std.testing.expect(engine.pixel_buffer != null); const rendered = engine.readRenderFrame(); try std.testing.expect(rendered.valid); try std.testing.expectEqual(@as(u32, 32), rendered.width); try std.testing.expectEqual(@as(u32, 16), rendered.height); const copied_pixels = engine.pixel_buffer.?; const renderer_pixels = rendered.pixels[0..copied_pixels.len]; try std.testing.expectEqualSlices(types.FramePixel, renderer_pixels, copied_pixels); var hit_count: usize = 0; for (copied_pixels) |pixel| { if (pixel.flags != 0) hit_count += 1; } try std.testing.expect(hit_count > 0); const second_result = engine.frameStep(.{ .frame_index = 1, .dt_s = 1.0 / 60.0, .input_event_count = 0, .render_width = 32, .render_height = 16, }); try std.testing.expectEqual(@as(u8, 1), second_result.render_valid); try engine.last_frame_context.stages.assertCompleted(.readback); try std.testing.expectEqual(frame_mod.StageAccess.none, engine.last_frame_context.stages.get(.readback).contract.world_access); const render_history = engine.frameHistoryLatest().?; try std.testing.expectEqual(second_result.frame_index, render_history.frame_index); try std.testing.expect(render_history.render_graph.pass_count > 0); try std.testing.expect(render_history.transient_bytes > 0); try std.testing.expectEqualStrings(expectedLiveRenderBackend(&engine), render_history.backend.selected_backend); var second_hit_count: usize = 0; for (engine.pixel_buffer.?) |pixel| { if (pixel.flags != 0) second_hit_count += 1; } try std.testing.expect(second_hit_count > 0);}test "authored scene replacement invalidates query binding until the next frame" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const sdf_types = @import("sdfii_sdf").types; const first_nodes = [_]SceneArtifact.NativeNodeDesc{.{ .kind_id = @backingInt(sdf_types.NodeKind.sphere), .f0 = 1.0, }}; try engine.publishSceneEdit(&first_nodes, &.{}, 0); const first = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, }); try std.testing.expect(engine.queries.isBoundTo(first.published_revision)); _ = try engine.queries.queryDistanceAtRevision(first.published_revision, @import("linear").Vec3.init(0.0, 0.0, 0.0)); const backend_binding = try engine.bindAcousticExecutor(); var backend_points = [_]@import("linear").Vec3{.{}}; var backend_distances = [_]f32{0}; try backend_binding.executor.sampleDistances(.{ .points = &backend_points, .distances_out = &backend_distances, }); const replacement_nodes = [_]SceneArtifact.NativeNodeDesc{.{ .kind_id = @backingInt(sdf_types.NodeKind.sphere), .f0 = 0.25, }}; try engine.publishSceneEdit(&replacement_nodes, &.{}, 0); try std.testing.expectError( error.NoRevisionBound, engine.queries.queryDistanceAtRevision(first.published_revision, @import("linear").Vec3.init(0.0, 0.0, 0.0)), ); try std.testing.expectError(error.NoRevisionBound, engine.currentQueryRevision()); try std.testing.expectError( error.ArtifactNotBound, backend_binding.executor.sampleDistances(.{ .points = &backend_points, .distances_out = &backend_distances, }), ); const next = engine.frameStep(.{ .frame_index = 1, .dt_s = 0.0, }); try std.testing.expect(next.published_revision != first.published_revision); try std.testing.expect(engine.queries.isBoundTo(next.published_revision)); _ = try engine.queries.queryDistanceAtRevision(next.published_revision, @import("linear").Vec3.init(0.0, 0.0, 0.0));}test "authored scene mutation retires borrowed backend bindings before storage changes" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const sdf_types = @import("sdfii_sdf").types; const nodes = [_]SceneArtifact.NativeNodeDesc{.{ .kind_id = @backingInt(sdf_types.NodeKind.sphere), .f0 = 1.0, }}; try engine.publishSceneEdit(&nodes, &.{}, 0); _ = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expect(engine.pixel_buffer != null); const owned_artifact = engine.compiled_artifact.?; const source_scene = engine.readSceneEditPublished().?.scene.?; try std.testing.expect(owned_artifact.sdfProgram().?.nodes.ptr != source_scene.nodes.ptr); const binding = try engine.bindAcousticExecutor(); var points = [_]@import("linear").Vec3{.{}}; var distances = [_]f32{0}; try binding.executor.sampleDistances(.{ .points = &points, .distances_out = &distances }); for (0..64) |index| { _ = try engine.publishScene(.{ .add_material = .{ .diffuse = @floatFromInt(index) } }); } try std.testing.expectError( error.ArtifactNotBound, binding.executor.sampleDistances(.{ .points = &points, .distances_out = &distances }), ); try std.testing.expectError(error.NoCompiledArtifact, engine.bindAcousticExecutor()); try std.testing.expect(!engine.readRenderRuntime().frame_valid); try std.testing.expect(engine.pixel_buffer == null); try std.testing.expect(engine.graphics_buffer == null); try std.testing.expectEqual(@as(u32, 0), engine.pixel_width); try std.testing.expectEqual(@as(u32, 0), engine.pixel_height); _ = engine.frameStep(.{ .frame_index = 1, .dt_s = 0.0 }); const rebound = try engine.bindAcousticExecutor(); try rebound.executor.sampleDistances(.{ .points = &points, .distances_out = &distances }); try std.testing.expect(std.math.isFinite(distances[0]));}test "unchanged scene frames retain one owned active publication" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); _ = try seedShapeEntity(&engine); const first = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); const first_active = engine.scene.activePublished(&engine.compiler).?.scene.?; const first_owned = engine.compiled_artifact.?.sdfProgram().?; try std.testing.expectEqual(@intFromPtr(first_owned.nodes.ptr), @intFromPtr(first_active.nodes.ptr)); try std.testing.expectEqual(@intFromPtr(first_owned.nodes.ptr), @intFromPtr(engine.queries.nodes.?.ptr)); const second = engine.frameStep(.{ .frame_index = 1, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); const second_active = engine.scene.activePublished(&engine.compiler).?.scene.?; const second_owned = engine.compiled_artifact.?.sdfProgram().?; try std.testing.expectEqual(first.published_revision, second.published_revision); try std.testing.expectEqual(@intFromPtr(first_owned.nodes.ptr), @intFromPtr(second_owned.nodes.ptr)); try std.testing.expectEqual(@intFromPtr(second_owned.nodes.ptr), @intFromPtr(second_active.nodes.ptr)); try std.testing.expectEqual(@intFromPtr(second_owned.nodes.ptr), @intFromPtr(engine.queries.nodes.?.ptr));}test "unchanged warmed frames perform exact preflight without retained allocation" { var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); _ = try seedShapeEntity(&engine); for (0..2) |frame_index| { const result = engine.frameStep(.{ .frame_index = frame_index, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), result.diagnostic_flags); } const revision = engine.readSceneRuntime().published_revision; const artifact_nodes = engine.compiled_artifact.?.sdfProgram().?.nodes.ptr; const scene_capacity = engine.scene_compile.retainedCapacity(); const collision_capacity = engine.collision_compile.retainedCapacity(); const scene_staging_index = engine.scene_compile.active_index ^ 1; engine.scene_compile.arenas[scene_staging_index].deinit(); engine.scene_compile.arenas[scene_staging_index] = std.heap.ArenaAllocator.init(failing.allocator()); const collision_staging_index = engine.collision_compile.active_index ^ 1; engine.collision_compile.arenas[collision_staging_index].deinit(); engine.collision_compile.arenas[collision_staging_index] = std.heap.ArenaAllocator.init(failing.allocator()); failing.fail_index = 0; failing.resize_fail_index = 0; const unchanged = engine.frameStep(.{ .frame_index = 2, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expect(!failing.has_induced_failure); try std.testing.expectEqual(@as(u32, 0), unchanged.diagnostic_flags); try std.testing.expectEqual(revision, unchanged.published_revision); try std.testing.expectEqual(@intFromPtr(artifact_nodes), @intFromPtr(engine.compiled_artifact.?.sdfProgram().?.nodes.ptr)); try std.testing.expect(engine.scene_compile.retainedCapacity() <= scene_capacity); try std.testing.expect(engine.collision_compile.retainedCapacity() <= collision_capacity);}test "fixed-cardinality scene churn retains two compile banks" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const entity = try seedShapeEntity(&engine); for (0..2) |frame_index| { var transform = engine.world.transforms.getDesc(entity.index) orelse return error.TestUnexpectedResult; transform.pos_x = @floatFromInt(frame_index); try engine.world.transforms.insert(entity.index, transform); const result = engine.frameStep(.{ .frame_index = frame_index, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), result.diagnostic_flags); } const scene_capacity = engine.scene_compile.retainedCapacity(); const collision_capacity = engine.collision_compile.retainedCapacity(); try std.testing.expect(scene_capacity != 0); try std.testing.expect(collision_capacity != 0); var previous_revision = engine.readSceneRuntime().published_revision; for (0..64) |offset| { var transform = engine.world.transforms.getDesc(entity.index) orelse return error.TestUnexpectedResult; transform.pos_x = @floatFromInt(offset + 2); try engine.world.transforms.insert(entity.index, transform); const result = engine.frameStep(.{ .frame_index = offset + 2, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), result.diagnostic_flags); try std.testing.expectEqual(@as(u8, 1), result.recompiled); try std.testing.expect(result.published_revision > previous_revision); try std.testing.expectEqual(scene_capacity, engine.scene_compile.retainedCapacity()); try std.testing.expectEqual(collision_capacity, engine.collision_compile.retainedCapacity()); previous_revision = result.published_revision; }}test "dynamic-only scene churn leaves collision compile bank unchanged" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); _ = try seedShapeEntity(&engine); const dynamic_entity = try seedShapeEntity(&engine); var batch = try WorldCommands.Batch.init(std.testing.allocator, 1); defer batch.deinit(); const dynamic_body = PhysicsWorld.PhysicsComponentDesc{ .motion_type = 0 }; try batch.pushUpsertComponent( dynamic_entity, @backingInt(Schema.ComponentSchema.physics), std.mem.asBytes(&dynamic_body), ); _ = try engine.world.submit(&batch, null); const first = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), first.diagnostic_flags); var scene_index = engine.scene_compile.active_index; const collision_index = engine.collision_compile.active_index; const collision_capacity = engine.collision_compile.retainedCapacity(); const collision = engine.compiler.collisionPublished() orelse return error.TestUnexpectedResult; const collision_scene = collision.scene orelse return error.TestUnexpectedResult; const collision_nodes = collision_scene.nodes.ptr; var previous_scene_revision = first.published_revision; for (0..64) |offset| { var transform = engine.world.transforms.getDesc(dynamic_entity.index) orelse return error.TestUnexpectedResult; transform.pos_x = @floatFromInt(offset + 3); try engine.world.transforms.insert(dynamic_entity.index, transform); const next = engine.frameStep(.{ .frame_index = offset + 1, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), next.diagnostic_flags); try std.testing.expectEqual(@as(u8, 1), next.recompiled); try std.testing.expect(next.published_revision > previous_scene_revision); try std.testing.expectEqual(scene_index ^ 1, engine.scene_compile.active_index); scene_index = engine.scene_compile.active_index; previous_scene_revision = next.published_revision; try std.testing.expectEqual(collision_index, engine.collision_compile.active_index); try std.testing.expectEqual(collision_capacity, engine.collision_compile.retainedCapacity()); const unchanged_collision = engine.compiler.collisionPublished() orelse return error.TestUnexpectedResult; try std.testing.expectEqual(collision.revision, unchanged_collision.revision); try std.testing.expectEqual( @intFromPtr(collision_nodes), @intFromPtr((unchanged_collision.scene orelse return error.TestUnexpectedResult).nodes.ptr), ); }}test "empty and unknown intersect edits leave collision publication unchanged" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); _ = try seedShapeEntity(&engine); const first = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), first.diagnostic_flags); const first_collision_index = engine.collision_compile.active_index; const first_collision = engine.compiler.collisionPublished() orelse return error.TestUnexpectedResult; const first_collision_scene = first_collision.scene orelse return error.TestUnexpectedResult; var batch = try WorldCommands.Batch.init(std.testing.allocator, 2); defer batch.deinit(); try batch.pushUpsertEdit(.{ .edit_id = "empty-intersect", .asset_id = "empty", .op = .intersect, }); try batch.pushUpsertEdit(.{ .edit_id = "unknown-intersect", .asset_id = "missing", .op = .intersect, }); _ = try engine.world.submit(&batch, null); const second = engine.frameStep(.{ .frame_index = 1, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), second.diagnostic_flags); try std.testing.expectEqual(first_collision_index, engine.collision_compile.active_index); const second_collision = engine.compiler.collisionPublished() orelse return error.TestUnexpectedResult; try std.testing.expectEqual(first_collision.revision, second_collision.revision); try std.testing.expectEqual( @intFromPtr(first_collision_scene.nodes.ptr), @intFromPtr((second_collision.scene orelse return error.TestUnexpectedResult).nodes.ptr), );}test "failed scene publication retains owned consumers and permits retry" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const sdf_types = @import("sdfii_sdf").types; const first_nodes = [_]SceneArtifact.NativeNodeDesc{.{ .kind_id = @backingInt(sdf_types.NodeKind.sphere), .f0 = 1.0, }}; try engine.publishSceneEdit(&first_nodes, &.{}, 0); const first = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); const committed_nodes = engine.compiled_artifact.?.sdfProgram().?.nodes.ptr; const binding = try engine.bindAcousticExecutor(); var points = [_]@import("linear").Vec3{.{}}; var distances = [_]f32{0}; try binding.executor.sampleDistances(.{ .points = &points, .distances_out = &distances }); const replacement_nodes = [_]SceneArtifact.NativeNodeDesc{.{ .kind_id = @backingInt(sdf_types.NodeKind.sphere), .f0 = 0.25, }}; var edit_failure = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); engine.scene.allocator = edit_failure.allocator(); try std.testing.expectError(error.OutOfMemory, engine.publishSceneEdit(&replacement_nodes, &.{}, 0)); engine.scene.allocator = std.testing.allocator; try std.testing.expectEqual(first.published_revision, engine.readSceneRuntime().published_revision); try std.testing.expectEqual(@intFromPtr(committed_nodes), @intFromPtr(engine.compiled_artifact.?.sdfProgram().?.nodes.ptr)); try std.testing.expectEqual(@intFromPtr(committed_nodes), @intFromPtr(engine.scene.activePublished(&engine.compiler).?.scene.?.nodes.ptr)); try std.testing.expectEqual(first.published_revision, try engine.currentQueryRevision()); try binding.executor.sampleDistances(.{ .points = &points, .distances_out = &distances }); try std.testing.expect(engine.readRenderRuntime().frame_valid); var metadata_failure = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); engine.scene.allocator = metadata_failure.allocator(); try std.testing.expectError( error.OutOfMemory, engine.publishScene(.{ .add_material = .{ .diffuse = 0.5 } }), ); engine.scene.allocator = std.testing.allocator; try std.testing.expectEqual(first.published_revision, engine.readSceneRuntime().published_revision); try std.testing.expectEqual(@intFromPtr(committed_nodes), @intFromPtr(engine.compiled_artifact.?.sdfProgram().?.nodes.ptr)); try std.testing.expectEqual(first.published_revision, try engine.currentQueryRevision()); try binding.executor.sampleDistances(.{ .points = &points, .distances_out = &distances }); try engine.publishSceneEdit(&replacement_nodes, &.{}, 0); try std.testing.expect(engine.compiled_artifact == null); try std.testing.expectError(error.NoRevisionBound, engine.currentQueryRevision()); try std.testing.expectError( error.ArtifactNotBound, binding.executor.sampleDistances(.{ .points = &points, .distances_out = &distances }), ); const retried = engine.frameStep(.{ .frame_index = 1, .dt_s = 0.0 }); try std.testing.expect(retried.published_revision > first.published_revision); try std.testing.expectEqual(retried.published_revision, try engine.currentQueryRevision());}test "compile scene artifact failure preserves the committed revision and history" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const entity = try seedShapeEntity(&engine); const completed = engine.frameStep(.{ .frame_index = 20, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); const committed_revision = completed.published_revision; const committed_compiler_revision = engine.compiler.published().?.revision; const committed_nodes = engine.compiled_artifact.?.sdfProgram().?.nodes.ptr; const committed_context_frame = engine.last_frame_context.frame_index; const committed_history_count = engine.frame_history.count; var transform = engine.world.transforms.getDesc(entity.index).?; transform.pos_x = 2.0; try engine.world.transforms.insert(entity.index, transform); var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); engine.artifact_allocator = failing.allocator(); const failed = engine.frameStep(.{ .frame_index = 21, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); engine.artifact_allocator = std.testing.allocator; try std.testing.expectEqual(step.diagnostic_task_failed, failed.diagnostic_flags & step.diagnostic_task_failed); try std.testing.expectEqual(step.diagnostic_task_skipped, failed.diagnostic_flags & step.diagnostic_task_skipped); try std.testing.expectEqual(frame_mod.task_graph.TaskName.compile_scene, engine.last_task_run.first_failure.?.task); try std.testing.expectEqual(error.OutOfMemory, engine.last_task_run.first_failure.?.err); try std.testing.expectEqual(committed_revision, failed.published_revision); try std.testing.expectEqual(committed_revision, engine.readSceneRuntime().published_revision); try std.testing.expectEqual(committed_compiler_revision, engine.compiler.published().?.revision); try std.testing.expectEqual(committed_revision, try engine.currentQueryRevision()); try std.testing.expectEqual(@intFromPtr(committed_nodes), @intFromPtr(engine.compiled_artifact.?.sdfProgram().?.nodes.ptr)); try std.testing.expectEqual(@intFromPtr(committed_nodes), @intFromPtr(engine.scene.activePublished(&engine.compiler).?.scene.?.nodes.ptr)); try std.testing.expectEqual(@intFromPtr(committed_nodes), @intFromPtr(engine.queries.nodes.?.ptr)); try std.testing.expectEqual(committed_context_frame, engine.last_frame_context.frame_index); try std.testing.expectEqual(committed_history_count, engine.frame_history.count); const recovered = engine.frameStep(.{ .frame_index = 22, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), recovered.diagnostic_flags); try std.testing.expect(recovered.published_revision > committed_revision); try expectActiveSceneAgreement(&engine, recovered.published_revision); try std.testing.expectEqual(committed_history_count + 1, engine.frame_history.count);}test "repeated collision gather failures retain one scratch high water and retry" { var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); var engine = Self.init(failing.allocator(), .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const entity = try seedShapeEntity(&engine); _ = try engine.publishScene(.{ .add_material = .{ .ambient = 0.15, .diffuse = 0.65, .specular = 0.2, .shininess = 8.0, } }); const completed = engine.frameStep(.{ .frame_index = 70, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), completed.diagnostic_flags); const committed = try captureCommittedFrameProducts(&engine); try std.testing.expectEqual(@as(usize, 1), committed.artifact_material_count); var transform = engine.world.transforms.getDesc(entity.index) orelse return error.TestUnexpectedResult; transform.pos_x = 1.5; try engine.world.transforms.insert(entity.index, transform); failing.fail_index = failing.alloc_index; failing.resize_fail_index = failing.resize_index; var failed: types.FrameStepResult = undefined; var scratch_high_water_capacity: usize = 0; for (0..16) |attempt| { failed = engine.frameStep(.{ .frame_index = 71 + attempt, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expect(failing.has_induced_failure); try std.testing.expectEqual(frame_mod.task_graph.TaskName.compile_collision, engine.last_task_run.first_failure.?.task); const scratch_capacity = engine.scene.scratch.queryCapacity(); try std.testing.expect(scratch_capacity != 0); if (attempt == 0) { scratch_high_water_capacity = scratch_capacity; } else { try std.testing.expectEqual(scratch_high_water_capacity, scratch_capacity); } } const failure = engine.last_task_run.first_failure.?; try std.testing.expectEqual(frame_mod.task_graph.TaskName.compile_collision, failure.task); try std.testing.expectEqual(step.TaskFailureStage.execution, failure.stage); try std.testing.expectEqual(error.OutOfMemory, failure.err); try std.testing.expectEqual(frame_mod.task_graph.TaskState.failed, engine.last_task_run.outcomes.state(.compile_collision)); inline for (.{ frame_mod.task_graph.TaskName.physics, frame_mod.task_graph.TaskName.animation, frame_mod.task_graph.TaskName.compile_scene, frame_mod.task_graph.TaskName.behavior, frame_mod.task_graph.TaskName.ui_render, frame_mod.task_graph.TaskName.render, }) |task| try std.testing.expectEqual(frame_mod.task_graph.TaskState.skipped, engine.last_task_run.outcomes.state(task)); try std.testing.expectEqual(step.diagnostic_task_failed, failed.diagnostic_flags & step.diagnostic_task_failed); try std.testing.expectEqual(step.diagnostic_task_skipped, failed.diagnostic_flags & step.diagnostic_task_skipped); try std.testing.expectEqual(@as(u8, 0), failed.recompiled); try std.testing.expectEqual(@as(u8, 0), failed.render_valid); try std.testing.expectEqual(committed.published_revision, failed.published_revision); try expectCommittedFrameProductsUnchanged(&engine, committed); failing.fail_index = std.math.maxInt(usize); failing.resize_fail_index = std.math.maxInt(usize); const recovered = engine.frameStep(.{ .frame_index = 87, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), recovered.diagnostic_flags); try std.testing.expectEqual(@as(u8, 1), recovered.recompiled); try std.testing.expectEqual(@as(u8, 1), recovered.render_valid); const advanced = try captureCommittedFrameProducts(&engine); try std.testing.expectEqual(committed.published_revision + 1, advanced.published_revision); try std.testing.expectEqual(committed.collision_revision + 1, advanced.collision_revision); try std.testing.expectEqual(committed.compiler_revision + 1, advanced.compiler_revision); try std.testing.expectEqual(committed.compiler_collision_revision + 1, advanced.compiler_collision_revision); try std.testing.expectEqual(advanced.published_revision, advanced.query_revision); try std.testing.expectEqual(advanced.published_revision, advanced.render_projection_revision); try std.testing.expectEqual(advanced.published_revision, advanced.frame_published_revision); try std.testing.expectEqual(committed.history_count + 1, advanced.history_count);}test "repeated scene gather failures retain one scratch high water and retry" { var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); var engine = Self.init(failing.allocator(), .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const entity = try seedShapeEntity(&engine); _ = try engine.publishScene(.{ .add_material = .{ .ambient = 0.1, .diffuse = 0.7, .specular = 0.2, .shininess = 12.0, } }); var batch = try WorldCommands.Batch.init(std.testing.allocator, 2); defer batch.deinit(); const body = PhysicsWorld.PhysicsComponentDesc{ .motion_type = 0 }; try batch.pushUpsertComponent( entity, @backingInt(Schema.ComponentSchema.physics), std.mem.asBytes(&body), ); try batch.pushUpsertEdit(.{ .edit_id = "visual-edit", .asset_id = "sphere", .params = "{\"radius\":1.0}", .dynamic = true, .collidable = false, }); _ = try engine.world.submit(&batch, null); const completed = engine.frameStep(.{ .frame_index = 80, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), completed.diagnostic_flags); const committed = try captureCommittedFrameProducts(&engine); try std.testing.expectEqual(@as(usize, 1), committed.artifact_material_count); try batch.pushUpsertEdit(.{ .edit_id = "visual-edit", .asset_id = "sphere", .params = "{\"radius\":0.5}", .dynamic = true, .collidable = false, }); _ = try engine.world.submit(&batch, null); failing.fail_index = failing.alloc_index; failing.resize_fail_index = failing.resize_index; var failed: types.FrameStepResult = undefined; var scratch_high_water_capacity: usize = 0; for (0..16) |attempt| { failed = engine.frameStep(.{ .frame_index = 81 + attempt, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expect(failing.has_induced_failure); try std.testing.expectEqual(frame_mod.task_graph.TaskName.compile_scene, engine.last_task_run.first_failure.?.task); const scratch_capacity = engine.scene.scratch.queryCapacity(); try std.testing.expect(scratch_capacity != 0); if (attempt == 0) { scratch_high_water_capacity = scratch_capacity; } else { try std.testing.expectEqual(scratch_high_water_capacity, scratch_capacity); } } const failure = engine.last_task_run.first_failure.?; try std.testing.expectEqual(frame_mod.task_graph.TaskName.compile_scene, failure.task); try std.testing.expectEqual(step.TaskFailureStage.execution, failure.stage); try std.testing.expectEqual(error.OutOfMemory, failure.err); try std.testing.expectEqual(frame_mod.task_graph.TaskState.failed, engine.last_task_run.outcomes.state(.compile_scene)); inline for (.{ frame_mod.task_graph.TaskName.behavior, frame_mod.task_graph.TaskName.ui_render, frame_mod.task_graph.TaskName.render, }) |task| try std.testing.expectEqual(frame_mod.task_graph.TaskState.skipped, engine.last_task_run.outcomes.state(task)); try std.testing.expectEqual(step.diagnostic_task_failed, failed.diagnostic_flags & step.diagnostic_task_failed); try std.testing.expectEqual(step.diagnostic_task_skipped, failed.diagnostic_flags & step.diagnostic_task_skipped); try std.testing.expectEqual(@as(u8, 0), failed.recompiled); try std.testing.expectEqual(@as(u8, 0), failed.render_valid); try std.testing.expectEqual(committed.published_revision, failed.published_revision); try expectCommittedFrameProductsUnchanged(&engine, committed); failing.fail_index = std.math.maxInt(usize); failing.resize_fail_index = std.math.maxInt(usize); const recovered = engine.frameStep(.{ .frame_index = 97, .dt_s = 0.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), recovered.diagnostic_flags); try std.testing.expectEqual(@as(u8, 1), recovered.recompiled); try std.testing.expectEqual(@as(u8, 1), recovered.render_valid); const advanced = try captureCommittedFrameProducts(&engine); try std.testing.expectEqual(committed.published_revision + 1, advanced.published_revision); try std.testing.expectEqual(committed.collision_revision, advanced.collision_revision); try std.testing.expectEqual(committed.compiler_revision + 1, advanced.compiler_revision); try std.testing.expectEqual(committed.compiler_collision_revision, advanced.compiler_collision_revision); try std.testing.expectEqual(advanced.published_revision, advanced.query_revision); try std.testing.expectEqual(advanced.published_revision, advanced.render_projection_revision); try std.testing.expectEqual(advanced.published_revision, advanced.frame_published_revision); try std.testing.expectEqual(committed.history_count + 1, advanced.history_count);}test "world and authored scene transitions share one monotonic active generation" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 16, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const world_entity = try seedShapeEntity(&engine); const world_first = engine.frameStep(.{ .frame_index = 0, .dt_s = 0.0, .render_width = 16, .render_height = 8, }); const sdf_types = @import("sdfii_sdf").types; const nodes = [_]SceneArtifact.NativeNodeDesc{.{ .kind_id = @backingInt(sdf_types.NodeKind.sphere), .f0 = 0.5, }}; const committed_nodes = engine.compiled_artifact.?.sdfProgram().?.nodes.ptr; const world_nodes = engine.compiler.published().?.scene.?.nodes.ptr; try std.testing.expect(committed_nodes != world_nodes); try engine.publishSceneEdit(&nodes, &.{}, 0); const safe_world = engine.currentPublishedScene(); try std.testing.expect(safe_world.has_value); try std.testing.expectEqual(@intFromPtr(world_nodes), @intFromPtr(safe_world.value.scene.?.nodes.ptr)); try std.testing.expect(@intFromPtr(committed_nodes) != @intFromPtr(safe_world.value.scene.?.nodes.ptr)); try std.testing.expectError(error.NoRevisionBound, engine.currentQueryRevision()); var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); engine.artifact_allocator = failing.allocator(); const failed = engine.frameStep(.{ .frame_index = 1, .dt_s = 0.0, .render_width = 16, .render_height = 8, }); engine.artifact_allocator = std.testing.allocator; try std.testing.expectEqual(step.diagnostic_task_failed, failed.diagnostic_flags & step.diagnostic_task_failed); try std.testing.expectEqual(frame_mod.task_graph.TaskName.compile_scene, engine.last_task_run.first_failure.?.task); try std.testing.expectEqual(error.OutOfMemory, engine.last_task_run.first_failure.?.err); const failed_safe_world = engine.currentPublishedScene(); try std.testing.expect(failed_safe_world.has_value); try std.testing.expectEqual(@intFromPtr(world_nodes), @intFromPtr(failed_safe_world.value.scene.?.nodes.ptr)); try std.testing.expectError(error.NoRevisionBound, engine.currentQueryRevision()); const world_second = engine.frameStep(.{ .frame_index = 2, .dt_s = 0.0, .render_width = 16, .render_height = 8, }); try std.testing.expectEqual(world_first.published_revision, world_second.published_revision); var batch = try WorldCommands.Batch.init(std.testing.allocator, 1); defer batch.deinit(); try batch.pushDespawn(world_entity); _ = try engine.world.submit(&batch, null); const edit = engine.frameStep(.{ .frame_index = 3, .dt_s = 0.0, .render_width = 16, .render_height = 8, }); try std.testing.expect(edit.published_revision > world_second.published_revision); try expectActiveSceneAgreement(&engine, edit.published_revision); _ = try seedShapeEntity(&engine); const world_third = engine.frameStep(.{ .frame_index = 4, .dt_s = 0.0, .render_width = 16, .render_height = 8, }); try std.testing.expect(world_third.published_revision > edit.published_revision); try expectActiveSceneAgreement(&engine, world_third.published_revision);}fn expectActiveSceneAgreement(engine: *Self, revision: u64) !void { const snapshot = engine.readSceneRuntime(); const render = engine.readRenderRuntime(); try std.testing.expectEqual(revision, snapshot.published_revision); try std.testing.expectEqual(revision, snapshot.render_projection_revision); try std.testing.expectEqual(revision, try engine.currentQueryRevision()); try std.testing.expectEqual(revision, render.render_projection_revision); try std.testing.expectEqual(revision, render.frame_published_revision); try std.testing.expect(engine.queries.isBoundTo(revision)); try std.testing.expect(render.frame_valid);}test "frameStep publishes deterministic typed task products" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); _ = try seedShapeEntity(&engine); const result = engine.frameStep(.{ .frame_index = 11, .dt_s = 1.0 / 60.0, .input_event_count = 0, .render_width = 0, .render_height = 0, }); const scene_runtime = engine.readSceneRuntime(); const ui_frame = engine.readUiPublishedFrame(); try std.testing.expect(scene_runtime.collision_revision != 0); try std.testing.expect(scene_runtime.published_revision != 0); try std.testing.expectEqual(result.published_revision, scene_runtime.published_revision); try std.testing.expect(engine.queries.isBoundTo(result.published_revision)); try std.testing.expectEqual(result.frame_index, ui_frame.revision); try std.testing.expectEqual( frame_mod.task_graph.defaultGraph().tasks.len + @as(usize, result.fixed_step_count), engine.phase_count, ); try std.testing.expectEqual(@as(u32, 0), result.diagnostic_flags & step.diagnostic_task_failed); try engine.last_frame_context.stages.assertCompleted(.simulation); try engine.last_frame_context.stages.assertCompleted(.scene_compile); try engine.last_frame_context.stages.assertCompleted(.render_extract); try engine.last_frame_context.stages.assertCompleted(.render_submit); const capture = try engine.captureLastFrame(std.testing.allocator); try std.testing.expectEqual(result.frame_index, capture.frame_index); try std.testing.expectEqual(result.published_revision, capture.revisions.published_revision); try std.testing.expectEqual(scene_runtime.collision_revision, capture.revisions.collision_revision); try std.testing.expect(capture.stage_count >= frame_mod.stage_count); try std.testing.expectEqual(engine.last_frame_context.phase_timings.get(.total), capture.timings[@backingInt(frame_mod.Phase.total)].elapsed_ns); try std.testing.expectEqual(frame_mod.allocator.tag_count, capture.allocation_count); try std.testing.expectEqualStrings("frame", capture.allocations[@backingInt(frame_mod.allocator.Tag.frame)].tag); try std.testing.expect(capture.allocations[@backingInt(frame_mod.allocator.Tag.frame)].peak_bytes > 0); try std.testing.expectEqualStrings("query", capture.allocations[@backingInt(frame_mod.allocator.Tag.query)].tag); try std.testing.expect(capture.allocations[@backingInt(frame_mod.allocator.Tag.query)].current_bytes > 0); try std.testing.expectEqualStrings("demo_validation", capture.allocations[@backingInt(frame_mod.allocator.Tag.demo_validation)].tag); const latest = engine.frameHistoryLatest().?; try std.testing.expectEqual(result.frame_index, latest.frame_index); try std.testing.expectEqual(result.published_revision, latest.published_revision); try std.testing.expect(latest.task_count >= frame_mod.phase_count); try std.testing.expectEqual(frame_mod.allocator.tag_count, latest.allocation_count); try std.testing.expectEqualStrings("scene_compile", latest.allocations[@backingInt(frame_mod.allocator.Tag.scene_compile)].tag); try std.testing.expect(latest.render_graph.pass_count > 0); try std.testing.expect(latest.render_graph.resource_count > 0); try std.testing.expectEqualStrings(expectedLiveRenderBackend(&engine), latest.backend.selected_backend); try std.testing.expect(latest.backend.upload_mode.len != 0);}test "SDFII Engine physics scratch exposes its derived caller limits" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(@import("./system/physics/scratch/root.zig").Store, "sdfii_physics_scratch_public"), null, null, null, null, null, null, ); } var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 3, .physics_scratch = .{ .program_nodes = 2 }, }); defer engine.deinit(); const status = engine.readPhysicsScratch(); try std.testing.expectEqual(@as(usize, 3), status.capacity.bodies); try std.testing.expectEqual(@as(usize, 2), status.capacity.program_nodes); try std.testing.expectEqual(@as(usize, 18), status.capacity.default_loss_values); try std.testing.expect(status.capacity.storage_bytes > 0);}test "SDFII Engine physics scratch preserves the last published frame on overload" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(@import("./system/physics/scratch/root.zig").Store, "sdfii_physics_scratch_engine_full"), null, null, null, null, null, null, ); } var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 1, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const entity = try seedShapeEntity(&engine); var batch = try WorldCommands.Batch.init(std.testing.allocator, 2); defer batch.deinit(); const body = PhysicsWorld.PhysicsComponentDesc{ .collider_type = 1, .collider_radius = 0.5, }; try batch.pushUpsertComponent( entity, @backingInt(Schema.ComponentSchema.physics), std.mem.asBytes(&body), ); _ = try engine.world.submit(&batch, null); const completed = engine.frameStep(.{ .frame_index = 20, .dt_s = 1.0 / 60.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), completed.diagnostic_flags); const published_revision = completed.published_revision; const rendered_revision = engine.readRenderRuntime().frame_published_revision; const context_frame_index = engine.last_frame_context.frame_index; const history_count = engine.frame_history.count; const ui_revision = engine.readUiPublishedFrame().revision; const physics_snapshot = engine.readPhysicsRuntime(); const body_pointer = engine.physics_scratch.bodies.ptr; const overflow_entity = try seedShapeEntity(&engine); try batch.pushUpsertComponent( overflow_entity, @backingInt(Schema.ComponentSchema.physics), std.mem.asBytes(&body), ); _ = try engine.world.submit(&batch, null); const failed = engine.frameStep(.{ .frame_index = 21, .dt_s = 1.0 / 60.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(step.diagnostic_task_failed, failed.diagnostic_flags & step.diagnostic_task_failed); try std.testing.expectEqual(step.diagnostic_task_skipped, failed.diagnostic_flags & step.diagnostic_task_skipped); try std.testing.expectEqual(@as(u8, 0), failed.recompiled); try std.testing.expectEqual(@as(u8, 0), failed.render_valid); try std.testing.expectEqual(@as(u32, 0), failed.behavior_execution_count); const failure = engine.last_task_run.first_failure.?; try std.testing.expectEqual(frame_mod.task_graph.TaskName.physics, failure.task); try std.testing.expectEqual(step.TaskFailureStage.execution, failure.stage); try std.testing.expectEqual(error.PhysicsBodyCapacityExceeded, failure.err); try std.testing.expectEqual(frame_mod.task_graph.TaskState.failed, engine.last_task_run.outcomes.state(.physics)); inline for (.{ frame_mod.task_graph.TaskName.animation, frame_mod.task_graph.TaskName.compile_scene, frame_mod.task_graph.TaskName.behavior, frame_mod.task_graph.TaskName.ui_render, frame_mod.task_graph.TaskName.render, }) |task| { try std.testing.expectEqual(frame_mod.task_graph.TaskState.skipped, engine.last_task_run.outcomes.state(task)); } try std.testing.expectEqual(published_revision, failed.published_revision); const failed_scene = engine.readSceneRuntime(); try std.testing.expectEqual(published_revision, failed_scene.published_revision); try std.testing.expect(!failed_scene.recompiled); try std.testing.expectEqual(rendered_revision, engine.readRenderRuntime().frame_published_revision); try std.testing.expectEqual(ui_revision, engine.readUiPublishedFrame().revision); try std.testing.expectEqual(context_frame_index, engine.last_frame_context.frame_index); try std.testing.expectEqual(history_count, engine.frame_history.count); try std.testing.expectEqual(physics_snapshot, engine.readPhysicsRuntime()); try std.testing.expect(engine.physics_scratch.bodies.ptr == body_pointer);}test "control allocation failure preserves world and desired pose atomically" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const controller_schema: u64 = @backingInt(Schema.ComponentSchema.controller); var batch = try WorldCommands.Batch.init(std.testing.allocator, 4); defer batch.deinit(); try batch.pushSpawn("Actor", "failing-input-actor", .{ .pos_x = 0.0, .pos_y = -0.35, .pos_z = 0.0, .scale = 1.0, }); var spawned: [1]WorldStore.EntityId = undefined; _ = try engine.world.submit(&batch, spawned[0..]); const controller = ControllerComponent.ComponentDesc{ .session_id = 1, .walk_speed = 3.2, .strafe_speed = 2.4, .ground_y = -0.35, }; try batch.pushUpsertComponent(spawned[0], controller_schema, std.mem.asBytes(&controller)); _ = try engine.world.submit(&batch, null); try publishRawDeviceEventForTest(&engine, InputRuntime.event_key_down, InputRuntime.key_d, 1.0); const before = engine.world.transformDesc(spawned[0]) orelse return error.TestUnexpectedResult; try std.testing.expect(engine.physics.desiredTransform(spawned[0]) == null); var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); const allocator = engine.allocator; engine.allocator = failing.allocator(); const failed = engine.frameStep(.{ .frame_index = 30, .dt_s = 1.0 / 60.0, .input_event_count = 1, }); engine.allocator = allocator; const failure = engine.last_task_run.first_failure.?; try std.testing.expectEqual(frame_mod.task_graph.TaskName.control, failure.task); try std.testing.expectEqual(step.TaskFailureStage.execution, failure.stage); try std.testing.expectEqual(error.OutOfMemory, failure.err); try std.testing.expectEqual(frame_mod.task_graph.TaskState.failed, engine.last_task_run.outcomes.state(.control)); inline for (.{ frame_mod.task_graph.TaskName.compile_collision, frame_mod.task_graph.TaskName.physics, frame_mod.task_graph.TaskName.animation, frame_mod.task_graph.TaskName.compile_scene, frame_mod.task_graph.TaskName.behavior, frame_mod.task_graph.TaskName.ui_render, frame_mod.task_graph.TaskName.render, }) |task| try std.testing.expectEqual(frame_mod.task_graph.TaskState.skipped, engine.last_task_run.outcomes.state(task)); try std.testing.expectEqual(step.diagnostic_task_failed, failed.diagnostic_flags & step.diagnostic_task_failed); try std.testing.expectEqual(step.diagnostic_task_skipped, failed.diagnostic_flags & step.diagnostic_task_skipped); try std.testing.expectEqual(before, engine.world.transformDesc(spawned[0]).?); try std.testing.expect(engine.physics.desiredTransform(spawned[0]) == null);}test "animation shape parameter failure preserves binding and world publication" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const entity = try seedShapeEntity(&engine); try engine.bindShapeAsset(entity, "sphere"); try engine.bindShapeParam(entity, "radius", 0.0); var clip = try curve.ParameterClip.init(std.testing.allocator, "radius", 1.0, 1); defer clip.deinit(std.testing.allocator); _ = try clip.addCurve(std.testing.allocator, &.{ 0.0, 1.0 }, &.{ 1.0, 2.0 }, 1); var mixer = try curve.ParameterMixer.init(std.testing.allocator, 2); defer mixer.deinit(std.testing.allocator); var fsm = try curve.ParameterMachine.init(std.testing.allocator, &mixer, 2, 1); defer fsm.deinit(std.testing.allocator); const state = try fsm.addState(&clip, "radius", .{ .loop = false }); try fsm.setInitialState(state); const controller_id = try engine.animation.attachFsm(std.testing.allocator, &fsm); try engine.animation.bindShapeParam( std.testing.allocator, &engine.world, controller_id, entity, "radius", 0, 1.0, 0.0, ); const completed = engine.frameStep(.{ .frame_index = 35, .dt_s = 0.0 }); try std.testing.expectEqual(@as(u32, 0), completed.diagnostic_flags); var binding = try shape_binding.readAssetBinding(std.testing.allocator, &engine.world, entity); defer binding.deinit(std.testing.allocator); try std.testing.expectEqual(@as(f32, 1.0), binding.params.get("radius").?); const shape_schema_id: u64 = @backingInt(Schema.ComponentSchema.shape); const before_component = engine.world.components.get(entity.index, shape_schema_id) orelse return error.TestUnexpectedResult; const before_payload = try std.testing.allocator.dupe(u8, before_component.payload.slice()); defer std.testing.allocator.free(before_payload); var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); const allocator = engine.allocator; engine.allocator = failing.allocator(); const failed = engine.frameStep(.{ .frame_index = 36, .dt_s = 0.5 }); engine.allocator = allocator; const failure = engine.last_task_run.first_failure.?; try std.testing.expectEqual(frame_mod.task_graph.TaskName.animation, failure.task); try std.testing.expectEqual(error.OutOfMemory, failure.err); try std.testing.expectEqual(frame_mod.task_graph.TaskState.failed, engine.last_task_run.outcomes.state(.animation)); inline for (.{ frame_mod.task_graph.TaskName.compile_scene, frame_mod.task_graph.TaskName.behavior, frame_mod.task_graph.TaskName.ui_render, frame_mod.task_graph.TaskName.render, }) |task| try std.testing.expectEqual(frame_mod.task_graph.TaskState.skipped, engine.last_task_run.outcomes.state(task)); try std.testing.expectEqual(step.diagnostic_task_failed, failed.diagnostic_flags & step.diagnostic_task_failed); try std.testing.expectEqual(step.diagnostic_task_skipped, failed.diagnostic_flags & step.diagnostic_task_skipped); var updated_binding = try shape_binding.readAssetBinding(std.testing.allocator, &engine.world, entity); defer updated_binding.deinit(std.testing.allocator); try std.testing.expectEqual(@as(f32, 1.0), updated_binding.params.get("radius").?); const after_component = engine.world.components.get(entity.index, shape_schema_id) orelse return error.TestUnexpectedResult; try std.testing.expectEqualSlices(u8, before_payload, after_component.payload.slice());}test "shape binding API edits the active play world without mutating authoring" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const entity = try seedShapeEntity(&engine); try engine.enterPlay(); try engine.bindShapeParam(entity, "radius", 2.0); var runtime_binding = try shape_binding.readAssetBinding(std.testing.allocator, engine.activeWorldConst(), entity); defer runtime_binding.deinit(std.testing.allocator); try std.testing.expectEqual(@as(f32, 2.0), runtime_binding.params.get("radius").?); var authoring_while_playing = try shape_binding.readAssetBinding(std.testing.allocator, &engine.world, entity); defer authoring_while_playing.deinit(std.testing.allocator); try std.testing.expectEqual(@as(f32, 1.0), authoring_while_playing.params.get("radius").?); engine.exitPlay(); var authoring_after_exit = try shape_binding.readAssetBinding(std.testing.allocator, &engine.world, entity); defer authoring_after_exit.deinit(std.testing.allocator); try std.testing.expectEqual(@as(f32, 1.0), authoring_after_exit.params.get("radius").?);}test "UI input allocation failure preserves interaction and published frame" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const root_id = 61; try publishInputCaptureSurfaceForTest(&engine, root_id, false); const completed = engine.frameStep(.{ .frame_index = 40, .dt_s = 1.0 / 60.0 }); try std.testing.expectEqual(@as(u32, 0), completed.diagnostic_flags); const published = engine.readUiPublishedFrame().*; const history_count = engine.frame_history.count; try engine.publishUiCommand(.{ .pointer_event = .{ .root_id = root_id, .kind = .down, .x = 10, .y = 10, } }); engine.ui.input.pointers_staging.deinit(); var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); engine.ui.input.pointers_staging = @TypeOf(engine.ui.input.pointers_staging).init(failing.allocator()); const failed = engine.frameStep(.{ .frame_index = 41, .dt_s = 1.0 / 60.0 }); const failure = engine.last_task_run.first_failure.?; try std.testing.expectEqual(frame_mod.task_graph.TaskName.ui_input, failure.task); try std.testing.expectEqual(error.OutOfMemory, failure.err); try std.testing.expectEqual(frame_mod.task_graph.TaskState.failed, engine.last_task_run.outcomes.state(.ui_input)); try std.testing.expectEqual(step.diagnostic_task_failed, failed.diagnostic_flags & step.diagnostic_task_failed); try std.testing.expectEqual(step.diagnostic_task_skipped, failed.diagnostic_flags & step.diagnostic_task_skipped); const after = engine.readUiPublishedFrame(); try std.testing.expectEqual(published.revision, after.revision); try std.testing.expectEqual(published.root_count, after.root_count); try std.testing.expectEqual(published.widget_count, after.widget_count); try std.testing.expectEqual(published.roots.ptr, after.roots.ptr); try std.testing.expectEqual(published.widgets.ptr, after.widgets.ptr); try std.testing.expect(!engine.ui.capturesInput(root_id)); try std.testing.expectEqual(history_count, engine.frame_history.count);}test "UI render allocation failure preserves the last published UI frame" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const root_id = 71; try publishInputCaptureSurfaceForTest(&engine, root_id, true); const completed = engine.frameStep(.{ .frame_index = 50, .dt_s = 1.0 / 60.0 }); try std.testing.expectEqual(@as(u32, 0), completed.diagnostic_flags); const published = engine.readUiPublishedFrame().*; const history_count = engine.frame_history.count; var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); const allocator = engine.ui.renderer.session.allocator; engine.ui.renderer.session.allocator = failing.allocator(); const failed = engine.frameStep(.{ .frame_index = 51, .dt_s = 1.0 / 60.0 }); engine.ui.renderer.session.allocator = allocator; const failure = engine.last_task_run.first_failure.?; try std.testing.expectEqual(frame_mod.task_graph.TaskName.ui_render, failure.task); try std.testing.expectEqual(error.OutOfMemory, failure.err); try std.testing.expectEqual(frame_mod.task_graph.TaskState.failed, engine.last_task_run.outcomes.state(.ui_render)); try std.testing.expectEqual(frame_mod.task_graph.TaskState.skipped, engine.last_task_run.outcomes.state(.render)); try std.testing.expectEqual(step.diagnostic_task_failed, failed.diagnostic_flags & step.diagnostic_task_failed); try std.testing.expectEqual(step.diagnostic_task_skipped, failed.diagnostic_flags & step.diagnostic_task_skipped); const after = engine.readUiPublishedFrame(); try std.testing.expectEqual(published.revision, after.revision); try std.testing.expectEqual(published.root_count, after.root_count); try std.testing.expectEqual(published.widget_count, after.widget_count); try std.testing.expectEqual(published.roots.ptr, after.roots.ptr); try std.testing.expectEqual(published.widgets.ptr, after.widgets.ptr); try std.testing.expectEqual(history_count, engine.frame_history.count);}test "render allocation failure preserves the last complete render product" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); _ = try seedShapeEntity(&engine); const completed = engine.frameStep(.{ .frame_index = 60, .dt_s = 1.0 / 60.0, .render_width = 8, .render_height = 8, }); try std.testing.expectEqual(@as(u32, 0), completed.diagnostic_flags); const rendered = engine.readRenderFrame(); try std.testing.expect(rendered.valid); const history_count = engine.frame_history.count; var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); const allocator = engine.renderer.allocator; engine.renderer.allocator = failing.allocator(); const failed = engine.frameStep(.{ .frame_index = 61, .dt_s = 1.0 / 60.0, .render_width = 16, .render_height = 16, }); engine.renderer.allocator = allocator; const failure = engine.last_task_run.first_failure.?; try std.testing.expectEqual(frame_mod.task_graph.TaskName.render, failure.task); try std.testing.expectEqual(error.OutOfMemory, failure.err); try std.testing.expectEqual(frame_mod.task_graph.TaskState.failed, engine.last_task_run.outcomes.state(.render)); try std.testing.expectEqual(step.diagnostic_task_failed, failed.diagnostic_flags & step.diagnostic_task_failed); try std.testing.expectEqual(@as(u32, 0), failed.diagnostic_flags & step.diagnostic_task_skipped); const after = engine.readRenderFrame(); try std.testing.expect(after.valid); try std.testing.expectEqual(rendered.width, after.width); try std.testing.expectEqual(rendered.height, after.height); try std.testing.expectEqual(rendered.published_revision, after.published_revision); try std.testing.expectEqual(rendered.pixels.ptr, after.pixels.ptr); try std.testing.expectEqual(history_count, engine.frame_history.count);}test "frameStep applies ClientInput during control phase" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const controller_schema: u64 = @backingInt(Schema.ComponentSchema.controller); var batch = try WorldCommands.Batch.init(std.testing.allocator, 4); defer batch.deinit(); try batch.pushSpawn("Actor", "input-actor", .{ .pos_x = 0.0, .pos_y = -0.35, .pos_z = 0.0, .scale = 1.0, }); var spawned: [1]WorldStore.EntityId = undefined; _ = try engine.world.submit(&batch, spawned[0..]); const controller = ControllerComponent.ComponentDesc{ .session_id = 1, .walk_speed = 3.2, .strafe_speed = 2.4, .ground_y = -0.35, }; try batch.pushUpsertComponent(spawned[0], controller_schema, std.mem.asBytes(&controller)); _ = try engine.world.submit(&batch, null); try publishRawDeviceEventForTest(&engine, InputRuntime.event_key_down, InputRuntime.key_d, 1.0); _ = engine.frameStep(.{ .frame_index = 0, .dt_s = 1.0 / 60.0, .input_event_count = 1, .render_width = 0, .render_height = 0, }); const moved = engine.world.transformDesc(spawned[0]) orelse return error.TestUnexpectedResult; try std.testing.expect(moved.pos_x > 0.0);}test "focused UI suppresses held movement for its bound input session" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const ui_root_id = 41; const input_session_id = 7; try engine.bindUiRootToInputSession(ui_root_id, input_session_id); try publishInputCaptureSurfaceForTest(&engine, ui_root_id, true); stepInputCaptureFrameForTest(&engine, 1, 0); try engine.publishInputCommand(.{ .raw_device_event = .{ .session_id = input_session_id, .event_type = InputRuntime.event_key_down, .code = InputRuntime.key_w, .value = 1, } }); stepInputCaptureFrameForTest(&engine, 2, 1); try std.testing.expectEqual(@as(f32, 1), engine.readClientInput(input_session_id).?.motion_y); try engine.publishUiCommand(.{ .key_event = .{ .root_id = ui_root_id, .key_code = Ui.key_tab, .modifiers = 0, } }); stepInputCaptureFrameForTest(&engine, 3, 1); try expectSuppressedInputForTest(engine.readClientInput(input_session_id).?); try engine.publishUiCommand(.{ .key_event = .{ .root_id = ui_root_id, .key_code = Ui.key_escape, .modifiers = 0, } }); stepInputCaptureFrameForTest(&engine, 4, 1); try std.testing.expectEqual(@as(f32, 1), engine.readClientInput(input_session_id).?.motion_y);}test "pointer-captured UI suppresses held movement for its bound input session" { var engine = Self.init(std.testing.allocator, .{ .entity_capacity = 8, .physics_scratch = .{ .program_nodes = 0 } }); defer engine.deinit(); const ui_root_id = 51; const input_session_id = 9; try engine.bindUiRootToInputSession(ui_root_id, input_session_id); try publishInputCaptureSurfaceForTest(&engine, ui_root_id, false); stepInputCaptureFrameForTest(&engine, 1, 0); try engine.publishInputCommand(.{ .raw_device_event = .{ .session_id = input_session_id, .event_type = InputRuntime.event_key_down, .code = InputRuntime.key_w, .value = 1, } }); stepInputCaptureFrameForTest(&engine, 2, 1); try std.testing.expectEqual(@as(f32, 1), engine.readClientInput(input_session_id).?.motion_y); try engine.publishUiCommand(.{ .pointer_event = .{ .root_id = ui_root_id, .kind = .down, .x = 10, .y = 10, } }); stepInputCaptureFrameForTest(&engine, 3, 1); try expectSuppressedInputForTest(engine.readClientInput(input_session_id).?); try engine.publishUiCommand(.{ .pointer_event = .{ .root_id = ui_root_id, .kind = .up, .x = 10, .y = 10, } }); stepInputCaptureFrameForTest(&engine, 4, 1); try std.testing.expectEqual(@as(f32, 1), engine.readClientInput(input_session_id).?.motion_y);}fn publishInputCaptureSurfaceForTest(engine: *Self, root_id: u64, with_focusable_child: bool) !void { const model = @import("gui").model; const children = [_]model.UiNode{.{ .widget_id = 2, .kind = .button, .size = .{ .width = 40, .height = 20 }, .focusable = true, }}; const tree = UiSurfaceTree{ .available_size = .{ .width = 100, .height = 40 }, .root = .{ .widget_id = 1, .size = .{ .width = 100, .height = 40 }, .children = if (with_focusable_child) &children else &.{}, }, }; try engine.publishUiSurface(root_id, 1, .{ .tree = &tree });}fn stepInputCaptureFrameForTest(engine: *Self, frame_index: u64, input_event_count: usize) void { _ = engine.worldStep(.{ .command_count = 0 }, .{ .frame_index = frame_index, .dt_s = 1.0 / 60.0, .input_event_count = input_event_count, });}fn expectSuppressedInputForTest(snapshot: ClientInputSnapshot) !void { try std.testing.expectEqual(@as(f32, 0), snapshot.motion_x); try std.testing.expectEqual(@as(f32, 0), snapshot.motion_y); try std.testing.expectEqual(@as(f32, 0), snapshot.look_x); try std.testing.expectEqual(@as(f32, 0), snapshot.look_y); try std.testing.expectEqual(@as(u32, 0), snapshot.verb_bits);}fn publishRawDeviceEventForTest( engine: *Self, event_type: u8, code: u32, value: f32,) !void { try engine.publishInputCommand(.{ .raw_device_event = .{ .session_id = 1, .event_type = event_type, .code = code, .value = value, } });}Source: fun/sdfii/src/root.zig:14
pub const Engine = engine.Engine;Source: fun/sdfii/src/engine/system/ui/command.zig:6
pub const UiPublishedFrame = model.UiFrame;Source: fun/sdfii/src/engine/system/ui/command.zig:5
Source: fun/sdfii/src/engine/system/ui/command.zig:4
pub const UiSurfaceTree = model.UiSurfaceTree;Complete caller list for Engine.deinit
16 direct callers.
fun.sdfii.src.engine.abi.scene.test.test_push_scene_low-level_API_replaces_the_typed_edit_publication[function] — test; no exact target atfun/sdfii/src/engine/abi/scene/test.zig:247in nearest public ownerfun.sdfii.src.engine.abi.scene.testtiny.gui.paint.Compositor.deinit[method] atlib/gui/src/paint/compositor.zig:67tiny.quic.connection.Connection.deinit[method] atlib/quic/src/connection/machine.zig:252lib.quic.src.properties.tls.DecoderTotality.property[function] — private source atlib/quic/src/properties/tls.zig:34in nearest public ownerlib.quic.src.properties.tlstiny.sql.history.repair.repair[function] atlib/sql/src/history/repair.zig:353lib.ui.src.style.test.test_a_5000_row_scroll_carries_survivors_and_visits_only_style_inputs[function] — test source atlib/ui/src/style/test.zig:273in nearest public ownerlib.ui.src.style.testlib.ui.src.style.test.test_ancestor_class_invalidation_walks_descendants_only_for_named_classes[function] — test source atlib/ui/src/style/test.zig:429in nearest public ownerlib.ui.src.style.testlib.ui.src.style.test.test_default_semantic_chrome_parses_without_diagnostics[function] — test source atlib/ui/src/style/test.zig:474in nearest public ownerlib.ui.src.style.testlib.ui.src.style.test.test_focus_visible_begins_with_keyboard_focus_and_clears_for_pointer_focus[function] — test source atlib/ui/src/style/test.zig:540in nearest public ownerlib.ui.src.style.testlib.ui.src.style.test.test_hover_crossing_two_row_boundaries_restyles_eighteen_nodes[function] — test source atlib/ui/src/style/test.zig:161in nearest public ownerlib.ui.src.style.testlib.ui.src.style.test.test_hover_crossing_visits_eighteen_nodes_in_a_16384_node_tree[function] — test source atlib/ui/src/style/test.zig:217in nearest public ownerlib.ui.src.style.testlib.ui.src.style.test.test_hover_invalidation_covers_ancestor_kind_selectors[function] — test source atlib/ui/src/style/test.zig:582in nearest public ownerlib.ui.src.style.testlib.ui.src.style.test.test_inherited_variables_distinguish_children_of_otherwise_shared_parents[function] — test source atlib/ui/src/style/test.zig:631in nearest public ownerlib.ui.src.style.testlib.ui.src.style.test.test_reparenting_a_hovered_node_updates_both_ancestor_chains[function] — test source atlib/ui/src/style/test.zig:381in nearest public ownerlib.ui.src.style.testlib.ui.src.style.test.test_reparenting_refreshes_old_and_new_sibling_positions[function] — test source atlib/ui/src/style/test.zig:334in nearest public ownerlib.ui.src.style.testlib.ui.src.style.test.test_stylesheet_change_refreshes_hover_sensitivity_of_copied_ancestors[function] — test source atlib/ui/src/style/test.zig:605in nearest public ownerlib.ui.src.style.test
Audit
| Definitions | 129 |
|---|---|
| Public names | 129 |
| Members | 85 |
| Version | 26.7.0 |
| Revision | daab053ee433 |