File size: 4,364 Bytes
ae4e95d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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]]