Fix menu cursor auto-move
This commit is contained in:
@@ -2727,6 +2727,25 @@ deferred ReadTextDB work should remain a seam rather than a storage dependency.
|
||||
names/comments the cursor, callback dispatch, retained-history navigation/render/metadata, and history-voice
|
||||
opcode paths.
|
||||
|
||||
### Menu default cursor auto-move (2026-08-16)
|
||||
|
||||
The menu option stored in `G[0x3238]` is `menu_cursor_auto_move_enabled`. `INITCONFIG.BIN` defaults it to
|
||||
one and `CONFIG.BIN` writes the paired enabled/disabled values. At `BUNKI.BIN@0x2b6` and
|
||||
`SBUNKI.BIN@0x289`, an enabled value makes the script compute a point in the default choice row and invoke
|
||||
`set-cursor-virtual` (`0x10a`); the disabled path instead samples the existing physical position through
|
||||
`get-cursor-virtual` (`0x109`). `BUNKIMOVE` and `SBUNKIMOVE` use the same toggle while animating the menu.
|
||||
The highlighted default row and the physical pointer are therefore two consequences of the same script
|
||||
path, not one frontend-owned selection operation.
|
||||
|
||||
The `/v2` handler `op_0x10a_set_cursor_virtual@0x421590` converts the client origin to screen space,
|
||||
applies active VirtualFullScreen scale/letterbox geometry, and calls USER32 `SetCursorPos`. The port now
|
||||
preserves its immediate virtual-pointer update and also forwards a cursor-warp request to the interactive
|
||||
host. Godot marshals that request onto its main thread, inversely maps the 800x600 AGE coordinate into the
|
||||
current viewport, and calls `Viewport.WarpMouse`. Godot supports that operation on the port's Windows,
|
||||
macOS, and Linux desktop targets; it is documented as a no-op on Android, iOS, and Web, where the VM's
|
||||
logical position still remains correct. A synthetic opcode regression covers both the VM position and host
|
||||
request. The Ghidra handler annotation was updated and the `/v2` program saved on 2026-08-16.
|
||||
|
||||
### ADV Hide Window implementation (2026-07-18)
|
||||
|
||||
The x=772 callback now follows the original control flow rather than a Godot-only visibility shortcut.
|
||||
|
||||
@@ -3730,6 +3730,7 @@
|
||||
| `0x6cb` | adv_hover_message_skip | high | investigation | Pointer-hover flag for the standard ADV all-message Skip button at (728,572). See adv_hover_history. |
|
||||
| `0x6cc` | adv_hover_read_message_skip | high | investigation | Pointer-hover flag for the standard ADV read-message-only Skip button at (750,572). See adv_hover_history. |
|
||||
| `0x6cd` | adv_hover_hide_window | high | investigation | Pointer-hover flag for the standard ADV Hide-window button at (772,572). See adv_hover_history. |
|
||||
| `0x3238` | menu_cursor_auto_move_enabled | high | investigation | Persistent game option controlling whether a newly opened BUNKI/SBUNKI choice menu moves the physical cursor to its default row. INITCONFIG defaults it to 1; CONFIG writes 1/0 for the paired choices. BUNKI@0x2b6 and SBUNKI@0x289 call set-cursor-virtual when enabled and sample the existing cursor with get-cursor-virtual when disabled; BUNKIMOVE/SBUNKIMOVE use the same toggle during menu entrance motion. Not a scene story flag. |
|
||||
| `0x62425` | adv_hide_window_enabled | high | investigation | Native ADV-scheduler permission for the standard Hide Window action. After op 0x199 enters the registered yield-A handler, every standard ADV scene calls HIDEWIN.BIN only while this value is nonzero. No script writes it and the complete boot-to-SC0000 VM-write capture does not contain it, so it is native-owned inherited state rather than saved-game or script boot data. The Godot scene bootstrap mirrors the original enabled value 1. |
|
||||
| `0x15a096` | information_message_handled | high | investigation | INFOMES clears this before walking handler rows and stops when it becomes one. CIMES, EIMES, and VIMES set it after rendering a matching character, enemy, or glossary message, implementing a first-handler-wins extension chain. |
|
||||
|
||||
@@ -3737,7 +3738,6 @@
|
||||
|
||||
| address | name | conf | source | usage |
|
||||
|---|---|---|---|---|
|
||||
| `0x3238` | — | low | inference | Config/settings global (CONFIG.BIN/INITCONFIG.BIN writer, scene-reach 0) — NOT a scene story flag; miner over-tagged it. Branch-read in 5 scripts; compared against [1]. |
|
||||
| `0x3301` | — | low | inference | Config/settings global (INITCONFIG.BIN writer, scene-reach 0) — NOT a scene story flag; miner over-tagged it. Branch-read in 6 scripts; compared against [1, 2]. |
|
||||
| `0x3303` | — | low | inference | Config/settings global (CONFIG.BIN/INITCONFIG.BIN writer, scene-reach 0) — NOT a scene story flag; miner over-tagged it. Branch-read in 8 scripts; compared against [1]. |
|
||||
| `0x3304` | — | low | inference | Config/settings global (CONFIG.BIN/INITCONFIG.BIN writer, scene-reach 0) — NOT a scene story flag; miner over-tagged it. Branch-read in 13 scripts; compared against [1]. |
|
||||
|
||||
@@ -1282,7 +1282,7 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it "
|
||||
### 0x10a `set-cursor-virtual` (u0041E540, argc 2)
|
||||
- **summary:** (x)(y) - convert AGE virtual-screen coordinates to client/screen coordinates and move the OS cursor.
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
- **evidence:** Ghidra /v2: op_0x10a_set_cursor_virtual@0x421590 maps virtual coordinates through the active VirtualFullScreen geometry and calls SetCursorPos. SC0000 alternates the cursor by one vertical pixel after state-changing ADV button clicks so the hover state re-enters cleanly.
|
||||
- **evidence:** Ghidra /v2: op_0x10a_set_cursor_virtual@0x421590 maps virtual coordinates through the active VirtualFullScreen geometry and calls USER32 SetCursorPos. BUNKI@0x2b6 and SBUNKI@0x289 test G[0x3238], compute the default choice point, and call 0x10a when menu cursor auto-move is enabled; their disabled branches retain the physical position through 0x109. SC0000 also alternates the cursor by one vertical pixel after state-changing ADV button clicks so hover re-enters cleanly.
|
||||
|
||||
### 0x10b `map-mouse-button` (u0041E5A0, argc 2)
|
||||
- **summary:** (button_slot)(physical_button) - map a physical mouse button to a logical button slot; polling emits the slot as logical action slot+4.
|
||||
|
||||
@@ -47,6 +47,25 @@ public class InputBindingTests
|
||||
Assert.NotEqual(0, vm.InputBindings.JoystickButtonActionMask(3) & (1 << 4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetCursorVirtualUpdatesVmCoordinatesAndRequestsAHostWarp()
|
||||
{
|
||||
var script = ScriptAssembler.Assemble(Table, "CURSOR_WARP", new List<(int, Operand[])>
|
||||
{
|
||||
(0x10a, new[] { I(321), I(234) }),
|
||||
(0x109, new[] { G(0x710), G(0x711) }),
|
||||
(0x2, Array.Empty<Operand>()),
|
||||
}, Array.Empty<string>());
|
||||
var host = new RecordingHost();
|
||||
var vm = new VirtualMachine(script, Table, host);
|
||||
|
||||
vm.Run();
|
||||
|
||||
Assert.Equal((321, 234), Assert.Single(host.CursorWarps));
|
||||
Assert.Equal(321, vm.Globals.GetValueOrDefault(0x710));
|
||||
Assert.Equal(234, vm.Globals.GetValueOrDefault(0x711));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void System4BootstrapReplaysAllSixteenInputConfigurationCalls()
|
||||
|
||||
@@ -76,6 +76,7 @@ internal class RecordingHost : IHost
|
||||
public readonly List<FullwidthTextEditRequest> FullwidthTextEdits = new();
|
||||
public System.Func<FullwidthTextEditRequest, FullwidthTextEditResult>? OnFullwidthTextEdit;
|
||||
public readonly List<long> CursorResources = new();
|
||||
public readonly List<(int X, int Y)> CursorWarps = new();
|
||||
public readonly List<bool> AdvPagePresentationSuspended = new();
|
||||
public readonly List<(AdvLiveTextRun Run, int GlyphDelayMilliseconds)> LiveTextRuns = new();
|
||||
public readonly Dictionary<int, RgbaImage> SurfacePixels = new();
|
||||
@@ -176,6 +177,7 @@ internal class RecordingHost : IHost
|
||||
public virtual long InputClockMilliseconds => Environment.TickCount64;
|
||||
public void SetCursorResource(long resourceId) => CursorResources.Add(resourceId);
|
||||
public void ClearCursorResource() => CursorClearCount++;
|
||||
public void WarpCursor(int virtualX, int virtualY) => CursorWarps.Add((virtualX, virtualY));
|
||||
public virtual void Sleep(long duration) => SleptDurations.Add(duration);
|
||||
public virtual void WaitForTimedCallbackDeadline(long duration)
|
||||
{
|
||||
|
||||
@@ -32,6 +32,7 @@ public interface IInputHost
|
||||
|
||||
void SetCursorResource(long resourceId) { }
|
||||
void ClearCursorResource() { }
|
||||
void WarpCursor(int virtualX, int virtualY) { }
|
||||
|
||||
// Native 0x1c7/0x1cc query two distinct ADV skip channels. Headless and non-interactive
|
||||
// hosts default to normal playback; the Godot host supplies the live interactive values.
|
||||
|
||||
@@ -159,8 +159,14 @@ public sealed partial class VirtualMachine
|
||||
return pc + 1;
|
||||
}
|
||||
case "u0041E540":
|
||||
case "set-cursor-virtual": // 0x10a; retain the virtual position even without OS warping
|
||||
UpdatePointer((int)Read(a[0]), (int)Read(a[1])); return pc + 1;
|
||||
case "set-cursor-virtual": // 0x10a: update AGE state immediately, then request the native warp
|
||||
{
|
||||
int x = (int)Read(a[0]);
|
||||
int y = (int)Read(a[1]);
|
||||
UpdatePointer(x, y);
|
||||
_host.WarpCursor(x, y);
|
||||
return pc + 1;
|
||||
}
|
||||
case "u0041E5A0":
|
||||
case "map-mouse-button": // 0x10b: physical button -> slot, polled action is slot+4
|
||||
InputBindings.MapMouseButton(unchecked((int)Read(a[0])), unchecked((int)Read(a[1])));
|
||||
|
||||
@@ -482,6 +482,13 @@ public sealed partial class GodotAdvHost
|
||||
_timeline?.Event("cursor-clear", new());
|
||||
}
|
||||
|
||||
public void WarpCursor(int virtualX, int virtualY)
|
||||
{
|
||||
// Script execution is on the VM thread; Godot input/display APIs belong to the main thread.
|
||||
_main.CallDeferred("WarpAgeCursor", virtualX, virtualY);
|
||||
_timeline?.Event("cursor-warp", new() { ["x"] = virtualX, ["y"] = virtualY });
|
||||
}
|
||||
|
||||
public void WaitForForegroundTransition(GfxState gfx)
|
||||
{
|
||||
_foregroundTransitionWaitBypassed = false;
|
||||
|
||||
@@ -256,6 +256,17 @@ public partial class Main
|
||||
(int)System.Math.Floor(position.Y * _screenHeight / size.Y));
|
||||
}
|
||||
|
||||
public void WarpAgeCursor(int virtualX, int virtualY)
|
||||
{
|
||||
// Viewport.WarpMouse expects viewport coordinates. This is the inverse of ToNativeScreen and
|
||||
// lets Godot account for the platform window origin before moving the desktop pointer.
|
||||
Vector2 size = GetViewport().GetVisibleRect().Size;
|
||||
if (size.X <= 0 || size.Y <= 0 || _screenWidth <= 0 || _screenHeight <= 0) return;
|
||||
GetViewport().WarpMouse(new Vector2(
|
||||
virtualX * size.X / _screenWidth,
|
||||
virtualY * size.Y / _screenHeight));
|
||||
}
|
||||
|
||||
public void SetAgeCursor(byte[] rgba, int width, int height, int hotspotX, int hotspotY)
|
||||
{
|
||||
var image = Image.CreateFromData(width, height, false, Image.Format.Rgba8, rgba);
|
||||
|
||||
@@ -2419,13 +2419,13 @@ depends_on = ["0x3234"]
|
||||
|
||||
[[global]]
|
||||
address = "0x3238"
|
||||
name = ""
|
||||
category = "unknown"
|
||||
name = "menu_cursor_auto_move_enabled"
|
||||
category = "ui-toggle"
|
||||
type = "int"
|
||||
value_domain = "{0,1}"
|
||||
usage = "Config/settings global (CONFIG.BIN/INITCONFIG.BIN writer, scene-reach 0) — NOT a scene story flag; miner over-tagged it. Branch-read in 5 scripts; compared against [1]."
|
||||
source = "inference"
|
||||
confidence = "low"
|
||||
usage = "Persistent game option controlling whether a newly opened BUNKI/SBUNKI choice menu moves the physical cursor to its default row. INITCONFIG defaults it to 1; CONFIG writes 1/0 for the paired choices. BUNKI@0x2b6 and SBUNKI@0x289 call set-cursor-virtual when enabled and sample the existing cursor with get-cursor-virtual when disabled; BUNKIMOVE/SBUNKIMOVE use the same toggle during menu entrance motion. Not a scene story flag."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = []
|
||||
|
||||
[[global]]
|
||||
|
||||
@@ -2426,7 +2426,7 @@ noop_headless = false
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = []
|
||||
evidence = "Ghidra /v2: op_0x10a_set_cursor_virtual@0x421590 maps virtual coordinates through the active VirtualFullScreen geometry and calls SetCursorPos. SC0000 alternates the cursor by one vertical pixel after state-changing ADV button clicks so the hover state re-enters cleanly."
|
||||
evidence = "Ghidra /v2: op_0x10a_set_cursor_virtual@0x421590 maps virtual coordinates through the active VirtualFullScreen geometry and calls USER32 SetCursorPos. BUNKI@0x2b6 and SBUNKI@0x289 test G[0x3238], compute the default choice point, and call 0x10a when menu cursor auto-move is enabled; their disabled branches retain the physical position through 0x109. SC0000 also alternates the cursor by one vertical pixel after state-changing ADV button clicks so hover re-enters cleanly."
|
||||
|
||||
[[opcode.semantics.args]]
|
||||
i = 1
|
||||
|
||||
Reference in New Issue
Block a user