diff --git a/docs/re/captures/tutorial-hud-target-select.png b/docs/re/captures/tutorial-hud-target-select.png new file mode 100644 index 0000000..7176997 Binary files /dev/null and b/docs/re/captures/tutorial-hud-target-select.png differ diff --git a/docs/re/flight-controls-runtime.md b/docs/re/flight-controls-runtime.md index b1b56bc..8a3a596 100644 --- a/docs/re/flight-controls-runtime.md +++ b/docs/re/flight-controls-runtime.md @@ -72,9 +72,26 @@ the craft's response to a given stick deflection is configurable. Any calibratio against one profile (e.g. the `ctrl_probe.py` throttle numbers) is only valid for the save's current settings. -**Next:** bind physical buttons to actions 9–14 by watching the *reticle and target -panel* (not the ammo counters) — and once `Change Target` and `Padlock` are identified, -re-run the missile test with a locked target. +## What the tutorials state outright + +`tutorial_capture.sh ` plays one lesson and photographs it. Captions +use a typewriter effect, so crop `900x125+160+40` from many frames to read a full +sentence. Lessons that require the player to *do* something stall (BASIC CONTROLS sits +on "Go to the box on your screen" forever with nobody flying); the expository ones run +on their own. + +- **HEADS-UP DISPLAY (index 1):** *"Enemies are displayed with **red markers** and allies + with **blue markers**." · "Targeting an enemy displays an Armor Gauge…" ·* **"Press Ⓐ + twice to target the enemy closest to the center of the screen."** +- **ADVANCED CONTROLS (index 5):** `B`+`LS` = Side Roll / 180 Degree Turn / Level Off · + `B`+`A` together = face the target · `LT`+`RT` together = *"sets your fighter's speed + to that of the target… works well when you are trying to get behind an enemy. Once + behind an enemy, this also helps you attack them."* + +**`Change Target` is Ⓐ pressed TWICE** — a double tap. That is why every button sweep in +this document found nothing and why I wrongly concluded targeting was automatic: each +sweep tapped once. It also explains the missiles — `GuidanceType 5` needs the *game's* +selection, and the loop had never made one, so 98 launches guided to nothing. ## Notes for the reimplementation diff --git a/tools/re-capture/pilot.py b/tools/re-capture/pilot.py index 2df9a21..92aef8b 100644 --- a/tools/re-capture/pilot.py +++ b/tools/re-capture/pilot.py @@ -159,6 +159,15 @@ class Pilot: # "Press B and A together to face [the target]" — a snap turn, far quicker # than winding the PD controller around for a contact behind us. FACE_MIN = math.radians(999) # 50 deg to re-enable the B+A snap turn + # HEADS-UP DISPLAY tutorial, verbatim: "Press A twice to target the enemy + # closest to the center of the screen." A DOUBLE tap — which is why every + # single-tap button sweep found nothing and concluded targeting was + # automatic. It also explains the missiles: GuidanceType 5 needs the GAME's + # selection, and we had never made one, so 98 launches guided to nothing. + # Select only when our committed contact is already near screen centre, so + # the game's choice and ours are the same object. + SELECT_CONE = math.radians(14) + SELECT_PERIOD = 3.0 FACE_PERIOD = 4.0 def __init__(self, W, pad, dry=False, log=sys.stdout): @@ -193,6 +202,8 @@ class Pilot: self.face_t = -1e9 self.face_down = None self.faces = 0 + self.select_t = -1e9 + self.selects = 0 def f32(self, off): b = os.pread(self.W.fd, 4, off) @@ -254,6 +265,15 @@ class Pilot: self.pad.trig("LT", 1.0 if want < 0 else 0.0) self.throttle = want + def select_target(self, t): + """A, twice: make the GAME target what we are already pointing at.""" + if self.dry or t - self.select_t < self.SELECT_PERIOD: + return + self.pad.f.write("tap A 90\n") + self.pad.f.write("tap A 90\n") + self.select_t = t + self.selects += 1 + def face_target(self, t): """B + A: snap the nose onto the selected target.""" if self.dry or t - self.face_t < self.FACE_PERIOD: @@ -556,6 +576,11 @@ class Pilot: if fire != self.firing: (self.pad.press if fire else self.pad.release)("RB") self.firing = fire + if (tgt and self.mode in ("ENGAGE", "DEFEND") + and abs(aim[0]) < self.SELECT_CONE + and abs(aim[1]) < self.SELECT_CONE + and tgt[4] < MISSILE_RANGE and pn < 1.2): + self.select_target(t) if self.face_down is not None and t - self.face_down > 0.2: self.pad.release("B") self.pad.release("A") @@ -577,7 +602,7 @@ class Pilot: f"thr={str(self.throttle):>5} yaw={math.degrees(yaw):+6.1f} " f"pit={math.degrees(pitch):+6.1f} aim={math.degrees(aim[0]):+6.1f}" f"/{math.degrees(aim[1]):+6.1f} fire={int(fire)} msl={self.missiles}" - f" fc={self.faces}") + f" fc={self.faces} sel={self.selects}") if ast is not None: msg += f" ast={a_frac*100:5.1f}%" if a_dmg > self.ASSET_ALERT: