merge origin/main: P5 gate met; keep Q10 answered, do not claim P6

main's P6 row reads 'Looping is blocked on HANDOFF Q10' while main's OWN
HANDOFF.md line 39 marks Q10 answered -- the row was stale, not a decision, so
the resolution keeps the answered status rather than silently un-resolving it.

P5 takes main's line verbatim: that is the human's gate call and not mine.
P6 explicitly does NOT claim the gate -- the same play-test found the SFX mix
wrong, and 'sound on the P5 gate' means the RIGHT sound.
This commit is contained in:
Sylpheed port agent
2026-09-02 16:33:45 +00:00
5 changed files with 264 additions and 21 deletions

View File

@@ -48,12 +48,17 @@ extends RefCounted
## not again until it comes back. That makes it behave exactly like the d-pad,
## which needs no latch because a button already is an edge.
##
## ⚠️ **AUTHORED, NOT MEASURED — and deliberately the conservative half.**
## Whether the real game *repeats* while a direction is held, and how fast, is
## unknown; it is an oracle question. One deflection = one step cannot run away
## and cannot invent a rate. If the game does repeat, this is a difference a
## human will notice as "I have to flick it again", and the fix is a measured
## repeat interval — not a guessed one. Logged for the Decoder as `pad-repeat`.
## ⚠️ ~~AUTHORED, NOT MEASURED — and deliberately the conservative half.~~
## 🔴 **THE GAME DOES REPEAT, and this paragraph predicted its own refutation.**
## It said: *"If the game does repeat, this is a difference a human will notice
## as 'I have to flick it again'."* On 2026-09-02 a human who has played both
## reported exactly that — *"holding only moves one item. In game it actually
## continues to move when holding up/down, just at a medium pace"*.
##
## So one-step-per-deflection is no longer the conservative reading; it is a
## known defect, and keeping it would be choosing a wrong behaviour over an
## approximate one. The repeat is implemented below. **Its RATE is authored and
## its FACT is not** — see `REPEAT_DELAY`.
## ✅ DECODED 2026-09-01, and it replaces an authored value.
##
@@ -88,11 +93,37 @@ const ENTER := 0.61
## smaller number.
const RELEASE := 0.4
## ## 3. A held direction repeats
##
## 🔴 **THE FACT IS REPORTED, THE RATE IS AUTHORED. Do not read the second as
## carried by the first.** A human who has played both said the game repeats at
## *"a medium pace … slow enough to see which item is selected"* — that settles
## THAT it repeats and gives an order of magnitude, nothing more. Nobody has
## measured an interval off the running game, and `pad-repeat` stays open for the
## Decoder.
##
## 📌 **A constant interval is the right SHAPE, and that part is measured.** The
## game digitises the left stick to four direction bits at 61 % deflection
## (`ENTER` above), so it cannot see a deflection magnitude at all — a repeat it
## drives cannot be rate-by-how-far-you-push. That excludes the one alternative
## model, so only the constants are open.
##
## The delay exists so a deliberate single step never repeats by accident: a
## flick to move one item is held for well under 0.4 s.
##
## ⚠️ **These two numbers change how the menu feels and only a human can judge
## them** — the same standing as `ENTER`'s 0.61. Too fast reads as a cursor that
## runs away; too slow reads as the defect this replaces.
const REPEAT_DELAY := 0.40
const REPEAT_INTERVAL := 0.20
## Only the left stick. The triggers are axes too, and latching them here would
## silently swallow input the port does not read yet but might.
const STICK := [JOY_AXIS_LEFT_X, JOY_AXIS_LEFT_Y]
var _latched: Dictionary = {}
var _repeat_direction := 0
var _repeat_clock := 0.0
## Add the joypad buttons the built-in map omits. Returns a human-readable line,
@@ -156,6 +187,53 @@ func accepts(event: InputEvent) -> bool:
return true
## Which way a direction is being HELD right now, as -1 (up), 0 or +1 (down).
##
## 🔴 **Polled at the DEVICE, never through `Input.is_action_pressed`.** `ui_up`
## and `ui_down` are bound to the stick axis at Godot's 0.50 action deadzone,
## while this port steps at the game's measured 0.61. Polling the action would
## repeat throughout the 0.50–0.61 band — the exact band `ENTER` exists to
## exclude — so the repeat would contradict the threshold on the same stick.
## That is the input-map lesson again: assert the device, not the layer above it.
func held_direction() -> int:
# The stick, from the latch `accepts()` already maintains, so the repeat and
# the first step read one state and cannot disagree about hysteresis.
var stick := int(_latched.get(JOY_AXIS_LEFT_Y, 0))
if stick != 0:
return stick
for device in Input.get_connected_joypads():
if Input.is_joy_button_pressed(device, JOY_BUTTON_DPAD_UP):
return -1
if Input.is_joy_button_pressed(device, JOY_BUTTON_DPAD_DOWN):
return 1
if Input.is_key_pressed(KEY_UP):
return -1
if Input.is_key_pressed(KEY_DOWN):
return 1
return 0
## One repeat step, or 0. Call once per frame with the frame's delta.
##
## The FIRST step is not this function's: it comes from the event edge in
## `_unhandled_input`, and the clock below starts from that same frame, so a held
## direction gives one step now and the next only after `REPEAT_DELAY`. A change
## of direction restarts the delay rather than inheriting the old cadence.
func repeat_due(delta: float) -> int:
var direction := held_direction()
if direction == 0 or direction != _repeat_direction:
_repeat_direction = direction
_repeat_clock = 0.0
return 0
_repeat_clock += delta
if _repeat_clock < REPEAT_DELAY:
return 0
# Subtract rather than reset, so the cadence cannot drift with the frame rate
# -- at 140 fps and at 30 fps the same number of steps happen per second.
_repeat_clock -= REPEAT_INTERVAL
return direction
## The pads Godot can see, for the startup line. A run where the human believes
## a controller is connected and Godot disagrees should say so on its own,
## rather than presenting as unresponsive buttons.