diff --git a/content/01-core.typ b/content/01-core.typ index 3e20df4..a675dda 100644 --- a/content/01-core.typ +++ b/content/01-core.typ @@ -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 diff --git a/content/02-interference.typ b/content/02-interference.typ index e9f3361..7efe4c4 100644 --- a/content/02-interference.typ +++ b/content/02-interference.typ @@ -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) + +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) + +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 -== 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.] diff --git a/content/03-tileset.typ b/content/03-timing.typ similarity index 77% rename from content/03-tileset.typ rename to content/03-timing.typ index 7d1387a..12bab17 100644 --- a/content/03-tileset.typ +++ b/content/03-timing.typ @@ -1,8 +1,10 @@ -= Global Ticking Order += Global Ticking Order == Tilesets === Binary === Lexicographic === Mixed Priority Jamming + == Block Event Delay + == Single-Priority Loops diff --git a/lib.typ b/lib.typ index 55c4bf1..b3738bf 100644 --- a/lib.typ +++ b/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:") \ No newline at end of file +#let tip = callout.with(kind: "tip", label: "Tip:") +#let todo = callout.with(kind: "todo", label: "TODO:") diff --git a/main.typ b/main.typ index 51f11a9..631c50b 100644 --- a/main.typ +++ b/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") diff --git a/static/style.css b/static/style.css index ce487d3..0b8ce57 100644 --- a/static/style.css +++ b/static/style.css @@ -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; }