Goal: Let the Programmer role (and eventually Creator) safely change the world at runtime: spawn/move/enable objects, switch scenes, and more.
Core idea: Lua does not touch Unity directly. Lua calls a whitelist API that publishes a typed command on the bus. The Unity layer executes the action on the main thread.
- LLM →
ApplyAiGameCommandwithCommandTypeId = AiEnvelope LuaAiEnvelopeProcessorextracts Lua and runs it inSecureLuaEnvironment- Lua calls
coreai_world_*→ publishesApplyAiGameCommandwithCommandTypeId = WorldCommand AiGameCommandRouteron the main thread callsICoreAiWorldCommandExecutor.TryExecute(...)
This preserves:
- Main-thread safety for Unity
- Control / logging via MessagePipe and
traceId(when present) - Extensibility via interfaces and registries
Direct MEAI tool calls through WorldLlmTool use the same safety rule: before invoking
ICoreAiWorldCommandExecutor.TryExecute(...), the tool switches to the Unity main thread.
Built-in functions:
coreai_world_spawn(prefabKeyOrName, targetName, x, y, z) -> boolcoreai_world_move(targetName, x, y, z)coreai_world_destroy(targetName)coreai_world_set_active(targetName, active)coreai_world_load_scene(sceneName)coreai_world_reload_scene()coreai_world_play_animation(targetName, animationName)coreai_world_list_animations(targetName)coreai_world_show_text(targetName, textToDisplay)coreai_world_apply_force(targetName, fx, fy, fz)coreai_world_spawn_particles(targetName, prefabKeyOrName)coreai_world_list_objects(searchPattern)
- prefabKeyOrName: Prefer a GUID string (or another stable id) or a prefab name from the registry.
- targetName: Scene object name (
GameObjectname). Commands resolve objects dynamically viaGameObject.Find(). - Animation commands:
coreai_world_play_animation,coreai_world_list_animations, and directworld_commandactionsplay_animation/list_animationsrequiretargetName; pass it as a structured argument, not only in prose.
Spawning goes only through CoreAiPrefabRegistryAsset (ScriptableObject registry).
- Create asset: Create → CoreAI → World → Prefab Registry
- Fill Key (GUID string) and/or Name, assign Prefab
- On
CoreAILifetimeScope, assign World Prefab Registry
If the registry is not assigned, spawn requests are rejected.
Options:
- A (recommended): Extend
ICoreAiWorldCommandExecutorwith your implementation (or a composition wrapper), add newactionvalues in the JSON envelope, and handle them on the main thread. - B: A separate
WorldCommandRouteron MessagePipe that subscribes toApplyAiGameCommandand handles onlyWorldCommand(if you want full isolation fromAiGameCommandRouter).
Direct reflection from Lua is risky. To “mutate object data,” do it through:
- An allowlist of types/fields/methods
- A dedicated
IWorldReflectionPolicy(project layer) - A set of strictly typed commands (e.g.
set_transform,add_force,set_anim_trigger)
By default in the template:
- World Commands are enabled (Lua API registered).
- Spawning requires an assigned
CoreAiPrefabRegistryAsset.
Configurable via CoreAILifetimeScope Inspector:
- Assign or disable the prefab registry
- Replace or wrap the command executor
- EditMode:
WorldCommandLuaBindingsEditModeTests— verifies Lua publishesWorldCommandwith valid JSON. - PlayMode (recommended for your title): smoke test on a scene where
coreai_world_spawncreates an object from the registry.