wolloseum/example-wasm-agent/src/lib.rs

66 lines
1.9 KiB
Rust

use extism_pdk::*;
use serde::{Deserialize, Serialize};
const ABI_VERSION: u16 = 1;
const BOUNDARY_TERRAIN: i32 = 3;
#[derive(Deserialize)]
struct DecisionInput {
#[allow(dead_code)]
abi_version: u16,
tick: u64,
x: u16,
y: u16,
adjacent_terrain: [i32; 4],
}
#[derive(Serialize)]
struct DecisionOutput {
abi_version: u16,
action: i32,
}
/// Deterministic decision function called once per simulation tick.
///
/// The host provides the full observation as JSON input and expects a JSON
/// decision back; no filesystem, network, clock, or RNG access is available.
/// Movement here is a reproducible function of (tick, x, y) alone, so replay
/// with the same seed and inputs always yields the same actions.
#[plugin_fn]
pub fn decide(input: Json<DecisionInput>) -> FnResult<Json<DecisionOutput>> {
let input = input.0;
// Action codes: 1=North, 2=East, 3=South, 4=West, matching adjacent_terrain order.
let open_directions: Vec<i32> = input
.adjacent_terrain
.iter()
.enumerate()
.filter(|(_, terrain)| **terrain != BOUNDARY_TERRAIN)
.map(|(index, _)| (index + 1) as i32)
.collect();
let action = if open_directions.is_empty() {
0 // Wait
} else {
let seed = deterministic_seed(input.tick, input.x, input.y);
open_directions[(seed as usize) % open_directions.len()]
};
Ok(Json(DecisionOutput {
abi_version: ABI_VERSION,
action,
}))
}
/// SplitMix64-style mix so the same (tick, x, y) always yields the same choice.
fn deterministic_seed(tick: u64, x: u16, y: u16) -> u64 {
let mut state = tick
.wrapping_mul(0x9E37_79B9_7F4A_7C15)
.wrapping_add(u64::from(x).wrapping_mul(0xBF58_476D_1CE4_E5B9))
.wrapping_add(u64::from(y).wrapping_mul(0x94D0_49BB_1331_11EB));
state ^= state >> 33;
state = state.wrapping_mul(0xFF51_AFD7_ED55_8CCD);
state ^= state >> 33;
state
}