Skip to content

Commit 299dc15

Browse files
carrdylan52claude
authored andcommitted
Port: add mouse to the Character, Options and Help menus (#98)
Character page portraits follow hover and confirm on click; Cedric's repair board takes hover, wheel and item clicks only; the retail Options and Help lists hit their drawn rows inside the list scissor and scroll with the wheel. The newest choice window takes the mouse when two draw at once, the pointer keeps one facing, and --screenshot-fps captures include the menu pointer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent be71a2e commit 299dc15

21 files changed

Lines changed: 1125 additions & 63 deletions

‎docs/PC.md‎

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ darkchronicle [options]
386386
| `--frames N` | exit with status 0 after frame N of the main loop. N is a positive integer |
387387
| `--screenshot PATH` | with `--frames`: write the last frame to PATH as a PNG on exit. A failed write exits with status 1 |
388388
| `--screenshot-display N` | with `--screenshot`: also write N interpolated display frames of that frame, as `<stem>-display-<k>.png` for k = 0 to N-1 |
389-
| `--screenshot-fps` | with `--screenshot`: draw the FPS counter into the image, from a presented frame at window size. With `--offscreen`, nothing is presented: the image is read back from the display image instead |
389+
| `--screenshot-fps` | with `--screenshot`: draw the FPS counter and any menu pointer into the image, from a presented frame at window size. With `--offscreen`, nothing is presented: the image is read back from the display image instead |
390390
| `--input SCRIPT` | drive the pads from an input script at start-up. A bad script exits with status 2 |
391391
| `--display-per-tick N` | also render N interpolated display frames per clock tick, discarded. N is a positive integer |
392392
| `--show-fps` | draw the FPS counter on presented frames, headless too |
@@ -683,6 +683,46 @@ pointer immediately. Ordinary dialogue choices share the message adapter;
683683
the rendered confirmation replays and drawn-message unit tests cover that input
684684
path, but campaign dialogue choices still need a dedicated replay.
685685

686+
### Character, Options and Help mouse
687+
688+
On the Character page, hovering a shown portrait (Max, Monica, the Ridepod,
689+
Monster Transform or the travelling townsperson) moves the retail cursor there,
690+
and clicking it confirms; a click beside the portraits does nothing. While a
691+
character change loads, left clicks do nothing. The change question, the
692+
townsperson's View Status/Send back question, their commands and their
693+
questions take clicks on their drawn choices; when a question opens over a
694+
list that still draws its cursor, the newest window takes the mouse. On
695+
Cedric's repair board, hover and the wheel select an item and only a click on
696+
an item confirms, so a stray click cannot spend his points. Waits that take
697+
any button (the opening, the townsperson's speech) still take any click.
698+
699+
Retail mirrors the hand to point left at Max; the mouse pointer is never
700+
mirrored, so its body stays on the same side of the hotspot on every target.
701+
702+
On the retail Options page, hovering a row's label moves the row cursor and keeps
703+
its choice; hovering a choice button also moves the choice. Clicking a button
704+
selects that setting. The PC SETTINGS label opens that page on click.
705+
706+
In Help, hovering an entry moves the cursor and a click opens it. On an entry's
707+
picture pages the wheel turns pages (down for the next) through the retail
708+
Left/Right keys; a click turns to the next page and right click returns, as Cross
709+
and Circle do. Movies keep their retail controls. Keyboard and controller input
710+
is unchanged on all three pages.
711+
712+
The wheel follows one rule on these pages: in a list longer than its window,
713+
each notch scrolls the list one row and the cursor takes the row that comes under
714+
the pointer, or stays in view (`MenuMouseScroll`; the ITEM page's bag scrolls the
715+
same way). Elsewhere it moves the cursor, as in shops and the root menu; the
716+
Character page's portrait map is not a list, so the wheel does nothing there.
717+
Options and Help rows are hit where they are drawn while the list eases to a new scroll, and
718+
only inside the list's scissor (`clip0`): a row sliding under the footer cannot
719+
be clicked, and a visible row above the new scroll still can.
720+
721+
`tools/input/character-mouse.input`, `character-mouse-cedric.input`, `options-mouse.input` (`--jump town:10`)
722+
and `help-mouse.input` (`--jump dungeon:0:1`) replay these at `--window 1920x1080`
723+
with `--fast-load`; their comments give capture frames. `--screenshot-fps`
724+
captures include the menu pointer.
725+
686726
### Mouse follow camera
687727

688728
Mouse motion directly turns the active walking camera around the character. It
@@ -935,10 +975,10 @@ The PAL scripts (`.stb`) hold the NTSC frame counts and convert them while they
935975
- `video.interpolation = false` presents each canonical image once.
936976
- Headless, the clock is unbounded: one canonical render and one present per game frame. `--display-per-tick N` also renders N display frames at k/N and discards them. Screenshots are byte-identical with it (`PresentIdentity.DisplayPerTickScreenshot`).
937977
- Frame grabs: `mgPortRenderTarget(..., frame_target = true)` makes a gfx frame target, which a display render grabs again from its own image.
938-
- The FPS counter: `video.show_fps` turns it on (F3 and the `fps_toggle` host action flip it; headless only with `--show-fps`). It is drawn through `OverlayRecord`, as a display render's overlay, in the overlay's own font (the Dark Cloud Compendium Community Font baked into `overlay_font.inc`), so no canonical image holds it. The game's message font is not used: its texture lives only in the current mode's texture block (`ReLoadFontTexture` per mode) and `FontTex[0]` dangles across mode changes, so a badge in it would come and go with the modes. Its text is set by `video.fps_detail`; with all fields it reads `FPS 143.9 TICK 60.0/60 DRAWS 412`. TICK counts clock ticks per second, so it reads the tick rate whatever `mgFrameRate` is. DRAWS is the newest list's mesh plus 2D draws. `--log-fps` prints the same text on stderr once a second.
978+
- The FPS counter: `video.show_fps` turns it on (F3 and the `fps_toggle` host action flip it; headless only with `--show-fps`). It is drawn through `RecordHostOverlay`, as a display render's overlay, in the overlay's own font (the Dark Cloud Compendium Community Font baked into `overlay_font.inc`), so no canonical image holds it. The game's message font is not used: its texture lives only in the current mode's texture block (`ReLoadFontTexture` per mode) and `FontTex[0]` dangles across mode changes, so a badge in it would come and go with the modes. Its text is set by `video.fps_detail`; with all fields it reads `FPS 143.9 TICK 60.0/60 DRAWS 412`. TICK counts clock ticks per second, so it reads the tick rate whatever `mgFrameRate` is. DRAWS is the newest list's mesh plus 2D draws. `--log-fps` prints the same text on stderr once a second.
939979
- Interpolation diagnostic: set `KZ_INTERPOLATION_TINT=1` to tint draws on the main presentation image (default off). Green is a matched transformed mesh; cyan is an unmatched or pose-excluded mesh carried by the display camera; red is newest-only geometry; blue is flat 2D or a screen effect; yellow is a depth-tested sprite with reprojected corners. Unchanged matched meshes remain green. This classifies geometry, not all its inputs: texture contents, lighting, fog parameters and sprite motion can still be tick-exact. Alpha tests, coverage, depth and blending are retained, so translucent passes can mix tints. Reusable intermediate render targets keep their original colours; their final composites are tinted on the main image. Canonical renders and ordinary screenshots never receive the tint. Use `--screenshot-display 8` to inspect the diagnostic at eight interpolation phases. Optional `KZ_INTERPOLATION_TRACE=<file>` appends per-draw CSV sections at alpha zero, including matching/cut reasons, texture IDs, camera IDs, target IDs, whether a draw is tinted on the main image, and changed fog/material/projection/local inputs. It writes only while tint is enabled; remove both environment variables after diagnosis.
940980
- Outlined models render into a shared transparent image. Canonical clears and copies retain the retail draw rectangle; presentation scratch clears and replacement vertices cover the full visible extent so the interpolated model/bone pose remains complete under the display camera, including fixed-camera mouse look. Each copy preserves its texture-to-screen offset. Fixed-look current and previous lists preserve presentation vertices and clear rectangles while leaving resource retirement ownership with their source lists. Transparent texels in a depth-writing presentation composite do not write depth. GPU regressions compare a rotating thin part at alpha 0.5 against an independently recorded horizontal pose and complete fixed-look outline pixels at alpha 1 and 0.5 against a direct full-extent reference render; translation, scratch-target reuse and transparent-depth regressions remain covered. `tools/input/area-dungeon-chest-turn.input` with `--jump dungeon:0:1 --fast-load --frames 2518 --screenshot chest.png --screenshot-display 8` exercises the unopened chest during a camera turn. For multi-part NPCs, use `--input tools/input/repro-town-npc-outline.input --jump town:10 --fast-load --frames 278 --window 1280x720 --screenshot woman.png --screenshot-display 8`; the woman's face must remain visible throughout the turn. `--input tools/input/repro-clown-outline.input --frames 27230 --window 1280x720 --screenshot clown.png --screenshot-display 8` checks the clown's face during the prologue camera move.
941-
- `--screenshot-display N` writes N display frames of the screenshot's frame, at alpha k/N from the previous list to the last, as `<stem>-display-<k>.png`. `--screenshot-fps` writes the screenshot with the FPS counter drawn into it, at window size, from a presented frame. With `--offscreen`, nothing is presented, and the image is read back from the display image instead. The counter reaches no other written image.
981+
- `--screenshot-display N` writes N display frames of the screenshot's frame, at alpha k/N from the previous list to the last, as `<stem>-display-<k>.png`. `--screenshot-fps` writes the screenshot with the FPS counter and any visible menu pointer drawn into it, at window size, from a presented frame (`RecordHostOverlay`, shared with presentation). With `--offscreen`, nothing is presented, and the image is read back from the display image instead. Neither reaches any other written image.
942982
- `video.max_fps=0` leaves presentation uncapped; a positive cap paces display starts without catching up in bursts. A due logic tick takes priority. FIFO follows the display's synchronization, while mailbox and immediate depend on device support. Interpolation off presents canonical frames at their game cadence; a high display cap does not increase the logic tick rate.
943983

944984
### Packets

‎port/include/stubs/menuchr.hpp‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#pragma once
22
#include <menuchr.hpp>
3+
34
#include "texture_color.hpp"
45

6+
extern CMenuChrCngMenu *ChrChangMenuPt asm("MenuMouse_ChrChange");
7+
58
void PortRetailInitMainCharaBG(int, mgCMemory *, int) asm("MenuChr_InitMainCharaBG");
6-
int PortRetailKeyMainCharaBG() asm("MenuChr_KeyMainCharaBG");
9+
int PortRetailKeyMainCharaBG() asm("MenuChr_KeyMainCharaBG");

‎port/include/stubs/menuop.hpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
extern CSaveMenuClass *SaveMenuPtr asm("MenuMouse_Save");
44
// Let the PC page wrap the original Options step without replacing its state machine.
55
extern CMenuOption *CMenuOptionPtr asm("MenuOption_Current");
6+
// The menu pointer reads the option count and the Help list it scrolls.
7+
extern float config_option_num_i asm("MenuOption_Count");
8+
extern CManualMenu *CManualPtr asm("MenuMouse_Manual");

‎port/src/main.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Options g_options;
6363

6464
[[noreturn]] void Usage(const char *program) {
6565
std::println(stderr,
66-
"usage: {} [--data DIR] [--run-dir DIR] [--save DIR] [--window WxH] [--headless] [--offscreen]\n [--frames N] [--screenshot PATH] [--input SCRIPT] [--display-per-tick N]\n [--show-fps] [--log-fps] [--screenshot-display N] [--screenshot-fps]\n --jump MODE[:ARG] start in town:<map>, dungeon:<n>[:<floor>], event:<id>, title, opening, menu (DC2_JUMP)\n --fast-load shorten loading-screen holds (DC2_FAST_LOAD=1)\n --give ITEM,... give missing items and weapons on arrival (DC2_GIVE)\n --run-dir DIR game root containing data/, port_data/ and saves/\n (default: the per-user chronicletwo app data directory)\n --data DIR override the extracted data/ subdirectory (DC2_DATA)\n --save DIR override the saves/ subdirectory (DC2_SAVE)\n --headless render offscreen (SDL offscreen driver, VK_EXT_headless_surface)\n --offscreen --headless without a Vulkan surface: render to an image only\n --frames N exit after frame N of the game's main loop (N a positive integer)\n --screenshot PATH with --frames: write the last frame to PATH as a PNG on exit\n --screenshot-display N with --screenshot: also write N interpolated display frames of that frame\n beside PATH, as PATH-display-K.png for K = 0..N-1\n --screenshot-fps with --screenshot: draw the FPS counter into the written image\n --input SCRIPT drive the pads named in SCRIPT from an input script\n --window WxH window size in pixels (default: config.json, then the monitor's)\n --display-per-tick N headless: also render N interpolated display frames per tick (discarded)\n --show-fps draw the FPS counter on presented frames, headless too\n --log-fps print the FPS counter's text on stderr every second",
66+
"usage: {} [--data DIR] [--run-dir DIR] [--save DIR] [--window WxH] [--headless] [--offscreen]\n [--frames N] [--screenshot PATH] [--input SCRIPT] [--display-per-tick N]\n [--show-fps] [--log-fps] [--screenshot-display N] [--screenshot-fps]\n --jump MODE[:ARG] start in town:<map>, dungeon:<n>[:<floor>], event:<id>, title, opening, menu (DC2_JUMP)\n --fast-load shorten loading-screen holds (DC2_FAST_LOAD=1)\n --give ITEM,... give missing items and weapons on arrival (DC2_GIVE)\n --run-dir DIR game root containing data/, port_data/ and saves/\n (default: the per-user chronicletwo app data directory)\n --data DIR override the extracted data/ subdirectory (DC2_DATA)\n --save DIR override the saves/ subdirectory (DC2_SAVE)\n --headless render offscreen (SDL offscreen driver, VK_EXT_headless_surface)\n --offscreen --headless without a Vulkan surface: render to an image only\n --frames N exit after frame N of the game's main loop (N a positive integer)\n --screenshot PATH with --frames: write the last frame to PATH as a PNG on exit\n --screenshot-display N with --screenshot: also write N interpolated display frames of that frame\n beside PATH, as PATH-display-K.png for K = 0..N-1\n --screenshot-fps with --screenshot: draw the FPS counter and menu pointer into the written image\n --input SCRIPT drive the pads named in SCRIPT from an input script\n --window WxH window size in pixels (default: config.json, then the monitor's)\n --display-per-tick N headless: also render N interpolated display frames per tick (discarded)\n --show-fps draw the FPS counter on presented frames, headless too\n --log-fps print the FPS counter's text on stderr every second",
6767
program);
6868
std::exit(kExitUsage);
6969
}

0 commit comments

Comments
 (0)