Skip to content

first person: the view at Link's eyes, as a menu option - #2567

Open
kafasam wants to merge 8 commits into
NotRexed:mainfrom
kafasam:first-person
Open

kafasam wants to merge 8 commits into
NotRexed:mainfrom
kafasam:first-person

Conversation

@kafasam

@kafasam kafasam commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

A walk-around first-person view, as a row in the Insert menu's Controls tab (option 47, first_person in hookshot_options.ini; HOS_FIRST_PERSON=1 for one run).

It is a constrained free camera rather than a camera patch: the shipped free camera's hook in the shared view-matrix routine (f_804e48c0, patch_view) already replaces the gameplay view's matrix after the game stores it, and the game's own frustum culling reads the replacement back the same frame. This adds hos_first_person(c, view) beside it, which takes the frame's eye->target direction (so yaw, pitch, mouse and lock-on easing all keep working — no angle-convention math, no feedback loop into the camera FSM) and moves the eye to the player actor's origin + HOS_FP_UP (1.5 m, adult; a child's eyes are nearer 1) + a small forward offset (HOS_FP_FORWARD, 0.15 m). The camera pointer comes from the f_80644c80 hook that mouse_native.c already publishes; liveness is the camera hook setting a flag the view hook consumes, plus the event-playing test, so cutscenes, shops and the game's own aim camera take the view back untouched.

Known limits, v1 (documented in the readme too):

  • the view inherits the game camera's angles, including its rest tilt down; the eye is not checked against walls
  • a recentering (Z tap) or a targeting/climb/swim state holds the view briefly, then steps out to third person until the state passes — safe, visible
  • the option id is 47 (46 went to the prompts row while this was in the works)

Two sliders are planned as a follow-up commit on this branch (eye height and forward offset, live in the menu) along with a smoother handover when the game's own first-person aiming takes the view.

Tested on an RX 6700 XT (RDNA2), Windows: walking, running, right-stick and native-mouse look, lock-on steering, cutscene fallback. Single commit on current main; no other PR's changes carried.

hckrman101 added a commit to omgitsgela/Hookshot-SteamDeck that referenced this pull request Nov 11, 2026
vk_api.h: main added HOS_OPT_FOV (51) next to the 49/50 reserved for NotRexed#2345.
The Keyboard row moves to 54 (49 is NotRexed#2345's; NotRexed#2345 and NotRexed#2567 now use 52/53).
Hookshot Builder added 5 commits November 11, 2026 19:08
two rows in Controls beside the switch (options 52 and 53, past main's
FOV 51 and its reserved 49 and 50), in hundredths of a metre: height
50 to 250, offset 0 to 100, steps of 5 — the camera speed row's own
mechanics (is_slider, stops at ends, hookshot_options.ini by the row's
name). the view reads them at every frame, so a slider shows at once;
HOS_FP_UP and HOS_FP_FORWARD still win for a run.
aiming (the slingshot, the bow, the hookshot) draws the game's own
first person camera, whose view runs the same matrix routine our hook
sits on. it is not camera zero's, so the gameplay-view check stood it
down already — untouched, every frame of the aim. what the playtest
saw was the moments around it: the 300 ms hold of the last good view
(the recenter's, state 2) fought the aim's first frames and flashed
the view from before the aim as it let go. a state not the view's
(0, the aim and shop park) now ends the held view, and while another
camera's view ran the routine within the hold's span the hold is
dropped too. tests/test_first_person_view.py drives the compiled
hook through the handover.
the view's direction is the game's own eye-to-target line, which at
rest carries the follow camera's downward angle at link — a bias at
his eyes, not a view. the pitch is lifted toward level by a
conservative correction before the matrix is built (0.12 radians,
about seven degrees; HOS_FP_PITCH over it, no row of its own), clamped
at the camera's own up limit (+0x178) so it never points above what
the game would show. the held view carries the corrected pitch too.
the dial-in is the rows' job; this only takes the systematic part.
Hookshot Builder added 3 commits November 11, 2026 19:48
The rebase of v2 onto main dropped nx2/oot_patches.py out of "the view at
link's eyes beside the free camera's hook" (473c2de carries every other file
of that commit, but its oot_patches diff is gone, and the original 2ff3c8f0 on
first-person-mode has it): patch_view went back to inserting the free camera's
hook alone, so nothing ever called hos_first_person — the view, and with it the
head hide's host answer, dead in a build of this branch — and the branch's own
tests/test_first_person_anchor.py failed at its tip.

Restored as the original had it, on main's counting (report's done, the counts
key): first person's hook after the free camera's, each hook and declaration
only when absent, so a lift the shipped free-camera patch touched takes it and
a second run takes nothing.
hckrman101 pushed a commit that referenced this pull request Nov 12, 2026
**What this is for:** resume Native mouse look from the game's current
camera angles after another camera takes over ([current main
CI](https://github.com/NotRexed/Hookshot/actions/runs/43151254256)).
**What is measured:** the affected game-free check before and after,
with GCC 16.2.1 and Clang 23.1.3 selected, plus one strict full-suite
run, as recorded below. **Not measured:** a game run or Windows
execution.

## Cause

#2617 retains mouse target angles to prevent drift, but the existing
inactive-state guard returns without invalidating that target; the
pre-existing handover test then applies movement to old yaw 5 instead of
current yaw 0 (`host/mouse_native.c:120-148`,
`tests/test_mouse_native.py`).

The relevant failure in [main's CI
log](https://github.com/NotRexed/Hookshot/actions/runs/43151254256) is:

```text
AssertionError: 1 != 0 : FAIL line 134: near(getf(YAW), -1.0f) (pitch 10 yaw 4)
```

## What changed

Clear `target_ok` when the player camera is inactive; keep immediate
stick handback and persistent angles while active. Preserve the original
failing yaw assertion, exercise changed pitch and yaw on return to both
active states, and document the handover in the readme.

## How it was tested

Recorded on 2026-11-11, Linux kernel `7.2.15-1-omarchy`, base
`de866017`, head `58c817f7` (version and test records).

- Untouched main `a016b7a6`: the affected check fails independently in
both compiler environments with the diagnostic above; the branch passes
independently in both (`python -I -B tests/run_all.py --strict --jobs 1
tests/test_mouse_native.py` records).
- Removing only the target invalidation from an isolated copy restores
the wrong-yaw failure with both compilers (two mutation records); the
original no-drift and stick-handover assertions remain.
- One full `python -I -B tests/run_all.py --strict`: **`456 passed, 14
failed, 20 skipped in 178 s (linux, 32 at once)`**, load 33.42 → 81.38
(complete suite output).
- That run's remaining failures are: `test_wayland_libdecor_hint.py`,
`test_display_startup_failure.py`, `test_texel_buffer_ownership.py`,
`test_audio_aux.py`, `test_audio_biquad.py`, `test_dlss_checks.py`,
`test_env_on.py`, `test_framegen_preset.py`, `test_hdr_encode.py`,
`test_linux_docker_packages.py`, `test_menu_i18n.py`,
`test_mods_size_table.py`, `test_readback_part.py`,
`test_rumble_menu.py` (complete suite output). The companion fixture,
capture-switch, PPA-parser and hint-catalog branches address their
separate causes; #2614 owns the HDR check and is not duplicated here.

## Not tested / risks

The executable fixture runs the actual camera function against authored
state, but no game, real mouse/controller, Windows or camera-transition
playtest was run.

## Overlaps

The inspected #2567 first-person changes and #2630 camera-centering
restoration do not contain this handover fix and merge cleanly in the
recorded comparison.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <noreply@openai.com>

Co-authored-by: GPT-6 Astra <noreply@openai.com>

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the workflows haven't run, I merged this into current main and ran tests/run_all.py: 429 passed and 1 failed. The failure is tests/test_lift_cache.py, which fails on main here too. test_first_person_anchor/options/view and test_mouse_native pass. The row is off by default and the hook stands down for other cameras, events and the free camera, so players who don't turn it on aren't at risk.

What blocks it:

  • The title "first person: …" fails the Conventional Commit title check. Something like feat(camera): first-person view at Link's eyes would pass, and CI then needs maintainer approval to run.
  • Option ids: you moved to 54 with 52/53 for the sliders, but open PRs already claim them. #2604 takes HOS_OPT_SPRINT = 52, #2669 takes 52 onward, and #2530 takes HOS_OPT_KEYBOARD = 54. Whoever lands second has to renumber, so it's worth agreeing on numbers now.
  • This changes what's on screen, so it needs a screenshot or short clip in the description (CONTRIBUTING).
  • The description still says option 47 and calls the sliders a follow-up, and host/hos_first_person.c:91 still says "option 47". Please bring both up to date.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants