Files
wireless-docs/content/core.typ
2026-06-22 15:03:43 -04:00

296 lines
10 KiB
Typst

#import "/lib.typ": callout, details, example, note, solution, tip, todo
= Core Mechanics <core>
== Entity IDs
Entities are the dynamic movable objects in the game. Minecraft tracks every entity with a unique ID
number. Whenever an entity is added to the game world, a global counter is incremented and the new
entity is assigned the current value.
A large number of game objects are tracked in this way, and so creating any one of them will
increment that global counter. To reiterate, *this is a global counter, shared for all entities in
the game*.
== Stationary Item Optimization
Typically, item entities fall to the floor and sit stationary soon after being created. Processing
movement for these stationary entities would be wasteful, so an optimization was added in 1.14: if
an item is sitting stationary on a block, then only process movement for that item every 4th tick.
However, it would also be wasteful if every 4th tick processed all items, so instead the items are
divided into four staggered groups. Each tick stationary items from only one of the four groups
check for movement.
#callout(kind: "tip", label: "Key Point:")[
Stationary items only begin to fall when their $"age" + "id"$ is a multiple of $4$.
]
In other words, divide the value by 4 and look at the remainder. If the remainder is 0, the item
checks for movement. That condition is written as `... % 4 == 0` in Java and as $... equiv 0 &(mod
4)$ in math.
#tip[
You can freely add or subtract multiples of 4 to any expression without changing the value $mod 4$.
For example, $-1 equiv 3 &(mod 4)$ because $3 = -1 + 4$.
]
#details(label: [the relevant Java code])[
#note[ `onGround` is only ever updated via `move`, and this branch is the *only* place that
`ItemEntity.move` is called. `tickCount` is the item's age. ]
```java
public class ItemEntity extends Entity {
public void tick() {
...
if (this.onGround() && !(this.getDeltaMovement().horizontalDistanceSqr() > (double)1.0E-5F) && (this.tickCount + this.getId()) % 4 != 0) {
...
} else {
this.move(MoverType.SELF, this.getDeltaMovement());
...
}
...
}
}
public class Entity {
public void move(final MoverType moverType, Vec3 delta) {
...
this.setOnGroundWithMovement(this.verticalCollisionBelow, this.horizontalCollision, movement);
...
}
public void setOnGroundWithMovement(final boolean onGround, final boolean horizontalCollision, final Vec3 movement) {
this.onGround = onGround;
...
}
}
```
]
The effect is that if the item is on a block and has negligible horizontal momentum, it *cannot
fall* until the condition is satisfied, even if it's no longer on the ground. It can only fall early
if it gains horizontal momentum or is pushed.
#todo[Falling item animation][Spawn a few items, note their ID and age. Let them settle on a block, then
remove that supporting block. There will be a few ticks where the item hovers in place before it
falls.]
== Observable Drop Delay
We can't control the absolute entity ID a particular item will receive, but we can *compare* ids of
multiple entities. By observing the drop delay for a "reference" item, we can predict the behavior
of other items.
#example[
For example, suppose we spawn an item $A$, then 2 game ticks later we spawn an item $B$. Assume no
other entities spawn between them, so $A$ and $B$ have consecutive IDs. Wait for the items to
settle, then remove the supporting blocks. If we observe item $A$ start to fall 1 game tick after
the support is removed, we can predict when item $B$ will fall.
It'll be easier if we orient ourselves around the tick when the support blocks are removed, call
that $t=0$. Say the age is $"age"_(A, 0)$ at that time. We have that item $A$ falls at $t=1$.
$A$ fell when it was $"age"_(A, 1) = "age"_(A, 0) + 1$ old, so we can put all this into a relation
that pins down the mod 4 cycle.
$ "age"_(A, 0) + 1 + "id"_A equiv 0 (mod 4) $
We want to find the equivalent relation for $B$, where $x$ will be the tick in which $B$ falls.
$ "age"_(B, 0) + x + "id"_B equiv 0 (mod 4) $
We know that $A$ has the item ID immediately before $B$, so $"id"_A = "id"_B - 1$.
We also know that $A$ is 2gt older than $B$, so $"age"_(A, 0) = "age"_(B, 0) + 2$.
So take the relation for $A$ and substitute in these values for $B$.
$
"age"_(A, 0) + 1 + "id"_A & equiv 0 (mod 4) \
"age"_(B, 0) + 2 + 1 + "id"_B - 1 & equiv 0 (mod 4) \
"age"_(B, 0) + 2 + "id"_B & equiv 0 (mod 4) \
$
Compare with the relation for $B$ and we see that $x=2$.
#callout(kind: "tip", label: "Therefore:")[$B$ falls at time $t=2$.]
]
#example[
Suppose we spawn four items in order. For simplicity, spawn them all in the same tick so their
ages are equal, and assume no other entities spawn.
1. Spawn items $A$, $B$, $C$, and $D$ in order in the same tick. Let them settle on a block.
2. At tick $t=0$, remove the supporting blocks.
3. At tick $t=1$, item $B$ begins to fall.
Based on when item $B$ fell, when should we expect the other items to fall?
#solution[
To simplify the arithmetic, orient everything around $t=0$. So we'll let $"age"_0$ be the
items' ages at $t=0$ and add tick offsets from there. All items spawned in the same tick, so
they all have the same $"age"_0$.
Since $B$ falls at $t=1$, the relation for $"id"_B$ must be
$ "age"_0 + "id"_B + 1 equiv 0 & (mod 4) $
Item $A$ gets the ID 1 before $B$, so $"id"_A + 1 = "id"_B$.
Plug that in to the relation we got for $"id"_B$.
$ "age"_0 + "id"_A + 1 + 1 equiv 0 & (mod 4) $
*$A$ must fall at $t=2$.*
Item $C$ gets the ID 1 after $B$, so $"id"_C - 1= "id"_B$. Plug that in.
$ "age"_0 + "id"_C - 1 + 1 equiv 0 & (mod 4) $
*$C$ must fall at $t=0$.*
Item $D$ gets the ID 2 after $B$, so $"id"_D - 2 = "id"_B$. Plug that in.
$
"age"_0 + "id"_D - 2 + 1 & equiv 0 & (mod 4) \
"age"_0 + "id"_D + 3 & equiv 0 & (mod 4)
$
*$D$ must fall at $t=3$.*
#callout(kind: "tip", label: "Therefore:")[
The expected fall times are:
- $C$ at $t=0$.
- $B$ at $t=1$.
- $A$ at $t=2$.
- $D$ at $t=3$.
]
]
#todo[Demonstration]
]
#example[
Consider a variation of the previous example, but where the items do not spawn on the same tick.
Their ages will be different. Again, assume no other entities spawn.
1. Spawn item $A$ at $t=-10$.
2. Spawn items $B$ and $C$ in order at $t=-8$.
3. Spawn item $D$ at $t=-7$. Let them settle on a block.
4. At tick $t=0$, remove the supporting blocks.
5. At tick $t=1$, item $B$ begins to fall.
Based on when item $B$ fell, and accounting for the different item ages, when should we expect the
other items to fall?
#solution[
We'll need to track the item ages separately this time, but still oriented around $t=0$.
Let $"age"_(0, B)$ be item $B$'s age at $t=0$.
We know item $B$ fell at $t=1$, so we have the relation
$ "age"_(0, B) + "id"_B + 1 equiv 0 & (mod 4) $
#todo[Add "gt" to the glossary]
Item $A$ gets the ID 1 before $B$, so $"id"_A + 1 = "id"_B$.
It spawned 2gt earlier, so $"age"_(0, A) - 2= "age"_(0, B)$. Plug those in.
$ "age"_(0, A) - 2 + "id"_A + 1 + 1 equiv 0 & (mod 4) $
*$A$ must fall at $t=0$.*
Item $C$ gets the ID 1 after $B$, so $"id"_C - 1 = "id"_B$.
It spawned in the same tick, so $"age"_(0, C) = "age"_(0, B)$. Plug those in.
$ "age"_(0, C) + "id"_C - 1 + 1 equiv 0 & (mod 4) $
*$C$ must fall at $t=0$.*
Item $D$ gets the ID 2 after $B$, so $"id"_D - 2 = "id"_B$.
It spawned 1gt later, so $"age"_(0, D) + 1 = "age"_(0, B)$. Plug those in.
$ "age"_(0, D) + 1 + "id"_D - 2 + 1 equiv 0 & (mod 4) $
*$D$ must fall at $t=0$.*
#callout(kind: "tip", label: "Therefore:")[
The expected fall times are:
- $A$ at $t=0$.
- $C$ at $t=0$.
- $D$ at $t=0$.
- $B$ at $t=1$.
]
]
#todo[Demonstration]
]
#example[
Now consider what it means if the pattern is broken. Keep the item ages the same for simplicity,
but this time do *not* assume that no other entities spawn.
1. Spawn items $A$, $B$, and $C$ in order in the same tick. Let them settle on a block.
2. At tick $t=0$, remove the supporting blocks. Item $C$ begins to fall.
3. At tick $t=1$, item $A$ begins to fall.
4. At tick $t=2$, item $B$ begins to fall.
What number of entities must have spawned between items $A$ and $B$? Between $B$ and $C$?
#solution[
Following the same conventions as before, where $x$ is the number of entities between $A$ and
$B$ and $y$ is the number of entities between $B$ and $C$.
The ids of $A$ and $B$ are related by
$ "id"_A + 1 + x = "id"_B $
And the observed drop delays satisfy
$ "age"_0 + "id"_A + 1 equiv 0 & (mod 4) $
$ "age"_0 + "id"_B + 2 equiv 0 & (mod 4) $
Substitute $"id"_B$ and solve for $x$.
$
"age"_0 + "id"_A + 1 + x + 2 & equiv "age"_0 + "id"_A + 1 & (mod 4) \
x + 2 & equiv 0 & (mod 4) \
x & equiv 2 & (mod 4) \
$
*So the number of entities between $A$ and $B$ is $2 &(mod 4)$.* (For example, 2, 6, 10, ...)
The ids of $B$ and $C$ are related by
$ "id"_B + 1 + y = "id"_C $
And the observed drop delays satisfy
$ "age"_0 + "id"_B + 2 equiv 0 & (mod 4) $
$ "age"_0 + "id"_C + 0 equiv 0 & (mod 4) $
Substitute $"id"_C$ and solve for $y$.
$
"age"_0 + "id"_B + 1 + y + 0 & equiv "age"_0 + "id"_B + 2 & (mod 4) \
y - 1 & equiv 0 & (mod 4) \
y & equiv 1 & (mod 4) \
$
*So the number of entities between $B$ and $C$ is $1 &(mod 4)$.* (For example, 1, 5, 9, ...)
#callout(kind: "tip", label: "Therefore:")[
The number of spawned entities must be
- $2 &(mod 4)$ between $A$ and $B$.
- $1 &(mod 4)$ between $B$ and $C$.
]
]
#todo[Demonstration]
]