content
This commit is contained in:
@@ -1,5 +1,69 @@
|
|||||||
|
#import "/lib.typ": note, tip, todo
|
||||||
|
|
||||||
= Core Mechanics
|
= Core Mechanics
|
||||||
|
|
||||||
== Entity IDs
|
== 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
|
== 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
|
== 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
|
= 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
|
== 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>
|
== 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
|
== 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
|
== Tilesets
|
||||||
=== Binary
|
=== Binary
|
||||||
=== Lexicographic
|
=== Lexicographic
|
||||||
=== Mixed Priority Jamming
|
=== Mixed Priority Jamming
|
||||||
|
|
||||||
== Block Event Delay
|
== Block Event Delay
|
||||||
|
|
||||||
== Single-Priority Loops
|
== Single-Priority Loops
|
||||||
3
lib.typ
3
lib.typ
@@ -12,4 +12,5 @@
|
|||||||
|
|
||||||
#let note = callout.with(kind: "note", label: "Note:")
|
#let note = callout.with(kind: "note", label: "Note:")
|
||||||
#let warn = callout.with(kind: "warn", label: "Warning:")
|
#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))
|
link(index().location(), title(index().title))
|
||||||
let target = selector(heading).within(chapter.get().loc).or(heading.where(level: 1))
|
let target = selector(heading).within(chapter.get().loc).or(heading.where(level: 1))
|
||||||
outline(title: none, target: target)
|
outline(title: none, target: target)
|
||||||
|
divider()
|
||||||
})
|
})
|
||||||
html.main(id: "main", main)
|
html.main(id: "main", main)
|
||||||
html.footer(context {
|
html.footer(context {
|
||||||
let next = query(selector(heading.where(level: 1, outlined: true)).after(here())).first(default: none)
|
let next = query(selector(heading.where(level: 1, outlined: true)).after(here())).first(default: none)
|
||||||
|
|
||||||
if next != none {
|
if next != none {
|
||||||
|
divider()
|
||||||
show outline.entry: it => [Next: #it]
|
show outline.entry: it => [Next: #it]
|
||||||
outline(title: none, target: next.location())
|
outline(title: none, target: next.location())
|
||||||
}
|
}
|
||||||
@@ -54,7 +56,7 @@
|
|||||||
rawdoc("/index.html", include "content/00-index.typ")
|
rawdoc("/index.html", include "content/00-index.typ")
|
||||||
doc("/core.html", include "content/01-core.typ")
|
doc("/core.html", include "content/01-core.typ")
|
||||||
doc("/interference.html", include "content/02-interference.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("/design.html", include "content/04-design.typ")
|
||||||
doc("/channels.html", include "content/05-channels.typ")
|
doc("/channels.html", include "content/05-channels.typ")
|
||||||
doc("/transport.html", include "content/06-transport.typ")
|
doc("/transport.html", include "content/06-transport.typ")
|
||||||
|
|||||||
@@ -22,6 +22,12 @@
|
|||||||
color: mediumblue;
|
color: mediumblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.todo {
|
||||||
|
color: red;
|
||||||
|
border-block-width: 9px !important;
|
||||||
|
border-style: double !important;
|
||||||
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
:root {
|
:root {
|
||||||
font: 10pt / 1.5 serif
|
font: 10pt / 1.5 serif
|
||||||
@@ -46,6 +52,10 @@ h1, h2, h3, h4, h5, h6, nav {
|
|||||||
font-family: Mojangles, monospace;
|
font-family: Mojangles, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
padding-inline-start: 2ch;
|
padding-inline-start: 2ch;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user