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 <noreply@anthropic.com>
This commit is contained in:
HakumaguroandClaude Opus 5.5 committed 2026-09-27 23:50:47 +09:00
1 parent f5ec1458fe
commit 1f31e0cc1a
4 files changed
+23 -8

No files matched your search

+5
View File
@@ -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=<tmp>`).
- `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=<file>` 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
+3
View File
@@ -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";
+6 -6
View File
@@ -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;
+9 -2
View File
@@ -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)