Skip to content

feat: add the horse mode for a locked full-screen view - #127

Open
oglassdev wants to merge 7 commits into
feat/fullscreen-inputfrom
feat/fullscreen-horse
Open

oglassdev wants to merge 7 commits into
feat/fullscreen-inputfrom
feat/fullscreen-horse

Conversation

@oglassdev

Copy link
Copy Markdown
Contributor

Closes #120
Part of #115

Adds horse mode, the locked full-screen view from #114 ("Horse cursor"), ported from the prototype.

How it works

  • View lock (Minestom): a new MinestomFullscreenHorse spawns two client-only entities. The first is an invisible bat at the eye, which the camera spectates. The second is an invisible, tamed, saddled horse with no AI, speed 0 and jump 0, which the player rides. The horse wears copper horse armour whose equippable asset is the pack's fullscreen/horse_cursor. On close: the camera returns to the player, the player dismounts, and both entities are destroyed.
  • Cursor (pack): horse packs get a generated core/entity.vsh (vanilla 26.2 plus one branch), an equipment asset, and an armour texture: the signature texel at (0, 0), then the built-in cursor. Equipment layers (NO_OVERLAY with PER_FACE_LIGHTING) whose texel (0, 0) matches the signature recover yaw and pitch from the head-top corners. They draw that face as the cursor with the include's window_fs_cursor_at, at the cursor layer's depth, and hide every other vertex. window_fs_ndc and window_fs_depth are shared with text.vsh through one Rust snippet, and text.vsh stays byte-identical.
  • Contract: fullscreen::horse holds the offset, signature and asset. The manifest/Kotlin FullscreenCanvasEntry gains an optional horse entry (offset, armour asset id), present only when a horse window exists.
  • Runtime: removed the horse refusal. FullscreenDefinition.cursor is null in horse mode, so no text cursor run is sent.

Design choices

  • Cursor and hit test agree: the client copies the rider's yaw and half their pitch onto the horse (AbstractHorse.getRiddenRotation, checked in 26.2 bytecode). The shader doubles the recovered pitch and calls the same window_fs_cursor_at as the text shader. The server maps the player's own rotation packets with FullscreenMapping, as in the unlocked mode. Hover stays server-driven: outlines are re-sent when the hovered control changes.
  • Horse placement: the horse is at (0, −1.75, 3) from the eye instead of the prototype's (0, −1, 3). It stays in view at every FOV, so it is never frustum-culled, but it is below the camera's line of sight. Otherwise the client's pick ray (from the bat's eye) could hit the horse instead of the interaction entity: clicks would be order-dependent, or become misses with a cooldown.
  • Spawn order: the bat and horse spawn on the tick after open, right after fake survival. A spectator client would draw the invisible horse as a translucent ghost. So the prototype's pack-wide "hide spectator ghost alpha" hack is not needed and not ported.
  • HUD: the world-age gate stays. In 26.2 the contextual bar (here the horse's jump bar or the XP bar), the XP level, the selected item name and the crosshair still draw with a non-player camera. The max-health and absorption clamps are skipped in horse mode: hearts (the player's and the vehicle's) draw only for a player camera. The blank hotbar stays (the hand and item name). An empty action bar replaces vanilla's "Press Left Shift to Dismount" hint.
  • Edge pull-back: kept unchanged. The cursor still clamps at the edges, so without pull-back an overshoot would have to be undone before the cursor moves. In this mode the correction only re-aims the horse: the camera is the bat, so the snap-back seen in the unlocked mode is not visible.

Files touched outside the new ones

MinestomFullscreen.kt (create the horse, lock it on the fake-survival tick, unlock it on restore, skip the heart clamp), FullscreenSession.kt (removed the horse check), FullscreenDefinition.kt (cursor is null in horse mode), FullscreenHost.kt/FullscreenView.kt (docs), FullscreenEntries.kt (HorseEntry), hud/renderer/fullscreen.rs (extracted the placement snippet), hud/mod.rs (horse_entity_shader), pipeline/fullscreen.rs, manifest, codegen, the fullscreen golden.

Checks

  • just test-rust -p window-core fullscreen: 17 passed. New: armour texture, and horse-mode pipeline emission (shaders, equipment, texture, manifest round trip, pack_format 84 rejected).
  • just gradle :runtime:test --tests '*Fullscreen*' and :minestom:test --tests '*Fullscreen*': pass. New: horse mode sends no cursor run and hit-tests the same rotation; horse lock, unlock and no heart clamp.
  • just golden: only fullscreen changed. Three new files (entity.vsh, the equipment JSON, the armour PNG) plus debug.json and WindowFullscreenEntries.kt. text.vsh, the include and the HUD shaders are unchanged, so unlocked-only packs are byte-identical.
  • glslang: entity.vsh with its includes inlined compiles for all 32 combinations of PER_FACE_LIGHTING, NO_OVERLAY, EMISSIVE, NO_CARDINAL_LIGHTING and APPLY_TEXTURE_MATRIX. A deliberately broken copy fails, so the check catches errors.
  • just lint: clean.

Not verified in a real client

  • The whole mode on this branch. The shader math is ported verbatim from the hand-tested prototype, but the new horse offset, fake survival (the prototype used real spectator mode) and the armour texture layout have not been run.
  • The pitch is off for about half a second after opening, while the camera's eye height settles from the player's to the bat's (as in the prototype).
  • The armour signature texel is reserved pack-wide.

The camera spectates an invisible client-only bat at the eye while the
player rides a client-steered horse wearing the pack's cursor armour. A
generated core/entity vertex shader recognises the armour, recovers the
rotation from the head's top face, and draws the cursor with the
include's mapping. The server hit-tests the player's rotation as in the
unlocked mode.

Closes #120
… cannot move it

The shader reads a head corner's position as the horse's rotation, so any
drift of the horse reads as a turn. A ridden horse still runs the client's
fluid travel: floatInWaterWhileRidden lifts it 0.04 blocks a tick and
currents push it, whatever its speed, jump strength, and gravity are. In a
real 26.2 client the cursor vanished with water around the horse.

A passenger is put back on its vehicle after each of its own ticks, and
nothing moves an item display. The horse now rides one at its offset.
…rners

Takes the head pivot and top face into shared constants and tests a Rust
twin of the shader's recovery: against corner positions produced by the
client's own armour model and render transforms, and across the whole
canvas against the server's cursor mapping. The twin's single-precision
error is under 0.0002 degrees, far inside the snap epsilon.
…her instance

Replace the delayed transition watch and deferred close callback. Death closes
the window through the normal path, a spawn closes it only when the instance
differs from the one it opened in, and opening on a dead or respawning player
fails fast.
@oglassdev
oglassdev changed the base branch from feat/fullscreen-session to feat/fullscreen-input October 16, 2026 00:08
@oglassdev
oglassdev marked this pull request as ready for review October 16, 2026 00:08
@oglassdev
oglassdev added this pull request to stack #130 October 16, 2026 00:26
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.

1 participant