From 1f31e0cc1a6d187c43b8dd2fbd5404bde886b150 Mon Sep 17 00:00:00 2001 From: Hakumaguro <257888523+hakumaguro@users.noreply.github.com> Date: Sun, 27 Sep 2026 23:50:47 +0900 Subject: [PATCH] Follow-up review fixes: config relationship checks, retry failed config reads, analyzer uses configured deadband - ModuleConfig.Validate: maxFloor + minRange <= 1, minCeil > 0; LidCal clamp bound can no longer exceed 1 - ReloadConfig: record the timestamp only after a successful load so a temporarily locked file is retried - tune.py analyze: map with lid.deadband / wink.assistClosed from the installed config; STEAMFRAME_CONFIG override Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 5 +++++ module/ModuleConfig.cs | 3 +++ module/SteamFrameVRCFTModule.cs | 12 ++++++------ tools/tune.py | 11 +++++++++-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2afe0a5..c7efd6c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,11 @@ Eye tracking for the Steam Frame in VRChat via VRCFaceTracking (VRCFT). Read `RE - `headset-setup.sh` pins frameeyeosc to a reviewed commit (`REV`, override `FRAMEEYEOSC_REV`) and moves existing checkouts to it; `FRAMEEYEOSC_NO_BUILD=1` tests only the checkout logic (works locally with `HOME=`). - `Select-HeadsetTarget` only counts addresses of connected adapters (Windows lists a disconnected adapter's IP). +Follow-up review of f5ec145, also fixed in 0.2.2: `Validate` checks relationships (`maxFloor + minRange <= 1`, `minCeil > 0`) and the +adaptive clamp can no longer get a lower bound above 1 (fuzzed: 7269 valid configs x 200 samples through `LidCal.Map`, no throw, output 0..1); +a failed config read is retried every second (timestamp recorded only after success, each distinct error logged once; tested with a 3 s +exclusive lock); `tune.py analyze` maps with the configured `lid.deadband` and `wink.assistClosed` (`STEAMFRAME_CONFIG=` overrides +the config path for tests). Not done yet: offline replay tests for the eyelid pipeline (the replays in this history were ad-hoc scripts). ## Working with the user diff --git a/module/ModuleConfig.cs b/module/ModuleConfig.cs index 340c39c..0f4b5ab 100644 --- a/module/ModuleConfig.cs +++ b/module/ModuleConfig.cs @@ -119,6 +119,9 @@ public sealed class ModuleConfig if (Bad(Lid.Deadband, 0f, 0.45f)) return "lid.deadband must be 0..0.45"; if (Bad(Lid.Smoothing, 0f, 1f) || Bad(Lid.Tau, 0f, 1f)) return "lid.smoothing and lid.tau must be 0..1"; if (Bad(Lid.MaxFloor, 0f, 1f) || Bad(Lid.MinCeil, 0f, 1f) || Bad(Lid.MinRange, 0.01f, 1f)) return "lid.maxFloor/minCeil/minRange out of range"; + // the adaptive ceiling is at least max(minCeil, floor + minRange) and at most 1, with floor up to maxFloor + if (Lid.MaxFloor + Lid.MinRange > 1f) return "lid.maxFloor + lid.minRange must not exceed 1"; + if (Lid.MinCeil <= 0f || Lid.MinCeil > 1f) return "lid.minCeil must be above 0 and at most 1"; foreach (var (c, o, eye) in new[] { (Lid.LeftClosed, Lid.LeftOpen, "left"), (Lid.RightClosed, Lid.RightOpen, "right") }) { if (c is float fc && Bad(fc, 0f, 1f)) return $"lid.{eye}Closed must be 0..1"; diff --git a/module/SteamFrameVRCFTModule.cs b/module/SteamFrameVRCFTModule.cs index 4aa0e6d..250689c 100644 --- a/module/SteamFrameVRCFTModule.cs +++ b/module/SteamFrameVRCFTModule.cs @@ -74,7 +74,7 @@ public class SteamFrameVRCFTModule : ExtTrackingModule if (Sm < Lo) Lo = Sm; else Lo += (Sm - Lo) * c.Tau; if (Sm > Hi) Hi = Sm; else Hi += (Sm - Hi) * c.Tau; Lo = Math.Clamp(Lo, 0f, c.MaxFloor); - Hi = Math.Clamp(Hi, MathF.Max(c.MinCeil, Lo + c.MinRange), 1f); + Hi = Math.Clamp(Hi, MathF.Min(1f, MathF.Max(c.MinCeil, Lo + c.MinRange)), 1f); // lower bound never above 1 } float n = (raw - Lo) / (Hi - Lo); return Math.Clamp((n - c.Deadband) / (1f - 2f * c.Deadband), 0f, 1f); @@ -175,14 +175,14 @@ public class SteamFrameVRCFTModule : ExtTrackingModule { var stamp = File.Exists(_cfgPath) ? File.GetLastWriteTimeUtc(_cfgPath) : DateTime.MinValue; if (!force && stamp == _cfgStamp) return; - _cfgStamp = stamp; var next = ModuleConfig.TryLoad(_cfgPath, out var error); if (next == null) { - // keep the configuration that was working; say why the new one was not used - _configError = $"steamframe-config.json was not applied ({error}); still using the previous settings"; - FileLog(_configError); - Logger.LogWarning("{0}", _configError); + // Keep the configuration that was working and say why the new one was not used. The timestamp is not + // recorded, so the file is read again next second (a temporary lock clears; a fixed file loads). + var msg = $"steamframe-config.json was not applied ({error}); still using the previous settings"; + if (msg != _configError) { FileLog(msg); Logger.LogWarning("{0}", msg); } + _configError = msg; return; } _cfg = next; diff --git a/tools/tune.py b/tools/tune.py index d28d887..2cf87cf 100644 --- a/tools/tune.py +++ b/tools/tune.py @@ -80,6 +80,8 @@ def beep(freq=880, ms=120): def config_path(): + if os.environ.get("STEAMFRAME_CONFIG"): # testing / non-standard installs + return os.environ["STEAMFRAME_CONFIG"] hits = glob.glob(os.path.join(os.environ.get("APPDATA", ""), "VRCFaceTracking", "CustomLibs", "*", "steamframe-config.json")) if hits: return max(hits, key=os.path.getmtime) @@ -320,14 +322,19 @@ def cmd_analyze(a): # Wink assist: does the other eye squint while one eye is closed? Compare it (in the module's mapped units) with that eye's # level when both eyes are closed; if the two separate, the assist can tell a wink from a real closure. - mapped = lambda n: max(0.0, min(1.0, (n - 0.06) / 0.88)) # module: deadband 0.06 + # use the settings the module will run with (applying keeps the existing deadband and assistClosed) + cp_now = config_path() + cfg_now = _load_cfg(cp_now) if cp_now else {} + db = float((cfg_now.get("lid") or {}).get("deadband", 0.06)) + a_closed = float((cfg_now.get("wink") or {}).get("assistClosed", 0.06)) + mapped = lambda n: max(0.0, min(1.0, (n - db) / (1 - 2 * db))) mL = lambda x: mapped(nL(x[1])) mR = lambda x: mapped(nR(x[2])) # per eye, its level while both eyes are closed (p75: the both-closed step also contains ceiling-pinned glitch frames) closed_lvl = {"L": pct([mL(x) for x in g["closed"]], 0.75), "R": pct([mR(x) for x in g["closed"]], 0.75)} squint, mins = [], [] for name, winking, other, oe in (("wink_left", mL, mR, "R"), ("wink_right", mR, mL, "L")): - seg = [x for x in g.get(name, []) if winking(x) < 0.06] + seg = [x for x in g.get(name, []) if winking(x) < a_closed] if len(seg) < 10: continue med = st.median(other(x) for x in seg)