Files
Frametop--frametop/pointer/tests/typing-test.cpp
T
DeeJanuzandClaude Opus 5.5 78ec0f14f3 pointer: typing.h times can be negative (replays relative to a flag)
The posture's and a raise's start used -1 for none, so a replay of a flag
(times before it negative) never saw the posture. Found replaying the
2026-10-10 flags through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:33:20 -06:00

353 lines
14 KiB
C++

// Unit tests for typing on a real keyboard (pointer/helper/typing.h): made-up keys and hands at
// ft-hands' 30 Hz, the head at eye height facing -z. The hands sit where the 2026-10-10 headset
// session and the hand recorder's desk take had them (docs/hands-typing.md).
// Build and run: just test-c (or g++ -std=c++17 -I. pointer/tests/typing-test.cpp).
#include "pointer/helper/typing.h"
#include <cmath>
#include <cstdio>
#include <functional>
#include <string>
using namespace typing;
static int failures = 0;
#define CHECK(cond, ...) \
do { \
if (!(cond)) { \
std::printf("FAIL %s:%d: ", __FILE__, __LINE__); \
std::printf(__VA_ARGS__); \
std::printf("\n"); \
++failures; \
} \
} while (0)
namespace {
constexpr double kFrame = 1 / 30.0;
constexpr double kEye = 1.2;
// Palms: on the keyboard (today's first flag: 0.31 m under the eyes, 0.30 ahead, 0.17 apart),
// on the mouse (the desk take: 0.39 under, 0.27 ahead, 0.39 from the other hand), raised to point
// (0.15 under, 0.40 ahead, palm to the side), in the lap.
Hand Keys(int side) { return {true, {side ? 0.085 : -0.085, kEye - 0.31, -0.30}, 0.97}; }
Hand Mouse() { return {true, {0.30, kEye - 0.39, -0.27}, 0.85}; }
Hand Pointing(int side) { return {true, {side ? 0.18 : -0.18, kEye - 0.15, -0.40}, 0.3}; }
Hand Lap(int side) { return {true, {side ? 0.15 : -0.15, kEye - 0.55, -0.05}, 0.5}; }
Hand Unseen() { return {}; }
struct World {
Typing ty;
double t = 1000;
Head head{{0, kEye, 0}, {0, 0, -1}};
Hand hands[2] = {Unseen(), Unseen()};
std::string log;
explicit World(Params p = {}) : ty(p) {}
void Step() {
ty.Frame(t, head, hands);
for (const std::string &l : ty.log) log += l + "\n";
ty.log.clear();
t += kFrame;
}
// Runs secs of frames; each: called before each frame; keyEvery: a key that often (0: none).
void Run(double secs, double keyEvery = 0, const std::function<void()> &each = {}) {
const double end = t + secs - 1e-9;
double nextKey = t;
while (t < end) {
if (keyEvery > 0 && t >= nextKey) ty.Key(t), nextKey += keyEvery;
if (each) each();
Step();
}
}
bool Blocked(int s) const { return ty.Blocked(s, t); }
};
// No hands seen: a key holds every hand back for POINTER_PINCH_TYPING (2 s), then nothing.
void TestKeysAlone() {
World w;
CHECK(!w.Blocked(0) && !w.Blocked(1), "no keys: nothing held back");
w.ty.Key(w.t);
w.Run(1.9);
CHECK(w.Blocked(0) && w.Blocked(1), "1.9 s after a key: held back");
CHECK(std::string(w.ty.Why(1, w.t)) == "typing", "why: %s", w.ty.Why(1, w.t));
w.Run(0.2);
CHECK(!w.Blocked(0) && !w.Blocked(1), "2.1 s after: not");
}
// The live complaint (14:57:10-30): typing with pauses of a few seconds, the hands on the keys all
// along. The old 1 s guard let the pinches in the pauses through; on the keyboard they stay held back.
void TestPausesKeepTheHandsParked() {
World w;
w.hands[0] = Keys(0), w.hands[1] = Keys(1);
w.Run(0.1);
CHECK(!w.Blocked(1), "hands on the keys, no key yet (and none for a while): free");
w.Run(3, 0.2);
CHECK(w.ty.Parked(0) && w.ty.Parked(1), "typing: both hands on the keyboard");
bool always = true;
w.Run(10, 0, [&] { always = always && w.Blocked(0) && w.Blocked(1); });
CHECK(always, "a 10 s pause with the hands on the keys: held back throughout");
CHECK(std::string(w.ty.Why(0, w.t)) == "typing: the hand is on the keyboard", "why: %s", w.ty.Why(0, w.t));
CHECK(w.log.find("left hand on the keyboard") != std::string::npos && w.log.find("right hand on the keyboard") != std::string::npos,
"logged: %s", w.log.c_str());
// small movements on the keys (reaching for a key, 3 cm up and forward) don't free it
w.Run(2, 0.25, [&] {
const double k = std::sin(w.t * 9);
w.hands[1].palm.y = Keys(1).palm.y + 0.03 * k, w.hands[1].palm.z = Keys(1).palm.z - 0.04 * std::fabs(k);
});
CHECK(w.ty.Parked(1), "typing movements keep it on the keyboard");
// a single wild frame (a misread hand 15 cm up, palm flipped) doesn't free it either
w.hands[1] = Keys(1), w.hands[1].palm.y += 0.15, w.hands[1].down = -0.5;
w.Step();
w.hands[1] = Keys(1);
w.Run(1);
CHECK(w.ty.Parked(1), "one misread frame: still on the keyboard");
}
// Lifting a hand off the keyboard: a deliberate pinch 0.5 s after it began to rise works, even with
// the other hand still typing; the typing hand stays held back.
void TestRaiseFreesAtOnce() {
World w;
w.hands[0] = Keys(0), w.hands[1] = Keys(1);
w.Run(2, 0.2);
const double from = w.t;
const Hand a = Keys(1), b = Pointing(1);
w.Run(0.5, 0.2, [&] {
const double f = std::clamp((w.t - from) / 0.25, 0.0, 1.0); // up in 0.25 s
w.hands[1].palm = {a.palm.x + (b.palm.x - a.palm.x) * f, a.palm.y + (b.palm.y - a.palm.y) * f, a.palm.z + (b.palm.z - a.palm.z) * f};
w.hands[1].down = a.down + (b.down - a.down) * f;
});
CHECK(!w.Blocked(1), "raised 0.5 s ago, keys still coming from the other hand: free");
CHECK(w.Blocked(0), "the typing hand: held back");
CHECK(w.log.find("right hand off the keyboard (raised") != std::string::npos, "logged: %s", w.log.c_str());
// the freed hand stays free while it's up, keys or not
w.Run(3, 0.2);
CHECK(!w.Blocked(1) && w.Blocked(0), "up while the other types: free");
// when it was freed: within 0.2 s of starting up (8 cm of a 16 cm raise in 0.25 s, then 0.1 s)
World v;
v.hands[0] = Keys(0), v.hands[1] = Keys(1);
v.Run(2, 0.2);
const double start = v.t;
double freedAt = -1;
v.Run(1, 0.2, [&] {
const double f = std::clamp((v.t - start) / 0.25, 0.0, 1.0);
v.hands[1].palm.y = a.palm.y + (b.palm.y - a.palm.y) * f;
if (freedAt < 0 && !v.Blocked(1)) freedAt = v.t;
});
CHECK(freedAt > 0 && freedAt - start <= 0.3, "free %.2f s after starting up", freedAt - start);
}
// Back down on the keys: on the keyboard again once keys come, or the posture holds.
void TestBackDown() {
World w;
w.hands[0] = Keys(0), w.hands[1] = Keys(1);
w.Run(1, 0.2);
w.hands[1] = Pointing(1);
w.Run(0.5);
CHECK(!w.ty.Parked(1) && w.ty.Free(1), "raised: free");
w.hands[1] = Keys(1);
w.Run(0.2, 0.1);
CHECK(w.ty.Parked(1) && w.Blocked(1), "back down while typing: on the keyboard");
// up again, and down after the keys stopped: the posture puts it back within 0.3 s
w.hands[1] = Pointing(1);
w.Run(3);
CHECK(!w.Blocked(1), "up, no keys for 3 s: free");
w.hands[1] = Keys(1);
w.Run(0.2);
CHECK(!w.Blocked(1), "just back down, no key: not yet");
w.Run(0.3);
CHECK(w.ty.Posture() && w.Blocked(1), "both hands in the posture 0.3 s, a key this session: held back");
}
// The posture before the first key: only within POINTER_TYPING_RECENT of a key (a keyboard in use).
void TestPosture() {
World never;
never.hands[0] = Keys(0), never.hands[1] = Keys(1);
never.Run(5);
CHECK(!never.Blocked(0) && !never.Blocked(1), "no key ever: the posture alone holds nothing back");
World w;
w.ty.Key(w.t);
w.Run(60);
CHECK(!w.Blocked(1), "a key a minute ago, hands away: free");
w.hands[0] = Keys(0), w.hands[1] = Keys(1);
w.Run(0.2);
CHECK(!w.Blocked(1), "hands just down: not yet");
w.Run(0.2);
CHECK(w.Blocked(0) && w.Blocked(1), "0.4 s in the posture: the first keystroke's pinch is held back");
CHECK(w.log.find("keyboard posture") != std::string::npos, "logged: %s", w.log.c_str());
// today's second flag: hands lower and nearer (0.41 under the eyes, 0.20 ahead, 0.15 apart)
World low;
low.ty.Key(low.t);
low.Run(5);
low.hands[0] = {true, {-0.075, kEye - 0.41, -0.20}, 0.87}, low.hands[1] = {true, {0.075, kEye - 0.42, -0.20}, 0.96};
low.Run(0.4);
CHECK(low.Blocked(0) && low.Blocked(1), "lower hands, nearer: the posture too");
// POINTER_TYPING_RECENT=0: always; POINTER_TYPING_POSTURE=0: keys only
Params always;
always.recent = 0;
World a(always);
a.hands[0] = Keys(0), a.hands[1] = Keys(1);
a.Run(0.4);
CHECK(a.Blocked(1), "recent 0: the posture without any key");
Params off;
off.posture = 0;
World o(off);
o.ty.Key(o.t);
o.Run(5);
o.hands[0] = Keys(0), o.hands[1] = Keys(1);
o.Run(2);
CHECK(!o.Blocked(1), "posture 0: keys only");
}
// What isn't the posture: one hand low alone (a gaze pinch with the elbow resting, the other hand in
// the lap), a hand on the mouse beside the keyboard hand, both hands up pointing.
void TestNotThePosture() {
World w;
w.ty.Key(w.t);
w.Run(5);
w.hands[0] = Lap(0), w.hands[1] = {true, {0.15, kEye - 0.40, -0.30}, 0.9};
w.Run(2);
CHECK(!w.Blocked(1), "one hand low alone: free (%s)", w.ty.Why(1, w.t));
w.hands[0] = Keys(0), w.hands[1] = Mouse();
w.Run(2);
CHECK(!w.Blocked(0) && !w.Blocked(1), "keyboard hand and mouse hand, no key: free");
w.hands[0] = Pointing(0), w.hands[1] = Pointing(1);
w.Run(2);
CHECK(!w.Blocked(0) && !w.Blocked(1), "both up: free");
}
// A mouse user with one hand on the keyboard: a key (a shortcut) puts both on the keyboard, the mouse
// hand too (its pinches are the mouse's buttons); lifting the mouse hand frees it.
void TestMouseUser() {
World w;
w.hands[0] = Keys(0), w.hands[1] = Mouse();
w.Run(1);
w.ty.Key(w.t);
w.Run(0.1);
CHECK(w.ty.Parked(0) && w.ty.Parked(1), "a shortcut: the keyboard hand and the mouse hand are on the keyboard");
w.Run(20, 0, [&] { w.hands[1].palm.x = Mouse().palm.x + 0.05 * std::sin(w.t); }); // mousing
CHECK(w.Blocked(1), "mousing 20 s on: still held back");
w.hands[1] = Pointing(1);
w.Run(0.3);
CHECK(!w.Blocked(1) && w.Blocked(0), "the mouse hand raised: free; the keyboard hand: not");
}
// Lost hands: through a short loss it stays on the keyboard; unseen 3 s, forgotten.
void TestLost() {
World w;
w.hands[0] = Keys(0), w.hands[1] = Keys(1);
w.Run(1, 0.2);
w.hands[1] = Unseen();
w.Run(2);
w.hands[1] = Keys(1);
w.Step();
CHECK(w.ty.Parked(1), "unseen 2 s, back where it typed: still on the keyboard");
w.hands[1] = Unseen();
w.Run(3.2);
CHECK(!w.ty.Parked(1) && !w.Blocked(1), "unseen 3 s: forgotten");
// back up where it was raised: free
w.hands[0] = Keys(0), w.hands[1] = Keys(1);
w.Run(0.5, 0.2);
w.hands[1] = Unseen();
w.Run(1);
w.hands[1] = Pointing(1);
w.Run(0.2);
CHECK(!w.Blocked(1), "lost on the keys, found raised: free");
}
// Pointing with one hand (its ray up) and pressing Enter with the other: the pointing hand isn't held
// back. The same hand idle where it was (not pointing): the key holds it back for 2 s.
void TestPointAndEnter() {
World w;
w.hands[0] = Lap(0), w.hands[1] = Pointing(1);
w.hands[1].busy = true;
w.Run(1);
w.ty.Key(w.t);
w.Run(0.5);
CHECK(!w.Blocked(1), "the pointing hand: free (%s)", w.ty.Why(1, w.t));
CHECK(w.Blocked(0), "the hand that pressed the key: held back");
World v;
v.hands[0] = Lap(0), v.hands[1] = Pointing(1);
v.Run(1);
v.ty.Key(v.t);
v.Run(0.5);
CHECK(v.Blocked(1), "a raised hand that isn't in use: held back by the key");
// a hand on the keys whose ray came up (today, 14:57:37): on the keyboard all the same
World k;
k.hands[0] = Keys(0), k.hands[1] = Keys(1);
k.hands[1].busy = true;
k.Run(1, 0.2);
CHECK(k.ty.Parked(1) && k.Blocked(1), "a typing hand with its ray up: on the keyboard");
}
// A palm turned up (the palm menu's pose) leaves the keyboard too.
void TestTurned() {
World w;
w.hands[0] = Keys(0), w.hands[1] = Keys(1);
w.Run(1, 0.2);
w.hands[1].down = -0.8;
w.Run(0.2);
CHECK(!w.Blocked(1), "palm turned up: free");
CHECK(w.log.find("palm turned") != std::string::npos, "logged: %s", w.log.c_str());
}
void TestOff() {
Params p;
p.hold = 0;
World w(p);
w.hands[0] = Keys(0), w.hands[1] = Keys(1);
w.Run(2, 0.2);
CHECK(!w.Blocked(0) && !w.Blocked(1) && !w.ty.Parked(0), "POINTER_PINCH_TYPING=0: no guard");
}
// Times below zero (a replay's, relative to a flag): the same.
void TestNegativeClock() {
World w;
w.t = -16;
w.ty.Key(w.t - 60);
w.hands[0] = Keys(0), w.hands[1] = Keys(1);
w.Run(0.4);
CHECK(w.ty.Posture() && w.Blocked(1), "the posture at t < 0");
w.Run(1, 0.2);
w.hands[1] = Pointing(1);
w.Run(0.3);
CHECK(!w.Blocked(1), "raised at t < 0: free");
}
// The head turned (facing +x): ahead counts along its heading.
void TestHeading() {
World w;
w.head.fwd = {1, -0.5, 0};
w.hands[0] = {true, {0.30, kEye - 0.31, -0.085}, 0.97}, w.hands[1] = {true, {0.30, kEye - 0.31, 0.085}, 0.97};
w.Run(1, 0.2);
CHECK(w.ty.Parked(0) && w.ty.Parked(1), "facing +x, the keyboard 0.3 m that way: on it");
World v;
v.head.fwd = {-1, 0, 0};
v.hands[0] = {true, {0.30, kEye - 0.31, -0.085}, 0.97}, v.hands[1] = {true, {0.30, kEye - 0.31, 0.085}, 0.97};
v.Run(1, 0.2);
CHECK(!v.ty.Parked(0) && !v.ty.Parked(1), "behind the head: not");
}
} // namespace
int main() {
TestKeysAlone();
TestPausesKeepTheHandsParked();
TestRaiseFreesAtOnce();
TestBackDown();
TestPosture();
TestNotThePosture();
TestMouseUser();
TestLost();
TestPointAndEnter();
TestTurned();
TestOff();
TestHeading();
TestNegativeClock();
if (failures) {
std::printf("%d failures\n", failures);
return 1;
}
std::printf("typing-test: ok\n");
return 0;
}