#[cfg(test)]
mod tests {
use super::super::{generate_truth_table, BlockPos, MchprsWorld, SimulationOptions};
use crate::{BlockState, UniversalSchematic};
fn create_simple_redstone_line() -> UniversalSchematic {
let mut schematic = UniversalSchematic::new("Simple Redstone Line".to_string());
for x in 0..16 {
schematic.set_block(
x,
0,
0,
&BlockState::new("minecraft:gray_concrete".to_string()),
);
}
for x in 1..15 {
let mut wire = BlockState::new("minecraft:redstone_wire".to_string());
wire.set_property("power", "0");
wire.set_property("east", "side");
wire.set_property("west", "side");
wire.set_property("north", "none");
wire.set_property("south", "none");
schematic.set_block(x, 1, 0, &wire);
}
let mut lever = BlockState::new("minecraft:lever".to_string());
lever.set_property("facing", "east");
lever.set_property("powered", "false");
lever.set_property("face", "floor");
schematic.set_block(0, 1, 0, &lever);
let mut lamp = BlockState::new("minecraft:redstone_lamp".to_string());
lamp.set_property("lit", "false");
schematic.set_block(15, 1, 0, &lamp);
schematic
}
fn create_and_gate() -> UniversalSchematic {
let mut schematic = UniversalSchematic::new("AND Gate".to_string());
for x in 0..3 {
for z in 0..4 {
schematic.set_block(
x,
0,
z,
&BlockState::new("minecraft:gray_concrete".to_string()),
);
}
}
let mut lever_a = BlockState::new("minecraft:lever".to_string());
lever_a.set_property("powered", "false");
lever_a.set_property("facing", "north");
lever_a.set_property("face", "floor");
schematic.set_block(0, 1, 0, &lever_a);
let mut lever_b = BlockState::new("minecraft:lever".to_string());
lever_b.set_property("powered", "false");
lever_b.set_property("facing", "north");
lever_b.set_property("face", "floor");
schematic.set_block(2, 1, 0, &lever_b);
let mut wire = BlockState::new("minecraft:redstone_wire".to_string());
wire.set_property("power", "0");
schematic.set_block(0, 1, 1, &wire);
schematic.set_block(1, 1, 1, &wire);
schematic.set_block(2, 1, 1, &wire);
schematic.set_block(1, 1, 2, &wire);
let mut lamp = BlockState::new("minecraft:redstone_lamp".to_string());
lamp.set_property("lit", "false");
schematic.set_block(1, 1, 3, &lamp);
schematic
}
#[test]
fn test_world_creation() {
let schematic = create_simple_redstone_line();
let world = MchprsWorld::new(schematic);
assert!(world.is_ok(), "World creation should succeed");
}
#[test]
fn test_lever_toggle() {
let schematic = create_simple_redstone_line();
let mut world = MchprsWorld::new(schematic).expect("World creation failed");
let lever_pos = BlockPos::new(0, 1, 0);
assert!(
!world.get_lever_power(lever_pos),
"Lever should start unpowered"
);
world.on_use_block(lever_pos);
assert!(
world.get_lever_power(lever_pos),
"Lever should be powered after toggle"
);
world.on_use_block(lever_pos);
assert!(
!world.get_lever_power(lever_pos),
"Lever should be unpowered after second toggle"
);
}
#[test]
fn test_redstone_propagation() {
let schematic = create_simple_redstone_line();
let mut world = MchprsWorld::new(schematic).expect("World creation failed");
let lever_pos = BlockPos::new(0, 1, 0);
let lamp_pos = BlockPos::new(15, 1, 0);
assert!(!world.is_lit(lamp_pos), "Lamp should start off");
world.on_use_block(lever_pos);
world.tick(2);
world.flush();
assert!(
world.is_lit(lamp_pos),
"Lamp should be lit after lever is toggled on"
);
world.on_use_block(lever_pos);
world.tick(2);
world.flush();
assert!(
!world.is_lit(lamp_pos),
"Lamp should be off after lever is toggled off"
);
}
#[test]
fn test_redstone_power_levels() {
let schematic = create_simple_redstone_line();
let mut world = MchprsWorld::new(schematic).expect("World creation failed");
let lever_pos = BlockPos::new(0, 1, 0);
world.on_use_block(lever_pos);
world.tick(2);
world.flush();
for x in 1..15 {
let wire_pos = BlockPos::new(x, 1, 0);
let power = world.get_redstone_power(wire_pos);
assert!(power > 0, "Wire at x={} should have power", x);
assert!(power <= 15, "Power should not exceed 15");
}
}
#[test]
fn test_and_gate_truth_table() {
let schematic = create_and_gate();
let truth_table = generate_truth_table(&schematic);
assert_eq!(
truth_table.len(),
4,
"AND gate should have 4 truth table entries"
);
assert!(
truth_table.iter().all(|row| {
row.contains_key("Input 0")
&& row.contains_key("Input 1")
&& row.contains_key("Output 0")
}),
"Truth table should have all required keys"
);
}
#[test]
fn test_multiple_ticks() {
let schematic = create_simple_redstone_line();
let mut world = MchprsWorld::new(schematic).expect("World creation failed");
let lever_pos = BlockPos::new(0, 1, 0);
let lamp_pos = BlockPos::new(15, 1, 0);
world.on_use_block(lever_pos);
world.tick(20); world.flush();
assert!(
world.is_lit(lamp_pos),
"Lamp should be lit after sufficient ticks"
);
}
#[test]
fn test_world_state_persistence() {
let schematic = create_simple_redstone_line();
let mut world = MchprsWorld::new(schematic).expect("World creation failed");
let lever_pos = BlockPos::new(0, 1, 0);
world.on_use_block(lever_pos);
world.tick(1);
world.flush();
let state_after_toggle = world.get_lever_power(lever_pos);
world.tick(10);
world.flush();
assert_eq!(
world.get_lever_power(lever_pos),
state_after_toggle,
"Lever state should persist across ticks"
);
}
#[test]
fn test_signal_strength_set_get() {
use super::super::SimulationOptions;
let schematic = create_simple_redstone_line();
let wire_pos = BlockPos::new(5, 1, 0);
let options = SimulationOptions {
custom_io: vec![wire_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
assert_eq!(
world.get_signal_strength(wire_pos),
0,
"Signal strength should start at 0"
);
world.set_signal_strength(wire_pos, 10);
world.tick(1);
world.flush();
let strength = world.get_signal_strength(wire_pos);
assert_eq!(
strength, 10,
"Signal strength should be readable after setting"
);
}
#[test]
fn test_signal_strength_boundary_values() {
use super::super::SimulationOptions;
let schematic = create_simple_redstone_line();
let wire_pos = BlockPos::new(5, 1, 0);
let options = SimulationOptions {
custom_io: vec![wire_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.set_signal_strength(wire_pos, 0);
world.tick(1);
world.flush();
assert_eq!(
world.get_signal_strength(wire_pos),
0,
"Should handle signal strength of 0"
);
world.set_signal_strength(wire_pos, 15);
world.tick(1);
world.flush();
assert_eq!(
world.get_signal_strength(wire_pos),
15,
"Should handle signal strength of 15"
);
world.set_signal_strength(wire_pos, 7);
world.tick(1);
world.flush();
assert_eq!(
world.get_signal_strength(wire_pos),
7,
"Should handle mid-range signal strength"
);
}
#[test]
fn test_signal_strength_update() {
use super::super::SimulationOptions;
let schematic = create_simple_redstone_line();
let wire_pos = BlockPos::new(5, 1, 0);
let options = SimulationOptions {
custom_io: vec![wire_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.set_signal_strength(wire_pos, 8);
world.tick(1);
world.flush();
assert_eq!(
world.get_signal_strength(wire_pos),
8,
"Should update signal strength"
);
world.set_signal_strength(wire_pos, 3);
world.tick(1);
world.flush();
assert_eq!(
world.get_signal_strength(wire_pos),
3,
"Should update to new signal strength"
);
}
#[test]
fn test_signal_strength_with_lever() {
use super::super::SimulationOptions;
let schematic = create_simple_redstone_line();
let lever_pos = BlockPos::new(0, 1, 0);
let wire_pos = BlockPos::new(5, 1, 0);
let lamp_pos = BlockPos::new(15, 1, 0);
let options = SimulationOptions {
custom_io: vec![wire_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
assert!(!world.is_lit(lamp_pos), "Lamp should start off");
world.on_use_block(lever_pos);
world.tick(5);
world.flush();
assert!(world.is_lit(lamp_pos), "Lamp should light up from lever");
let custom_signal = world.get_signal_strength(wire_pos);
let _ = custom_signal;
}
#[test]
fn test_signal_strength_multiple_positions() {
use super::super::SimulationOptions;
let schematic = create_simple_redstone_line();
let pos1 = BlockPos::new(3, 1, 0);
let pos2 = BlockPos::new(7, 1, 0);
let pos3 = BlockPos::new(11, 1, 0);
let options = SimulationOptions {
custom_io: vec![pos1, pos2, pos3],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.set_signal_strength(pos1, 5);
world.set_signal_strength(pos2, 10);
world.set_signal_strength(pos3, 15);
world.tick(5);
world.flush();
assert_eq!(
world.get_signal_strength(pos1),
5,
"Position 1 should have signal strength 5"
);
assert_eq!(
world.get_signal_strength(pos2),
10,
"Position 2 should have signal strength 10"
);
assert_eq!(
world.get_signal_strength(pos3),
15,
"Position 3 should have signal strength 15"
);
}
#[test]
fn test_signal_strength_persistence() {
use super::super::SimulationOptions;
let schematic = create_simple_redstone_line();
let wire_pos = BlockPos::new(5, 1, 0);
let options = SimulationOptions {
custom_io: vec![wire_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.set_signal_strength(wire_pos, 12);
world.tick(1);
world.flush();
let initial_strength = world.get_signal_strength(wire_pos);
world.tick(20);
world.flush();
assert_eq!(
world.get_signal_strength(wire_pos),
initial_strength,
"Signal strength should persist across ticks"
);
}
#[test]
fn test_signal_strength_invalid_position() {
let schematic = create_simple_redstone_line();
let world = MchprsWorld::new(schematic).expect("World creation failed");
let invalid_pos = BlockPos::new(100, 100, 100);
let strength = world.get_signal_strength(invalid_pos);
assert_eq!(
strength, 0,
"Invalid position should return signal strength of 0"
);
}
#[test]
fn test_bracket_notation_set_block() {
let mut schematic = UniversalSchematic::new("Bracket Notation Test".to_string());
schematic.set_block(
0,
0,
0,
&BlockState::new("minecraft:gray_concrete".to_string()),
);
schematic.set_block(
15,
0,
0,
&BlockState::new("minecraft:gray_concrete".to_string()),
);
schematic.set_block_str(
0,
1,
0,
"minecraft:lever[facing=east,powered=false,face=floor]",
);
for x in 1..15 {
schematic.set_block_str(
x,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=side,north=none,south=none]",
);
}
schematic.set_block_str(15, 1, 0, "minecraft:redstone_lamp[lit=false]");
let lever = schematic.get_block(0, 1, 0).expect("Lever should exist");
assert_eq!(
lever.get_name(),
"minecraft:lever",
"Lever should have correct name"
);
assert_eq!(
lever.get_property("facing").map(|s| s.as_str()),
Some("east"),
"Lever should have facing=east"
);
assert_eq!(
lever.get_property("powered").map(|s| s.as_str()),
Some("false"),
"Lever should have powered=false"
);
assert_eq!(
lever.get_property("face").map(|s| s.as_str()),
Some("floor"),
"Lever should have face=floor"
);
let wire = schematic.get_block(5, 1, 0).expect("Wire should exist");
assert_eq!(
wire.get_name(),
"minecraft:redstone_wire",
"Wire should have correct name"
);
assert_eq!(
wire.get_property("power").map(|s| s.as_str()),
Some("0"),
"Wire should have power=0"
);
assert_eq!(
wire.get_property("east").map(|s| s.as_str()),
Some("side"),
"Wire should have east=side"
);
let lamp = schematic.get_block(15, 1, 0).expect("Lamp should exist");
assert_eq!(
lamp.get_name(),
"minecraft:redstone_lamp",
"Lamp should have correct name"
);
assert_eq!(
lamp.get_property("lit").map(|s| s.as_str()),
Some("false"),
"Lamp should have lit=false"
);
let mut world = MchprsWorld::new(schematic)
.expect("World creation should succeed with bracket notation blocks");
let lever_pos = BlockPos::new(0, 1, 0);
let lamp_pos = BlockPos::new(15, 1, 0);
assert!(!world.is_lit(lamp_pos), "Lamp should start off");
world.on_use_block(lever_pos);
world.tick(2);
world.flush();
assert!(
world.is_lit(lamp_pos),
"Lamp should be lit after lever is toggled with bracket notation blocks"
);
}
fn create_wire_to_lamp_circuit() -> UniversalSchematic {
let mut schematic = UniversalSchematic::new("Wire to Lamp Test".to_string());
for x in 0..5 {
schematic.set_block(x, 0, 0, &BlockState::new("minecraft:stone".to_string()));
}
schematic.set_block_str(
0,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=none,north=none,south=none]",
);
schematic.set_block_str(
1,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=side,north=none,south=none]",
);
schematic.set_block_str(
2,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=side,north=none,south=none]",
);
schematic.set_block_str(3, 1, 0, "minecraft:redstone_lamp[lit=false]");
schematic
}
#[test]
fn test_custom_io_injection_powers_wire() {
use super::super::SimulationOptions;
let schematic = create_wire_to_lamp_circuit();
let inject_pos = BlockPos::new(0, 1, 0);
let options = SimulationOptions {
custom_io: vec![inject_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
let initial_signal = world.get_signal_strength(inject_pos);
assert_eq!(initial_signal, 0, "Wire should start with no signal");
world.set_signal_strength(inject_pos, 15);
world.tick(5);
world.flush();
let signal_strength = world.get_signal_strength(inject_pos);
assert_eq!(
signal_strength, 15,
"Custom IO must store injected signal strength"
);
}
#[test]
fn test_custom_io_injection_lights_lamp() {
use super::super::SimulationOptions;
let schematic = create_wire_to_lamp_circuit();
let inject_pos = BlockPos::new(0, 1, 0);
let lamp_pos = BlockPos::new(3, 1, 0);
let options = SimulationOptions {
custom_io: vec![inject_pos, lamp_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
assert!(!world.is_lit(lamp_pos), "Lamp should start off");
world.set_signal_strength(inject_pos, 15);
world.flush(); world.tick(10);
world.flush();
let is_lit = world.is_lit(lamp_pos);
let signal = world.get_signal_strength(inject_pos);
let wire_power = world.get_redstone_power(inject_pos);
assert!(
is_lit,
"CRITICAL: Injecting signal via custom IO MUST light the lamp. Signal={}, Wire power={}",
signal, wire_power
);
}
#[test]
fn test_custom_io_monitoring_natural_power() {
use super::super::SimulationOptions;
let mut schematic = UniversalSchematic::new("Powered Circuit".to_string());
for x in 0..5 {
schematic.set_block(x, 0, 0, &BlockState::new("minecraft:stone".to_string()));
}
schematic.set_block_str(0, 1, 0, "minecraft:redstone_block");
schematic.set_block_str(
1,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=side,north=none,south=none]",
);
schematic.set_block_str(
2,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=side,north=none,south=none]",
);
let monitor_pos = BlockPos::new(2, 1, 0);
let options = SimulationOptions {
custom_io: vec![monitor_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.tick(5);
world.flush();
let signal = world.get_signal_strength(monitor_pos);
let power = world.get_redstone_power(monitor_pos);
assert!(
signal > 0,
"Custom IO should read signal from naturally powered circuit"
);
assert!(power > 0, "Natural power should exist");
}
#[test]
fn test_custom_io_relay_between_circuits() {
use super::super::SimulationOptions;
let mut circuit_a = UniversalSchematic::new("Circuit A".to_string());
for x in 0..3 {
circuit_a.set_block(x, 0, 0, &BlockState::new("minecraft:stone".to_string()));
}
circuit_a.set_block_str(0, 1, 0, "minecraft:redstone_block");
circuit_a.set_block_str(
1,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=side,north=none,south=none]",
);
circuit_a.set_block_str(
2,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=side,north=none,south=none]",
);
let output_pos = BlockPos::new(2, 1, 0);
let options_a = SimulationOptions {
custom_io: vec![output_pos],
..Default::default()
};
let mut world_a =
MchprsWorld::with_options(circuit_a, options_a).expect("Failed to create world A");
let circuit_b = create_wire_to_lamp_circuit();
let input_pos = BlockPos::new(0, 1, 0);
let lamp_pos = BlockPos::new(3, 1, 0);
let options_b = SimulationOptions {
custom_io: vec![input_pos, lamp_pos],
..Default::default()
};
let mut world_b =
MchprsWorld::with_options(circuit_b, options_b).expect("Failed to create world B");
world_a.tick(5);
world_a.flush();
let output_signal = world_a.get_signal_strength(output_pos);
assert!(output_signal > 0, "Circuit A should produce output signal");
world_b.set_signal_strength(input_pos, output_signal);
world_b.flush(); world_b.tick(10);
world_b.flush();
let lamp_lit = world_b.is_lit(lamp_pos);
assert!(
lamp_lit,
"Circuit B's lamp should light from relayed signal (signal={})",
output_signal
);
}
#[test]
fn test_custom_io_sync_to_schematic_preserves_power() {
use super::super::SimulationOptions;
use mchprs_world::World;
let mut schematic = UniversalSchematic::new("Custom IO Sync Test".to_string());
schematic.set_block(
0,
0,
0,
&BlockState::new("minecraft:gray_concrete".to_string()),
);
let mut wire = BlockState::new("minecraft:redstone_wire".to_string());
wire.set_property("power", "0");
wire.set_property("east", "side");
wire.set_property("west", "side");
wire.set_property("north", "none");
wire.set_property("south", "none");
schematic.set_block(0, 1, 0, &wire);
let custom_io_pos = BlockPos::new(0, 1, 0);
let options = SimulationOptions {
custom_io: vec![custom_io_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.set_signal_strength(custom_io_pos, 15);
world.tick(5);
world.flush();
world.sync_to_schematic();
let synced_schematic = world.get_schematic();
let synced_block = synced_schematic
.get_block(0, 1, 0)
.expect("Block should exist at custom IO position");
let power_value = synced_block
.get_property("power")
.expect("Redstone wire should have power property");
assert_eq!(
power_value.as_str(),
"15",
"Synced schematic should have power=15 after custom IO injection, got power={}",
power_value
);
}
#[test]
fn test_custom_io_with_adjacent_wires() {
use super::super::SimulationOptions;
use mchprs_world::World;
let mut schematic = UniversalSchematic::new("Custom IO Adjacent Test".to_string());
for x in 0..3 {
schematic.set_block(
x,
0,
0,
&BlockState::new("minecraft:gray_concrete".to_string()),
);
}
for x in 0..3 {
let mut wire = BlockState::new("minecraft:redstone_wire".to_string());
wire.set_property("power", "0");
wire.set_property("east", "side");
wire.set_property("west", "side");
wire.set_property("north", "none");
wire.set_property("south", "none");
schematic.set_block(x, 1, 0, &wire);
}
let custom_io_pos = BlockPos::new(0, 1, 0);
let _adjacent_pos = BlockPos::new(1, 1, 0);
let _far_pos = BlockPos::new(2, 1, 0);
let options = SimulationOptions {
custom_io: vec![custom_io_pos],
io_only: false, optimize: false, ..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.set_signal_strength(custom_io_pos, 15);
world.tick(5); world.flush();
world.sync_to_schematic();
let synced_schematic = world.get_schematic();
let custom_io_block = synced_schematic
.get_block(0, 1, 0)
.expect("Custom IO block should exist");
let custom_io_power: u8 = custom_io_block
.get_property("power")
.and_then(|p| p.parse().ok())
.unwrap_or(0);
let adjacent_block = synced_schematic
.get_block(1, 1, 0)
.expect("Adjacent block should exist");
let adjacent_power: u8 = adjacent_block
.get_property("power")
.and_then(|p| p.parse().ok())
.unwrap_or(0);
let far_block = synced_schematic
.get_block(2, 1, 0)
.expect("Far block should exist");
let far_power: u8 = far_block
.get_property("power")
.and_then(|p| p.parse().ok())
.unwrap_or(0);
eprintln!("[TEST] Custom IO wire power: {}", custom_io_power);
eprintln!("[TEST] Adjacent wire power: {}", adjacent_power);
eprintln!("[TEST] Far wire power: {}", far_power);
assert_eq!(custom_io_power, 15, "Custom IO wire should have power 15");
assert!(
adjacent_power > 0,
"Adjacent wire should have power > 0, got {}",
adjacent_power
);
assert!(
far_power > 0,
"Far wire should have power > 0, got {}",
far_power
);
}
#[test]
fn test_io_only_mode_performance() {
use super::super::SimulationOptions;
let mut schematic = UniversalSchematic::new("IO Only Test".to_string());
for x in 0..3 {
schematic.set_block(
x,
0,
0,
&BlockState::new("minecraft:gray_concrete".to_string()),
);
}
for x in 0..3 {
let mut wire = BlockState::new("minecraft:redstone_wire".to_string());
wire.set_property("power", "0");
wire.set_property("east", "side");
wire.set_property("west", "side");
wire.set_property("north", "none");
wire.set_property("south", "none");
schematic.set_block(x, 1, 0, &wire);
}
let input_pos = BlockPos::new(0, 1, 0);
let output_pos = BlockPos::new(2, 1, 0);
let options = SimulationOptions {
custom_io: vec![input_pos, output_pos],
io_only: true, optimize: false, ..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.set_signal_strength(input_pos, 15);
world.tick(5);
world.flush();
let input_signal = world.get_signal_strength(input_pos);
let output_signal = world.get_signal_strength(output_pos);
eprintln!(
"[TEST] IO-only mode - Input signal: {}, Output signal: {}",
input_signal, output_signal
);
assert_eq!(
input_signal, 15,
"Should be able to read input signal in io_only mode"
);
assert!(
output_signal > 0,
"Output should receive signal in io_only mode"
);
}
#[test]
fn test_custom_io_callbacks_basic() {
let schematic = create_and_gate();
let input_a = BlockPos::new(0, 1, 1);
let input_b = BlockPos::new(2, 1, 1);
let output = BlockPos::new(1, 1, 2);
let options = SimulationOptions {
custom_io: vec![input_a, input_b, output],
optimize: false,
io_only: false,
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.check_custom_io_changes();
world.clear_custom_io_changes();
world.set_signal_strength(input_a, 15);
world.check_custom_io_changes();
let changes = world.poll_custom_io_changes();
eprintln!(
"[TEST] Changes after setting input A: {} changes",
changes.len()
);
assert!(!changes.is_empty(), "Should detect at least input A change");
let input_a_change = changes.iter().find(|c| c.x == 0 && c.y == 1 && c.z == 1);
assert!(input_a_change.is_some(), "Should detect input A change");
assert_eq!(
input_a_change.unwrap().new_power,
15,
"Input A should be powered to 15"
);
world.tick(5);
world.flush();
world.check_custom_io_changes();
world.poll_custom_io_changes();
world.set_signal_strength(input_b, 15);
eprintln!("[TEST] After set input_b to 15:");
eprintln!(
" get_signal_strength(input_a) = {}",
world.get_signal_strength(input_a)
);
eprintln!(
" get_signal_strength(input_b) = {}",
world.get_signal_strength(input_b)
);
world.check_custom_io_changes();
let changes = world.poll_custom_io_changes();
eprintln!(" Changes detected: {}", changes.len());
assert!(!changes.is_empty(), "Should detect input B change");
let input_b_change = changes.iter().find(|c| c.x == 2 && c.y == 1 && c.z == 1);
assert!(input_b_change.is_some(), "Should detect input B change");
world.tick(5);
world.flush();
let output_power = world.get_signal_strength(output);
assert!(
output_power > 0,
"Output should be powered when both inputs are high"
);
}
#[test]
fn test_custom_io_callbacks_multiple_changes() {
let schematic = create_and_gate();
let input_a = BlockPos::new(0, 1, 1); let input_b = BlockPos::new(2, 1, 1);
let options = SimulationOptions {
custom_io: vec![input_a, input_b],
optimize: false,
io_only: false,
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.check_custom_io_changes();
world.clear_custom_io_changes();
eprintln!("[TEST] Node existence check:");
eprintln!(
" input_a ({:?}) has_node: {}",
input_a,
world.has_node(input_a)
);
eprintln!(
" input_b ({:?}) has_node: {}",
input_b,
world.has_node(input_b)
);
world.set_signal_strength(input_a, 15);
eprintln!("[TEST] After set input_a to 15:");
eprintln!(
" get_signal_strength(input_a) = {}",
world.get_signal_strength(input_a)
);
eprintln!(
" get_signal_strength(input_b) = {}",
world.get_signal_strength(input_b)
);
world.check_custom_io_changes();
let changes = world.poll_custom_io_changes();
eprintln!(" Changes detected: {}", changes.len());
assert!(!changes.is_empty(), "Should detect at least input A change");
let input_a_change = changes.iter().find(|c| c.x == 0 && c.y == 1 && c.z == 1);
assert!(input_a_change.is_some(), "Should detect input A change");
world.set_signal_strength(input_b, 15);
eprintln!("[TEST] After set input_b to 15:");
eprintln!(
" get_signal_strength(input_a) = {}",
world.get_signal_strength(input_a)
);
eprintln!(
" get_signal_strength(input_b) = {}",
world.get_signal_strength(input_b)
);
world.check_custom_io_changes();
let changes = world.poll_custom_io_changes();
eprintln!(" Changes detected: {}", changes.len());
assert!(!changes.is_empty(), "Should detect at least input B change");
let input_b_change = changes.iter().find(|c| c.x == 2 && c.y == 1 && c.z == 1);
assert!(input_b_change.is_some(), "Should detect input B change");
world.set_signal_strength(input_a, 0);
eprintln!("[TEST] After set input_a to 0:");
eprintln!(
" get_signal_strength(input_a) = {}",
world.get_signal_strength(input_a)
);
eprintln!(
" get_signal_strength(input_b) = {}",
world.get_signal_strength(input_b)
);
world.check_custom_io_changes();
let changes = world.poll_custom_io_changes();
eprintln!(" Changes detected: {}", changes.len());
assert!(
!changes.is_empty(),
"Should detect at least input A change back to 0"
);
let input_a_change = changes.iter().find(|c| c.x == 0 && c.y == 1 && c.z == 1);
assert!(
input_a_change.is_some(),
"Should detect input A change to 0"
);
assert_eq!(input_a_change.unwrap().new_power, 0, "Input A should be 0");
let changes = world.poll_custom_io_changes();
assert_eq!(changes.len(), 0, "Queue should be empty");
}
#[test]
fn test_custom_io_callbacks_peek() {
let schematic = create_and_gate();
let input_a = BlockPos::new(0, 1, 0);
let options = SimulationOptions {
custom_io: vec![input_a],
optimize: false,
io_only: false,
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.check_custom_io_changes();
world.clear_custom_io_changes();
world.set_signal_strength(input_a, 15);
world.check_custom_io_changes();
let peeked1 = world.peek_custom_io_changes();
assert_eq!(peeked1.len(), 1, "Should see 1 change via peek");
let peeked2 = world.peek_custom_io_changes();
assert_eq!(peeked2.len(), 1, "Changes should still be in queue");
let polled = world.poll_custom_io_changes();
assert_eq!(polled.len(), 1, "Should poll 1 change");
let peeked3 = world.peek_custom_io_changes();
assert_eq!(peeked3.len(), 0, "Queue should be empty after poll");
}
#[test]
fn test_custom_io_callbacks_no_false_positives() {
let schematic = create_and_gate();
let input_a = BlockPos::new(0, 1, 0);
let options = SimulationOptions {
custom_io: vec![input_a],
optimize: false,
io_only: false,
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.check_custom_io_changes();
world.clear_custom_io_changes();
world.set_signal_strength(input_a, 15);
world.check_custom_io_changes();
world.poll_custom_io_changes();
world.set_signal_strength(input_a, 15);
world.check_custom_io_changes();
let changes = world.poll_custom_io_changes();
assert_eq!(
changes.len(),
0,
"Setting to same value should not trigger change"
);
world.tick(5);
world.flush();
world.check_custom_io_changes();
let changes = world.poll_custom_io_changes();
assert_eq!(
changes.len(),
0,
"Ticking with no changes should not trigger callbacks"
);
}
#[test]
fn test_custom_io_callbacks_performance_zero_overhead() {
let schematic = create_and_gate();
let options = SimulationOptions {
custom_io: vec![], optimize: false,
io_only: false,
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
let start = std::time::Instant::now();
for _ in 0..1000 {
world.check_custom_io_changes();
}
let elapsed = start.elapsed();
eprintln!(
"[TEST] 1000 check_custom_io_changes() calls with no custom IO: {:?}",
elapsed
);
assert!(
elapsed.as_micros() < 1000,
"Should have near-zero overhead when no custom IO"
);
let changes = world.poll_custom_io_changes();
assert_eq!(changes.len(), 0);
}
#[test]
fn test_custom_io_callbacks_performance_minimal_overhead() {
let schematic = create_and_gate();
let input_a = BlockPos::new(0, 1, 0);
let input_b = BlockPos::new(0, 1, 2);
let output = BlockPos::new(4, 1, 1);
let options = SimulationOptions {
custom_io: vec![input_a, input_b, output],
optimize: false,
io_only: false,
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.check_custom_io_changes();
world.clear_custom_io_changes();
let start = std::time::Instant::now();
for _ in 0..1000 {
world.check_custom_io_changes();
}
let elapsed = start.elapsed();
eprintln!(
"[TEST] 1000 check_custom_io_changes() calls with 3 custom IO: {:?}",
elapsed
);
assert!(
elapsed.as_millis() < 50,
"Should have minimal overhead with custom IO"
);
}
#[test]
fn test_custom_io_callbacks_clear() {
let schematic = create_and_gate();
let input_a = BlockPos::new(0, 1, 0);
let options = SimulationOptions {
custom_io: vec![input_a],
optimize: false,
io_only: false,
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
world.check_custom_io_changes();
world.clear_custom_io_changes();
world.set_signal_strength(input_a, 15);
world.check_custom_io_changes();
world.set_signal_strength(input_a, 0);
world.check_custom_io_changes();
let peeked = world.peek_custom_io_changes();
assert_eq!(peeked.len(), 2);
world.clear_custom_io_changes();
let polled = world.poll_custom_io_changes();
assert_eq!(polled.len(), 0, "Queue should be empty after clear");
}
fn create_comparator_to_lamp_circuit() -> UniversalSchematic {
let mut schematic = UniversalSchematic::new("Comparator to Lamp".to_string());
for x in 0..5 {
schematic.set_block(x, 0, 0, &BlockState::new("minecraft:stone".to_string()));
}
schematic.set_block_str(
0,
1,
0,
"minecraft:comparator[facing=west,mode=compare,powered=false]",
);
for x in 1..4 {
schematic.set_block_str(
x,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=side,north=none,south=none]",
);
}
schematic.set_block_str(4, 1, 0, "minecraft:redstone_lamp[lit=false]");
schematic
}
#[test]
fn test_comparator_signal_injection() {
let schematic = create_comparator_to_lamp_circuit();
let comp_pos = BlockPos::new(0, 1, 0);
let options = SimulationOptions {
custom_io: vec![comp_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
let initial = world.get_signal_strength(comp_pos);
assert_eq!(initial, 0, "Comparator should start at 0");
world.set_signal_strength(comp_pos, 15);
world.tick(5);
world.flush();
let signal = world.get_signal_strength(comp_pos);
assert_eq!(
signal, 15,
"Comparator should have signal 15 after injection"
);
}
#[test]
fn test_comparator_direct_to_lamp() {
let mut schematic = UniversalSchematic::new("Comparator Direct Lamp".to_string());
schematic.set_block(0, 0, 0, &BlockState::new("minecraft:stone".to_string()));
schematic.set_block(1, 0, 0, &BlockState::new("minecraft:stone".to_string()));
schematic.set_block_str(
0,
1,
0,
"minecraft:comparator[facing=west,mode=compare,powered=false]",
);
schematic.set_block_str(1, 1, 0, "minecraft:redstone_lamp[lit=false]");
let comp_pos = BlockPos::new(0, 1, 0);
let lamp_pos = BlockPos::new(1, 1, 0);
let options = SimulationOptions {
custom_io: vec![comp_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
assert!(world.has_node(comp_pos), "Comparator should be in graph");
assert!(world.has_node(lamp_pos), "Lamp should be in graph");
assert!(!world.is_lit(lamp_pos), "Lamp should start off");
world.set_signal_strength(comp_pos, 15);
world.flush();
assert!(world.is_lit(lamp_pos), "Lamp should be lit after set+flush");
}
#[test]
fn test_comparator_injection_lights_lamp() {
let schematic = create_comparator_to_lamp_circuit();
let comp_pos = BlockPos::new(0, 1, 0);
let lamp_pos = BlockPos::new(4, 1, 0);
let options = SimulationOptions {
custom_io: vec![comp_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
assert!(world.has_node(comp_pos), "Comparator should be in graph");
assert!(world.has_node(lamp_pos), "Lamp should be in graph");
assert!(!world.is_lit(lamp_pos), "Lamp should start off");
world.set_signal_strength(comp_pos, 15);
let comp_signal = world.get_signal_strength(comp_pos);
assert_eq!(comp_signal, 15, "Comparator should be 15 after set");
world.tick(10);
world.flush();
assert!(
world.is_lit(lamp_pos),
"Lamp should be lit after injecting signal into comparator"
);
}
#[test]
fn test_comparator_signal_read_after_circuit() {
let mut schematic = UniversalSchematic::new("Barrel Comparator Read".to_string());
for x in 0..3 {
schematic.set_block(x, 0, 0, &BlockState::new("minecraft:stone".to_string()));
}
schematic
.set_block_from_string(0, 1, 0, "minecraft:barrel[facing=north]{signal=10}")
.expect("Failed to set barrel");
schematic.set_block_str(
1,
1,
0,
"minecraft:comparator[facing=west,mode=compare,powered=false]",
);
schematic.set_block_str(
2,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=side,north=none,south=none]",
);
let comp_pos = BlockPos::new(1, 1, 0);
let options = SimulationOptions {
custom_io: vec![comp_pos],
..Default::default()
};
let world = MchprsWorld::with_options(schematic, options).expect("World creation failed");
let signal = world.get_signal_strength(comp_pos);
assert_eq!(signal, 10, "Comparator should output signal 10 from barrel");
}
#[test]
fn test_comparator_analog_values() {
let schematic = create_comparator_to_lamp_circuit();
let comp_pos = BlockPos::new(0, 1, 0);
let options = SimulationOptions {
custom_io: vec![comp_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
for strength in [0, 1, 5, 7, 10, 14, 15] {
world.set_signal_strength(comp_pos, strength);
world.tick(2);
world.flush();
let read = world.get_signal_strength(comp_pos);
assert_eq!(
read, strength,
"Comparator should maintain signal strength {}",
strength
);
}
}
#[test]
fn test_two_comparators_in_out() {
let mut schematic = UniversalSchematic::new("IN/OUT Comparator Pair".to_string());
for x in 0..5 {
schematic.set_block(x, 0, 0, &BlockState::new("minecraft:stone".to_string()));
}
schematic.set_block_str(
0,
1,
0,
"minecraft:comparator[facing=west,mode=compare,powered=false]",
);
for x in 1..4 {
schematic.set_block_str(
x,
1,
0,
"minecraft:redstone_wire[power=0,east=side,west=side,north=none,south=none]",
);
}
schematic.set_block_str(
4,
1,
0,
"minecraft:comparator[facing=west,mode=compare,powered=false]",
);
let in_pos = BlockPos::new(0, 1, 0);
let out_pos = BlockPos::new(4, 1, 0);
let options = SimulationOptions {
custom_io: vec![in_pos, out_pos],
..Default::default()
};
let mut world =
MchprsWorld::with_options(schematic, options).expect("World creation failed");
assert_eq!(world.get_signal_strength(in_pos), 0);
assert_eq!(world.get_signal_strength(out_pos), 0);
world.set_signal_strength(in_pos, 15);
world.tick(10);
world.flush();
assert_eq!(world.get_signal_strength(in_pos), 15);
let out_signal = world.get_signal_strength(out_pos);
assert!(
out_signal > 0,
"OUT comparator should receive signal from IN comparator, got {}",
out_signal
);
}
#[test]
fn test_comparator_reading_barrel_signal_strengths() {
for signal in 0..=15 {
let mut schematic = UniversalSchematic::new(format!("Barrel Signal {}", signal));
for i in 0..4 {
schematic.set_block(
i,
0,
0,
&BlockState::new("minecraft:gray_concrete".to_string()),
);
}
schematic
.set_block_from_string(
0,
1,
0,
&format!("minecraft:barrel[facing=north]{{signal={}}}", signal),
)
.expect("Failed to set barrel");
let mut comparator = BlockState::new("minecraft:comparator".to_string());
comparator.set_property("facing", "west");
comparator.set_property("mode", "compare");
comparator.set_property("powered", "false");
schematic.set_block(1, 1, 0, &comparator);
let mut wire = BlockState::new("minecraft:redstone_wire".to_string());
wire.set_property("power", "0");
schematic.set_block(2, 1, 0, &wire);
schematic.set_block(3, 1, 0, &wire);
let world = MchprsWorld::new(schematic).expect("World creation failed");
let power_at_2 = world.get_redstone_power(BlockPos::new(2, 1, 0));
let power_at_3 = world.get_redstone_power(BlockPos::new(3, 1, 0));
assert_eq!(
power_at_2, signal,
"Wire at x=2 should have power {} for barrel signal {}",
signal, signal
);
let expected_power_3 = if signal > 0 { signal - 1 } else { 0 };
assert_eq!(
power_at_3, expected_power_3,
"Wire at x=3 should have power {} for barrel signal {}",
expected_power_3, signal
);
}
}
#[test]
fn test_comparator_reading_hopper_signal_strengths() {
use mchprs_world::World;
for signal in 0..=15 {
let mut schematic = UniversalSchematic::new(format!("Hopper Signal {}", signal));
schematic.set_block(
0,
0,
0,
&BlockState::new("minecraft:gray_concrete".to_string()),
);
schematic.set_block(
1,
0,
0,
&BlockState::new("minecraft:gray_concrete".to_string()),
);
schematic
.set_block_from_string(
0,
1,
0,
&format!("minecraft:hopper[facing=down]{{signal={}}}", signal),
)
.expect("Failed to set hopper");
let mut comparator = BlockState::new("minecraft:comparator".to_string());
comparator.set_property("facing", "west");
comparator.set_property("mode", "compare");
schematic.set_block(1, 1, 0, &comparator);
let mut wire = BlockState::new("minecraft:redstone_wire".to_string());
wire.set_property("power", "0");
schematic.set_block(2, 1, 0, &wire);
let world = MchprsWorld::new(schematic).expect("World creation failed");
let power_at_2 = world.get_redstone_power(BlockPos::new(2, 1, 0));
assert_eq!(
power_at_2, signal,
"Wire should have power {} for hopper signal {}",
signal, signal
);
}
}
}