Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 83 additions & 1 deletion Engine/apps/agent-backend/src/editor_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde_json::{Value, json};
pub fn tools() -> Vec<Value> {
let empty = json!({"type":"object","properties":{},"additionalProperties":false});
let scene = json!({"type":"object","required":["path"],"properties":{"path":{"type":"string","description":"Relative to the configured scenes folder, e.g. levels/forest.scene"}},"additionalProperties":false});
let mut tools = Vec::new();
let mut tools = texture_tools();
for (name, description, schema) in [
(
"editor_status",
Expand Down Expand Up @@ -104,3 +104,85 @@ pub fn tools() -> Vec<Value> {
pub fn contains(name: &str) -> bool {
tools().iter().any(|tool| tool["name"] == name)
}

fn texture_tools() -> Vec<Value> {
let slots = [
"BaseColor",
"Normal",
"Roughness",
"Metallic",
"AmbientOcclusion",
"Height",
"Emissive",
"Clearcoat",
"ClearcoatRoughness",
"ClearcoatNormal",
"Transmission",
"Thickness",
"SheenColor",
"SheenRoughness",
"Specular",
"SpecularColor",
"Anisotropy",
];
let maps: serde_json::Map<String, Value> = slots
.into_iter()
.map(|slot| (slot.into(), json!({"type":["string","null"]})))
.collect();
let pair = json!({"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2});
let edit = json!({"type":"object","required":["entity_id"],"additionalProperties":false,"properties":{
"entity_id":{"type":"string"},
"maps":{"type":"object","properties":maps,"additionalProperties":false,"description":"Partial slot update. Project-relative PNG/JPEG/TGA/HDR paths; null clears a slot. Unspecified slots remain."},
"clear_maps":{"type":"boolean","description":"Clear existing slots before applying maps; retains the texture component and controls."},
"uv_scale":pair,"uv_offset":pair,
"height_scale":{"type":"number","minimum":-100,"maximum":100},
"emissive_strength":{"type":"number","minimum":0,"maximum":10000}
}});
let mut child = edit.clone();
child["properties"]["name"] =
json!({"type":"string","description":"Optional child name; defaults to Texture."});
let color =
json!({"type":"array","items":{"type":"number","minimum":0},"minItems":3,"maxItems":3});
let vector = json!({"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3});
let positive = json!({"type":"number","minimum":0});
vec![
json!({"name":"environment_update","description":"Patch the scene sky/atmosphere through editor undo. Use editor_status.environment for current settings. A project-relative 2:1 HDR panorama belongs in sky_image; empty restores solid sky. Validates and imports the image before editing. Save with scene_save.","inputSchema":{"type":"object","required":["settings"],"additionalProperties":false,"properties":{"settings":{"type":"object","additionalProperties":false,"properties":{
"enabled":{"type":"boolean"},"sky_image":{"type":"string"},"sky_color":color,"rotation_degrees":{"type":"number"},"exposure":{"type":"number","minimum":-20,"maximum":20},
"ambient_color":color,"ambient_intensity":positive,"sun_color":color,"sun_intensity":positive,"sun_direction":vector,"haze_color":color,"haze_density":positive,"haze_start":positive
}}}}}),
json!({"name":"texture_inspect","description":"Inspect direct texture maps, effective maps after Texture-child overrides, and immediate texture children for an object in the live editor.","inputSchema":{"type":"object","required":["entity_id"],"properties":{"entity_id":{"type":"string"}},"additionalProperties":false}}),
json!({"name":"texture_update","description":"Atomically edit optional HDR/PBR slots and UV/intensity controls on a mesh, primitive or existing Texture child through editor undo. Paths must be existing project-relative images. Validates all images before attaching; use null to clear slots. Save with scene_save.","inputSchema":edit}),
json!({"name":"texture_create_child","description":"Create a Texture child of the mesh/primitive identified by entity_id, optionally with maps and controls, as one undoable scene edit. Child maps apply only to the immediate parent. Save with scene_save.","inputSchema":child}),
]
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn texture_schema_tracks_renderer_slots_and_exposes_partial_updates() {
let tools = tools();
let update = tools
.iter()
.find(|t| t["name"] == "texture_update")
.unwrap();
let properties = &update["inputSchema"]["properties"];
let maps = properties["maps"]["properties"].as_object().unwrap();
assert_eq!(maps.len(), engine_world::TextureMap::ALL.len());
for map in engine_world::TextureMap::ALL {
let name = serde_json::to_value(map).unwrap();
assert_eq!(
maps[name.as_str().unwrap()]["type"],
json!(["string", "null"])
);
}
assert_eq!(update["inputSchema"]["additionalProperties"], false);
for name in [
"texture_inspect",
"texture_create_child",
"environment_update",
] {
assert!(contains(name));
}
}
}
15 changes: 14 additions & 1 deletion Engine/apps/agent-backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ fn tools() -> Value {
{"name":"docs_index","description":"List relevant Markdown documentation available in this project and its engine repository.","inputSchema":{"type":"object","properties":{}}}
]);
tools.as_array_mut().expect("tools array").push(json!({"name":"authoring_guide","description":"Read the bundled, current Rustic game design, modeling, scene, scope and playtest guidance. Works with existing customized integrations.","inputSchema":{"type":"object","properties":{}}}));
tools.as_array_mut().expect("tools array").push(json!({"name":"texture_guide","description":"Read current HDR/PBR map, Texture child and sky guidance, including slot names, channel conventions, examples and renderer limits. Available even when installed skills were customized.","inputSchema":{"type":"object","properties":{},"additionalProperties":false}}));

tools.as_array_mut().expect("tools array").push(json!({"name":"scene_explorer_guide","description":"Read the current rustic-scene-explorer skill: organize hierarchy folders, add objects, duplicate/delete subtrees, reparent safely, undo and save. Available with preserved custom instructions.","inputSchema":{"type":"object","properties":{}}}));
tools
.as_array_mut()
Expand All @@ -167,6 +169,8 @@ fn call_tool(root: &Path, name: &str, args: &Value) -> Result<Value, String> {
return engine_scripting::call_editor(root, name, args);
}
match name {
"texture_guide" => Ok(json!({"content":engine_scripting::AGENT_TEXTURE_GUIDE})),

"scene_explorer_guide" => {
Ok(json!({"content":engine_scripting::AGENT_SCENE_EXPLORER_SKILL}))
}
Expand Down Expand Up @@ -279,7 +283,7 @@ fn scene_summary(root: &Path, value: &str) -> Result<Value, String> {
let entities: Vec<_> = load.document.entities.iter().map(|entity| json!({
"id":entity.id.to_string(), "name":entity.name, "folder":entity.folder, "parent":entity.parent.map(|id| id.to_string()),
"translation":entity.local_transform.translation.to_array(), "rotation":entity.local_transform.rotation.to_array(), "scale":entity.local_transform.scale.to_array(),
"components":{"camera":entity.camera.is_some(),"light":entity.light.is_some(),"mesh":entity.mesh.is_some(),"material":entity.material.is_some(),"primitive":entity.primitive.as_ref().map(|p| format!("{p:?}")),"scripts":entity.scripts.len()}
"components":{"camera":entity.camera.is_some(),"light":entity.light.is_some(),"mesh":entity.mesh.is_some(),"material":entity.material.is_some(),"textures":entity.textures,"primitive":entity.primitive.as_ref().map(|p| format!("{p:?}")),"scripts":entity.scripts.len()}
})).collect();
Ok(
json!({"path":relative(root,&path),"name":load.document.name,"schema_version":load.schema_version,"read_only":load.read_only,"entities":entities,"diagnostics":load.diagnostics.iter().map(|d| d.message.clone()).collect::<Vec<_>>()}),
Expand Down Expand Up @@ -518,6 +522,15 @@ mod tests {
)
.unwrap();
let root = project.root().canonicalize().unwrap();
let guide = call_tool(&root, "texture_guide", &json!({})).unwrap();
assert_eq!(guide["content"], engine_scripting::AGENT_TEXTURE_GUIDE);
assert!(
tools()
.as_array()
.unwrap()
.iter()
.any(|tool| tool["name"] == "texture_guide")
);
let info = call_tool(&root, "workspace_info", &json!({})).unwrap();
assert!(info["agent_directory"].as_str().is_some());
call_tool(
Expand Down
Loading
Loading