LLSSS 20260930 Core Engine State of the Union
This document attempts to explain the workings of the core engine of LLSSS, sort of from a hypothetical user perspective, as it stands today (20260930). I'm going to simplify quite a few things and not discuss all (or even most) features. I will try very hard to mark any such simplifications.
The very first thing we have to discuss is UVW geometry. LLSSS can build patterns "in any direction" although most common are orthogonal and diagonal. To do this, it picks three vectors in XYT space, named "U", "V", and "W". V is the translation of the pattern and any two coordinates in XYT space that are separated by an integral multiple of V are identical. W is the direction the search is progressing and nearly anywhere I use top/up/bottom/down in this document I mean along W (top/up being negative W, bottom/down being positive W). U is some other vector that completes a basis for 3-space and nearly anywhere I use left/right in this document I mean along U (left being negative U, right being positive U). As |det(UVW)| is not necessarily 1, it is possible for UVW coordinates of integer XYT points to not themselves be integers. We think of all of space as being broken into rows (collections of coordinates that all have the same integer floor(W)) and being broken into columns (collections of coordinates that all have the same integer floor(U)). The code and a lot of prior writing call the intersections of rows and columns "tiles" and focus more on them, although it won't come up much here as this division into tiles is a little less important for users.
When searching c2-f2b (c/2 north, front-to-back), V=(0, -1, 2), U=X, W=T, the rows are:
Code: Select all
| 000000 | 111111 |
| 222222 | 333333 |
| 444444 | 555555 |
The columns are:
Code: Select all
| 012345 | 012345 |
| 012345 | 012345 |
| 012345 | 012345 |
When searching 2c4-f2b (2c/4 north, front-to-back), V=(0, -2, 4), U=X, W=T, the rows are:
Code: Select all
| | | 000000 | 111111 |
| 000000 | 111111 | 222222 | 333333 |
| 222222 | 333333 | 444444 | 555555 |
| 444444 | 555555 | | |
Notice especially that each row has two horizontal lines of cells!
The columns are:
Code: Select all
| | | 012345 | 012345 |
| 012345 | 012345 | 012345 | 012345 |
| 012345 | 012345 | 012345 | 012345 |
| 012345 | 012345 | | |
When searching c4d-down (c/4d NW, building south), V=(-1, -1, 4), U=X, W=Y, the rows are:
Code: Select all
| 000000 | 000000 | 000000 | 000000 |
| 111111 | 111111 | 111111 | 111111 |
| 222222 | 222222 | 222222 | 222222 |
| 333333 | 333333 | 333333 | 333333 |
| 444444 | 444444 | 444444 | 444444 |
| 555555 | 555555 | 555555 | 555555 |
The columns are:
Code: Select all
| 012345 | 012345 | 012345 | 012345 |
| 012345 | 012345 | 012345 | 012345 |
| 012345 | 012345 | 012345 | 012345 |
| 012345 | 012345 | 012345 | 012345 |
| 012345 | 012345 | 012345 | 012345 |
| 012345 | 012345 | 012345 | 012345 |
When searching c4d-f2b (c/4d NW, actually front-to-back), V=(-1, -1, 4), U=X-Y, W=X+Y, the rows are:
Code: Select all
| | | 0 | 1 |
| 02 | 13 | 024 | 135 |
| 024 | 135 | 024 | 135 |
| 024 | 135 | 024 | 135 |
| 024 | 135 | 024 | 135 |
| 024 | 135 | 024 | 135 |
| 024 | 135 | 24 | 35 |
| 4 | 5 | | |
Note that each row is two HDs, one in each of two generations opposite each other. The columns are:
Code: Select all
| | | 5 | 5 |
| 55 | 55 | 455 | 455 |
| 445 | 445 | 344 | 344 |
| 334 | 334 | 233 | 233 |
| 223 | 223 | 122 | 122 |
| 112 | 112 | 011 | 011 |
| 001 | 001 | 00 | 00 |
| 0 | 0 | | |
Note that each column is two HDs.
A common concern in discussing geometry is the CA check sizes. The CA check sizes (called "nh_w_size" and "nh_u_size" in the code) are the greatest number of rows/columns a single CA neighborhood can span, i.e. the size of strips (of rows/columns) of the world you have to be looking at to be able to adjudicate CA checks locally. One less than each of these are the overlap sizes, i.e. the number of rows/columns that two half planes most agree on to be able to patch together and be guaranteed to meet CA checks. Most relevant for below are (1) the U CA check size, which for all named geometries is 3, and (2) the W overlap size, which is, well, it's complicated. For named orthogonal-aligned geometries the W overlap size is what users would probably think of as "2 rows of cells in each generation". For named diagonal-aligned geometries the W overlap size is what users would probably think of as "4 HDs in each generation".
The core objects in LLSSS are strips of partial pattern whose widths are a compile-time-specified constant, "AF2", which must be at least as wide as the U CA check size (so these strips' contents can adjudicate all CA checks), and whose height is whatever the height/depth of the search is so far. By default AF2 is 3 and below here I'm going to simplify under that assumption. I think it's worth repeating and restating that all state is made up of these 3-wide strips and that their shape in XYT space is determined by the geometry.
LLSSS search states are probably best thought of as built from collections of these 3-wide strips. Each such collection is called a "jcol", short for "join column". Historically these have been called (insanely confusingly, I know) "columns" and I have been trying to switch to calling them "jcols". The states actually include collections of 2-wide strips ("bcols") that are the boundaries between neighboring jcols. In the code bcols have primacy and jcols are "just" the CA compatibility relationships between neighboring bcols, but from the user perspective I think it's easier to focus on the 3-wide strips.
In "fixed board" mode, the state is some fixed-length array of jcols and each adjacent pair of jcols in the array are considered to be neighbors. It is required that each strip in each jcol have a valid neighbor strip both to the left and to the right and thus have a path (of strips) all the way to either edge. "Valid neighbor strip" here means agreeing on the overlapping 2-wide strip.
In "recentering" mode, the state is just a single jcol and also some extra data tracking what 2-wide strips are considered to be the left edge and what 2-wide strips are considered to be the right edge. It is required that each strip in the jcol have a path (of any length) to the left edge and also to the right edge. It is unlikely to matter for typical searches, but it is possible for the left edge and right edge to agree on (include) a 2-wide strip that is not present in the jcol anywhere (this sort of thing is part of the problem of thinking in jcols instead of bcols...).
It is probably not worth dwelling too hard on how the state extension happens (one-bit-at-a-time versus one-tile-at-a-time, how recentering mid_steps limits it, etc.), other than to say the edge-reachability invariants are preserved and all 3-wide strips always meet CA checks. Fixed board searches can really put in whatever cell values they want in the middle, while recentering searches are in some ways limited to trying to keep apparent widths of the bottom edge of partials down, but the semantics are exceptionally complicated and mostly not worth knowing.
Now, about boundary conditions. The board has 4 sides and each is its own distinct concern/configuration (although left/right are similar)...
The top side of the board is dictated by how the state is initialized. For fixed board, we take the provided grid, chop it into 3-grams of columns to make strips, and make one jcol for each such strip. For recentering, we take the provided grid, chop it into 3-grams of columns, and shovel them all into one jcol. Recentering edges are initialized with just the left-most 2-gram and the right-most 2-gram. This hides a lot of details like wildcards (uncovered), multiple boards (ditto), CA checks (we do them), recentering roots (see below), and question marks ("picture mode", see below).
The left and right sides of the board are their own bag of worms and features. Such extension behaviours ("edges") act on the outermost 2-grams and essentially restrict the extension choices in each new row. Typically they are either extended with the background agar only, or extended according to a specified symmetry. Note that these restrictions mean searches' effective widths may be thinner than the board itself, by amounts varying by edge (background agar is 2 U thinner, symmetry edges vary depending on where they place the axis in the edge).
The board doesn't have a bottom boundary condition per se, in that the extension could continue forever. Usually though, the operator will have some sense of what constitutes a success (called "ends"). The default ends is "bg", i.e. does the last W overlap size of rows match the background agar. It checks one W overlap (as do most ends) as that is what is needed to guarantee validity of patching together with an imagined half plane of background agar below it. After each round of expansion, ends run their checks and print out any successes.
That I think completes a whirlwind tour of the core engine. Nearly every part of this is extensible and there are a wealth of features that did not make this summary, although I think they should be possible to explain independently, building on what's written here. It's possible I may do (some of) them later and I will edit in links somewhere in this post. Even limited more or less to things relevant to intermediate branch solving, candidates include at least all of:
(*) Extension semantics, especially recentering mid_steps and probably one-bit-at-a-time (versus one-tile-at-a-time) and tile order.
(*) Recentering closures/inertnesses. Alter recentering's measurement of what constitutes width of a partial.
(*) Pre-reify autochoke. Discards part of the state when it seems the state would become "too big".
(*) Pre-partials. Status reports shown at the top of each extension round.
(*) Partials. Interesting bits shown at the end of each round (or at least most). Distinction from ends questionable. I think just SRV2 right now.
(*) Constraints. Rules cell values must obey that are extremely local and checked during extension.
(*) Filters. Rules cell values must obey that are less local and are enforced after the fact.
(*) Ends. Other things that could constitute success. PD, symmetry, EDBV3.
(*) Recentering fuzzy edges.
(*) WAO.
(*) Grid editing. Mostly from-uwi/to-uwi?
(*) mgp-tool and/or @yolo_1gp.
(*) MDSE V2.
For starters, let's complete the couple of things I foreshadowed above:
Recentering roots. A problem that came up in actually using recentering is that allowing all 3-grams to recombine according to just cell values is too permissive. Consider what happens when we try to solve a simple still life edge. We'll include 3 columns of zeros on each side so they can be recombined endlessly and we'll put our target in the middle (center two columns):
Code: Select all
| ........ |
| ........ |
| ...**... |
The problem is, this is a valid partial (as the edges match):
So too is this, even though it has also skipped the prompt:
To solve this we allow (where by "allow", I mean "require") special markers on the top of each column which are considered part of the column for matching purposes. Specifically, we rip off the first W row of inputs and require that each column within it have all-identical characters and each character value determines a distinct root. We can solve this case like:
Code: Select all
| LLLMMRRR |
| ........ |
| ........ |
| ...**... |
The only valid way to recombine 3-grams is some string of L's at least 2 long, followed by MM, followed by some string of R's at least 2 long. Unfortunately, this also breaks down for sufficiently weird inputs, e.g. if I try this:
Code: Select all
| LLLMMMMMMRRR |
| ............ |
| ............ |
| ...**..**... |
With this, there are these (among endless others) undesired recombinations of 3-grams:
Code: Select all
| LLLMMRRR |
| ........ |
| ........ |
| ...**... |
Code: Select all
| LLLMMMMMMMMMMRRR |
| ................ |
| ................ |
| ...**..**..**... |
Finally I settled on making the M's unique:
Code: Select all
| LLL123456RRR |
| ............ |
| ............ |
| ...**..**... |
Now the only possible recombinations of 3-grams are the ones you expect (2+ L's, then 123456, then 2+ R's). To make it easier, the code special-cases that "u" roots are considered distinct from each other (acting as if you had picked a new letter for each) and so we can (should) do this as:
Code: Select all
| LLLuuuuuuRRR |
| ............ |
| ............ |
| ...**..**... |
For typical searches you should have a string of u's in the center and edges depending on desired behavior. If you want a fixed-like edge, because you care about clearance, the edge is a symmetric edge, etc., it should just be u's all the way to the edge. If you want an unrollable edge you should have a complete cycle of the agar with some distinct-per-side labels, including all 3-gram transitions. For zero agar and b0 agar in typical geometries, this is just 3 of the same letter, but for other agars/geometries it can get a little more interesting.
These examples above are all in the very simple "p1" geometry, but when you go to other geometries each root marker is repeated |det(UVW)| times. E.g. 2c4-f2b:
Code: Select all
| | | | |
| | | 123 | ... |
| 123 | ... | ... | ... |
| ... | ... | ... | |
| ... | | | |
Or p3:
Code: Select all
| 123 | 123 | 123 |
| ... | ... | ... |
| ... | ... | ... |
Or c4d-f2b:
Code: Select all
| | | | |
| | | 3 | . |
| 3. | .. | 2.. | ... |
| 2... | .... | 1.... | ..... |
| 1..... | ..... | ..... | .... |
| .... | ... | ... | .. |
| .. | . | . | |
Picture mode(s). All of the above is written for initializations that are some strict W prefix of space and which are input as a complete UVW prism of cell values (periods and asterisks) (and also one W row of root markers iff recentering). For finding a spaceship in a single shot (or a series of shots, each extending a partial which is a strict prefix of W space), this is maybe fine, but when solving tougher problems there will often be surrounding parts (e.g. sibling branches) that need to be respected. In these cases we use a slightly different input format where, again, we have a complete UVW prism of stuff, but now we mark out (with question marks) where the search will go, a UVW sub-prism extending to the W bottom.
In this mode the search is initialized with just (the 3-grams drawn from) the W overlap above the question marks (instead of the entire board) and the edges will extend first with cell values drawn from the U overlaps left/right of the question marks, and then fall back to the background agar once you've walked off the end of the board.
E.g. you might do a fixed board search extending this still life:
Code: Select all
| .......... |
| .......... |
| ....*..... |
| ...*.*.... |
| ...*.*.... |
Your state starts with exactly that picture, namely 5 rows deep and 10 columns wide (6 of which can vary as the edges are fixed). If you drew in question marks to do similar in picture mode, it's not quite the same:
Code: Select all
| .......... |
| .......... |
| ....*..... |
| ...*.*.... |
| ...*.*.... |
| ..??????.. |
| ..??????.. |
| ..??????.. |
| ..??????.. |
You get the same effective search, but it only starts with the two rows above it so your starting state is just this:
The real benefit of picture mode is you could shrink the search window and fill in a side:
Code: Select all
| .......... |
| .......... |
| ....*..... |
| ...*.*.... |
| ...*.*.... |
| ...*???... |
| ..**???... |
| ....???... |
| ....???... |
Your start is effectively:
And the left edge will be extended like:
Code: Select all
| .* |
| .* |
| .* |
| ** |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
For convenience you can pass --top-pad, --left-pad, and --right-pad to extend the question marks (by W steps up, U steps left, and U steps right, respectively), without having to do the editing yourself. E.g. adding --top-pad 1 --left-pad 1 --right-pad 1 changes from this:
Code: Select all
| .......... |
| .......... |
| ....*..... |
| ...*.*.... |
| ...*.*.... |
| ...*???... |
| ..**???... |
| ....???... |
| ....???... |
Into effectively this:
Code: Select all
| .......... |
| .......... |
| ....*..... |
| ...*.*.... |
| ...?????.. |
| ...?????.. |
| ..*?????.. |
| ...?????.. |
| ...?????.. |
Code: Select all
| ...*..... |
| ..*.*.... |
| ..?????.. |
| ..?????.. |
| .*?????.. |
| ..?????.. |
| ..?????.. |
For an initial state of:
And a left edge extended with:
Code: Select all
| .. |
| .. |
| .. |
| .. |
| .* |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
Picture mode for recentering is more or less the same, only the roots are preserved separately. E.g. you could start with:
Code: Select all
| LLLuuuuuRRR |
| ........... |
| ........... |
| .....*..... |
| ....*.*.... |
| ....*.*.... |
| ...**???... |
| .....???... |
| .....???... |
| .....???... |
And add --top-pad 1 --left-pad 1 --right-pad 1 to get effectively:
Code: Select all
| LLLuuuuuRRR |
| ........... |
| ........... |
| .....*..... |
| ....*.*.... |
| ....?????.. |
| ...*?????.. |
| ....?????.. |
| ....?????.. |
| ....?????.. |
Code: Select all
| LuuuuuRRR |
| ...*..... |
| ..*.*.... |
| ..?????.. |
| .*?????.. |
| ..?????.. |
| ..?????.. |
| ..?????.. |
I.e. initial state:
Code: Select all
| LuuuuuRRR |
| ...*..... |
| ..*.*.... |
And left edge extended with:
Code: Select all
| Lu |
| .. |
| .. |
| .. |
| .* |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
| .. |
Note that "LuuuuuRRR" does not strictly meet the above guidance on roots, but it is effectively "uuuuuuRRR" which does (fixed left edge, unrollable right edge).