use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct SpawnUnit {
unit_id: u64,
location_id: u64,
}
#[allow(dead_code)]
#[derive(Debug, Serialize, Deserialize)]
struct GetWorldSnapshot;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct UnitDestroyed {
unit_id: u64,
destination: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct WorldSnapshot {
units_active: u32,
units_idle: u32,
}
struct GameState {
units_active: u32,
units_idle: u32,
events: Vec<UnitDestroyed>,
}
impl GameState {
fn new() -> Self {
Self {
units_active: 0,
units_idle: 3,
events: Vec::new(),
}
}
}
type CmdResult = Result<(), String>;
type QueryResult<T> = Result<T, String>;
trait GameHandlers {
fn handle_spawn_unit(&mut self, cmd: SpawnUnit) -> CmdResult;
fn handle_get_world_snapshot(&self) -> QueryResult<WorldSnapshot>;
fn drain_events(&mut self) -> Vec<UnitDestroyed>;
}
impl GameHandlers for GameState {
fn handle_spawn_unit(&mut self, cmd: SpawnUnit) -> CmdResult {
if self.units_idle == 0 {
return Err("no units available".into());
}
self.units_idle -= 1;
self.units_active += 1;
self.events.push(UnitDestroyed {
unit_id: cmd.unit_id,
destination: format!("contract-{}", cmd.location_id),
});
Ok(())
}
fn handle_get_world_snapshot(&self) -> QueryResult<WorldSnapshot> {
Ok(WorldSnapshot {
units_active: self.units_active,
units_idle: self.units_idle,
})
}
fn drain_events(&mut self) -> Vec<UnitDestroyed> {
std::mem::take(&mut self.events)
}
}
struct LocalAdapter<'a> {
state: &'a mut GameState,
}
impl<'a> LocalAdapter<'a> {
fn spawn_unit(&mut self, cmd: SpawnUnit) -> CmdResult {
self.state.handle_spawn_unit(cmd)
}
fn get_world_snapshot(&self) -> QueryResult<WorldSnapshot> {
self.state.handle_get_world_snapshot()
}
fn drain_events(&mut self) -> Vec<UnitDestroyed> {
self.state.drain_events()
}
}
struct RemoteAdapter<'a> {
state: &'a mut GameState,
}
impl<'a> RemoteAdapter<'a> {
fn spawn_unit_json(&mut self, request_json: &str) -> String {
let cmd: SpawnUnit = serde_json::from_str(request_json).unwrap();
let result = self.state.handle_spawn_unit(cmd);
match result {
Ok(()) => r#"{"ok":true}"#.to_string(),
Err(e) => format!(r#"{{"ok":false,"error":"{}"}}"#, e),
}
}
fn get_world_snapshot_json(&self) -> String {
let result = self.state.handle_get_world_snapshot().unwrap();
serde_json::to_string(&result).unwrap()
}
fn drain_events_json(&mut self) -> Vec<String> {
self.state
.drain_events()
.into_iter()
.map(|e| serde_json::to_string(&e).unwrap())
.collect()
}
}
fn main() {
println!("=== Protocol Proof of Concept ===\n");
println!("--- Local Adapter (in-process) ---");
let mut state = GameState::new();
{
let mut local = LocalAdapter { state: &mut state };
let snapshot = local.get_world_snapshot().unwrap();
println!("Before: {:?}", snapshot);
assert_eq!(snapshot.units_idle, 3);
assert_eq!(snapshot.units_active, 0);
local
.spawn_unit(SpawnUnit {
unit_id: 1,
location_id: 42,
})
.unwrap();
let snapshot = local.get_world_snapshot().unwrap();
println!("After dispatch: {:?}", snapshot);
assert_eq!(snapshot.units_idle, 2);
assert_eq!(snapshot.units_active, 1);
let events = local.drain_events();
println!("Events: {:?}", events);
assert_eq!(events.len(), 1);
assert_eq!(
events[0],
UnitDestroyed {
unit_id: 1,
destination: "contract-42".into(),
}
);
}
println!("Local adapter: PASS\n");
println!("--- Remote Adapter (simulated HTTP) ---");
let mut state = GameState::new();
{
let mut remote = RemoteAdapter { state: &mut state };
let snapshot_json = remote.get_world_snapshot_json();
println!("GET /queries/world-snapshot -> {}", snapshot_json);
let snapshot: WorldSnapshot = serde_json::from_str(&snapshot_json).unwrap();
assert_eq!(snapshot.units_idle, 3);
let request = r#"{"unit_id":2,"location_id":99}"#;
let response = remote.spawn_unit_json(request);
println!("POST /commands/dispatch-unit {} -> {}", request, response);
assert!(response.contains("true"));
let snapshot_json = remote.get_world_snapshot_json();
let snapshot: WorldSnapshot = serde_json::from_str(&snapshot_json).unwrap();
println!("GET /queries/world-snapshot -> {:?}", snapshot);
assert_eq!(snapshot.units_idle, 2);
assert_eq!(snapshot.units_active, 1);
let event_jsons = remote.drain_events_json();
println!("WS events: {:?}", event_jsons);
assert_eq!(event_jsons.len(), 1);
let event: UnitDestroyed = serde_json::from_str(&event_jsons[0]).unwrap();
assert_eq!(event.unit_id, 2);
assert_eq!(event.destination, "contract-99");
}
println!("Remote adapter: PASS\n");
println!("=== Proof Complete ===");
println!("Same GameHandlers trait, same protocol types,");
println!("two execution modes (local + remote) — architecture validated.");
}