def _clamp(v: float, lo: float, hi: float) -> float: return float(max(float(lo), min(float(hi), float(v)))) def _as_float(v, default: float) -> float: try: return float(v) except Exception: return float(default) def _client_schema_5d(state: dict | None) -> list[float]: """ Map Next.js/WebRTC client controls to our 5-D action vector: [forwardBack, strafeLeftRight, turnLeftRight, vertical, boost] Inputs supported: - { "pressed": { w,a,s,d,shift,space,left,right } } - { "pressed_keys": ["w","a",...] } (legacy) - { "schemaVersion": "keys+intensity-v1", "actionVector5d": [f0, f1, f2, f3, f4], "sensitivity": { "forwardBack": x, "strafeLeftRight": x, "turnLeftRight": x, "vertical": x, "boost": x }, } Rules (keyboard → axes/buttons): - axis0 = (w - s) * forwardBack - axis1 = (d - a) * strafeLeftRight - axis2 = (right - left) * turnLeftRight - axis3 = space * vertical - axis4 = shift * boost """ if not isinstance(state, dict): return [0.0, 0.0, 0.0, 0.0, 0.0] schema_version = str(state.get("schemaVersion", "")).strip() vec_direct = state.get("actionVector5d", None) if schema_version == "keys+intensity-v1" and isinstance(vec_direct, (list, tuple)) and int(len(vec_direct)) == 5: return [_clamp(_as_float(x, 0.0), -1.0, 1.0) for x in vec_direct] sens = state.get("sensitivity", None) if not isinstance(sens, dict): sens = {} s_forward = _clamp(_as_float(sens.get("forwardBack", 1.0), 1.0), 0.0, 1.0) s_strafe = _clamp(_as_float(sens.get("strafeLeftRight", 1.0), 1.0), 0.0, 1.0) s_turn = _clamp(_as_float(sens.get("turnLeftRight", 1.0), 1.0), 0.0, 1.0) s_vertical = _clamp(_as_float(sens.get("vertical", 1.0), 1.0), 0.0, 1.0) s_boost = _clamp(_as_float(sens.get("boost", 1.0), 1.0), 0.0, 1.0) pressed = state.get("pressed") if isinstance(pressed, dict): w = bool(pressed.get("w", False)) a = bool(pressed.get("a", False)) s = bool(pressed.get("s", False)) d = bool(pressed.get("d", False)) shift = bool(pressed.get("shift", False)) space = bool(pressed.get("space", False)) left = bool(pressed.get("left", False)) right = bool(pressed.get("right", False)) else: keys = set(k.lower() for k in state.get("pressed_keys", []) if isinstance(k, str)) w = ("w" in keys) a = ("a" in keys) s = ("s" in keys) d = ("d" in keys) shift = ("shift" in keys) space = ("space" in keys) left = ("arrowleft" in keys) or ("left" in keys) right = ("arrowright" in keys) or ("right" in keys) wv = 1.0 if w else 0.0 sv = 1.0 if s else 0.0 av = 1.0 if a else 0.0 dv = 1.0 if d else 0.0 lv = 1.0 if left else 0.0 rv = 1.0 if right else 0.0 space_v = 1.0 if space else 0.0 shift_v = 1.0 if shift else 0.0 axis0 = _clamp((wv - sv) * s_forward, -1.0, 1.0) axis1 = _clamp((dv - av) * s_strafe, -1.0, 1.0) axis2 = _clamp((rv - lv) * s_turn, -1.0, 1.0) axis3 = _clamp(space_v * s_vertical, -1.0, 1.0) axis4 = _clamp(shift_v * s_boost, -1.0, 1.0) return [axis0, axis1, axis2, axis3, axis4] def nextjs_client_actions_to_world_model_5_dim_actions(state: dict | None, *, allow_jump_sprint: bool = False) -> list[float]: """ Client controls -> the model's 5-d action: [forward(W/S), sideways(A/D), sprint, jump, camera_rx] Signs: +sideways = D, +camera_rx = turning right (arrow right). allow_jump_sprint=True (the checkpoint config's `jump_sprint`): shift goes to dim 2 (sprint) and space to dim 3 (jump), with the client's gains applied, clamped to [0, 1]. allow_jump_sprint=False: dims 2 and 3 are 0 whatever the client sends (space / shift are ignored). No keys pressed -> the all-zero vector (standing still). """ # Client schema (both the key path and actionVector5d): [forwardBack, strafeLeftRight, turnLeftRight, vertical, boost] v = _client_schema_5d(state) if allow_jump_sprint: sprint = _clamp(v[4], 0.0, 1.0) # shift -> dim 2 jump = _clamp(v[3], 0.0, 1.0) # space -> dim 3 return [v[0], v[1], sprint, jump, v[2]] return [v[0], v[1], 0.0, 0.0, v[2]]