Tuning Knobs
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
---
|
||||
date: 2026-06-10
|
||||
type: session
|
||||
tags:
|
||||
- session
|
||||
- combat
|
||||
- mc-0
|
||||
- tuning
|
||||
- netcode
|
||||
- dev-tools
|
||||
permalink: gamevault/07-sessions/2026/2026-06-10-mc0-tuningconfig-live-tuning
|
||||
---
|
||||
|
||||
# MC-0 completion — TuningConfig live-tuning singleton (dash/Charger knobs, no recompile)
|
||||
|
||||
> Closes the last MC-0 gap flagged in [[2026-06-09_MC1_Implementation]] ("the TuningConfig live-singleton still does not exist — dash/Charger knobs are baked consts"). Direction: [[Path_to_Fun]] (MC-0) · locks: [[DR-029_Path_A_Fork_Locks]]. **Purpose: make the [[2026-06-09_MC1_Build_Spec|MC-1]] fun-gate a playtest→nudge→replay loop instead of edit→recompile→replay.** All three gates passed (compile clean · 267/267 EditMode · live server==client round-trip). The MC-1 **feel** fun-gate is still the open operator item — this only removes the recompile friction from it.
|
||||
|
||||
## What was built
|
||||
A per-world `TuningConfig` `IComponentData` singleton holding 10 live feel knobs, mirroring the proven `DevTelemetry` dev-tools pattern (singleton + scalar `DebugCommandRequest` RPC + a broadcast report + a client readout static). **Zero ghost-hash change, no re-bake; fully editor-strippable** (types unconditional for RpcCollection hash parity, all systems `#if UNITY_EDITOR`).
|
||||
|
||||
- **`TuningConfig` + `DebugTuningReport` + `TuningKnob`** (`Simulation/Debug/TuningConfig.cs`, UNCONDITIONAL types): 10 floats (dash: distance/iframe/recover/cooldown/sharpness; Charger: windup/lunge-speed/lunge-dur/whiff-stagger; + Grunt windup). `Defaults()` is the **single source of truth** == the historical baked consts; `GruntWindupTicks` **references `Tuning.AttackWindupTicks`** (not a duplicate literal — `Tuning.AttackWindupTicks` stays alive for `TelegraphTests`). `Apply`/`Get`/`ClampKnob` switch on a **byte** index (no enum on a Bursted/RPC path).
|
||||
- **`TuningBroadcastSystem`** (Server, editor-only): ensures+seeds the server singleton to `Defaults()` in OnCreate; every 15 ticks broadcasts the FULL config to every connection (load-bearing for MPPM thin clients + late joiners — they have no overlay, so the report is their only path to tuned values).
|
||||
- **`DevTuningReceiveSystem` + `TuningReadout`** (Client, editor-only): drains the authoritative report into the readout, then writes the client `TuningConfig` singleton from it each tick (so the PREDICTED `DashSystem` reads live values). The overlay mutates the readout **optimistically** via `SetLocal` (instant feel for the tuner; eventually consistent with the server within ~1 RTT).
|
||||
- **`DebugOp.SetTuning = 12`** + the server handler (`TuningConfig.Apply` on the server singleton) + `DebugCommandSendSystem.SetTuning(knob, value)` (×1000 fixed-point over the int `ArgB`) + a collapsible, scroll-wrapped **Tuning section in `DebugOverlay`** (per-knob −/+ rows; each nudge fires the authoritative RPC AND the optimistic local apply).
|
||||
- **Consumers repointed:** `DashSystem` (predicted) + `EnemyAISystem` (server-only Charger/Grunt) read the singleton via `TryGetSingleton ? : Defaults()` — so **release builds fall back to `Defaults()` == today's consts, behaviour unchanged**. The 5 dash consts and 4 Charger consts were deleted (now in `Defaults()`); the 7 Charger read-sites kept their names (locals shadow the old const names) so only the const *declarations* changed.
|
||||
|
||||
## The mandatory pre-code design review (operator-forced, per the standing rule)
|
||||
Operator chose "run the design review first" over "build it now" (honoring [[validate-netcode-design-before-coding]] / [[DR-029_Path_A_Fork_Locks]]'s ritual). A 3-lens adversarial Workflow (netcode/determinism · reuse/scope/Burst · edge-cases, each finding adversarially refuted vs ground-truth code) → **15 findings, 1 confirmed, 14 refuted**.
|
||||
|
||||
- **CONFIRMED — MAJOR (fixed): dash-speed divide-by-zero → PERMANENT NaN.** `DashSystem` computes `dashSpeed = DashDistance / (IFrameWindowTicks/60)` at a read site distinct from the window math. The spec only guarded the window, not the divisor — and the **optimistic `SetLocal` path bypasses `Apply`'s clamp**, so nudging the i-frame-window knob to 0 → 0 denominator → `MoveVelocity` NaN → the kinematic body's `LocalTransform.Position` goes NaN and **stays NaN forever** (not self-correcting; corrupts the replicated transform — bricks the player). **Fix:** clamp at the read site (`uint iFrameTicks = (uint)math.max(1f, t.IFrameWindowTicks)`) used for BOTH the window and the divisor, AND clamp in `Apply` + `SetLocal` (defense in depth). Pinned by `TuningConfigTests.Apply_Clamps_Tick_Knobs_To_At_Least_One` + the golden-defaults test.
|
||||
- **Hardening folded in** (several refuted findings converged): `Apply`/`SetLocal` clamp all tick knobs ≥1 and value knobs ≥0; Charger read-sites guard with `(uint)math.max(1,…)`; **do NOT delete `Tuning.AttackWindupTicks`** (a reviewer caught `TelegraphTests.cs:73` references it → `Defaults().GruntWindupTicks` references it instead); a **golden test** pins `Defaults()` to the historical consts.
|
||||
- **Architecture validated (no change):** unconditional types / no re-bake confirmed; the broadcast is genuinely load-bearing for MPPM thin clients (not redundant); the knob-change transient mis-predict is acceptable for a dev tool; within one client's rollback re-sim the singleton is per-tick-constant (mutated ≤once/tick) so per-tick determinism holds.
|
||||
|
||||
## Validation (all 3 gates)
|
||||
- **Compile:** clean — 0 errors / 0 warnings (Bursted `DashSystem`/`EnemyAISystem` reading the singleton compiled fine; no enum-on-Burst hazard, byte knob map).
|
||||
- **EditMode: 267/267 green** (was 259; +8 `TuningConfigTests`: golden `Defaults()`==consts incl. `GruntWindupTicks==Tuning.AttackWindupTicks`, `Apply` index-map, tick/value clamps, out-of-range no-op, ×1000 wire round-trip, `ToReport`/`FromReport` round-trip, and a world test proving `DashSystem` reads a **non-default** singleton — IFrameUntilTick = now+20 with a tuned window, not Defaults' 12). The pre-existing `DashSystemTests` (asserting 112/121/145/200/20 with no singleton) double as the behaviour-preserving guard on the `Defaults()` fallback path.
|
||||
- **Live Play (real netcode session, server+client):** both worlds create+seed `TuningConfig` to `Defaults()` (iframe 12 / dist 4 / chWindup 30 / lungeSpd 16 / grunt 18); a `SetTuning` RPC (iframe 12→18, chargerWindup 30→40) **applied on the server AND converged on the client via the broadcast**, untargeted knobs unchanged; **zero console errors/exceptions** (only benign Server-Tick-Batching warnings from in-editor host load) — no Burst stale-binary / undeclared-component throw.
|
||||
|
||||
## Notes / deviations (deliberate)
|
||||
- Broadcast is **every 15 ticks unconditionally** (full state, not a delta) — simplest robust convergence for late joiners / thin clients; ~16 bytes/0.25 s on an editor-only loopback is negligible.
|
||||
- Charger knobs are **server-only consumed** (clients never simulate Chargers) but are broadcast anyway for uniformity + overlay display.
|
||||
- A mid-dash knob change desyncs the in-flight dash's baked window-length from its live recomputed speed for ONE dash (cosmetic, self-corrects next dash) — acceptable per the review.
|
||||
|
||||
## Open items (operator)
|
||||
- **The MC-1 fun-gate is still the open gate** — now unblocked for fast live tuning: open `DEV ▲` → "- Tuning (MC-0) -", nudge dash/Charger knobs while playing, read the live counters in "- Telemetry (MC-0) -". MC-1 is NOT "done" until the feel pass + bench (timed vs spam ≥70% fewer hits) + friend read pass; MC-1 is the project kill-switch.
|
||||
- After MC-1 passes → **MC-4 (melee cleave)** is the next committed code milestone ([[Path_to_Fun]]).
|
||||
- `Assets/_Recovery/0.unity` untracked artifact still pending review/delete (carried from [[2026-06-09_MC1_Implementation]]).
|
||||
|
||||
## Links
|
||||
[[Path_to_Fun]] · [[DR-029_Path_A_Fork_Locks]] · [[DR-028_Combat_Primary_Verb_Depth_First]] · [[2026-06-09_MC1_Implementation]] · [[2026-06-09_MC1_Build_Spec]] · [[validate-netcode-design-before-coding]]
|
||||
Reference in New Issue
Block a user