Merge pull request #2750 from horizon-rust/refactor/horizon-client-input

🤖 Agent: Merged by Ludovic's AI agent (orchestrator), crate split route (c): head 64c174118 (main merged in, no rebase) passed the full tools/pr_ready.py on the Mac mini (2327 workspace tests, 398 Python, launcher 205; parity --changed 130 run, 0 failed) plus command_exec and save_roundtrip --fresh; verify green.
This commit is contained in:
Ludovic Marie authored and GitHub committed 2026-10-27 21:58:06 +01:00
commit 1feb28c045
24 files changed
+75 -23

No files matched your search

+3 -2
View File
@@ -39,6 +39,7 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
| `horizon-items` | Blocks and items below gameplay: `blocks.rs` (block facts), `items.rs`, `item_data.rs` (components), `enchant.rs`, `cooldowns.rs`, `bundle.rs`, `item_nbt.rs`. Re-exported as `horizon_client::items`... |
| `horizon-command` | Command parsing (Brigadier): the vanilla tree, argument types, selectors, suggestions, the tree's network form. Re-exported as `horizon_client::command` |
| `horizon-client-core` | The client lib's bottom layer: `text.rs`, `lang.rs`, `snbt.rs`, `nbt_path.rs`, `component.rs`, `scoreboard.rs`, `bossbar.rs`, `gamerules.rs`, `block_entity_nbt.rs`, registry/particle/sound tables. Re-exported under the old paths (`horizon_client::text`...) |
| `horizon-client-input` | Key bindings and key names, toggle keys, mouse options, controller settings and glyphs, the clipboard, window options (`keybinds.rs`, `gamepad.rs`, `mouse.rs`, `window.rs`...). Re-exported under the old paths |
| `horizon-world` | Vanilla world data with no gameplay loop: NBT, regions, chunks, `datafix/`, `worldgen/`, `level/` (server-side block behaviour, ticks, redstone), entity physics, items, effects, food, damage |
| `horizon-core` | Simulation primitives: Java `Random`s, `jmath` (HotSpot-exact `Math`), positions, noise, density, paletted storage, `version.rs` (the pinned version) |
| `horizon-protocol` | Network protocol 777: codecs, framing, encryption, `packets/{handshake,status,login,configuration,play}.rs`, `entity_data.rs` |
@@ -114,7 +115,7 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
| Subsystem | Files |
|---|---|
| Game loop, tick, frame | `game.rs`: `fn run`, `impl ApplicationHandler for Game` (`window_event`, `about_to_wait`), `fn tick`, `fn tick_world`, `fn render`; L: `clock.rs`, `window.rs`, `spans.rs` |
| Game loop, tick, frame | `game.rs`: `fn run`, `impl ApplicationHandler for Game` (`window_event`, `about_to_wait`), `fn tick`, `fn tick_world`, `fn render`; L: `clock.rs`, `spans.rs`; `horizon-client-input/src/window.rs` |
| Rendering | L: `gfx.rs` (`Renderer::new`, `Renderer::render`), `gfx/` (cull, occlusion, translucency, upload, timing, lod, weather, stars, end_sky), `*.wgsl`, `particles.rs`, `block_entity_render.rs`, `item_render.rs`, `fog.rs`, `clouds.rs`, `camera.rs`, `first_person.rs`; G: `render_tick.rs`, `item_draw.rs` |
| Chunks, streaming, light | L: `streaming.rs` (worker pool: generate, light, mesh), `world.rs`, `chunk.rs`, `chunk_map.rs`, `light.rs`, `mesher.rs` + `mesher/`, `vanillagen.rs` (binds horizon-world's generator), `lod.rs` + `lod/`, `ticket_levels.rs`; G: `world_loading.rs`, `postprocess.rs`; game.rs `fn update_world` |
| World generation | `crates/horizon-world/src/worldgen/` (`generator*.rs`, `feature_graph.rs`, `structure/`, `carver/`) |
@@ -130,7 +131,7 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
| Integrated server | G: `integrated.rs` (the server thread), `levels.rs` (one level per dimension, `fn tick_server`), `server_travel.rs` |
| Dedicated server | G: `dedicated.rs`, `dedicated/status.rs`; crates `horizon-server-files`, `horizon-server` |
| Save and load | L: `save.rs` (level.dat, regions, playerdata), `entity_nbt.rs`, `options_file.rs`; `horizon-client-core/src/block_entity_nbt.rs`; G: `saving.rs`, `persist.rs` (entities); `crates/horizon-world/src/{region.rs,nbt.rs,datafix/}` |
| Input | game.rs `fn window_event`, `fn key_pressed`, `fn play_mouse`; L: `keybinds.rs`, `keymapping.rs`, `key_names.rs`, `mouse.rs`, `input.rs`, `interaction_input.rs`, `gamepad.rs`, `osk.rs`, `clipboard.rs`; G: `pad.rs`, `pad_menu.rs` |
| Input | game.rs `fn window_event`, `fn key_pressed`, `fn play_mouse`; crate `horizon-client-input` (`keybinds.rs`, `keymapping.rs`, `key_names.rs`, `mouse.rs`, `input.rs`, `interaction_input.rs`, `gamepad.rs`, `clipboard.rs`); L: `osk.rs`; G: `pad.rs`, `pad_menu.rs` |
| Audio | L: `audio.rs` (mixer), `sound_engine.rs`, `sound_events.rs`, `music.rs`, `ambience.rs`; G: `music.rs`, `subtitle_hud.rs` |
| Resource packs | L: `packs.rs` + `packs/`, `pack_screen.rs`, `atlas_sources.rs`, `sprites.rs`, `mipmap.rs`, `model_library.rs`, `font.rs`; `horizon-client-core/src/lang.rs`; crate `horizon-assets`; G: `packs.rs` |
| Agent control | L: `control.rs` (socket), `bin/horizon-ctl/`; G: `control.rs`, `agent.rs`, `events.rs`, `ui_control.rs` ([docs/agent-control.md](docs/agent-control.md)) |
Generated
+13 -1
View File
@@ -1370,7 +1370,6 @@ name = "horizon-client"
version = "0.1.0"
dependencies = [
"ab_glyph",
"arboard",
"bytemuck",
"chrono",
"cpal",
@@ -1380,6 +1379,7 @@ dependencies = [
"horizon-assets",
"horizon-bench",
"horizon-client-core",
"horizon-client-input",
"horizon-command",
"horizon-core",
"horizon-dualsense",
@@ -1422,6 +1422,18 @@ dependencies = [
"serde_json",
]
[[package]]
name = "horizon-client-input"
version = "0.1.0"
dependencies = [
"arboard",
"horizon-client-core",
"horizon-dualsense",
"horizon-theme",
"png",
"winit",
]
[[package]]
name = "horizon-command"
version = "0.1.0"
+1
View File
@@ -8,6 +8,7 @@ members = [
"crates/horizon-world",
"crates/horizon-client",
"crates/horizon-client-core",
"crates/horizon-client-input",
"crates/horizon-command",
"crates/horizon-items",
"crates/horizon-server-files",
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "horizon-client-input"
version = "0.1.0"
edition.workspace = true
license.workspace = true
rust-version = "1.90"
description = "The client's input layer: key bindings and their names, mouse, controllers, clipboard and window options"
[dependencies]
horizon-client-core = { path = "../horizon-client-core" }
winit = "0.30.13"
# The window icon.
horizon-theme = { path = "../horizon-theme" }
png = "0.18"
arboard = { version = "3.6.1", default-features = false, features = ["wayland-data-control"] }
# The DualSense's trigger effects (the `controllers` feature).
horizon-dualsense = { path = "../horizon-dualsense", optional = true }
[features]
controllers = ["dep:horizon-dualsense"]
[lints]
workspace = true
File renamed without changes.
+14
View File
@@ -0,0 +1,14 @@
//! The client's input layer, below gameplay and screens: key bindings and their
//! names, toggle keys, mouse settings, controllers, the clipboard and window
//! options. horizon-client re-exports each module under its old path
//! (`horizon_client::keybinds`, ...).
#![forbid(unsafe_code)]
pub mod clipboard;
pub mod gamepad;
pub mod input;
pub mod interaction_input;
pub mod key_names;
pub mod keybinds;
pub mod keymapping;
pub mod mouse;
pub mod window;
File renamed without changes.
+2 -2
View File
@@ -10,6 +10,7 @@ description = "Horizon native development client"
[dependencies]
horizon-assets = { path = "../horizon-assets" }
horizon-client-core = { path = "../horizon-client-core" }
horizon-client-input = { path = "../horizon-client-input" }
horizon-command = { path = "../horizon-command" }
horizon-items = { path = "../horizon-items" }
horizon-core = { path = "../horizon-core" }
@@ -55,7 +56,6 @@ horizon-theme = { path = "../horizon-theme" }
# the anonymous "I'm playing" ping the game sends itself (`heartbeat.rs`).
horizon-telemetry = { path = "../horizon-telemetry", default-features = false, features = ["live"] }
horizon-bench = { path = "../horizon-bench" }
arboard = { version = "3.6.1", default-features = false, features = ["wayland-data-control"] }
zip = { version = "2", default-features = false, features = ["deflate"] }
# Controllers (evdev + libudev on Linux, IOKit on macOS, Windows.Gaming.Input); the `controllers` feature.
gilrs = { version = "0.11", optional = true }
@@ -90,7 +90,7 @@ default = ["audio", "controllers"]
# Linux: the server build (`--no-default-features`, released as horizon-engine; docs/server.md).
audio = ["dep:cpal"]
# Controllers: gilrs, and the DualSense's own output. Without it only `horizon-ctl pad`'s virtual pad.
controllers = ["dep:gilrs", "dep:horizon-dualsense"]
controllers = ["dep:gilrs", "dep:horizon-dualsense", "horizon-client-input/controllers"]
# Runs code mods (wasmtime and cranelift: a quarter of a clean build). On in every release build
# (release.yml); the tests and probes don't need it. Content mods load without it.
mods = ["horizon-mods/runtime"]
+9 -9
View File
@@ -31,7 +31,6 @@ pub mod cauldron;
pub mod chat;
pub mod chunk;
pub mod chunk_map;
pub mod clipboard;
pub mod clock;
pub mod clouds;
pub mod collisions;
@@ -72,7 +71,6 @@ pub mod font;
pub mod friends_screen;
pub mod game_mode_switcher;
pub mod game_rules_screen;
pub mod gamepad;
pub mod gfx;
pub mod gizmos;
pub mod gossip;
@@ -86,17 +84,12 @@ pub mod hud;
pub mod hud_layout;
pub mod hud_overlays;
pub mod hunger;
pub mod input;
pub mod interaction_input;
pub mod inv_sort;
pub mod inventory_effects;
pub mod item_render;
pub mod item_sync;
pub mod item_tooltip;
pub mod java_text;
pub mod key_names;
pub mod keybinds;
pub mod keymapping;
pub mod lan;
pub mod landing;
pub mod level_loading;
@@ -116,7 +109,6 @@ pub mod mod_list;
mod model_library;
pub mod models;
pub mod motion;
pub mod mouse;
pub mod moved_extras;
pub mod multiplayer;
pub mod music;
@@ -187,7 +179,6 @@ pub mod vault;
pub mod weather;
pub mod widgets;
pub mod win_screen;
pub mod window;
pub mod world;
pub mod world_spawn;
pub mod worldborder;
@@ -203,6 +194,15 @@ pub use horizon_client_core::nbt_path;
pub use horizon_client_core::scoreboard;
pub use horizon_client_core::snbt;
pub use horizon_client_core::text;
pub use horizon_client_input::clipboard;
pub use horizon_client_input::gamepad;
pub use horizon_client_input::input;
pub use horizon_client_input::interaction_input;
pub use horizon_client_input::key_names;
pub use horizon_client_input::keybinds;
pub use horizon_client_input::keymapping;
pub use horizon_client_input::mouse;
pub use horizon_client_input::window;
pub use horizon_command as command;
pub use horizon_items::blocks;
pub use horizon_items::bundle;
+1 -1
View File
@@ -25,7 +25,7 @@ Select/Start). No second windowing or event stack: gilrs is polled once per
loop iteration next to winit's events.
- The pure logic (mapping table, deadzones and curves, button edges, the
settings file, glyph choice) lives in `crates/horizon-client/src/gamepad.rs`
settings file, glyph choice) lives in `crates/horizon-client-input/src/gamepad.rs`
and is unit-tested. The game owns a thin gilrs backend and applies actions.
- Controller actions go through the same path a keyboard key takes after
rebinding: an action names a vanilla key mapping (`key.jump`, `key.attack`,
+1 -1
View File
@@ -62,7 +62,7 @@ def main():
'real OS sleep accuracy is outside the comparison.')
harness.differential(
'rust-frame-limiter-parity', 'VanillaFrameLimiterOracle', 'frame_limiter_probe', make_cases, scope,
sources=[lab.ROOT / 'crates/horizon-client/src/window.rs'])
sources=[lab.ROOT / 'crates/horizon-client-input/src/window.rs'])
if __name__ == '__main__':
+2 -1
View File
@@ -154,7 +154,8 @@ def main():
'FramerateLimiter and vsync present-mode selection are covered by Rust unit tests only.')
harness.differential(
'rust-game-loop-parity', 'VanillaGameLoopOracle', 'game_loop_probe', make_cases, scope,
sources=[lab.ROOT / f'crates/horizon-client/src/{name}.rs' for name in ('clock', 'mouse', 'window', 'keymapping')])
sources=[lab.ROOT / 'crates/horizon-client/src/clock.rs']
+ [lab.ROOT / f'crates/horizon-client-input/src/{name}.rs' for name in ('mouse', 'window', 'keymapping')])
if __name__ == '__main__':
+1 -1
View File
@@ -43,7 +43,7 @@ def main():
"Includes passing entity interactions over solid blocks: only the held "
"item may be tried afterward. "
"Game-mode side effects and world simulation are controlled fixtures.",
sources=[lab.ROOT / "crates/horizon-client/src/interaction_input.rs",
sources=[lab.ROOT / "crates/horizon-client-input/src/interaction_input.rs",
lab.ROOT / "crates/horizon-client/src/game/remote.rs",
lab.ROOT / "crates/horizon-client/src/game/hand.rs",
lab.ROOT / "mods/horizon/src/test/java/dev/horizonlab/parity/ParityData.java"])
+1 -1
View File
@@ -35,7 +35,7 @@ def main():
build=lab.LOCAL/'rust-local-input';build.mkdir(exist_ok=True)
report=build/'report.json';report.unlink(missing_ok=True)
oracle=lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaLocalInputOracle.java'
sources=[Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/test_model_baking_parity.py',lab.ROOT/'tools/lab.py',oracle,lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-client/Cargo.toml',lab.ROOT/'crates/horizon-client/src/input.rs',lab.ROOT/'crates/horizon-client/src/lib.rs',lab.ROOT/'crates/horizon-client/examples/probe/input_probe.rs']
sources=[Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/test_model_baking_parity.py',lab.ROOT/'tools/lab.py',oracle,lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-client/Cargo.toml',lab.ROOT/'crates/horizon-client-input/src/input.rs',lab.ROOT/'crates/horizon-client/src/lib.rs',lab.ROOT/'crates/horizon-client/examples/probe/input_probe.rs']
tested=base.source_hashes(sources)
def run(command,name):
with (build/(name+'.log')).open('w') as log:subprocess.run(list(map(str,command)),cwd=lab.ROOT,check=True,timeout=600,stdout=log,stderr=subprocess.STDOUT)
+2 -2
View File
@@ -189,8 +189,8 @@ def main():
harness.differential(
'rust-options-parity', 'VanillaOptionsOracle', 'options_probe', make_cases, scope,
sources=[lab.ROOT / 'crates/horizon-client/src/options_file.rs', lab.ROOT / 'crates/horizon-client/src/options_fixes.rs',
lab.ROOT / 'crates/horizon-client/src/java_text.rs', lab.ROOT / 'crates/horizon-client/src/key_names.rs',
lab.ROOT / 'crates/horizon-client/src/keybinds.rs'])
lab.ROOT / 'crates/horizon-client/src/java_text.rs', lab.ROOT / 'crates/horizon-client-input/src/key_names.rs',
lab.ROOT / 'crates/horizon-client-input/src/keybinds.rs'])
if __name__ == '__main__':
+1 -1
View File
@@ -40,7 +40,7 @@ def main():
build=lab.LOCAL/'rust-sprint-input';build.mkdir(exist_ok=True)
report=build/'report.json';report.unlink(missing_ok=True)
oracle=lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaSprintInputOracle.java'
sources=[Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/test_model_baking_parity.py',lab.ROOT/'tools/lab.py',oracle,lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-client/Cargo.toml',lab.ROOT/'crates/horizon-client/src/input.rs',lab.ROOT/'crates/horizon-client/src/sprint.rs',lab.ROOT/'crates/horizon-client/src/lib.rs',lab.ROOT/'crates/horizon-client/examples/probe/sprint_probe.rs']
sources=[Path(__file__),lab.ROOT/'tools/test_rust_parity.py',lab.ROOT/'tools/test_model_baking_parity.py',lab.ROOT/'tools/lab.py',oracle,lab.ROOT/'Cargo.toml',lab.ROOT/'Cargo.lock',lab.ROOT/'crates/horizon-client/Cargo.toml',lab.ROOT/'crates/horizon-client-input/src/input.rs',lab.ROOT/'crates/horizon-client/src/sprint.rs',lab.ROOT/'crates/horizon-client/src/lib.rs',lab.ROOT/'crates/horizon-client/examples/probe/sprint_probe.rs']
tested=base.source_hashes(sources)
def run(command,name):
with (build/(name+'.log')).open('w') as log:subprocess.run(list(map(str,command)),cwd=lab.ROOT,check=True,timeout=600,stdout=log,stderr=subprocess.STDOUT)
+1 -1
View File
@@ -69,7 +69,7 @@ def main():
'not exercised; KeyboardInput then reads isDown as the sprint and local input harnesses compare.')
harness.differential(
'rust-toggle-keys-parity', 'VanillaToggleKeysOracle', 'toggle_keys_probe', make_cases, scope,
sources=[lab.ROOT / 'crates/horizon-client/src/keymapping.rs'])
sources=[lab.ROOT / 'crates/horizon-client-input/src/keymapping.rs'])
if __name__ == '__main__':