content
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
= Global Ticking Order
|
||||
= Global Ticking Order <timing>
|
||||
|
||||
== Tilesets
|
||||
=== Binary
|
||||
=== Lexicographic
|
||||
=== Mixed Priority Jamming
|
||||
|
||||
== Block Event Delay
|
||||
|
||||
== Single-Priority Loops
|
||||
3
lib.typ
3
lib.typ
@@ -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:")
|
||||
|
||||
4
main.typ
4
main.typ
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user