Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions crates/athena-sim/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::state;
use crate::state_writes::write_cell;
use crate::step::{TICK_EVENT_LIMIT, World, dead, world_state};
use crate::unsupported;
use crate::{Change, PlayerWorld, Stop, Val};
use crate::{Change, Finite, PlayerWorld, Stop, Val};

/// Execute a modeled status/button/rule action against `world`; `None`
/// when `name` is not one of them.
Expand All @@ -27,7 +27,7 @@ pub(crate) fn action(
inst: &Instance<'_>,
rule_idx: usize,
subroutines: &BTreeMap<String, usize>,
now: f64,
now: Finite,
) -> Option<Result<(), Stop>> {
if !matches!(
name,
Expand Down Expand Up @@ -55,7 +55,7 @@ fn run(
inst: &Instance<'_>,
rule_idx: usize,
subroutines: &BTreeMap<String, usize>,
now: f64,
now: Finite,
) -> Result<(), Stop> {
// The caller's `Env` cannot cross this call — it shares a borrow of
// `world.players` — so the `Env` is built here. Each arm evaluates
Expand Down Expand Up @@ -94,13 +94,14 @@ fn run(
}
let (applied, died) = {
let target = world_state(world, &id)?;
let applied = amount * dealt * target.damage_received;
let applied =
Finite::produced(amount * dealt * target.damage_received, "action damage")?;
let before = pools(&target.world);
let health = target.world.health.current;
state::damage(&mut target.world, applied);
state::damage(&mut target.world, applied.get(), "action damage")?;
// The hit kills when the pool had health and now has
// none; the gate already ruled out a declared death.
let died = health > 0.0 && target.world.health.current == 0.0;
let died = health.get() > 0.0 && target.world.health.current.get() == 0.0;
let after = pools(&target.world);
for ((field, old), (_, new)) in before.into_iter().zip(after) {
if old != new {
Expand All @@ -114,7 +115,7 @@ fn run(
}
(applied, died)
};
if applied > 0.0 {
if applied.get() > 0.0 {
// The events a modeled damage hit causes, in the
// order they occur: the victim's event, the dealer's,
// then the death the hit caused. `Event Damage`,
Expand Down Expand Up @@ -158,9 +159,10 @@ fn run(
continue;
}
let target = world_state(world, &id)?;
let applied = amount * dealt * target.healing_received;
let applied =
Finite::produced(amount * dealt * target.healing_received, "action heal")?;
let old = target.world.health.current;
state::heal(&mut target.world, applied);
state::heal(&mut target.world, applied.get(), "action heal")?;
let new = target.world.health.current;
if new != old {
world.changes.borrow_mut().push(Change::World {
Expand All @@ -170,7 +172,7 @@ fn run(
new: Val::Number(new),
});
}
if applied > 0.0 {
if applied.get() > 0.0 {
let values = event_values(&healer, &id, "eventHealing", applied);
raise(world, "playerReceivedHealing", &id, values.clone())?;
if let Val::Player(dealer_id) = &healer {
Expand All @@ -188,7 +190,8 @@ fn run(
// scales it is what P7 is for.
let ids = player_ids(player, &env)?;
let slot = button_slot(button)?;
let seconds = number(&eval(cooldown, &env)?)?;
let seconds =
Finite::produced(number(&eval(cooldown, &env)?)?, "action setAbilityCooldown")?;
for id in ids {
let target = world_state(world, &id)?;
let old = target.world.cooldowns.get(slot).copied();
Expand Down Expand Up @@ -218,7 +221,14 @@ fn run(
return unsupported(format!("{other:?} where a player is needed"));
}
}
(ids, status, Some(now + number(&eval(duration, &env)?)?))
(
ids,
status,
Some(Finite::produced(
now.get() + number(&eval(duration, &env)?)?,
"action setStatusEffect",
)?),
)
}
("clearStatusEffect", [player, status]) => {
(player_ids(player, &env)?, status, None)
Expand Down Expand Up @@ -334,7 +344,7 @@ fn run(

/// The `(field, current)` pairs `Damage` reports for the pools that
/// exist, in the order the pools absorb damage.
fn pools(world: &PlayerWorld) -> [(&'static str, Option<f64>); 3] {
fn pools(world: &PlayerWorld) -> [(&'static str, Option<Finite>); 3] {
[
("shield", world.shield.as_ref().map(|pool| pool.current)),
("armor", world.armor.as_ref().map(|pool| pool.current)),
Expand Down Expand Up @@ -371,7 +381,7 @@ fn raise(
/// The event values a `Damage`/`Heal` raise binds: `Attacker` is the
/// action's dealer (`Null` for the environment), `Victim` the target,
/// and `key` — `eventDamage` or `eventHealing` — the applied amount.
fn event_values(cause: &Val, victim: &str, key: &str, amount: f64) -> BTreeMap<String, Val> {
fn event_values(cause: &Val, victim: &str, key: &str, amount: Finite) -> BTreeMap<String, Val> {
BTreeMap::from([
("attacker".to_string(), cause.clone()),
("victim".to_string(), Val::Player(victim.to_string())),
Expand Down
76 changes: 35 additions & 41 deletions crates/athena-sim/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::ambiguous;
use crate::eval::{Env, arguments, eval, number, values_equal};
use crate::scenario::{Phase, ServerLoad};
use crate::unsupported;
use crate::{Change, MissingInput, MissingInputKind, Stop, Val};
use crate::{Change, Finite, MissingInput, MissingInputKind, Stop, Val};

fn missing<T>(
input: MissingInputKind,
Expand Down Expand Up @@ -40,7 +40,7 @@ const EVENT_VALUES: &[&str] = &[
fn truthy(value: &Val) -> Result<bool, Stop> {
match value {
Val::Bool(b) => Ok(*b),
Val::Number(n) => Ok(*n != 0.0),
Val::Number(n) => Ok(n.get() != 0.0),
Val::Null => Ok(false),
other => unsupported(format!("condition value {other:?}")),
}
Expand All @@ -58,7 +58,7 @@ fn items(value: Val, construct: &str) -> Result<Vec<Val>, Stop> {

fn render(value: &Val) -> Result<String, Stop> {
match value {
Val::Number(n) => Ok(format!("{n:.2}")),
Val::Number(n) => Ok(format!("{:.2}", n.get())),
Val::Null => Ok("0".into()),
Val::Bool(b) => Ok(b.to_string()),
Val::Text(text) => Ok(text.clone()),
Expand Down Expand Up @@ -165,7 +165,7 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
let args: &[Value] = &resolved;
let binary = |op: fn(f64, f64) -> f64| -> Result<Val, Stop> {
let [a, b] = arguments::<2>(name, args)?;
crate::vector::produced_number(op(number(&eval(a, env)?)?, number(&eval(b, env)?)?), name)
crate::produced_number(op(number(&eval(a, env)?)?, number(&eval(b, env)?)?), name)
};
match name {
// Evaluation reached a sentinel no operand position resolved —
Expand Down Expand Up @@ -194,11 +194,14 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
current,
average,
peak,
}) => Ok(Val::Number(match name {
"getServerLoad" => current,
"getAverageServerLoad" => average,
_ => peak,
})),
}) => crate::produced_number(
match name {
"getServerLoad" => current,
"getAverageServerLoad" => average,
_ => peak,
},
name,
),
None => missing(
MissingInputKind::ServerLoad,
None,
Expand Down Expand Up @@ -286,7 +289,7 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
.hud
.borrow()
.last_text_id
.map_or(Val::Null, |id| Val::Number(id as f64)))
.map_or(Val::Null, |id| Val::Number(Finite::from(id))))
}
// `Last Created Entity` is the id of the newest durable entity;
// a `playEffect` has no entity id to hand out.
Expand All @@ -296,7 +299,7 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
.effects
.borrow()
.last_created
.map_or(Val::Null, |id| Val::Number(id as f64)))
.map_or(Val::Null, |id| Val::Number(Finite::from(id))))
}
// The catalog's `Player|EntityId` names "the specified player,
// icon entity, or effect entity". A scenario player exists for
Expand All @@ -306,7 +309,7 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
let [entity] = arguments::<1>(name, args)?;
match eval(entity, env)? {
Val::Player(_) => Ok(Val::Bool(true)),
Val::Number(id) => Ok(Val::Bool(env.effects.borrow().entity_exists(id))),
Val::Number(id) => Ok(Val::Bool(env.effects.borrow().entity_exists(id.get()))),
Val::Null => Ok(Val::Bool(false)),
other => unsupported(format!("{name} on {other:?}")),
}
Expand All @@ -315,11 +318,13 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
// one is no longer an instance.
"getNumberOfEntityIds" => {
let [] = arguments::<0>(name, args)?;
Ok(Val::Number(env.effects.borrow().entity_count() as f64))
Ok(Val::Number(Finite::from(
env.effects.borrow().entity_count(),
)))
}
"getNumberOfTextIds" => {
let [] = arguments::<0>(name, args)?;
Ok(Val::Number(env.hud.borrow().text_count() as f64))
Ok(Val::Number(Finite::from(env.hud.borrow().text_count())))
}
"and" | "or" => {
let [a, b] = arguments::<2>(name, args)?;
Expand Down Expand Up @@ -379,19 +384,15 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
// non-finite argument has no sine to report.
"sin" => {
let [value] = arguments::<1>(name, args)?;
let sine = number(&eval(value, env)?)?.sin();
if !sine.is_finite() {
return unsupported(format!("{name} produced a non-finite number"));
}
Ok(Val::Number(sine))
crate::produced_number(number(&eval(value, env)?)?.sin(), name)
}
"roundToInteger" => {
let [value, rounding] = arguments::<2>(name, args)?;
let n = number(&eval(value, env)?)?;
let Value::Enum { value: mode, .. } = rounding else {
return unsupported(format!("{name} rounding {rounding:?}"));
};
crate::vector::produced_number(
crate::produced_number(
match mode.as_str() {
"UP" => n.ceil(),
"DOWN" => n.floor(),
Expand Down Expand Up @@ -441,7 +442,9 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
}
"countOf" => {
let [array] = arguments::<1>(name, args)?;
Ok(Val::Number(items(eval(array, env)?, name)?.len() as f64))
Ok(Val::Number(Finite::from(
items(eval(array, env)?, name)?.len(),
)))
}
"appendToArray" => {
let [array, value] = arguments::<2>(name, args)?;
Expand Down Expand Up @@ -490,7 +493,7 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
}
Ok(match name {
"arrayContains" => Val::Bool(found.is_some()),
_ => Val::Number(found.map_or(-1.0, |index| index as f64)),
_ => Val::Number(found.map_or(Finite::from(-1), Finite::from)),
})
}
"slice" => {
Expand Down Expand Up @@ -525,24 +528,20 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
"randomInteger" | "randomReal" => {
let [min, max] = arguments::<2>(name, args)?;
let (min, max) = (number(&eval(min, env)?)?, number(&eval(max, env)?)?);
if !min.is_finite() || !max.is_finite() {
return unsupported(format!("{name} on a non-finite bound"));
}
// A `Min` past `Max` swaps the bounds; unmeasured.
let (min, max) = if min > max { (max, min) } else { (min, max) };
// Finite bounds still overflow the range: `Max - Min` is
// `inf` from `-1e308` to `1e308`, and a non-finite value
// serializes as `null` in the trace.
// `inf` from `-1e308` to `1e308`.
if !(max - min).is_finite() {
return unsupported(format!("{name} on a non-finite range"));
}
let u = draw(env, name)?;
let value = if name == "randomInteger" {
// The bounds floor to integers; the range is inclusive.
let (lo, hi) = (min.floor(), max.floor());
Val::Number(lo + (u * (hi - lo + 1.0)).floor())
crate::produced_number(lo + (u * (hi - lo + 1.0)).floor(), name)?
} else {
Val::Number(min + (max - min) * u)
crate::produced_number(min + (max - min) * u, name)?
};
Ok(drawn(env, name, value))
}
Expand Down Expand Up @@ -577,7 +576,7 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
Some((element, index)) => Ok(if name == "currentArrayElement" {
element.clone()
} else {
Val::Number(index as f64)
Val::Number(Finite::from(index))
}),
None => unsupported(format!("{name} outside an element context")),
}
Expand Down Expand Up @@ -611,16 +610,11 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
keyed.into_iter().map(|(_, item)| item.clone()).collect(),
))
}
// `Mapped Array` collects the operand's per-element
// result; a produced number stays a value only while
// finite, as everywhere one is produced.
// `Mapped Array` collects the operand's per-element result.
"mappedArray" => {
let mut mapped = Vec::with_capacity(items.len());
for (index, item) in items.iter().enumerate() {
match eval(operand, &element_env(env, item, index))? {
Val::Number(n) => mapped.push(crate::vector::produced_number(n, name)?),
element => mapped.push(element),
}
mapped.push(eval(operand, &element_env(env, item, index))?);
}
Ok(Val::Array(mapped))
}
Expand Down Expand Up @@ -765,11 +759,11 @@ pub(crate) fn call(name: &str, args: &[Value], env: &Env) -> Result<Val, Stop> {
string(&eval(character, env)?)?,
);
Ok(Val::Number(if character.is_empty() {
-1.0
Finite::from(-1)
} else {
source
.find(&character)
.map_or(-1.0, |byte| source[..byte].chars().count() as f64)
source.find(&character).map_or(Finite::from(-1), |byte| {
Finite::from(source[..byte].chars().count())
})
}))
}
// `String Replace` replaces every occurrence of the pattern. An
Expand Down
Loading
Loading