Files
horizon/docs/native-client.md
T

23 KiB
Raw Blame History

Horizon native development client

horizon-client opens a native Rust window and renders a controlled block scene stored in Horizon's resident chunks. Every occupied cell selects models from its registry state, including the terrain and sixteen featured placements. It loads block definitions, cuboid models, sprites and occlusion grids from pinned Java Edition 26.3 resources. Model selection uses the default block position seed; neighbor visibility uses the validated Rust face rules across chunk boundaries. The default camera is an inspection tool. F4 enables a standing-sized collision volume that queries the rendered resident chunks through the shared world collision implementation. The camera can still fly; it does not simulate a player. F5 switches to a dry walking test with gravity, ground support, stepping and held-jump timing over the same resident chunks. Its movement runs at 20 ticks/s; the camera interpolates between tick positions.

Eligible opaque unit-cube faces use greedy rectangles only when texture layer, UV rotation/mirroring and shading match. Each rectangle retains its signed UV basis, so weighted texture variation survives merging. Other baked geometry and binary-alpha cutouts use the separate model-face instance pipeline. Geometry with nonintegral UV-lock rounding also stays on that path.

This checkpoint has partial player movement. Vanilla world generation, block interaction, inventory, sound, networking and world save/load remain unbound in the native client. It does not meet the playable-build acceptance criteria. The controlled chunk scene now uses blockstate dispatch, weighted variants, multipart conditions and transformed baking. Full block-catalog material support, fractional transparency, tinting, lighting, resource packs, chunk streaming and full visual parity remain unfinished.

The resource gallery sits beside the pyramid and includes oak stairs, a slab, an anvil, a torch, a lantern, connected fences and iron bars, a cobweb, a wall grindstone, a ladder, a sideways amethyst cluster and four weighted stone placements. Registry state properties select the models: the stairs are an upside-down inner corner, the slab is a top slab and the fence gate is open. These are resident chunk states; simulation callbacks remain unfinished. Binary-alpha cutouts are rendered; the lantern's three frames advance every eight inspection ticks at 20 ticks/s. The animation metadata codec and frame selection now use the shared horizon-assets implementation checked against the pinned client. GPU interpolation and synchronization with the eventual game loop remain unfinished. Sprites currently require matching square frame dimensions.

Run from a checkout

Use current stable Rust (the locked client dependencies require Rust 1.90 or newer), Python 3.10+ and JDK 25. A native desktop display and compatible graphics driver are required. Resource preparation verifies the pinned game artifacts, exports the registry using the reference server and property schemas using the reference client, and extracts selected client resources into ignored .local/horizon-client/26.3/.

python3 tools/prepare_native_client.py --prepare
cargo +stable run --locked -p horizon-client

Java is used during preparation. The native executable itself runs without a JVM. Prepared resources stay private; do not commit or publish the extracted game files. Final distributable builds still need resource provisioning and packaging.

Use --resources PATH for a prepared resource directory. The executable also looks for a sibling resources/ directory before the checkout default. Use --radius N to select 1–16 chunks around the origin; this is a finite test scene, not a render-distance or world-streaming implementation.

Control Action
WASD Move horizontally
Space / either Shift Rise / descend in inspection mode; Space jumps in walking mode
Either Ctrl Boost inspection speed
Arrow keys Rotate camera
Left click Capture mouse for camera rotation
Escape Release mouse
F3 Toggle FPS/chunk/mesh diagnostics in the window title
F4 Toggle inspection camera collisions; enabling requires an unobstructed position
F5 Toggle walking test at the current camera position; enabling requires an unobstructed position
Ctrl+Q or window close Exit

Controls bind physical keyboard positions. Losing focus clears held keys and releases the mouse. Walking pauses while unfocused and resets partial tick time on focus changes. Walking currently has no sprint, crouch, flight, item use or block interaction. F5 returns to inspection mode; the finite scene has no streaming or boundary wall. --trace-input logs keyboard, mouse-button and focus events for diagnosis. On headless Linux use vscreen run ./target/debug/horizon-client, then vscreen shot. The wtype tool's default generated keymap does not preserve physical key positions; typing the character W can report physical Escape.

Validation

The client loads a separate, registry-checked collision-shape export alongside render resources. SceneCollisions feeds its actual TerrainChunk instances into registered shape selection, block scanning, world movement resolution and occupancy checks. The scene has no other entities, block entities or border; entity-dependent queries report an error until an entity runtime is supplied. Camera collision uses a position context and disables stepping. The walking binding uses a standing entity context and its registered step-height attribute. Sneaking and the complete authoritative player tick remain unfinished.

python3 tools/prepare_native_client.py
python3 tools/test_native_scene_collision.py

The scene collision comparison checks 14,150 movement requests, each with before/after occupancy queries, against both pinned reference JARs. Native code independently builds the nine- and twenty-five-chunk scenes and all sixteen gallery placements. Movement components compare by exact double bits. Cases include partial blocks, pyramid ledges, ceilings, initial overlaps, negative and missing chunk boundaries, tiny and zero movement, and varied step heights. This verifies the scene adapter; it does not establish player locomotion or complete gameplay parity.

The movement binding stores a player's position, velocity, standing bounds, ground/support state, collision flags, movement history and live registered attributes. MotionWorld composes the existing air-travel, jumping, collision, ground-support, restitution and block-coefficient code over the same scene. The stored-motion comparison checks 42,257 operations across 637 evolving scenarios against actual native Player.travel, jumping and movement, using real native attributes and chunks. It compares every resulting position, velocity and bounding-box component by exact bits, along with support, climbing and collision state and both separate velocity/position synchronization flags.

The dry movement tick additionally binds small-velocity cleanup, float input damping, held-jump delay and server frost-modifier removal. The expanded tick comparison checks 47,393 operations in 847 scenarios against both pinned JARs, including direct calls to the actual LivingEntity.aiStep body. It compares lazy attribute instance creation, stored base/cache/dirty state, modifier values and permanence, and pending update/sync membership alongside the motion fields. Queue iteration order depends on runtime identity hashes and is outside this scene comparison. The test caught an omission where matching positions concealed a retained powder-snow modifier and missing attribute notifications; dry ticks now remove that modifier after travel, as vanilla does.

The walking movement comparison extends this to 65,893 operations in 947 scenarios, invoking actual Player.aiStep with empty inventory/equipment and no nearby entities. It checks head yaw, jump-trigger countdown and speed refresh after movement. Prepared input is supplied at the native virtual input boundary. Separately, the local input comparison checks 13,988 cases against actual KeyboardInput.tick and LocalPlayer.modifyInput, covering every key combination, diagonal adjustment, item/riding/sneaking speed gates, signed zero and nonfinite values. This verifies input arithmetic; full LocalPlayer lifecycle, sprint eligibility and poses remain unfinished.

The fixed-rate driver limits work per rendered frame while retaining overdue ticks. Clock tests cover varied frame schedules and long stalls. The development focus pause is explicit; no vanilla pause-menu or multiplayer timing is claimed. The Linux walking smoke check enabled F5, landed at ground height 4, walked, jumped, switched modes and exited with Ctrl+Q. Ground, moved and airborne screenshots were visually inspected; the run recorded 207 movement ticks and finished grounded without an error. The renderer startup check also passes after the movement integration.

python3 tools/prepare_native_client.py
python3 tools/test_native_scene_motion.py
python3 tools/test_local_input_parity.py

This binding is not yet a complete player tick or input controller. Its current domain is the dry resident scene, standing dimensions, zero frozen ticks, empty inventory/equipment, no entity interpolation, crouching, vehicles or status effects. Selected Player.aiStep phases are bound; the full tick and local input lifecycle remain unfinished. Landing/damage, movement emission and external networking remain recorded phase boundaries; damage is not applied and ordinary falling does not yet accumulate fall distance. Block-inside effects such as cobweb slowdown and world/entity lifecycle remain unfinished. These boundaries are explicit in the native harness and are not included in its parity claim. The earlier scene collision comparison and renderer startup also pass with this binding and the additional prepared movement resources.

The accompanying startup check passes after sharing scene loading between the GUI and headless collision probe. An interactive Linux check enables F4, holds Shift to descend, verifies that the camera stops at the terrain surface, and exits with Ctrl+Q. The window was visually inspected on Vulkan llvmpipe; this is not a hardware performance measurement.

The separate horizon-assets crate decodes the pinned 26.3 cuboid model format. Its native comparison covers all 2,883 block-model JSON files in the client JAR plus 2,457 synthetic cases: element bounds, optional geometry, nullable children, face UVs and quarter turns, single-axis/Euler rotations, texture references and translucency materials, light emission, display transforms and inheritance markers. Floating-point fields compare by their exact bits. The comparison also covers primitive coercion, integer truncation/wraparound and selected strict-reader numeric/syntax limits. The numeric scanner also preserves vanilla's rejection when its wrapping integer accumulator hits zero before another digit, including in ignored fields. Twenty newly exposed acceptance mismatches were corrected; the complete comparison now covers 5,340 models. The resolution, baking and animation comparisons also pass with the shared scanner correction.

python3 tools/test_cuboid_model_parity.py

The archived decoder result records source, fixture and reference hashes. The oracle uses actual client classes; those rendering APIs are absent from the server. The decoder, parent resolver and baker now feed the development gallery. Blockstate selection, transformed models and UV locking have the separate checks described below. Rendered appearance still needs its own checks.

ModelLibrary resolves parent geometry, ambient occlusion, GUI lighting and each display context. It shares inherited elements rather than cloning them. Texture aliases bind after child overrides are applied; successful and failed alias walks are cached. Missing resources use the native missing-model cube; parent cycles invalidate their descendants. The resolution comparison matches 5,648 requested roots across 439 graphs, including the full bundled block-model catalog, missing parents, 702 cyclic roots, 1,024-model parent chains, 512-slot alias chains, explicit empty geometry and identity/null transforms. It calls native ModelDiscovery, ResolvedModel and TextureSlots APIs.

python3 tools/test_model_resolution_parity.py
cargo +stable test --locked -p horizon-assets

Cuboid baking now produces positions in block units, normalized unit-atlas UVs, face winding and facing, material references, tint indices, shade overrides and light-emission metadata. It supports single-axis and Euler element rotations with the pinned rescaling rule. The baking comparison matches 7,100 roots across 440 graphs, using native FaceBakery with a real sprite and the default JOML math options. All bundled block models bake without errors. Synthetic coverage includes reversed/degenerate bounds, signed zero, arbitrary rotations, rescaling, null geometry children and parent cycles. Position, UV and matrix fields compare by exact float bits for these fixtures.

python3 tools/test_model_baking_parity.py

That comparison uses the identity blockstate transform. Atlas packing, transparency classification, actual tint/light evaluation and GPU rendering remain separate from the geometry result. Other operating systems still need their native validation runs.

BlockModelTransform now prepares all 64 XYZ quarter-turn combinations once and shares them across baked models. It rotates geometry around the block center, rotates culling directions and applies UV locking in the original face basis. The transformed baking comparison checks every distinct model/rotation/UV-lock tuple in bundled blockstates and all 128 rotation/lock combinations on 81 synthetic models, plus further arbitrary element rotations. Positions, UVs and every orientation/inverse-face matrix compare by exact float bits: 19,704 roots and 148,439 quads match, including 7,884 bundled model/transform tuples. Quaternion basis rounding and numeric identity rechecking are retained. This remains geometry validation; atlas packing and pixel parity are separate work.

python3 tools/test_blockstate_transform_parity.py

Blockstate resources now decode into ordered variant selectors and multipart conditions. The native blockstate comparison covers all 1,288 bundled definitions and 1,351 synthetic cases (2,639 total, 468 expected rejections). It uses the same StrictJsonParser and BlockStateModelDispatcher.CODEC as the pinned loader. The Rust representation preserves single versus weighted variants, all three quarter-turn axes, UV-lock flags, positive weights and total-weight limits, ordered selectors, nested AND/OR conditions and per-term negation.

python3 tools/test_blockstate_model_parity.py

Property binding now selects variant roots or multipart part indices for an ordered native state list. The binding comparison covers all 1,288 bundled definitions against 35,723 registered block states plus the four special item-frame states, followed by synthetic overlap, malformed selector and predicate cases: 3,802 definitions and 687,466 state results match, with 220 expected binding errors. It checks the native replacement-before-abort behavior for overlapping variants, eager multipart validation even when variants cover every state, integer-property aliases, nested conditions and empty part selections. The controlled chunk renderer now uses this binder for its model catalog.

The Java integer-property parser is shared by horizon-core, client binding and world-state decoding. Its codec regression matches 221,261 output lines against each authenticated client/server JAR.

python3 tools/test_blockstate_binding_parity.py

CompiledChoices prepares cumulative weights once per definition and shares those choices across bound states. Selecting an ordinary single variant consumes no random draw; a weighted singleton still does. Multipart collection draws a seed even for an empty subset and resets the same stream before each chosen part. The native choice comparison uses actual WeightedVariants.collectParts and MultiPartModel.collectParts with inert observable leaves. All seven distinct bundled weight patterns, synthetic large weights and random multipart graphs produce matching selections and subsequent random values: 6,960 fixtures, 111,360 collections, both legacy and xoroshiro streams. The development gallery now bakes and renders selected models using the legacy stream and the default block position seed. Specialized block seed callbacks remain separate work.

python3 tools/test_model_choice_parity.py

The sprite animation module reproduces the pinned metadata codec's integer conversion, strict primitive types, optional null fields and frame dimensions. It preserves repeated frames, filters out-of-image frame indices, and treats sequences with fewer than two valid entries as static physical frame zero. Frame lookup uses cumulative durations and binary search, so skipped rendering frames do not require replaying all elapsed animation ticks. The native oracle also checks dirty flags, draw requirements and interpolation timing, including transitions near the maximum Java integer duration. Interpolation pixel output and resource-loader failure recovery are outside this comparison.

python3 tools/test_sprite_animation_parity.py

The animation comparison records 907 fixtures and 258,996 native tick states from the actual client codec and SpriteContents.AnimationState.tick, including all 63 bundled animated texture metadata sections and synthetic boundary cases. The associated startup report checks the renderer using this shared implementation. Images, input fixtures and native traces stay private under .local/.

cargo +stable test -p horizon-client
cargo +stable clippy --workspace --all-targets -- -D warnings
python3 tools/test_native_client.py
# Headless Linux alternative:
python3 tools/test_native_client.py --vscreen

The mesh tests compare expanded rectangles against independently enumerated exposed voxel faces, including solid neighbors, cavities, material boundaries, negative chunk origins and random surroundings. The startup runner launches a nine-chunk scene, requires 30 presented frames and a clean exit, verifies 3,169 greedy cube rectangles and 179 other model faces across 18 sprite layers, and writes GPU, platform, binary and source information to ignored .local/horizon-client/startup-smoke.json. It has an external timeout as well as the application's frame deadline. These checks do not establish gameplay or pixel parity with Java. Source and resource fingerprints must remain unchanged during the run. An animation test covers duration boundaries, wrapping and repeated physical frames independently of render frequency.

Initial local validation uses Linux x86_64, a headless Wayland compositor and Vulkan llvmpipe software rendering. Textured geometry and resizing were visually inspected; movement, arrow rotation and Ctrl+Q were exercised using virtual physical key codes. Captured relative mouse movement and Escape release were exercised through a virtual pointer. Native macOS/Metal and Windows/Direct3D 12 runtime tests remain pending. Software-renderer frame rates are not evidence of a hardware speedup.

The model gallery was also inspected at close range on Linux/Vulkan llvmpipe. With the camera stationary, screenshot differences were confined to the lantern as its frames changed. The model-client startup report records the bounded presentation check and the scope of this visual inspection.

The blockstate-client report also checks all 16 gallery placements against actual Java registry binding, root baking and model collection using state.getSeed(position). A tagged empty geometry adapter identifies the 20 selected model parts and their transforms; the separate baking gate covers actual quad geometry. Preparation exports typed property domains and canonical state order, which the Rust loader checks against its imported registry before binding. Each distinct transformed model is baked once during gallery preparation and reused across positions. The selected state IDs, properties, positions and models are recorded in the startup report.

Neighbor-face visibility now has native client/server coverage across all registered states and suppression-rule families. Integrating that result with resident chunk model meshing is now covered by the scene check below.

The resident-model report checks all 10,919 occupied cells in the bounded startup scene against a separately constructed native scene. Actual Java state-model selection, parent geometry, rotated culling directions and Block.shouldRenderFace produce the same 5,991 visible and 59,636 culled model faces. The sixteen featured model selections also retain their direct native comparison. Expanded greedy-rectangle tests cover rotated and mirrored UV bases, texture and shade boundaries, negative origins, neighbor seams and cavities; cube projection tests cover all XYZ quarter turns. These checks cover CPU mesh behavior and GPU startup, not native shader pixels or a hardware performance comparison.