Consolidated from the qa-input-collision-fix-0.1.0 work, applied onto main so only the changes main genuinely lacks are carried (main already merged the walkable-demo/collision work independently). - fix(input): populate the UE 5.8 FInputMappingContextMappingData DefaultKeyMappings field in ue_create_input_assets.py (the runtime reads GetMappings()->DefaultKeyMappings.Mappings; the script previously wrote only the deprecated 'mappings' array, so the cooked game had 0 runtime mappings and every key silently did nothing — in packaged AND PIE). Regenerated IMC_Default.uasset. WASD/mouse/Space/R/Escape verified working. - fix(build): drop editor-only APIs that break the game target build — UStaticMesh::PostEditChange() (GigLineAssetTools) and AActor::GetActorLabel() (character ground probe). GigLine and GigLineEditor now both compile clean. - tools: OS-level uinput input harness (input_emulation.py + input_test.py) and run_pie.sh for PIE isolation; document the PIE-vs-packaged input test in Docs/build.md. Removed the temporary SIM/EI/screenshot diagnostics from the character.
172 lines
5.3 KiB
Python
172 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""QA harness: inject scripted OS-level keyboard/mouse input at the running game.
|
|
|
|
This is the tool that proves whether OS input actually reaches the game. It uses
|
|
`Tools/input_emulation.py` to create a virtual input device directly on
|
|
`/dev/uinput`, so a focused game window receives the injected keys and mouse
|
|
movement as genuine hardware input — indistinguishable from a real keyboard.
|
|
|
|
Typical workflow
|
|
----------------
|
|
The game must already be running and focused (it will not be launched here; the
|
|
user brings up the window). Run this script from a separate terminal:
|
|
|
|
python3 Tools/input_test.py --sequence move:2,look:3,jump:1,reset:1,pause:1
|
|
|
|
It prints a per-step timeline (seconds) to stdout, which you can compare against
|
|
the `GigLine IN:` rows in the game's `Saved/Logs`. If the game log shows
|
|
`MoveForward fired` / `Look fired` for the injected steps, OS input is reaching
|
|
the pawn and the remaining problem is not input delivery.
|
|
|
|
Usage
|
|
-----
|
|
python3 Tools/input_test.py # default smoke sequence
|
|
python3 Tools/input_test.py --sequence move:2,look:3,jump:1,reset:1,pause:1
|
|
python3 Tools/input_test.py --device /dev/uinput --name "GigLine QA Input"
|
|
|
|
Steps supported in `--sequence` (comma-separated, `name:count`):
|
|
move hold W forward (each unit = ~0.5 s)
|
|
back hold S
|
|
strafe hold D
|
|
left hold A
|
|
look nudge mouse right (rotate camera)
|
|
jump press Space
|
|
reset press R
|
|
pause press Escape
|
|
click left mouse click
|
|
|
|
Design
|
|
------
|
|
* The device is kept open for the whole run and destroyed on exit (context
|
|
manager), so nothing persists after the harness quits.
|
|
* Each step logs an absolute timestamp so you can line it up with the game log
|
|
wall-clock times (the game logs to `Saved/Logs/GigLine.log` with the same
|
|
system clock).
|
|
* No third-party packages: only the Python standard library.
|
|
"""
|
|
|
|
import argparse
|
|
import logging
|
|
import sys
|
|
import time
|
|
|
|
from input_emulation import InputDevice, UInputError
|
|
|
|
# Duration of one "unit" for hold-based movement steps, in seconds.
|
|
MOVE_STEP_S = 0.5
|
|
|
|
# Handlers map step names to a callable(dev) that performs the key/mouse action.
|
|
def _move(dev):
|
|
dev.hold("w", MOVE_STEP_S)
|
|
|
|
|
|
def _back(dev):
|
|
dev.hold("s", MOVE_STEP_S)
|
|
|
|
|
|
def _strafe(dev):
|
|
dev.hold("d", MOVE_STEP_S)
|
|
|
|
|
|
def _left(dev):
|
|
dev.hold("a", MOVE_STEP_S)
|
|
|
|
|
|
def _look(dev):
|
|
# nudge the mouse +240 px on X repeatedly so the camera has time to respond.
|
|
for _ in range(6):
|
|
dev.move(240, 0, delay=0.03)
|
|
time.sleep(0.15)
|
|
|
|
|
|
def _jump(dev):
|
|
dev.key("space", delay=0.05)
|
|
|
|
|
|
def _reset(dev):
|
|
dev.key("r", delay=0.05)
|
|
|
|
|
|
def _pause(dev):
|
|
dev.key("esc", delay=0.05)
|
|
|
|
|
|
def _click(dev):
|
|
dev.bclick("left")
|
|
|
|
|
|
STEPS = {
|
|
"move": _move, "back": _back, "strafe": _strafe, "left": _left,
|
|
"look": _look, "jump": _jump, "reset": _reset, "pause": _pause,
|
|
"click": _click,
|
|
}
|
|
|
|
|
|
def parse_sequence(text):
|
|
"""'move:2,look:1,jump:1' -> ordered list of (name, count)."""
|
|
seq = []
|
|
for item in (text or "").split(","):
|
|
item = item.strip()
|
|
if not item:
|
|
continue
|
|
if ":" in item:
|
|
name, _, count = item.partition(":")
|
|
count = int(count or "1")
|
|
else:
|
|
name, count = item, 1
|
|
if name not in STEPS:
|
|
raise ValueError(f"unknown step {name!r}; known: {sorted(STEPS)}")
|
|
seq.append((name, count))
|
|
if not seq:
|
|
seq = [("move", 2), ("look", 1), ("jump", 1), ("reset", 1), ("pause", 1)]
|
|
return seq
|
|
|
|
|
|
def run_sequence(dev, seq):
|
|
t0 = time.time()
|
|
for name, count in seq:
|
|
for _ in range(count):
|
|
stamp = time.time() - t0
|
|
print(f"[{stamp:6.3f}s] -> {name}")
|
|
# A brief settle so distinct keystrokes are logged separately.
|
|
time.sleep(0.1)
|
|
STEPS[name](dev)
|
|
|
|
|
|
def main(argv=None):
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--sequence",
|
|
help="comma-separated steps and repeats, e.g. move:2,look:3")
|
|
ap.add_argument("--device", default="/dev/uinput", help="uinput device path")
|
|
ap.add_argument("--name", default="GigLine QA Input", help="virtual device name")
|
|
ap.add_argument("-v", "--verbose", action="store_true",
|
|
help="log every injected event via logging")
|
|
args = ap.parse_args(argv)
|
|
|
|
if args.verbose:
|
|
logging.basicConfig(level=logging.INFO,
|
|
format="%(asctime)s.%(msecs)03d %(name)s %(message)s",
|
|
datefmt="%H:%M:%S")
|
|
try:
|
|
seq = parse_sequence(args.sequence)
|
|
except ValueError as exc:
|
|
ap.error(str(exc))
|
|
|
|
try:
|
|
with InputDevice(name=args.name, path=args.device) as dev:
|
|
print(f"# virtual device {args.name!r} active; send sequence now")
|
|
print(f"# steps: {' '.join(f'{n}x{c}' for n, c in seq)}")
|
|
run_sequence(dev, seq)
|
|
except UInputError as exc:
|
|
print(f"ERROR: {exc}", file=sys.stderr)
|
|
return 1
|
|
except KeyboardInterrupt:
|
|
print("\n# interrupted; device destroyed on exit")
|
|
return 130
|
|
print("# done; inputs sent")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main()) |