diff --git a/crates/athena-sim/src/actions.rs b/crates/athena-sim/src/actions.rs index 1da6bdd..997a3a7 100644 --- a/crates/athena-sim/src/actions.rs +++ b/crates/athena-sim/src/actions.rs @@ -186,8 +186,8 @@ fn run( return unsupported("setAbilityCooldown arguments"); }; // `Set Ability Cooldown` writes the reported cooldown for - // the slot literally; whether `abilityCooldown%` also - // scales it is what P7 is for. + // the slot literally; P7 measured that `abilityCooldown%` + // does not scale the write. let ids = player_ids(player, &env)?; let slot = button_slot(button)?; let seconds = diff --git a/crates/athena-sim/tests/fixtures.rs b/crates/athena-sim/tests/fixtures.rs index cafd0d7..2f25340 100644 --- a/crates/athena-sim/tests/fixtures.rs +++ b/crates/athena-sim/tests/fixtures.rs @@ -244,14 +244,13 @@ fn p6_wait_behavior() { } #[test] -#[ignore = "no captures yet (`runs` is 0)"] fn p7_hero_settings() { check_probe("p7-hero-settings"); } -/// P7 is `inconclusive`: it has no captures. This test does not assert -/// client behavior; it checks the simulator's modeled player state produces -/// the hypothesis lines in `expect.json`, and that a bare `run_text` — no +/// `p7_hero_settings` asserts the P7 client capture. This test does not +/// assert client behavior; it checks the simulator's modeled player state +/// produces the lines in `expect.json`, and that a bare `run_text` — no /// scenario, so no player — reports the probe `not_modeled`. The values are /// test-loaded so the check does not wait on a workshop-rs release. #[test] diff --git a/docs/architecture.md b/docs/architecture.md index ea3548c..0244c2d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -64,9 +64,9 @@ The simulator makes these choices only to stay deterministic; each needs a probe - Comparing values of different types with `==` gives false; `Null` equals `0`. Two mocked players compare by scenario `id`. - A mocked player is a scenario `id`; `Event Player` yields `{"type":"player"}`. `Hero Of` yields the catalog member id the declared `hero` resolves to; `Team Of` likewise yields the `Team` member id of the declared `team`, so it compares equal to a `Team` literal. - An event rule fires when the triggering event's `kind` — injected or raised — matches the rule event's catalog id and the event player satisfies the rule's `team` and player (`target`) filters. -- **Modeled, not measured** — no client capture backs the player state table or the `Damage`/`Heal` rules: +- **Modeled, not measured** — probe P7 measured `health%`, `damageReceived%` and `healingReceived%` on scripted `Damage`/`Heal`, and a literal `Set Ability Cooldown` under `abilityCooldown%` 50 (Ana, client 2.25.0.1); nothing else backs the player state table or the `Damage`/`Heal` rules: - Each scenario player has a state-table row built at run start from the `workshop-rs` hero-values file pinned at `CURRENT_VERSION`: a `health` pool, `shield` and `armor` pools where the catalog records them (a hero without the pool has no entry, never a zero), the cooldown seconds of the ability slots the catalog records (`slot` or `slot/variant` keys), and an ultimate charge of `0` toward the catalog's `ultimateCost`. Pools start at their maximum. The program's hero settings then apply: `health%` scales every pool, the `abilityCooldown%` and per-slot `*Cooldown%` scalars scale the reported cooldowns, and the dealt/received scalars feed `Damage`/`Heal` below. Percent scalars authored at several scopes multiply together. A hero the file does not supply, a player with no `hero`, or a hero setting authored for the player that the simulator does not apply (one that moves a reported value — ultimate generation, spawning with ultimate, passive health regeneration or the role passive, headshots-only, spawning without mech, or a cooldown scalar with no reported slot) leaves the row's `world` null; an action that touches that world state (`Damage`, `Heal`, dealt or received) stops the run as `not_modeled`. The exception is a player whose version-2 `state` map declares every `state` key the setting can move — `ultCharge` for the ultimate-charge settings (`ultGen%`, `passiveUltGen%`, `combatUltGen%`, `enableSpawningWithUlt`), `health`, `maxHealth` and `normalizedHealth` for the health ones (`passiveHealthRegen`, `enableRolePassive`): the scenario already supplies the value the setting could move, so the setting is skipped — `state.` stands, the setting itself is not simulated (athena#91). The other unapplied settings have no `state` coverage and keep failing, and an opaque `Raw*` member fails the same way. - - `Health`/`Max Health` read the pools' summed `current`/`max` only while the player declares no version-2 `state` map — a declared `state.health`/`state.maxHealth` answers instead (athena#89); `Ability Cooldown` reads the slot's reported seconds (`0` where the player has no such slot); `Set Ability Cooldown` writes the slot's reported seconds literally — whether `abilityCooldown%` also scales a scripted set is what probe P7 is for. Reading a player's world state is a touch like `Damage`/`Heal`. + - `Health`/`Max Health` read the pools' summed `current`/`max` only while the player declares no version-2 `state` map — a declared `state.health`/`state.maxHealth` answers instead (athena#89); `Ability Cooldown` reads the slot's reported seconds (`0` where the player has no such slot); `Set Ability Cooldown` writes the slot's reported seconds literally; P7 measured that `abilityCooldown%` 50 does not scale a scripted set (20 stays 20). Natural ability cooldowns are not measured. Reading a player's world state is a touch like `Damage`/`Heal`. - `Damage(player, damager, amount)` takes `amount` off the pools in shield, armor, health order: each pool absorbs up to its `current` and the rest spills into the next; health floors at zero and a negative amount does nothing. `Heal(player, healer, amount)` raises the `health` pool by `amount`, capped at `max` — it never exceeds the maximum, never refills shield or armor, and a negative amount does nothing. Both accept a `Player` or an array of players. The amount lands scaled: by the dealer's `damageDealt%` and the target's `damageReceived%` for `Damage`, and by the healer's `healingDealt%` and the target's `healingReceived%` for `Heal`; a `Null` dealer or healer carries no scale. A `damager`/`healer` value that is not a player or `Null` is not modeled. - `Damage`/`Heal` act only on living players (athena#47): the one liveness read — a state cell or the declared `state.isDead`, the same read `Is Alive`/`Is Dead`/`Set Player Health` make, with the health pool deciding where `isDead` cannot answer — gates both, so a dead target's pools do not move and no events raise. A hit that lands raises `Player Took Damage` on the target and `Player Dealt Damage` on a player dealer; a hit that empties `health` writes the `isDead` cell and adds `Player Died` then `Player Dealt Final Blow`; a heal raises `Player Received Healing`/`Player Dealt Healing`. The events bind `Event Player` to the player they happened to and `Attacker`, `Victim`, `Event Damage`/`Event Healing` to the cause; an event value the cause does not bind is `not_modeled`. `Respawn` refills the health pool to `max` alongside its `isDead`/`health` writes — "with full health, even if they were already alive" — and `Resurrect` refills only when it revives a dead target, a living one keeping its health; either revive leaves a player that can die again. - **Modeled, not measured** — spawn state and the host player. No capture covers them: diff --git a/probes/p7-hero-settings/capture-2.txt b/probes/p7-hero-settings/capture-2.txt new file mode 100644 index 0000000..391c0c3 --- /dev/null +++ b/probes/p7-hero-settings/capture-2.txt @@ -0,0 +1,5 @@ +[00:00:18] P7 max_health=375 +[00:00:18] P7 health_after_damage=275 +[00:00:18] P7 health_after_heal=355 +[00:00:18] P7 ability1_cooldown=20 +[00:00:18] P7 done diff --git a/probes/p7-hero-settings/capture-3.txt b/probes/p7-hero-settings/capture-3.txt new file mode 100644 index 0000000..8c8254a --- /dev/null +++ b/probes/p7-hero-settings/capture-3.txt @@ -0,0 +1,5 @@ +[00:00:19] P7 max_health=375 +[00:00:19] P7 health_after_damage=275 +[00:00:19] P7 health_after_heal=355 +[00:00:19] P7 ability1_cooldown=20 +[00:00:19] P7 done diff --git a/probes/p7-hero-settings/capture.json b/probes/p7-hero-settings/capture.json index 301d966..fa61e55 100644 --- a/probes/p7-hero-settings/capture.json +++ b/probes/p7-hero-settings/capture.json @@ -1,15 +1,31 @@ { "probe": "p7-hero-settings", "client": { - "version": "", - "date": "", + "version": "2.25.0.1 - 154504.1", + "date": "2026-11-03", "locale": "zh-CN" }, - "runs": 0, + "runs": 3, "probe_sha256": "5aa48b5a6807880ed6531c58486c22d8ca44e56803965db8482cc386c138e504", "hypothesis": "hero settings move the reported player state: health% 150 on Ana (health pool 250) reports Max Health 375; damageReceived% 50 lands a scripted Damage of 200 as 100; healingReceived% 200 lands a scripted Heal of 40 as 80; abilityCooldown% 50 applies to ability cooldowns, while Set Ability Cooldown(player, Ability 1, 20) is expected to write the timer literally and report 20", - "observed": [], - "result": "inconclusive", - "notes": "No captures yet; running the probe in the client is an owner step. The expected lines are hypotheses in expect.json. A cooldown reading of 10 instead of 20 would mean abilityCooldown% scales Set Ability Cooldown as well; either observation is a real result. The settings restrict the lobby to Ana (enabledHeroes), so any player produces the same numbers.", - "sources": [] + "observed": [ + { + "runs": [ + 1, + 2, + 3 + ], + "max_health": 375, + "health_after_damage": 275, + "health_after_heal": 355, + "ability1_cooldown_s": 20 + } + ], + "result": "match", + "notes": "All three runs gave the same four readings. Set Ability Cooldown wrote 20 literally, so abilityCooldown% 50 did not scale the scripted write. The probe measures script-applied damage, healing and cooldown writes, not natural ability cooldowns. The client version was read on 2026-11-03; the versions of runs 1 and 2 were not recorded. Balance version: workshop-rs names the client version of its hero-values file as `balanceVersion` (CURRENT_VERSION `2.25.0.0 - 154461.1`), not a named patch. This capture's client is 2.25.0.1 and workshop-rs has no file for it, so the balance version of the Ana 250 health reading is not determinable; the 375 reading only matches a 250 base. Raw logs were received through UU Remote file transfer.", + "sources": [ + "Log-2026-10-27-18-29-27.txt", + "Log-2026-11-02-16-11-32.txt", + "Log-2026-11-03-01-53-07.txt" + ] } diff --git a/probes/p7-hero-settings/capture.txt b/probes/p7-hero-settings/capture.txt new file mode 100644 index 0000000..8c8254a --- /dev/null +++ b/probes/p7-hero-settings/capture.txt @@ -0,0 +1,5 @@ +[00:00:19] P7 max_health=375 +[00:00:19] P7 health_after_damage=275 +[00:00:19] P7 health_after_heal=355 +[00:00:19] P7 ability1_cooldown=20 +[00:00:19] P7 done