Archive both-JAR matches for all 51 survival families, 1,286 block definitions and 35,723 states across 9,229,463 fixtures. Preserve the scope boundary: placement, updates and complete gameplay remain unfinished.
19 KiB
Horizon collision geometry
The version target is Minecraft Java Edition 26.3. Geometry is a simulation foundation; Horizon still has no Rust client, live world, or entity simulation.
horizon-core::geometry provides Vec3, Aabb, Axis, Direction, BlockHit,
and immutable VoxelShape values. Coordinates are doubles. Arithmetic keeps
vanilla's operation order, signed zero, NaN comparisons, normalization threshold,
and collision epsilon. It does not enable approximate or fused simulation math.
Implemented vector operations include addition/subtraction, vector-to, component products, scaling, normalization, dot/cross products, interpolation, projection, distance/length, horizontal vectors and finite/equality checks. Rotations, look directions, alignment, axis replacement, direction offsets, and pitch/yaw extraction are also implemented. The full Vec3 API and entity integration remain absent.
Trig uses vanilla's 65,536-entry float sine table with double angle scaling and Java long narrowing. The table is generated from the pinned formula in Rust; the geometry suite compares every generated entry against the runtime table. Inverse trig narrows to float at vanilla's conversion points. These comparisons validate the tested platform; other platforms need the same bit comparisons.
Bounding boxes normalize endpoints with Java-compatible min/max. Queries include
half-open containment, strict intersection, transforms, distances, ray clipping,
block-coordinate construction with wrapping integer endpoints, setters, next-value
deflation, and swept-box collision tests. A disjoint intersect preserves vanilla's
constructor normalization rather than introducing a different empty-box rule.
Shapes keep exact coordinate arrays and a packed occupancy grid. Greedy boxes are cached once in vanilla order. Collision-distance queries use occupied grid cells; ray and closest-point queries reuse the cached boxes. Column minima/maxima use vanilla's cyclic perpendicular coordinates and infinity for empty/outside cells. Cube shapes retain arithmetic lookup; moved shapes use array lookup even for a zero offset. Ray hits preserve face tie ordering, endpoint exclusion, tolerances, and the 0.001 inside-hit nudge. Boolean shape joins and overlap predicates preserve cube, identical, disjoint and epsilon-merger behavior. Shape operations reject truth tables that fill two empty cells, as vanilla does. Per-axis cube-coordinate metadata survives partial merges and is checked when importing cached shapes. Box construction preserves thin-box rejection and up to 1/8 cube quantization. Optimized joins reconstruct greedy boxes in vanilla order. Face/edge extraction and shape rotations remain absent.
Discrete axis extrema are retained separately from occupied cells. A box can quantize to no occupied cells while retaining a finite minimum/maximum on other axes. That state affects vanilla's lower-block floor-height lookup. Empty joins also preserve their integer sentinel extrema; optimization and moving an empty shape return the canonical empty shape.
Shape clones preserve identity and share immutable coordinate, occupancy and box storage. New shapes keep separate identities even when their grids are equal. This preserves alias-sensitive join shortcuts and avoids copying large grids when an operation returns an existing shape. The cache importer shares equal grid storage while retaining the distinct vanilla shape identities.
resolve_shape_movement ports vanilla's movement resolver over a supplied shape
list: Y first, then horizontal axes in movement-magnitude order, translating the
box by resolved motion before each axis query. candidate_step_heights preserves
float narrowing, duplicate identity, current-Y exclusion, and sorted candidate
heights. resolve_step_movement adds the initial collision and step-up decision,
including landing-height adjustment, a second collider query, and the first
candidate improving horizontal movement. entity_collision_bounds preserves
the initial entity query bounds. These helpers take caller-selected colliders;
they do not choose world/entity shapes, set grounding, change velocity, or apply
inside-block effects.
The step-up corpus calls the actual private Entity.collide method for 17,680
cases per JAR. Reference fixtures supply entity inputs and controlled
block/entity/border shape lists; vanilla performs the movement decisions. Query
bounds and results are compared bit for bit. This verifies the decision with
those inputs, not live world selection or entity state transitions.
block_floor_height returns the selected shape's maximum Y, or lazily queries
the lower shape only when the current one is empty. Its 12,000-case corpus also
checks the lower supplier's invocation count against BlockGetter.
traverse_blocks visits blocks in vanilla ray order, including its endpoint nudge,
strict corner comparisons, early callback return, integer wrap and signed-zero
endpoint equality. It accepts finite rays and an explicit visit budget. Invalid
geometry or exceeding that budget returns a typed error; earlier callbacks remain
applied. Actual block/fluid ray selection is a separate integration task.
Inclusive block-box iterators preserve X/Y/Z order, while directional iterators preserve vanilla's movement-dependent axis order and corner signs. AABB iteration includes floored maximum faces. They return independent position values and reject true volumes above the positive Java-int domain, instead of reproducing overflowed Java iteration counts.
sweep_intersected_blocks ports movement-path visits from
BlockGetter.forEachBlockIntersectedBetween: tiny movement, the initial box,
ray-clipped footprints, packed-position deduplication, final box, step indices,
and early callback stops. Stationary/tiny movement avoids a deduplication-set
allocation. Inputs and computed geometry must be finite; a caller-selected work
budget counts ray steps and iterator candidates, including suppressed duplicates.
Earlier callbacks are not rolled back on an error. Block effects still need
their own implementations and integration with entity movement.
horizon-world::collision::CachedCollisionRegistry imports a private local export
of vanilla cache data. The export includes DataVersion, all state descriptors,
deduplicated shapes, coordinate-kind metadata, discrete extrema, and a per-state
cache index. Import verifies every state ID
against the block registry. Section lookup also requires the same borrowed registry.
Imported nonempty extrema must agree with occupancy; empty extrema must be in
the grid's domain or match vanilla's empty-join sentinel triple. Extrema form part
of the grid-sharing key, so equal occupancy does not erase different metadata.
There is no fallback shape for an unsupported state: None means block behavior
must select it, while a cached empty shape is a valid Some value.
This covers the cached branch of the no-context BlockState.getCollisionShape
method. Vanilla's overload taking CollisionContext always calls block behavior;
the cache cannot establish entity collision parity. Dynamic and entity-dependent
selection, neighboring blocks, world borders, movement integration, fluids, block
effects, and live collision broad-phase queries still need implementation.
Run the comparisons after preparing the pinned artifacts:
python3 tools/test_geometry_parity.py
python3 tools/test_collision_parity.py
Both suites invoke actual vanilla methods from the authenticated client and server JARs. They compare canonical double bits, including infinities/NaNs, rather than using a numerical tolerance. The geometry corpus includes empty and sparse grids, greedy decomposition, overlapping boxes, rays, tiny movements and IEEE boundaries. It compares 1,320,187 output lines per JAR, including 2,400 integer-box iterator cases, 2,400 AABB iterator cases, and 9,022 movement-path traces. The registry suite exercises cached shape queries through resident section IDs, translation at negative and world-border coordinates, and invalid import rejection. Its export records 35,524 cached states, 199 uncached states, 6,093 shape identities, and 330 distinct grids. The comparison runs 213,144 queries plus state/box/identity checks, producing 1,097,456 matching output lines per JAR. These counts describe this branch and corpus; they are not a gameplay coverage percentage.
Raw grids admit at most 1,048,576 cells with strictly increasing, non-NaN coordinates. Oversized or malformed imports/joins return typed errors. This bounded library API does not reproduce Java's complete exception/object surface.
All exported data, test inputs, reference output, and compiled adapters remain
under gitignored .local/. Success reports record exact sources, inputs, outputs,
and reference hashes. A runner removes its prior success report before running
and rejects edits to the captured sources during validation. Passing these suites
does not prove whole-game parity or any FPS/render-distance improvement.
Checked-in geometry metadata and cached-collision metadata identify their tested source versions. Reports cover their recorded snapshots; later changes require fresh validation.
Entity obstruction now evaluates ordered candidate snapshots, excluded entity
identity, spectator/removal/building flags, shared root-vehicle identity and
voxel-shape intersection. Empty shapes skip the query; candidate collection
finishes before obstruction evaluation. Unit-block queries translate in floating
point, including integer-coordinate boundaries. Run
python3 tools/test_entity_obstruction_parity.py to compare against the actual
vanilla EntityGetter default methods and entity metadata queries.
The entity-obstruction report records 15,121 matching output lines per JAR over composite shapes, boundary contacts, thin boxes, metadata filters, vehicle roots and injected query failures. The Java fixture privately allocates entity snapshots without running their constructors. Entity lifecycle, spatial indexing and vehicle-tree maintenance remain separate implementation work. Workspace tests, warning-free Clippy and 138 Python tests passed at this snapshot.
Legacy block solidity now uses explicit definition overrides and cached collision bounds, with force-on precedence and native mean-size/height thresholds. The no-context replaceable and liquid flags are kept independently. Solid rendering uses the occlusion definition flag and a full-shape test over imported raw occlusion geometry. Import validates the pinned DataVersion, full state identities and complete block definitions before building a shared property table. Predicates query this table in constant time.
python3 tools/test_block_properties_parity.py compares all 35,723 states and
18,096 synthetic cache/flag/shape cases, including adjacent floating-point values
around solidity thresholds and both occlusion flag values. The block-properties report
records 143,361 matching lines per JAR. Definition flags, collision and raw
occlusion data are private runtime inputs; Rust calculates solidity and solid
rendering. The report was produced in an isolated staging copy and archived only
after the integrated sources and copied fixtures matched every recorded hash.
Item/fluid-context replacement and collision with live entities remain separate work.
Face selection now preserves native cube-axis shortcuts, epsilon sample points,
slice coordinates and clamped parent extrema, including sparse slices whose
native bounds differ from occupied boxes. Repeated queries share face identities.
The FULL, CENTER and RIGID support tests execute against the selected shape.
python3 tools/test_face_support_parity.py compares 1,615 input shapes and
nested faces, producing 595,686 matching lines per JAR in the
face-support report.
The refreshed geometry report
also retains 1,320,187 matching lines per JAR after face slicing was added.
Block-specific support-shape selection is a separate layer.
Registered support caches now derive FULL/CENTER/RIGID masks from privately
imported support geometry and share results by shape identity. The importer
checks the data kind and full state identities. None explicitly requests
uncached behavior; it never becomes an assumed unsupported face.
python3 tools/test_block_support_parity.py compares 35,524 cached states
across six directions and three support kinds, while retaining 199 uncached
states. The cached-support report
records 248,867 matching lines per JAR.
Face-cache storage is now lazy and attached to shape identity. Clones share it; new identities and moved shapes start with an empty cache. A regression test checks that cached faces do not retain their parent after it is dropped. The face and geometry differential suites passed again after this allocation change, as did workspace tests, Clippy and 140 Python tests. No FPS claim is inferred from these library checks.
Dynamic support selection now handles bamboo collision offsets, dripstone and
sulfur-spike thickness/direction, the empty-context scaffolding support shape,
powder snow, and shulker animation-state support faces. Moving-piston lookup
preserves missing/wrong block-entity behavior and delegates active entity shape
calculation to the world. The dynamic-support report
records 4,867,780 matching lines per JAR over 107,308 fixtures covering all
199 uncached states, coordinate boundaries, block-entity snapshots and query
failures. Run python3 tools/test_dynamic_support_parity.py to reproduce it.
The current suite also compares no-entity-context collision geometry in 60,161
fixtures, including 13,014 shulker progress fixtures. Shulker collision
bounds retain native float interpolation and differ from animation-state support
faces. BlockCollisionRegistry dispatches cached and dynamic collision queries;
entity movement contexts and block-entity lifecycle remain separate work.
Workspace tests, warning-free Clippy and seven focused Python tests passed.
Moving-piston collision now selects stationary extended bases and moving heads, translates shapes using native float progress arithmetic, and honors the movement-direction collision exclusion. Interpolation preserves float operation order, including negative partial ticks and IEEE edge cases. The separate collision-related state used for entity pushing retains its own sticky/head rules; entity pushing itself is not implemented by this module.
python3 tools/test_piston_shape_parity.py compares 102,315 fixtures,
including every registered moved state and 10,000 raw float interpolation
cases. The piston-shape report
records 1,096,811 matching lines per JAR. This corpus uses absent block
entities for moved-block collision selection. Workspace tests, warning-free
Clippy and 142 Python tests passed. Tick progression and live world mutation
remain separate work.
The refreshed dynamic-support corpus also includes 9,216 active piston snapshots, composing block-entity lookup with piston collision generation and all support tests. It compares extension/retraction, source heads, progress boundaries, collision exclusions and query failures. These snapshots do not exercise piston ticks, pushed entities or chunk updates.
The archived block-survival snapshot executes 51 rule kinds with 63 distinct rule/tag
bindings over registry-bound solid flags, air identity, support tags and
FULL/CENTER/RIGID geometry and empty-context collision. The verified snapshot covers
all 1,286 pinned block definitions and 35,723 states. Candidate
properties select attachment direction, hanging state, door/plant halves and
pale moss carpet's serialized bottom property. Pressure plates and cactus
flowers preserve repeated neighbor reads. Downward center support applies the
unstable-bottom-center tag only where native helpers do. Upper doors require the
same block below; upper double plants also require its lower half. Crop survival
checks brightness against 8 before querying support; farmland preserves its
conditional second read above, and dripleaf stems read both neighbors. Sugar cane,
lily pads, frogspawn, spore blossoms, small dripleaves and tall seagrass preserve
their independent fluid queries, source/full-water requirements and tag checks.
Growing plants preserve head/body identity and kelp support restrictions.
Scaffolding recomputes neighbor distance in native order; chorus plants and flowers
preserve branching reads and early exits. Piston heads validate base type, facing
and extension. Mangrove propagules, speleothems, pitcher crops, bells and shelf
mushrooms use their version-specific attachment rules. Multiface blocks, vines,
hanging moss, snow layers and sea pickles preserve support/collision query order.
Fire uses imported ignition odds and waterlogging; cactus checks horizontal solids
and lava before its support and upper liquid state. Paths recognize fence gates,
and mushrooms preserve the light-override tag and solid-render requirement.
Interrupted runs can be resumed with python3 tools/test_block_survival_parity.py --resume.
The runner verifies source, environment, fixture and compiled-artifact hashes
before reusing completed stages. A changed output is regenerated; changed inputs
require a fresh run without --resume. Checkpoints stay under .local, and
a parity report is written only after both native comparisons pass.
python3 tools/test_block_survival_parity.py compares actual native canSurvive
calls in both pinned JARs. The block-survival report
records 35,998,488 matching output lines per JAR across 9,229,463 fixtures.
Each supported candidate state is exercised; behavior representatives are tested
against every neighbor state, changing repeated reads, integer-coordinate
wrapping, absent/closed/open shulker snapshots and injected block/entity query
failures, plus light thresholds, independent fluid sequences and fluid-query
failures. Native method ownership
and support-tag fields supply private
definition metadata; survival results are calculated in Rust. Workspace tests,
Clippy and the 147-test Python suite passed. This establishes the recorded
survival scope; full placement, neighbor updates, scheduled behavior and complete
block gameplay remain pending.