use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub const SCHEMA_VERSION: &str = "dreamwell_waymark_v1.0.0";
fn default_schema_version() -> String {
SCHEMA_VERSION.to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DreamwellPackV1 {
pub id: String,
pub title: String,
#[serde(default)]
pub version: String,
#[serde(default)]
pub description: String,
#[serde(default = "default_schema_version")]
pub schema_version: String,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub world_id: Option<String>,
#[serde(default)]
pub theme: Option<String>,
#[serde(default)]
pub scenario: Option<String>,
#[serde(default)]
pub scenario_script: Option<String>,
#[serde(default)]
pub starting_map: Option<String>,
#[serde(default)]
pub grid: GridConfig,
#[serde(default)]
pub spatial: SpatialConfig,
#[serde(default)]
pub display: DisplayConfig,
#[serde(default)]
pub features: FeatureFlags,
#[serde(default)]
pub equip_slots: Vec<String>,
#[serde(default)]
pub simulation: SimulationConfig,
#[serde(default)]
pub combat: CombatConfig,
#[serde(default)]
pub economy: EconomyConfig,
#[serde(default)]
pub progression: ProgressionConfig,
#[serde(default)]
pub agents: AgentConfig,
#[serde(default)]
pub tiles: TileConfig,
#[serde(default)]
pub canon: CanonConfig,
#[serde(default)]
pub scripting: ScriptingConfig,
#[serde(default)]
pub content: ContentConfig,
#[serde(default)]
pub eviction: EvictionConfig,
#[serde(default)]
pub props: Vec<PropDefinition>,
#[serde(default)]
pub generators: serde_json::Value,
#[serde(default)]
pub connection_type_props: HashMap<String, String>,
#[serde(default)]
pub boon_triggers: Vec<serde_json::Value>,
#[serde(default)]
pub uses_companion_services: bool,
#[serde(default)]
pub scenario_config: serde_json::Value,
#[serde(default)]
pub encumbrance: Option<serde_json::Value>,
#[serde(default)]
pub ai_brains: HashMap<String, serde_json::Value>,
#[serde(default)]
pub rules: Option<serde_json::Value>,
#[serde(default)]
pub identity: Option<serde_json::Value>,
#[serde(default)]
pub boot_sequence: Option<serde_json::Value>,
#[serde(default)]
pub topology: TopologyConfig,
#[serde(default)]
pub chronoshift: ChronoshiftConfig,
#[serde(default)]
pub forensics: ForensicsConfig,
#[serde(default)]
pub physics: PhysicsConfig,
#[serde(default)]
pub meshlet_lod: Option<MeshletLodConfig>,
#[serde(default)]
pub meshlet_emission: Option<MeshletEmissionConfig>,
#[serde(default)]
pub observer_config: Option<WaymarkObserverConfig>,
#[serde(default)]
pub promotion_targets: Vec<WaymarkPromotionTarget>,
#[serde(default)]
pub avatar_defaults: Option<AvatarDefaults>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AvatarDefaults {
#[serde(default = "default_avatar_radius")]
pub radius: f32,
#[serde(default = "default_avatar_height")]
pub height: f32,
#[serde(default = "default_avatar_step_height")]
pub step_height: f32,
#[serde(default = "default_avatar_slope_limit")]
pub slope_limit_degrees: f32,
#[serde(default = "default_avatar_mass")]
pub mass: f32,
#[serde(default = "default_avatar_input_scheme")]
pub input_scheme: String,
}
fn default_avatar_radius() -> f32 {
0.3
}
fn default_avatar_height() -> f32 {
1.8
}
fn default_avatar_step_height() -> f32 {
0.35
}
fn default_avatar_slope_limit() -> f32 {
45.0
}
fn default_avatar_mass() -> f32 {
70.0
}
fn default_avatar_input_scheme() -> String {
"ThirdPerson".into()
}
impl Default for AvatarDefaults {
fn default() -> Self {
Self {
radius: default_avatar_radius(),
height: default_avatar_height(),
step_height: default_avatar_step_height(),
slope_limit_degrees: default_avatar_slope_limit(),
mass: default_avatar_mass(),
input_scheme: default_avatar_input_scheme(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GridConfig {
#[serde(default = "default_grid_width")]
pub width: u32,
#[serde(default = "default_grid_height")]
pub height: u32,
#[serde(default = "default_chunk_size")]
pub chunk_size: u32,
}
fn default_grid_width() -> u32 {
80
}
fn default_grid_height() -> u32 {
50
}
fn default_chunk_size() -> u32 {
32
}
impl Default for GridConfig {
fn default() -> Self {
Self {
width: default_grid_width(),
height: default_grid_height(),
chunk_size: default_chunk_size(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpatialConfig {
#[serde(default = "default_cell_size")]
pub cell_size: i32,
#[serde(default = "default_fov_default_radius")]
pub fov_default_radius: i32,
#[serde(default = "default_fov_max_radius")]
pub fov_max_radius: i32,
#[serde(default = "default_max_pathfind_steps")]
pub max_pathfind_steps: u32,
#[serde(default = "default_aoi_neighbor_depth")]
pub aoi_neighbor_depth: u32,
#[serde(default = "default_true")]
pub collision_enabled: bool,
}
fn default_cell_size() -> i32 {
128
}
fn default_fov_default_radius() -> i32 {
8
}
fn default_fov_max_radius() -> i32 {
24
}
fn default_max_pathfind_steps() -> u32 {
512
}
fn default_aoi_neighbor_depth() -> u32 {
1
}
fn default_true() -> bool {
true
}
impl Default for SpatialConfig {
fn default() -> Self {
Self {
cell_size: default_cell_size(),
fov_default_radius: default_fov_default_radius(),
fov_max_radius: default_fov_max_radius(),
max_pathfind_steps: default_max_pathfind_steps(),
aoi_neighbor_depth: default_aoi_neighbor_depth(),
collision_enabled: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DisplayConfig {
#[serde(default = "default_mana_name")]
pub mana_name: String,
#[serde(default)]
pub subtitle: Option<String>,
#[serde(default = "default_true")]
pub show_minimap: bool,
#[serde(default)]
pub show_coordinates: bool,
#[serde(default)]
pub theme: Option<String>,
}
fn default_mana_name() -> String {
"Mana".to_string()
}
impl Default for DisplayConfig {
fn default() -> Self {
Self {
mana_name: default_mana_name(),
subtitle: None,
show_minimap: true,
show_coordinates: false,
theme: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureFlags {
#[serde(default)]
pub content_plan: bool,
#[serde(default)]
pub boons: bool,
#[serde(default)]
pub shrines: bool,
#[serde(default)]
pub containers: bool,
#[serde(default)]
pub encumbrance: bool,
#[serde(default)]
pub pvp: bool,
#[serde(default)]
pub dynasties: bool,
#[serde(default)]
pub crafting: bool,
#[serde(default)]
pub market: bool,
#[serde(default)]
pub instances: bool,
#[serde(default)]
pub crime_system: bool,
#[serde(default)]
pub weather: bool,
#[serde(default)]
pub day_night_cycle: bool,
#[serde(default)]
pub fog_of_war: bool,
#[serde(default)]
pub chronoshift_enabled: bool,
#[serde(default)]
pub proof_lane_enabled: bool,
#[serde(default = "default_true")]
pub dungeon_ambient: bool,
#[serde(default = "default_true")]
pub line_of_sight: bool,
#[serde(default = "default_true")]
pub collisions: bool,
#[serde(default)]
pub identity_system: bool,
#[serde(default)]
pub death_reprint: bool,
}
impl Default for FeatureFlags {
fn default() -> Self {
Self {
content_plan: false,
boons: false,
shrines: false,
containers: false,
encumbrance: false,
pvp: false,
dynasties: false,
crafting: false,
market: false,
instances: false,
crime_system: false,
weather: false,
day_night_cycle: false,
fog_of_war: false,
chronoshift_enabled: false,
proof_lane_enabled: false,
dungeon_ambient: true,
line_of_sight: true,
collisions: true,
identity_system: false,
death_reprint: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimulationConfig {
#[serde(default = "default_tick_rate_ms")]
pub tick_rate_ms: u32,
#[serde(default = "default_ticks_per_day")]
pub ticks_per_day: u32,
#[serde(default = "default_time_dilation")]
pub time_dilation: f64,
#[serde(default = "default_max_entities_per_area")]
pub max_entities_per_area: u32,
#[serde(default = "default_max_events_per_tick")]
pub max_events_per_tick: u32,
#[serde(default)]
pub deterministic_seed: u64,
}
fn default_tick_rate_ms() -> u32 {
100
}
fn default_ticks_per_day() -> u32 {
365
}
fn default_time_dilation() -> f64 {
1.0
}
fn default_max_entities_per_area() -> u32 {
256
}
fn default_max_events_per_tick() -> u32 {
1024
}
impl Default for SimulationConfig {
fn default() -> Self {
Self {
tick_rate_ms: default_tick_rate_ms(),
ticks_per_day: default_ticks_per_day(),
time_dilation: default_time_dilation(),
max_entities_per_area: default_max_entities_per_area(),
max_events_per_tick: default_max_events_per_tick(),
deterministic_seed: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CombatConfig {
#[serde(default = "default_base_hit_chance")]
pub base_hit_chance: u32,
#[serde(default = "default_crit_multiplier")]
pub crit_multiplier: f64,
#[serde(default = "default_dodge_base")]
pub dodge_base: u32,
#[serde(default = "default_parry_base")]
pub parry_base: u32,
#[serde(default = "default_flee_threshold_hp_pct")]
pub flee_threshold_hp_pct: u32,
#[serde(default = "default_max_threat_targets")]
pub max_threat_targets: u32,
#[serde(default = "default_duel_timeout_ticks")]
pub duel_timeout_ticks: u32,
#[serde(default = "default_revive_hp_pct")]
pub revive_hp_pct: u32,
#[serde(default = "default_status_max_stacks")]
pub status_max_stacks: u32,
#[serde(default = "default_damage_types")]
pub damage_types: Vec<String>,
#[serde(default = "default_resistance_cap")]
pub resistance_cap: u32,
}
fn default_base_hit_chance() -> u32 {
80
}
fn default_crit_multiplier() -> f64 {
2.0
}
fn default_dodge_base() -> u32 {
10
}
fn default_parry_base() -> u32 {
5
}
fn default_flee_threshold_hp_pct() -> u32 {
20
}
fn default_max_threat_targets() -> u32 {
8
}
fn default_duel_timeout_ticks() -> u32 {
100
}
fn default_revive_hp_pct() -> u32 {
25
}
fn default_status_max_stacks() -> u32 {
5
}
fn default_damage_types() -> Vec<String> {
vec![
"physical".to_string(),
"fire".to_string(),
"ice".to_string(),
"lightning".to_string(),
"arcane".to_string(),
"poison".to_string(),
"holy".to_string(),
"shadow".to_string(),
]
}
fn default_resistance_cap() -> u32 {
75
}
impl Default for CombatConfig {
fn default() -> Self {
Self {
base_hit_chance: default_base_hit_chance(),
crit_multiplier: default_crit_multiplier(),
dodge_base: default_dodge_base(),
parry_base: default_parry_base(),
flee_threshold_hp_pct: default_flee_threshold_hp_pct(),
max_threat_targets: default_max_threat_targets(),
duel_timeout_ticks: default_duel_timeout_ticks(),
revive_hp_pct: default_revive_hp_pct(),
status_max_stacks: default_status_max_stacks(),
damage_types: default_damage_types(),
resistance_cap: default_resistance_cap(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EconomyConfig {
#[serde(default = "default_starting_gold")]
pub starting_gold: u64,
#[serde(default = "default_trade_tax_pct")]
pub trade_tax_pct: u32,
#[serde(default = "default_market_fee_pct")]
pub market_fee_pct: u32,
#[serde(default = "default_max_listings_per_player")]
pub max_listings_per_player: u32,
#[serde(default = "default_currency_types")]
pub currency_types: Vec<String>,
#[serde(default = "default_crafting_fail_chance_base")]
pub crafting_fail_chance_base: u32,
}
fn default_starting_gold() -> u64 {
100
}
fn default_trade_tax_pct() -> u32 {
5
}
fn default_market_fee_pct() -> u32 {
10
}
fn default_max_listings_per_player() -> u32 {
20
}
fn default_currency_types() -> Vec<String> {
vec!["gold".to_string()]
}
fn default_crafting_fail_chance_base() -> u32 {
10
}
impl Default for EconomyConfig {
fn default() -> Self {
Self {
starting_gold: default_starting_gold(),
trade_tax_pct: default_trade_tax_pct(),
market_fee_pct: default_market_fee_pct(),
max_listings_per_player: default_max_listings_per_player(),
currency_types: default_currency_types(),
crafting_fail_chance_base: default_crafting_fail_chance_base(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressionConfig {
#[serde(default = "default_max_level")]
pub max_level: u32,
#[serde(default = "default_xp_curve_base")]
pub xp_curve_base: f64,
#[serde(default = "default_base_xp")]
pub base_xp: u64,
#[serde(default = "default_level_hp_bonus")]
pub level_hp_bonus: u32,
#[serde(default = "default_level_stat_bonus")]
pub level_stat_bonus: u32,
#[serde(default = "default_reputation_min")]
pub reputation_min: i64,
#[serde(default = "default_reputation_max")]
pub reputation_max: i64,
}
fn default_max_level() -> u32 {
100
}
fn default_xp_curve_base() -> f64 {
1.5
}
fn default_base_xp() -> u64 {
100
}
fn default_level_hp_bonus() -> u32 {
10
}
fn default_level_stat_bonus() -> u32 {
2
}
fn default_reputation_min() -> i64 {
-1000
}
fn default_reputation_max() -> i64 {
1000
}
impl Default for ProgressionConfig {
fn default() -> Self {
Self {
max_level: default_max_level(),
xp_curve_base: default_xp_curve_base(),
base_xp: default_base_xp(),
level_hp_bonus: default_level_hp_bonus(),
level_stat_bonus: default_level_stat_bonus(),
reputation_min: default_reputation_min(),
reputation_max: default_reputation_max(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
#[serde(default = "default_cognition_budget_per_tick")]
pub cognition_budget_per_tick: u32,
#[serde(default = "default_perception_range")]
pub perception_range: u32,
#[serde(default = "default_memory_capacity")]
pub memory_capacity: u32,
#[serde(default = "default_reasoning_tier")]
pub reasoning_tier_default: u32,
#[serde(default = "default_motor_tier")]
pub motor_tier_default: u32,
#[serde(default = "default_max_concurrent_agents")]
pub max_concurrent_agents: u32,
#[serde(default = "default_inference_timeout_ms")]
pub inference_timeout_ms: u32,
#[serde(default = "default_inference_max_tokens")]
pub inference_max_tokens: u32,
}
fn default_cognition_budget_per_tick() -> u32 {
3
}
fn default_perception_range() -> u32 {
10
}
fn default_memory_capacity() -> u32 {
32
}
fn default_reasoning_tier() -> u32 {
1
}
fn default_motor_tier() -> u32 {
1
}
fn default_max_concurrent_agents() -> u32 {
64
}
fn default_inference_timeout_ms() -> u32 {
5000
}
fn default_inference_max_tokens() -> u32 {
512
}
impl Default for AgentConfig {
fn default() -> Self {
Self {
cognition_budget_per_tick: default_cognition_budget_per_tick(),
perception_range: default_perception_range(),
memory_capacity: default_memory_capacity(),
reasoning_tier_default: default_reasoning_tier(),
motor_tier_default: default_motor_tier(),
max_concurrent_agents: default_max_concurrent_agents(),
inference_timeout_ms: default_inference_timeout_ms(),
inference_max_tokens: default_inference_max_tokens(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TileTypeDef {
pub glyph: u16,
#[serde(default)]
pub display_char: Option<char>,
#[serde(default = "default_move_cost_1")]
pub move_cost: u32,
#[serde(default)]
pub blocks_move: bool,
#[serde(default)]
pub blocks_sight: bool,
}
fn default_move_cost_1() -> u32 {
1
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TileConfig {
#[serde(default = "default_tile_ground")]
pub ground: TileTypeDef,
#[serde(default = "default_tile_dirt")]
pub dirt: TileTypeDef,
#[serde(default = "default_tile_sand")]
pub sand: TileTypeDef,
#[serde(default = "default_tile_mud")]
pub mud: TileTypeDef,
#[serde(default = "default_tile_liquid")]
pub liquid: TileTypeDef,
#[serde(default = "default_tile_deep_water")]
pub deep_water: TileTypeDef,
#[serde(default = "default_tile_rocky_ground")]
pub rocky_ground: TileTypeDef,
#[serde(default = "default_tile_grass")]
pub grass: TileTypeDef,
#[serde(default = "default_tile_debris")]
pub debris: TileTypeDef,
#[serde(default = "default_tile_wall")]
pub wall: TileTypeDef,
#[serde(default = "default_tile_wall_vert")]
pub wall_vert: TileTypeDef,
#[serde(default = "default_tile_wall_horiz")]
pub wall_horiz: TileTypeDef,
#[serde(default = "default_tile_door_closed")]
pub door_closed: TileTypeDef,
#[serde(default = "default_tile_door_open")]
pub door_open: TileTypeDef,
#[serde(default = "default_tile_stairs_up")]
pub stairs_up: TileTypeDef,
#[serde(default = "default_tile_stairs_down")]
pub stairs_down: TileTypeDef,
#[serde(default = "default_tile_bridge")]
pub bridge: TileTypeDef,
#[serde(default = "default_tile_boulder")]
pub boulder: TileTypeDef,
#[serde(default = "default_tile_chest")]
pub chest: TileTypeDef,
#[serde(default = "default_tile_crate")]
pub crate_obj: TileTypeDef,
#[serde(default = "default_tile_shrine")]
pub shrine: TileTypeDef,
#[serde(default = "default_tile_bed")]
pub bed: TileTypeDef,
#[serde(default = "default_tile_consumable")]
pub consumable: TileTypeDef,
#[serde(default = "default_tile_currency")]
pub currency: TileTypeDef,
#[serde(default = "default_tile_key_item")]
pub key_item: TileTypeDef,
#[serde(default = "default_tile_unknown_obj")]
pub unknown_obj: TileTypeDef,
#[serde(default = "default_tile_hazard")]
pub hazard: TileTypeDef,
#[serde(default = "default_tile_smoke")]
pub smoke: TileTypeDef,
#[serde(default)]
pub custom: HashMap<String, TileTypeDef>,
}
fn default_tile_ground() -> TileTypeDef {
TileTypeDef {
glyph: b'.' as u16,
display_char: None,
move_cost: 1,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_dirt() -> TileTypeDef {
TileTypeDef {
glyph: b',' as u16,
display_char: None,
move_cost: 1,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_sand() -> TileTypeDef {
TileTypeDef {
glyph: b':' as u16,
display_char: None,
move_cost: 2,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_mud() -> TileTypeDef {
TileTypeDef {
glyph: b'`' as u16,
display_char: None,
move_cost: 2,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_liquid() -> TileTypeDef {
TileTypeDef {
glyph: b'~' as u16,
display_char: None,
move_cost: 3,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_deep_water() -> TileTypeDef {
TileTypeDef {
glyph: b'=' as u16,
display_char: None,
move_cost: 999,
blocks_move: true,
blocks_sight: false,
}
}
fn default_tile_rocky_ground() -> TileTypeDef {
TileTypeDef {
glyph: b'b' as u16,
display_char: None,
move_cost: 2,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_grass() -> TileTypeDef {
TileTypeDef {
glyph: b';' as u16,
display_char: None,
move_cost: 2,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_debris() -> TileTypeDef {
TileTypeDef {
glyph: b'\'' as u16,
display_char: None,
move_cost: 2,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_wall() -> TileTypeDef {
TileTypeDef {
glyph: b'#' as u16,
display_char: None,
move_cost: 999,
blocks_move: true,
blocks_sight: true,
}
}
fn default_tile_wall_vert() -> TileTypeDef {
TileTypeDef {
glyph: b'|' as u16,
display_char: None,
move_cost: 999,
blocks_move: true,
blocks_sight: true,
}
}
fn default_tile_wall_horiz() -> TileTypeDef {
TileTypeDef {
glyph: b'_' as u16,
display_char: None,
move_cost: 999,
blocks_move: true,
blocks_sight: true,
}
}
fn default_tile_door_closed() -> TileTypeDef {
TileTypeDef {
glyph: b'I' as u16,
display_char: None,
move_cost: 999,
blocks_move: true,
blocks_sight: true,
}
}
fn default_tile_door_open() -> TileTypeDef {
TileTypeDef {
glyph: b'*' as u16,
display_char: None,
move_cost: 1,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_stairs_up() -> TileTypeDef {
TileTypeDef {
glyph: b'^' as u16,
display_char: None,
move_cost: 1,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_stairs_down() -> TileTypeDef {
TileTypeDef {
glyph: b'v' as u16,
display_char: None,
move_cost: 1,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_bridge() -> TileTypeDef {
TileTypeDef {
glyph: b'=' as u16,
display_char: None,
move_cost: 1,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_boulder() -> TileTypeDef {
TileTypeDef {
glyph: b'o' as u16,
display_char: None,
move_cost: 999,
blocks_move: true,
blocks_sight: true,
}
}
fn default_tile_chest() -> TileTypeDef {
TileTypeDef {
glyph: b'C' as u16,
display_char: None,
move_cost: 999,
blocks_move: true,
blocks_sight: false,
}
}
fn default_tile_crate() -> TileTypeDef {
TileTypeDef {
glyph: b'c' as u16,
display_char: None,
move_cost: 999,
blocks_move: true,
blocks_sight: false,
}
}
fn default_tile_shrine() -> TileTypeDef {
TileTypeDef {
glyph: b'S' as u16,
display_char: None,
move_cost: 1,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_bed() -> TileTypeDef {
TileTypeDef {
glyph: b'B' as u16,
display_char: None,
move_cost: 999,
blocks_move: true,
blocks_sight: false,
}
}
fn default_tile_consumable() -> TileTypeDef {
TileTypeDef {
glyph: b'!' as u16,
display_char: None,
move_cost: 1,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_currency() -> TileTypeDef {
TileTypeDef {
glyph: b'$' as u16,
display_char: None,
move_cost: 1,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_key_item() -> TileTypeDef {
TileTypeDef {
glyph: b'K' as u16,
display_char: None,
move_cost: 1,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_unknown_obj() -> TileTypeDef {
TileTypeDef {
glyph: b'?' as u16,
display_char: None,
move_cost: 999,
blocks_move: true,
blocks_sight: false,
}
}
fn default_tile_hazard() -> TileTypeDef {
TileTypeDef {
glyph: b'x' as u16,
display_char: None,
move_cost: 2,
blocks_move: false,
blocks_sight: false,
}
}
fn default_tile_smoke() -> TileTypeDef {
TileTypeDef {
glyph: b'%' as u16,
display_char: None,
move_cost: 2,
blocks_move: false,
blocks_sight: true,
}
}
impl Default for TileConfig {
fn default() -> Self {
Self {
ground: default_tile_ground(),
dirt: default_tile_dirt(),
sand: default_tile_sand(),
mud: default_tile_mud(),
liquid: default_tile_liquid(),
deep_water: default_tile_deep_water(),
rocky_ground: default_tile_rocky_ground(),
grass: default_tile_grass(),
debris: default_tile_debris(),
wall: default_tile_wall(),
wall_vert: default_tile_wall_vert(),
wall_horiz: default_tile_wall_horiz(),
door_closed: default_tile_door_closed(),
door_open: default_tile_door_open(),
stairs_up: default_tile_stairs_up(),
stairs_down: default_tile_stairs_down(),
bridge: default_tile_bridge(),
boulder: default_tile_boulder(),
chest: default_tile_chest(),
crate_obj: default_tile_crate(),
shrine: default_tile_shrine(),
bed: default_tile_bed(),
consumable: default_tile_consumable(),
currency: default_tile_currency(),
key_item: default_tile_key_item(),
unknown_obj: default_tile_unknown_obj(),
hazard: default_tile_hazard(),
smoke: default_tile_smoke(),
custom: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CanonConfig {
#[serde(default = "default_block_hash_interval")]
pub block_hash_interval: u64,
#[serde(default = "default_tick_hash_interval")]
pub tick_hash_interval: u64,
#[serde(default = "default_max_traversal_depth")]
pub max_traversal_depth: u32,
#[serde(default = "default_scope_budget")]
pub scope_budget_default: u32,
#[serde(default = "default_token_refill_per_tick")]
pub token_refill_per_tick: u32,
#[serde(default = "default_max_tokens")]
pub max_tokens: u32,
#[serde(default = "default_max_trigger_depth")]
pub max_trigger_depth: u32,
#[serde(default = "default_max_triggers_per_event")]
pub max_triggers_per_event: u32,
#[serde(default = "default_max_triggers_per_tick")]
pub max_triggers_per_tick: u32,
}
fn default_block_hash_interval() -> u64 {
100
}
fn default_tick_hash_interval() -> u64 {
10
}
fn default_max_traversal_depth() -> u32 {
10
}
fn default_scope_budget() -> u32 {
100
}
fn default_token_refill_per_tick() -> u32 {
5
}
fn default_max_tokens() -> u32 {
50
}
fn default_max_trigger_depth() -> u32 {
3
}
fn default_max_triggers_per_event() -> u32 {
5
}
fn default_max_triggers_per_tick() -> u32 {
50
}
impl Default for CanonConfig {
fn default() -> Self {
Self {
block_hash_interval: default_block_hash_interval(),
tick_hash_interval: default_tick_hash_interval(),
max_traversal_depth: default_max_traversal_depth(),
scope_budget_default: default_scope_budget(),
token_refill_per_tick: default_token_refill_per_tick(),
max_tokens: default_max_tokens(),
max_trigger_depth: default_max_trigger_depth(),
max_triggers_per_event: default_max_triggers_per_event(),
max_triggers_per_tick: default_max_triggers_per_tick(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScriptingConfig {
#[serde(default = "default_max_runes")]
pub max_runes: u32,
#[serde(default = "default_max_omens")]
pub max_omens: u32,
#[serde(default = "default_max_chapters")]
pub max_chapters: u32,
#[serde(default = "default_max_dialogues")]
pub max_dialogues: u32,
#[serde(default = "default_max_visions")]
pub max_visions: u32,
#[serde(default = "default_max_gates")]
pub max_gates: u32,
#[serde(default = "default_fire_once_capacity")]
pub fire_once_capacity: u32,
#[serde(default = "default_true")]
pub meta_persistence_enabled: bool,
#[serde(default)]
pub hot_reload_enabled: bool,
}
fn default_max_runes() -> u32 {
256
}
fn default_max_omens() -> u32 {
128
}
fn default_max_chapters() -> u32 {
64
}
fn default_max_dialogues() -> u32 {
64
}
fn default_max_visions() -> u32 {
32
}
fn default_max_gates() -> u32 {
64
}
fn default_fire_once_capacity() -> u32 {
512
}
impl Default for ScriptingConfig {
fn default() -> Self {
Self {
max_runes: default_max_runes(),
max_omens: default_max_omens(),
max_chapters: default_max_chapters(),
max_dialogues: default_max_dialogues(),
max_visions: default_max_visions(),
max_gates: default_max_gates(),
fire_once_capacity: default_fire_once_capacity(),
meta_persistence_enabled: true,
hot_reload_enabled: false,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ContentConfig {
#[serde(default)]
pub items_path: Option<String>,
#[serde(default)]
pub enemies_path: Option<String>,
#[serde(default)]
pub abilities_path: Option<String>,
#[serde(default)]
pub loot_tables_path: Option<String>,
#[serde(default)]
pub economy_path: Option<String>,
#[serde(default)]
pub dialogue_path: Option<String>,
#[serde(default)]
pub balance_path: Option<String>,
#[serde(default)]
pub stats_path: Option<String>,
#[serde(default)]
pub scenario_text_path: Option<String>,
#[serde(default)]
pub lore_path: Option<String>,
#[serde(default)]
pub crafting_path: Option<String>,
#[serde(default)]
pub quests_path: Option<String>,
#[serde(default)]
pub factions_path: Option<String>,
#[serde(default)]
pub maps_path: Option<String>,
#[serde(default)]
pub textures_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvictionConfig {
#[serde(default = "default_max_cached")]
pub max_cached: u32,
#[serde(default)]
pub keep_ids: Vec<String>,
#[serde(default)]
pub keep_tags: Vec<String>,
}
fn default_max_cached() -> u32 {
20
}
impl Default for EvictionConfig {
fn default() -> Self {
Self {
max_cached: default_max_cached(),
keep_ids: Vec::new(),
keep_tags: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropStateDef {
#[serde(default)]
pub glyph: Option<String>,
#[serde(default)]
pub blocking: Option<bool>,
#[serde(default)]
pub walkthrough: Option<bool>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub on_interact: Option<String>,
#[serde(default)]
pub on_secondary_interact: Option<String>,
#[serde(default)]
pub bump_messages: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropDefinition {
pub id: String,
pub name: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub look_description: Option<String>,
#[serde(default)]
pub glyph: Option<String>,
#[serde(default)]
pub blocking: bool,
#[serde(default)]
pub walkthrough: bool,
#[serde(default)]
pub targetable: bool,
#[serde(default)]
pub interactive: bool,
#[serde(default)]
pub has_secondary_state: bool,
#[serde(default)]
pub default_state: Option<String>,
#[serde(default)]
pub foley_stub_id: Option<String>,
#[serde(default)]
pub bump_messages: Vec<String>,
#[serde(default)]
pub has_inventory: bool,
#[serde(default)]
pub container_capacity: Option<u32>,
#[serde(default)]
pub loot_table: Option<String>,
#[serde(default)]
pub states: HashMap<String, PropStateDef>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChronoshiftConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_max_checkpoint_depth")]
pub max_checkpoint_depth: u32,
#[serde(default = "default_replay_speed_multiplier")]
pub replay_speed_multiplier: f64,
#[serde(default)]
pub auto_checkpoint_interval_ticks: u64,
#[serde(default = "default_max_fork_count")]
pub max_fork_count: u32,
#[serde(default = "default_fork_compute_cap_pct")]
pub fork_compute_cap_pct: u32,
}
fn default_max_checkpoint_depth() -> u32 {
10
}
fn default_replay_speed_multiplier() -> f64 {
1.0
}
fn default_max_fork_count() -> u32 {
4
}
fn default_fork_compute_cap_pct() -> u32 {
25
}
impl Default for ChronoshiftConfig {
fn default() -> Self {
Self {
enabled: false,
max_checkpoint_depth: default_max_checkpoint_depth(),
replay_speed_multiplier: default_replay_speed_multiplier(),
auto_checkpoint_interval_ticks: 0,
max_fork_count: default_max_fork_count(),
fork_compute_cap_pct: default_fork_compute_cap_pct(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForensicsConfig {
#[serde(default)]
pub proof_lane_enabled: bool,
#[serde(default = "default_true")]
pub blake3_domain_separation: bool,
#[serde(default)]
pub merkle_state_root_enabled: bool,
#[serde(default)]
pub ed25519_signing_enabled: bool,
#[serde(default)]
pub public_audit_tables: bool,
}
impl Default for ForensicsConfig {
fn default() -> Self {
Self {
proof_lane_enabled: false,
blake3_domain_separation: true,
merkle_state_root_enabled: false,
ed25519_signing_enabled: false,
public_audit_tables: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhysicsConfig {
#[serde(default = "default_gravity_planetary")]
pub gravity_planetary: f32,
#[serde(default)]
pub gravity_local: Option<f32>,
#[serde(default = "default_atmosphere_density")]
pub atmosphere_density: f32,
#[serde(default = "default_temperature_ambient")]
pub temperature_ambient: f32,
#[serde(default)]
pub emitter_presets: Vec<String>,
#[serde(default)]
pub force_fields: Vec<ForceFieldConfig>,
#[serde(default)]
pub collision_planes: Vec<CollisionPlaneConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForceFieldConfig {
pub kind: String,
#[serde(default)]
pub position: [f32; 3],
#[serde(default = "default_ff_strength")]
pub strength: f32,
#[serde(default = "default_ff_radius")]
pub radius: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollisionPlaneConfig {
#[serde(default = "default_plane_normal")]
pub normal: [f32; 3],
#[serde(default)]
pub offset: f32,
#[serde(default = "default_restitution")]
pub restitution: f32,
}
fn default_gravity_planetary() -> f32 {
9.81
}
fn default_atmosphere_density() -> f32 {
1.0
}
fn default_temperature_ambient() -> f32 {
20.0
}
fn default_ff_strength() -> f32 {
9.81
}
fn default_ff_radius() -> f32 {
100.0
}
fn default_plane_normal() -> [f32; 3] {
[0.0, 1.0, 0.0]
}
fn default_restitution() -> f32 {
0.5
}
impl Default for PhysicsConfig {
fn default() -> Self {
Self {
gravity_planetary: default_gravity_planetary(),
gravity_local: None,
atmosphere_density: default_atmosphere_density(),
temperature_ambient: default_temperature_ambient(),
emitter_presets: Vec::new(),
force_fields: Vec::new(),
collision_planes: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeshletLodConfig {
#[serde(default = "default_max_meshlets_per_object")]
pub max_meshlets_per_object: u32,
#[serde(default)]
pub lod_distances: Vec<f32>,
#[serde(default = "default_max_vertices_per_meshlet")]
pub max_vertices_per_meshlet: u32,
#[serde(default = "default_max_triangles_per_meshlet")]
pub max_triangles_per_meshlet: u32,
}
fn default_max_meshlets_per_object() -> u32 {
1024
}
fn default_max_vertices_per_meshlet() -> u32 {
64
}
fn default_max_triangles_per_meshlet() -> u32 {
126
}
impl Default for MeshletLodConfig {
fn default() -> Self {
Self {
max_meshlets_per_object: default_max_meshlets_per_object(),
lod_distances: vec![50.0, 100.0, 200.0],
max_vertices_per_meshlet: default_max_vertices_per_meshlet(),
max_triangles_per_meshlet: default_max_triangles_per_meshlet(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeshletEmissionConfig {
#[serde(default = "default_max_particles_per_emitter")]
pub max_particles_per_emitter: u32,
#[serde(default = "default_particle_lifetime")]
pub default_lifetime: f32,
#[serde(default = "default_emission_rate")]
pub default_emission_rate: f32,
}
fn default_max_particles_per_emitter() -> u32 {
1024
}
fn default_particle_lifetime() -> f32 {
2.0
}
fn default_emission_rate() -> f32 {
100.0
}
impl Default for MeshletEmissionConfig {
fn default() -> Self {
Self {
max_particles_per_emitter: default_max_particles_per_emitter(),
default_lifetime: default_particle_lifetime(),
default_emission_rate: default_emission_rate(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaymarkObserverConfig {
#[serde(default = "default_fov_radius")]
pub default_fov_radius: i32,
#[serde(default = "default_observer_layer")]
pub default_layer: String,
}
fn default_fov_radius() -> i32 {
10
}
fn default_observer_layer() -> String {
"area".into()
}
impl Default for WaymarkObserverConfig {
fn default() -> Self {
Self {
default_fov_radius: default_fov_radius(),
default_layer: default_observer_layer(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaymarkPromotionTarget {
pub kind: String,
pub entity_type: String,
#[serde(default)]
pub min_age: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeshSourceConfig {
#[serde(default)]
pub primitive_type: Option<String>,
#[serde(default)]
pub fbx_path: Option<String>,
#[serde(default)]
pub gltf_path: Option<String>,
#[serde(default)]
pub active_layers: Vec<u32>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TopologyConfig {
#[serde(default)]
pub universe_name: Option<String>,
#[serde(default)]
pub galaxies: Vec<GalaxyDef>,
#[serde(default)]
pub sectors: Vec<SectorDef>,
#[serde(default)]
pub worlds: Vec<WorldDef>,
#[serde(default)]
pub realms: Vec<RealmDef>,
#[serde(default)]
pub regions: Vec<RegionDef>,
#[serde(default)]
pub areas: Vec<AreaDef>,
#[serde(default)]
pub locations: Vec<LocationDef>,
#[serde(default)]
pub points: Vec<PointDef>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GalaxyDef {
pub id: String,
pub name: String,
#[serde(default)]
pub kind: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SectorDef {
pub id: String,
pub galaxy_id: String,
pub name: String,
#[serde(default)]
pub governing_entity_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorldDef {
pub id: String,
#[serde(default)]
pub sector_id: String,
pub name: String,
#[serde(default)]
pub theme: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealmDef {
pub id: String,
pub world_id: String,
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegionDef {
pub id: String,
pub realm_id: String,
pub name: String,
#[serde(default = "default_pressure")]
pub pressure_security: i32,
#[serde(default = "default_pressure")]
pub pressure_scarcity: i32,
#[serde(default = "default_pressure")]
pub pressure_unrest: i32,
#[serde(default = "default_pressure")]
pub pressure_anomaly: i32,
}
fn default_pressure() -> i32 {
25
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AreaDef {
pub id: String,
pub region_id: String,
pub name: String,
#[serde(default)]
pub kind: String,
#[serde(default = "default_danger")]
pub danger_rating: i32,
}
fn default_danger() -> i32 {
1
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocationDef {
pub id: String,
pub area_id: String,
pub name: String,
#[serde(default)]
pub kind: String,
#[serde(default)]
pub x: i32,
#[serde(default)]
pub y: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PointDef {
pub id: String,
#[serde(default)]
pub location_id: String,
#[serde(default)]
pub room_id: String,
#[serde(default)]
pub x: i32,
#[serde(default)]
pub y: i32,
#[serde(default)]
pub kind: String,
#[serde(default)]
pub label: String,
#[serde(default)]
pub trigger_id: String,
#[serde(default)]
pub template_id: String,
}
impl Default for DreamwellPackV1 {
fn default() -> Self {
Self {
id: String::new(),
title: String::new(),
version: String::new(),
description: String::new(),
schema_version: default_schema_version(),
tags: Vec::new(),
world_id: None,
theme: None,
scenario: None,
scenario_script: None,
starting_map: None,
grid: GridConfig::default(),
spatial: SpatialConfig::default(),
display: DisplayConfig::default(),
features: FeatureFlags::default(),
equip_slots: Vec::new(),
simulation: SimulationConfig::default(),
combat: CombatConfig::default(),
economy: EconomyConfig::default(),
progression: ProgressionConfig::default(),
agents: AgentConfig::default(),
tiles: TileConfig::default(),
canon: CanonConfig::default(),
scripting: ScriptingConfig::default(),
content: ContentConfig::default(),
eviction: EvictionConfig::default(),
props: Vec::new(),
generators: serde_json::Value::Null,
connection_type_props: HashMap::new(),
boon_triggers: Vec::new(),
uses_companion_services: false,
scenario_config: serde_json::Value::Null,
encumbrance: None,
ai_brains: HashMap::new(),
rules: None,
identity: None,
boot_sequence: None,
topology: TopologyConfig::default(),
chronoshift: ChronoshiftConfig::default(),
forensics: ForensicsConfig::default(),
physics: PhysicsConfig::default(),
meshlet_lod: None,
meshlet_emission: None,
observer_config: None,
promotion_targets: Vec::new(),
avatar_defaults: None,
}
}
}
impl DreamwellPackV1 {
pub fn from_json(json: &str) -> Result<Self, String> {
serde_json::from_str::<Self>(json).map_err(|e| format!("pack_parse_error: {}", e))
}
pub fn to_json(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_else(|e| format!("{{\"error\": \"{}\"}}", e))
}
pub fn validate(&self) -> Vec<String> {
let mut errors = Vec::new();
if self.id.is_empty() {
errors.push("id_required: pack id must be non-empty".to_string());
} else if self.id.len() > 128 {
errors.push(format!("id_too_long: {} chars (max 128)", self.id.len()));
} else if !self
.id
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
{
errors.push("id_invalid_chars: pack id must contain only [a-zA-Z0-9_-]".to_string());
}
if self.title.is_empty() {
errors.push("title_required: pack title must be non-empty".to_string());
} else if self.title.len() > 256 {
errors.push(format!("title_too_long: {} chars (max 256)", self.title.len()));
}
if self.description.len() > 4096 {
errors.push(format!(
"description_too_long: {} chars (max 4096)",
self.description.len()
));
}
if self.grid.width == 0 {
errors.push("grid_width_zero: width must be > 0".to_string());
}
if self.grid.height == 0 {
errors.push("grid_height_zero: height must be > 0".to_string());
}
if self.grid.width > 4096 {
errors.push(format!("grid_width_too_large: {} (max 4096)", self.grid.width));
}
if self.grid.height > 4096 {
errors.push(format!("grid_height_too_large: {} (max 4096)", self.grid.height));
}
if self.grid.chunk_size == 0 || !self.grid.chunk_size.is_power_of_two() {
errors.push(format!(
"grid_chunk_size_invalid: {} (must be a non-zero power of two)",
self.grid.chunk_size
));
}
if self.spatial.cell_size <= 0 {
errors.push(format!(
"spatial_cell_size_invalid: {} (must be > 0)",
self.spatial.cell_size
));
}
if self.spatial.fov_default_radius < 0 {
errors.push(format!(
"spatial_fov_default_radius_negative: {}",
self.spatial.fov_default_radius
));
}
if self.spatial.fov_max_radius < self.spatial.fov_default_radius {
errors.push(format!(
"spatial_fov_max_radius_less_than_default: max={} default={}",
self.spatial.fov_max_radius, self.spatial.fov_default_radius
));
}
if self.spatial.max_pathfind_steps == 0 {
errors.push("spatial_max_pathfind_steps_zero".to_string());
}
if self.simulation.tick_rate_ms == 0 {
errors.push("simulation_tick_rate_ms_zero".to_string());
}
if self.simulation.ticks_per_day == 0 {
errors.push("simulation_ticks_per_day_zero".to_string());
}
if self.simulation.time_dilation <= 0.0 {
errors.push(format!(
"simulation_time_dilation_invalid: {} (must be > 0.0)",
self.simulation.time_dilation
));
}
if self.simulation.max_entities_per_area == 0 {
errors.push("simulation_max_entities_per_area_zero".to_string());
}
if self.simulation.max_events_per_tick == 0 {
errors.push("simulation_max_events_per_tick_zero".to_string());
}
if self.combat.base_hit_chance > 100 {
errors.push(format!(
"combat_base_hit_chance_out_of_range: {} (max 100)",
self.combat.base_hit_chance
));
}
if self.combat.crit_multiplier < 1.0 {
errors.push(format!(
"combat_crit_multiplier_too_low: {} (min 1.0)",
self.combat.crit_multiplier
));
}
if self.combat.dodge_base > 100 {
errors.push(format!(
"combat_dodge_base_out_of_range: {} (max 100)",
self.combat.dodge_base
));
}
if self.combat.parry_base > 100 {
errors.push(format!(
"combat_parry_base_out_of_range: {} (max 100)",
self.combat.parry_base
));
}
if self.combat.flee_threshold_hp_pct > 100 {
errors.push(format!(
"combat_flee_threshold_hp_pct_out_of_range: {} (max 100)",
self.combat.flee_threshold_hp_pct
));
}
if self.combat.revive_hp_pct > 100 {
errors.push(format!(
"combat_revive_hp_pct_out_of_range: {} (max 100)",
self.combat.revive_hp_pct
));
}
if self.combat.resistance_cap > 100 {
errors.push(format!(
"combat_resistance_cap_out_of_range: {} (max 100)",
self.combat.resistance_cap
));
}
if self.economy.trade_tax_pct > 100 {
errors.push(format!(
"economy_trade_tax_pct_out_of_range: {} (max 100)",
self.economy.trade_tax_pct
));
}
if self.economy.market_fee_pct > 100 {
errors.push(format!(
"economy_market_fee_pct_out_of_range: {} (max 100)",
self.economy.market_fee_pct
));
}
if self.economy.crafting_fail_chance_base > 100 {
errors.push(format!(
"economy_crafting_fail_chance_base_out_of_range: {} (max 100)",
self.economy.crafting_fail_chance_base
));
}
if self.progression.max_level == 0 {
errors.push("progression_max_level_zero".to_string());
}
if self.progression.xp_curve_base <= 0.0 {
errors.push(format!(
"progression_xp_curve_base_invalid: {} (must be > 0.0)",
self.progression.xp_curve_base
));
}
if self.progression.reputation_min > self.progression.reputation_max {
errors.push(format!(
"progression_reputation_range_inverted: min={} max={}",
self.progression.reputation_min, self.progression.reputation_max
));
}
if self.agents.cognition_budget_per_tick == 0 {
errors.push("agents_cognition_budget_per_tick_zero".to_string());
}
if self.agents.perception_range == 0 {
errors.push("agents_perception_range_zero".to_string());
}
if self.agents.memory_capacity == 0 {
errors.push("agents_memory_capacity_zero".to_string());
}
if self.agents.inference_timeout_ms == 0 {
errors.push("agents_inference_timeout_ms_zero".to_string());
}
if self.agents.inference_max_tokens == 0 {
errors.push("agents_inference_max_tokens_zero".to_string());
}
if self.canon.block_hash_interval == 0 {
errors.push("canon_block_hash_interval_zero".to_string());
}
if self.canon.tick_hash_interval == 0 {
errors.push("canon_tick_hash_interval_zero".to_string());
}
if self.canon.max_traversal_depth == 0 {
errors.push("canon_max_traversal_depth_zero".to_string());
}
if self.scripting.max_runes == 0 {
errors.push("scripting_max_runes_zero".to_string());
}
if self.scripting.fire_once_capacity == 0 {
errors.push("scripting_fire_once_capacity_zero".to_string());
}
if self.chronoshift.enabled {
if self.chronoshift.max_checkpoint_depth == 0 {
errors.push("chronoshift_max_checkpoint_depth_zero".to_string());
}
if self.chronoshift.replay_speed_multiplier <= 0.0 {
errors.push(format!(
"chronoshift_replay_speed_multiplier_invalid: {} (must be > 0.0)",
self.chronoshift.replay_speed_multiplier
));
}
if self.chronoshift.max_fork_count == 0 {
errors.push("chronoshift_max_fork_count_zero".to_string());
}
if self.chronoshift.fork_compute_cap_pct > 100 {
errors.push(format!(
"chronoshift_fork_compute_cap_pct_out_of_range: {} (max 100)",
self.chronoshift.fork_compute_cap_pct
));
}
}
let mut prop_ids = std::collections::HashSet::new();
for (i, prop) in self.props.iter().enumerate() {
if prop.id.is_empty() {
errors.push(format!("prop[{}]_id_required", i));
} else if !prop_ids.insert(&prop.id) {
errors.push(format!("prop_id_duplicate: \"{}\"", prop.id));
}
if prop.name.is_empty() {
errors.push(format!("prop[{}]_name_required (id: \"{}\")", i, prop.id));
}
if prop.has_secondary_state && prop.states.is_empty() {
errors.push(format!("prop_has_secondary_state_but_no_states: \"{}\"", prop.id));
}
if let Some(ref default_state) = prop.default_state {
if prop.has_secondary_state && !prop.states.contains_key(default_state) {
errors.push(format!(
"prop_default_state_not_found: \"{}\" state \"{}\"",
prop.id, default_state
));
}
}
if prop.has_inventory && (prop.container_capacity.is_none() || prop.container_capacity == Some(0)) {
errors.push(format!("prop_has_inventory_but_no_capacity: \"{}\"", prop.id));
}
}
errors
}
pub fn is_legacy_format(json: &serde_json::Value) -> bool {
if let Some(obj) = json.as_object() {
obj.contains_key("id") && !obj.contains_key("schema_version")
} else {
false
}
}
pub fn migrate_legacy(json: &serde_json::Value) -> Result<Self, String> {
let obj = json
.as_object()
.ok_or_else(|| "migrate_legacy_error: expected JSON object".to_string())?;
let mut migrated = obj.clone();
migrated.insert(
"schema_version".to_string(),
serde_json::Value::String(SCHEMA_VERSION.to_string()),
);
let json_str =
serde_json::to_string(&migrated).map_err(|e| format!("migrate_legacy_serialize_error: {}", e))?;
Self::from_json(&json_str)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_pack_has_schema_version() {
let pack = DreamwellPackV1::default();
assert_eq!(pack.schema_version, SCHEMA_VERSION);
}
#[test]
fn default_pack_validation_requires_id_and_title() {
let pack = DreamwellPackV1::default();
let errors = pack.validate();
assert!(errors.iter().any(|e| e.contains("id_required")));
assert!(errors.iter().any(|e| e.contains("title_required")));
}
#[test]
fn minimal_valid_pack() {
let json = r#"{"id": "test_pack", "title": "Test Pack"}"#;
let pack = DreamwellPackV1::from_json(json).unwrap();
assert_eq!(pack.id, "test_pack");
assert_eq!(pack.title, "Test Pack");
assert_eq!(pack.schema_version, SCHEMA_VERSION);
assert_eq!(pack.grid.width, 80);
assert_eq!(pack.grid.height, 50);
assert_eq!(pack.grid.chunk_size, 32);
assert_eq!(pack.spatial.cell_size, 128);
assert_eq!(pack.combat.base_hit_chance, 80);
assert_eq!(pack.economy.starting_gold, 100);
assert!(pack.validate().is_empty());
}
#[test]
fn roundtrip_serialization() {
let json = r#"{"id": "roundtrip", "title": "Roundtrip Test"}"#;
let pack = DreamwellPackV1::from_json(json).unwrap();
let serialized = pack.to_json();
let reparsed = DreamwellPackV1::from_json(&serialized).unwrap();
assert_eq!(reparsed.id, "roundtrip");
assert_eq!(reparsed.grid.width, 80);
assert_eq!(reparsed.combat.crit_multiplier, 2.0);
}
#[test]
fn legacy_format_detection() {
let legacy: serde_json::Value =
serde_json::from_str(r#"{"id": "arena", "title": "Training Arena", "grid": {"width": 32, "height": 16}}"#)
.unwrap();
assert!(DreamwellPackV1::is_legacy_format(&legacy));
let v1: serde_json::Value = serde_json::from_str(
r#"{"id": "arena", "title": "Training Arena", "schema_version": "dreamwell_waymark_v1.0.0"}"#,
)
.unwrap();
assert!(!DreamwellPackV1::is_legacy_format(&v1));
}
#[test]
fn legacy_migration() {
let legacy: serde_json::Value = serde_json::from_str(
r#"{
"id": "arena",
"title": "Training Arena",
"version": "0.1.0",
"grid": {"width": 32, "height": 16},
"features": {"content_plan": false, "boons": false}
}"#,
)
.unwrap();
let pack = DreamwellPackV1::migrate_legacy(&legacy).unwrap();
assert_eq!(pack.id, "arena");
assert_eq!(pack.schema_version, SCHEMA_VERSION);
assert_eq!(pack.grid.width, 32);
assert_eq!(pack.grid.height, 16);
assert!(!pack.features.content_plan);
assert!(!pack.features.boons);
assert_eq!(pack.combat.base_hit_chance, 80);
assert_eq!(pack.spatial.cell_size, 128);
}
#[test]
fn legacy_ayora_pack_loads() {
let json = r#"{
"id": "ayora",
"title": "Ayora: The Barracks",
"scenario_script": "scenario/main.wm",
"uses_companion_services": true,
"scenario_config": {
"companion_ids": ["kael", "ryn", "senna"],
"boss_npc_id": "grimsby"
},
"version": "0.1.0",
"grid": {"width": 120, "height": 80},
"display": {"mana_name": "Grace"},
"features": {"content_plan": false, "boons": false, "shrines": false},
"equip_slots": ["Head", "Body", "Hands", "Feet", "Weapon", "Offhand"],
"boon_triggers": [{"id": "pool_room_5", "type": "RoomEntry"}],
"props": [
{
"id": "weapon_rack",
"name": "Weapon Rack",
"glyph": "\u2551",
"description": "A training weapon rests in the rack.",
"blocking": true,
"targetable": true,
"interactive": false,
"bump_messages": ["The rack is bolted to the wall."]
}
]
}"#;
let pack = DreamwellPackV1::from_json(json).unwrap();
assert_eq!(pack.id, "ayora");
assert_eq!(pack.grid.width, 120);
assert_eq!(pack.grid.height, 80);
assert_eq!(pack.display.mana_name, "Grace");
assert!(pack.uses_companion_services);
assert_eq!(pack.equip_slots.len(), 6);
assert_eq!(pack.props.len(), 1);
assert_eq!(pack.props[0].id, "weapon_rack");
assert!(pack.validate().is_empty());
}
#[test]
fn legacy_embersteel_pack_loads() {
let json = r#"{
"id": "embersteel",
"title": "Operation: Embersteel",
"description": "Restore an abandoned research outpost.",
"world_id": "world:braxxis:v1",
"theme": "SURVIVAL",
"version": "1.0.0",
"tags": ["ascii", "roguelike", "survival"],
"ai_brains": {
"passive_roamer": {"base_brain": "state_machine", "profile": "passive_roamer"}
},
"starting_map": "olympus_9",
"scenario_script": "scenario/main.wm",
"grid": {"width": 80, "height": 55},
"features": {"containers": true, "line_of_sight": true, "collisions": true, "identity_system": true},
"rules": {"tick_rate_hz": 20, "determinism": "STRICT"},
"identity": {"format": "{glyph}#{suffix}"},
"boot_sequence": {"lines": ["[INFO] Signal Received"]},
"generators": {
"olympus_9": {"generator_type": "template", "template": "olympus_9"}
},
"props": [
{"id": "printer", "name": "Bioprinter", "glyph": "P", "description": "Still warm.", "blocking": true, "targetable": true, "interactive": true}
]
}"#;
let pack = DreamwellPackV1::from_json(json).unwrap();
assert_eq!(pack.id, "embersteel");
assert_eq!(pack.world_id, Some("world:braxxis:v1".to_string()));
assert_eq!(pack.theme, Some("SURVIVAL".to_string()));
assert!(pack.features.containers);
assert!(pack.features.identity_system);
assert!(pack.features.line_of_sight);
assert_eq!(pack.ai_brains.len(), 1);
assert!(pack.rules.is_some());
assert!(pack.identity.is_some());
assert!(pack.boot_sequence.is_some());
assert!(pack.validate().is_empty());
}
#[test]
fn tile_defaults_match_tile_rs() {
let tiles = TileConfig::default();
assert_eq!(tiles.ground.glyph, b'.' as u16);
assert_eq!(tiles.dirt.glyph, b',' as u16);
assert_eq!(tiles.sand.glyph, b':' as u16);
assert_eq!(tiles.mud.glyph, b'`' as u16);
assert_eq!(tiles.liquid.glyph, b'~' as u16);
assert_eq!(tiles.deep_water.glyph, b'=' as u16);
assert_eq!(tiles.rocky_ground.glyph, b'b' as u16);
assert_eq!(tiles.grass.glyph, b';' as u16);
assert_eq!(tiles.debris.glyph, b'\'' as u16);
assert_eq!(tiles.wall.glyph, b'#' as u16);
assert_eq!(tiles.wall_vert.glyph, b'|' as u16);
assert_eq!(tiles.wall_horiz.glyph, b'_' as u16);
assert_eq!(tiles.door_closed.glyph, b'I' as u16);
assert_eq!(tiles.door_open.glyph, b'*' as u16);
assert_eq!(tiles.stairs_up.glyph, b'^' as u16);
assert_eq!(tiles.stairs_down.glyph, b'v' as u16);
assert_eq!(tiles.boulder.glyph, b'o' as u16);
assert_eq!(tiles.chest.glyph, b'C' as u16);
assert_eq!(tiles.shrine.glyph, b'S' as u16);
assert_eq!(tiles.hazard.glyph, b'x' as u16);
assert_eq!(tiles.smoke.glyph, b'%' as u16);
assert_eq!(tiles.ground.move_cost, 1);
assert_eq!(tiles.sand.move_cost, 2);
assert_eq!(tiles.liquid.move_cost, 3);
assert_eq!(tiles.wall.move_cost, 999);
assert!(tiles.wall.blocks_move);
assert!(tiles.door_closed.blocks_move);
assert!(tiles.deep_water.blocks_move);
assert!(tiles.boulder.blocks_move);
assert!(!tiles.ground.blocks_move);
assert!(!tiles.liquid.blocks_move);
assert!(tiles.wall.blocks_sight);
assert!(tiles.door_closed.blocks_sight);
assert!(tiles.boulder.blocks_sight);
assert!(tiles.smoke.blocks_sight);
assert!(!tiles.ground.blocks_sight);
assert!(!tiles.deep_water.blocks_sight);
}
#[test]
fn prop_validation_catches_duplicates() {
let json = r#"{
"id": "test",
"title": "Test",
"props": [
{"id": "p1", "name": "Prop One"},
{"id": "p1", "name": "Prop One Dupe"}
]
}"#;
let pack = DreamwellPackV1::from_json(json).unwrap();
let errors = pack.validate();
assert!(errors.iter().any(|e| e.contains("prop_id_duplicate")));
}
#[test]
fn prop_validation_catches_state_mismatch() {
let json = r#"{
"id": "test",
"title": "Test",
"props": [
{
"id": "broken",
"name": "Broken Prop",
"has_secondary_state": true,
"default_state": "missing_state"
}
]
}"#;
let pack = DreamwellPackV1::from_json(json).unwrap();
let errors = pack.validate();
assert!(errors.iter().any(|e| e.contains("has_secondary_state_but_no_states")));
assert!(errors.iter().any(|e| e.contains("default_state_not_found")));
}
#[test]
fn combat_range_validation() {
let json = r#"{
"id": "test",
"title": "Test",
"combat": {"base_hit_chance": 150, "dodge_base": 101, "resistance_cap": 200}
}"#;
let pack = DreamwellPackV1::from_json(json).unwrap();
let errors = pack.validate();
assert!(errors.iter().any(|e| e.contains("base_hit_chance_out_of_range")));
assert!(errors.iter().any(|e| e.contains("dodge_base_out_of_range")));
assert!(errors.iter().any(|e| e.contains("resistance_cap_out_of_range")));
}
#[test]
fn v1_full_config_loads() {
let json = r#"{
"id": "full",
"title": "Full Config",
"schema_version": "dreamwell_waymark_v1.0.0",
"simulation": {"tick_rate_ms": 50, "ticks_per_day": 720, "time_dilation": 2.0},
"combat": {"base_hit_chance": 75, "crit_multiplier": 2.5, "damage_types": ["physical", "fire"]},
"economy": {"starting_gold": 500, "currency_types": ["gold", "silver"]},
"progression": {"max_level": 50, "xp_curve_base": 1.8},
"agents": {"cognition_budget_per_tick": 5, "max_concurrent_agents": 128},
"canon": {"block_hash_interval": 200, "max_trigger_depth": 5},
"scripting": {"max_runes": 512, "hot_reload_enabled": true},
"chronoshift": {"enabled": true, "max_fork_count": 8},
"forensics": {"proof_lane_enabled": true, "blake3_domain_separation": true}
}"#;
let pack = DreamwellPackV1::from_json(json).unwrap();
assert_eq!(pack.simulation.tick_rate_ms, 50);
assert_eq!(pack.simulation.ticks_per_day, 720);
assert_eq!(pack.combat.base_hit_chance, 75);
assert_eq!(pack.combat.damage_types.len(), 2);
assert_eq!(pack.economy.starting_gold, 500);
assert_eq!(pack.economy.currency_types.len(), 2);
assert_eq!(pack.progression.max_level, 50);
assert_eq!(pack.agents.max_concurrent_agents, 128);
assert_eq!(pack.canon.block_hash_interval, 200);
assert_eq!(pack.scripting.max_runes, 512);
assert!(pack.scripting.hot_reload_enabled);
assert!(pack.chronoshift.enabled);
assert_eq!(pack.chronoshift.max_fork_count, 8);
assert!(pack.forensics.proof_lane_enabled);
assert!(pack.validate().is_empty());
}
#[test]
fn cartographers_toolkit_with_encumbrance_loads() {
let json = r#"{
"id": "cartographers_toolkit",
"title": "The Cartographer's Toolkit",
"version": "0.1.0",
"grid": {"width": 120, "height": 80},
"features": {"content_plan": false, "encumbrance": true, "containers": true},
"encumbrance": {
"capacity_stat": "strength",
"capacity_multiplier": 1000,
"tiers": [
{"id": "burdened", "threshold": 0.75, "speed_modifier": 0.5},
{"id": "overloaded", "threshold": 1.0, "speed_modifier": 0.0}
]
},
"equip_slots": ["Head", "Body", "Weapon", "Ring1", "Ring2"],
"generators": {
"cartographers_toolkit": {
"player_spawn": [60, 5],
"player_stats": {"hp": 25, "atk": 4, "def": 2, "mana": 20}
}
}
}"#;
let pack = DreamwellPackV1::from_json(json).unwrap();
assert_eq!(pack.id, "cartographers_toolkit");
assert!(pack.features.encumbrance);
assert!(pack.features.containers);
assert!(pack.encumbrance.is_some());
assert_eq!(pack.equip_slots.len(), 5);
assert!(pack.validate().is_empty());
}
#[test]
fn radiant_forest_with_ai_brains_loads() {
let json = r#"{
"id": "radiant_forest",
"title": "The Radiant Forest",
"ai_brains": {"wisp_brain": {"base_brain": "state_machine"}},
"starting_map": "compound",
"scenario_script": "scenario/main.wm",
"version": "0.1.0",
"grid": {"width": 80, "height": 55},
"features": {"containers": true}
}"#;
let pack = DreamwellPackV1::from_json(json).unwrap();
assert_eq!(pack.id, "radiant_forest");
assert_eq!(pack.starting_map, Some("compound".to_string()));
assert_eq!(pack.ai_brains.len(), 1);
assert!(pack.ai_brains.contains_key("wisp_brain"));
assert!(pack.validate().is_empty());
}
}