Files
shacraft-core/scripts/physics_reference.java
Emil c7e86663d8
MVP checks / mvp (push) Waiting to run
Expand voxel gameplay, lighting, full-height streaming and world imports
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
2026-09-17 02:10:53 +03:00

333 lines
20 KiB
Java

// Copyright Shacraft contributors. MIT OR Apache-2.0.
// Measure the original Java runtime. No original source or assets are exported.
import java.nio.file.*;
import java.lang.reflect.*;
import java.util.*;
import com.google.gson.*;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
import net.minecraft.world.entity.*;
import net.minecraft.world.entity.player.*;
import net.minecraft.world.entity.ai.attributes.*;
import net.minecraft.world.effect.*;
import net.minecraft.world.level.*;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.phys.*;
import net.minecraft.world.phys.shapes.*;
import net.minecraft.tags.*;
class physics_reference {
static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
static final sun.misc.Unsafe ALLOCATOR;
static final Method COLLIDE;
static {
try {
var field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
ALLOCATOR = (sun.misc.Unsafe)field.get(null);
COLLIDE = Entity.class.getDeclaredMethod("collideWithShapes", Vec3.class, AABB.class, List.class);
COLLIDE.setAccessible(true);
} catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); }
}
static void field(Object target, Class<?> owner, String name, Object value) {
try { var f=owner.getDeclaredField(name); f.setAccessible(true); f.set(target,value); }
catch (ReflectiveOperationException e) { throw new RuntimeException(e); }
}
static double sprintModifier() {
try {
var f=LivingEntity.class.getDeclaredField("SPEED_MODIFIER_SPRINTING"); f.setAccessible(true);
return ((AttributeModifier)f.get(null)).amount();
} catch (ReflectiveOperationException e) { throw new RuntimeException(e); }
}
static JsonArray vector(Vec3 v) {
var a=new JsonArray(); a.add(v.x); a.add(v.y); a.add(v.z); return a;
}
static Block block(String name) { return BuiltInRegistries.BLOCK.getValue(Identifier.parse("minecraft:"+name)); }
static class SampleWorld implements BlockGetter {
BlockState state;
SampleWorld(BlockState state) { this.state=state; }
public BlockState getBlockState(BlockPos p) { return p.equals(BlockPos.ZERO)?state:Blocks.AIR.defaultBlockState(); }
public FluidState getFluidState(BlockPos p) { return getBlockState(p).getFluidState(); }
public BlockEntity getBlockEntity(BlockPos p) { return null; }
public int getHeight() { return 384; }
public int getMinY() { return -64; }
}
// Constructors are deliberately never invoked: this registry-only harness
// does not create a running server, world files, connections, or EULA state.
static class PlaneLevel extends ServerLevel {
BlockState surface;
List<VoxelShape> obstacles;
WorldBorder border;
PlaneLevel() { super(null,null,null,null,null,null,false,0,List.of(),false); }
public BlockState getBlockState(BlockPos p) { return p.getY()<0?surface:Blocks.AIR.defaultBlockState(); }
public FluidState getFluidState(BlockPos p) { return getBlockState(p).getFluidState(); }
public WorldBorder getWorldBorder() { return border; }
public List<VoxelShape> getEntityCollisions(Entity entity,AABB bounds) { return List.of(); }
public Iterable<VoxelShape> getBlockCollisions(Entity entity,AABB bounds) {
return obstacles.stream().filter(s->s.bounds().intersects(bounds.inflate(1e-7))).toList();
}
}
static class ProbePlayer extends Player {
PlaneLevel plane;
boolean grounded, sprint;
String medium;
double fluidDepth;
Vec3 coordinates, velocity;
Abilities testAbilities;
AttributeSupplier defaults;
List<VoxelShape> obstacles;
ProbePlayer() { super(null,null); }
public GameType gameMode() { return GameType.SURVIVAL; }
public Level level() { return plane; }
public boolean onGround() { return grounded; }
public boolean isPassenger() { return false; }
public boolean isSwimming() { return false; }
public boolean isSprinting() { return sprint; }
public boolean isInWater() { return "water".equals(medium); }
public boolean isInLava() { return "lava".equals(medium); }
public boolean isFallFlying() { return false; }
public boolean isNoGravity() { return false; }
public boolean onClimbable() { return false; }
public boolean shouldDiscardFriction() { return false; }
public boolean isSteppingCarefully() { return false; }
public boolean isSuppressingBounce() { return false; }
public void gameEvent(Holder<GameEvent> event) {}
public double getFluidHeight(TagKey<Fluid> fluid) { return fluidDepth; }
public double getFluidJumpThreshold() { return .4; }
public boolean hasEffect(Holder<MobEffect> effect) { return false; }
public MobEffectInstance getEffect(Holder<MobEffect> effect) { return null; }
public Abilities getAbilities() { return testAbilities; }
public double getAttributeValue(Holder<Attribute> attribute) {
double value=defaults.getBaseValue(attribute);
return attribute.equals(Attributes.MOVEMENT_SPEED)&&sprint?value*(1+sprintModifier()):value;
}
public Vec3 getDeltaMovement() { return velocity; }
public void setDeltaMovement(Vec3 v) { velocity=v; }
public void setDeltaMovement(double x,double y,double z) { velocity=new Vec3(x,y,z); }
protected float getBlockJumpFactor() { return plane.surface.getBlock().getJumpFactor(); }
public void liquidJump() { jumpInLiquid(isInWater()?FluidTags.WATER:FluidTags.LAVA); }
public BlockPos getBlockPosBelowThatAffectsMyMovement() { return new BlockPos(0,-1,0); }
public void move(MoverType type,Vec3 requested) {
try {
var dimensions=getDefaultDimensions(Pose.STANDING);
var bounds=dimensions.makeBoundingBox(coordinates);
var moved=(Vec3)COLLIDE.invoke(null,requested,bounds,obstacles);
horizontalCollision=moved.x!=requested.x||moved.z!=requested.z;
verticalCollision=moved.y!=requested.y;
grounded=verticalCollision&&requested.y<0;
coordinates=coordinates.add(moved);
field(this,Entity.class,"position",coordinates);
if(moved.x!=requested.x)velocity=new Vec3(0,velocity.y,velocity.z);
if(moved.y!=requested.y)velocity=new Vec3(velocity.x,0,velocity.z);
if(moved.z!=requested.z)velocity=new Vec3(velocity.x,velocity.y,0);
} catch (ReflectiveOperationException e) { throw new RuntimeException(e); }
}
}
static ProbePlayer player(String surface,boolean sprint,double y) throws Exception {
var level=(PlaneLevel)ALLOCATOR.allocateInstance(PlaneLevel.class);
level.surface=block(surface).defaultBlockState();
var player=(ProbePlayer)ALLOCATOR.allocateInstance(ProbePlayer.class);
player.plane=level; player.grounded=y==0; player.sprint=sprint;
player.coordinates=new Vec3(0,y,0); player.velocity=new Vec3(0,y==0?-0.0784000015258789:0,0);
player.defaults=Player.createAttributes().build();
player.testAbilities=new Abilities();
player.obstacles=List.of(Shapes.create(new AABB(-1000,-1,-1000,1000,0,1000)));
level.obstacles=player.obstacles; level.border=new WorldBorder();
field(player,Entity.class,"position",player.coordinates);
field(player,Entity.class,"type",EntityTypes.PLAYER);
field(player,Entity.class,"level",level);
field(player,Entity.class,"blockPosition",BlockPos.ZERO);
field(player,Player.class,"abilities",player.testAbilities);
return player;
}
static JsonObject trajectory(String surface,boolean sprint,boolean jump,double y,int moveTicks,int ticks) throws Exception {
return trajectory(surface,sprint,jump,y,moveTicks,ticks,"air",0,false);
}
static JsonObject trajectory(String surface,boolean sprint,boolean jump,double y,int moveTicks,int ticks,String medium,double fluidDepth,boolean flying) throws Exception {
var player=player(surface,sprint,y);
player.medium=medium; player.fluidDepth=fluidDepth; player.testAbilities.flying=flying;
var out=new JsonObject(); out.addProperty("surface",surface); out.addProperty("sprint",sprint);
out.addProperty("medium",medium); out.addProperty("fluid_depth",fluidDepth); out.addProperty("flying",flying);
out.addProperty("jump_first_tick",jump); out.addProperty("input_ticks",moveTicks);
out.add("initial_position",vector(player.coordinates)); out.add("initial_velocity",vector(player.velocity));
var samples=new JsonArray();
for(int tick=1;tick<=ticks;tick++) {
// Match the documented aiStep input boundary. These two threshold rules
// are harness preparation, not claimed as a full original aiStep call.
var v=player.velocity;
double x=v.x,z=v.z;
if(v.horizontalDistanceSqr()<9e-6) { x=0; z=0; }
player.velocity=new Vec3(x,Math.abs(v.y)<.003?0:v.y,z);
if(jump&&tick==1) {
if("air".equals(medium)) player.jumpFromGround(); else player.liquidJump();
}
player.travel(new Vec3(0,0,tick<=moveTicks?(double).98f:0));
var sample=new JsonObject(); sample.addProperty("tick",tick);
sample.add("position",vector(player.coordinates)); sample.add("velocity",vector(player.velocity));
sample.addProperty("on_ground",player.grounded); samples.add(sample);
}
out.add("samples",samples); return out;
}
static JsonObject callbacks() throws Exception {
var root=new JsonObject(); var bounces=new JsonArray();
var bounce=Entity.class.getDeclaredMethod("restituteMovementAfterCollisions",BlockState.class,boolean.class,boolean.class,Vec3.class);
bounce.setAccessible(true);
for(String name:List.of("stone","slime_block","white_bed"))
for(double incoming:List.of(-.0784000015258789,-.3,-1.0))
for(double fraction:List.of(0.0,.25,.9)) {
var p=player(name,false,0); p.velocity=new Vec3(.1,incoming,.2);
p.verticalCollision=true; p.verticalCollisionBelow=true;
bounce.invoke(p,block(name).defaultBlockState(),false,false,new Vec3(.1,incoming*fraction,.2));
var v=new JsonObject(); v.addProperty("surface",name); v.addProperty("incoming_y",incoming);
v.addProperty("moved_fraction",fraction); v.add("velocity",vector(p.velocity)); bounces.add(v);
}
root.add("vertical_collision_restitution",bounces);
var slides=new JsonArray();
var slide=HoneyBlock.class.getDeclaredMethod("doSlideMovement",Entity.class); slide.setAccessible(true);
for(double incoming:List.of(-.16,-.3,-1.0)) {
var p=player("honey_block",false,10); p.velocity=new Vec3(.1,incoming,.2);
slide.invoke(Blocks.HONEY_BLOCK,p);
var v=new JsonObject(); v.addProperty("incoming_y",incoming); v.add("velocity",vector(p.velocity)); slides.add(v);
}
root.add("honey_slide_callback",slides);
var slimeSteps=new JsonArray();
for(double incoming:List.of(0.0,-.0784000015258789,.05,.2)) {
var p=player("slime_block",false,0); p.velocity=new Vec3(.1,incoming,.2);
Blocks.SLIME_BLOCK.stepOn(p.plane,BlockPos.ZERO,Blocks.SLIME_BLOCK.defaultBlockState(),p);
var v=new JsonObject(); v.addProperty("incoming_y",incoming); v.add("velocity",vector(p.velocity)); slimeSteps.add(v);
}
root.add("slime_step_callback",slimeSteps);
var bubbles=new JsonArray();
for(boolean above:List.of(false,true))for(boolean down:List.of(false,true))for(double incoming:List.of(-1.0,0.0,1.0)) {
var p=player("stone",false,10); p.velocity=new Vec3(.1,incoming,.2);
// Null private level suppresses the callback's optional server particles.
field(p,Entity.class,"level",null);
if(above)p.onAboveBubbleColumn(down,BlockPos.ZERO); else p.onInsideBubbleColumn(down);
var v=new JsonObject(); v.addProperty("above",above); v.addProperty("down",down);
v.addProperty("incoming_y",incoming); v.add("velocity",vector(p.velocity)); bubbles.add(v);
}
root.add("bubble_column_callback",bubbles); return root;
}
static JsonArray currentCases() throws Exception {
var result=new JsonArray();
var tracker=Class.forName("net.minecraft.world.entity.EntityFluidInteraction$Tracker");
var constructor=tracker.getDeclaredConstructor(); constructor.setAccessible(true);
var accumulate=tracker.getDeclaredMethod("accumulateCurrent",Vec3.class); accumulate.setAccessible(true);
var apply=tracker.getDeclaredMethod("applyCurrentTo",Entity.class,double.class); apply.setAccessible(true);
for(double incomingX:List.of(0.0,.01))for(double currentX:List.of(.001,.01,1.0)) {
var p=player("stone",false,10); p.velocity=new Vec3(incomingX,0,0);
var state=constructor.newInstance(); accumulate.invoke(state,new Vec3(currentX,0,0));
apply.invoke(state,p,.014);
var v=new JsonObject(); v.addProperty("incoming_x",incomingX); v.addProperty("current_x",currentX);
v.addProperty("strength",.014); v.add("velocity",vector(p.velocity)); result.add(v);
}
return result;
}
static JsonObject collisionCase(String label,double y,boolean grounded,Vec3 requested,List<AABB> boxes) throws Exception {
var p=player("stone",false,y); p.grounded=grounded;
p.plane.obstacles=boxes.stream().map(Shapes::create).toList();
field(p,Entity.class,"bb",p.getDefaultDimensions(Pose.STANDING).makeBoundingBox(p.coordinates));
var method=Entity.class.getDeclaredMethod("collide",Vec3.class); method.setAccessible(true);
var result=(Vec3)method.invoke(p,requested);
var out=new JsonObject(); out.addProperty("name",label); out.addProperty("on_ground",grounded);
out.add("position",vector(p.coordinates)); out.add("requested",vector(requested)); out.add("result",vector(result));
out.addProperty("step_height",(double)p.maxUpStep());
var geometry=new JsonArray();
for(var b:boxes) {
var object=new JsonObject(); object.add("min",vector(new Vec3(b.minX,b.minY,b.minZ)));
object.add("max",vector(new Vec3(b.maxX,b.maxY,b.maxZ))); geometry.add(object);
}
out.add("boxes",geometry); return out;
}
static JsonArray collisionCases() throws Exception {
var result=new JsonArray();
var floor=new AABB(-10,-1,-10,10,0,10);
var slab=new AABB(.8,0,-1,1.8,.5,1);
var full=new AABB(.8,0,-1,1.8,1,1);
var thin=new AABB(.8,0,-1,1.8,.0625,1);
var ceiling=new AABB(-1,2.3,-1,2,2.4,1);
var wall=new AABB(-1,0,.8,2,2,1.8);
result.add(collisionCase("half_slab",0,true,new Vec3(.8,-.0784,0),List.of(floor,slab)));
result.add(collisionCase("half_slab_low_ceiling",0,true,new Vec3(.8,-.0784,0),List.of(floor,slab,ceiling)));
result.add(collisionCase("full_block",0,true,new Vec3(.8,-.0784,0),List.of(floor,full)));
result.add(collisionCase("thin_step",0,true,new Vec3(.8,-.0784,0),List.of(floor,thin)));
result.add(collisionCase("lowest_improving_step",0,true,new Vec3(.8,-.0784,0),List.of(floor,new AABB(.4,0,-1,1.4,.125,1),slab)));
result.add(collisionCase("descending_into_step",.2,false,new Vec3(.8,-.4,0),List.of(floor,slab)));
result.add(collisionCase("corner_major_z",0,true,new Vec3(.8,-.0784,.9),List.of(floor,slab,wall)));
result.add(collisionCase("airborne_no_step",0,false,new Vec3(.8,.1,0),List.of(floor,slab)));
return result;
}
public static void main(String[] args) throws Exception {
SharedConstants.tryDetectVersion(); Bootstrap.bootStrap();
var root=new JsonObject(); root.addProperty("version","26.2");
var surfaces=new JsonObject();
for(String name:List.of("stone","ice","packed_ice","blue_ice","frosted_ice","slime_block","honey_block","soul_sand","soul_soil","white_bed","water","lava","cobweb","powder_snow","ladder","scaffolding")) {
var b=block(name); var values=new JsonObject();
values.addProperty("friction",(double)b.getFriction());
values.addProperty("speed_factor",(double)b.getSpeedFactor());
values.addProperty("jump_factor",(double)b.getJumpFactor());
values.addProperty("bounce_restitution",(double)b.getBounceRestitution());
surfaces.add(name,values);
}
root.add("surfaces",surfaces);
var attrs=new JsonObject(); var defaults=Player.createAttributes().build();
for(var holder:BuiltInRegistries.ATTRIBUTE.listElements().toList())
if(defaults.hasAttribute(holder))attrs.addProperty(holder.unwrapKey().get().identifier().toString(),defaults.getBaseValue(holder));
root.add("player_attributes",attrs);
root.addProperty("sprint_attribute_modifier",sprintModifier());
var poses=new JsonObject(); var actor=player("stone",false,0);
for(var pose:List.of(Pose.STANDING,Pose.CROUCHING,Pose.SWIMMING,Pose.FALL_FLYING,Pose.SLEEPING)) {
var d=actor.getDefaultDimensions(pose); var values=new JsonObject();
values.addProperty("width",(double)d.width()); values.addProperty("height",(double)d.height());
values.addProperty("eye_height",(double)d.eyeHeight()); poses.add(pose.name().toLowerCase(Locale.ROOT),values);
}
root.add("poses",poses);
var fluids=new JsonObject();
for(String name:List.of("water","lava")) {
var levels=new JsonArray();
for(int level=0;level<=15;level++) {
var state=block(name).defaultBlockState().setValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.LEVEL,level);
var world=new SampleWorld(state); var fluid=state.getFluidState(); var values=new JsonObject();
values.addProperty("level",level); values.addProperty("height",(double)fluid.getHeight(world,BlockPos.ZERO));
values.addProperty("source",fluid.isSource()); levels.add(values);
}
fluids.add(name,levels);
}
root.add("fluid_levels",fluids);
var trajectories=new JsonObject();
trajectories.add("stone_walk_stop",trajectory("stone",false,false,0,20,40));
trajectories.add("stone_sprint_stop",trajectory("stone",true,false,0,20,40));
trajectories.add("stone_jump",trajectory("stone",false,true,0,0,16));
trajectories.add("stone_sprint_jump",trajectory("stone",true,true,0,15,20));
trajectories.add("ice_walk_stop",trajectory("ice",false,false,0,20,40));
trajectories.add("blue_ice_walk_stop",trajectory("blue_ice",false,false,0,20,40));
trajectories.add("water_move_stop",trajectory("stone",false,false,10,20,40,"water",1,false));
trajectories.add("water_sprint_stop",trajectory("stone",true,false,10,20,40,"water",1,false));
trajectories.add("water_jump",trajectory("stone",false,true,10,0,12,"water",1,false));
trajectories.add("lava_deep_move_stop",trajectory("stone",false,false,10,20,40,"lava",1,false));
trajectories.add("lava_shallow_move_stop",trajectory("stone",false,false,10,20,40,"lava",.2,false));
trajectories.add("creative_fly_move_stop",trajectory("stone",false,false,10,20,40,"air",0,true));
trajectories.add("creative_fly_sprint_stop",trajectory("stone",true,false,10,20,40,"air",0,true));
root.add("travel_kernel_trajectories",trajectories);
root.add("callback_measurements",callbacks());
root.add("collision_cases",collisionCases());
root.add("fluid_current_cases",currentCases());
root.addProperty("measurement_scope","Public block and attribute APIs; original Player.travel, jumpFromGround and Entity.collideWithShapes execute in an isolated flat-plane harness. Player constructors and server constructors are bypassed. The harness supplies inputs, threshold preparation, the measured sprint attribute modifier, constant medium/depth and flat-plane movement bookkeeping. Collision cases call original Entity.collide including step selection on explicit boxes. Collision restitution, honey slide, slime step, bubble column and fluid current application are invoked separately. This is not an original full game tick, multiplayer, fluid-world sampling, or automatic callback-dispatch measurement. The water sprint case intentionally leaves swimming pose false to isolate travelInWater.");
Files.writeString(Path.of(args[0]),GSON.toJson(root)+"\n");
System.out.println("Measured "+surfaces.size()+" surfaces, "+poses.size()+" poses, "+trajectories.size()+" original travel-kernel trajectories.");
}
}