Files
Verbatim/tests/body_template.rs
T
Emil 61bf21bda0 feat: BodyTemplate system — data-driven entity editor for AI
BodyTemplate: JSON-serializable entity body definition.
- parts: Vec<BodyPart> with x, y, color, label
- constraints: Vec<(usize, usize)> connecting parts
- half_w/half_h/radius: collider and physics params

Built-in templates:
- humanoid_player: purple wizard (15 parts)
- humanoid_goblin: green (15 parts)
- boulder: 4x4 rock (16 parts)

API for AI agents:
- BodyTemplate::to_json() / from_json() — serialize/deserialize
- BodyTemplate::preview_ascii() — text preview of silhouette
- BodyTemplate::apply_to(&mut entity, cx, cy) — build entity from template
- template_for_kind(EntityKind) — get template by entity type
- Custom templates: create any shape (snake, dragon, boss) from code or JSON

Entity::build_humanoid() now delegates to template_for_kind().apply_to()
— single source of truth for body layout.

7 new tests for template system (116 total, 0 failures)
2026-06-21 10:27:37 +03:00

117 lines
3.1 KiB
Rust

use verbatim::entity::body_template::{BodyPart, BodyTemplate};
#[test]
fn player_template_has_correct_shape() {
let t = BodyTemplate::humanoid_player();
assert_eq!(t.name, "player");
assert_eq!(t.parts.len(), 15);
assert_eq!(t.half_w, 2.5);
assert_eq!(t.half_h, 3.5);
}
#[test]
fn goblin_template_has_correct_shape() {
let t = BodyTemplate::humanoid_goblin();
assert_eq!(t.name, "goblin");
assert_eq!(t.parts.len(), 15);
}
#[test]
fn boulder_template_has_correct_shape() {
let t = BodyTemplate::boulder();
assert_eq!(t.name, "boulder");
assert_eq!(t.parts.len(), 16);
assert!(t.parts.iter().all(|p| p.label == "rock"));
}
#[test]
fn template_json_roundtrip() {
let t = BodyTemplate::humanoid_player();
let json = t.to_json();
let t2 = BodyTemplate::from_json(&json).expect("parse");
assert_eq!(t2.name, t.name);
assert_eq!(t2.parts.len(), t.parts.len());
assert_eq!(t2.half_w, t.half_w);
assert_eq!(t2.half_h, t.half_h);
}
#[test]
fn preview_ascii_not_empty() {
let t = BodyTemplate::humanoid_player();
let preview = t.preview_ascii();
assert!(!preview.is_empty());
assert!(preview.contains("O"));
}
#[test]
fn custom_template_works() {
let t = BodyTemplate {
name: "snake".to_string(),
half_w: 3.0,
half_h: 0.5,
radius: 0.5,
parts: vec![
BodyPart {
x: -3.0,
y: 0.0,
color: [100, 200, 50, 255],
label: "body".into(),
},
BodyPart {
x: -2.0,
y: 0.0,
color: [100, 200, 50, 255],
label: "body".into(),
},
BodyPart {
x: -1.0,
y: 0.0,
color: [100, 200, 50, 255],
label: "body".into(),
},
BodyPart {
x: 0.0,
y: 0.0,
color: [100, 200, 50, 255],
label: "body".into(),
},
BodyPart {
x: 1.0,
y: 0.0,
color: [100, 200, 50, 255],
label: "body".into(),
},
BodyPart {
x: 2.0,
y: 0.0,
color: [100, 200, 50, 255],
label: "body".into(),
},
BodyPart {
x: 3.0,
y: 0.0,
color: [80, 180, 40, 255],
label: "head".into(),
},
],
constraints: vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)],
};
assert_eq!(t.parts.len(), 7);
let json = t.to_json();
let t2 = BodyTemplate::from_json(&json).expect("parse");
assert_eq!(t2.parts.len(), 7);
assert_eq!(t2.name, "snake");
}
#[test]
fn template_apply_to_entity() {
use verbatim::entity::entity::{Entity, EntityKind};
let mut e = Entity::new(0, EntityKind::Goblin);
let t = BodyTemplate::humanoid_goblin();
t.apply_to(&mut e, 100.0, 100.0);
assert_eq!(e.bodies.len(), 15);
assert_eq!(e.cx, 100.0);
assert_eq!(e.cy, 100.0);
assert!(!e.constraints.is_empty());
}