extends Node class QueryHost: extends RefCounted func lua_query(_entity_id: int, query_name: String, params: Dictionary) -> Variant: print("lua_query: ", query_name, " ", params) match query_name: "sense.is_walkable": return params.get("x", 0) == 2 and params.get("y", 0) == 3 "sense.find_nearest": return { "entity_id": 7, "distance": 4, "world_x": 32.0, "world_y": 48.0, "group": params.get("group", ""), } _: return null var query_host := QueryHost.new() func _ready() -> void: var rt := OneBitLuaRuntime.new() rt.set_entity_id(42) rt.set_query_host(query_host) rt.set_instruction_budget(10000) var loaded := rt.load_string(""" return { api_version = 2, on_update = function(ctx, state) state.count = (state.count or 0) + 1 state.world_x = ctx.self.world.x state.tint_r = ctx.self.tint.r if not ctx.self.is_moving and sense.is_walkable(2, 3) then local nearest = sense.find_nearest("players") move.step(1, 0) log.say("tick " .. tostring(state.count) .. " target " .. tostring(nearest.entity_id), 1.0) end end } """) if not loaded.ok: push_error("load_string failed: " + str(loaded.error)) get_tree().quit(1) return var ctx := { "self": { "is_moving": false, "world": Vector2(2.0, 3.0), "tint": Color(0.25, 0.5, 0.75, 1.0), }, } var state := {} var result := rt.call_event("on_update", ctx, state, []) if not result.ok: push_error("call_event failed: " + str(result.error)) get_tree().quit(1) return if result.commands.size() != 1 or result.logs.size() != 1 or result.state.get("count", 0) != 1 or result.state.get("world_x", 0) != 2 or result.state.get("tint_r", 0) != 0.25 or result.logs[0].message != "tick 1 target 7": push_error("unexpected result: " + str(result)) get_tree().quit(1) return if not _expect_runtime_error(""" return { api_version = 2, on_update = function(ctx, state) sense.is_walkable = function() return true end end } """, "read-only"): get_tree().quit(1) return if not _expect_runtime_error(""" return { api_version = 2, on_update = function(ctx, state) sense = nil end } """, "globals are read-only"): get_tree().quit(1) return var mem_rt := OneBitLuaRuntime.new() mem_rt.set_entity_id(42) mem_rt.set_query_host(query_host) mem_rt.set_instruction_budget(1000000) mem_rt.set_memory_budget(1024 * 1024) var mem_loaded := mem_rt.load_string(""" return { api_version = 2, on_update = function(ctx, state) local chunks = {} for i = 1, 10000 do chunks[i] = string.rep("x", 4000) .. tostring(i) end state.done = true end } """) if not mem_loaded.ok: push_error("memory test failed to load: " + str(mem_loaded.error)) get_tree().quit(1) return var mem_result := mem_rt.call_event("on_update", ctx, {}, []) if mem_result.ok or not str(mem_result.error).contains("memory budget"): push_error("memory budget test did not fail as expected: " + str(mem_result)) get_tree().quit(1) return print("OneBitLuaRuntime smoke test passed: ", result) get_tree().quit(0) func _expect_runtime_error(source: String, expected_error: String) -> bool: var rt := OneBitLuaRuntime.new() rt.set_entity_id(42) rt.set_query_host(query_host) var loaded := rt.load_string(source) if not loaded.ok: push_error("expected load success, got: " + str(loaded.error)) return false var result := rt.call_event("on_update", {"self": {"is_moving": false}}, {}, []) if result.ok: push_error("expected runtime error, got success: " + str(result)) return false if not str(result.error).contains(expected_error) or not str(result.error).contains("stack traceback"): push_error("unexpected runtime error: " + str(result.error)) return false return true