Files
bigmak94andClaude Opus 5.5 525c73c6e9 Fix the Wii Remote's tilt for Ray surfing and the Star Ball
The emulated accelerometer had its own axes (x right, y along the
pointer, z out of the button face) where KPAD's are x left, y out of the
button face, z along the pointer ((0, -1, 0) held level, KPADReset),
which the game then remaps (WPadAcceleration::update).  A level
controller read as a remote standing on end: the surfing coach's "Point
your Wii Remote at the screen" never passed, and the Star Ball would
roll forward at full speed.

- wpad.cpp adds gravity in KPAD's axes.  While the game steers by tilt
  (port_input_use_tilt), it follows the right controller pitched up by
  the game's neutral angle, so a level controller gives the grip the
  game asks for; a controller whose orientation is unknown counts as
  level.  PortPadState carries which way is down from the controller
  (xr_app.cpp) and, as accelerations, only the motion on top (shakes).
- Star Ball: neutral 80 degrees (90 - sCoreBaseDegreeYZ), not 100.  Its
  tutorial reads the tilt only while it checks that the remote is
  raised, so aiming near the sign never counts as a shake.
- PETARI_INPUT scripts tilt the controller with PITCH= and ROLL=.
- docs/CONTROLS.md: the in-game instructions in controller terms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:21:10 +02:00

110 lines
4.4 KiB
C

// Scripted controller input for tests (PETARI_INPUT), shared by the headless
// launcher and the VR app: comma-separated "<t>[-<t1>]:<item>|<item>..."
// entries (seconds since boot; a single time holds for 0.15 s). Items: A B 1
// 2 PLUS MINUS HOME Z C UP DOWN LEFT RIGHT, SPIN (remote shake), SX=<f>
// SY=<f> (stick), PX=<f> PY=<f> (pointer), PITCH=<deg> ROLL=<deg> (the right
// controller's tilt from level: pitched up, then rolled right; negative:
// down, left).
// Example: PETARI_INPUT="22:A,26:A,30-33:SY=1|A"
#pragma once
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "port/input.h"
typedef struct PortInputEvent {
int t0, t1; // ms
uint32_t buttons;
int spin, hasStick, hasPointer, hasTilt;
float sx, sy, px, py, pitch, roll;
} PortInputEvent;
static const struct {
const char* name;
uint32_t bit;
} kPortScriptButtons[] = {{"LEFT", 0x0001}, {"RIGHT", 0x0002}, {"DOWN", 0x0004}, {"UP", 0x0008}, {"PLUS", 0x0010},
{"2", 0x0100}, {"1", 0x0200}, {"B", 0x0400}, {"A", 0x0800}, {"MINUS", 0x1000},
{"Z", 0x2000}, {"C", 0x4000}, {"HOME", 0x8000}};
static inline int portParseInputScript(const char* spec, PortInputEvent* out, int max) {
int n = 0;
static char buf[16384];
snprintf(buf, sizeof(buf), "%s", spec);
char* save = NULL;
for (char* tok = strtok_r(buf, ",", &save); tok && n < max; tok = strtok_r(NULL, ",", &save)) {
char* colon = strchr(tok, ':');
if (!colon) {
fprintf(stderr, "input script: bad entry '%s'\n", tok);
continue;
}
*colon = 0;
PortInputEvent e;
memset(&e, 0, sizeof(e));
char* dash = strchr(tok, '-');
e.t0 = (int)(atof(tok) * 1000.0);
e.t1 = dash ? (int)(atof(dash + 1) * 1000.0) : e.t0 + 150;
char* s2 = NULL;
for (char* item = strtok_r(colon + 1, "|", &s2); item; item = strtok_r(NULL, "|", &s2)) {
if (!strncmp(item, "SX=", 3)) {
e.hasStick = 1, e.sx = (float)atof(item + 3);
} else if (!strncmp(item, "SY=", 3)) {
e.hasStick = 1, e.sy = (float)atof(item + 3);
} else if (!strncmp(item, "PX=", 3)) {
e.hasPointer = 1, e.px = (float)atof(item + 3);
} else if (!strncmp(item, "PY=", 3)) {
e.hasPointer = 1, e.py = (float)atof(item + 3);
} else if (!strncmp(item, "PITCH=", 6)) {
e.hasTilt = 1, e.pitch = (float)atof(item + 6);
} else if (!strncmp(item, "ROLL=", 5)) {
e.hasTilt = 1, e.roll = (float)atof(item + 5);
} else if (!strcmp(item, "SPIN")) {
e.spin = 1;
} else {
int found = 0;
for (size_t i = 0; i < sizeof(kPortScriptButtons) / sizeof(kPortScriptButtons[0]); i++) {
if (!strcmp(item, kPortScriptButtons[i].name)) {
e.buttons |= kPortScriptButtons[i].bit;
found = 1;
}
}
if (!found) fprintf(stderr, "input script: unknown item '%s'\n", item);
}
}
out[n++] = e;
}
return n;
}
// Applies the entries active at `ms` since boot to `pad`; returns nonzero if
// any was.
static inline int portApplyInputScript(const PortInputEvent* ev, int count, int ms, PortPadState* pad) {
int active = 0;
for (int i = 0; i < count; i++) {
const PortInputEvent* e = &ev[i];
if (ms < e->t0 || ms >= e->t1) continue;
active = 1;
pad->buttons |= e->buttons;
if (e->hasStick) pad->stickX = e->sx, pad->stickY = e->sy;
if (e->hasPointer) pad->pointerValid = 1, pad->pointerX = e->px, pad->pointerY = e->py;
if (e->hasTilt) {
// Down in the controller's axes (x right, y up, z where it aims).
float p = e->pitch * 3.14159265f / 180.0f, r = e->roll * 3.14159265f / 180.0f;
pad->tilted = 1;
pad->downX = cosf(p) * sinf(r);
pad->downY = -cosf(p) * cosf(r);
pad->downZ = -sinf(p);
}
if (e->spin) {
// Alternating spikes, like the XR layer's shake.
float s = ((ms - e->t0) / 16) & 1 ? 2.5f : -2.5f;
pad->accX = s;
pad->accY = s;
}
}
return active;
}