This commit is contained in:
2026-06-20 01:13:14 -04:00
parent 85c67e1334
commit 0e93efe76d
6 changed files with 155 additions and 5 deletions

View File

@@ -1,5 +1,69 @@
#import "/lib.typ": note, tip, todo
= Core Mechanics
== 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 check for gravity every 4 ticks.
Here is the relevant code path for the optimization.
#note[ `onGround` is only ever updated from `move()`, and this branch is the *only* place that
`move()` is called. ]
```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;
...
}
}
```
== Observable Drop Delay
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 fall early if
it gains horizontal momentum or is pushed.
#tip[ You can rewrite the condition as `(age % 4 == (-id) % 4)` ]
#todo[ An 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. ]
For simplicity, suppose we spawn all our items on the same tick. Then `this.tickCount` will be the
same for all of them, and we only need to consider the ID.
The result of this is that if you spawn several item entities, allow them to settle on a block, then
remove that block

View File

@@ -1,7 +1,78 @@
#import "/lib.typ": note, tip, todo
= Interference
We need precise control over the entity age and ID for this to work, so sources of interference are
generally unexpected entity spawns that mess up the ID.
== Other Entities
== Single-Player (before 26.2) <singleplayer>
Recall that *any* entity spawning will increment the ID counter, and the full list of entities is
surprising. An exhaustive list is available at
#link("https://minecraft.wiki/w/Entity#Types_of_entities")[The Minecraft Wiki].
In general, the solution is to spawn our item entities at nearly the same time, so there is no
chance for other entities to spawn in-between. For example, by spawning all items with droppers in
the same game tick, all the items are created in the same tick phase, so player inputs and world
events cannot affect it. See @timing for details.
== Singleplayer (before 26.2) <singleplayer>
In singleplayer worlds, there are separate execution threads for the renderer and game world. The
game world ("server thread") contains the item entities we care about for the purposes of EID
Wireless, as these are the entities we can detect with redstone. However, the renderer ("client
thread") *also* tracks some entities.
In versions before 26.2, there was a bug that caused the client thread and server thread to use the
*same* global ID counter. Therefore every entity might increment the counter twice: once when it
spawns in the server, and once when the client begins to track it.
The solution is to either use a dedicated minecraft server, so that the client and servers run in
separate processes, or install a mod which patches the game to use a separate counter for the
client.
#todo[Grab links for these mods and figure out exactly which versions they're good for.]
== Paper Servers <paper>
== Lazy Chunks
#todo[Figure out which versions of Paper broke the thing.]
Most recent versions of Paper server are compatible with EID Wireless. However, there are old
versions of Spigot which include a *different* stationary item optimization which corrupts this. A
few versions of Paper erroneously included this old optimization (on top of the mod 4 optimization)
which breaks EID Wireless.
#todo[Show the patch with the busted optimization.]
== Unloaded Chunks
When a chunk unloads and reloads, all entities contained within it are destroyed and recreated with
new entity IDs. Therefore any information encoded in the ID group offsets is destroyed.
#tip[ When a chunk containing an EID Wireless receiver is reloaded, every in-progress transmission
*MUST* be discarded. ]
The best thing to do is to use a reload detector and lock the receiver output for one full cycle
after a reload.
#todo[Get links to frost walker and sculk sensor reload detector designs.]
== Lazy Chunks
Entities in lazy chunks do not age or move, and their IDs are preserved. However, the redstone
circuitry will continue to function. If a chunk becomes lazy while a transmission is being
processed, the detection circuits will not observe the right drop delays and so the transmission is
corrupted.
#tip[ When a chunk containing an EID Wireless receiver becomes lazy, every in-progress transmission
*MUST* be discarded. ]
Further, depending on the receiver design, there is a small chance the items become clipped into
blocks. When the chunk becomes entity-processing again, the clipped items will fly out of the
machine instead of being recycled.
The best thing to do is to use a chunkloader to prevent the receiver ever being lazy-loaded. If
chunkloaders cannot be used, a lazy-chunk detector can be used to lock the outputs while the chunk
is lazy-loaded and for one full cycle after it becomes entity-processing.
#todo[Get links to lazy-chunk detectors.]

View File

@@ -1,8 +1,10 @@
= Global Ticking Order
= Global Ticking Order <timing>
== Tilesets
=== Binary
=== Lexicographic
=== Mixed Priority Jamming
== Block Event Delay
== Single-Priority Loops

View File

@@ -12,4 +12,5 @@
#let note = callout.with(kind: "note", label: "Note:")
#let warn = callout.with(kind: "warn", label: "Warning:")
#let tip = callout.with(kind: "tip", label: "Tip:")
#let tip = callout.with(kind: "tip", label: "Tip:")
#let todo = callout.with(kind: "todo", label: "TODO:")

View File

@@ -38,12 +38,14 @@
link(index().location(), title(index().title))
let target = selector(heading).within(chapter.get().loc).or(heading.where(level: 1))
outline(title: none, target: target)
divider()
})
html.main(id: "main", main)
html.footer(context {
let next = query(selector(heading.where(level: 1, outlined: true)).after(here())).first(default: none)
if next != none {
divider()
show outline.entry: it => [Next: #it]
outline(title: none, target: next.location())
}
@@ -54,7 +56,7 @@
rawdoc("/index.html", include "content/00-index.typ")
doc("/core.html", include "content/01-core.typ")
doc("/interference.html", include "content/02-interference.typ")
doc("/tileset.html", include "content/03-tileset.typ")
doc("/timing.html", include "content/03-timing.typ")
doc("/design.html", include "content/04-design.typ")
doc("/channels.html", include "content/05-channels.typ")
doc("/transport.html", include "content/06-transport.typ")

View File

@@ -22,6 +22,12 @@
color: mediumblue;
}
.todo {
color: red;
border-block-width: 9px !important;
border-style: double !important;
}
@media print {
:root {
font: 10pt / 1.5 serif
@@ -46,6 +52,10 @@ h1, h2, h3, h4, h5, h6, nav {
font-family: Mojangles, monospace;
}
pre {
overflow-x: scroll;
}
ol {
padding-inline-start: 2ch;
}