From bb71b7e872809eb33c0409a0c5c6596dd93074de Mon Sep 17 00:00:00 2001 From: David Allemang Date: Wed, 29 Jul 2026 12:28:56 -0400 Subject: [PATCH] big revamp tilesets intro --- content/tilesets.typ | 374 +++++++++++++++++++++++++++++++++---------- src/style.css | 16 +- 2 files changed, 301 insertions(+), 89 deletions(-) diff --git a/content/tilesets.typ b/content/tilesets.typ index c6581c9c..faf19350 100644 --- a/content/tilesets.typ +++ b/content/tilesets.typ @@ -1,17 +1,19 @@ #import "/lib.typ": diorama, example, note, todo -#show raw.where(lang: "mc-diorama"): it => diorama(autoplay: true, loop: true, theta: 160, phi: 20, it.text) = Tilesets The protocols as described are very sensitive to the exact timing that the EID counter is -incremented. The fundamental cause for all types of interference is that we spawn a reference and -measurement item with too much delay, and some unexpected entity (or entities) spawn in-between. To -mitigate interference, then, we need that window to be *as short as possible* while still allowing -us to spawn transmission entities in that window. +incremented. The fundamental cause for all types of interference is that there is a chance for +unexpected entities to spawn in-between the measurement items, which increments the counter and +corrupts the data. The solution, then, is to make that window *as short as possible* so the chance +for interference is minimized. -We use the tile tick priority queue to define a global order of sub-tick execution. +The fundamental principle is to use the game scheduled tick priority queue to do this. A *tile +sequence* is a chain of redstone components that all update in the scheduled tick phase. A *tile +set* is a system of constructing tile sequences that enforces a particular global ordering. +#show raw.where(lang: "mc-diorama"): it => diorama(autoplay: true, loop: true, theta: 160, phi: 20, it.text) ```mc-diorama p 0 -1 0 smooth_stone_slab type=top p 1 -1 0 smooth_stone_slab type=top @@ -45,112 +47,241 @@ t 20 p 0 0 0 ``` -#todo[fix tileset naming. the tileset is the *structure*. the particular sequence is just one instance of that tileset] +== The Scheduled Tick Priority Queue -A *tile sequence* is a chain of redstone components that all update in the tile tick phase. -The fundamental principle here is to use tile-tick priority (TTP) to set a global order. -#link("https://youtu.be/sLftwVwqPQE")[See Charlie's great video on TTP for details]. +There are two aspects that affect a scheduled tick's placement in the queue: its _priority_ and its +_delay_. Scheduled ticks with higher (more negative) priority update first in a given game tick. +Scheduled ticks with equal priority update in the order they were scheduled. -A *tile set* is a system of constructing tile sequences that enforces a particular global ordering. +See @block-priority for a list of priorities. The most important point is that repeaters have higher +priority than other components. + +== Binary Tilesets + +The simplest (and generally most useful) tilesets use only two priorities. This allows us to +identify each tile sequence with a binary value and easily read the update order and carve up the +the full tile set for different systems. Typically we use comparators (or observers) and 2gt +repeaters. + +We identify the higher priority (repeater) with `0`, since it sorts/updates earlier, and identify +the lower priority (observer/comparator) with `1`. Then we can read off the elements of the tile +sequence to form a binary number. Components that activate later in the sequence have a stronger effect on the order, so to match our left-to-right reading convention, we diagram tile sequences so the signal flows right-to-left. -== The Tile Tick Priority Queue +Consider this 4gt tileset. -The game schedules tile updates through a priority queue; different components have different -priority, so each stage of the tileset iteratively refines the global order. Across the full -tileset, we can enforce an arbitrary global ordering. +#show raw.where(lang: "mc-diorama"): it => diorama( + autoplay: true, + loop: true, + theta: 90, + phi: 90, + speed: 0.33, + radius: 8, + orbit: false, + it.text, +) -#todo[the priority table][ - call out here that basic comparators and all other components, so from here on out we're just - going to use comparators for simplicity. -] +```mc-diorama +p 0 -1 0 smooth_stone_slab type=top +p 1 -1 0 smooth_stone_slab type=top +p 2 -1 0 smooth_stone_slab type=top +p 3 -1 0 smooth_stone_slab type=top +p 4 -1 0 smooth_stone_slab type=top +p 5 -1 0 smooth_stone_slab type=top +p 6 -1 0 smooth_stone_slab type=top +p 7 -1 0 smooth_stone_slab type=top -Components with different priority always update in priority order, but components with equal -priority update in scheduled order. +p 0 0 0 redstone_wire east=side west=none south=side power=0 +p 1 0 0 redstone_wire east=side west=side south=side power=0 +p 2 0 0 redstone_wire east=side west=side south=side power=0 +p 3 0 0 redstone_wire east=side west=side south=side power=0 +p 4 0 0 redstone_wire east=side west=side south=side power=0 +p 5 0 0 redstone_wire east=side west=side south=side power=0 +p 6 0 0 redstone_wire east=side west=side south=side power=0 +p 7 0 0 redstone_wire east=none west=side south=side power=0 -#todo[rephrase or remove][ - The core principal is that repeaters have higher priority than other components. For example, if a - repeater and comparator are scheduled to activate in the same tick, the repeater always (with one - exception, if the comparator faces into a diode and the repeater does not) activates before the - comparator does. By choosing a regular building pattern, we can avoid this exception entirely. +p 0 0 1 repeater facing=north delay=1 powered=false locked=false +p 1 0 1 comparator facing=north mode=compare powered=false +p 2 0 1 repeater facing=north delay=1 powered=false locked=false +p 3 0 1 comparator facing=north mode=compare powered=false +p 4 0 1 repeater facing=north delay=1 powered=false locked=false +p 5 0 1 comparator facing=north mode=compare powered=false +p 6 0 1 repeater facing=north delay=1 powered=false locked=false +p 7 0 1 comparator facing=north mode=compare powered=false - // Among repeaters, they activate in the order in which they were scheduled. And among - // comparators, they activate in the order in which they are scheduled. But every repeater - // activates before any comparator (aside from that one exception). This gives us a *stable sort* - // which we can use to globally define update order with arbitrary precision. +p 0 0 2 repeater facing=north delay=1 powered=false locked=false +p 1 0 2 repeater facing=north delay=1 powered=false locked=false +p 2 0 2 comparator facing=north mode=compare powered=false +p 3 0 2 comparator facing=north mode=compare powered=false +p 4 0 2 repeater facing=north delay=1 powered=false locked=false +p 5 0 2 repeater facing=north delay=1 powered=false locked=false +p 6 0 2 comparator facing=north mode=compare powered=false +p 7 0 2 comparator facing=north mode=compare powered=false -] +p 0 0 3 repeater facing=north delay=1 powered=false locked=false +p 1 0 3 repeater facing=north delay=1 powered=false locked=false +p 2 0 3 repeater facing=north delay=1 powered=false locked=false +p 3 0 3 repeater facing=north delay=1 powered=false locked=false +p 4 0 3 comparator facing=north mode=compare powered=false +p 5 0 3 comparator facing=north mode=compare powered=false +p 6 0 3 comparator facing=north mode=compare powered=false +p 7 0 3 comparator facing=north mode=compare powered=false -#example[ - #todo[this explanation is so janky] +t 0 +p 0 0 0 power=15 +p 1 0 0 power=15 +p 2 0 0 power=15 +p 3 0 0 power=15 +p 4 0 0 power=15 +p 5 0 0 power=15 +p 6 0 0 power=15 +p 7 0 0 power=15 - Say these three tilesets are started in the same gametick, `t=0`. +t 1 +p 0 0 1 powered=true +p 2 0 1 powered=true +p 4 0 1 powered=true +p 6 0 1 powered=true - ``` - <------ - 1 rep cmp ab - 2 cmp rep cd - 3 2rep e - <------ - ``` +t 2 +p 1 0 1 powered=true +p 3 0 1 powered=true +p 5 0 1 powered=true +p 7 0 1 powered=true - Once `b`, `d`, and `e` are scheduled, the priority queue looks like this: +t 3 +p 0 0 0 power=0 +p 1 0 0 power=0 +p 2 0 0 power=0 +p 3 0 0 power=0 +p 4 0 0 power=0 +p 5 0 0 power=0 +p 6 0 0 power=0 +p 7 0 0 power=0 - ``` - t=2 [d] [b] - t=4 [e] - ``` +t 4 +p 0 0 2 powered=true +p 4 0 2 powered=true +t 5 +p 1 0 2 powered=true +p 5 0 2 powered=true +t 6 +p 2 0 2 powered=true +p 6 0 2 powered=true +t 7 +p 3 0 2 powered=true +p 7 0 2 powered=true - Now at `t=2`, we process the queue in order. +t 8 +p 0 0 1 powered=false +p 2 0 1 powered=false +p 4 0 1 powered=false +p 6 0 1 powered=false - - `d` activates and schedules `c`. - - `b` activates and schedules `a`. +t 9 +p 0 0 3 powered=true +t 10 +p 1 0 3 powered=true +t 11 +p 2 0 3 powered=true +t 12 +p 3 0 3 powered=true - ``` - t=4 [e a] [c] - ``` +t 13 +p 1 0 1 powered=false +p 3 0 1 powered=false +p 5 0 1 powered=false +p 7 0 1 powered=false - Now at `t=4`, the end of the tileset, the lanes will always update in order `3 1 2` +t 14 +p 4 0 3 powered=true +t 15 +p 5 0 3 powered=true +t 16 +p 6 0 3 powered=true +t 17 +p 7 0 3 powered=true -] +t 18 +p 0 0 2 powered=false +p 4 0 2 powered=false +t 19 +p 1 0 2 powered=false +p 5 0 2 powered=false +t 20 +p 2 0 2 powered=false +p 6 0 2 powered=false +t 21 +p 3 0 2 powered=false +p 7 0 2 powered=false -So the full picture involves arbitrary components and priorities. As long as all the tilesets have -the same total delay and end at the same time, we can determine a global ordering. However the -general picture is hard to reason about, we basically have to simulate the priority queue to make -predictions. If we restrict the design of the tilesets a bit, there are two simplifications we could -take to make things easier to reason about. +t 22 +p 0 0 3 powered=false +t 23 +p 1 0 3 powered=false +t 24 +p 2 0 3 powered=false +t 25 +p 3 0 3 powered=false +t 26 +p 4 0 3 powered=false +t 27 +p 5 0 3 powered=false +t 28 +p 6 0 3 powered=false +t 29 +p 7 0 3 powered=false -== Permutation Tilesets - -#todo[describe the mixed delay permutation tilesets and the alphabetization procedure] - -== Binary Tilesets - -The simplest tilesets are made entirely of comparators and 2gt repeaters (alternatively: observers -and 2gt repeaters). Repeaters activate before comparators, so if we think of it like sorting words -alphabetically, we can identify repeaters with "A" and comparators with "B". - -So, suppose we have a "binary tileset" that is 2gt long. There are two options, A and B. If we -alphabetize these, we see the A (repeater) always executes before the B (comparator). With such a -short tileset, that seems trivial; things get more interesting as we add more elements. - -``` -<-- -cmp (B) -rep (A) +t 30 +p 0 0 0 ``` -Now let's use 2 components for a 4gt tileset. There are now four options, AA, AB, BA, BB. We can -alphabetize these and see the order. +We can express the same tileset numerically like so: ``` -<------ -cmp cmp (BB) -cmp rep (BA) -rep cmp (AB) -rep rep (AA) +000 = 0 +001 = 1 +010 = 2 +011 = 3 +100 = 4 +101 = 5 +110 = 6 +111 = 7 +``` + +Using the same pattern, we can generate a tile sequence for an arbitrary value of arbitrary +bit-length. For example, the 8-bit tileset for `150` is: + +10010110 + +#show raw.where(lang: "mc-diorama"): it => diorama( + theta: 90, + phi: 90, + speed: 0.33, + radius: 5, + orbit: false, + it.text, +) + +```mc-diorama +p 0 -1 0 smooth_stone_slab type=top +p 0 -1 1 smooth_stone_slab type=top +p 0 -1 2 smooth_stone_slab type=top +p 0 -1 3 smooth_stone_slab type=top +p 0 -1 4 smooth_stone_slab type=top +p 0 -1 5 smooth_stone_slab type=top +p 0 -1 6 smooth_stone_slab type=top +p 0 -1 7 smooth_stone_slab type=top +p 0 0 0 repeater facing=north powered=false locked=false delay=1 +p 0 0 1 comparator facing=north powered=false mode=compare +p 0 0 2 comparator facing=north powered=false mode=compare +p 0 0 3 repeater facing=north powered=false locked=false delay=1 +p 0 0 4 comparator facing=north powered=false mode=compare +p 0 0 5 repeater facing=north powered=false locked=false delay=1 +p 0 0 6 repeater facing=north powered=false locked=false delay=1 +p 0 0 7 comparator facing=north powered=false mode=compare ``` To break this down: the bottom two (AA, AB) end in repeaters, so they must come first. Among those, @@ -197,6 +328,81 @@ next lane by simply incrementing by one. cmp rep rep cmp cmp rep rep ``` + +// So the full picture involves arbitrary components and priorities. As long as all the tilesets +// have +// the same total delay and end at the same time, we can determine a global ordering. However the +// general picture is hard to reason about, we basically have to simulate the priority queue to make +// predictions. If we restrict the design of the tilesets a bit, there are two simplifications we could +take to make things easier to reason about. + +== Permutation Tilesets + +#todo[describe the mixed delay permutation tilesets and the alphabetization procedure] + + == Lexicographic Tilesets == Jamming + +== Observer Bug + +== Tables + +=== Block Tick Priorities + +#table( + columns: 4, + table.cell(rowspan: 3)[Repeater], + [Facing diode (that is not facing it)], + [`EXTREMELY_HIGH`], + [-3], + [Powering off], + [`VERY_HIGH`], + [-2], + [Powering on], + [`HIGH`], + [-1], + table.cell(rowspan: 2)[Comparator], + [Facing diode (that is not facing it)], + [`HIGH`], + [-1], + [Otherwise], + [`NORMAL`], + [0], + table.cell(colspan: 2)[All other components], + [`NORMAL`], + [0], +) + +=== Block Tick Delays + +#table( + columns: 2, + [Repeater], [2gt, 4gt, 6gt, 8gt], + [Comparator], table.cell(rowspan: 4)[2gt], + [Redstone Torch], + [Falling Blocks], + [Observer (see @observer-bug)], + [Dispenser], table.cell(rowspan: 3)[4gt], + [Dropper], + [Redstone Lamp], + [Iron Pressure Plate], table.cell(rowspan: 4)[10gt], + [Gold Pressure Plate], + [Tripwire Hook], + [Tripwire], + [Stone Button], table.cell(rowspan: 3)[20gt], + [Stone Pressure Plate], + [Detector Rail], + [Wooden Button], table.cell(rowspan: 2)[30gt], + [Wooden Pressure Plate], +) + +=== Fluid Tick Delays + +#table( + columns: 2, + [Water], [5gt], + [Nether Lava], [10gt], + [Lava], [30gt], +) diff --git a/src/style.css b/src/style.css index d041959e..f23cbc19 100644 --- a/src/style.css +++ b/src/style.css @@ -17,7 +17,6 @@ body { font: 1rem / 1.5 sans-serif; } - .tip { color: forestgreen; } @@ -56,13 +55,11 @@ pre code { text-wrap: wrap; } -.callout, details { +.callout, details, table td { margin: 1em 0; padding: 0.5em 1ch; background: rgb(from currentColor r g b / 5%); - border: solid rgb(from currentColor r g b / 30%); - border-width: 1px; - border-style: outset; + border: 1px outset rgb(from currentColor r g b / 30%); } .callout { @@ -85,4 +82,13 @@ pre code { summary::marker { content: "Click to show "; padding-left: 0; +} + +table { + border-collapse: collapse; + width: 100%; + + td { + vertical-align: text-top; + } } \ No newline at end of file