mirror of
https://github.com/hakumaguro/vrcft-steam-frame.git
synced 2026-10-06 06:00:16 +02:00
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:
1 parent
f5ec1458fe
commit
1f31e0cc1a
4 files changed
+23
-8
No files matched your search
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user