The 13 vault notes were left dirty by basic-memory back-filling title/type/permalink frontmatter into files written with Write/Edit (it also strips the trailing newline — restored here so future diffs stay clean). Content is unchanged; verified frontmatter-only. packages-lock.json is the com.coplaydev.unity-mcp #main branch ref silently re-resolving. Pinning that dependency lands with the package cleanup batch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3.4 KiB
title, type, permalink
| title | type | permalink |
|---|---|---|
| 2026-07-20_Tuning_Bench_C | note | gamevault/07-sessions/2026/2026-07-20-tuning-bench-c |
2026-07-20 (C) — Data-driven tuning bench (profiles + audit + probe)
Operator: "the knobs are a lot & not very intuitive — is there any way for you to do data-driven tuning?" The answer: raw scalars are bad for both of us — the operator can't intuit ticks/radians, and I can't feel the game. What I CAN do precisely is derive and measure. Three tools:
1. Feel profiles — whole gestalts as data
Assets/_Project/Tuning/Profiles/*.json — one file = sim knobs (TuningKnob names) + FeelConfig fields, PARTIAL
(only listed keys apply). FeelProfileService (Client/Debug, editor-only) applies via the existing authoritative
SetTuning path + FeelConfig reflection; "Save current" captures a hand-dialed state back to a profile.
DebugOverlay grew a Profiles section (one-click A/B) + the probe button. Starter set: baseline-0720 (the shipped
defaults / reset), heavy-committed (MonHun-lean), snappier (Hades-lean) — all DPS-held (~60) via dmg = recover.
Rule: code defaults stay the single shipped truth; profiles are experiments — a winner gets folded into
TuningConfig.Defaults() / FeelConfig.ResetDefaults() and the profile archived.
2. TuningAuditTools — the derived truth (edit-time, no eyes needed)
ProjectM/Tuning/Audit Melee Timing + Reach: derives per-step contact from the ACTUAL clip WindUp takes ÷ wired
state speeds and compares against MeleeTiming + the knob (drift detector — kills the hand-copied-constant class
of bug); checks the un-clamped coupling invariants the design review flagged (contact < lock, ≤ CastFacingTicks);
computes cadence/DPS; measures the reach-honesty ratio off the BAKED weapon mesh. First run: zero drift
(16/10/20 == clip truth), DPS 60.0 flat, and reach ratio ×0.93 base / ×1.16 finisher — the "swings don't line
up" complaint is now a measured number inside the ≤1.25 budget (was >2×).
3. CombatProbe — measured play numbers
CombatProbe.Run(n) (Client/Debug, editor-only; overlay button): spawns a 100k-HP dummy in the cone, mash-drives
a real injected attack chain, samples the SERVER per frame → per-swing start/scheduled-contact/damage-tick/amount,
measured cadence + DPS + buffer usage. Demo run at defaults: gaps 30/30/45 exactly, contacts +16/+10/+20 exactly,
damage 36/36/54 (class seed × finisher). Then the full loop: applied snappier → measured 24/24/36 gaps and
+12/+8/+15 contacts immediately. Tuning = author JSON → apply → measure → compare. Probe fix during the demo: wait
for the FINAL swing's contact before finishing (first run cut off swing 6).
The workflow now
- Me (precise): edit/author a profile →
Apply→CombatProbe→ read measured numbers vs targets → iterate. - Operator (intuitive): tap profile buttons in the overlay to A/B whole feels → nudge individual rows if desired → Save current → I fold the winner into code defaults.
Validation: console clean; audit + probe both demonstrated live; profile apply verified against measured sim output. No netcode surface (profiles ride the existing SetTuning RPC; probe is read-only diagnostics).
Next: operator A/Bs the three starter profiles in DevSandbox (DEV overlay → Feel Profiles); tell me which direction wins (or Save-current a custom) and I fold it into defaults + build the next profile iteration.