build(combat): numbers foundation — formulas→Lua hot-reload + CombatSim + a CI band #216

Closed
opened 2026-07-26 01:19:24 +02:00 by marco · 0 comments
Owner

Build phase 1 of the combat rework (design: #214 · docs/game-design/systems/combat.md): the demonstrable-numbers foundation. Server-only, no combat-behaviour change — proves the "edit a Lua formula → hot-reload → CombatSim → CI band" loop on the real code, on the existing HitChance first. The combat-model rework (spacing, stats, counter-web) builds on this in later phases.

Reviewed via critical-design-review (Invariants Check below; no ).

What to build (in order)

  1. content/scripts/combat/formulas.lua — a pure-function module, starting with hitChance(atkSkillTenths, defSkillTenths) identical to today (constants inline in the Lua). No behaviour change — only where the number comes from.
  2. Host: a "function-module" load path. Extend ScriptHost to compile a module and hand its Closures to C# (compile-once cache + hot-reload swap on the sim thread — reuse the existing Compile/reload). This is the one genuinely new capability (today the host only does content registration + handlers).
  3. LuaCombatFormulas (GameServer) behind the current CombatFormulas surface: calls the closure and clamps/validates the result (a NaN/out-of-range return must never reach the tick — fatal pre-online, isolated online, per the scripting validation-symmetry). CombatSystem uses it; the static CombatFormulas stays as a fallback/test-double.
  4. IsoMmo.CombatSim.Core (lib) — the seeded Tier-1 duel harness (calls the Lua formulas). tools/IsoMmo.CombatSim (console + HTML report) and the CI tests both reuse it.
  5. One CI regression band to start: a seeded test running the harness on the real Lua formulas asserting one derived target (e.g. even-fight winrate ∈ [45,55]%), reproducible. More bands land as the model does.

Accepted decisions

  • Generate a .d.lua for the formula-module signatures (editor types when tuning formulas.lua).
  • Harness lives in a dedicated IsoMmo.CombatSim.Core lib (tool + tests reuse it).
  • Tool output = an HTML report (reuse the prototype's look).
  • Non-formula constants (weapon range, tick, drop cadence) stay typed options; only the formula curves/constants move to Lua.

Invariants Check (delta beyond the base DoD)

Server-authoritative ✓ (formulas are pure math called server-side; the sim is dev-only). Single-threaded sim ✓ (closure called in-thread; hot-reload swap on the sim thread, no lock). World.cs / Screen HARD GATE ✓ / N/A. Typed options ✓ (deliberate divergence: formula curves become Lua content; non-formula options stay typed — justified by the owner's hard "demonstrable + fast-iterate" requirement). ModernUO reference ✓ (divergence: MUO keeps formulas in compiled C#; we hot-reload them in Lua, pipeline stays C#). Content-in-Lua gate ✓ (refines the gate: combat pipeline stays C#, tunable formulas are Lua content — update Scripting/CLAUDE.md wording in the same change). Layering ✓ (formulas.lua loaded by the host; CombatSim refs IsoMmo.Scripting, not GameServer). Protocol/AoI/String-catalog/Persistence/GM/Identity — N/A (no wire, no user text, no persisted state, no admin). API self-describing ✓ (.d.lua generated for the module). Multi-platform ✓ (MoonSharp pure-managed; .NET tool).

Server-side validation (trust boundary)

The CombatSim is dev-only — no player surface, no intents, no authoritative state. The only untrusted input is the value returned by a Lua formula: the C# call-site validates/clamps it (a NaN/negative never reaches the tick). Pre-online a broken formula is fatal (like any script), online it is isolated (the tick survives).

Verification plan

  • Unit: LuaCombatFormulas.hitChance == the static formula across an input grid (migration changes no numbers); the guard clamps a NaN/out-of-range return.
  • Hot-reload: load formulas.lua, call it, rewrite the file, reload, see the number change (proves the tuning loop).
  • CI band: the Tier-1 harness on the real Lua formulas, seeded, asserts the even-fight band; re-running reproduces the number.
  • Tool: just combatsim runs and produces the HTML report (win-rate / TTK).
  • Screenshots: N/A — dev tool, output is a report/console, not a game surface.

Definition of Done

  • The live server reads hitChance from content/scripts/combat/formulas.lua (compiled/cached), with identical numbers to before — proven by a unit test comparing to the old static formula.
  • Editing formulas.lua and reloading changes the number at runtime with no recompile (a hot-reload test proves it).
  • A NaN / out-of-range formula return is clamped/rejected server-side and never reaches the tick.
  • just combatsim runs the seeded Tier-1 harness on the shared Lua formulas and emits a visible HTML report (win-rate + fight-duration).
  • At least one derived-target CI regression band asserts on the harness output and is reproducible (same seed → same number); a formula change that breaks it fails CI.
  • Scripting/CLAUDE.md gate updated (pipeline C# / formulas Lua); docs/scripting.md documents the function-module capability; a .d.lua for the module is generated.
  • Base DoD: dotnet test green, whole-solution build (client + tools), zero warnings; multi-platform preserved.

Refs #214.

Build phase 1 of the combat rework (design: #214 · `docs/game-design/systems/combat.md`): the **demonstrable-numbers foundation**. Server-only, no combat-behaviour change — proves the "edit a Lua formula → hot-reload → CombatSim → CI band" loop on the *real* code, on the existing `HitChance` first. The combat-model rework (spacing, stats, counter-web) builds on this in later phases. Reviewed via `critical-design-review` (Invariants Check below; no `✗`). ## What to build (in order) 1. `content/scripts/combat/formulas.lua` — a **pure-function module**, starting with `hitChance(atkSkillTenths, defSkillTenths)` identical to today (constants inline in the Lua). No behaviour change — only *where the number comes from*. 2. **Host: a "function-module" load path.** Extend `ScriptHost` to compile a module and hand its `Closure`s to C# (compile-once cache + hot-reload swap on the sim thread — reuse the existing `Compile`/reload). This is the one genuinely new capability (today the host only does content registration + handlers). 3. **`LuaCombatFormulas` (GameServer)** behind the current `CombatFormulas` surface: calls the closure and **clamps/validates the result** (a NaN/out-of-range return must never reach the tick — fatal pre-online, isolated online, per the scripting validation-symmetry). `CombatSystem` uses it; the static `CombatFormulas` stays as a fallback/test-double. 4. **`IsoMmo.CombatSim.Core`** (lib) — the seeded Tier-1 duel harness (calls the Lua formulas). **`tools/IsoMmo.CombatSim`** (console + **HTML report**) and the CI tests both reuse it. 5. **One CI regression band** to start: a seeded test running the harness on the real Lua formulas asserting one derived target (e.g. `even-fight winrate ∈ [45,55]%`), reproducible. More bands land as the model does. ## Accepted decisions - Generate a `.d.lua` for the formula-module signatures (editor types when tuning `formulas.lua`). - Harness lives in a dedicated `IsoMmo.CombatSim.Core` lib (tool + tests reuse it). - Tool output = an HTML report (reuse the prototype's look). - Non-formula constants (weapon range, tick, drop cadence) stay typed options; only the *formula curves/constants* move to Lua. ## Invariants Check (delta beyond the base DoD) Server-authoritative ✓ (formulas are pure math called server-side; the sim is dev-only). Single-threaded sim ✓ (closure called in-thread; hot-reload swap on the sim thread, no lock). World.cs / Screen HARD GATE ✓ / N/A. Typed options ✓ *(deliberate divergence: formula curves become Lua content; non-formula options stay typed — justified by the owner's hard "demonstrable + fast-iterate" requirement)*. ModernUO reference ✓ *(divergence: MUO keeps formulas in compiled C#; we hot-reload them in Lua, pipeline stays C#)*. Content-in-Lua gate ✓ *(refines the gate: combat **pipeline** stays C#, tunable **formulas** are Lua content — update `Scripting/CLAUDE.md` wording in the same change)*. Layering ✓ (`formulas.lua` loaded by the host; `CombatSim` refs `IsoMmo.Scripting`, not GameServer). Protocol/AoI/String-catalog/Persistence/GM/Identity — N/A (no wire, no user text, no persisted state, no admin). API self-describing ✓ (`.d.lua` generated for the module). Multi-platform ✓ (MoonSharp pure-managed; .NET tool). ## Server-side validation (trust boundary) The `CombatSim` is dev-only — no player surface, no intents, no authoritative state. The only untrusted input is the **value returned by a Lua formula**: the C# call-site **validates/clamps** it (a NaN/negative never reaches the tick). Pre-online a broken formula is fatal (like any script), online it is isolated (the tick survives). ## Verification plan - Unit: `LuaCombatFormulas.hitChance` == the static formula across an input grid (migration changes no numbers); the guard clamps a NaN/out-of-range return. - Hot-reload: load `formulas.lua`, call it, rewrite the file, reload, see the number change (proves the tuning loop). - CI band: the Tier-1 harness on the real Lua formulas, seeded, asserts the even-fight band; re-running reproduces the number. - Tool: `just combatsim` runs and produces the HTML report (win-rate / TTK). - Screenshots: **N/A** — dev tool, output is a report/console, not a game surface. ## Definition of Done - [ ] The live server reads `hitChance` from `content/scripts/combat/formulas.lua` (compiled/cached), with identical numbers to before — proven by a unit test comparing to the old static formula. - [ ] Editing `formulas.lua` and reloading changes the number at runtime with no recompile (a hot-reload test proves it). - [ ] A NaN / out-of-range formula return is clamped/rejected server-side and never reaches the tick. - [ ] `just combatsim` runs the seeded Tier-1 harness on the **shared** Lua formulas and emits a visible HTML report (win-rate + fight-duration). - [ ] At least one derived-target **CI regression band** asserts on the harness output and is reproducible (same seed → same number); a formula change that breaks it fails CI. - [ ] `Scripting/CLAUDE.md` gate updated (pipeline C# / formulas Lua); `docs/scripting.md` documents the function-module capability; a `.d.lua` for the module is generated. - [ ] Base DoD: `dotnet test` green, whole-solution build (client + tools), zero warnings; multi-platform preserved. Refs #214.
panda closed this issue 2026-08-02 00:41:34 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo#216
No description provided.