AI System
AstraWeave’s AI system is the core differentiator of the engine. Unlike traditional game engines where AI is an afterthought, AstraWeave treats AI agents as first-class citizens that interact with the game world through validated tools.
In AstraWeave, AI agents cannot cheat. They must use the same validated game systems as players, ensuring fair and emergent gameplay.
Architecture Overview
graph TB
subgraph "AI Pipeline"
Perception[Perception Bus]
Memory[Agent Memory]
Planning[Planning Layer]
Tools[Tool Sandbox]
Validation[Engine Validation]
end
subgraph "Game World"
ECS[ECS World State]
Physics[Physics System]
Nav[Navigation]
end
ECS --> Perception
Perception --> Memory
Memory --> Planning
Planning --> Tools
Tools --> Validation
Validation --> ECS
Core Components
Perception Bus
The perception system provides AI agents with a filtered view of the world:
#![allow(unused)]
fn main() {
use astraweave_ai::perception::*;
#[derive(Component)]
struct AiAgent {
perception_radius: f32,
perception_filter: PerceptionFilter,
}
fn perception_system(
agents: Query<(Entity, &Transform, &AiAgent)>,
percievables: Query<(Entity, &Transform, &Percievable)>,
mut perception_bus: ResMut<PerceptionBus>,
) {
for (agent_entity, agent_transform, agent) in agents.iter() {
let mut percepts = Vec::new();
for (target, target_transform, percievable) in percievables.iter() {
let distance = agent_transform.translation
.distance(target_transform.translation);
if distance <= agent.perception_radius {
percepts.push(Percept {
entity: target,
position: target_transform.translation,
category: percievable.category,
properties: percievable.properties.clone(),
});
}
}
perception_bus.update(agent_entity, percepts);
}
}
}
See Perception Bus for details.
Planning Layer
AI agents use LLM-based planning to decide actions:
#![allow(unused)]
fn main() {
use astraweave_ai::planning::*;
fn planning_system(
mut agents: Query<(&mut AiPlanner, &PerceptionState)>,
llm: Res<LlmClient>,
) {
for (mut planner, perception) in agents.iter_mut() {
if planner.needs_replan() {
let context = build_context(perception);
match llm.plan(&context, &planner.available_tools) {
Ok(plan) => planner.set_plan(plan),
Err(e) => planner.fallback_behavior(),
}
}
}
}
}
See Planning Layer for details.
Tool Sandbox
All AI actions go through the tool sandbox for validation:
#![allow(unused)]
fn main() {
use astraweave_ai::tools::*;
fn execute_tool_system(
mut agents: Query<&mut AiPlanner>,
mut tool_executor: ResMut<ToolExecutor>,
validator: Res<ToolValidator>,
) {
for mut planner in agents.iter_mut() {
if let Some(tool_call) = planner.next_action() {
match validator.validate(&tool_call) {
ValidationResult::Valid => {
tool_executor.execute(tool_call);
}
ValidationResult::Invalid(reason) => {
planner.action_failed(reason);
}
}
}
}
}
}
See Tool Sandbox for details.
Behavior Trees
For deterministic, reactive behaviors:
#![allow(unused)]
fn main() {
use astraweave_behavior::*;
let patrol_tree = BehaviorTree::new(
Selector::new(vec![
Sequence::new(vec![
Condition::new(|ctx| ctx.enemy_visible()),
Action::new(|ctx| ctx.engage_combat()),
]),
Sequence::new(vec![
Condition::new(|ctx| ctx.at_patrol_point()),
Action::new(|ctx| ctx.next_patrol_point()),
]),
Action::new(|ctx| ctx.move_to_patrol_point()),
])
);
}
See Behavior Trees for details.
AI Agent Configuration
Basic Agent Setup
#![allow(unused)]
fn main() {
fn spawn_companion(world: &mut World) -> Entity {
world.spawn((
Transform::default(),
AiAgent::new()
.with_personality("friendly and helpful")
.with_perception_radius(15.0)
.with_tick_budget(Duration::from_millis(8)),
PerceptionState::default(),
AiPlanner::new(vec![
Tool::move_to(),
Tool::attack(),
Tool::use_item(),
Tool::speak(),
]),
NavAgent::default(),
DialogueCapable::default(),
))
}
}
Tick Budget
AI has a strict time budget per simulation tick:
#![allow(unused)]
fn main() {
let config = AiConfig {
tick_budget_ms: 8,
max_concurrent_plans: 4,
plan_cache_duration: Duration::from_secs(1),
fallback_on_timeout: true,
};
}
If planning exceeds the budget, agents use cached plans or fallback behaviors.
LLM Integration
Ollama Setup
AstraWeave uses Ollama for local LLM inference:
ollama serve
ollama pull hermes2-pro-mistral
Configuration
#![allow(unused)]
fn main() {
let llm_config = LlmConfig {
endpoint: "http://localhost:11434".into(),
model: "hermes2-pro-mistral".into(),
temperature: 0.7,
max_tokens: 256,
timeout: Duration::from_millis(100),
};
}
Tool Calling
AstraWeave uses structured tool calling:
#![allow(unused)]
fn main() {
let tools = vec![
ToolDefinition {
name: "move_to",
description: "Move to a target location",
parameters: json!({
"type": "object",
"properties": {
"target": { "type": "array", "items": { "type": "number" } }
}
}),
},
];
let response = llm.generate_with_tools(prompt, &tools).await?;
}
Performance Considerations
Batching
Group AI queries for efficiency:
#![allow(unused)]
fn main() {
let batch = agents.iter()
.filter(|a| a.needs_replan())
.take(4)
.collect::<Vec<_>>();
let results = llm.batch_plan(&batch).await;
}
Caching
Cache plans to reduce LLM calls:
#![allow(unused)]
fn main() {
let planner = AiPlanner::new(tools)
.with_plan_cache(Duration::from_secs(2))
.with_context_hash(true);
}
Fallback Behaviors
Always define fallbacks:
#![allow(unused)]
fn main() {
impl AiAgent {
fn fallback_behavior(&self) -> Action {
match self.role {
Role::Companion => Action::FollowPlayer,
Role::Guard => Action::Patrol,
Role::Merchant => Action::Idle,
}
}
}
}
Subsections
- Perception Bus - How AI perceives the world
- Planning Layer - LLM-based decision making
- Tool Sandbox - Validated action execution
- Behavior Trees - Deterministic reactive behaviors