Optimized Snarkmaker

For discussion of specific patterns or specific families of patterns in Conway's Game of Life, both newly-discovered and well-known.
russellsprouts
Posts: 69
Joined: January 28th, 2026, 10:04 am

Re: Optimized Snarkmaker

Post by russellsprouts »

Hippo.69 wrote: September 3rd, 2026, 1:47 pm I have not checked how much cheaper it is ... and how much faster the destroyal is. ...
What is the use case?

If the construction is ended and after a while the SoD is invoked ... than this is definitely helpfull/required (otherwise you should build one glider absorber after all gliders pass by ... what would be troublesome).

...

For the use during the construction I would actually welcome construction including speboe behind the turn ... so when the snark is removed, the construction can continue. ... Of course you can duplicate the speboe before building the snark, but I expect inenvelope version would be much cheaper.
I think you'll always be better off with the original snarkbreaker recipe, if you want to continue construction. It takes 17 gliders to send the reverse glider, then 3 more gliders timed to convert the snark into an elbow block.

In contrast, the chain recipe is +50 gliders from a regular snarkmaker. The reverse glider is the same as in the original snarkbreaker, so you could technically build snark -> chain snark -> chain snark -> snarkbreaker to return to a block. However, it would still be cheaper to do snark -> snark -> snark -> snarkbreaker -> snarkbreaker -> snarkbreaker.

In my case, I need to destroy the snarks after the entire recipe has long passed.

---

The antship will be a loopship that can detect and leave behind tubs to indicate the state of the Langton's ant cells.
  1. Recipe hits a block placed in front of the tub.
  2. Recipe builds snark -> snark -> snark -> simkin gun to line up the simkin gun lane beside the tub.
  3. Conditional recipes detect the tub. If the tub is present, it builds one future elbow block on one side of the input recipe lane, else it builds a future elbow block on the other side. This implements the turning.
  4. Conditional recipes toggle the state of the tub
  5. Snarkbreaker -> snarkbreaker -> snarkbreaker -> destroy elbow
  6. Now, the recipe will hit the block that was placed by the conditional recipes
  7. Snarkmaker (end) -> snarkmaker (chain) -> simkin gun. We now have a construction arm facing left or right.
  8. Build crabstretcher, build signal loop pieces including seeds of destruction
  9. Destroy simkin gun
  10. Snarkmaker (chain) (we now have a chain of snark (end) -> snark (chain) -> snark (chain) which turns around the marker tub and into the next signal loop)
  11. Send the trigger signal for the seeds of destruction (with a long delay)
  12. Destroy elbow block
  13. End of the recipe, Previous signal loop starts outputting the recipe a second time.
  14. Full recipe flows through the snarks into the next signal loop.
  15. After the recipe has finished flowing into the next signal loop, the seeds of destruction finish, including removing the previous loop's snarks using the sod recipes.
User avatar
Hippo.69
Posts: 696
Joined: July 14th, 2020, 7:35 pm
Contact:

Re: Optimized Snarkmaker

Post by Hippo.69 »

For those limited by space where OGGCA will be (I know only about 0e0p...), here are "edgy left emissions" (color/phase ... to cheaper variants for less edgy)

Code: Select all

x = 1765, y = 1774, rule = LifeHistory
13.3A17$2A$A.A$.A64.3A2$17.A6.2A38.A5.A$16.A.A5.2A38.A5.A$16.A.A45.A
5.A$17.A$34.A31.3A$9.2A23.3A$8.A.A26.A$9.A26.2A4$45.A$25.2A5.2A9.2A.A
$25.2A5.2A8.A4.A32.2A$46.A32.A2.A$29.2A11.4A34.2A$24.A4.2A$5.2A16.A.A
$5.2A16.A.A$24.A$52.2A$52.2A2$49.2A5.2A$49.2A5.2A$12.A$6.2A3.A.A$5.A
2.A2.2A$6.2A3$49.A$47.A.A$48.2A16$68.A$67.2A$67.A.A26$88.3A$88.A$89.A
48$139.2A$138.2A$140.A47$188.2A$187.2A$189.A30$220.2A$219.2A$221.A62$
285.A$284.2A$284.A.A21$307.2A$306.2A$308.A20$330.A$329.2A$329.A.A21$
353.A$352.2A$352.A.A41$395.2A$394.2A$396.A27$424.2A$424.A.A$424.A26$
453.A$452.2A$452.A.A21$475.2A$474.2A$476.A22$498.3A$498.A$499.A47$
548.2A$547.2A$549.A60$611.A$610.2A$610.A.A28$641.A$640.2A$640.A.A52$
695.A$694.2A$694.A.A21$717.2A$716.2A$718.A22$740.3A$740.A$741.A44$
788.A$787.2A$787.A.A21$810.2A$809.2A$811.A39$851.2A$851.A.A$851.A43$
897.A$896.2A$896.A.A21$920.A$919.2A$919.A.A32$953.2A$952.2A$954.A29$
985.A$984.2A$984.A.A21$1007.2A$1006.2A$1008.A25$1034.2A$1034.A.A$
1034.A33$1069.2A$1069.A.A$1069.A49$1121.A$1120.2A$1120.A.A45$1167.2A$
1166.2A$1168.A22$1191.2A$1190.2A$1192.A21$1214.2A$1213.2A$1215.A35$
1250.3A$1250.A$1251.A30$1283.2A$1282.2A$1284.A23$1309.A$1308.2A$1308.
A.A21$1331.2A$1330.2A$1332.A50$1383.2A$1382.2A$1384.A47$1431.3A$1431.
A$1432.A23$1457.2A$1457.A.A$1457.A21$1479.3A$1479.A$1480.A47$1529.2A$
1528.2A$1530.A48$1579.2A$1579.A.A$1579.A69$1651.A$1650.2A$1650.A.A21$
1673.2A$1672.2A$1674.A25$1700.2A$1700.A.A$1700.A34$1736.2A$1735.2A$
1737.A24$1763.A$1762.2A$1762.A.A!
Edit: ^ right emissions added (there could be faster ones, but there is no problem to get to the simkin gun caused limit).

Using these emissions I would be able to do both "biblock" and "blinker loaf" in 0e0p metacell with rather long salvo (not using slow^2 at all) ... phase%2 of OGGCA is fixed by it.
(I have not made special version of pslmake to work with lane restriciton based on signatures ... it would probably save few gliders from current 24 of blinker-loaf).

OK, I have modified pslmake to allow signature limited lanes in the salvo ... rather conservative extension ... signature was specified by state 4 gliders, typically using 1 per allowed signature. I have extended the interpretation such that even number of gliders of a signature specifies intervals of allowed lanes for given signature, so our task had following specification:

Code: Select all

x = 84, y = 35, rule = LifeHistory
79.2A$78.A2.A$79.A.A$69.3A8.A20$2D7.2D61.2D7.2D$D.D6.D.D60.D.D6.D.D$D
8.D62.D8.D3$4.D71.D$3.2D70.2D$3.D.D69.D.D2$9.D71.D$8.2D70.2D$8.D.D69.
D.D!
I was unhappy with the 3/4 of traffic light to be build by full traffic light with half destroyed and 1/4 recreated so I have halfmanually helped it to get following (with restriction shown):
I have specified signatures %8, with meaning %2 (I was not carefull during editting to maintain the original %8, but pslmake generated the correct %8).

Code: Select all

x = 219, y = 148, rule = LifeHistory
38.2A$38.2A$45.2A$45.A.A$45.A4$2D7.2D61.2D7.2D$D.D6.D.D60.D.D6.D.D$D
8.D62.D8.D3$4.D71.D$3.2D51.2A17.2D$3.D.D50.A.A16.D.D$56.A$9.D71.D$8.
2D70.2D$8.D.D69.D.D9$85.2A$85.A.A$85.A6$86.2A$86.A.A$86.A12$98.2A$98.
A.A$98.A7$121.3A$121.A$122.A5$104.2A$104.A.A$104.A$133.3A$133.A$134.A
6$137.2A$136.2A$138.A8$135.3A$135.A$136.A2$145.A$144.2A$144.A.A12$
156.A$155.2A$155.A.A8$175.A$174.2A$165.A8.A.A$164.2A13.2A$164.A.A12.A
.A$179.A15$203.3A$193.2A8.A$193.A.A8.A$193.A7$216.2A$216.A.A$216.A!
Getting 18 gliders rather to 22 from modified pslmake ... (original had 24).

Actually pslmake would not found the salvo, I have at first limited the search to signature with the only one glider ... and monoxl recipestore shown me the seed for the final glider ... so I was actually building following...

Code: Select all

x = 125, y = 62, rule = LifeHistory
70.A$70.A$70.A$63.A$63.A2.3A3.3A$63.A$70.A$59.3A8.A$70.A8$2D7.2D61.2D
3.2A2.2D$D.D6.D.D60.D.D2.A.A.D.D$D8.D62.D4.A3.D3$4.D71.D$3.2D70.2D$3.
D.D69.D.D2$9.D71.D$8.2D70.2D$8.D.D69.D.D32$122.2A$122.A.A$122.A!
(Traffic light with this parity was a bit challenging ... 4 gliders)
Last edited by Hippo.69 on September 22nd, 2026, 2:39 am, edited 2 times in total.
User avatar
Hippo.69
Posts: 696
Joined: July 14th, 2020, 7:35 pm
Contact:

Re: Optimized Snarkmaker

Post by Hippo.69 »

And here are close to snark left emissions:

Code: Select all

x = 2007, y = 384, rule = LifeHistory
.A$2.A$3A28$31.A629.A329.A449.A$32.A629.A329.A449.A$30.3A627.3A327.3A
447.3A25$619.A$618.A$618.3A$61.A629.A329.A449.A$62.A629.A329.A449.A$
60.3A627.3A327.3A447.3A14$1560.A$1559.A$1559.3A3$2005.A$2004.A$2004.
3A7$91.A629.A329.A449.A$92.A629.A329.A449.A$90.3A627.3A327.3A447.3A
13$1981.A.A$1981.2A$1982.A6$1193.A$1193.A.A$1193.2A3$558.A.A$558.2A$
121.A437.A191.A329.A449.A$122.A629.A329.A449.A$120.3A627.3A327.3A447.
3A2$1512.A$1511.A$1511.3A20$1941.A$1939.2A$1940.2A2$151.A629.A329.A
449.A$152.A629.A329.A449.A$150.3A627.3A327.3A447.3A2$522.A.A$522.2A$
523.A957.A$1479.2A$1480.2A15$1913.A$1912.A$1912.3A5$181.A629.A329.A
449.A$182.A629.A329.A313.A135.A$180.3A627.3A327.3A311.2A134.3A$1455.
2A9$484.A.A$484.2A628.A$485.A626.2A$1113.2A15$211.A629.A329.A449.A$
212.A629.A329.A449.A$210.3A627.3A327.3A447.3A5$1090.A$1088.2A$457.A
631.2A$457.A.A$457.2A10$1405.A$1405.A.A$1405.2A446.A$1852.A$1852.3A5$
241.A629.A194.A134.A449.A$242.A629.A193.A.A133.A449.A$240.3A627.3A
193.2A132.3A447.3A4$431.A$429.2A$430.2A13$1825.A.A$1825.2A$1043.A.A
780.A$1043.2A$1044.A6$402.3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.3D5.3D.2D2.3D
5.3D3.D3.D$403.D7.D2.D.D.D.D6.D2.D.D3.D6.D2.D.D3.D6.D4.D.D8.D4.D3.D$
403.DA6.D2.D.D.3D6.D2.D.D.3D6.D2.D.D.3D6.D2.3D.3D6.D2.3D.3D$402.2E6.
2D2.D.D.D.D5.2D2.D.D.D.D5.2D2.D.D.D.D5.2D2.D5.D5.2D2.D.D.D.D$403.EA6.
D2.3D.3D6.D2.3D.3D6.D2.3D.3D6.D2.3D.3D6.D2.D.D.D.D6$1352.3D5.3D.3D2.D
6.3D.3D.3D5.3D.3D.3D5.3D.3D2.D384.3D5.3D.3D5.3D.3D.3D5.3D3.D.3D5.3D.
3D.3D5.3D.3D.3D$1354.D5.D3.D.D2.D7.D4.D3.D6.D2.D.D.D.D6.D2.D4.D385.D
8.D.D.D6.D2.D.D3.D5.D5.D.D.D6.D2.D.D3.D6.D2.D.D.D.D$1352.DEDA4.3D.D.D
3.D6.D2.3D.3D6.D2.D.D.D.D6.D2.3D3.D384.DA5.3D.D.D6.D2.D.D.3D5.3D.3D.D
.D6.D2.D.D.3D6.D2.3D.3D$1353.AE7.D.D.D3.D5.2D2.D.D.D.D5.2D2.D.D.D.D5.
2D4.D3.D383.2E6.D.D.D.D5.2D2.D.D.D.D7.D.D.D.D.D5.2D2.D.D.D.D5.2D2.D3.
D$1018.3D5.3D.3D5.3D.3D5.3D.3D5.3D.3D283.2DE5.3D.3D.3D6.D2.3D.3D6.D2.
3D.3D6.D2.3D.3D384.EA5.3D.3D6.D2.3D.3D5.3D.D.D.3D6.D2.3D.3D6.D2.3D.3D
$1020.D7.D.D.D7.D2.D8.D2.D8.D.D.D$1018.DEDA4.3D.3D5.3D2.D6.3D2.D6.3D.
3D$1019.AE5.D.D.D7.D.D.2D6.D.D.2D6.D.D.D$1018.2DE5.3D.3D5.3D2.D6.3D2.
D6.3D.3D24$308.D7.D2.3D.3D6.D2.3D.3D6.D2.3D.3D6.D2.3D.3D6.D2.D.D.D.D
547.3D5.3D.3D5.3D2.D6.3D2.D6.3D.3D279.3D5.3D.3D.3D6.D2.3D.3D6.D2.3D.
3D6.D2.3D.3D384.D6.3D.3D6.D2.3D.3D5.3D.D.D.3D6.D2.3D.3D6.D2.3D.3D$
307.2D6.2D2.D.D.D.D5.2D2.D.D.D.D5.2D2.D.D.D.D5.2D2.D5.D5.2D2.D.D.D.D
549.D5.D.D.D7.D.D.2D6.D.D.2D6.D.D.D283.D7.D.D.D3.D5.2D2.D.D.D.D5.2D2.
D.D.D.D5.2D4.D3.D226.D6.3D.3D5.3D.3D5.3D.3D.3D5.3D2.D104.2D6.D.D.D.D
5.2D2.D.D.D.D7.D.D.D.D.D5.2D2.D.D.D.D5.2D2.D3.D$308.D7.D2.D.D.3D6.D2.
D.D.3D6.D2.D.D.3D6.D2.3D.3D6.D2.3D.3D547.3D5.3D.3D5.3D2.D6.3D2.D6.3D.
3D279.3D5.3D.D.D3.D6.D2.3D.3D6.D2.D.D.D.D6.D2.3D3.D225.2D6.D.D3.D5.D.
D3.D7.D3.D.D.D5.D.D.2D105.D6.3D.D.D6.D2.D.D.3D5.3D.3D.D.D6.D2.D.D.3D
6.D2.3D.3D$308.D7.D2.D.D.D.D6.D2.D.D3.D6.D2.D.D3.D6.D4.D.D8.D4.D3.D
549.D7.D.D.D7.D2.D8.D2.D8.D.D.D281.D5.D3.D.D2.D7.D4.D3.D6.D2.D.D.D.D
6.D2.D4.D227.D6.3D.3D5.3D.3D5.3D.3D.3D5.3D2.D105.D8.D.D.D6.D2.D.D3.D
5.D5.D.D.D6.D2.D.D3.D6.D2.D.D.D.D$307.3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.
3D5.3D.2D2.3D5.3D3.D3.D547.3D5.3D.3D5.3D.3D5.3D.3D5.3D.3D279.3D5.3D.
3D2.D6.3D.3D.3D5.3D.3D.3D5.3D.3D2.D227.D8.D3.D7.D.D7.D3.D5.D7.D2.D
104.3D5.3D.3D5.3D.3D.3D5.3D3.D.3D5.3D.3D.3D5.3D.3D.3D$1559.3D5.3D.3D
5.3D.3D5.3D.3D.3D5.3D.3D13$214.C119.C629.C329.C449.C$214.3C117.3C627.
3C327.3C447.3C$217.C119.C629.C329.C449.C$216.2C118.2C628.2C328.2C448.
2C2$1561.D6.3D.3D6.D2.3D.3D$1560.2D6.D.D3.D5.2D2.D5.D$1561.D6.3D.3D6.
D2.3D.3D$1561.D8.D.D8.D2.D.D3.D$1560.3D5.3D.3D5.3D.3D.3D$226.2C118.2C
628.2C328.2C448.2C$219.2C5.C.C110.2C5.C.C620.2C5.C.C320.2C5.C.C440.2C
5.C.C$219.2C7.C110.2C7.C620.2C7.C320.2C7.C440.2C7.C$228.2C118.2C628.
2C328.2C448.2C2$215.C119.C629.C329.C449.C$214.C.C.2C28.3D6.D2.3D.3D6.
D2.3D.3D51.C.C.2C624.C.C.2C324.C.C.2C444.C.C.2C$214.C.C.C.C28.AD5.2D
2.D.D.D.D5.2D2.D.D.D53.C.C.C.C623.C.C.C.C323.C.C.C.C443.C.C.C.C$211.
2C.C.C.C.C2.C24.3DA5.D2.3D.3D6.D2.D.D.3D48.2C.C.C.C.C2.C617.2C.C.C.C.
C2.C317.2C.C.C.C.C2.C437.2C.C.C.C.C2.C$211.C2.C2.2C.4C26.D6.D4.D.D.D
6.D2.D.D3.D48.C2.C2.2C.4C617.C2.C2.2C.4C317.C2.C2.2C.4C437.C2.C2.2C.
4C$213.2C4.C28.3D5.3D.3D.3D5.3D.3D.2D51.2C4.C623.2C4.C323.2C4.C443.2C
4.C$219.C.C117.C.C627.C.C327.C.C447.C.C$220.2C118.2C628.2C328.2C448.
2C2$190.D.D117.D.D627.D.D327.D.D447.D.D$190.2D118.2D628.2D328.2D448.
2D$191.D119.D629.D329.D449.D4$190.D119.D629.D329.D449.D$188.2D118.2D
628.2D328.2D448.2D$189.2D118.2D628.2D328.2D448.2D2$184.D119.D629.D
329.D449.D$183.D119.D629.D329.D449.D$183.3D117.3D627.3D327.3D447.3D
14$171.D119.D629.D329.D449.D$170.D119.D629.D329.D449.D$170.3D117.3D
627.3D327.3D447.3D!
User avatar
Hippo.69
Posts: 696
Joined: July 14th, 2020, 7:35 pm
Contact:

Re: Optimized Snarkmaker

Post by Hippo.69 »

I am looking for snarkbreaker (in the OGGCA variant).
So far I have

Code: Select all

x = 96, y = 191, rule = LifeHistory
10.2A$9.A.A$3.2A4.A$.A2.A2.2A.4A$.2A.A.A.A.A2.A$4.A.A.A.A$4.A.A.2A$5.
A2$18.2A$9.2A7.A$9.2A5.A.A$16.2A4$3A$2.A$.A$6.2A$7.A$4.3A$4.A2$19.2A$
18.2A$20.A3$48.D2.D.D2.D6.3D.3D$47.2D2.D.D.2D6.D.D3.D$48.D2.3D2.D6.3D
3.D$48.D4.D2.D8.D2.D$47.3D3.D.3D5.3D2.D26$54.2A$54.A.A$54.A22$79.A$
78.2A$78.A.A5$10.2A$9.A.A$3.2A4.A$.A2.A2.2A.4A$.2A.A.A.A.A2.A$4.A.A.A
.A$4.A.A.2A$5.A2$18.2A$9.2A7.A$9.2A5.A.A$16.2A4$3A$2.A$.A$6.2A$7.A$4.
3A$4.A2$19.2A$18.2A$20.A3$48.D3.D2.3D5.3D.3D5.3D.3D$47.2D2.2D2.D.D5.D
.D.D.D5.D.D.D.D$48.D3.D2.3D5.3D.D.D5.3D.D.D$48.D3.D2.D.D7.D.D.D7.D.D.
D$47.3D.3D.3D5.3D.3D5.3D.3D20$49.A$48.2A$48.A.A21$71.2A$70.2A$72.A20$
94.A$93.2A$93.A.A!
Edit: OK, there will be no OGGCA gliders available after simkin gun destroyal so only snarkbreaker producing/not producing speboe are important.
Pattern reduced.
russellsprouts
Posts: 69
Joined: January 28th, 2026, 10:04 am

Re: Optimized Snarkmaker

Post by russellsprouts »

Hippo.69 wrote: September 13th, 2026, 1:26 am I am looking for snarkbreaker (in the OGGCA variant).
So far I have

Code: Select all

x = 96, y = 191, rule = LifeHistory
10.2A$9.A.A$3.2A4.A$.A2.A2.2A.4A$.2A.A.A.A.A2.A$4.A.A.A.A$4.A.A.2A$5.
A2$18.2A$9.2A7.A$9.2A5.A.A$16.2A4$3A$2.A$.A$6.2A$7.A$4.3A$4.A2$19.2A$
18.2A$20.A3$48.D2.D.D2.D6.3D.3D$47.2D2.D.D.2D6.D.D3.D$48.D2.3D2.D6.3D
3.D$48.D4.D2.D8.D2.D$47.3D3.D.3D5.3D2.D26$54.2A$54.A.A$54.A22$79.A$
78.2A$78.A.A5$10.2A$9.A.A$3.2A4.A$.A2.A2.2A.4A$.2A.A.A.A.A2.A$4.A.A.A
.A$4.A.A.2A$5.A2$18.2A$9.2A7.A$9.2A5.A.A$16.2A4$3A$2.A$.A$6.2A$7.A$4.
3A$4.A2$19.2A$18.2A$20.A3$48.D3.D2.3D5.3D.3D5.3D.3D$47.2D2.2D2.D.D5.D
.D.D.D5.D.D.D.D$48.D3.D2.3D5.3D.D.D5.3D.D.D$48.D3.D2.D.D7.D.D.D7.D.D.
D$47.3D.3D.3D5.3D.3D5.3D.3D20$49.A$48.2A$48.A.A21$71.2A$70.2A$72.A20$
94.A$93.2A$93.A.A!
Edit: OK, there will be no OGGCA gliders available after simkin gun destroyal so only snarkbreaker producing/not producing speboe are important.
Pattern reduced.
I incorporated those snarkbreaker recipes into my repo, and posted an example using them in the script-related questions thread:
https://conwaylife.com/forums/viewtopic ... 89#p235089

I ran a search for a reverse glider to chain snarkbreakers, but these are the only backwards glider results with 5 gliders:

Code: Select all

(1, 106, 103, 119, 95) ['glider(l14)(ph1)(♗⬂①)']
(1, 106, 103, 119, 138) ['glider(l14)(ph1)(♗⬂①)']
(1, 119, 127, 90, 197) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 92, 195) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 100, 187) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 98, 189) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 96, 191) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 94, 193) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 106, 181) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 102, 185) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 104, 183) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 108, 179) ['glider(l2)(ph1)(♗⬂①)']

Code: Select all

x = 355, y = 160, rule = LifeHistory
10.2A198.2A$9.A.A197.A.A$3.2A4.A193.2A4.A$.A2.A2.2A.4A187.A2.A2.2A
.4A$.2A.A.A.A.A2.A187.2A.A.A.A.A2.A$4.A.A.A.A193.A.A.A.A$4.A.A.2A
194.A.A.2A$5.A199.A2$18.2A198.2A$9.2A7.A190.2A7.A$9.2A5.A.A190.2A
5.A.A$16.2A198.2A4$3A197.3A$2.A199.A$.A199.A$6.2A198.2A$7.A199.A$
4.3A197.3A$4.A199.A2$19.EA6.D2.3D.3D6.D2.3D.3D6.D3.D2.3D6.D2.3D.3D
134.EA6.D3.D2.3D6.D2.3D.3D5.3D.3D6.D2.3D.3D$18.2E6.2D2.D.D.D7.2D2.
D.D3.D5.2D2.2D2.D.D5.2D4.D.D.D133.2E6.2D2.2D2.D.D5.2D4.D3.D5.D.D.D
.D5.2D2.D.D3.D$19.DA6.D2.D.D.3D6.D2.D.D.3D6.D3.D2.3D6.D2.3D.3D134.
DA6.D3.D2.3D6.D2.3D3.D5.3D.D.D6.D2.3D3.D$19.D7.D2.D.D.D.D6.D2.D.D
3.D6.D3.D4.D6.D4.D.D.D134.D7.D3.D4.D6.D2.D4.D8.D.D.D6.D4.D2.D$18.
3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.3D133.3D5.3D.3D.3D5.3D.3D
2.D6.3D.3D5.3D.3D2.D22$46.A$45.2A$45.A.A2$248.3A$248.A$249.A20$71.
2A$71.A.A$71.A7$281.A$280.2A$280.A.A19$101.2A$100.2A$102.A200.2A$
302.2A$304.A30$136.A$135.2A$135.A.A15$352.2A$352.A.A$352.A!
A reverse glider would be on l1. Maybe we can find new snarkbreaker recipes with one of these gliders, but we would need to find left and right variants depending on which direction the snarks turn. The perfect reverse glider the snarkbreaker uses is right in the middle so it works for both directions.

Code: Select all

uv run oncoming.py --subtree="1" --depth=5 --with-debris-rle='3$17b2o$16bobo$10b2o4bo$8bo2bo2b2ob4o$8b2obobobobo2bo$11bobobobo$11bobob2o$12bo2$25b2o$16b2o7bo$16b2o5bobo$23b2o4$7b3o$9bo$8bo$13b2o$14bo$11b3o$11bo2$25b3o$25bo$26bo!' --only-gliders --n-gun-gliders=0 --simulate-gens=2048 > results/snarkbreaker-gliders.txt
Depth 5 with a fixed first glider was 760 million recipes to search, which took about 4 hours on my new PC
User avatar
Hippo.69
Posts: 696
Joined: July 14th, 2020, 7:35 pm
Contact:

Re: Optimized Snarkmaker

Post by Hippo.69 »

russellsprouts wrote: September 14th, 2026, 1:27 am I incorporated those snarkbreaker recipes into my repo, and posted an example using them in the script-related questions thread:
https://conwaylife.com/forums/viewtopic ... 89#p235089

I ran a search for a reverse glider to chain snarkbreakers, but these are the only backwards glider results with 5 gliders:

Code: Select all

(1, 106, 103, 119, 95) ['glider(l14)(ph1)(♗⬂①)']
(1, 106, 103, 119, 138) ['glider(l14)(ph1)(♗⬂①)']
(1, 119, 127, 90, 197) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 92, 195) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 100, 187) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 98, 189) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 96, 191) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 94, 193) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 106, 181) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 102, 185) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 104, 183) ['glider(l2)(ph1)(♗⬂①)']
(1, 119, 127, 108, 179) ['glider(l2)(ph1)(♗⬂①)']

Code: Select all

x = 355, y = 160, rule = LifeHistory
10.2A198.2A$9.A.A197.A.A$3.2A4.A193.2A4.A$.A2.A2.2A.4A187.A2.A2.2A
.4A$.2A.A.A.A.A2.A187.2A.A.A.A.A2.A$4.A.A.A.A193.A.A.A.A$4.A.A.2A
194.A.A.2A$5.A199.A2$18.2A198.2A$9.2A7.A190.2A7.A$9.2A5.A.A190.2A
5.A.A$16.2A198.2A4$3A197.3A$2.A199.A$.A199.A$6.2A198.2A$7.A199.A$
4.3A197.3A$4.A199.A2$19.EA6.D2.3D.3D6.D2.3D.3D6.D3.D2.3D6.D2.3D.3D
134.EA6.D3.D2.3D6.D2.3D.3D5.3D.3D6.D2.3D.3D$18.2E6.2D2.D.D.D7.2D2.
D.D3.D5.2D2.2D2.D.D5.2D4.D.D.D133.2E6.2D2.2D2.D.D5.2D4.D3.D5.D.D.D
.D5.2D2.D.D3.D$19.DA6.D2.D.D.3D6.D2.D.D.3D6.D3.D2.3D6.D2.3D.3D134.
DA6.D3.D2.3D6.D2.3D3.D5.3D.D.D6.D2.3D3.D$19.D7.D2.D.D.D.D6.D2.D.D
3.D6.D3.D4.D6.D4.D.D.D134.D7.D3.D4.D6.D2.D4.D8.D.D.D6.D4.D2.D$18.
3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.3D133.3D5.3D.3D.3D5.3D.3D
2.D6.3D.3D5.3D.3D2.D22$46.A$45.2A$45.A.A2$248.3A$248.A$249.A20$71.
2A$71.A.A$71.A7$281.A$280.2A$280.A.A19$101.2A$100.2A$102.A200.2A$
302.2A$304.A30$136.A$135.2A$135.A.A15$352.2A$352.A.A$352.A!
A reverse glider would be on l1. Maybe we can find new snarkbreaker recipes with one of these gliders, but we would need to find left and right variants depending on which direction the snarks turn. The perfect reverse glider the snarkbreaker uses is right in the middle so it works for both directions.

Code: Select all

uv run oncoming.py --subtree="1" --depth=5 --with-debris-rle='3$17b2o$16bobo$10b2o4bo$8bo2bo2b2ob4o$8b2obobobobo2bo$11bobobobo$11bobob2o$12bo2$25b2o$16b2o7bo$16b2o5bobo$23b2o4$7b3o$9bo$8bo$13b2o$14bo$11b3o$11bo2$25b3o$25bo$26bo!' --only-gliders --n-gun-gliders=0 --simulate-gens=2048 > results/snarkbreaker-gliders.txt
Depth 5 with a fixed first glider was 760 million recipes to search, which took about 4 hours on my new PC
Have you searched for other offsets of the first glider? Its position could affect the result a lot.

Oh I was just solving changing the snark direction problem ... (with realocation to further already prepared speboe), you are right, I would require to destroy the 2 snarks at the end so chaining the snarkbreakers would help.

I will check with these offset by one gliders.

I am using modified ongoing.py for the purposes with for example this gun

Code: Select all

x = 130, y = 133, rule = B3/S23
 120b2o$119bobo$113b2o4bo$111bo2bo2b2ob4o$111b2obobobobo2bo$114bobobobo
 $114bobob2o$115bo2$128b2o$119b2o7bo$119b2o5bobo$126b2o4$127bo$125bobo$
 126b2o$116b2o$117bo$114b3o$114bo108$b2o$obo$2bo!
(I should shift the glider by 4)
with for example --n-gun-gliders=1 --max-delay=450 --simulate-gens=4096 --subtree="0" --depth=3 --max-tree-population=300 --max-population=15 > sdA2_3.txt
then inspecting the results. And finishing the perspective ones.

So we would like convert to speboe, convert to nothing, convert to backward glider (optimally at lanes -1,0, or 1).
I can try input with the other glider lane shift as well.

Code: Select all

import sqlite3
import sys
import argparse
import re
import itertools
import functools
import pathlib
import os
from multiprocessing import Pool
import multiprocessing
from datetime import timedelta
import math

from arg_parser import range_str_to_list
from components import pattern_components
from font import write_text
from life_history import write_life_history
from lifetree import lt
from gliders import PI_BLOCKS, mk_glider, single_channel_stream
from parse_p120_recipes import parse_p120_recipe
from speedometer import Speedometer

argparser = argparse.ArgumentParser(
    prog="oncoming.py", description="Search for glider patterns"
)


class ToolkitDef:
    min_spacing: int
    lane_offset: int
    period: int
    name: str

    def __init__(self, str):
        m = re.match(r"sc(\d+)b(\d+)p(\d+)", str)
        if not m:
            raise ValueError(
                f"Toolkit {str} is invalid. Expected something like sc90b5p120"
            )
        self.min_spacing = int(m.group(1))
        self.lane_offset = int(m.group(2))
        self.period = int(m.group(3))
        self.name = str


argparser.add_argument(
    "--toolkit",
    default="sc90b5p120",
    type=ToolkitDef,
    help="The oncoming gliders to search. Format sc[min spacing]b[lane_offset]p[period]. E.g., sc90b5p120",
)
argparser.add_argument(
    "--depth", type=int, help="The number of gliders to include in salvos"
)
argparser.add_argument(
    "--print-rle",
    type=str,
    default="",
    help="Prints a rle showing the given recipes. Skips processing anything else.",
)
argparser.add_argument(
    "--required-prefix",
    type=str,
    default="",
    help="results not containing info with given prefix are not reported",
)
argparser.add_argument(
    "--required",
    type=str,
    default="",
    help="results not containing all given exact semicolon separated infos are not reported",
)
argparser.add_argument(
    "--ignored",
    type=str,
    default="",
    help="given semicolon separated infos are not reported, but it does not block reporting the salvo",
)
argparser.add_argument(
    "--avoid-prefix",
    type=str,
    default=")(",
    help="results containing info with given prefix are not reported",
)
argparser.add_argument(
    "--n-gun-gliders", type=int, default=10, help="How many gun gliders to simulate"
)
argparser.add_argument(
    "--simulate-gens", type=int, default=None, help="How many generations to simulate"
)
argparser.add_argument(
    "--max-population", type=int, default=float('inf'), help="The maximum population results to report"
)
argparser.add_argument(
    "--min-population", type=int, default=3, help="The minimum population results to report"
)
argparser.add_argument(
    "--max-tree-population", type=int, default=float('inf'), help="The maximum population allowed in intermediates"
)


class SubtreeDef:
    options: tuple[tuple[int]]

    def __init__(self, str):
        depths = ()
        for d in str.split(";"):
            options = range_str_to_list(d)
            depths = depths + (tuple(options),)
        self.options = depths


argparser.add_argument(
    "--subtree",
    default=[],
    type=SubtreeDef,
    action="append",
    help="Search range. E.g. '1;90-255' to search the glider 1 followed by any glider in [90,255], or '1,3,5,7' to search the starting gliders 1,3,5 and 7. Use comma to separate at the same depth, then semicolon to separate depths. Ranges are inclusive.",
)
argparser.add_argument(
    "--max-delay", type=int, default=255, help="Maximum delay for a glider."
)
argparser.add_argument(
    "--check-for-shutoff",
    type=str,
    help="Checks if the given sequences shut off the glider stream by sending 120 extra gun gliders and comparing the state",
)
argparser.add_argument(
    "--find-minimum-follow",
    type=str,
    help="Finds the minimum follow distance for the given recipes",
)
argparser.add_argument("--use-gun-rle", type=str, help="Uses the rle as the gun")
argparser.add_argument(
    "--extract-recipes",
    type=pathlib.Path,
    help="Extracts the recipes from the given file. Outputs a JSON.",
)
argparser.add_argument(
    "--to-apgluxe",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Just print out the rles, don't simulate anything.",
)
argparser.add_argument(
    "--only-gliders",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Only process glider recipes.",
)
argparser.add_argument(
    "--do-not-report-gliders",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Do not report glider including results.",
)
argparser.add_argument(
    "--swim-upstream-after",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Send 2,96,96,96... after each part of the subtree."
)
argparser.add_argument(
    "--concurrent",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Use multiple threads for searching."
)
argparser.add_argument(
    "--view-orientations",
    type=str,
    default=None
)

args = argparser.parse_args()
simulate_gens = args.simulate_gens or args.toolkit.period * args.n_gun_gliders

simkin_gun = lt.pattern("""x = 37, y = 26, rule = B3/S23
3$11bo$11b3o$14bo$13b2o5$2b2o5b2o$2b2o5b2o2$6b2o$6b2o4$29b2o$16bo12b2o
$14bobo$14b3o9b2o5b2o$14bo11b2o5b2o!
""")(-490, -501)

gun_period = args.toolkit.period


def mk_fake_gun(n):
    fake_gun = sum(
        [mk_glider(0, gun_period * x) for x in range(0, n)], start=lt.pattern("")
    )
    fake_gun = fake_gun("rot180")(-10 + args.toolkit.lane_offset, -11)
    return fake_gun


fake_gun = mk_fake_gun(args.n_gun_gliders)
expected_incoming_gliders_pattern = fake_gun[simulate_gens]
expected_incoming_gliders = pattern_components(expected_incoming_gliders_pattern)

# phase 0 gliders
canonical_se = mk_glider(0, 0)("rot180").centre()
canonical_nw = mk_glider(0, 0).centre()
canonical_ne = mk_glider(0, 0)("rot270").centre()
canonical_sw = mk_glider(0, 0)("rot90").centre()


def recipe_stream_to_delays(stream):
    gliders = stream.split(",")
    result = []
    total = 0
    for g in gliders:
        m = re.match(r"^\(\s*(\d+)\s*\)\s*(?:\s*(\d+)\s*\+\s*)?(\d+)$", g.strip())
        if m:
            minimum_follow = int(m.group(1))
            addition = m.group(2)
            if addition:
                minimum_follow += int(addition)
            next_parity = int(m.group(3))
            parity = (total + minimum_follow) % 8
            g = minimum_follow + ((next_parity - parity) % 8)
            total += g
            result.append(g)
        else:
            g = int(g)
            total += g
            result.append(g)
    return result


if args.find_minimum_follow:
    expected_incoming_gliders = pattern_components(fake_gun[simulate_gens])
    recipes = args.find_minimum_follow.split(";")
    for i, r in enumerate(recipes):
        if not r.strip():
            continue
        delays = recipe_stream_to_delays(r)
        total = sum(delays)

        p = fake_gun + single_channel_stream(delays)
        p = p[simulate_gens]

        escaping_gliders = []
        for c in pattern_components(p):
            for i, e in enumerate(expected_incoming_gliders):
                if c == e:
                    # this is one of the escaping gliders
                    escaping_gliders.append(i)
        n_escaping = max(escaping_gliders) + 1

        # now we know which gliders participate. Remove the rest.
        envelope = lt.pattern("5o$5o$5o$5o$5o").centre()

        pattern = mk_fake_gun(args.n_gun_gliders - n_escaping) + single_channel_stream(
            delays
        )

        @functools.lru_cache(maxsize=None)
        def pattern_at_gen(n):
            if n == 0:
                return pattern
            return pattern_at_gen(n - 1)[1]

        def try_follow(i):
            follow_up_glider = mk_glider(0, total + i)
            glider_envelope = lt.pattern()
            for j in range(0, simulate_gens + i):
                follow_up_glider = follow_up_glider[1]
                glider_envelope += follow_up_glider.convolve(envelope)
                if (pattern_at_gen(j + 1) & glider_envelope).nonempty():
                    return False
            return True

        low = args.toolkit.min_spacing
        r = 1
        if try_follow(low):
            print(",".join(map(str, delays)) + f",({low})")
        else:
            while r<=1024:
                hi = args.toolkit.min_spacing+r
                r += r
                if try_follow(hi):
                    while (hi-low>1):
                        test=(hi+low)//2
                        if try_follow(test):
                            hi=test
                        else:
                            low=test
                    print(",".join(map(str, delays)) + f",({hi})")
                    break
                else:
                    low=hi
            else:
                print(
                    ",".join(map(str, delays)) + f",({hi}+???)"
                )
    sys.exit(0)

required_split = args.required.split(";")
args.required = set()
for req in required_split:
    if req != "":
        args.required.add(req)
ignored_split = args.ignored.split(";")
args.ignored = set()
for ign in ignored_split:
    args.ignored.add(ign)
args.gun_X = False

if args.use_gun_rle:

    def remove_gliders(p):
        p = p + lt.pattern()  # copy
        for c in pattern_components(p):
            cc = c.centre()
            if (
                cc == canonical_se.centre()
                or cc == canonical_se[1].centre()
                or cc == canonical_se[2].centre()
                or cc == canonical_se[3].centre()
            ):
                p -= c
        return p

    if args.use_gun_rle == 'breaksnark0':
        args.use_gun_rle = '''x = 75, y = 73, rule = B3/S23
                              65b2o$64bobo$58b2o4bo$56bo2bo2b2ob4o$56b2obobobobo2bo$59bobobobo$59bob
                              ob2o$60bo2$73b2o$64b2o7bo$64b2o5bobo$71b2o4$72bo$70bobo$71b2o$61b2o$
                              62bo$59b3o$59bo48$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark1':
        args.use_gun_rle = '''x = 134, y = 133, rule = B3/S23
                              124b2o$123bobo$117b2o4bo$115bo2bo2b2ob4o$115b2obobobobo2bo$118bobobobo
                              $118bobob2o$119bo2$132b2o$123b2o7bo$123b2o5bobo$130b2o4$131bo$129bobo$
                              130b2o$120b2o$121bo$118b3o$118bo108$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark-1':
        args.use_gun_rle = '''x = 135, y = 132, rule = B3/S23
                              125b2o$124bobo$118b2o4bo$116bo2bo2b2ob4o$116b2obobobobo2bo$119bobobobo
                              $119bobob2o$120bo2$133b2o$124b2o7bo$124b2o5bobo$131b2o4$132bo$130bobo$
                              131b2o$121b2o$122bo$119b3o$119bo107$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark13':
        args.use_gun_rle = '''x = 121, y = 132, rule = B3/S23
                              111b2o$110bobo$104b2o4bo$102bo2bo2b2ob4o$102b2obobobobo2bo$105bobobobo
                              $105bobob2o$106bo2$119b2o$110b2o7bo$110b2o5bobo$117b2o4$118bo$116bobo$
                              117b2o$107b2o$108bo$105b3o$105bo107$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark-13':
        args.use_gun_rle = '''x = 147, y = 132, rule = B3/S23
                              137b2o$136bobo$130b2o4bo$128bo2bo2b2ob4o$128b2obobobobo2bo$131bobobobo
                              $131bobob2o$132bo2$145b2o$136b2o7bo$136b2o5bobo$143b2o4$144bo$142bobo$
                              143b2o$133b2o$134bo$131b3o$131bo107$b2o$obo$2bo!'''
    if args.use_gun_rle == 'hooked':
        args.use_gun_rle = '''x = 75, y = 79, rule = B3/S23
                            9bo$9b3o$12bo$11b2o5$2o5b2o$2o5b2o2$4b2o$4b2o$9b2o$8bo2bo$7b2o$8b2o17b
                            2o$9bo17b2o2$24b2o5b2o$24b2o5b2o32$47b2o$47bo$48b3o$50bo21$73bo$74bo$
                            72b3o!'''

    if args.use_gun_rle == 'perp_gli':
        args.use_gun_rle = '''x = 70, y = 84, rule = B3/S23
                              68bo$69bo$67b3o79$3o$2bo$bo!'''
    if args.use_gun_rle == '120':
        args.use_gun_rle = '''x = 83, y = 51, rule = B3/S23
                              14bo$14bo$14bo16$2o$obo64bo$bo65bo$67bo$17bo6b2o$16bobo5b2o37b3o3b3o$
                              16bobo$17bo49bo$34bo32bo$9b2o23b3o30bo$8bobo26bo$9bo26b2o5$25b2o5b2o$
                              25b2o5b2o46b2o$79bo2bo$29b2o49b2o$24bo4b2o$5b2o16bobo$5b2o16bobo$24bo$
                              52b2o$39bo12b2o$37bobo$37b3o9b2o5b2o$37bo11b2o5b2o$12bo$6b2o3bobo$5bo
                              2bo2b2o$6b2o!'''

    if args.use_gun_rle == '120C':
        args.use_gun_rle = '''x = 44, y = 47, rule = B3/S23
o$o$o20$3bo6b2o$2bobo5b2o$2bobo$3bo$20bo$20b3o$23bo$22b2o5$11b2o5b2o$11b2o5b2o2$15b2o$15b2o4$38b2o$25bo12b2o$23bobo$23b3o9b2o5b2o$23bo11b2o5b2o!'''

    if args.use_gun_rle == '120X':
        args.gun_X = True
        args.use_gun_rle = '''x = 99, y = 58, rule = B3/S23
                              9bo$9b3o$12bo$11b2o5$2o5b2o$2o5b2o2$4b2o$4b2o$9b2o$8bo2bo$7b2o$8b2o3bo
                              13b2o$9bo4bo12b2o$12b3o$24b2o5b2o$24b2o5b2o25$82bo$81bobo4b2o$82bobo3b
                              obo$83bo6bo4b2o$86b4ob2o2bo2bo$86bo2bobobobob2o$76bo12bobobobo$75bobo
                              12b2obobo$75bobo16bo$76bo2$89b2o$89b2o!'''
        args.required.add("barge(l46,o0,d26)")
        args.required.add("beehive(l34,o0,d25)")
        args.required.add("blinker(l26,o0,d12)")
        args.required.add("blinker(l26,o1,d6)")
        args.required.add("block(l43,o0,d41)")
        args.ignored.add("glider(l6)(0B0)(ph0)")

    gun = lt.pattern(args.use_gun_rle.strip())
    gun_without_gliders = remove_gliders(gun)

    glider_appears = gun
    # now, let's fast forward the gun until we see an output glider.
    for i in range(0, args.toolkit.period):
        if remove_gliders(glider_appears) != glider_appears:
            # there's a glider
            break
        glider_appears = glider_appears[1]

    glider_appears = glider_appears[(args.n_gun_gliders - 1) * args.toolkit.period]

    # and make the gliders canonical
    rect = (-float("inf"), 0, 0, 0)
    for i in range(0, 4):
        found = False
        for c in pattern_components(glider_appears):
            if c.centre() == canonical_se.centre():
                rect = max(rect, tuple(c.getrect()))
                found = True
        if found:
            break
        glider_appears = glider_appears[1]
    else:
        raise Exception("Couldn't find gliders?")

    # shift the gun so that it lines up with the fake gun.
    fx, fy, _, _ = mk_fake_gun(1).getrect()
    x, y, _, _ = rect
    fake_gun = glider_appears(fx - x, fy - y)


if args.extract_recipes:

    def parse_object(obj):
        m = re.match(r"^'([\w ]+)((?:\([^)]*\))*)'$", obj.strip())
        if not m:
            raise SyntaxError(f"Invalid object: {obj}")
        name = m.group(1)
        info = m.group(2)
        return {"name": name, "info": info}

    gun_glider_names = set()
    for i in range(0, args.n_gun_gliders):
        gun_glider_names.add(f"g{i}")

    def parse_objects(objs):
        results = []
        gun_gliders = set()
        for o in re.split(r"('[^']*')", objs):
            o = o.strip()
            if not o or o == ",":
                continue
            obj = parse_object(o)
            if obj["name"] in gun_glider_names:
                gun_gliders.add(obj["name"])
            else:
                results.append(obj)

        for i in range(0, args.n_gun_gliders):
            if not f"g{i}" in gun_gliders:
                break

        consumed = args.n_gun_gliders - i
        if i != len(gun_gliders):
            results.append("!skipped glider")
        if i == 0:
            results.append("!consumed all")

        return {"consumed": consumed, "objects": results}

    with open(args.extract_recipes) as results_file:
        for line in results_file:
            m = re.match(
                r"""^\((\d+(?:, \d+)*,?)\)\s*\[('[^']*'(?:, '[^']*')*)?\]$""",
                line.strip(),
            )
            if not m:
                raise SyntaxError(f"Invalid line: {line}")
            stream = tuple(map(int, filter(bool, m.group(1).split(","))))
            info = parse_objects(m.group(2) or "")
            if info["consumed"] < 10 and len(info["objects"]) == 1:
                print(stream, info)

    sys.exit(0)

if args.print_rle:
    macros = {
        'sc90b5p120': {
            'construct_arm': 'mode sc, 0, 126, 102, 100, 195, 90, 91, 95, 98, 105, 90, 101, 141, 94, 159, 92, 146, 99, 90, 152, 139, 144, 92, 161, 131, 116, 101, 114, 111, 112, 93, 127, 98, 102, 114, 107, 157, 90, 90, 90, 91, 91, 243, 113, 139, 108, 95, 127, 121, 99, 257, 144, 94, 218, 148, 226, 111, 119, 100, 95, 91, 138, 201, 221, 216, 138, 125, mode p120, set 28 (mod 120), (90)'
        }
    }
    green_patt = lt.pattern("")
    red_patt = lt.pattern("")
    recipes = args.print_rle.split(";")
    for i, r in enumerate(recipes):
        if not r.strip():
            continue
        parse = parse_p120_recipe(r, macros.get(args.toolkit.name, {}))
        gliders_int = parse.delays
        print("#", end="")
        print(gliders_int)
        if parse.start_mode == "p120":
            green_patt += fake_gun(200 * i, 0) + single_channel_stream(gliders_int)(
                200 * i, 0
            )
            red_patt += write_text(" ".join(map(str, gliders_int)))(200 * i, 0)
        elif parse.start_mode == "sc":
            green_patt += PI_BLOCKS[1](200 * i, 0) + single_channel_stream(gliders_int)(200*i, 0)
            red_patt += write_text(" ".join(map(str, gliders_int)))(200 * i, 0)
    print(write_life_history(green=green_patt, red=red_patt))

    sys.exit(0)

if args.check_for_shutoff:
    print("Checking if patterns shut off the stream.")

    recipes = args.check_for_shutoff.split(";")

    for i, r in enumerate(recipes):
        gliders_int = recipe_stream_to_delays(r)
        original = fake_gun + single_channel_stream(gliders_int)
        extended_fake_gun = mk_fake_gun(args.n_gun_gliders + 120)
        extended = extended_fake_gun + single_channel_stream(gliders_int)

        original = original[simulate_gens]

        extended_simulate_gens = simulate_gens + args.toolkit.period * 120 * 4
        extended = extended[extended_simulate_gens]
        # allow the first n+20 gliders to possibly escape, but the rest should be absorbed.
        expected_gun_positions = mk_fake_gun(args.n_gun_gliders + 20)[
            extended_simulate_gens
        ]

        if original == extended:
            print("Confirmed shutoff!")
            print(gliders_int)
        elif (original - expected_gun_positions) == (extended - expected_gun_positions):
            print("Possible shutoff with escaping gun gliders")
            print(gliders_int)

    sys.exit(0)

friendly_names = {
    "xq4_153": "glider",
    "xs4_33": "block",
    "xp2_7": "blinker",
    "xs6_696": "beehive",
    "xs7_2596": "loaf",
    "xs5_253": "boat",
    "xs6_356": "ship",
    "xs8_6996": "pond",
    "xs4_252": "tub",
    "xs7_25ac": "long boat",
    "xs12_g8o653z11": "ship tie",
    "xp2_7e": "toad",
    "xp2_318c": "beacon",
    "xs14_g88m952z121": "half bakery",
    "xs7_178c": "eater 1",
    "xs6_25a4": "barge",
    "xs6_bd": "snake",
    "xs11_g8o652z11": "boat tie ship",
    "xq4_6frc": "lwss",
    "xs8_178k8": "tub_with_tail",
}

reference = fake_gun[simulate_gens]

class HashablePattern:
    def __init__(self, c: lifelib.Pattern, digest: int):
        self.c = c
        self.digest = digest
    
    def __hash__(self):
        return self.digest

    def __eq__(self, other):
        return self.digest == other.digest

@functools.lru_cache(maxsize=None)
def pattern_orientation(c):
    c = c.c
    orientations = [c.digest()]
    for o in ["rot270", "rot180", "rot90", "flip_x", "flip_y", "swap_xy", "swap_xy_flip"]:
        orientations.append(c(o).digest())
    orientations = sorted(set(orientations))
    return orientations.index(c.digest())

def component_info(c, alt=False):
    if c == c[1]:
        # still life
        code = c.apgcode
        name = friendly_names.get(code, code)
        x, y, w, h = c.getrect()
        lane = x - y
        depth = x + y + w + h
        orientation = pattern_orientation(HashablePattern(c, c.digest()))
        if args.gun_X:
            if depth < -50:
                return ""
        return f"{name}(l{lane},o{orientation},d{depth})"
    elif c == c[120]:
        # oscillator
        code = c.apgcode
        name = friendly_names.get(code, code)
        x, y, w, h = c.getrect()
        if (name == 'blinker'): # coordinates of the center ??
            x = x + w//2
            y = y + h//2
        lane = x - y
        depth = x + y + w + h
        if args.gun_X:
            if depth < -50:
                return ""
        orientation = pattern_orientation(HashablePattern(c, c.digest()))
        if (name == 'blinker') and alt:
            # skip blinkers when processing the alt.
            return None
        return f"{name}(l{lane},o{orientation},d{depth})"
    elif c.centre() == c[4].centre():
        # spaceship
        code = c.apgcode
        name = friendly_names.get(code, code)

        x, y, w, h = c.getrect()
        x4, y4, _, _ = c[4].getrect()

        if x4 - x == 0 or y4 - y == 0:
            # xWSS, need to process even if alt is true.
            return f"{name}({x},{y})({x4-x},{y4-y})"
        elif alt:
            # ignore gliders in alt -- we've already got them.
            return None

        # give the incoming gliders the names g0, g1, etc.
        for i, e in enumerate(expected_incoming_gliders):
            if c == e:
                return f"g{i}"

        # glider phase/color info
        match (x4 - x, y4 - y):
            case (1, 1):
                # same direction as gun
                canonical = canonical_se
                direction = "B"
            case (1, -1):
                # gun side 90 degree
                canonical = canonical_ne
                direction = "R"
            case (-1, 1):
                # recipe side 90 degree
                canonical = canonical_sw
                direction = "L"
            case (-1, -1):
                # same direction as recipe
                canonical = canonical_nw
                direction = "F"

        if c.centre() == canonical:
            phase = 0
            phase_mod2 = "0"
        elif c[1].centre() == canonical:
            phase = 1
            phase_mod2 = "1"
        elif c[2].centre() == canonical:
            phase = 2
            phase_mod2 = "0"
        elif c[3].centre() == canonical:
            phase = 3
            phase_mod2 = "1"

        lx, ly, lw, lh = c[phase].getrect()
        lane = lx - ly
        depth = lx + ly + lw + lh

        match (x4 - x, y4 - y):
            case (1, 1):
                location = f"l{lane}"
                color = "01"[lane % 2]
            case (1, -1):
                location = f"d{depth}"
                color = "01"[depth % 2]
            case (-1, 1):
                location = f"d{depth}"
                color = "01"[depth % 2]
            case (-1, -1):
                location = f"l{lane}"
                color = "01"[lane % 2]

        return f"{name}({location})({color}{direction}{phase_mod2})(ph{phase})"
    else:
        # not an object
        return None

if args.view_orientations:
    if args.view_orientations in friendly_names.values():
        args.view_orientations = next(k for k,v in friendly_names.items() if v == args.view_orientations)
    p = lt.pattern(args.view_orientations)
    results = {}
    orientations = [p]
    for o in ["rot270", "rot180", "rot90", "flip_x", "flip_y", "swap_xy", "swap_xy_flip"]:
        orientations.append(p(o))
    for oriented in orientations:
        x, y, w, h = oriented.getrect()
        oriented = oriented(-x + 1, -y + 1)
        data = ['.'] * (w+2) * (h*2)
        for (cx, cy) in oriented.coords():
            data[cy*(w+2) + cx] = '#'
        data = ''.join(data)
        lines = []
        for i in range(0, h+2):
            lines.append(data[i*(w+2):(i+1)*(w+2)])
        lines = '\n'.join(lines)
        results[pattern_orientation(HashablePattern(oriented, oriented.digest()))] = lines
    
    for orientation, view in sorted(results.items()):
        print()
        print(f'o{orientation}')
        print('==========================')
        print(view)

    sys.exit(0)

def evaluate(s, patt):
    if patt.population > args.max_population:
        # print(s, 'too big')
        return
    if (patt - expected_incoming_gliders_pattern).population > args.max_population:
        return
    if patt.population < args.min_population:
        # print(s, 'too small')
        return
    if (patt - expected_incoming_gliders_pattern).population < args.min_population:
        return

    infos = set()
    infos.add(f"#{patt.population}")
    required_cnt = len(args.required)
    required_prefix_matched = args.required_prefix == ""

    if args.only_gliders:
        if patt.population % 5 or patt.population >= 5 + 5 * args.n_gun_gliders:
            return
        diff = patt - reference
        if diff.population != 5:
            return
        if diff.centre() != diff[4].centre():
            return
        gli_info = component_info(diff)
        if gli_info.startswith(args.avoid_prefix):
            return
        if gli_info.startswith(args.required_prefix):
            required_prefix_matched = True
        if not gli_info.startswith("glider"):
            return
        for i in range(patt.population // 5 - 1):
            infos.add(f"g{i}")
        infos.add(gli_info)
    else:
        components = pattern_components(patt)
        has_unidentified = False
        for c in components:
            info = component_info(c, False)
            if info is not None:
                if info.startswith("glider") and args.do_not_report_gliders:
                    return
                if info!="":
                    if info.startswith(args.avoid_prefix):
                        return
                    if info.startswith(args.required_prefix):
                        required_prefix_matched = True
                    if info in args.required:
                        required_cnt -= 1
                    elif not info in args.ignored:
                        infos.add(info)
            else:
                has_unidentified = True
        if has_unidentified:
            components1 = pattern_components(patt[1])
            for c in components1:
                info = component_info(c, True)
                if info is not None:
                    if info.startswith(args.avoid_prefix):
                        return
                    if info.startswith(args.required_prefix):
                        required_prefix_matched = True
                    if not info in args.required and not info in args.ignored:
                        infos.add(info)
    #infos.add(f"!{required_cnt}")
    #infos.add(f"!{required_prefix_matched}")
    if not required_prefix_matched:
        return
    if required_cnt>0:
        return
    return (s, sorted(infos))

def process_concurrent(s):
    stream_to_check = s
    if args.swim_upstream_after:
        stream_to_check = parse_p120_recipe(','.join(map(str, s)) + ',(90), 2, 96, 96, 96, 96, 96, 96, 96, 96', {}).delays
    patt = fake_gun + single_channel_stream(stream_to_check)

    patt_n = patt[simulate_gens]
    result = evaluate(s, patt_n)
    return result

def nontrivial(evaldescription):
    for i in range(len(evaldescription)):
        if evaldescription[i]==f"g{i}":
            continue
        if evaldescription[i]=="glider(l0)(ph3)(WB1)":
            continue
        return True

seen = []
def recurse(s, depth=0):
    stream_to_check = s
    if args.swim_upstream_after:
        stream_to_check = parse_p120_recipe(','.join(map(str, s)) + ',(90), 2, 96, 96, 96, 96, 96, 96, 96, 96', {}).delays
    patt = fake_gun + single_channel_stream(stream_to_check)

    if not args.to_apgluxe:
        patt_n = patt[simulate_gens]
        if (patt_n - expected_incoming_gliders_pattern).population > args.max_tree_population:
             #print("big\n"+(patt_n - expected_incoming_gliders_pattern).rle_string())
             return
        key = (patt_n - expected_incoming_gliders_pattern).rle_string()
        if key in seen[depth+1]:
             #print("seen:"+key)
             return
        seen[depth+1].add(key)
        e=evaluate(s, patt_n)
        if e:
            if nontrivial(e[1]):
                print(e, flush=True)
    else:
        print(patt.rle_string()[len("#CLL state-numbering golly\n") :])

    if depth > 0:
        seen[depth]=set()
        for n in range(args.toolkit.min_spacing, args.max_delay + 1):
            recurse(s + (n,), depth - 1)


if __name__ == "__main__":
    """
    p = lt.pattern('')
    for d1 in range(0, 8):
        p += (fake_gun + mk_glider(0, d1) + mk_glider(0, d1 + 90))(100*(d1), 0)

    print(p.rle_string())

    Results:
    0 - delete
    1 - block on gun side
    2 - backwards kickback
    3 - pond on gun side
    4 - delete
    5 - pond on recipe side
    6 - forwards kickback
    7 - block on recipe side
    """

    if args.concurrent:
        print("Generating jobs...", file=sys.stderr)
        if not args.subtree:
            args.subtree = [SubTreeDef("0-7")]
        len_all_options = 0
        for subtree in args.subtree:
            for depth in range(0, args.depth - len(subtree.options) + 1):
                deeper = (tuple(range(args.toolkit.min_spacing, args.max_delay + 1)),) * depth
                len_all_options += math.prod(map(len, subtree.options + deeper))

        print('Total:', len_all_options, file=sys.stderr)

        def all_options():
            for subtree in args.subtree:
                for depth in range(0, args.depth - len(subtree.options) + 1):
                    for starting_point in itertools.product(*subtree.options):
                        deeper = (tuple(range(args.toolkit.min_spacing, args.max_delay + 1)),) * depth
                        for d in itertools.product(*map(lambda a: (a,), starting_point), *deeper):
                            yield d

        multiprocessing.set_start_method('spawn')
        with Pool(processes=os.cpu_count() - 1) as pool:
            speedo = Speedometer()
            print("Starting...", file=sys.stderr)
            for result in pool.imap_unordered(process_concurrent, all_options(), chunksize=256):
                if speedo.tick(1):
                    current_per_s = speedo.get_current_speed_and_reset()
                    avg_per_s = speedo.overall_speed()
                    done = speedo.n_finished
                    percent = done/len_all_options*100
                    estimate_s = int((len_all_options - done)/avg_per_s)
                    estimate_min = estimate_s // 60
                    estimate_hr = estimate_min // 60
                    if estimate_hr > 0:
                        estimate = f"{estimate_hr}h {estimate_min % 60}m {estimate_s % 60}s"
                    elif estimate_min > 0:
                        estimate = f"{estimate_min % 60}m {estimate_s % 60}s"
                    else:
                        estimate = f"{estimate_s}s"
                    print(f"{current_per_s:.2f}/s, {avg_per_s:.2f}/s avg, {done}/{len_all_options} ({percent:0.2f}%), {estimate} left", file=sys.stderr)
                if result:
                    print(*result)
        pool.join()
    else:
        if not args.subtree:
            args.subtree = [SubtreeDef("0-7")]
        for d in range(0,args.depth+2):
            seen.append(set())
        for subtree in args.subtree:
            for starting_point in itertools.product(*subtree.options):
                seen[args.depth - len(starting_point)+1]=set()
                recurse(starting_point, depth=args.depth - len(starting_point))
The transposition tables ... "seen" are important for the search complexity.

Oops ... they actually filter by final configuration, but the reaction could significantly differ so a lot of possibilities could be missed this way.
For example "0; 197; 141; 97" encoding the original snarkreaker was not found by search started at subtrees "0" and "0; 197" ... it required start at subtree "0; 197; 141" :(. But without simillar pruning the search would last forever.

One shift to nothing was easy: (this is actually what I would need ... twice the same side turn destroyal)

Code: Select all

x = 59, y = 50, rule = LifeHistory
11.2A$10.A.A$4.2A4.A$2.A2.A2.2A.4A$2.2A.A.A.A.A2.A$5.A.A.A.A$5.A.A.2A
$6.A2$19.2A$10.2A7.A$10.2A5.A.A$17.2A4$3A$2.A$.A$7.2A$8.A$5.3A8.3A$5.
A10.A$17.A6$49.D2.3D.3D$48.2D2.D.D.D$49.D2.D.D.3D$49.D2.D.D3.D$48.3D.
3D.2D14$43.2A$42.2A$44.A!
Using the other lane, we have turns different directions as well:

Code: Select all

x = 74, y = 78, rule = LifeHistory
11.2A$10.A.A$4.2A4.A$2.A2.A2.2A.4A$2.2A.A.A.A.A2.A$5.A.A.A.A$5.A.A.2A
$6.A2$19.2A$10.2A7.A$10.2A5.A.A$17.2A7$7.2A$8.A$5.3A$5.A3$21.2A$20.2A
$22.A2$48.3D.3D6.D3.D2.3D$3A45.D.D.D.D5.2D2.2D4.D$2.A45.3D.D.D6.D3.D
2.3D$.A48.D.D.D6.D3.D2.D$48.3D.3D5.3D.3D.3D14$44.A$43.2A$43.A.A26$72.
A$71.2A$71.A.A!
I am still searching for other cases, but do we really need them?

I am mainly concentrated on the "glider(l1)(1B" case ... it would solve all our problems.

I have added tree cutting by bounding box sizes to ongoing.py to be able to finish the search.
This esentially allows removing partials with escaping ships in unwanted directions.
Unfortunately it removes all solutions containing prefix with such escaping ships even when the fast followup would prevent them.
But I hope it is worth much faster search in slow-like recipe space.

Meanwhile I got better snarkbreaker to nothing ...

Code: Select all

x = 82, y = 378, rule = LifeHistory
11.2A$10.A.A$4.2A4.A$2.A2.A2.2A.4A$2.2A.A.A.A.A2.A$5.A.A.A.A$5.A.A.2A
$6.A2$19.2A$10.2A7.A$10.2A5.A.A$17.2A4$.3A$3.A$2.A$7.2A$8.A$5.3A$5.A
2$20.2A$19.2A$21.A3$49.D2.D.D2.D6.3D.3D$48.2D2.D.D.2D6.D.D3.D$49.D2.
3D2.D6.3D3.D$49.D4.D2.D8.D2.D$48.3D3.D.3D5.3D2.D26$55.2A$55.A.A$55.A
22$80.A$79.2A$79.A.A5$11.2A$10.A.A$4.2A4.A$2.A2.A2.2A.4A$2.2A.A.A.A.A
2.A$5.A.A.A.A$5.A.A.2A$6.A2$19.2A$10.2A7.A$10.2A5.A.A$17.2A4$.3A$3.A$
2.A10.3A$7.2A4.A$8.A5.A$5.3A$5.A7$48.3D.3D6.D2.3D.3D$48.D.D.D.D5.2D2.
D.D3.D$48.3D.D.D6.D2.D.D3.D$50.D.D.D6.D2.D.D2.D$48.3D.3D5.3D.3D2.D7$
36.2A$36.A.A$36.A25$63.2A$62.2A$64.A51$11.2A$10.A.A$4.2A4.A$2.A2.A2.
2A.4A$2.2A.A.A.A.A2.A$5.A.A.A.A$5.A.A.2A$6.A2$19.2A$10.2A7.A$10.2A5.A
.A$17.2A4$3A$2.A$.A$7.2A$8.A$5.3A8.3A$5.A10.A$17.A6$49.D2.3D.3D$48.2D
2.D.D.D$49.D2.D.D.3D$49.D2.D.D3.D$48.3D.3D.2D14$43.2A$42.2A$44.A41$
11.2A$10.A.A$4.2A4.A$2.A2.A2.2A.4A$2.2A.A.A.A.A2.A$5.A.A.A.A$5.A.A.2A
$6.A2$19.2A$10.2A7.A$10.2A5.A.A$17.2A7$7.2A$8.A$5.3A$5.A3$21.2A$20.2A
$22.A2$48.3D.3D6.D3.D2.3D$3A45.D.D.D.D5.2D2.2D4.D$2.A45.3D.D.D6.D3.D
2.3D$.A48.D.D.D6.D3.D2.D$48.3D.3D5.3D.3D.3D14$44.A$43.2A$43.A.A26$72.
A$71.2A$71.A.A!
So my "restricted search to depth 6" looking even for timing of the first glider unfortunately have not found a "glider(l1)(1B".

So far this is what I have found (with russellsprouts chain reaction):

Code: Select all

x = 779, y = 742, rule = LifeHistory
22.2A118.2A118.2A118.2A$21.A.A117.A.A117.A.A117.A.A$15.2A4.A113.2A4.A
113.2A4.A113.2A4.A$13.A2.A2.2A.4A107.A2.A2.2A.4A107.A2.A2.2A.4A107.A
2.A2.2A.4A$13.2A.A.A.A.A2.A107.2A.A.A.A.A2.A107.2A.A.A.A.A2.A107.2A.A
.A.A.A2.A$16.A.A.A.A113.A.A.A.A113.A.A.A.A113.A.A.A.A$16.A.A.2A114.A.
A.2A114.A.A.2A114.A.A.2A$17.A119.A119.A119.A2$30.2A118.2A118.2A118.2A
$21.2A7.A110.2A7.A110.2A7.A110.2A7.A$21.2A5.A.A110.2A5.A.A110.2A5.A.A
110.2A5.A.A$28.2A118.2A118.2A118.2A4$12.3E117.3E117.3E117.3E$14.E119.
E119.E119.E$13.E10.3E106.E119.E119.E$18.2A4.E113.2A118.2A118.2A$19.A
5.E113.A119.A119.A$16.3A117.3A117.3A117.3A$16.A119.A119.A119.A2$151.
2E118.2E6.D2.3D.3D6.D2.3D.3D6.D3.D2.3D6.D2.3D.3D54.2E6.D3.D2.3D6.D2.
3D.3D5.3D.3D6.D2.3D.3D$150.2E118.2E6.2D2.D.D.D7.2D2.D.D3.D5.2D2.2D2.D
.D5.2D4.D.D.D53.2E6.2D2.2D2.D.D5.2D4.D3.D5.D.D.D.D5.2D2.D.D3.D$152.E
119.E6.D2.D.D.3D6.D2.D.D.3D6.D3.D2.3D6.D2.3D.3D55.E6.D3.D2.3D6.D2.3D
3.D5.3D.D.D6.D2.3D3.D$279.D2.D.D.D.D6.D2.D.D3.D6.D3.D4.D6.D4.D.D.D62.
D3.D4.D6.D2.D4.D8.D.D.D6.D4.D2.D$278.3D.3D.3D5.3D.3D.3D5.3D.3D.3D5.3D
.3D.3D61.3D.3D.3D5.3D.3D2.D6.3D.3D5.3D.3D2.D$59.3D.3D6.D2.3D.3D98.D2.
D.D2.D6.3D.3D$59.D.D.D.D5.2D2.D.D3.D97.2D2.D.D.2D6.D.D3.D$59.3D.D.D6.
D2.D.D3.D98.D2.3D2.D6.3D3.D$61.D.D.D6.D2.D.D2.D99.D4.D2.D8.D2.D$59.3D
.3D5.3D.3D2.D98.3D3.D.3D5.3D2.D7$47.2A$47.A.A$47.A8$298.A$297.2A$297.
A.A2$420.3A$420.A$421.A3$186.2A$186.A.A$186.A6$74.2A$73.2A$75.A7$323.
2A$323.A.A$323.A5$211.A$210.2A$210.A.A240.A$452.2A$452.A.A17$111.2D$
110.2D$112.D120.2D118.2A$232.2D118.2A$234.D119.A120.2A$474.2A$476.A
10$22.2A118.2A$21.A.A117.A.A$15.2A4.A113.2A4.A$13.A2.A2.2A.4A107.A2.A
2.2A.4A$13.2A.A.A.A.A2.A107.2A.A.A.A.A2.A$16.A.A.A.A113.A.A.A.A$16.A.
A.2A114.A.A.2A$17.A119.A2$30.2A118.2A$21.2A7.A110.2A7.A$21.2A5.A.A
110.2A5.A.A$28.2A118.2A4$11.3E$13.E$12.E$18.2A118.2A$19.A119.A248.A$
16.3A8.3E106.3A8.3E237.2A$16.A10.E108.A10.E239.A.A$28.E119.E6$60.D2.
3D.3D48.E61.D2.3D.D.D6.D2.3D.3D5.3D.3D$59.2D2.D.D.D50.2E59.2D2.D3.D.D
5.2D4.D.D.D5.D.D.D.D$60.D2.D.D.3D47.E.E60.D2.3D.3D6.D2.3D.3D5.3D.D.D$
60.D2.D.D3.D110.D2.D.D3.D6.D2.D3.D.D7.D.D.D90.2E$59.3D.3D.2D110.3D.3D
3.D5.3D.3D.3D5.3D.3D90.2E4$524.2A$524.A.A$524.A5$317.2A$316.2A$318.A$
54.2A$53.2A$55.A13$188.3A$188.A$189.A3$340.2A$340.A.A$77.D262.A$76.2D
$76.D.D19$22.2A118.2A118.2A99.2A$21.A.A117.A.A117.A.A98.2A$15.2A4.A
113.2A4.A113.2A4.A102.A$13.A2.A2.2A.4A107.A2.A2.2A.4A107.A2.A2.2A.4A$
13.2A.A.A.A.A2.A107.2A.A.A.A.A2.A74.3A30.2A.A.A.A.A2.A$16.A.A.A.A113.
A.A.A.A77.A35.A.A.A.A$16.A.A.2A114.A.A.2A79.A34.A.A.2A$17.A119.A119.A
2$30.2A118.2A118.2A$21.2A7.A110.2A7.A110.2A7.A$21.2A5.A.A110.2A5.A.A
110.2A5.A.A$28.2A118.2A118.2A7$18.2A118.2A6.2E110.2A$19.A119.A5.2E
112.A$16.3A10.E106.3A8.E108.3A10.E$16.A11.2E106.A119.A11.2E$28.E.E
237.E.E3$243.2A$243.A.A$243.A$60.D2.3D2.D6.3D.3D98.D2.3D.3D5.3D.3D.3D
5.3D.D.D82.D2.3D.3D5.3D.3D71.A$11.3E45.2D2.D.D.2D6.D.D.D.D49.3E45.2D
2.D.D.D9.D3.D3.D5.D.D.D.D33.3E45.2D2.D.D.D.D5.D.D.D.D70.2A$13.E46.D2.
D.D2.D6.3D.D.D51.E46.D2.D.D.3D5.3D.3D3.D5.3D.3D35.E46.D2.D.D.D.D5.3D.
D.D70.A.A$12.E47.D2.D.D2.D8.D.D.D50.E47.D2.D.D.D.D5.D5.D2.D8.D3.D34.E
47.D2.D.D.D.D7.D.D.D$59.3D.3D.3D5.3D.3D97.3D.3D.3D5.3D.3D2.D6.3D3.D
81.3D.3D.3D5.3D.3D12$173.A$172.2A120.A$53.3A116.A.A118.2A$53.A239.A.A
$54.A210.3D$265.D$266.D2$415.3A$415.A$416.A14$76.2A238.2A$76.A.A236.
2A$76.A240.A19$22.2A118.2A310.A$21.A.A117.A.A195.E113.2A$15.2A4.A76.
3D34.2A4.A196.2E113.A.A$13.A2.A2.2A.4A72.D34.A2.A2.2A.4A192.E.E$13.2A
.A.A.A.A2.A73.D33.2A.A.A.A.A2.A$16.A.A.A.A113.A.A.A.A$16.A.A.2A114.A.
A.2A$17.A119.A2$30.2A118.2A$21.2A7.A110.2A7.A$21.2A5.A.A110.2A5.A.A$
28.2A118.2A3$3E117.3E108.3A$2.E119.E108.A$.E119.E110.A2$18.2A118.2A$
19.A119.A$16.3A117.3A$16.A119.A$476.2A$475.2A$361.3A113.A$361.A$362.A
2$59.3D.3D.3D5.3D2.D98.3D.D.D6.D2.3D.3D6.D2.3D.3D$61.D3.D3.D5.D.D.2D
98.D.D.D.D5.2D2.D.D.D.D5.2D2.D3.D$59.3D.3D.3D5.3D2.D98.3D.3D6.D2.D.D.
3D6.D2.3D.3D$59.D3.D3.D9.D2.D100.D3.D6.D2.D.D.D.D6.D2.D.D3.D$59.3D.3D
.3D5.3D.3D97.3D3.D5.3D.3D.3D5.3D.3D.2D5$164.3E88.2A$164.E90.A.A$165.E
89.A5$52.2E$52.E.E$52.E332.A$384.2A114.3A$384.A.A113.A$501.A11$188.2A
$188.A.A$188.A14$414.2A$413.2A$415.A7$540.A$539.2A$215.2A322.A.A$215.
A.A$215.A7$314.2D$314.D.D$314.D$437.2A$107.3A327.A.A$107.A329.A$108.A
13$570.A$569.2A$569.A.A5$131.A$130.2A$130.A.A4$257.A$256.2A$256.A.A7$
475.2A$474.2A117.A$476.A115.2A$592.A.A5$153.2D$152.2D$154.D4$279.2D$
278.2D$280.D6$498.A$497.2A$497.A.A4$620.A$619.2A$619.A.A17$522.2A$
522.A.A$522.A2$642.2A$641.2A$643.A23$668.A$667.2A$667.A.A8$561.2A$
560.2A$562.A28$591.2A$590.2A117.A$592.A115.2A$708.A.A20$614.2A$613.2A
116.2A$615.A114.2A$732.A22$755.2A$754.2A$641.2A113.A$640.2A$642.A20$
664.A$663.2A$663.A.A24$689.2A$688.2A$690.A39$730.2A$729.2A$731.A20$
753.A$752.2A$752.A.A22$777.A$776.2A$776.A.A!
So we can send backward glider, convert it to 13hd shifted glider while destroying one turn and use the glider to either cleanly destroy previous turn or to convert it to speboe. Then we can restart the chain again. We can as well convert the last turn directly to nothing or to speboe.

Double turn the same direction can be destroyed to nothing a by glider shifted by just 1hd.
We can as well convert single turn to either speboe or to nothing.

0 shifted glider would be better to destroy each turn by the same method, but no such conversion was found.

I am done with the search for now. Oh I have another result in the data ... we have both destruction and conversion to speboe from one direction of 1hd shift. Unfortunatey for the other 1hd shift no interesting recipe was found.

The restart is rather expensive compared to the conversions ... it would be nice to find a backward glider shifted by 0hd, +/-13hd (or 1hd) ... for same/different turn.

BTW: I was surprised by the following (sc90) SPEBOE: (the boat could be probably combined by others ...)

Code: Select all

x = 19, y = 16, rule = LifeHistory
5$5.2A$5.A.A$6.A4$9.2A3.A$9.2A2.2AB$13.ABAB$14.4B$15.4B!
User avatar
Hippo.69
Posts: 696
Joined: July 14th, 2020, 7:35 pm
Contact:

Re: Optimized Snarkmaker

Post by Hippo.69 »

OK, I have to show my current search method:
Working on windows notebook with 4 cores/8threads.
I am running 5 *.bat files in parallel with the content like:

Code: Select all

@set task=--use-gun-rle "breaksnark-13" --n-gun-gliders=1 --max-delay=300 --simulate-gens=4096 --min-tree-x=-50 --min-tree-y=-50 --max-tree-width=50 --max-tree-height=50 --required-prefixes="empty;speboe;glider"
  @rem uv run oncoming.py %task% --subtree="0; 420-600" --depth=2 --max-tree-population=300 --max-population=20 > sdD2t.txt
  @rem uv run oncoming.py %task% --subtree="0; 420-600" --depth=3 --max-tree-population=300 --max-population=15 > sdD3t.txt
  @rem empty uv run oncoming.py %task% --subtree="0; 420-600" --depth=4 --max-tree-population=300 --max-population=10 > sdD4t.txt
  @rem 
uv run oncoming.py %task% --subtree="0; 420-600" --depth=5 --max-tree-population=300 --max-population=8 > sdD5t.txt
  @rem uv run oncoming.py %task% --subtree="0; 420-600" --depth=6 --max-tree-population=300 --max-population=8 > sdD6t.txt
generating outputs like sdD4t.txt

Code: Select all

((0, 426), ['#30', '<-22,-34,19,17>', 'boat(l13,o0,d-33)', 'loaf(l12,o1,d-48)', 'loaf(l14,o2,d-20)', 'loaf(l16,o2,d-40)', 'speboeblock(l4,o0,d-36)'])
((0, 431), ['#27', '<-20,-32,19,26>', 'beehive(l-2,o0,d-31)', 'blinker(l15,o1,d-43)', 'eater 1(l-9,o3,d-21)', 'long boat(l17,o1,d-19)', 'speboeblock(l4,o0,d-36)'])
((0, 492, 94, 90), ['#8', '<-19,-24,7,10>', 'block(l10,o0,d-34)', 'speboeblock(l-3,o0,d-31)'])
((0, 522, 222, 91), ['#0', '<0,0,-1,-1>', 'empty'])
search done
I am slowly increasing the depth of the search till all goals will be fullfilled or I run of my patience.

Current version of oncoming.py used is

Code: Select all

import sqlite3
import sys
import argparse
import re
import itertools
import functools
import pathlib
import os
from multiprocessing import Pool
import multiprocessing
from datetime import timedelta
import math

from arg_parser import range_str_to_list
from components import pattern_components
from font import write_text
from life_history import write_life_history
from lifetree import lt
from gliders import PI_BLOCKS, mk_glider, single_channel_stream
from parse_p120_recipes import parse_p120_recipe
from speedometer import Speedometer

argparser = argparse.ArgumentParser(
    prog="oncoming.py", description="Search for glider patterns"
)


class ToolkitDef:
    min_spacing: int
    lane_offset: int
    period: int
    name: str

    def __init__(self, str):
        m = re.match(r"sc(\d+)b(\d+)p(\d+)", str)
        if not m:
            raise ValueError(
                f"Toolkit {str} is invalid. Expected something like sc90b5p120"
            )
        self.min_spacing = int(m.group(1))
        self.lane_offset = int(m.group(2))
        self.period = int(m.group(3))
        self.name = str


argparser.add_argument(
    "--toolkit",
    default="sc90b5p120",
    type=ToolkitDef,
    help="The oncoming gliders to search. Format sc[min spacing]b[lane_offset]p[period]. E.g., sc90b5p120",
)
argparser.add_argument(
    "--depth", type=int, help="The number of gliders to include in salvos"
)
argparser.add_argument(
    "--print-rle",
    type=str,
    default="",
    help="Prints a rle showing the given recipes. Skips processing anything else.",
)
argparser.add_argument(
    "--required-prefix",
    type=str,
    default="",
    help="results not containing info with given prefix are not reported",
)
argparser.add_argument(
    "--required-prefixes",
    type=str,
    default="",
    help="results not containing info with a prefix from the list are not reported",
)
argparser.add_argument(
    "--required",
    type=str,
    default="",
    help="results not containing all given exact semicolon separated infos are not reported",
)
argparser.add_argument(
    "--ignored",
    type=str,
    default="",
    help="given semicolon separated infos are not reported, but it does not block reporting the salvo",
)
argparser.add_argument(
    "--avoid-prefix",
    type=str,
    default=")(",
    help="results containing info with given prefix are not reported",
)
argparser.add_argument(
    "--n-gun-gliders", type=int, default=10, help="How many gun gliders to simulate"
)
argparser.add_argument(
    "--simulate-gens", type=int, default=None, help="How many generations to simulate"
)
argparser.add_argument(
    "--max-population", type=int, default=float('inf'), help="The maximum population results to report"
)
argparser.add_argument(
    "--min-population", type=int, default=0, help="The minimum population results to report"
)
argparser.add_argument(
    "--max-tree-population", type=int, default=float('inf'), help="The maximum population allowed in intermediates"
)
argparser.add_argument(
    "--max-tree-width", type=int, default=float('inf'), help="The maximum width allowed in intermediates"
)
argparser.add_argument(
    "--max-tree-height", type=int, default=float('inf'), help="The maximum height allowed in intermediates"
)
argparser.add_argument(
    "--max-tree-x", type=int, default=float('inf'), help="The maximum x allowed in intermediates"
)
argparser.add_argument(
    "--max-tree-y", type=int, default=float('inf'), help="The maximum y allowed in intermediates"
)
argparser.add_argument(
    "--min-tree-x", type=int, default=-float('inf'), help="The minimum x allowed in intermediates"
)
argparser.add_argument(
    "--min-tree-y", type=int, default=-float('inf'), help="The minimum y allowed in intermediates"
)

class SubtreeDef:
    options: tuple[tuple[int]]

    def __init__(self, str):
        depths = ()
        for d in str.split(";"):
            options = range_str_to_list(d)
            depths = depths + (tuple(options),)
        self.options = depths


argparser.add_argument(
    "--subtree",
    default=[],
    type=SubtreeDef,
    action="append",
    help="Search range. E.g. '1;90-255' to search the glider 1 followed by any glider in [90,255], or '1,3,5,7' to search the starting gliders 1,3,5 and 7. Use comma to separate at the same depth, then semicolon to separate depths. Ranges are inclusive.",
)
argparser.add_argument(
    "--max-delay", type=int, default=255, help="Maximum delay for a glider."
)
argparser.add_argument(
    "--check-for-shutoff",
    type=str,
    help="Checks if the given sequences shut off the glider stream by sending 120 extra gun gliders and comparing the state",
)
argparser.add_argument(
    "--find-minimum-follow",
    type=str,
    help="Finds the minimum follow distance for the given recipes",
)
argparser.add_argument("--use-gun-rle", type=str, help="Uses the rle as the gun")
argparser.add_argument(
    "--extract-recipes",
    type=pathlib.Path,
    help="Extracts the recipes from the given file. Outputs a JSON.",
)
argparser.add_argument(
    "--to-apgluxe",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Just print out the rles, don't simulate anything.",
)
argparser.add_argument(
    "--only-gliders",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Only process glider recipes.",
)
argparser.add_argument(
    "--do-not-report-gliders",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Do not report glider including results.",
)
argparser.add_argument(
    "--swim-upstream-after",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Send 2,96,96,96... after each part of the subtree."
)
argparser.add_argument(
    "--concurrent",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Use multiple threads for searching."
)
argparser.add_argument(
    "--view-orientations",
    type=str,
    default=None
)

args = argparser.parse_args()
simulate_gens = args.simulate_gens or args.toolkit.period * args.n_gun_gliders

simkin_gun = lt.pattern("""x = 37, y = 26, rule = B3/S23
3$11bo$11b3o$14bo$13b2o5$2b2o5b2o$2b2o5b2o2$6b2o$6b2o4$29b2o$16bo12b2o
$14bobo$14b3o9b2o5b2o$14bo11b2o5b2o!
""")(-490, -501)

gun_period = args.toolkit.period


def mk_fake_gun(n):
    fake_gun = sum(
        [mk_glider(0, gun_period * x) for x in range(0, n)], start=lt.pattern("")
    )
    fake_gun = fake_gun("rot180")(-10 + args.toolkit.lane_offset, -11)
    return fake_gun


fake_gun = mk_fake_gun(args.n_gun_gliders)
expected_incoming_gliders_pattern = fake_gun[simulate_gens]
expected_incoming_gliders = pattern_components(expected_incoming_gliders_pattern)

# phase 0 gliders
canonical_se = mk_glider(0, 0)("rot180").centre()
canonical_nw = mk_glider(0, 0).centre()
canonical_ne = mk_glider(0, 0)("rot270").centre()
canonical_sw = mk_glider(0, 0)("rot90").centre()

def recipe_stream_to_delays(stream):
    gliders = stream.split(",")
    result = []
    total = 0
    for g in gliders:
        m = re.match(r"^\(\s*(\d+)\s*\)\s*(?:\s*(\d+)\s*\+\s*)?(\d+)$", g.strip())
        if m:
            minimum_follow = int(m.group(1))
            addition = m.group(2)
            if addition:
                minimum_follow += int(addition)
            next_parity = int(m.group(3))
            parity = (total + minimum_follow) % 8
            g = minimum_follow + ((next_parity - parity) % 8)
            total += g
            result.append(g)
        else:
            g = int(g)
            total += g
            result.append(g)
    return result

def find_minimum_follow(recipes):
    expected_incoming_gliders = pattern_components(fake_gun[simulate_gens])
    for i, r in enumerate(recipes):
        if not r.strip():
            continue
        delays = recipe_stream_to_delays(r)
        total = sum(delays)

        p = fake_gun + single_channel_stream(delays)
        p = p[simulate_gens]

        escaping_gliders = []
        for c in pattern_components(p):
            for i, e in enumerate(expected_incoming_gliders):
                if c == e:
                    # this is one of the escaping gliders
                    escaping_gliders.append(i)
        n_escaping = max(escaping_gliders) + 1

        # now we know which gliders participate. Remove the rest.
        envelope = lt.pattern("5o$5o$5o$5o$5o").centre()

        pattern = mk_fake_gun(args.n_gun_gliders - n_escaping) + single_channel_stream(
            delays
        )

        @functools.lru_cache(maxsize=None)
        def pattern_at_gen(n):
            if n == 0:
                return pattern
            return pattern_at_gen(n - 1)[1]

        def try_follow(i):
            follow_up_glider = mk_glider(0, total + i)
            glider_envelope = lt.pattern()
            for j in range(0, simulate_gens + i):
                follow_up_glider = follow_up_glider[1]
                glider_envelope += follow_up_glider.convolve(envelope)
                if (pattern_at_gen(j + 1) & glider_envelope).nonempty():
                    return False
            return True

        low = args.toolkit.min_spacing
        r = 1
        if try_follow(low):
            return f",({low})"
        else:
            while r<=1024:
                hi = args.toolkit.min_spacing+r
                r += r
                if try_follow(hi):
                    while (hi-low>1):
                        test=(hi+low)//2
                        if try_follow(test):
                            hi=test
                        else:
                            low=test
                    return f",({hi})"
                else:
                    low=hi
            else:
                return f",({hi}+???)"

if args.find_minimum_follow:
    follow = find_minimum_follow(args.find_minimum_follow.split(";"))
    print(",".join(map(str, delays)) + follow)
    sys.exit(0)

required_split = args.required.split(";")
args.required = set()
for req in required_split:
    if req != "":
        args.required.add(req)
args.required_prefixes = args.required_prefixes.split(";")
ignored_split = args.ignored.split(";")
args.ignored = set()
for ign in ignored_split:
    args.ignored.add(ign)
args.gun_X = False

if args.use_gun_rle:

    def remove_gliders(p):
        p = p + lt.pattern()  # copy
        for c in pattern_components(p):
            cc = c.centre()
            if (
                cc == canonical_se.centre()
                or cc == canonical_se[1].centre()
                or cc == canonical_se[2].centre()
                or cc == canonical_se[3].centre()
            ):
                p -= c
        return p

    args.speboes = ["block(l-3,", "block(l4,", "beehive(l-3,o0", "beehive(l4,o1", "pond(l-4,", "pond(l5,", "boat(l-4,o1", "boat(l5,o2", "ship(l-4,o1", "ship(l5,o1"]
    if args.use_gun_rle == 'breaksnark0Speboe': # just to catalogue speboes (block l-3,l4; beehive l-3o0, l4o1; pond l-4,l5; boat l-401,l5o2; ship l-4o1,l5o1)
        args.use_gun_rle = '''x = 75, y = 119, rule = B3/S23
                              9b2o$8bobo$8b2o5$7b2o7bo$6bobo6bobo$6b2o7b2o4$22b2o$14b2o5bo2bo$13bobo
                              5bo2bo$14bo7b2o3$19b2o6b2o$18bo2bo4bo2bo$18bo2bo5b2o$19b2o3$26bo5b2o$
                              25bobo4b2o$25bobo$26bo3$31b2o$31b2o14$65b2o$64bobo$58b2o4bo$56bo2bo2b
                              2ob4o$56b2obobobobo2bo$59bobobobo$59bobob2o$60bo2$73b2o$64b2o7bo$64b2o
                              5bobo$71b2o4$72bo$70bobo$71b2o$61b2o$62bo$59b3o$59bo48$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark0':
        args.use_gun_rle = '''x = 75, y = 73, rule = B3/S23
                              65b2o$64bobo$58b2o4bo$56bo2bo2b2ob4o$56b2obobobobo2bo$59bobobobo$59bob
                              ob2o$60bo2$73b2o$64b2o7bo$64b2o5bobo$71b2o4$72bo$70bobo$71b2o$61b2o$
                              62bo$59b3o$59bo48$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark1':
        args.use_gun_rle = '''x = 134, y = 133, rule = B3/S23
                              124b2o$123bobo$117b2o4bo$115bo2bo2b2ob4o$115b2obobobobo2bo$118bobobobo
                              $118bobob2o$119bo2$132b2o$123b2o7bo$123b2o5bobo$130b2o4$131bo$129bobo$
                              130b2o$120b2o$121bo$118b3o$118bo108$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark-1':
        args.use_gun_rle = '''x = 135, y = 132, rule = B3/S23
                              125b2o$124bobo$118b2o4bo$116bo2bo2b2ob4o$116b2obobobobo2bo$119bobobobo
                              $119bobob2o$120bo2$133b2o$124b2o7bo$124b2o5bobo$131b2o4$132bo$130bobo$
                              131b2o$121b2o$122bo$119b3o$119bo107$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark13':
        args.use_gun_rle = '''x = 121, y = 132, rule = B3/S23
                              111b2o$110bobo$104b2o4bo$102bo2bo2b2ob4o$102b2obobobobo2bo$105bobobobo
                              $105bobob2o$106bo2$119b2o$110b2o7bo$110b2o5bobo$117b2o4$118bo$116bobo$
                              117b2o$107b2o$108bo$105b3o$105bo107$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark-13':
        args.use_gun_rle = '''x = 147, y = 132, rule = B3/S23
                              137b2o$136bobo$130b2o4bo$128bo2bo2b2ob4o$128b2obobobobo2bo$131bobobobo
                              $131bobob2o$132bo2$145b2o$136b2o7bo$136b2o5bobo$143b2o4$144bo$142bobo$
                              143b2o$133b2o$134bo$131b3o$131bo107$b2o$obo$2bo!'''
    if args.use_gun_rle == 'hooked':
        args.use_gun_rle = '''x = 75, y = 79, rule = B3/S23
                            9bo$9b3o$12bo$11b2o5$2o5b2o$2o5b2o2$4b2o$4b2o$9b2o$8bo2bo$7b2o$8b2o17b
                            2o$9bo17b2o2$24b2o5b2o$24b2o5b2o32$47b2o$47bo$48b3o$50bo21$73bo$74bo$
                            72b3o!'''

    if args.use_gun_rle == 'perp_gli':
        args.use_gun_rle = '''x = 70, y = 84, rule = B3/S23
                              68bo$69bo$67b3o79$3o$2bo$bo!'''
    if args.use_gun_rle == '120':
        args.use_gun_rle = '''x = 83, y = 51, rule = B3/S23
                              14bo$14bo$14bo16$2o$obo64bo$bo65bo$67bo$17bo6b2o$16bobo5b2o37b3o3b3o$
                              16bobo$17bo49bo$34bo32bo$9b2o23b3o30bo$8bobo26bo$9bo26b2o5$25b2o5b2o$
                              25b2o5b2o46b2o$79bo2bo$29b2o49b2o$24bo4b2o$5b2o16bobo$5b2o16bobo$24bo$
                              52b2o$39bo12b2o$37bobo$37b3o9b2o5b2o$37bo11b2o5b2o$12bo$6b2o3bobo$5bo
                              2bo2b2o$6b2o!'''

    if args.use_gun_rle == '120C':
        args.use_gun_rle = '''x = 44, y = 47, rule = B3/S23
o$o$o20$3bo6b2o$2bobo5b2o$2bobo$3bo$20bo$20b3o$23bo$22b2o5$11b2o5b2o$11b2o5b2o2$15b2o$15b2o4$38b2o$25bo12b2o$23bobo$23b3o9b2o5b2o$23bo11b2o5b2o!'''

    if args.use_gun_rle == '120X':
        args.gun_X = True
        args.use_gun_rle = '''x = 99, y = 58, rule = B3/S23
                              9bo$9b3o$12bo$11b2o5$2o5b2o$2o5b2o2$4b2o$4b2o$9b2o$8bo2bo$7b2o$8b2o3bo
                              13b2o$9bo4bo12b2o$12b3o$24b2o5b2o$24b2o5b2o25$82bo$81bobo4b2o$82bobo3b
                              obo$83bo6bo4b2o$86b4ob2o2bo2bo$86bo2bobobobob2o$76bo12bobobobo$75bobo
                              12b2obobo$75bobo16bo$76bo2$89b2o$89b2o!'''
        args.required.add("barge(l46,o0,d26)")
        args.required.add("beehive(l34,o0,d25)")
        args.required.add("blinker(l26,o0,d12)")
        args.required.add("blinker(l26,o1,d6)")
        args.required.add("block(l43,o0,d41)")
        args.ignored.add("glider(l6)(0B0)(ph0)")

    gun = lt.pattern(args.use_gun_rle.strip())
    gun_without_gliders = remove_gliders(gun)

    glider_appears = gun
    # now, let's fast forward the gun until we see an output glider.
    for i in range(0, args.toolkit.period):
        if remove_gliders(glider_appears) != glider_appears:
            # there's a glider
            break
        glider_appears = glider_appears[1]

    glider_appears = glider_appears[(args.n_gun_gliders - 1) * args.toolkit.period]

    # and make the gliders canonical
    rect = (-float("inf"), 0, 0, 0)
    for i in range(0, 4):
        found = False
        for c in pattern_components(glider_appears):
            if c.centre() == canonical_se.centre():
                rect = max(rect, tuple(c.getrect()))
                found = True
        if found:
            break
        glider_appears = glider_appears[1]
    else:
        raise Exception("Couldn't find gliders?")

    # shift the gun so that it lines up with the fake gun.
    fx, fy, _, _ = mk_fake_gun(1).getrect()
    x, y, _, _ = rect
    fake_gun = glider_appears(fx - x, fy - y)


if args.extract_recipes:

    def parse_object(obj):
        m = re.match(r"^'([\w ]+)((?:\([^)]*\))*)'$", obj.strip())
        if not m:
            raise SyntaxError(f"Invalid object: {obj}")
        name = m.group(1)
        info = m.group(2)
        return {"name": name, "info": info}

    gun_glider_names = set()
    for i in range(0, args.n_gun_gliders):
        gun_glider_names.add(f"g{i}")

    def parse_objects(objs):
        results = []
        gun_gliders = set()
        for o in re.split(r"('[^']*')", objs):
            o = o.strip()
            if not o or o == ",":
                continue
            obj = parse_object(o)
            if obj["name"] in gun_glider_names:
                gun_gliders.add(obj["name"])
            else:
                results.append(obj)

        for i in range(0, args.n_gun_gliders):
            if not f"g{i}" in gun_gliders:
                break

        consumed = args.n_gun_gliders - i
        if i != len(gun_gliders):
            results.append("!skipped glider")
        if i == 0:
            results.append("!consumed all")

        return {"consumed": consumed, "objects": results}

    with open(args.extract_recipes) as results_file:
        for line in results_file:
            m = re.match(
                r"""^\((\d+(?:, \d+)*,?)\)\s*\[('[^']*'(?:, '[^']*')*)?\]$""",
                line.strip(),
            )
            if not m:
                raise SyntaxError(f"Invalid line: {line}")
            stream = tuple(map(int, filter(bool, m.group(1).split(","))))
            info = parse_objects(m.group(2) or "")
            if info["consumed"] < 10 and len(info["objects"]) == 1:
                print(stream, info)

    sys.exit(0)

friendly_names = {
    "xq4_153": "glider",
    "xs4_33": "block",
    "xp2_7": "blinker",
    "xs6_696": "beehive",
    "xs7_2596": "loaf",
    "xs5_253": "boat",
    "xs6_356": "ship",
    "xs8_6996": "pond",
    "xs4_252": "tub",
    "xs7_25ac": "long boat",
    "xs12_g8o653z11": "ship tie",
    "xp2_7e": "toad",
    "xp2_318c": "beacon",
    "xs14_g88m952z121": "half bakery",
    "xs7_178c": "eater 1",
    "xs6_25a4": "barge",
    "xs6_bd": "snake",
    "xs11_g8o652z11": "boat tie ship",
    "xq4_6frc": "lwss",
    "xs8_178k8": "tub_with_tail",
}

class HashablePattern:
    def __init__(self, c: lifelib.Pattern, digest: int):
        self.c = c
        self.digest = digest

    def __hash__(self):
        return self.digest

    def __eq__(self, other):
        return self.digest == other.digest

@functools.lru_cache(maxsize=None)
def pattern_orientation(c):
    c = c.c
    orientations = [c.digest()]
    for o in ["rot270", "rot180", "rot90", "flip_x", "flip_y", "swap_xy", "swap_xy_flip"]:
        orientations.append(c(o).digest())
    orientations = sorted(set(orientations))
    return orientations.index(c.digest())

def component_info(c, alt=False):
    if c == c[1]:
        # still life
        code = c.apgcode
        name = friendly_names.get(code, code)
        x, y, w, h = c.getrect()
        lane = x - y
        depth = x + y + w + h
        orientation = pattern_orientation(HashablePattern(c, c.digest()))
        if args.gun_X:
            if depth < -50:
                return ""
        return f"{name}(l{lane},o{orientation},d{depth})"
    elif c == c[120]:
        # oscillator
        code = c.apgcode
        name = friendly_names.get(code, code)
        x, y, w, h = c.getrect()
        if (name == 'blinker'): # coordinates of the center ??
            x = x + w//2
            y = y + h//2
        lane = x - y
        depth = x + y + w + h
        if args.gun_X:
            if depth < -50:
                return ""
        orientation = pattern_orientation(HashablePattern(c, c.digest()))
        if (name == 'blinker') and alt:
            # skip blinkers when processing the alt.
            return None
        return f"{name}(l{lane},o{orientation},d{depth})"
    elif c.centre() == c[4].centre():
        # spaceship
        code = c.apgcode
        name = friendly_names.get(code, code)

        x, y, w, h = c.getrect()
        x4, y4, _, _ = c[4].getrect()

        if x4 - x == 0 or y4 - y == 0:
            # xWSS, need to process even if alt is true.
            return f"{name}({x},{y})({x4-x},{y4-y})"
        elif alt:
            # ignore gliders in alt -- we've already got them.
            return None

        # give the incoming gliders the names g0, g1, etc.
        for i, e in enumerate(expected_incoming_gliders):
            if c == e:
                return f"g{i}"

        # glider phase/color info
        match (x4 - x, y4 - y):
            case (1, 1):
                # same direction as gun
                canonical = canonical_se
                direction = "B"
            case (1, -1):
                # gun side 90 degree
                canonical = canonical_ne
                direction = "R"
            case (-1, 1):
                # recipe side 90 degree
                canonical = canonical_sw
                direction = "L"
            case (-1, -1):
                # same direction as recipe
                canonical = canonical_nw
                direction = "F"

        if c.centre() == canonical:
            phase = 0
            phase_mod2 = "0"
        elif c[1].centre() == canonical:
            phase = 1
            phase_mod2 = "1"
        elif c[2].centre() == canonical:
            phase = 2
            phase_mod2 = "0"
        elif c[3].centre() == canonical:
            phase = 3
            phase_mod2 = "1"

        lx, ly, lw, lh = c[phase].getrect()
        lane = lx - ly
        depth = lx + ly + lw + lh

        match (x4 - x, y4 - y):
            case (1, 1):
                location = f"l{lane}"
                color = "01"[lane % 2]
            case (1, -1):
                location = f"d{depth}"
                color = "01"[depth % 2]
            case (-1, 1):
                location = f"d{depth}"
                color = "01"[depth % 2]
            case (-1, -1):
                location = f"l{lane}"
                color = "01"[lane % 2]

        return f"{name}({location})({color}{direction}{phase_mod2})(ph{phase})"
    else:
        # not an object
        return None

args.min_big_pop_so_farA = float('inf') # soory for using args
args.min_big_pop_so_farB = float('inf') # soory for using args

def evaluate(s, patt):
    rect = patt.getrect()
    x, y, w, h = 0, 0, -1, -1
    if rect:
        x, y, w, h = rect
        if x < args.min_tree_x:
            args.tree_continue = False
            return
        if y < args.min_tree_y:
            args.tree_continue = False
            return
        if w > args.max_tree_width:
            args.tree_continue = False
            return
        if h > args.max_tree_height:
            args.tree_continue = False
            return
        if x+w > args.max_tree_x:
            args.tree_continue = False
            return
        if y+h > args.max_tree_y:
            args.tree_continue = False
            return
    if patt.population > args.max_population:
        if patt.population < args.min_big_pop_so_farA:
            args.min_big_pop_so_farA = patt.population
            #print(s, 'defining min_big_pop_so_far A' + f"#{args.min_big_pop_so_farA}" )
        else:
            #print(s, 'too big A' + f"#{patt.population} >= {args.min_big_pop_so_farA}" )
            return
    if (patt - expected_incoming_gliders_pattern).population > args.max_population:
        if (patt - expected_incoming_gliders_pattern).population < args.min_big_pop_so_farB:
            args.min_big_pop_so_farB = (patt - expected_incoming_gliders_pattern).population
            #print(s, 'defining min_big_pop_so_far B' + f"#{args.min_big_pop_so_farB}" )
        else:
            #print(s, 'too big B' + f"#{(patt - expected_incoming_gliders_pattern).population} >= {args.min_big_pop_so_farB}" )
            return
    if patt.population < args.min_population:
        print(s, 'too small A' + f"#{patt.population}")
        return
    if (patt - expected_incoming_gliders_pattern).population < args.min_population:
        print(s, 'too small B' + f"#{patt.population}")
        return

    infos = set()
    infos.add(f"#{patt.population}")
    infos.add(f"<{x},{y},{w},{h}>")
    if patt.population == 0:
        infos.add("empty")
    required_cnt = len(args.required)
    if args.required_prefix == "":
        if len(args.required_prefixes)>0:
            args.required_prefix = args.required_prefixes[0]
    required_prefix_matched = args.required_prefix == ""

    if args.only_gliders:
        if patt.population % 5 or patt.population >= 5 + 5 * args.n_gun_gliders:
            return
        diff = patt - reference
        if diff.population != 5:
            return
        if diff.centre() != diff[4].centre():
            return
        gli_info = component_info(diff)
        if gli_info.startswith(args.avoid_prefix):
            return
        if gli_info.startswith(args.required_prefix):
            required_prefix_matched = True
        if not gli_info.startswith("glider"):
            return
        for i in range(patt.population // 5 - 1):
            infos.add(f"g{i}")
        infos.add(gli_info)
    else:
        components = pattern_components(patt)
        has_unidentified = False
        for c in components:
            info = component_info(c, False)
            if info is not None:
                if info.startswith("glider") and args.do_not_report_gliders:
                    return
                for speboe in args.speboes:
                    if info.startswith(speboe):
                        info = 'speboe'+info
                        break
                if info!="":
                    if info.startswith(args.avoid_prefix):
                        return
                    if info.startswith(args.required_prefix):
                        required_prefix_matched = True
                    if info in args.required:
                        required_cnt -= 1
                    elif not info in args.ignored:
                        infos.add(info)
            else:
                has_unidentified = True
        if has_unidentified:
            components1 = pattern_components(patt[1])
            for c in components1:
                info = component_info(c, True)
                if info is not None:
                    if info.startswith(args.avoid_prefix):
                        return
                    if info.startswith(args.required_prefix):
                        required_prefix_matched = True
                    if not info in args.required and not info in args.ignored:
                        infos.add(info)
    #infos.add(f"!{required_cnt}")
    #infos.add(f"!{required_prefix_matched}")
    if not required_prefix_matched:
        for req_prefix in args.required_prefixes:
            for info in infos:
                if info.startswith(req_prefix):
                    required_prefix_matched = True
                    break
            if required_prefix_matched:
                break
        if not required_prefix_matched:
            return
    if required_cnt>0:
        return
    return (s, sorted(infos))

if args.print_rle:
    macros = {
        'sc90b5p120': {
            'construct_arm': 'mode sc, 0, 126, 102, 100, 195, 90, 91, 95, 98, 105, 90, 101, 141, 94, 159, 92, 146, 99, 90, 152, 139, 144, 92, 161, 131, 116, 101, 114, 111, 112, 93, 127, 98, 102, 114, 107, 157, 90, 90, 90, 91, 91, 243, 113, 139, 108, 95, 127, 121, 99, 257, 144, 94, 218, 148, 226, 111, 119, 100, 95, 91, 138, 201, 221, 216, 138, 125, mode p120, set 28 (mod 120), (90)'
        }
    }
    green_patt = lt.pattern("")
    red_patt = lt.pattern("")
    recipes = args.print_rle.split(";")
    for i, r in enumerate(recipes):
        if not r.strip():
            continue
        parse = parse_p120_recipe(r, macros.get(args.toolkit.name, {}))
        gliders_int = parse.delays
        #gtxt = ""
        #for g in gliders_int:
        #    gtxt += f",{g}"
        #print(f"#{gliders_int}")
        if parse.start_mode == "p120":
            green_patt += fake_gun(200 * i, 0) + single_channel_stream(gliders_int)(
                200 * i, 0
            )
            red_patt += write_text(" ".join(map(str, gliders_int)))(200 * i, 0)
        elif parse.start_mode == "sc":
            green_patt += PI_BLOCKS[1](200 * i, 0) + single_channel_stream(gliders_int)(200*i, 0)
            red_patt += write_text(" ".join(map(str, gliders_int)))(200 * i, 0)

    #follow = "" #find_minimum_follow(gtxt[1:]) -- does not work as expected
    result = evaluate(f"{gliders_int}", green_patt[args.simulate_gens])
    print(f"#{result}")
    print(write_life_history(green=green_patt, red=red_patt))

    sys.exit(0)

if args.check_for_shutoff:
    print("Checking if patterns shut off the stream.")

    recipes = args.check_for_shutoff.split(";")

    for i, r in enumerate(recipes):
        gliders_int = recipe_stream_to_delays(r)
        original = fake_gun + single_channel_stream(gliders_int)
        extended_fake_gun = mk_fake_gun(args.n_gun_gliders + 120)
        extended = extended_fake_gun + single_channel_stream(gliders_int)

        original = original[simulate_gens]

        extended_simulate_gens = simulate_gens + args.toolkit.period * 120 * 4
        extended = extended[extended_simulate_gens]
        # allow the first n+20 gliders to possibly escape, but the rest should be absorbed.
        expected_gun_positions = mk_fake_gun(args.n_gun_gliders + 20)[
            extended_simulate_gens
        ]

        if original == extended:
            print("Confirmed shutoff!")
            print(gliders_int)
        elif (original - expected_gun_positions) == (extended - expected_gun_positions):
            print("Possible shutoff with escaping gun gliders")
            print(gliders_int)

    sys.exit(0)

reference = fake_gun[simulate_gens]

if args.view_orientations:
    if args.view_orientations in friendly_names.values():
        args.view_orientations = next(k for k,v in friendly_names.items() if v == args.view_orientations)
    p = lt.pattern(args.view_orientations)
    results = {}
    orientations = [p]
    for o in ["rot270", "rot180", "rot90", "flip_x", "flip_y", "swap_xy", "swap_xy_flip"]:
        orientations.append(p(o))
    for oriented in orientations:
        x, y, w, h = oriented.getrect()
        oriented = oriented(-x + 1, -y + 1)
        data = ['.'] * (w+2) * (h*2)
        for (cx, cy) in oriented.coords():
            data[cy*(w+2) + cx] = '#'
        data = ''.join(data)
        lines = []
        for i in range(0, h+2):
            lines.append(data[i*(w+2):(i+1)*(w+2)])
        lines = '\n'.join(lines)
        results[pattern_orientation(HashablePattern(oriented, oriented.digest()))] = lines

    for orientation, view in sorted(results.items()):
        print()
        print(f'o{orientation}')
        print('==========================')
        print(view)

    sys.exit(0)

def process_concurrent(s):
    stream_to_check = s
    if args.swim_upstream_after:
        stream_to_check = parse_p120_recipe(','.join(map(str, s)) + ',(90), 2, 96, 96, 96, 96, 96, 96, 96, 96', {}).delays
    patt = fake_gun + single_channel_stream(stream_to_check)

    patt_n = patt[simulate_gens]
    result = evaluate(s, patt_n)
    return result

def nontrivial(evaldescription):
    for i in range(len(evaldescription)):
        if evaldescription[i]==f"g{i}":
            continue
        if evaldescription[i]=="glider(l0)(ph3)(WB1)":
            continue
        return True

seen = []
def recurse(s, depth=0):
    stream_to_check = s
    if args.swim_upstream_after:
        stream_to_check = parse_p120_recipe(','.join(map(str, s)) + ',(90), 2, 96, 96, 96, 96, 96, 96, 96, 96', {}).delays
    patt = fake_gun + single_channel_stream(stream_to_check)

    if not args.to_apgluxe:
        patt_n = patt[simulate_gens]
        if (patt_n - expected_incoming_gliders_pattern).population > args.max_tree_population:
             #print("big\n"+(patt_n - expected_incoming_gliders_pattern).rle_string())
             return
        key = (patt_n - expected_incoming_gliders_pattern).rle_string()
        if key in seen[depth+1]:
             #print("seen:"+key)
             return
        args.seen = False
        seen[depth+1].add(key)
        args.tree_continue = True
        e=evaluate(s, patt_n)
        if e:
            #if nontrivial(e[1]):
            print(e, flush=True)
    else:
        print(patt.rle_string()[len("#CLL state-numbering golly\n") :])

    if depth > 0:
        if args.tree_continue:
            seen[depth]=set()
            for n in range(args.toolkit.min_spacing, args.max_delay + 1):
                recurse(s + (n,), depth - 1)


if __name__ == "__main__":
    """
    p = lt.pattern('')
    for d1 in range(0, 8):
        p += (fake_gun + mk_glider(0, d1) + mk_glider(0, d1 + 90))(100*(d1), 0)

    print(p.rle_string())

    Results:
    0 - delete
    1 - block on gun side
    2 - backwards kickback
    3 - pond on gun side
    4 - delete
    5 - pond on recipe side
    6 - forwards kickback
    7 - block on recipe side
    """

    if args.concurrent:
        print("Generating jobs...", file=sys.stderr)
        if not args.subtree:
            args.subtree = [SubTreeDef("0-7")]
        len_all_options = 0
        for subtree in args.subtree:
            for depth in range(0, args.depth - len(subtree.options) + 1):
                deeper = (tuple(range(args.toolkit.min_spacing, args.max_delay + 1)),) * depth
                len_all_options += math.prod(map(len, subtree.options + deeper))

        print('Total:', len_all_options, file=sys.stderr)

        def all_options():
            for subtree in args.subtree:
                for depth in range(0, args.depth - len(subtree.options) + 1):
                    for starting_point in itertools.product(*subtree.options):
                        deeper = (tuple(range(args.toolkit.min_spacing, args.max_delay + 1)),) * depth
                        for d in itertools.product(*map(lambda a: (a,), starting_point), *deeper):
                            yield d

        multiprocessing.set_start_method('spawn')
        with Pool(processes=os.cpu_count() - 1) as pool:
            speedo = Speedometer()
            print("Starting...", file=sys.stderr)
            for result in pool.imap_unordered(process_concurrent, all_options(), chunksize=256):
                if speedo.tick(1):
                    current_per_s = speedo.get_current_speed_and_reset()
                    avg_per_s = speedo.overall_speed()
                    done = speedo.n_finished
                    percent = done/len_all_options*100
                    estimate_s = int((len_all_options - done)/avg_per_s)
                    estimate_min = estimate_s // 60
                    estimate_hr = estimate_min // 60
                    if estimate_hr > 0:
                        estimate = f"{estimate_hr}h {estimate_min % 60}m {estimate_s % 60}s"
                    elif estimate_min > 0:
                        estimate = f"{estimate_min % 60}m {estimate_s % 60}s"
                    else:
                        estimate = f"{estimate_s}s"
                    print(f"{current_per_s:.2f}/s, {avg_per_s:.2f}/s avg, {done}/{len_all_options} ({percent:0.2f}%), {estimate} left", file=sys.stderr)
                if result:
                    print(*result)
        pool.join()
    else:
        if not args.subtree:
            args.subtree = [SubtreeDef("0-7")]
        for d in range(0,args.depth+2):
            seen.append(set())
        for subtree in args.subtree:
            for starting_point in itertools.product(*subtree.options):
                args.seen = True
                seen[args.depth - len(starting_point)+1]=set()
                print(f"{starting_point}", end = " ", file=sys.stderr, flush=True)
                recurse(starting_point, depth=args.depth - len(starting_point))
                print(f"all_seen = {args.seen}", file=sys.stderr, flush=True)
        print("search done")
I have not find a glider yet, but I hope it is not a problem of the method.

----
I have decided to replace branch cutting by their depth reuciton ... (if the slow condition is "permanent" the subtree height would be halved what esentially means no time to process if the condition was "temporary" the search would gain significant portion of time ... and increasing entire search depth would allow us to eventually reach the recipe).

Here is current snarkbreaker set I got: We are almost chainable on 1HD shift.

Code: Select all

x = 1087, y = 541, rule = LifeHistory
596.D.3D119.D$596.D3.D119.D$596.D.3D119.D$596.D3.D119.D$596.D.3D119.D
15$361.2A118.2A118.2A118.2A$360.A.A117.A.A117.A.A117.A.A$354.2A4.A
113.2A4.A113.2A4.A113.2A4.A$352.A2.A2.2A.4A107.A2.A2.2A.4A107.A2.A2.
2A.4A107.A2.A2.2A.4A$352.2A.A.A.A.A2.A107.2A.A.A.A.A2.A107.2A.A.A.A.A
2.A107.2A.A.A.A.A2.A$355.A.A.A.A113.A.A.A.A113.A.A.A.A113.A.A.A.A$
355.A.A.2A114.A.A.2A114.A.A.2A114.A.A.2A$356.A119.A119.A119.A2$369.2A
118.2A118.2A118.2A$360.2A7.A110.2A7.A110.2A7.A110.2A7.A$331.3D26.2A5.
A.A110.2A5.A.A110.2A5.A.A110.2A5.A.A$331.D.D33.2A118.2A118.2A118.2A$
331.D.D$331.D.D$331.3D$351.3A117.3A117.3A117.3A$353.A119.A119.A119.A$
352.A10.3A106.A119.A119.A$357.2A4.A113.2A118.2A118.2A$358.A5.A113.A
119.A119.A$355.3A117.3A117.3A117.3A$355.A119.A119.A119.A2$490.2A118.
2A6.D2.3D.3D6.D2.3D.3D6.D3.D2.3D6.D2.3D.3D54.2A6.D3.D2.3D6.D2.3D.3D5.
3D.3D6.D2.3D.3D$489.2A118.2A6.2D2.D.D.D7.2D2.D.D3.D5.2D2.2D2.D.D5.2D
4.D.D.D53.2A6.2D2.2D2.D.D5.2D4.D3.D5.D.D.D.D5.2D2.D.D3.D$491.A119.A6.
D2.D.D.3D6.D2.D.D.3D6.D3.D2.3D6.D2.3D.3D55.A6.D3.D2.3D6.D2.3D3.D5.3D.
D.D6.D2.3D3.D$618.D2.D.D.D.D6.D2.D.D3.D6.D3.D4.D6.D4.D.D.D62.D3.D4.D
6.D2.D4.D8.D.D.D6.D4.D2.D$617.3D.3D.3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.3D
61.3D.3D.3D5.3D.3D2.D6.3D.3D5.3D.3D2.D$398.3D.3D6.D2.3D.3D98.D2.D.D2.
D6.3D.3D$398.D.D.D.D5.2D2.D.D3.D97.2D2.D.D.2D6.D.D3.D$398.3D.D.D6.D2.
D.D3.D98.D2.3D2.D6.3D3.D$400.D.D.D6.D2.D.D2.D99.D4.D2.D8.D2.D$398.3D.
3D5.3D.3D2.D98.3D3.D.3D5.3D2.D7$386.2A$386.A.A$386.A8$637.A$636.2A$
636.A.A2$759.3A$759.A$760.A3$525.2A$525.A.A$525.A$3D$D.D$D.D$D.D$3D$
413.2A$412.2A$2E412.A$2E6$662.2A$662.A.A$662.A3$9.2A$8.2A$10.A539.A$
549.2A$549.A.A240.A$791.2A$791.A.A3$361.2A$360.A.A$354.2A4.A$352.A2.A
2.2A.4A$352.2A.A.A.A.A2.A$355.A.A.A.A$355.A.A.2A$356.A2$369.2A$360.2A
7.A$333.D26.2A5.A.A$333.D33.2A$333.D$32.2A299.D116.2D$32.A.A298.D115.
2D$32.A418.D120.2D118.2A$571.2D118.2A$573.D119.A120.2A$357.2A454.2A$
358.A7.2A447.A$355.3A8.A.A$355.A10.A7$399.D3.D2.3D6.D2.3D.3D$398.2D2.
2D2.D.D5.2D4.D.D$399.D3.D2.D.D6.D2.3D.3D$399.D3.D2.D.D6.D4.D3.D$398.
3D.3D.3D5.3D.3D.2D$334.3A$336.A$335.A$55.2A$54.2A$56.A9$393.3A$393.A$
394.A332.A$726.2A$726.A.A14$85.A$84.2A777.2A$84.A.A776.A.A$863.A2$
720.D235.D.3D$720.D25.D6.D202.D.D$720.D24.D3.2D3.D201.D.3D$720.D24.D
2.2D4.D201.D.D.D$720.D24.D4.D3.D201.D.3D$746.D6.D5$428.A$427.2A$427.A
.A7$107.3A251.2A118.2A238.2A238.2A$107.A252.A.A117.A.A237.A.A237.A.A$
108.A245.2A4.A113.2A4.A233.2A4.A233.2A4.A$352.A2.A2.2A.4A107.A2.A2.2A
.4A227.A2.A2.2A.4A227.A2.A2.2A.4A$352.2A.A.A.A.A2.A107.2A.A.A.A.A2.A
227.2A.A.A.A.A2.A227.2A.A.A.A.A2.A$355.A.A.A.A113.A.A.A.A233.A.A.A.A
233.A.A.A.A$355.A.A.2A114.A.A.2A234.A.A.2A234.A.A.2A$356.A119.A239.A
239.A2$369.2A118.2A238.2A238.2A$360.2A7.A110.2A7.A230.2A7.A230.2A7.A$
333.D26.2A5.A.A110.2A5.A.A230.2A5.A.A230.2A5.A.A$333.D33.2A118.2A238.
2A238.2A$333.D$333.D$333.D$350.3A129.2A478.2A$352.A128.2A478.2A$351.A
131.A479.A$357.2A97.D20.2A238.2A238.2A$358.A96.2D21.A239.A239.A$355.
3A8.3A86.D.D17.3A237.3A8.3A226.3A$355.A10.A108.A239.A10.A228.A$367.A
359.A2$461.3A477.3A$463.A479.A$462.A479.A2$399.D2.3D.3D110.D2.3D.D.D
6.D2.3D.3D5.3D.3D202.D2.3D.3D5.3D.3D.3D214.D2.3D.D.D6.D2.3D2.D6.3D2.D
2.D.D$398.2D2.D.D.D111.2D2.D3.D.D5.2D4.D.D.D5.D.D.D.D201.2D4.D.D9.D.D
.D.D.D213.2D2.D3.D.D5.2D4.D.2D8.D.2D2.D.D$399.D2.D.D.3D110.D2.3D.3D6.
D2.3D.3D5.3D.D.D202.D2.3D.3D5.3D.D.D.D.D214.D2.3D.3D6.D2.3D2.D6.3D2.D
2.3D$399.D2.D.D3.D110.D2.D.D3.D6.D2.D3.D.D7.D.D.D137.2A63.D2.D3.D.D7.
D.D.D.D.D214.D4.D3.D6.D2.D4.D6.D4.D4.D$398.3D.3D.2D110.3D.3D3.D5.3D.
3D.3D5.3D.3D136.A.A62.3D.3D.3D5.3D.3D.3D213.3D.2D4.D5.3D.3D.3D5.3D.3D
3.D$695.A3$146.A$145.2A$145.A.A8$393.2A$392.2A$394.A3$758.2A$758.A.A$
758.A242.A$1000.2A$1000.A.A$523.2A$522.2A$524.A$168.2A$167.2A$169.A7$
416.D$415.2D$415.D.D302.D117.3D$720.D117.D.D$720.D117.3D$720.D119.D$
720.D117.3D10$192.3A835.3A$192.A837.A$193.A837.A2$555.2A$361.2A118.2A
71.2A165.2A118.2A$360.A.A117.A.A73.A163.A.A117.A.A$354.2A4.A113.2A4.A
233.2A4.A113.2A4.A$352.A2.A2.2A.4A107.A2.A2.2A.4A227.A2.A2.2A.4A107.A
2.A2.2A.4A$352.2A.A.A.A.A2.A107.2A.A.A.A.A2.A227.2A.A.A.A.A2.A107.2A.
A.A.A.A2.A$355.A.A.A.A113.A.A.A.A233.A.A.A.A113.A.A.A.A$355.A.A.2A
114.A.A.2A234.A.A.2A114.A.A.2A$356.A119.A239.A119.A2$369.2A118.2A238.
2A118.2A$360.2A7.A110.2A7.A230.2A7.A110.2A7.A$329.D.3D26.2A5.A.A110.
2A5.A.A230.2A5.A.A110.2A5.A.A$329.D3.D33.2A118.2A238.2A118.2A$329.D.
3D$329.D3.D$329.D.3D4$357.2A118.2A238.2A6.2A110.2A6.2A$358.A119.A239.
A5.2A112.A5.2A$355.3A10.A106.3A10.A89.A136.3A8.A108.3A8.A$355.A11.2A
106.A11.2A88.2A136.A119.A$367.A.A117.A.A87.A.A6$399.D2.3D2.D6.3D.3D
98.D2.3D.3D5.3D.3D218.D2.3D.3D5.3D.3D97.3D.3D.3D6.D2.3D2.D6.3D2.D$
350.3A45.2D2.D.D.2D6.D.D.D.D49.3A45.2D2.D.D.D.D5.D.D.D.D169.3A45.2D4.
D3.D5.D.D.D51.3A47.D.D.D3.D5.2D4.D.2D6.D.D.2D$352.A46.D2.D.D2.D6.3D.D
.D51.A46.D2.D.D.D.D5.3D.D.D171.A46.D4.D.3D5.3D.3D51.A45.3D.D.D.3D6.D
2.3D2.D6.3D2.D$351.A47.D2.D.D2.D8.D.D.D50.A47.D2.D.D.D.D7.D.D.D170.A
47.D3.D2.D9.D3.D50.A46.D3.D.D.D8.D2.D4.D8.D2.D$232.A165.3D.3D.3D5.3D.
3D97.3D.3D.3D5.3D.3D217.3D2.D2.3D5.3D.2D98.3D.3D.3D5.3D.3D.3D5.3D.3D$
231.2A$231.A.A2$833.2A$833.A.A$833.A5$600.2D$599.2D$513.A87.D$392.3A
117.2A$392.A119.A.A569.2A$393.A690.A.A$1084.A12$768.2A$262.A504.2A$
261.2A506.A$261.A.A4$415.2A118.2A359.A$415.A.A116.2A359.2A$415.A120.A
358.A.A15$285.A505.3A$284.2A505.A$284.A.A505.A2$361.2A118.2A$360.A.A
117.A.A75.D$354.2A4.A76.3D34.2A4.A76.2D$352.A2.A2.2A.4A72.D34.A2.A2.
2A.4A72.D.D$352.2A.A.A.A.A2.A73.D33.2A.A.A.A.A2.A$355.A.A.A.A113.A.A.
A.A$355.A.A.2A114.A.A.2A$356.A119.A2$369.2A118.2A$344.2A14.2A7.A110.
2A7.A435.3A$329.D.3D9.A.A14.2A5.A.A110.2A5.A.A435.A$329.D3.D11.A21.2A
118.2A437.A$329.D.3D$329.D3.D$329.D.3D125.3A$461.A$460.A2$357.2A118.
2A$358.A119.A$355.3A9.2A106.3A$355.A10.2A107.A$312.A55.A$311.2A$311.A
.A4$399.D2.3D.3D5.3D.3D6.D2.3D.3D81.3D.D.D6.D2.3D.3D6.D2.3D.3D$398.2D
4.D.D.D5.D.D.D.D5.2D4.D3.D81.D.D.D.D5.2D2.D.D.D.D5.2D2.D3.D$399.D2.3D
.3D5.3D.3D6.D2.3D3.D81.3D.3D6.D2.D.D.3D6.D2.3D.3D$399.D2.D5.D7.D3.D6.
D2.D4.D84.D3.D6.D2.D.D.D.D6.D2.D.D3.D392.A$398.3D.3D.3D5.3D.3D5.3D.3D
2.D82.3D3.D5.3D.3D.3D5.3D.3D.2D392.2A$948.A.A4$503.3A$503.A$504.A6$
334.2A$333.2A$335.A5$399.2A$399.A.A566.D6.D$399.A567.D3.2D3.D$967.D2.
2D4.D$967.D4.D3.D$968.D6.D3$527.2A$527.A.A$527.A8$360.A$359.2A$359.A.
A5$424.2A$423.2A$425.A8$554.2A$554.A.A$554.A20$455.3A$455.A$401.A54.A
$400.2A$400.A.A15$596.A$595.2A$595.A.A$478.2D$478.D.D$478.D$423.2A$
422.2A$424.A15$618.2D$617.2D$619.D5$447.2A$446.2A$448.A!
Last edited by Hippo.69 on September 17th, 2026, 2:38 am, edited 1 time in total.
russellsprouts
Posts: 69
Joined: January 28th, 2026, 10:04 am

Re: Optimized Snarkmaker

Post by russellsprouts »

Hippo.69 wrote: September 16th, 2026, 3:08 am OK, I have to show my current search method:
I think snark.py might be a better tool for finding the reverse glider, but it does require some configuration to work.

The big issue is that snark.py doesn't handle unstable results, so finding a backward glider is hard. To fix that, I set up this custom starting point where we want to destroy the second snark and send a backward glider to the first snark.

Code: Select all

x = 74, y = 97, rule = B3/S23
9$53b2o$52bobo$46b2o4bo$44bo2bo2b2ob4o$44b2obobobobo2bo$47bobobobo$47b
obob2o$48bo2$61b2o$52b2o7bo$52b2o5bobo$59b2o2$51b3o$51bo$52bo3$49b2o$
50bo$47b3o$47bo5$18bo$18b3o$21bo$20b2o3$12b2o$12bo$9b2obo$9bo2b3o4b2o$
10b2o3bo3b2o$12b4o$12bo15b2o$13b3o12bobo$16bo13bo$11b5o14b2o$11bo$13bo
$12b2o34$60b2o$60bobo$60bo!
That was the earliest that the recipe glider can intercept the incoming glider and do something besides just destroying it. There are about 107 options for delays before the glider is just hitting the stable result of the incoming glider hitting the snark:

Code: Select all

x = 91, y = 94, rule = B3/S23
7$51b2o$50bobo$44b2o4bo$42bo2bo2b2ob4o$42b2obobobobo2bo$45bobobobo$45b
obob2o$46bo2$59b2o$50b2o7bo$50b2o5bobo$57b2o7$47b2o$48bo$45b3o$45bo5$
16bo$16b3o$19bo$18b2o3$10b2o$10bo$7b2obo$7bo2b3o4b2o$8b2o3bo3b2o$10b4o
$10bo15b2o$11b3o12bobo48bo$14bo13bo47b2o$9b5o14b2o46bobo$9bo$11bo$10b
2o34$58b2o$58bobo$58bo!
So, I set up a sqlite database for snark.py to search all of those options:

Code: Select all

for i in {0..106}; do
    uv run snark.py custom-starting-point -t '44b2o$43bobo$37b2o4bo$35bo2bo2b2ob4o$35b2obobobobo2bo$38bobobobo$38bob
ob2o$39bo2$52b2o$43b2o7bo$43b2o5bobo$50b2o2$42b3o$42bo$43bo3$40b2o$41b
o$38b3o$38bo5$9bo$9b3o$12bo$11b2o3$3b2o$3bo$2obo$o2b3o4b2o$b2o3bo3b2o$
3b4o$3bo15b2o$4b3o12bobo$7bo13bo$2b5o14b2o$2bo$4bo$3b2o34$51b2o$51bobo
$51bo!' -s "${i}" -o results/snarkbreaker-starting-points.sqlite
done
Then I set up a custom intermediate database with just the first snark. This means that the database will track whether the first snark is intact by setting full_intermediate 0 instead of null.

Code: Select all

uv run snark.py custom-intermediates -r results/snarkbreaker-intermediates.sqlite -i 'x = 27, y = 30, rule = B3/S23
5$13b2o$12bobo$6b2o4bo$4bo2bo2b2ob4o$4b2obobobobo2bo$7bobobobo$7bobob
2o$8bo2$21b2o$12b2o7bo$12b2o5bobo$19b2o2$11b3o$11bo$12bo3$9b2o$10bo$7b
3o$7bo!'
Finally, we can run the optimize command to find results. We set the must-contain to filter the results. Each result must either keep the first snark intact (indicating that the second snark has been reduced to a stable ash that we can use for further searching), or it must destroy the first snark with the correct glider, which leaves behind a boat, tub, blinker, and eater. Snark.py can do some pruning of the search automatically -- it will prune identical subtrees resulting from gliders hitting already stable ash.

Code: Select all

uv run snark.py optimize -r results/snarkbreaker-intermediates.sqlite -o results/snarkbreaker-starting-points.sqlite -n 1024 --max-allowed-population=100 --must-contain='11$9b2o17b2o$8bobo17bo$9bo16bobo$26b2o2$12bo$11bobo$12bo5$13b3o!
|11$9b2o17b2o$8bobo17bo$9bo16bobo$26b2o2$12bo$11bobo$12bo4$14bo$14bo$
14bo!|3$14b2o$13bobo$7b2o4bo$5bo2bo2b2ob4o$5b2obobobobo2bo$8bobobobo$8bobob
2o$9bo2$22b2o$13b2o7bo$13b2o5bobo$20b2o7$10b2o$11bo$8b3o$8bo!' --n-processes=32 --depth-range=0
This will save results in a database. I ran it for a while, and got this progress:

Code: Select all

5129.32/s, 3693.14 avg/s, 874,174/25,041,112 done, 343-508 gens, 52,622/2,008,410,594 pending, 0x872216, -infx872798, 0x25 A (722), -9 (196) fd, 0 overlap (3491), 29 pop (2)
So it processed 25 million recipes with length up to 508 gens, and found 874,174 results which were stable, met the must-contain filter, and had a population <= 100. Of those, 872,216 kept the first snark intact -- they didn't send any gliders. The remaining 1,958 results did send a backward glider, but none of them were clean. The best result has a population of 29 (vs 19 for a clean result).

That looks like this:

Code: Select all

x = 228, y = 164, rule = B3/S23
19$70b2o$69bobo$63b2o4bo$61bo2bo2b2ob4o$61b2obobobobo2bo$64bobobobo$
64bobob2o$65bo2$78b2o$69b2o7bo$69b2o5bobo$76b2o7$66b2o$67bo$64b3o$64bo
3$79b3o$79bo$35bo44bo$35b3o$38bo$37b2o3$29b2o$29bo$26b2obo$26bo2b3o4b
2o$27b2o3bo3b2o$29b4o$29bo15b2o$30b3o12bobo$33bo13bo$28b5o14b2o$28bo$
30bo$29b2o5$106bo$105b2o$105bobo25$133bo$132b2o$77b2o53bobo$77bobo$77b
o20$156b2o$155b2o$157bo!
Unfortunately the 1958 results which sent a backwards glider are dead ends -- we can't really improve from there. But, we can setup the next searches to only the best of the previous stable results, which means we've cut out 96% of the search tree that has very large ash or escaping gliders. I also realized that I was missing the --depth-range=0 option the first time. When I added that, it increased the performance to ~12k recipes/s on my machine.

Code: Select all

uv run snark.py setup-next-search -i results/snarkbreaker-starting-points.sqlite -o results/snarkbreaker-round2.sqlite -q 'full_intermediate is not null and population < 75'

----

Quick edit: I ran the next step of the search, and I quickly found this result:

Code: Select all

x = 197, y = 173, rule = B3/S23
17$48b2o$47bobo$41b2o4bo$39bo2bo2b2ob4o$39b2obobobobo2bo$42bobobobo$
42bobob2o$43bo2$56b2o$47b2o7bo$47b2o5bobo$54b2o7$44b2o$45bo$42b3o$42bo
21$77bo$10b3o63b2o$12bo63bobo$11bo20$99b2o$98b2o$100bo22$124bo$123b2o$
123bobo26$150b3o$150bo$151bo21$175bo$174b2o$174bobo!
We could use the same technique to try to find a faster recipe for the SPEBOE -> reverse glider as well. I may run a search for that next
User avatar
Hippo.69
Posts: 696
Joined: July 14th, 2020, 7:35 pm
Contact:

Re: Optimized Snarkmaker

Post by Hippo.69 »

russellsprouts wrote: September 16th, 2026, 6:43 pm Quick edit: I ran the next step of the search, and I quickly found this result:

Code: Select all

x = 197, y = 173, rule = B3/S23
17$48b2o$47bobo$41b2o4bo$39bo2bo2b2ob4o$39b2obobobobo2bo$42bobobobo$
42bobob2o$43bo2$56b2o$47b2o7bo$47b2o5bobo$54b2o7$44b2o$45bo$42b3o$42bo
21$77bo$10b3o63b2o$12bo63bobo$11bo20$99b2o$98b2o$100bo22$124bo$123b2o$
123bobo26$150b3o$150bo$151bo21$175bo$174b2o$174bobo!
We could use the same technique to try to find a faster recipe for the SPEBOE -> reverse glider as well. I may run a search for that next
Wow, perfect, it's the last required piece ... I can throw away all the HD1 HD13 stuff :). And the first row of results is sufficient.

Code: Select all

x = 1087, y = 541, rule = LifeHistory
596.D.3D119.D117.3D$596.D3.D119.D117.D.D$596.D.3D119.D117.D.D$596.D3.
D119.D117.D.D$596.D.3D119.D117.3D15$361.2A118.2A118.2A118.2A118.2A$
360.A.A117.A.A117.A.A117.A.A117.A.A$354.2A4.A113.2A4.A113.2A4.A113.2A
4.A113.2A4.A$352.A2.A2.2A.4A107.A2.A2.2A.4A107.A2.A2.2A.4A107.A2.A2.
2A.4A107.A2.A2.2A.4A$352.2A.A.A.A.A2.A107.2A.A.A.A.A2.A107.2A.A.A.A.A
2.A107.2A.A.A.A.A2.A107.2A.A.A.A.A2.A$355.A.A.A.A113.A.A.A.A113.A.A.A
.A113.A.A.A.A113.A.A.A.A$355.A.A.2A114.A.A.2A114.A.A.2A114.A.A.2A114.
A.A.2A$356.A119.A119.A119.A119.A2$369.2A118.2A118.2A118.2A118.2A$360.
2A7.A110.2A7.A110.2A7.A110.2A7.A110.2A7.A$331.3D26.2A5.A.A110.2A5.A.A
110.2A5.A.A110.2A5.A.A110.2A5.A.A$331.D.D33.2A118.2A118.2A118.2A118.
2A$331.D.D$331.D.D$331.3D508.A$351.3A117.3A117.3A117.3A117.3A7.2A$
353.A119.A119.A119.A119.A7.A.A$352.A10.3A106.A119.A119.A119.A$357.2A
4.A113.2A118.2A118.2A118.2A$358.A5.A113.A119.A119.A119.A$355.3A117.3A
117.3A117.3A117.3A$355.A119.A119.A119.A119.A2$490.2A118.2A6.D2.3D.3D
6.D2.3D.3D6.D3.D2.3D6.D2.3D.3D54.2A6.D3.D2.3D6.D2.3D.3D5.3D.3D6.D2.3D
.3D$489.2A118.2A6.2D2.D.D.D7.2D2.D.D3.D5.2D2.2D2.D.D5.2D4.D.D.D53.2A
6.2D2.2D2.D.D5.2D4.D3.D5.D.D.D.D5.2D2.D.D3.D$491.A119.A6.D2.D.D.3D6.D
2.D.D.3D6.D3.D2.3D6.D2.3D.3D55.A6.D3.D2.3D6.D2.3D3.D5.3D.D.D6.D2.3D3.
D$618.D2.D.D.D.D6.D2.D.D3.D6.D3.D4.D6.D4.D.D.D62.D3.D4.D6.D2.D4.D8.D.
D.D6.D4.D2.D$617.3D.3D.3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.3D61.3D.3D.3D5.
3D.3D2.D6.3D.3D5.3D.3D2.D$398.3D.3D6.D2.3D.3D98.D2.D.D2.D6.3D.3D337.
3D.3D5.3D.3D6.D2.3D.3D5.3D.3D$398.D.D.D.D5.2D2.D.D3.D97.2D2.D.D.2D6.D
.D3.D337.D.D.D.D5.D.D.D.D5.2D2.D.D.D.D5.D.D.D$398.3D.D.D6.D2.D.D3.D
98.D2.3D2.D6.3D3.D337.3D.D.D5.3D.3D6.D2.D.D.3D5.3D.3D$400.D.D.D6.D2.D
.D2.D99.D4.D2.D8.D2.D340.D.D.D7.D.D.D6.D2.D.D3.D7.D3.D$398.3D.3D5.3D.
3D2.D98.3D3.D.3D5.3D2.D338.3D.3D5.3D.3D5.3D.3D.3D5.3D.2D5$864.2A$863.
2A$386.2A477.A$386.A.A$386.A8$637.A$636.2A$636.A.A2$759.3A$759.A$760.
A3$525.2A$525.A.A$525.A$3D886.A$D.D885.2A$D.D885.A.A$D.D$3D$413.2A$
412.2A$2E412.A$2E6$662.2A$662.A.A$662.A3$9.2A$8.2A$10.A539.A$549.2A$
549.A.A240.A$791.2A$791.A.A3$361.2A552.3A$360.A.A552.A$354.2A4.A555.A
$352.A2.A2.2A.4A$352.2A.A.A.A.A2.A$355.A.A.A.A$355.A.A.2A$356.A2$369.
2A$360.2A7.A$333.D26.2A5.A.A$333.D33.2A$333.D$32.2A299.D116.2D$32.A.A
298.D115.2D$32.A418.D120.2D118.2A$571.2D118.2A$573.D119.A120.2A$357.
2A454.2A$358.A7.2A447.A$355.3A8.A.A$355.A10.A$940.A$939.2A$939.A.A4$
399.D3.D2.3D6.D2.3D.3D$398.2D2.2D2.D.D5.2D4.D.D$399.D3.D2.D.D6.D2.3D.
3D$399.D3.D2.D.D6.D4.D3.D$398.3D.3D.3D5.3D.3D.2D$334.3A$336.A$335.A$
55.2A$54.2A$56.A9$393.3A$393.A$394.A332.A$726.2A$726.A.A14$85.A$84.2A
777.2A$84.A.A776.A.A$863.A2$720.D235.D.3D$720.D25.D6.D202.D.D$720.D
24.D3.2D3.D201.D.3D$720.D24.D2.2D4.D201.D.D.D$720.D24.D4.D3.D201.D.3D
$746.D6.D5$428.A$427.2A$427.A.A7$107.3A251.2A118.2A238.2A238.2A$107.A
252.A.A117.A.A237.A.A237.A.A$108.A245.2A4.A113.2A4.A233.2A4.A233.2A4.
A$352.A2.A2.2A.4A107.A2.A2.2A.4A227.A2.A2.2A.4A227.A2.A2.2A.4A$352.2A
.A.A.A.A2.A107.2A.A.A.A.A2.A227.2A.A.A.A.A2.A227.2A.A.A.A.A2.A$355.A.
A.A.A113.A.A.A.A233.A.A.A.A233.A.A.A.A$355.A.A.2A114.A.A.2A234.A.A.2A
234.A.A.2A$356.A119.A239.A239.A2$369.2A118.2A238.2A238.2A$360.2A7.A
110.2A7.A230.2A7.A230.2A7.A$333.D26.2A5.A.A110.2A5.A.A230.2A5.A.A230.
2A5.A.A$333.D33.2A118.2A238.2A238.2A$333.D$333.D$333.D$350.3A129.2A
478.2A$352.A128.2A478.2A$351.A131.A479.A$357.2A97.D20.2A238.2A238.2A$
358.A96.2D21.A239.A239.A$355.3A8.3A86.D.D17.3A237.3A8.3A226.3A$355.A
10.A108.A239.A10.A228.A$367.A359.A2$461.3A477.3A$463.A479.A$462.A479.
A2$399.D2.3D.3D110.D2.3D.D.D6.D2.3D.3D5.3D.3D202.D2.3D.3D5.3D.3D.3D
214.D2.3D.D.D6.D2.3D2.D6.3D2.D2.D.D$398.2D2.D.D.D111.2D2.D3.D.D5.2D4.
D.D.D5.D.D.D.D201.2D4.D.D9.D.D.D.D.D213.2D2.D3.D.D5.2D4.D.2D8.D.2D2.D
.D$399.D2.D.D.3D110.D2.3D.3D6.D2.3D.3D5.3D.D.D202.D2.3D.3D5.3D.D.D.D.
D214.D2.3D.3D6.D2.3D2.D6.3D2.D2.3D$399.D2.D.D3.D110.D2.D.D3.D6.D2.D3.
D.D7.D.D.D137.2A63.D2.D3.D.D7.D.D.D.D.D214.D4.D3.D6.D2.D4.D6.D4.D4.D$
398.3D.3D.2D110.3D.3D3.D5.3D.3D.3D5.3D.3D136.A.A62.3D.3D.3D5.3D.3D.3D
213.3D.2D4.D5.3D.3D.3D5.3D.3D3.D$695.A3$146.A$145.2A$145.A.A8$393.2A$
392.2A$394.A3$758.2A$758.A.A$758.A242.A$1000.2A$1000.A.A$523.2A$522.
2A$524.A$168.2A$167.2A$169.A7$416.D$415.2D$415.D.D302.D117.3D$720.D
117.D.D$720.D117.3D$720.D119.D$720.D117.3D10$192.3A835.3A$192.A837.A$
193.A837.A2$555.2A$361.2A118.2A71.2A165.2A118.2A$360.A.A117.A.A73.A
163.A.A117.A.A$354.2A4.A113.2A4.A233.2A4.A113.2A4.A$352.A2.A2.2A.4A
107.A2.A2.2A.4A227.A2.A2.2A.4A107.A2.A2.2A.4A$352.2A.A.A.A.A2.A107.2A
.A.A.A.A2.A227.2A.A.A.A.A2.A107.2A.A.A.A.A2.A$355.A.A.A.A113.A.A.A.A
233.A.A.A.A113.A.A.A.A$355.A.A.2A114.A.A.2A234.A.A.2A114.A.A.2A$356.A
119.A239.A119.A2$369.2A118.2A238.2A118.2A$360.2A7.A110.2A7.A230.2A7.A
110.2A7.A$329.D.3D26.2A5.A.A110.2A5.A.A230.2A5.A.A110.2A5.A.A$329.D3.
D33.2A118.2A238.2A118.2A$329.D.3D$329.D3.D$329.D.3D4$357.2A118.2A238.
2A6.2A110.2A6.2A$358.A119.A239.A5.2A112.A5.2A$355.3A10.A106.3A10.A89.
A136.3A8.A108.3A8.A$355.A11.2A106.A11.2A88.2A136.A119.A$367.A.A117.A.
A87.A.A6$399.D2.3D2.D6.3D.3D98.D2.3D.3D5.3D.3D218.D2.3D.3D5.3D.3D97.
3D.3D.3D6.D2.3D2.D6.3D2.D$350.3A45.2D2.D.D.2D6.D.D.D.D49.3A45.2D2.D.D
.D.D5.D.D.D.D169.3A45.2D4.D3.D5.D.D.D51.3A47.D.D.D3.D5.2D4.D.2D6.D.D.
2D$352.A46.D2.D.D2.D6.3D.D.D51.A46.D2.D.D.D.D5.3D.D.D171.A46.D4.D.3D
5.3D.3D51.A45.3D.D.D.3D6.D2.3D2.D6.3D2.D$351.A47.D2.D.D2.D8.D.D.D50.A
47.D2.D.D.D.D7.D.D.D170.A47.D3.D2.D9.D3.D50.A46.D3.D.D.D8.D2.D4.D8.D
2.D$232.A165.3D.3D.3D5.3D.3D97.3D.3D.3D5.3D.3D217.3D2.D2.3D5.3D.2D98.
3D.3D.3D5.3D.3D.3D5.3D.3D$231.2A$231.A.A2$833.2A$833.A.A$833.A5$600.
2D$599.2D$513.A87.D$392.3A117.2A$392.A119.A.A569.2A$393.A690.A.A$
1084.A12$768.2A$262.A504.2A$261.2A506.A$261.A.A4$415.2A118.2A359.A$
415.A.A116.2A359.2A$415.A120.A358.A.A15$285.A505.3A$284.2A505.A$284.A
.A505.A2$361.2A118.2A$360.A.A117.A.A75.D$354.2A4.A76.3D34.2A4.A76.2D$
352.A2.A2.2A.4A72.D34.A2.A2.2A.4A72.D.D$352.2A.A.A.A.A2.A73.D33.2A.A.
A.A.A2.A$355.A.A.A.A113.A.A.A.A$355.A.A.2A114.A.A.2A$356.A119.A2$369.
2A118.2A$344.2A14.2A7.A110.2A7.A435.3A$329.D.3D9.A.A14.2A5.A.A110.2A
5.A.A435.A$329.D3.D11.A21.2A118.2A437.A$329.D.3D$329.D3.D$329.D.3D
125.3A$461.A$460.A2$357.2A118.2A$358.A119.A$355.3A9.2A106.3A$355.A10.
2A107.A$312.A55.A$311.2A$311.A.A4$399.D2.3D.3D5.3D.3D6.D2.3D.3D81.3D.
D.D6.D2.3D.3D6.D2.3D.3D$398.2D4.D.D.D5.D.D.D.D5.2D4.D3.D81.D.D.D.D5.
2D2.D.D.D.D5.2D2.D3.D$399.D2.3D.3D5.3D.3D6.D2.3D3.D81.3D.3D6.D2.D.D.
3D6.D2.3D.3D$399.D2.D5.D7.D3.D6.D2.D4.D84.D3.D6.D2.D.D.D.D6.D2.D.D3.D
392.A$398.3D.3D.3D5.3D.3D5.3D.3D2.D82.3D3.D5.3D.3D.3D5.3D.3D.2D392.2A
$948.A.A4$503.3A$503.A$504.A6$334.2A$333.2A$335.A5$399.2A$399.A.A566.
D6.D$399.A567.D3.2D3.D$967.D2.2D4.D$967.D4.D3.D$968.D6.D3$527.2A$527.
A.A$527.A8$360.A$359.2A$359.A.A5$424.2A$423.2A$425.A8$554.2A$554.A.A$
554.A20$455.3A$455.A$401.A54.A$400.2A$400.A.A15$596.A$595.2A$595.A.A$
478.2D$478.D.D$478.D$423.2A$422.2A$424.A15$618.2D$617.2D$619.D5$447.
2A$446.2A$448.A!
BTW: ... the version with tree cutting replaced by depth reduction works nicely (and is significantly slower as a lot of early cuted trees are considered interesting now) ... it rediscovered all the recipes (in at most 1 deeper search then the full search would done) ... I am not searching on depth 6 for the hd0 yet ... so ruslesprouts final discovery was not in the range.
(And actually I have finished search to the depth 6 with subtree cutting so I would need the search to depth at least 7 to find it ... it would proabably be out of my patience range.)

I have added interruptability to preserve information what was already searched.

Code: Select all

import sqlite3
import sys
import argparse
import re
import itertools
import functools
import pathlib
import os
import signal
from multiprocessing import Pool
import multiprocessing
from datetime import timedelta
import math

from arg_parser import range_str_to_list
from components import pattern_components
from font import write_text
from life_history import write_life_history
from lifetree import lt
from gliders import PI_BLOCKS, mk_glider, single_channel_stream
from parse_p120_recipes import parse_p120_recipe
from speedometer import Speedometer

argparser = argparse.ArgumentParser(
    prog="oncoming.py", description="Search for glider patterns"
)


class ToolkitDef:
    min_spacing: int
    lane_offset: int
    period: int
    name: str

    def __init__(self, str):
        m = re.match(r"sc(\d+)b(\d+)p(\d+)", str)
        if not m:
            raise ValueError(
                f"Toolkit {str} is invalid. Expected something like sc90b5p120"
            )
        self.min_spacing = int(m.group(1))
        self.lane_offset = int(m.group(2))
        self.period = int(m.group(3))
        self.name = str


argparser.add_argument(
    "--toolkit",
    default="sc90b5p120",
    type=ToolkitDef,
    help="The oncoming gliders to search. Format sc[min spacing]b[lane_offset]p[period]. E.g., sc90b5p120",
)
argparser.add_argument(
    "--depth", type=int, help="The number of gliders to include in salvos"
)
argparser.add_argument(
    "--print-rle",
    type=str,
    default="",
    help="Prints a rle showing the given recipes. Skips processing anything else.",
)
argparser.add_argument(
    "--required-prefix",
    type=str,
    default="",
    help="results not containing info with given prefix are not reported",
)
argparser.add_argument(
    "--required-prefixes",
    type=str,
    default="",
    help="results not containing info with a prefix from the list are not reported",
)
argparser.add_argument(
    "--required",
    type=str,
    default="",
    help="results not containing all given exact semicolon separated infos are not reported",
)
argparser.add_argument(
    "--ignored",
    type=str,
    default="",
    help="given semicolon separated infos are not reported, but it does not block reporting the salvo",
)
argparser.add_argument(
    "--avoid-prefix",
    type=str,
    default=")(",
    help="results containing info with given prefix are not reported",
)
argparser.add_argument(
    "--n-gun-gliders", type=int, default=10, help="How many gun gliders to simulate"
)
argparser.add_argument(
    "--simulate-gens", type=int, default=None, help="How many generations to simulate"
)
argparser.add_argument(
    "--max-population", type=int, default=float('inf'), help="The maximum population results to report"
)
argparser.add_argument(
    "--min-population", type=int, default=0, help="The minimum population results to report"
)
argparser.add_argument(
    "--max-tree-population", type=int, default=float('inf'), help="The maximum population allowed in intermediates"
)
argparser.add_argument(
    "--max-tree-width", type=int, default=float('inf'), help="The maximum width allowed in intermediates"
)
argparser.add_argument(
    "--max-tree-height", type=int, default=float('inf'), help="The maximum height allowed in intermediates"
)
argparser.add_argument(
    "--max-tree-x", type=int, default=float('inf'), help="The maximum x allowed in intermediates"
)
argparser.add_argument(
    "--max-tree-y", type=int, default=float('inf'), help="The maximum y allowed in intermediates"
)
argparser.add_argument(
    "--min-tree-x", type=int, default=-float('inf'), help="The minimum x allowed in intermediates"
)
argparser.add_argument(
    "--min-tree-y", type=int, default=-float('inf'), help="The minimum y allowed in intermediates"
)

class SubtreeDef:
    options: tuple[tuple[int]]

    def __init__(self, str):
        depths = ()
        for d in str.split(";"):
            options = range_str_to_list(d)
            depths = depths + (tuple(options),)
        self.options = depths


argparser.add_argument(
    "--subtree",
    default=[],
    type=SubtreeDef,
    action="append",
    help="Search range. E.g. '1;90-255' to search the glider 1 followed by any glider in [90,255], or '1,3,5,7' to search the starting gliders 1,3,5 and 7. Use comma to separate at the same depth, then semicolon to separate depths. Ranges are inclusive.",
)
argparser.add_argument(
    "--max-delay", type=int, default=255, help="Maximum delay for a glider."
)
argparser.add_argument(
    "--check-for-shutoff",
    type=str,
    help="Checks if the given sequences shut off the glider stream by sending 120 extra gun gliders and comparing the state",
)
argparser.add_argument(
    "--find-minimum-follow",
    type=str,
    help="Finds the minimum follow distance for the given recipes",
)
argparser.add_argument("--use-gun-rle", type=str, help="Uses the rle as the gun")
argparser.add_argument(
    "--extract-recipes",
    type=pathlib.Path,
    help="Extracts the recipes from the given file. Outputs a JSON.",
)
argparser.add_argument(
    "--to-apgluxe",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Just print out the rles, don't simulate anything.",
)
argparser.add_argument(
    "--only-gliders",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Only process glider recipes.",
)
argparser.add_argument(
    "--do-not-report-gliders",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Do not report glider including results.",
)
argparser.add_argument(
    "--swim-upstream-after",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Send 2,96,96,96... after each part of the subtree."
)
argparser.add_argument(
    "--concurrent",
    action=argparse.BooleanOptionalAction,
    default=False,
    help="Use multiple threads for searching."
)
argparser.add_argument(
    "--view-orientations",
    type=str,
    default=None
)

args = argparser.parse_args()
args.starting_point = "not started yet"

def where_we_are(*a):
    print(f"Interrupted at search from: {args.starting_point}")
    raise Exception("Interrupted!")

signal.signal(signal.SIGINT, where_we_are)

simulate_gens = args.simulate_gens or args.toolkit.period * args.n_gun_gliders

simkin_gun = lt.pattern("""x = 37, y = 26, rule = B3/S23
3$11bo$11b3o$14bo$13b2o5$2b2o5b2o$2b2o5b2o2$6b2o$6b2o4$29b2o$16bo12b2o
$14bobo$14b3o9b2o5b2o$14bo11b2o5b2o!
""")(-490, -501)

gun_period = args.toolkit.period


def mk_fake_gun(n):
    fake_gun = sum(
        [mk_glider(0, gun_period * x) for x in range(0, n)], start=lt.pattern("")
    )
    fake_gun = fake_gun("rot180")(-10 + args.toolkit.lane_offset, -11)
    return fake_gun


fake_gun = mk_fake_gun(args.n_gun_gliders)
expected_incoming_gliders_pattern = fake_gun[simulate_gens]
expected_incoming_gliders = pattern_components(expected_incoming_gliders_pattern)

# phase 0 gliders
canonical_se = mk_glider(0, 0)("rot180").centre()
canonical_nw = mk_glider(0, 0).centre()
canonical_ne = mk_glider(0, 0)("rot270").centre()
canonical_sw = mk_glider(0, 0)("rot90").centre()

def recipe_stream_to_delays(stream):
    gliders = stream.split(",")
    result = []
    total = 0
    for g in gliders:
        m = re.match(r"^\(\s*(\d+)\s*\)\s*(?:\s*(\d+)\s*\+\s*)?(\d+)$", g.strip())
        if m:
            minimum_follow = int(m.group(1))
            addition = m.group(2)
            if addition:
                minimum_follow += int(addition)
            next_parity = int(m.group(3))
            parity = (total + minimum_follow) % 8
            g = minimum_follow + ((next_parity - parity) % 8)
            total += g
            result.append(g)
        else:
            g = int(g)
            total += g
            result.append(g)
    return result

def find_minimum_follow(recipes):
    expected_incoming_gliders = pattern_components(fake_gun[simulate_gens])
    for i, r in enumerate(recipes):
        if not r.strip():
            continue
        delays = recipe_stream_to_delays(r)
        total = sum(delays)

        p = fake_gun + single_channel_stream(delays)
        p = p[simulate_gens]

        escaping_gliders = []
        for c in pattern_components(p):
            for i, e in enumerate(expected_incoming_gliders):
                if c == e:
                    # this is one of the escaping gliders
                    escaping_gliders.append(i)
        n_escaping = max(escaping_gliders) + 1

        # now we know which gliders participate. Remove the rest.
        envelope = lt.pattern("5o$5o$5o$5o$5o").centre()

        pattern = mk_fake_gun(args.n_gun_gliders - n_escaping) + single_channel_stream(
            delays
        )

        @functools.lru_cache(maxsize=None)
        def pattern_at_gen(n):
            if n == 0:
                return pattern
            return pattern_at_gen(n - 1)[1]

        def try_follow(i):
            follow_up_glider = mk_glider(0, total + i)
            glider_envelope = lt.pattern()
            for j in range(0, simulate_gens + i):
                follow_up_glider = follow_up_glider[1]
                glider_envelope += follow_up_glider.convolve(envelope)
                if (pattern_at_gen(j + 1) & glider_envelope).nonempty():
                    return False
            return True

        low = args.toolkit.min_spacing
        r = 1
        if try_follow(low):
            return f",({low})"
        else:
            while r<=1024:
                hi = args.toolkit.min_spacing+r
                r += r
                if try_follow(hi):
                    while (hi-low>1):
                        test=(hi+low)//2
                        if try_follow(test):
                            hi=test
                        else:
                            low=test
                    return f",({hi})"
                else:
                    low=hi
            else:
                return f",({hi}+???)"

if args.find_minimum_follow:
    follow = find_minimum_follow(args.find_minimum_follow.split(";"))
    print(",".join(map(str, delays)) + follow)
    sys.exit(0)

required_split = args.required.split(";")
args.required = set()
for req in required_split:
    if req != "":
        args.required.add(req)
args.required_prefixes = args.required_prefixes.split(";")
ignored_split = args.ignored.split(";")
args.ignored = set()
for ign in ignored_split:
    args.ignored.add(ign)
args.gun_X = False

if args.use_gun_rle:

    def remove_gliders(p):
        p = p + lt.pattern()  # copy
        for c in pattern_components(p):
            cc = c.centre()
            if (
                cc == canonical_se.centre()
                or cc == canonical_se[1].centre()
                or cc == canonical_se[2].centre()
                or cc == canonical_se[3].centre()
            ):
                p -= c
        return p

    args.speboes = ["block(l-3,", "block(l4,", "beehive(l-3,o0", "beehive(l4,o1", "pond(l-4,", "pond(l5,", "boat(l-4,o1", "boat(l5,o2", "ship(l-4,o1", "ship(l5,o1"]
    if args.use_gun_rle == 'breaksnark0Speboe': # just to catalogue speboes (block l-3,l4; beehive l-3o0, l4o1; pond l-4,l5; boat l-401,l5o2; ship l-4o1,l5o1)
        args.use_gun_rle = '''x = 75, y = 119, rule = B3/S23
                              9b2o$8bobo$8b2o5$7b2o7bo$6bobo6bobo$6b2o7b2o4$22b2o$14b2o5bo2bo$13bobo
                              5bo2bo$14bo7b2o3$19b2o6b2o$18bo2bo4bo2bo$18bo2bo5b2o$19b2o3$26bo5b2o$
                              25bobo4b2o$25bobo$26bo3$31b2o$31b2o14$65b2o$64bobo$58b2o4bo$56bo2bo2b
                              2ob4o$56b2obobobobo2bo$59bobobobo$59bobob2o$60bo2$73b2o$64b2o7bo$64b2o
                              5bobo$71b2o4$72bo$70bobo$71b2o$61b2o$62bo$59b3o$59bo48$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark0':
        args.use_gun_rle = '''x = 75, y = 73, rule = B3/S23
                              65b2o$64bobo$58b2o4bo$56bo2bo2b2ob4o$56b2obobobobo2bo$59bobobobo$59bob
                              ob2o$60bo2$73b2o$64b2o7bo$64b2o5bobo$71b2o4$72bo$70bobo$71b2o$61b2o$
                              62bo$59b3o$59bo48$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark1':
        args.use_gun_rle = '''x = 134, y = 133, rule = B3/S23
                              124b2o$123bobo$117b2o4bo$115bo2bo2b2ob4o$115b2obobobobo2bo$118bobobobo
                              $118bobob2o$119bo2$132b2o$123b2o7bo$123b2o5bobo$130b2o4$131bo$129bobo$
                              130b2o$120b2o$121bo$118b3o$118bo108$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark-1':
        args.use_gun_rle = '''x = 135, y = 132, rule = B3/S23
                              125b2o$124bobo$118b2o4bo$116bo2bo2b2ob4o$116b2obobobobo2bo$119bobobobo
                              $119bobob2o$120bo2$133b2o$124b2o7bo$124b2o5bobo$131b2o4$132bo$130bobo$
                              131b2o$121b2o$122bo$119b3o$119bo107$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark13':
        args.use_gun_rle = '''x = 121, y = 132, rule = B3/S23
                              111b2o$110bobo$104b2o4bo$102bo2bo2b2ob4o$102b2obobobobo2bo$105bobobobo
                              $105bobob2o$106bo2$119b2o$110b2o7bo$110b2o5bobo$117b2o4$118bo$116bobo$
                              117b2o$107b2o$108bo$105b3o$105bo107$b2o$obo$2bo!'''
    if args.use_gun_rle == 'breaksnark-13':
        args.use_gun_rle = '''x = 147, y = 132, rule = B3/S23
                              137b2o$136bobo$130b2o4bo$128bo2bo2b2ob4o$128b2obobobobo2bo$131bobobobo
                              $131bobob2o$132bo2$145b2o$136b2o7bo$136b2o5bobo$143b2o4$144bo$142bobo$
                              143b2o$133b2o$134bo$131b3o$131bo107$b2o$obo$2bo!'''
    if args.use_gun_rle == 'hooked':
        args.use_gun_rle = '''x = 75, y = 79, rule = B3/S23
                            9bo$9b3o$12bo$11b2o5$2o5b2o$2o5b2o2$4b2o$4b2o$9b2o$8bo2bo$7b2o$8b2o17b
                            2o$9bo17b2o2$24b2o5b2o$24b2o5b2o32$47b2o$47bo$48b3o$50bo21$73bo$74bo$
                            72b3o!'''

    if args.use_gun_rle == 'perp_gli':
        args.use_gun_rle = '''x = 70, y = 84, rule = B3/S23
                              68bo$69bo$67b3o79$3o$2bo$bo!'''
    if args.use_gun_rle == '120':
        args.use_gun_rle = '''x = 83, y = 51, rule = B3/S23
                              14bo$14bo$14bo16$2o$obo64bo$bo65bo$67bo$17bo6b2o$16bobo5b2o37b3o3b3o$
                              16bobo$17bo49bo$34bo32bo$9b2o23b3o30bo$8bobo26bo$9bo26b2o5$25b2o5b2o$
                              25b2o5b2o46b2o$79bo2bo$29b2o49b2o$24bo4b2o$5b2o16bobo$5b2o16bobo$24bo$
                              52b2o$39bo12b2o$37bobo$37b3o9b2o5b2o$37bo11b2o5b2o$12bo$6b2o3bobo$5bo
                              2bo2b2o$6b2o!'''

    if args.use_gun_rle == '120C':
        args.use_gun_rle = '''x = 44, y = 47, rule = B3/S23
o$o$o20$3bo6b2o$2bobo5b2o$2bobo$3bo$20bo$20b3o$23bo$22b2o5$11b2o5b2o$11b2o5b2o2$15b2o$15b2o4$38b2o$25bo12b2o$23bobo$23b3o9b2o5b2o$23bo11b2o5b2o!'''

    if args.use_gun_rle == '120X':
        args.gun_X = True
        args.use_gun_rle = '''x = 99, y = 58, rule = B3/S23
                              9bo$9b3o$12bo$11b2o5$2o5b2o$2o5b2o2$4b2o$4b2o$9b2o$8bo2bo$7b2o$8b2o3bo
                              13b2o$9bo4bo12b2o$12b3o$24b2o5b2o$24b2o5b2o25$82bo$81bobo4b2o$82bobo3b
                              obo$83bo6bo4b2o$86b4ob2o2bo2bo$86bo2bobobobob2o$76bo12bobobobo$75bobo
                              12b2obobo$75bobo16bo$76bo2$89b2o$89b2o!'''
        args.required.add("barge(l46,o0,d26)")
        args.required.add("beehive(l34,o0,d25)")
        args.required.add("blinker(l26,o0,d12)")
        args.required.add("blinker(l26,o1,d6)")
        args.required.add("block(l43,o0,d41)")
        args.ignored.add("glider(l6)(0B0)(ph0)")

    gun = lt.pattern(args.use_gun_rle.strip())
    gun_without_gliders = remove_gliders(gun)

    glider_appears = gun
    # now, let's fast forward the gun until we see an output glider.
    for i in range(0, args.toolkit.period):
        if remove_gliders(glider_appears) != glider_appears:
            # there's a glider
            break
        glider_appears = glider_appears[1]

    glider_appears = glider_appears[(args.n_gun_gliders - 1) * args.toolkit.period]

    # and make the gliders canonical
    rect = (-float("inf"), 0, 0, 0)
    for i in range(0, 4):
        found = False
        for c in pattern_components(glider_appears):
            if c.centre() == canonical_se.centre():
                rect = max(rect, tuple(c.getrect()))
                found = True
        if found:
            break
        glider_appears = glider_appears[1]
    else:
        raise Exception("Couldn't find gliders?")

    # shift the gun so that it lines up with the fake gun.
    fx, fy, _, _ = mk_fake_gun(1).getrect()
    x, y, _, _ = rect
    fake_gun = glider_appears(fx - x, fy - y)


if args.extract_recipes:

    def parse_object(obj):
        m = re.match(r"^'([\w ]+)((?:\([^)]*\))*)'$", obj.strip())
        if not m:
            raise SyntaxError(f"Invalid object: {obj}")
        name = m.group(1)
        info = m.group(2)
        return {"name": name, "info": info}

    gun_glider_names = set()
    for i in range(0, args.n_gun_gliders):
        gun_glider_names.add(f"g{i}")

    def parse_objects(objs):
        results = []
        gun_gliders = set()
        for o in re.split(r"('[^']*')", objs):
            o = o.strip()
            if not o or o == ",":
                continue
            obj = parse_object(o)
            if obj["name"] in gun_glider_names:
                gun_gliders.add(obj["name"])
            else:
                results.append(obj)

        for i in range(0, args.n_gun_gliders):
            if not f"g{i}" in gun_gliders:
                break

        consumed = args.n_gun_gliders - i
        if i != len(gun_gliders):
            results.append("!skipped glider")
        if i == 0:
            results.append("!consumed all")

        return {"consumed": consumed, "objects": results}

    with open(args.extract_recipes) as results_file:
        for line in results_file:
            m = re.match(
                r"""^\((\d+(?:, \d+)*,?)\)\s*\[('[^']*'(?:, '[^']*')*)?\]$""",
                line.strip(),
            )
            if not m:
                raise SyntaxError(f"Invalid line: {line}")
            stream = tuple(map(int, filter(bool, m.group(1).split(","))))
            info = parse_objects(m.group(2) or "")
            if info["consumed"] < 10 and len(info["objects"]) == 1:
                print(stream, info)

    sys.exit(0)

friendly_names = {
    "xq4_153": "glider",
    "xs4_33": "block",
    "xp2_7": "blinker",
    "xs6_696": "beehive",
    "xs7_2596": "loaf",
    "xs5_253": "boat",
    "xs6_356": "ship",
    "xs8_6996": "pond",
    "xs4_252": "tub",
    "xs7_25ac": "long boat",
    "xs12_g8o653z11": "ship tie",
    "xp2_7e": "toad",
    "xp2_318c": "beacon",
    "xs14_g88m952z121": "half bakery",
    "xs7_178c": "eater 1",
    "xs6_25a4": "barge",
    "xs6_bd": "snake",
    "xs11_g8o652z11": "boat tie ship",
    "xq4_6frc": "lwss",
    "xs8_178k8": "tub_with_tail",
}

class HashablePattern:
    def __init__(self, c: lifelib.Pattern, digest: int):
        self.c = c
        self.digest = digest

    def __hash__(self):
        return self.digest

    def __eq__(self, other):
        return self.digest == other.digest

@functools.lru_cache(maxsize=None)
def pattern_orientation(c):
    c = c.c
    orientations = [c.digest()]
    for o in ["rot270", "rot180", "rot90", "flip_x", "flip_y", "swap_xy", "swap_xy_flip"]:
        orientations.append(c(o).digest())
    orientations = sorted(set(orientations))
    return orientations.index(c.digest())

def component_info(c, alt=False):
    if c == c[1]:
        # still life
        code = c.apgcode
        name = friendly_names.get(code, code)
        x, y, w, h = c.getrect()
        lane = x - y
        depth = x + y + w + h
        orientation = pattern_orientation(HashablePattern(c, c.digest()))
        if args.gun_X:
            if depth < -50:
                return ""
        return f"{name}(l{lane},o{orientation},d{depth})"
    elif c == c[120]:
        # oscillator
        code = c.apgcode
        name = friendly_names.get(code, code)
        x, y, w, h = c.getrect()
        if (name == 'blinker'): # coordinates of the center ??
            x = x + w//2
            y = y + h//2
        lane = x - y
        depth = x + y + w + h
        if args.gun_X:
            if depth < -50:
                return ""
        orientation = pattern_orientation(HashablePattern(c, c.digest()))
        if (name == 'blinker') and alt:
            # skip blinkers when processing the alt.
            return None
        return f"{name}(l{lane},o{orientation},d{depth})"
    elif c.centre() == c[4].centre():
        # spaceship
        code = c.apgcode
        name = friendly_names.get(code, code)

        x, y, w, h = c.getrect()
        x4, y4, _, _ = c[4].getrect()

        if x4 - x == 0 or y4 - y == 0:
            # xWSS, need to process even if alt is true.
            return f"{name}({x},{y})({x4-x},{y4-y})"
        elif alt:
            # ignore gliders in alt -- we've already got them.
            return None

        # give the incoming gliders the names g0, g1, etc.
        for i, e in enumerate(expected_incoming_gliders):
            if c == e:
                return f"g{i}"

        # glider phase/color info
        match (x4 - x, y4 - y):
            case (1, 1):
                # same direction as gun
                canonical = canonical_se
                direction = "B"
            case (1, -1):
                # gun side 90 degree
                canonical = canonical_ne
                direction = "R"
            case (-1, 1):
                # recipe side 90 degree
                canonical = canonical_sw
                direction = "L"
            case (-1, -1):
                # same direction as recipe
                canonical = canonical_nw
                direction = "F"

        if c.centre() == canonical:
            phase = 0
            phase_mod2 = "0"
        elif c[1].centre() == canonical:
            phase = 1
            phase_mod2 = "1"
        elif c[2].centre() == canonical:
            phase = 2
            phase_mod2 = "0"
        elif c[3].centre() == canonical:
            phase = 3
            phase_mod2 = "1"

        lx, ly, lw, lh = c[phase].getrect()
        lane = lx - ly
        depth = lx + ly + lw + lh

        match (x4 - x, y4 - y):
            case (1, 1):
                location = f"l{lane}"
                color = "01"[lane % 2]
            case (1, -1):
                location = f"d{depth}"
                color = "01"[depth % 2]
            case (-1, 1):
                location = f"d{depth}"
                color = "01"[depth % 2]
            case (-1, -1):
                location = f"l{lane}"
                color = "01"[lane % 2]

        return f"{name}({location})({color}{direction}{phase_mod2})(ph{phase})"
    else:
        # not an object
        return None

args.min_big_pop_so_farA = float('inf') # soory for using args
args.min_big_pop_so_farB = float('inf') # soory for using args

def evaluate(s, patt):
    rect = patt.getrect()
    x, y, w, h = 0, 0, -1, -1
    if rect:
        x, y, w, h = rect
        if x < args.min_tree_x:
            args.tree_continue = False
            return
        if y < args.min_tree_y:
            args.tree_continue = False
            return
        if w > args.max_tree_width:
            args.tree_continue = False
            return
        if h > args.max_tree_height:
            args.tree_continue = False
            return
        if x+w > args.max_tree_x:
            args.tree_continue = False
            return
        if y+h > args.max_tree_y:
            args.tree_continue = False
            return
    if patt.population > args.max_population:
        if patt.population < args.min_big_pop_so_farA:
            args.min_big_pop_so_farA = patt.population
            #print(s, 'defining min_big_pop_so_far A' + f"#{args.min_big_pop_so_farA}" )
        else:
            #print(s, 'too big A' + f"#{patt.population} >= {args.min_big_pop_so_farA}" )
            return
    if (patt - expected_incoming_gliders_pattern).population > args.max_population:
        if (patt - expected_incoming_gliders_pattern).population < args.min_big_pop_so_farB:
            args.min_big_pop_so_farB = (patt - expected_incoming_gliders_pattern).population
            #print(s, 'defining min_big_pop_so_far B' + f"#{args.min_big_pop_so_farB}" )
        else:
            #print(s, 'too big B' + f"#{(patt - expected_incoming_gliders_pattern).population} >= {args.min_big_pop_so_farB}" )
            return
    if patt.population < args.min_population:
        print(s, 'too small A' + f"#{patt.population}")
        return
    if (patt - expected_incoming_gliders_pattern).population < args.min_population:
        print(s, 'too small B' + f"#{patt.population}")
        return

    infos = set()
    infos.add(f"#{patt.population}")
    infos.add(f"<{x},{y},{w},{h}>")
    if patt.population == 0:
        infos.add("empty")
    required_cnt = len(args.required)
    if args.required_prefix == "":
        if len(args.required_prefixes)>0:
            args.required_prefix = args.required_prefixes[0]
    required_prefix_matched = args.required_prefix == ""

    if args.only_gliders:
        if patt.population % 5 or patt.population >= 5 + 5 * args.n_gun_gliders:
            return
        diff = patt - reference
        if diff.population != 5:
            return
        if diff.centre() != diff[4].centre():
            return
        gli_info = component_info(diff)
        if gli_info.startswith(args.avoid_prefix):
            return
        if gli_info.startswith(args.required_prefix):
            required_prefix_matched = True
        if not gli_info.startswith("glider"):
            return
        for i in range(patt.population // 5 - 1):
            infos.add(f"g{i}")
        infos.add(gli_info)
    else:
        components = pattern_components(patt)
        has_unidentified = False
        for c in components:
            info = component_info(c, False)
            if info is not None:
                if info.startswith("glider") and args.do_not_report_gliders:
                    return
                for speboe in args.speboes:
                    if info.startswith(speboe):
                        info = 'speboe'+info
                        break
                if info!="":
                    if info.startswith(args.avoid_prefix):
                        return
                    if info.startswith(args.required_prefix):
                        required_prefix_matched = True
                    if info in args.required:
                        required_cnt -= 1
                    elif not info in args.ignored:
                        infos.add(info)
            else:
                has_unidentified = True
        if has_unidentified:
            components1 = pattern_components(patt[1])
            for c in components1:
                info = component_info(c, True)
                if info is not None:
                    if info.startswith(args.avoid_prefix):
                        return
                    if info.startswith(args.required_prefix):
                        required_prefix_matched = True
                    if not info in args.required and not info in args.ignored:
                        infos.add(info)
    #infos.add(f"!{required_cnt}")
    #infos.add(f"!{required_prefix_matched}")
    if not required_prefix_matched:
        for req_prefix in args.required_prefixes:
            for info in infos:
                if info.startswith(req_prefix):
                    required_prefix_matched = True
                    break
            if required_prefix_matched:
                break
        if not required_prefix_matched:
            return
    if required_cnt>0:
        return
    return (s, sorted(infos))

if args.print_rle:
    macros = {
        'sc90b5p120': {
            'construct_arm': 'mode sc, 0, 126, 102, 100, 195, 90, 91, 95, 98, 105, 90, 101, 141, 94, 159, 92, 146, 99, 90, 152, 139, 144, 92, 161, 131, 116, 101, 114, 111, 112, 93, 127, 98, 102, 114, 107, 157, 90, 90, 90, 91, 91, 243, 113, 139, 108, 95, 127, 121, 99, 257, 144, 94, 218, 148, 226, 111, 119, 100, 95, 91, 138, 201, 221, 216, 138, 125, mode p120, set 28 (mod 120), (90)'
        }
    }
    green_patt = lt.pattern("")
    red_patt = lt.pattern("")
    recipes = args.print_rle.split(";")
    for i, r in enumerate(recipes):
        if not r.strip():
            continue
        parse = parse_p120_recipe(r, macros.get(args.toolkit.name, {}))
        gliders_int = parse.delays
        #gtxt = ""
        #for g in gliders_int:
        #    gtxt += f",{g}"
        #print(f"#{gliders_int}")
        if parse.start_mode == "p120":
            green_patt += fake_gun(200 * i, 0) + single_channel_stream(gliders_int)(
                200 * i, 0
            )
            red_patt += write_text(" ".join(map(str, gliders_int)))(200 * i, 0)
        elif parse.start_mode == "sc":
            green_patt += PI_BLOCKS[1](200 * i, 0) + single_channel_stream(gliders_int)(200*i, 0)
            red_patt += write_text(" ".join(map(str, gliders_int)))(200 * i, 0)

    #follow = "" #find_minimum_follow(gtxt[1:]) -- does not work as expected
    result = evaluate(f"{gliders_int}", green_patt[args.simulate_gens])
    print(f"#{result}")
    print(write_life_history(green=green_patt, red=red_patt))

    sys.exit(0)

if args.check_for_shutoff:
    print("Checking if patterns shut off the stream.")

    recipes = args.check_for_shutoff.split(";")

    for i, r in enumerate(recipes):
        gliders_int = recipe_stream_to_delays(r)
        original = fake_gun + single_channel_stream(gliders_int)
        extended_fake_gun = mk_fake_gun(args.n_gun_gliders + 120)
        extended = extended_fake_gun + single_channel_stream(gliders_int)

        original = original[simulate_gens]

        extended_simulate_gens = simulate_gens + args.toolkit.period * 120 * 4
        extended = extended[extended_simulate_gens]
        # allow the first n+20 gliders to possibly escape, but the rest should be absorbed.
        expected_gun_positions = mk_fake_gun(args.n_gun_gliders + 20)[
            extended_simulate_gens
        ]

        if original == extended:
            print("Confirmed shutoff!")
            print(gliders_int)
        elif (original - expected_gun_positions) == (extended - expected_gun_positions):
            print("Possible shutoff with escaping gun gliders")
            print(gliders_int)

    sys.exit(0)

reference = fake_gun[simulate_gens]

if args.view_orientations:
    if args.view_orientations in friendly_names.values():
        args.view_orientations = next(k for k,v in friendly_names.items() if v == args.view_orientations)
    p = lt.pattern(args.view_orientations)
    results = {}
    orientations = [p]
    for o in ["rot270", "rot180", "rot90", "flip_x", "flip_y", "swap_xy", "swap_xy_flip"]:
        orientations.append(p(o))
    for oriented in orientations:
        x, y, w, h = oriented.getrect()
        oriented = oriented(-x + 1, -y + 1)
        data = ['.'] * (w+2) * (h*2)
        for (cx, cy) in oriented.coords():
            data[cy*(w+2) + cx] = '#'
        data = ''.join(data)
        lines = []
        for i in range(0, h+2):
            lines.append(data[i*(w+2):(i+1)*(w+2)])
        lines = '\n'.join(lines)
        results[pattern_orientation(HashablePattern(oriented, oriented.digest()))] = lines

    for orientation, view in sorted(results.items()):
        print()
        print(f'o{orientation}')
        print('==========================')
        print(view)

    sys.exit(0)

def process_concurrent(s):
    stream_to_check = s
    if args.swim_upstream_after:
        stream_to_check = parse_p120_recipe(','.join(map(str, s)) + ',(90), 2, 96, 96, 96, 96, 96, 96, 96, 96', {}).delays
    patt = fake_gun + single_channel_stream(stream_to_check)

    patt_n = patt[simulate_gens]
    result = evaluate(s, patt_n)
    return result

def nontrivial(evaldescription):
    for i in range(len(evaldescription)):
        if evaldescription[i]==f"g{i}":
            continue
        if evaldescription[i]=="glider(l0)(ph3)(WB1)":
            continue
        return True

seen = []
def recurse(s, depth=0):
    stream_to_check = s
    if args.swim_upstream_after:
        stream_to_check = parse_p120_recipe(','.join(map(str, s)) + ',(90), 2, 96, 96, 96, 96, 96, 96, 96, 96', {}).delays
    patt = fake_gun + single_channel_stream(stream_to_check)

    depth_step = 1
    if not args.to_apgluxe:
        patt_n = patt[simulate_gens]
        if (patt_n - expected_incoming_gliders_pattern).population > args.max_tree_population:
             #print("big\n"+(patt_n - expected_incoming_gliders_pattern).rle_string())
             return
        key = (patt_n - expected_incoming_gliders_pattern).rle_string()
        if key in seen[depth+1]:
             #print("seen:"+key)
             depth_step += 1
        else:
            args.seen = False
            seen[depth+1].add(key)
            args.tree_continue = True
            e=evaluate(s, patt_n) # could reset args.tree_continue
            if not args.tree_continue:
                depth_step += 1
            if e:
                #if nontrivial(e[1]):
                print(e, flush=True)
    else:
        print(patt.rle_string()[len("#CLL state-numbering golly\n") :])

    if depth - depth_step >= 0:
        seen[depth]=set()
        for n in range(args.toolkit.min_spacing, args.max_delay + 1):
            recurse(s + (n,), depth - depth_step)


if __name__ == "__main__":
    """
    p = lt.pattern('')
    for d1 in range(0, 8):
        p += (fake_gun + mk_glider(0, d1) + mk_glider(0, d1 + 90))(100*(d1), 0)

    print(p.rle_string())

    Results:
    0 - delete
    1 - block on gun side
    2 - backwards kickback
    3 - pond on gun side
    4 - delete
    5 - pond on recipe side
    6 - forwards kickback
    7 - block on recipe side
    """

    if args.concurrent:
        print("Generating jobs...", file=sys.stderr)
        if not args.subtree:
            args.subtree = [SubTreeDef("0-7")]
        len_all_options = 0
        for subtree in args.subtree:
            for depth in range(0, args.depth - len(subtree.options) + 1):
                deeper = (tuple(range(args.toolkit.min_spacing, args.max_delay + 1)),) * depth
                len_all_options += math.prod(map(len, subtree.options + deeper))

        print('Total:', len_all_options, file=sys.stderr)

        def all_options():
            for subtree in args.subtree:
                for depth in range(0, args.depth - len(subtree.options) + 1):
                    for starting_point in itertools.product(*subtree.options):
                        deeper = (tuple(range(args.toolkit.min_spacing, args.max_delay + 1)),) * depth
                        for d in itertools.product(*map(lambda a: (a,), starting_point), *deeper):
                            yield d

        multiprocessing.set_start_method('spawn')
        with Pool(processes=os.cpu_count() - 1) as pool:
            speedo = Speedometer()
            print("Starting...", file=sys.stderr)
            for result in pool.imap_unordered(process_concurrent, all_options(), chunksize=256):
                if speedo.tick(1):
                    current_per_s = speedo.get_current_speed_and_reset()
                    avg_per_s = speedo.overall_speed()
                    done = speedo.n_finished
                    percent = done/len_all_options*100
                    estimate_s = int((len_all_options - done)/avg_per_s)
                    estimate_min = estimate_s // 60
                    estimate_hr = estimate_min // 60
                    if estimate_hr > 0:
                        estimate = f"{estimate_hr}h {estimate_min % 60}m {estimate_s % 60}s"
                    elif estimate_min > 0:
                        estimate = f"{estimate_min % 60}m {estimate_s % 60}s"
                    else:
                        estimate = f"{estimate_s}s"
                    print(f"{current_per_s:.2f}/s, {avg_per_s:.2f}/s avg, {done}/{len_all_options} ({percent:0.2f}%), {estimate} left", file=sys.stderr)
                if result:
                    print(*result)
        pool.join()
    else:
        if not args.subtree:
            args.subtree = [SubtreeDef("0-7")]
        for d in range(0,args.depth+2):
            seen.append(set())
        for subtree in args.subtree:
            for starting_point in itertools.product(*subtree.options):
                args.seen = True
                seen[args.depth - len(starting_point)+1]=set()
                if args.starting_point == "not started yet":
                    print(f"search started, at starting point: {starting_point}")
                args.starting_point = starting_point
                print(f"{starting_point}", end = " ", file=sys.stderr, flush=True)
                recurse(starting_point, depth=args.depth - len(starting_point))
                print(f"all_seen = {args.seen}", file=sys.stderr, flush=True)
        print(f"search done, last starting point: {args.starting_point}")
        
So this is our kit now:

Code: Select all

x = 449, y = 463, rule = LifeHistory
91.2A$90.A.A118.2A$84.2A4.A119.A.A$82.A2.A2.2A.4A109.2A4.A$82.2A.A.A.
A.A2.A107.A2.A2.2A.4A$85.A.A.A.A110.2A.A.A.A.A2.A$85.A.A.2A114.A.A.A.
A$86.A118.A.A.2A$206.ED$99.2A105.2D$2E88.2A7.A119.2A$2E88.2A5.A.A110.
2A7.A$97.2A111.2A5.A.A$217.2A3$81.3A$83.A117.3A$82.A10.3A107.A$87.2A
4.A108.A$88.A5.A112.2A$85.3A120.A$9.2A74.A119.3A$8.2A195.A$10.A$220.
2A$219.2A$221.A2$128.3D.3D6.D2.3D.3D$128.D.D.D.D5.2D2.D.D3.D98.D2.D.D
2.D6.3D.3D$128.3D.D.D6.D2.D.D3.D97.2D2.D.D.2D6.D.D3.D$130.D.D.D6.D2.D
.D2.D99.D2.3D2.D6.3D3.D$128.3D.3D5.3D.3D2.D99.D4.D2.D8.D2.D$248.3D3.D
.3D5.3D2.D6$116.2A$116.A.A$116.A3$32.2A$32.A.A$32.A13$255.2A$255.A.A$
255.A$49.D.D$50.2D$50.D2$143.2A$55.2A85.2A$54.2A88.A$56.A14$280.A$
279.2A$279.A.A11$85.A$84.2A$84.A.A5$180.2D$179.2D$181.D$302.2D$301.2D
$303.D12$107.3A$107.A$108.A35$146.A$145.2A$145.A.A21$168.2A$167.2A$
169.A23$192.3A$192.A$193.A2$91.2A$90.A.A$84.2A4.A$82.A2.A2.2A.4A$82.
2A.A.A.A.A2.A$85.A.A.A.A$85.A.A.2A$86.A2$99.2A$90.2A7.A$90.2A5.A.A$
97.2A3$92.A$81.3A7.2A$83.A7.A.A$82.A$87.2A$88.A$85.3A$85.A7$128.3D.3D
5.3D.3D6.D2.3D.3D5.3D.3D$128.D.D.D.D5.D.D.D.D5.2D2.D.D.D.D5.D.D.D$
106.D.D19.3D.D.D5.3D.3D6.D2.D.D.3D5.3D.3D$107.2D21.D.D.D7.D.D.D6.D2.D
.D3.D7.D3.D$107.D20.3D.3D5.3D.3D5.3D.3D.3D5.3D.2D$232.A$231.2A$231.A.
A2$114.2A$113.2A$115.A22$139.A$138.2A$138.A.A121.A$261.2A$261.A.A21$
285.A$284.2A$284.A.A$165.3A$165.A$166.A21$190.A$189.2A121.A$189.A.A
119.2A$311.A.A21$334.2A$333.2A$335.A23$360.A$359.2A$359.A.A39$401.A$
400.2A$400.A.A21$423.2A$422.2A$424.A22$447.2A$446.2A$448.A!
We can send the backward glider, destroy sending further, destroy cleanly and convert to speboe.
(I will let the search at hd0 to continue for a while to find a speboe on the other side of the lane, but we can easily live without it, and wiki could already be updated). It would be nice to have backward glider 4HD shifted to either side ... as (one side) universal pull speboes.

The backward glider 4HD shifted ("glider(l5)(1B" resp. "glider(l-3)(1B") would be nice to have as OGGCA destroyal result as well.
User avatar
Hippo.69
Posts: 696
Joined: July 14th, 2020, 7:35 pm
Contact:

Re: Optimized Snarkmaker

Post by Hippo.69 »

russellsprouts wrote: September 16th, 2026, 6:43 pm

Code: Select all

uv run snark.py optimize -r results/snarkbreaker-intermediates.sqlite -o results/snarkbreaker-starting-points.sqlite -n 1024 --max-allowed-population=100 --must-contain='11$9b2o17b2o$8bobo17bo$9bo16bobo$26b2o2$12bo$11bobo$12bo5$13b3o!
|11$9b2o17b2o$8bobo17bo$9bo16bobo$26b2o2$12bo$11bobo$12bo4$14bo$14bo$
14bo!|3$14b2o$13bobo$7b2o4bo$5bo2bo2b2ob4o$5b2obobobobo2bo$8bobobobo$8bobob
2o$9bo2$22b2o$13b2o7bo$13b2o5bobo$20b2o7$10b2o$11bo$8b3o$8bo!' --n-processes=32 --depth-range=0
We could use the same technique to try to find a faster recipe for the SPEBOE -> reverse glider as well. I may run a search for that next
I would extend must contain by '|6bo$5bobo$6bo4$6b2o$6b2o3$14b2o$14bo$12bobo$12b2o7$2b2o$3bo$3o$o!|11b2o$10bobo$4b2o4bo$2bo2bo2b2ob4o$2b2obobobobo2bo$5bobobobo$5bobob2o$
6bo2$19b2o$10b2o7bo$10b2o5bobo$17b2o7$b2o4b2o$o2bo2bobo$o2bo3bo$b2o!'

corresponding to backward speboeglider (impact on snark).

Having reduction to such glider would be very versatile ... you can easily use it for speboe on arbitrary "bigger" distance of the same parity, you can easily convert it to nothing.
Similarly such output(s) for OGGCA destroyal would be nice.
User avatar
Hippo.69
Posts: 696
Joined: July 14th, 2020, 7:35 pm
Contact:

Re: Optimized Snarkmaker

Post by Hippo.69 »

Actually I would like another piece in the kit:(.

In the 0e0pmetacell the last OGGCA is in really big distance. It would be waste of resources to convert it to speboe and start snark destruction by converting the speboe to hd0 backward glider ... and waiting till the glider returns.

Much more effificent method is stopping OGGCA and using escaping gliders to send backward glider to destroy snark while the salvo destroying the OGGCA would be on the way (actually it could be sent even before sending the stop glider) . The backward glider should pass by the destroying salvo, so it should be at hd with absolute value at least 8.

It would be nice to have such a snark destruction leading to hd0 backward glider to start the destruction chain.

For 0e0p metacell as I have just 2 turns, so I could use the already computed 13hd glider converting to 1hd glider with conversion to nothing, and I actually am not sure we would need more then 2 turns in "long range constructions" so it could be enough.

But I would probably run the searches for 8hd->0hd,-8hd->0hd,9hd->0hd,... giving it reasonable depth to finish the search and have chance for the success.

For now my patience on 0hd input case is over ... the depth 6 search (with depth reductions) is going to finish and it found only several reductions to empty, one extra reduction to speboeboat(l5,o2,d-27), what is on the same side as the original speboeblock(l4,o0,d-36) just a slightly different distance. The only found partial to be converted to backward glider is 'blinker(l-3,o0,d19)', 'glider(l-29)(1B0)(ph2)'.
I have about 20 more starting points to finish the search, but I am sceptical it would find something usefull.
russellsprouts
Posts: 69
Joined: January 28th, 2026, 10:04 am

Re: Optimized Snarkmaker

Post by russellsprouts »

Hippo.69 wrote: September 6th, 2026, 4:21 am For those limited by space where OGGCA will be (I know only about 0e0p...), here are "edgy left emissions" (color/phase ... to cheaper variants for less edgy)
I6_I6 wrote: September 22nd, 2026, 1:31 am Are you planning on implementing Hippo.69's "edgy left emissions"? If not, what's the closest lane to the simkin gun that you can fire from using only the default recipes?
I've transcribed the recipes into the recipe format:

Code: Select all

# Gliders that can emit very close to the gun.

# min d-26
let g1_edgiest(d-10) = 3, 196, 128, swim 4, (90) in
# min d-20
let g2_edgy(d-12) = 1, 92, 134, swim 3, (90) in
# min d-24
let g2_edgiest(d-34) = 1, 90, 90, 92, 170, 117, swim 6, (90) in
# min d-27
let g3_edgy(d-17) = 1, 90, 109, 140, 205, swim 6, (167) in
# min d-35
let g3_edgiest(d-33) = 1, 90, 95, 197, 250, 120, swim 8, (90) in
# min d-23
let g4_edgy(d-15) = 3, 96, 92, 147, swim 4, (90) in
# min d-25
let g4_edgiest(d-25) = 1, 90, 95, 187, 90, 165, swim 7, (90) in

# min d-17 (ignoring beehive), d-1 with beehive
let g5_edgy(d-5) = 3, 102, 90, swim 3, (90) in
# min d-17 (ignoring beehive), d-1 with beehive
let g6_edgy(d-15) = 3, 195, 102, 90, swim 5, (152) in
# min d-16 (ignoring beehive), d-2 with beehive
let g7_edgy(d-10) = 3, 201, swim 4, (167) in
# min d-16 (ignoring beehive), d-3 with beehive
let g8_edgy(d-12) = 1, 90, 109, 143, 106, swim 4, (90) in

construct_arm

g1_edgiest(d-26)
g2_edgiest(d-24)
g3_edgiest(d-35)
g4_edgiest(d-25)

g2_edgy(d-20)
g3_edgy(d-27)
g4_edgy(d-23)

g5_edgy(d-17)
g5_edgy(d-1)
g6_edgy(d-17)
g6_edgy(d-1)
g7_edgy(d-16)
g7_edgy(d-2)
g8_edgy(d-16)
g8_edgy(d-2)

Code: Select all

x = 4363, y = 4360, rule = LifeHistory
2A$2A2.3E6.D2.3D.3D6.D2.3D.3D6.D2.3D.3D6.D2.3D.3D5.3D.3D5.3D2.D6.3D.
3D5.3D.3D6.D2.3D.3D5.3D.3D6.D2.3D2.D7.D2.D.D2.D6.3D.D.D6.D2.3D.3D5.3D
.3D6.D2.D.D.3D5.3D.3D5.3D.3D6.D2.3D.3D6.D2.3D.3D6.D2.D.D.D.D5.3D.3D6.
D2.3D2.D7.D2.3D2.D7.D3.D2.3D6.D2.3D2.D7.D3.D2.D.D6.D3.D3.D7.D3.D2.3D
5.3D.3D6.D2.3D.3D5.3D.3D6.D2.3D.3D6.D3.D2.D.D6.D2.3D.3D6.D2.3D.3D5.3D
.3D5.3D.3D5.3D.3D5.3D2.D6.3D2.D6.3D.D.D.3D6.D3.D2.3D6.D2.3D.3D6.D2.3D
.3D5.3D.3D6.D2.3D.3D6.D2.3D2.D6.3D.3D5.3D.3D.3D6.D2.D.D.D.D5.3D.D.D5.
3D2.D2.3D6.D2.D.D.3D5.3D.3D.3D6.D3.D3.D7.D3.D2.3D6.D2.3D.3D5.3D.3D5.
3D2.D7.D2.3D.3D5.3D.3D2.D6.3D.3D2.D6.3D2.D2.3D6.D2.3D.3D6.D2.3D.3D5.
3D.D.D5.3D.3D5.3D.3D2.D7.D2.3D.3D6.D2.3D.3D5.3D.3D.3D5.3D.3D5.3D.3D5.
3D.3D6.D2.3D.3D6.D3.D2.3D6.D3.D2.3D5.3D.3D5.3D.3D6.D2.3D.3D5.3D.3D.3D
6.D2.3D.3D5.3D2.D2.3D5.3D.3D5.3D.3D6.D2.3D.3D5.3D.3D6.D2.3D.3D6.D2.3D
2.D6.3D.3D6.D2.3D.D.D6.D2.3D.3D5.3D.3D6.D2.3D.3D6.D2.D.D.3D5.3D.3D.3D
6.D2.3D.3D5.3D.3D5.3D.3D6.D2.D.D.3D6.D2.3D.3D6.D2.3D.3D5.3D.3D5.3D.3D
.3D6.D2.3D.3D5.3D.3D6.D2.D.D.D.D6.D2.3D.3D6.D2.3D.3D5.3D.3D5.3D.3D.3D
6.D2.3D.3D6.D2.3D.3D5.3D.3D6.D2.3D.D.D5.3D.3D5.3D.3D2.D6.3D.3D.3D5.3D
.3D2.D6.3D.3D.3D5.3D.3D6.D2.3D.3D6.D2.D.D.3D6.D2.3D.3D5.3D2.D7.D3.D2.
3D5.3D.3D6.D2.3D.3D6.D2.D.D.3D6.D2.3D.3D$4.E.D5.2D4.D.D7.2D2.D.D3.D5.
2D2.D.D.D.D5.2D2.D.D.D7.D.D.D.D5.D.D.2D6.D.D.D7.D.D.D.D5.2D2.D.D.D7.D
.D.D.D5.2D2.D.D.2D6.2D2.D.D.2D6.D.D.D.D5.2D2.D3.D.D5.D.D3.D5.2D2.D.D.
D7.D.D.D.D5.D.D.D.D5.2D2.D5.D5.2D4.D.D.D5.2D2.D.D.D.D5.D.D3.D5.2D2.D
3.2D6.2D4.D.2D6.2D2.2D2.D7.2D2.D.D.2D6.2D2.2D2.D.D5.2D2.2D2.2D6.2D2.
2D4.D5.D.D3.D5.2D4.D3.D5.D.D.D.D5.2D2.D.D3.D5.2D2.2D2.D.D5.2D2.D.D3.D
5.2D2.D5.D5.D.D.D.D5.D.D.D.D5.D.D.D.D5.D.D.2D6.D.D.2D8.D.D.D3.D5.2D2.
2D4.D5.2D4.D.D.D5.2D2.D.D.D.D5.D.D.D7.2D4.D3.D5.2D4.D.2D6.D.D.D.D7.D.
D5.D5.2D2.D.D.D.D5.D.D.D.D7.D.2D2.D.D5.2D2.D.D.D.D7.D3.D.D7.2D2.2D2.
2D6.2D2.2D2.D.D5.2D2.D.D.D.D5.D.D.D7.D.D.2D6.2D4.D.D.D7.D.D.D.2D8.D3.
D.2D8.D.2D2.D7.2D4.D.D.D5.2D4.D.D7.D.D.D.D5.D.D.D9.D.D.D.2D6.2D2.D.D.
D7.2D4.D.D.D7.D.D3.D.D5.D.D.D.D5.D.D.D.D5.D.D3.D5.2D4.D.D.D5.2D2.2D4.
D5.2D2.2D4.D5.D.D.D.D5.D.D.D7.2D2.D.D3.D7.D.D3.D.D5.2D4.D.D.D7.D.2D2.
D7.D.D.D.D5.D.D.D7.2D2.D.D3.D5.D.D.D.D5.2D2.D3.D7.2D2.D.D.2D6.D.D3.D
5.2D4.D.D.D5.2D4.D.D7.D.D.D.D5.2D2.D.D.D.D5.2D2.D.D.D.D7.D.D.D.D7.2D
2.D.D.D7.D.D.D7.D.D3.D5.2D2.D.D3.D5.2D4.D.D.D5.2D2.D.D3.D5.D.D.D.D7.D
3.D3.D5.2D2.D.D3.D5.D.D.D.D5.2D2.D.D.D.D5.2D2.D.D.D7.2D2.D.D3.D5.D.D.
D.D7.D3.D3.D5.2D2.D.D.D7.2D2.D.D3.D5.D.D.D.D5.2D2.D3.D.D5.D.D.D.D7.D.
D.D.2D8.D3.D.D9.D.D.D.2D8.D3.D.D.D5.D.D.D.D5.2D2.D.D.D.D5.2D2.D.D3.D
5.2D2.D.D.D7.D.D.2D6.2D2.2D4.D5.D.D.D.D5.2D2.D.D.D.D5.2D2.D.D3.D5.2D
2.D.D.D$4.DAD6.D2.3D.3D6.D2.D.D.3D6.D2.D.D.D.D6.D2.3D.3D5.3D.D.D5.3D
2.D6.3D.3D5.3D.3D6.D2.D.D.3D5.3D.D.D6.D2.D.D2.D7.D2.3D2.D6.3D.3D6.D2.
3D.3D5.3D.3D6.D2.3D.3D5.3D.3D5.3D.D.D6.D2.3D.3D6.D2.3D.3D6.D2.3D.3D5.
3D.3D6.D2.3D2.D7.D2.3D2.D7.D3.D2.3D6.D2.D.D2.D7.D3.D2.3D6.D3.D3.D7.D
3.D2.3D5.3D.3D6.D2.3D3.D5.3D.3D6.D2.D.D.3D6.D3.D2.3D6.D2.D.D3.D6.D2.
3D3.D5.3D.D.D5.3D.D.D5.3D.D.D5.3D2.D6.3D2.D6.3D.3D.3D6.D3.D2.3D6.D2.
3D.3D6.D2.D.D.3D5.3D.3D6.D2.3D3.D6.D2.3D2.D6.3D.3D5.3D.3D3.D6.D2.3D.
3D5.3D.3D5.3D2.D2.3D6.D2.3D.3D5.3D.3D.3D6.D3.D3.D7.D3.D2.3D6.D2.D.D.D
.D5.3D.3D5.3D2.D7.D2.3D.3D5.3D.D.D2.D6.3D.3D2.D6.3D2.D2.3D6.D2.3D.3D
6.D2.3D.3D5.3D.3D5.3D.3D5.3D.D.D2.D7.D2.3D.3D6.D2.3D.3D5.3D.3D.3D5.3D
.D.D5.3D.D.D5.3D.3D6.D4.D.D.D6.D3.D4.D6.D3.D2.3D5.3D.D.D5.3D.3D6.D2.
3D3.D5.3D.3D.D.D6.D2.3D.D.D5.3D2.D2.3D5.3D.D.D5.3D.3D6.D2.3D3.D5.3D.D
.D6.D2.3D.3D6.D2.3D2.D6.3D.3D6.D2.3D.3D6.D2.3D.3D5.3D.D.D6.D2.D.D.3D
6.D2.3D.D.D5.3D.D.D.3D6.D2.3D.3D5.3D.3D5.3D.3D6.D2.3D3.D6.D2.3D.3D6.D
2.D.D.3D5.3D.D.D5.3D.3D.3D6.D2.D.D.3D5.3D.D.D6.D2.3D.3D6.D2.3D.3D6.D
2.D.D.3D5.3D.D.D5.3D3.D3.D6.D2.3D.3D6.D2.D.D.3D5.3D.D.D6.D2.3D.3D5.3D
.3D5.3D.D.D2.D6.3D.3D.3D5.3D.D.D2.D6.3D.3D.3D5.3D.D.D6.D2.D.D.3D6.D2.
3D.3D6.D2.D.D.3D5.3D2.D7.D3.D4.D5.3D.D.D6.D2.D.D.3D6.D2.3D.3D6.D2.D.D
.3D$4.D.D6.D2.D3.D.D6.D2.D.D.D8.D2.D.D.D.D6.D4.D3.D7.D.D.D7.D2.D8.D3.
D7.D.D.D6.D2.D.D3.D7.D.D.D6.D2.D.D2.D7.D4.D2.D8.D3.D6.D4.D3.D7.D.D8.D
4.D.D.D7.D3.D7.D.D.D6.D4.D.D8.D4.D3.D6.D4.D3.D7.D.D8.D2.D.D2.D7.D4.D
2.D7.D3.D2.D.D6.D2.D.D2.D7.D3.D4.D6.D3.D3.D7.D3.D2.D9.D3.D6.D2.D4.D8.
D.D.D6.D2.D.D.D8.D3.D4.D6.D2.D.D2.D7.D4.D2.D8.D.D.D7.D.D.D7.D.D.D7.D
2.D8.D2.D6.D5.D3.D6.D3.D4.D6.D4.D3.D6.D2.D.D.D.D7.D3.D6.D2.D4.D7.D2.D
4.D8.D3.D5.D5.D2.D7.D4.D3.D7.D3.D5.D4.D2.D.D6.D4.D.D.D5.D3.D3.D.D6.D
3.D3.D7.D3.D4.D6.D2.D.D.D.D7.D3.D7.D2.D7.D4.D.D.D5.D3.D.D2.D6.D3.D4.D
6.D4.D2.D.D6.D4.D.D.D6.D2.D5.D7.D3.D7.D.D.D5.D3.D.D2.D7.D4.D.D.D6.D2.
D3.D.D5.D5.D.D.D7.D.D.D7.D.D.D7.D.D8.D3.D2.D.D6.D3.D3.D7.D3.D4.D7.D.D
.D7.D3.D6.D4.D2.D6.D5.D.D.D6.D2.D3.D.D5.D4.D2.D.D7.D.D.D7.D3.D6.D2.D.
D2.D8.D.D.D6.D2.D.D3.D6.D2.D.D2.D8.D.D8.D4.D3.D6.D2.D3.D.D7.D.D.D6.D
2.D.D3.D6.D4.D.D.D5.D3.D.D3.D6.D2.D.D.D.D7.D.D.D7.D.D8.D4.D2.D7.D2.D
5.D6.D2.D.D.D9.D.D.D5.D5.D.D8.D2.D.D.D9.D.D.D6.D4.D3.D6.D4.D3.D6.D2.D
.D.D9.D.D.D5.D4.D3.D7.D4.D3.D6.D2.D.D.D9.D.D.D6.D4.D3.D7.D3.D5.D3.D.D
2.D8.D3.D3.D5.D3.D.D2.D6.D3.D5.D7.D.D.D6.D2.D.D3.D6.D4.D3.D6.D2.D.D.D
.D7.D2.D7.D3.D3.D8.D.D.D6.D2.D.D3.D6.D4.D3.D6.D2.D.D.D.D$4.3D5.3D.3D.
3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.2D6.3D.3D5.3D.3D5.3D.2D6.3D.3D5.3D.3D.
2D6.3D.3D5.3D.3D.3D5.3D3.D.3D5.3D3.D5.3D.2D2.3D5.3D.3D5.3D3.D.3D5.3D.
3D5.3D.3D5.3D.2D2.3D5.3D.3D.3D5.3D3.D3.D5.3D.3D5.3D.3D.3D5.3D.3D.3D5.
3D.3D.3D5.3D.3D.3D5.3D.3D3.D5.3D.3D.3D5.3D.3D.3D5.3D.3D5.3D.3D2.D6.3D
.3D5.3D.3D.3D5.3D.3D3.D5.3D.3D2.D6.3D.2D3.D6.3D.3D5.3D.3D5.3D.3D5.3D.
3D5.3D.3D5.3D3.D.3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.3D5.3D.2D6.3D.3D2.D6.
3D.3D.3D5.3D.3D5.3D.2D3.D6.3D3.D3.D5.3D3.D5.3D.3D.3D5.3D3.D.3D5.3D.3D
.3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.3D5.3D.2D6.3D.3D5.3D.3D.3D5.3D.3D.3D5.
3D.3D.3D5.3D.3D.3D5.3D.3D.3D5.3D.3D.2D6.3D3.D5.3D.3D5.3D.3D.3D5.3D.3D
.3D5.3D.3D.3D5.3D.2D2.3D5.3D.3D5.3D.3D5.3D.3D5.3D2.D2.3D5.3D.3D2.D6.
3D.3D.3D5.3D.3D5.3D.2D6.3D.3D2.D6.3D.2D2.3D5.3D.3D.3D5.3D.3D.3D5.3D.
3D5.3D.2D6.3D.3D2.D6.3D.3D5.3D.3D.2D6.3D.3D.3D5.3D.3D5.3D.3D3.D5.3D.
3D.3D5.3D.3D5.3D.3D.3D5.3D3.D.3D5.3D.3D.2D6.3D.3D.3D5.3D.3D5.3D.3D5.
3D3.D2.D6.3D.3D.3D5.3D.3D.3D5.3D.3D5.3D.3D.3D5.3D.3D.3D5.3D.3D5.3D3.D
3.D5.3D.3D.2D6.3D.3D.3D5.3D.3D5.3D2.D3.D6.3D.3D.2D6.3D.3D.3D5.3D.3D5.
3D.2D4.D5.3D.3D5.3D.3D.3D5.3D.3D.2D6.3D.3D.3D5.3D.3D.3D5.3D.3D5.3D.3D
.3D5.3D3.D.3D5.3D.3D.3D5.3D.3D5.3D.3D2.D6.3D.3D5.3D.3D.3D5.3D3.D.3D5.
3D.3D.3D27$36.2A$36.A.A$36.A24$61.3A$61.A$62.A23$86.3A$86.A$87.A46$
136.A$135.2A$135.A.A21$158.2A$157.2A$159.A21$180.3A$180.A$181.A21$
205.A$204.2A$204.A.A23$229.2A$228.2A$230.A24$255.2A$255.A.A$255.A21$
277.3A$277.A$278.A23$303.2A$302.2A$304.A33$338.2A$338.A.A$338.A22$
361.3A$361.A$362.A37$402.A$401.2A$401.A.A21$425.A$424.2A$424.A.A35$
461.2A$460.2A$462.A23$485.3A$485.A$486.A20$508.2A$508.A.A$508.A36$
546.2A$546.A.A$546.A33$581.2A$580.2A$582.A34$617.2A$616.2A$618.A21$
640.2A$639.2A$641.A38$680.2A$680.A.A$680.A31$713.2A$712.2A$714.A27$
742.2A$741.2A$743.A23$767.2A$767.A.A$767.A27$795.3A$795.A$796.A25$
824.A$823.2A$823.A.A26$852.A$851.2A$851.A.A22$874.3A$874.A$875.A29$
907.A$906.2A$906.A.A23$931.2A$930.2A$932.A23$957.A$956.2A$956.A.A27$
985.2A$984.2A$986.A25$1011.3A$1011.A$1012.A37$1051.2A$1050.2A$1052.A
20$1074.A$1073.2A$1073.A.A21$1096.2A$1095.2A$1097.A20$1119.A$1118.2A$
1118.A.A21$1141.2A$1141.A.A$1141.A21$1164.2A$1163.2A$1165.A59$1224.3A
$1224.A$1225.A26$1253.2A$1252.2A$1254.A33$1287.3A$1287.A$1288.A25$
1314.3A$1314.A$1315.A21$1339.A$1338.2A$1338.A.A30$1370.2A$1370.A.A$
1370.A28$1401.A$1400.2A$1400.A.A23$1425.2A$1425.A.A$1425.A62$1490.A$
1489.2A$1489.A.A34$1526.A$1525.2A$1525.A.A22$1549.2A$1548.2A$1550.A
52$1604.A$1603.2A$1603.A.A35$1641.A$1640.2A$1640.A.A55$1697.2A$1696.
2A$1698.A26$1724.3A$1724.A$1725.A27$1755.A$1754.2A$1754.A.A23$1780.A$
1779.2A$1779.A.A22$1803.2A$1803.A.A$1803.A21$1826.2A$1825.2A$1827.A
32$1861.A$1860.2A$1860.A.A49$1910.3A$1910.A$1911.A53$1966.2A$1965.2A$
1967.A52$2020.2A$2019.2A$2021.A32$2055.A$2054.2A$2054.A.A30$2085.3A$
2085.A$2086.A21$2109.2A$2109.A.A$2109.A22$2133.2A$2133.A.A$2133.A48$
2184.A$2183.2A$2183.A.A47$2233.A$2232.2A$2232.A.A30$2265.A$2264.2A$
2264.A.A63$2329.2A$2328.2A$2330.A20$2352.A$2351.2A$2351.A.A21$2374.2A
$2373.2A$2375.A21$2397.2A$2396.2A$2398.A40$2440.A$2439.2A$2439.A.A28$
2468.3A$2468.A$2469.A26$2497.2A$2496.2A$2498.A20$2520.A$2519.2A$2519.
A.A22$2543.2A$2543.A.A$2543.A47$2593.A$2592.2A$2592.A.A61$2655.2A$
2654.2A$2656.A28$2685.2A$2684.2A$2686.A52$2739.2A$2738.2A$2740.A20$
2762.A$2761.2A$2761.A.A22$2785.2A$2785.A.A$2785.A45$2832.2A$2831.2A$
2833.A20$2855.A$2854.2A$2854.A.A40$2895.3A$2895.A$2896.A43$2941.2A$
2940.2A$2942.A21$2964.2A$2963.2A$2965.A31$2998.A$2997.2A$2997.A.A30$
3029.2A$3028.2A$3030.A20$3052.A$3051.2A$3051.A.A26$3078.3A$3078.A$
3079.A33$3113.3A$3113.A$3114.A49$3165.2A$3164.2A$3166.A44$3212.A$
3211.2A$3211.A.A22$3236.A$3235.2A$3235.A.A21$3259.A$3258.2A$3258.A.A
35$3295.2A$3295.A.A$3295.A30$3328.A$3327.2A$3327.A.A24$3353.2A$3352.
2A$3354.A20$3376.A$3375.2A$3375.A.A56$3434.A$3433.2A$3433.A.A24$3459.
2A$3458.2A$3460.A20$3482.A$3481.2A$3481.A.A34$3518.A$3517.2A$3517.A.A
47$3566.2A$3566.A.A$3566.A24$3591.3A$3591.A$3592.A20$3614.2A$3614.A.A
$3614.A67$3684.A$3683.2A$3683.A.A47$3732.2A$3732.A.A$3732.A24$3757.3A
$3757.A$3758.A20$3780.2A$3780.A.A$3780.A37$3818.3A$3818.A$3819.A22$
3844.A$3843.2A$3843.A.A49$3893.3A$3893.A$3894.A81$3978.A$3977.2A$
3977.A.A49$4027.3A$4027.A$4028.A55$4085.2A$4084.2A$4086.A20$4108.A$
4107.2A$4107.A.A26$4134.3A$4134.A$4135.A33$4171.A$4170.2A$4170.A.A25$
4197.2A$4196.2A$4198.A21$4219.3A$4219.A$4220.A27$4249.2A$4248.2A$
4250.A20$4272.A$4271.2A$4271.A.A26$4298.3A$4298.A$4299.A33$4335.A$
4334.2A$4334.A.A25$4361.2A$4360.2A$4362.A!
It's not automatic, but if you have a recipe that fails because the gliders are too close to the gun, you can add these recipe definitions and just swap the original g1y(d-26) for g1_edgiest(d-26), for example.
russellsprouts
Posts: 69
Joined: January 28th, 2026, 10:04 am

Re: Optimized Snarkmaker

Post by russellsprouts »

Hippo.69 wrote: September 22nd, 2026, 1:21 am Actually I would like another piece in the kit:(.

In the 0e0pmetacell the last OGGCA is in really big distance. It would be waste of resources to convert it to speboe and start snark destruction by converting the speboe to hd0 backward glider ... and waiting till the glider returns.

Much more effificent method is stopping OGGCA and using escaping gliders to send backward glider to destroy snark while the salvo destroying the OGGCA would be on the way (actually it could be sent even before sending the stop glider) . The backward glider should pass by the destroying salvo, so it should be at hd with absolute value at least 8.
We could use an inserter seed like this to send the reverse glider (is there a 2 object OTT for this?).

Code: Select all

x = 39, y = 28, rule = B3/S23
7$18bo$19bo5bo$17b3o4bobo$25bobo$26b2o$31b2o$30bo2bo$30bo2bo$31b2o6$
30b2o$29bobo$30bo!
We would send the disable_arm recipe as early as possible, so that we use up most of the gun gliders finishing our recipe. Since the gun is so far away, there will be a lot of gliders in flight, and we'll be able to do a bunch of recipes even after we send the disable recipe. The last few gun gliders should build the inserter seed and start to trigger it. At the end there may still be 3 or 4 extra gun gliders to absorb which we can do with swim upstream recipes.

The inserter trigger needs to be delayed enough that we can use up the last gun gliders and send the 18 glider disabled arm delete recipe before the inserter is triggered. The simplest method would be to build a 180 degree OTT far upstream, so that the trigger glider goes up and back while the 18 gliders go past.
User avatar
Hippo.69
Posts: 696
Joined: July 14th, 2020, 7:35 pm
Contact:

Re: Optimized Snarkmaker

Post by Hippo.69 »

russellsprouts wrote: September 23rd, 2026, 3:39 am We could use an inserter seed like this to send the reverse glider (is there a 2 object OTT for this?).

Code: Select all

x = 39, y = 28, rule = B3/S23
7$18bo$19bo5bo$17b3o4bobo$25bobo$26b2o$31b2o$30bo2bo$30bo2bo$31b2o6$
30b2o$29bobo$30bo!
We would send the disable_arm recipe as early as possible, so that we use up most of the gun gliders finishing our recipe. Since the gun is so far away, there will be a lot of gliders in flight, and we'll be able to do a bunch of recipes even after we send the disable recipe. The last few gun gliders should build the inserter seed and start to trigger it. At the end there may still be 3 or 4 extra gun gliders to absorb which we can do with swim upstream recipes.

The inserter trigger needs to be delayed enough that we can use up the last gun gliders and send the 18 glider disabled arm delete recipe before the inserter is triggered. The simplest method would be to build a 180 degree OTT far upstream, so that the trigger glider goes up and back while the 18 gliders go past.
I will think about ... as there are no suffciently low hanging friuts to do the job my way. ...

OK 0e0p situation is following:
The input goes from OSSSE to turn at OSSSS through the turn at OSSSW to OGGCA at ONEEE.
We would send OGGCA stopping signal probably from OSE (may be even earlier from somewhere between OSE and OEE), we would build (rest of) ONNWW and ONW using the escaping gliders and ONWWW, OWW, OSS, and OSSSE as well (including main clock activation). Then OGGCA and the snarks should disappear. The OSSSW snark could disapear just after the salvo destroying OGGCA is turned by it.
We can achieve this by using 13hd shifted backward glider and convert the OGGCA snark to 1hd shifted backward glider ... and convert OSSSS snark to empty using it.

The solution with ott0 redirecting a backward glider to 0hd shifted backward glider requires building the seed .. as close to OSSSW snark.
But the OGGCA would operate only in range SS to NEEE, so I would need to build the seed without using this OGGCA.
Buildng the seed at SS would mean significant delay (and bulding and destroying extra OGGCA at OSSSW would be cheaper, as well as converting the snark to SPEBOE and then to hd0 backward glider).

In the case some more complicated scenario then 0e0p appears, reduction of snark activated by backward glider hd(x) with |x|>=8 to hd0 backward glider would be very nice ... as it would chain to destruction of an arbitrary sequence of snarks. ... and I have not found it yet ...

S far I have found g-8, g11, g-4 and g12 (except the hd13 already mentioned cases
Hippo.69 wrote: September 17th, 2026, 2:45 am
) (and we need g1).

EDIT: I got several more reductions to gliders, but still without g1 :(

EDIT: Still without g1, but we have HD-8 glider to speboeglider reduction, which could be nice destruction sequence end. (It would be definitely more interesting to have it for HD0 glider)

Code: Select all

x = 128, y = 132, rule = LifeHistory
11.2A$10.A.A$4.2A4.A$2.A2.A2.2A.4A$2.2A.A.A.A.A2.A$5.A.A.A.A$5.A.A.2A
$6.A2$2A17.2A$.2A7.2A7.A$A9.2A5.A.A$17.2A7$7.2A$8.A$5.3A8.3A$5.A10.A$
17.A6$48.3D.3D.3D6.D2.3D.3D6.D2.3D.3D$50.D.D.D3.D5.2D2.D.D3.D5.2D4.D.
D$48.3D.D.D3.D6.D2.D.D.3D6.D2.3D.3D$48.D3.D.D2.D7.D2.D.D3.D6.D2.D5.D$
48.3D.3D2.D6.3D.3D.3D5.3D.3D.2D39$69.A$68.2A$68.A.A24$94.2A$94.A.A$
94.A29$126.A$125.2A$125.A.A!
... so close:

Code: Select all

x = 104, y = 107, rule = LifeHistory
13.2A$12.A.A$6.2A4.A$4.A2.A2.2A.4A$4.2A.A.A.A.A2.A$7.A.A.A.A$7.A.A.2A
$8.A2$21.2A$.2A9.2A7.A$A.A9.2A5.A.A$2.A16.2A7$9.2A$10.A$7.3A$7.A$22.A
$21.2A$21.A.A4$51.D2.D.D.3D5.3D.3D5.3D.3D$50.2D2.D.D3.D5.D.D.D.D5.D.D
.D.D$51.D2.3D.3D5.3D.D.D5.3D.D.D$51.D4.D.D9.D.D.D7.D.D.D$50.3D3.D.3D
5.3D.3D5.3D.3D26$57.2A$56.2A$58.A20$80.A$79.2A$79.A.A21$102.2A$101.2A
$103.A!
I6_I6 wrote: September 22nd, 2026, 1:31 am Are you planning on implementing Hippo.69's "edgy left emissions"? If not, what's the closest lane to the simkin gun that you can fire from using only the default recipes?
russellsprouts wrote: September 22nd, 2026, 10:06 pm I've transcribed the recipes into the recipe format:
It's not automatic, but if you have a recipe that fails because the gliders are too close to the gun, you can add these recipe definitions and just swap the original g1y(d-26) for g1_edgiest(d-26), for example.
My current SimkinEcca.txt looks like this:

Code: Select all

--[phase%8] exact delay (minimal delay of followup) -- simkin gliders, direction, (simkin) delay, lane (relative to std collision) --name
--I made the right coordinates to have the opposite color then left coordinates (glider crossing the arm lane has shadow with the same coordinate on both sides)
+1       -- 0  -- p  -- A*
[0] (90) -- 1  -- m0 -- B*
[2] (90) -- 2  -- m2 -- C*
[4] (90) -- 1  -- m4 -- D*

--[3] 198 105 (90)      -- 3;l210,10  -- l00 -- plan to replace V ... very small improvement to be worth resyncing
[3] 96 143 130 (90)   -- 4;l352,-16 -- l00a -- E*
[3] 196 128 (90)      -- 4;l238,-10 -- l00b -- F*
[3] 185 129 (90)      -- 4;l246,-6  -- l00c -- G*
[1] 92 134 (90)       -- 3;l275,-12 -- l01a -- H*
[1] 100 137 115 (105) -- 4;l442,-7  -- l10a -- I*
[5] 144 132 107 (90)  -- 4;l384,3   -- l10b -- J*
[7] 162 110 195 (90)  -- 4;l340,27  -- l10c -- K**-
[3] 96 92 147 (90)    -- 4;l337,-15 -- l11a -- L*
[1] 92 163 (150)      -- 4;l273,-5  -- l11b -- M*
[5] 138 125 (90)      -- 3;l151,-1  -- l11c -- N*

[1] 96 224 96 (150)   -- 6;l227,-6  ;l587,-16 -- ll0_-10 -- O*
[3] 196 136 211 (190) -- 7;l238,-10 ;l586,-17 -- ll0_-7  -- P*
[1] 96 240 96 (150)   -- 6;l227,-6  ;l587,-12 -- ll0_-6  -- Q*
[3] 196 147 147 (171) -- 7;l238,-10 ;l651,-8  -- ll0_2   -- R*
[3] 196 152 108 (90)  -- 5;l238,-10 ;l455,-3  -- ll0_7   -- S*
[3] 196 146 130 (178) -- 6;l238,-10 ;l452,3   -- ll0_13  -- T*
[3] 196 171 212 (90)  -- 6;l238,-10 ;l480,7   -- ll0_17  -- U*
[3] 165 101 103 (526) -- 8;l323,-16 ;l506,12  -- ll0_28  -- V-
[5] 138 119 163 (336) -- 7;l151,-1  ;l455,-10 -- ll1_-9  -- W*
[5] 138 (121)         -- 3;l151,-1,-2 ;l      -- ll1     -- X*
[0] 221 (90)          -- 2;l476,-2  ;l        -- ll1_o   -- Y*
[0] 219 (90)          -- 2;l478,-3  ;l        -- ll1_e   -- Z*

[3] 102 90 (90)        -- 3;r148,-4  -- r00a -- a*
[3] 212 96 (104)       -- 4;r267,22  -- r01a -- b*
[3] 104 129 97 (90)    -- 3;r249,22  -- r01b -- c*
[3] 105 233 (90)       -- 4;r357,-2  -- r01c -- d*
[7] 170 94 92 (90)     -- 4;r333,8   -- r01d -- e*
[7] 148 106 (90)       -- 3;r264,7   -- r10a -- f*
[3] 201 (167)          -- 4;r232,-9  -- r10b -- g*
[3] 217 129 93 (90)    -- 4;r373,9   -- r11b -- h*
[3] 159 122 95 (90)    -- 4;r363,13  -- r11a -- i*
[7] 164 182 133 (90)   -- 4;r381,41  -- r11c -- j*

[3] 102 136 123 (162)  -- 5;r148,-4 ;r363,0 -- rr0_4   -- k*
[3] 163 250 226 (413)  -- 9;r401,6  ;r988,8 -- rr0_2   -- l-
[7] 156 222 113 (486)  -- 8;r363,0 ;r770,17 -- rr0_17  -- m-

[3] 225 137 148 (90)   -- 5;b37,4    -- br  -- n*
[7] 252 93 173 (289)   -- 6;b-37,35  -- bl  -- o

[3] 195 96 90 179 195 104 161 (90) -- 9;A,0 -- EEEE
[7] 130 236 270 96 152 268 (90) -- 10;B,0 -- EEEESoDpart
[2] 96 96 90 128 131 92 90 109 90 93 91 90 90 90 (90) -- 20;C,0 -- destroyal
[7] 132 230 151 563 219 107 90 95 207 91 90 91 161 (90) -- 21;D,0 -- eaterseed
[5] 144 165 125 (112) -- 5;E,0 -- eater_seed_activation
[3] 95 177 90 154 129 165 164 97 177 225 202 127 91 97 90 109 106 95 92 135 224 154 175 147 379 201 343 202 253 103 97 153 102 90 312 201 207 217 136 131 91 129 200 106 91 312 159 130 200 90 214 103 (90) -- 72;F,0 -- SoD
[3] 98 90 115 291 93 169 109 112 90 183 92 134 95 213 140 218 124 162 103 258 202 235 92 134 93 115 216 109 168 99 106 116 92 267 193 152 92 233 144 100 96 90 111 163 200 170 93 140 90 104 111 191 223 201 92 134 200 102 90 118 92 225 194 93 140 97 96 177 215 102 114 220 307 92 267 127 102 91 107 219 100 95 118 130 115 127 (144) -- 109;G,0 -- NEEE_NNNE
[7] 136 155 105 124 (90) -- 5;H,0 -- block_l-27
--[3] 198 105 (90)      -- 3;I,10 -- another_L00_glider replaced by LH
[3] 118 114 222 90 92 144 (132) -- 8;J,0 -- block_l-43
[1] 96 138 298 434 97 107 201 279 201 111 102 90 344 201 269 90 109 143 106 97 185 201 279 201 255 201 295 102 90 192 201 177 132 90 129 97 142 102 90 95 123 132 90 129 97 182 201 263 102 90 224 201 295 102 90 128 201 223 102 90 230 90 109 143 106 97 121 102 90 90 90 220 134 90 96 123 232 97 178 92 174 94 94 134 224 121 120 93 93 160 93 163 (124) -- 120;K,0 -- cleanup
[1] 90 91 295 (90) -- 5;L,0  -- boato1l-22
[1] 90 90 92 170 117 (90) -- 6;LA,-34 -- L01_glider_from-54
[1] 90 95 197 250 120 (90) -- 8;LB,-33 -- L10_glider_from-65
[1] 90 95 187 90 165 (90) -- 7;LC,-25 -- L11_glider_from-55
[3] 196 128 (90)      -- 4;LD,-10 -- L00_glider_from-56
[1] 92 134 (90)       -- 3;LE,-12 -- L01_glider_from-50
[1] 90 109 140 205 (167) -- 6;LF,-17 -- L10_glider_from-57
[3] 96 92 147 (90)    -- 4;LG,-15 -- L11_glider_from-53
[3] 198 105 (90)      -- 3;LH,10 -- L00_glider_nearSnark
[3] 96 91 91 96 (321) -- 6;LI,34 -- L01_glider_nearestSnark
[1] 100 137 115 (105) -- 4;LJ,-7 -- L10_glider_from-43
[5] 138 125 (90)      -- 3;LK,-1 -- L11_glider_fast
[1] 108 109 109 152 144 (241) -- 8;LL,22 -- L00_glider_nearestSnark
[3] 207 199 100 127 (192) -- 8;LN,29 -- L10_glider_nearestSnark
[1] 92 163 (150)      -- 4;LO,-5 -- L11_glider_fastnearSnark
[1] 93 92 229 91 (141)       -- 5;LS,23 -- L11_glider_nearerSnark
[1] 90 109 240 109 166 (90)  -- 7;LW,25 -- L11_glider_nearestSnark
[3] 111 230 226 115 138 91 (132) -- 10;M,0 -- block_l_31
[3] 225 152 159 226 171 285 142 222 108 92 (90) -- 15;N,0 -- near_arm_subrecipe
[3] 147 156 90 91 146 90 109 134 (90) -- 9;O,0 -- beehive_pond_SoD
[7] 136 155 105 124 164 109 112 90 (90) -- 9;P,0 -- blinkerl-19blockl-27
[3] 195 128 137 211 96 131 138 120 90 97 123 138 120 90 91 97 138 120 90 97 155 138 120 90 97 171 138 120 90 (90) -- 30;Q,0 -- boato0l-43
[3] 202 177 128 (90) -- 5;R,0 -- boato3l-12
[3] 195 102 90 (152)    -- 5;RA,-14 -- R11_glider_from-46
[3] 201 (167)           -- 4;RB,-9  -- R10_glider_from-45
[1] 90 109 143 106 (90) -- 4;RC,-11 -- R01_glider_from-45
[3] 102 90 (90)         -- 3;RD,-4  -- R00_glider_from-46
[7] 180 204 96 (90) -- 5;S,0 -- stop_simkin
-- it should be sent correctly as 79 mod 120 (at 18+30K position increasing K by 1 increases number of escaping gliders by 1)
[0] 91 90 104 121 121 96 99 112 94 102 94 115 235 106 118 90 208 (90) -- 1;T,0 -- remove_stopped_simkin
[0] 91 90 104 121 121 96 99 112 94 102 94 115 231 111 118 91 92 108 129 90 93 91 118 93 151 90 99 155 120 92 108 90 102 164 90 96 (90) -- 1;TG,0 -- replace_stopped_simkin_by_backward_glider
[0] 91 90 104 121 121 96 99 112 94 102 94 115 231 111 118 91 92 108 129 (90) -- 1;TS,0 -- replace_stopped_simkin_by_speboe
-- requires one escaping glider (sync mod 2)
[3] 202 167 179 129 (90) -- 6;U,0 -- boato2l-7
[3] 163 225 294 90 90 98 106 138 125 (90) -- 12;V,0 -- cross_checkpoint_activation
[5] 151 199 210 181 (148) -- 8;VA,0 -- checkpoint_activation
-- activation should be 80 mod 120 later than seed (V position should be among 30X+Wposition+{20,22,28,30,32} ... X gliders would escape)
[5] 144 165 125 115 177 225 90 107 206 156 96 104 (90) -- 15;W,0 -- checkpoint_seed
[3] 91 166 133 100 140 (97) -- 8;X,0 -- blockl-25
[3] 101 200 192 90 159 92 134 119 183 100 137 115 (105) -- 15;Y,0 -- ship_blinker180_ots
[3] 197 194 (90) -- 4;Z,0 -- boato2l-8
[3] 197 194 124 (90) -- 6;ZA,0 -- boato2l-8delay
[5] 144 107 201 91 (90) -- 5;AA,0 -- glider_f-9

--[3] 93 177 112 110 (90) -- 4;N210,31 -- another_r11_glider
--[3] 219 113 107 (90) -- 4;O210,13 -- another_r10_glider
[3] 93 118 183 (106) --  4;GR,22 -- another_r01_glider
-- [3] 174 138 657+240*n 104 (90) cap uncap ... for state reading tape
-- [5=K%120] 144 165 125 115 177 225 90 107 206 156 96 104
-- construction (290) [(20+K)%120] 151 199 210 181 let A simkin gliders escape) for 290 + (A*120)
-- [(80+K)%120] release

-- [1] 104 93 129 (90)                  -- lwss RF
-- [7] 194 104 93 (90)                  -- lwss RF
-- [3] 219 108 132 183 93 (90)          -- lwss RB
-- [1] 118 136 147 111 (90)             -- lwss LBO
-- [3] 128 136 186 155 102 97 129 (130) -- lwss LF7
-- [3] 184 131 239 (201)                -- mwss RBO
-- [3] 225 194 140 208 (284)            -- mwss RFO

-- 79 (mod 120), 180, 204, 96, (90) -- disable (rather to anchor) (-2 0degree)
-- 126 102 100 195 90 91 95 98 105 90 101 141 94 159 92 146 99 90 152 139 144 92 161 131 116 101 114 111 112 93 127 98 102 114 107 157 90 90 90 91 91 243 113 139 108
-- 95 127 121 99 257 144 94 218 148 226 111 119 100 (90) -- build with in envelope cleanup (including NW boat cleanup)
-- [2] 91 138 289 221 -- cleanup leaving block target
-- mode sc, 0, 126, 102, 100, 195, 90, 91, 95, 98, 105, 90, 101, 141, 94, 159, 92, 146, 99, 90, 152, 139, 144, 92, 161, 131, 116, 101, 114, 111, 112, 93, 127, 98, 102, 114, 107, 157,
-- 90, 90, 90, 91, 91, 243, 113, 139, 108, 95, 127, 121, 99, 257, 144, 94, 218, 148, 226, 111, 119, 100, 95, 91, 138, 201, 221, 216, 138, 125, mode p120, set 28 (mod 120), (90) -- build
With it the b/e/o emissions are automated (optimalized), and you can use for example LB in the arm proximty ...
... here is portion of the lanephase program doing part of ONNNN:

Code: Select all

--salvoB start 
o106 o114 b115|b116|b119 e106 b98 -- *
o98 o100 e84 b103 b82 --
o87 b79 e83 o80 
-- NNNE interruption to clean the lane (61-63) ... should go somewhere to let -61<= l <=69 and 90 <= l clean. ... (75-77)
b68 b65|b70 o65 e60 o56 o65 o69 o61 b76|b77|b78|b79|b80|b81 e80 -- *
-- NNNE interruption end
o66 b69 e60 b50 -- *
o46 e40 b34|b35|b36|b37 b39 b15 b13|b18 -- *
o13 e21 e9 b4 b-12|b-11|b-10|b-9|b-8 -- *
e0 e-8 o-5 o12 b16 o12 e-3 b-3 b-3 b-10 b-12 -- *
o7 e-4 o-17 o-10 b-15 b-6 b-9|b-4 b-16 -- *
LG-19 e-31|b-30|e-29|b-28 LG-29 e-23|b-20|e-19 o-26 LG-39 LE-40 LE-44
LE-44 LE-40 LD-32 LG-41 LE-42 LD-44 LE-48 LD-54 LB-61 LF-57 e-16 b3 b2 o-3 e4|e5 b9
e12 e20 o9 e8 e2 e10|e11|o6|o7 e11 o8 b30|b31|b34 e43 o41 o41
b45|b46|b47|b48|b49|b50 b51 b51 b59 o51 b28|b29|b30|b31|b32|b33 o22 e15 e26
e27 e23 o27 o16 o15 e15 b10 e15 o11|o13 o-3 e-11|e-10|o-7|o-6 e-13 o-18 e-13 o-2 b-9|b-5 b-5 b-11|b-8|b-3|b3 b-5|b-4|b-3|b-2|b-1 b39|b40|b41|b42|b43 b36 -- forced
o32 e26 b28|b29|b30|b31|b32 b32|b33|b34|b35|b36 b47 b44|b49 b51 b60 -- *
o42 o42 e52 o50 o50 b36 b39 b29|b30|b33 b29 -- *
o25 o30 -- *
o25 o15 o18 e28 o25 b90|b97|b100|b101 b119 b128 -- *
o131 e138 e129 b167 -- *
o158 o168 o170 b200 -- *
e203 o210 b218 b237 b226 b260 e259 o271 b280
b280 b287 b281 e279 b254|b265 b238 b227 b230 e227 b236|b237|b238|b239|b240 o229 e245 e217 o213
o223 b220 b206 o201 b166 b164|b169 e164 e167 b164 b168 b168 b180 b177|b182 e177 o176 e173
o164|o165|e168|e169 o178 b180|b181|b182|b183|b184 b181 b173 o167 b171 b172 b169|b170|b171|b172
e185 e183 b177 b180|b181 e194 b192 e188 e195 e183 o187 o193 o188 o192 b192 b190|b195 e195
o199|o200|o201|e202|e203|e204 o198 e195 o195 e192 e198 e161|e168 e169 e159 o170 e165 e154 o158
e151 o140 o162 e193 b193|b194 b195 b193 b207 e208 o196 o210 b216|b219|b220 o208 b216 e220 b212
e219 e219 e216 e201 b200 b197 e200 o193 e197 e191 o191 b188 b212 b212 e208 o205|o206|e201|e202
e200 e201 e192 b179|b180|b181|b182|b183 o190 o200 e201 e192 e165 e169 e178 b176 e179
o181|o182|e185|e186 o186 b166|b167|b168|b169|b170 e177 o165 e176 b167 o209 o214
b214|b221|b224|b225 b261 e257 o260 b262 b271 o274 o283 o286 e304|e305|e312 o301 b331 -- fixed! (1st blinker) (could be split to independent parts, but they intermix)
o331 b335 b342|b345|b346 o336 o320 e341 o367 b375 -- *
o371 o373 o376 b402 b391 b396|b397|b398|b399|b400|b401 b425 b417 -- *
o429 o422 o426 o435 e441 e431 b428 -- *
o429 o414 e437 w1 o441 o445|o446|o447|e442|e443|e444 b439 -- *
The * mean I have already chosen cheaper phase for p1->p1 transition. w1 ... had to delay the too fast glider by 1*120 ticks.
I am trying to maintain https://github.com/Hippo-69/CGoL_0e0p_optimization updated.

Code: Select all

class SimkinECCAp2compiler(object):

    def load_recipes(self):
        recipe_l = 64
        with open('data\\SimkinECCA.txt') as f:
            rows=f.read().split("\n")
            for row in rows:
                #print (row)
                sections = row.split("--")
                if sections[0]!="":
                    recipe_l += 1
                    while chr(recipe_l) in '':
                        recipe_l += 1
                    name = sections[2].strip()
                    self.name2l[name] = chr(recipe_l)
                    self.l2name[chr(recipe_l)] = name
                    self.recipes[name] = (sections[0].strip(),sections[1].strip())
                    if chr(recipe_l)=="Z":
                        recipe_l = 96

    def pack_letters(self, str):
        last_ch = ""
        last_cnt = 0
        res = ""
        #print(f"packing letters {str}")
        for ch in str:
            if ch==last_ch:
                last_cnt += 1
            else:
                if last_cnt>0:
                    res += last_ch
                if last_cnt>2:
                    res += f"*{last_cnt}"
                elif last_cnt==2:
                    res += last_ch
                last_cnt = 1
                last_ch = ch
        if last_cnt>0:
            res += last_ch
        if last_cnt>2:
            res += f"*{last_cnt}"
        elif last_cnt==2:
            res += last_ch
        return res

    # add delay component to recipes and
    # do Bellman Ford on recipes to calculate optimal movement
    def make_move_table(self):
        movekeys = []
        plusMove, plusEff, minusMove, minusEff = "",0,"",0
        for recipekey in self.recipes:
            recipe = self.recipes[recipekey][0].split(" ")
            info = self.recipes[recipekey][1].split(";") # works only for 270 degree recipes
            sg = int(info[0])
            delay = -120*sg
            if recipe[0][0]=="+": # the only not synced recipe
                delay += int(recipe[0][1:])
                self.recipes[recipekey]=(self.recipes[recipekey][0],self.recipes[recipekey][1],delay,sg)
            elif recipe[0][0]=="[":
                for td_i in range(1,len(recipe)):
                    if recipe[td_i][0]=="(":
                        delay+=int(recipe[td_i][1:-1])
                    elif recipe[td_i][0]=="[":
                        #synchronization not included in delay info
                        #but only allows inserting +8X ... should be preceded by (A) matching the [b] rounding
                        delay = delay #syntax trash
                    else:
                        delay+=int(recipe[td_i])
                self.recipes[recipekey]=(self.recipes[recipekey][0],self.recipes[recipekey][1],delay,sg)
            if len(info)==1:
                movekeys.append(recipekey)
                if delay>plusEff:
                    plusEff=delay
                    plusMove=self.name2l[recipekey]
                if delay<minusEff:
                    minusEff=delay
                    minusMove=self.name2l[recipekey]

        if plusEff != 1:
            print(f"Expected +1 be the only plus move! {plusEff}")
        if minusEff != -150:
            print(f"Expected -150 be the best minus move! {minusEff}")
        #print(f"long range best moves {plusMove}={self.l2name[plusMove]} {minusMove}={self.l2name[minusMove]}")

        #BellmanFord
        for i in range(8):
            # changing the collision timeposition by 0
            self.m[f"[{i}]_0"]=("", 0, 0)
        plusPeriod,minusPeriod=8,-144
        maxPlus,minMinus=1+plusPeriod,-1+minusPeriod # -150 for faster stabilisation
        toFindPeriod = True
        orig_moves = [k for k in movekeys]
        #print ("table computation starts")
        progress = True
        def updateIfBetter(key, move, cost, sgliders):
            if (not key in self.m) or (self.m[key][1] > cost):
                self.m[key] = (move, cost, sgliders)
                return True
            elif self.m[key][1] == cost:
                #we prefere plus moves at the end
                for i in range(1,len(self.m[key][0])):
                    if i>len(move):
                        break
                    lo = self.m[key][0][-i]
                    ln = move[-i]
                    if lo!=ln:
                        if ln==plusMove:
                            #print(f"A-tail better {self.pack_letters(move)} then {self.pack_letters(self.m[key][0])} i,lo,ln:{i},{lo},{ln}")
                            self.m[key] = (move, cost, sgliders)
                        return ln==plusMove
            return False

        while toFindPeriod:
            progress = True
            #print ("Finding period")
            while progress:
                progress = False
                old_moves = [k for k in self.m]
                #print ("Doing progress")
                for edge in orig_moves:
                    erecipe = self.recipes[edge][0].split(" ")
                    edge_str = self.name2l[edge]
                    sg = self.recipes[edge][3]
                    delay = self.recipes[edge][2]
                    ecost = 120*sg + delay # delay has 120*sg subtracted
                    #print(f"edge {edge} '{edge_str}' {delay} {ecost}")
                    if erecipe[0][0]=="+":
                        for src_vertex in old_moves:
                            src = src_vertex.split("_")
                            pos = int(src[1])+delay
                            if pos<=maxPlus:
                                key = f"{src[0]}_{pos}"
                                sgliders = self.m[src_vertex][2] + sg
                                cost = self.m[src_vertex][1] + ecost
                                move = self.m[src_vertex][0] + edge_str
                                new = updateIfBetter(key, move, cost, sgliders)
                                if not progress and new:
                                #if new:
                                    #print (f"progress moves[{key}]=({self.pack_letters(move)}, {cost})")
                                    progress = True
                    else:
                        sync = int(erecipe[0][1:-1])
                        for src_vertex in old_moves:
                            src = src_vertex.split("_")
                            vpos = int(src[0][1:-1])+int(src[1])
                            if vpos % 8 == sync:
                                pos = int(src[1])+delay
                                if pos>=minMinus-150:
                                    key = f"{src[0]}_{pos}"
                                    sgliders = self.m[src_vertex][2] + sg
                                    cost = self.m[src_vertex][1] + ecost
                                    move = self.m[src_vertex][0] + edge_str
                                    new = updateIfBetter(key, move, cost, sgliders)
                                    if not progress and new:
                                    #if new:
                                        #print (f"progress moves[{key}]=({self.pack_letters(move)}, {cost})")
                                        progress = True
            for n in range(minMinus,maxPlus+1):
                row = f"_{n}"
                for s in range(8):
                    key = f"[{s}]_{n}"
                    if key in self.m:
                        row += f" {self.pack_letters(self.m[key][0])}({self.m[key][1]},{self.m[key][2]})"
                    else:
                        row += " ?"
                #print(row)
            toFindPeriod = False
            for n in range(maxPlus,maxPlus-plusPeriod-1,-1):
                if toFindPeriod:
                    break
                for s in range(8):
                    toFindPeriod = True
                    key = f"[{s}]_{n}"
                    if key in self.m:
                        if (self.m[key][0].find(plusMove*8)>=0):
                            toFindPeriod = False
                    if toFindPeriod:
                        maxPlus = n + plusPeriod + 1
                        #print (f"maxPlus changed to {n}+{plusPeriod+1}={maxPlus}")
                        break
            toFindPlus = toFindPeriod
            toFindPeriod = False
            for n in range(minMinus,minMinus-minusPeriod+1):
                if toFindPeriod:
                    break
                for s in range(8):
                    toFindPeriod = True
                    key = f"[{s}]_{n}"
                    if key in self.m:
                        if (self.m[key][0].find(minusMove+plusMove*6)>=0):
                            toFindPeriod = False
                    if toFindPeriod:
                        minMinus = n + minusPeriod - 1
                        #print (f"minMinus changed to {n}+{minusPeriod-1}={minMinus}")
                        break
            toFindPeriod = toFindPeriod or toFindPlus
            #toFindPeriod = toFindPlus
        all_moves = [k for k in self.m]
        for k in all_moves:
            pos = int(k.split("_")[1])
            if (pos<minMinus) or (pos>maxPlus):
                del self.m[k]
        self.mMinMinus = minMinus
        self.mMaxPlus = maxPlus
        self.mMinusPeriod = minusPeriod
        self.mPlusPeriod = plusPeriod
        self.mMinus = minusMove
        self.mPlus = plusMove

        #print ("get_move table:")
        #for n in range(minMinus-10,maxPlus+11):
        #    row = f"_{n}"
        #    for s in range(8):
        #        move = self.get_move(s,n)
        #        row += f" {self.pack_letters(move[0])}({move[1]},{move[2]})"
        #    print(row)

    #let us make table of optpimal single emissions. The problem is we do not know the continuation and the total cost depends on the followup.
    #fortunately for the same number of used simkin gliders the recipe able to finish in shorter time (able to do earlier collision)
    #is definitely better (it can delay the followup to match the other)
    #so we can store the best recipes for each number of destroyed simkin gliders
    #the best recipe using the minimal number of simkin gliders should be stored, (followup with increased lane)
    #but for recipes using more simkin gliders (followup with decreased lane), they should be compared to smaller number
    #of simkin gliders followed by a "B/C/D" move. If they are dominated, there is no need to store them.

    def x_is_dominated(self, sync,muster,questioned):
        #print(f"dom [{sync}],({self.pack_letters(muster[0])},{muster[1]},{muster[2]}),({self.pack_letters(questioned[0])},{questioned[1]},{questioned[2]})")
        if muster[2]>questioned[2]: #actually we would not ask
            return False
        elif muster[2]==questioned[2]: #actually we would not ask either
            return muster[1]<=questioned[1]
        else:
            dist = questioned[1]-muster[1] - 120*(questioned[2]-muster[2])
            move = self.get_move((sync+muster[1])%8,dist)
            #print (f"is {(self.pack_letters(muster[0]),muster[1],muster[2])} dominating {(self.pack_letters(questioned[0]),questioned[1],questioned[2])} ... dist{dist} {move[1]}+{muster[1]}={move[1]+muster[1]}")
            return move[1]+muster[1]<=questioned[1]

    def x_updateIfBetter(self, key, move, cost, sgliders):
        #print(f"uib {key},{self.pack_letters(move)},{cost},{sgliders}")
        cur_moveitem = (move, cost, sgliders)
        if (not key in self.x):
            self.x[key]=[cur_moveitem]
            return True
        inserted, try_insert = False, True
        for r in range(len(self.x[key])):
            if try_insert:
                if self.x[key][r][2] > cur_moveitem[2]:
                    cur_moveitem, self.x[key][r], inserted = self.x[key][r], cur_moveitem, True
                elif self.x[key][r][2] == cur_moveitem[2]:
                    if self.x[key][r][1] > cur_moveitem[1]:
                        self.x[key][r], inserted, try_insert = cur_moveitem, True, False
                    else:
                        return False

        if try_insert:
            if not self.x_is_dominated(int(key[1]),self.x[key][-1], cur_moveitem):
                self.x[key].append(cur_moveitem)
                inserted = True
        if inserted:
            #print (f"recheck {range(1,len(self.x[key]))}")
            i=1
            while i<len(self.x[key]):
                if self.x_is_dominated(int(key[1]),self.x[key][-i-1],self.x[key][-i]):
                    #print (f"removing dominated {self.x[key][-i]}, leaving {self.x[key][-i-1]}")
                    del self.x[key][-i]
                    if i>1:
                        i-=1
                else:
                    i+=1
        return inserted

    def init_x_table(self):
        #TODO block emissions (with dir="_")
        for recipekey in self.recipes:
            info = self.recipes[recipekey][1].split(";") # works only for 270 degree recipes
            if len(info)==2 and (info[1][0]=="l" or info[1][0]=="r"): #single glider emissions
                ginfo = info[1].split(",")
                phase = "eo"[int(ginfo[0][1:])%2]
                dir  = {"l":"L","r":"R"}[info[1][0]]
                lane = int(ginfo[1])
                sync = int(self.recipes[recipekey][0][1])
                sg = self.recipes[recipekey][3]
                cost = self.recipes[recipekey][2]+120*sg # delay has 120*sg subtracted
                self.x_updateIfBetter(f"[{sync}]_{phase}{dir}{lane}",self.name2l[recipekey],cost,sg)
                self.x_updateIfBetter(f"[{sync}]_b{dir}{lane}",self.name2l[recipekey],cost,sg)
            if len(info)==2 and (info[1][0]=="b"): #single block emissions
                binfo = info[1].split(",")
                phasedir = "l_" if binfo[0][1]=="-" else "r_"
                lane = int(binfo[1])
                sync = int(self.recipes[recipekey][0][1])
                sg = self.recipes[recipekey][3]
                cost = self.recipes[recipekey][2]+120*sg # delay has 120*sg subtracted
                print (f"block move [sync]lane,cost,sg:[{sync}]{lane},{cost},{sg}")
                self.x_updateIfBetter(f"[{sync}]_{phasedir}{lane}",self.name2l[recipekey],cost,sg)
                self.x_updateIfBetter(f"[{sync}]_{phasedir}{lane+1}",self.name2l[recipekey],cost,sg)
                # we want the target be in close proximity of determined position, it is rarely used and position easily debugged (we use just one color of block)
            if len(info)==2 and ord(info[1][0])>=ord("A") and ord(info[1][0])<=ord("Z"):
                xinfo = info[1].split(",")
                phasedir = f"{xinfo[0]}_"[0:2]
                lane = int(xinfo[1])
                sync = int(self.recipes[recipekey][0][1])
                sg = self.recipes[recipekey][3]
                cost = self.recipes[recipekey][2]+120*sg # delay has 120*sg subtracted
                print (f"extra move {phasedir} [sync]lane,cost,sg:[{sync}]{lane},{cost},{sg}")
                self.x_updateIfBetter(f"[{sync}]_{phasedir}{lane}",self.name2l[recipekey],cost,sg)
                self.x_updateIfBetter(f"[{sync}]_{phasedir}{lane+1}",self.name2l[recipekey],cost,sg)
                self.extras.append(xinfo[0])
                # we want the target be in close proximity of determined position, it is rarely used and position easily debugged (we use just one color of it)
                # expecting p1 so far

    def make_x_table(self):

        #print ("table computation starts")
        def lane_recipes(lane,phasedir):
            #print (f"lane {lane}")
            for omove in orig_moves:
                #print(omove)
                os=int(omove[1])
                opd=omove[4:6]
                olane=int(omove[6:])
                shift = lane-olane
                if shift%2 == 0 and opd==phasedir:
                    for oinfo in self.x[omove]:
                        for s in range(8):
                            minfo = self.get_move(s,os-s+4*shift)
                            self.x_updateIfBetter(f"{[s]}_{opd}{lane}",minfo[0]+oinfo[0],minfo[1]+oinfo[1],minfo[2]+oinfo[2])

        def print_xtable(phasedir,minM,maxP):
            print (f"x_table {phasedir}({minM},{maxP})")
            for n in range(minM,maxP+1):
                row = f"_{phasedir}{n}"
                for s in range(8):
                    key = f"[{s}]_{phasedir}{n}"
                    if key in self.x:
                        alt = ""
                        for xmove in self.x[key]:
                            alt += f",{self.pack_letters(xmove[0])}({xmove[1]},{xmove[2]})"
                        row += " "+alt[1:]
                    else:
                        row += " ?"
                print(row)

        self.xPlusPeriod,self.xMinusPeriod=2,-36
        orig_moves = [k for k in self.x]
        maxmin={}
        self.xMinMinus={}
        self.xMaxPlus={}

        for omove in orig_moves:
            maxmin[omove[4:6]]=True

        for phasedir in maxmin:
            toFindPeriod = True
            maxPlus,minMinus,prevMaxPlus,prevMinMinus = self.mMaxPlus//4,self.mMinMinus//4,-1,0
            while toFindPeriod:
                for lane in range(prevMaxPlus+1,maxPlus+1):
                     lane_recipes(lane,phasedir)
                for lane in range(prevMinMinus-1,minMinus-1,-1):
                     lane_recipes(lane,phasedir)

                #print_xtable(phasedir,maxmin[1],maxmin[0])
                prevMinMinus,prevMaxPlus = minMinus,maxPlus

                toFindPeriod = False
                for n in range(maxPlus,maxPlus-self.xPlusPeriod-1,-1):
                    if toFindPeriod:
                        break
                    for s in range(8):
                        if toFindPeriod:
                            break
                        toFindPeriod = True
                        key = f"[{s}]_{phasedir}{n}"
                        if key in self.x:
                            toFindPeriod = False
                            for option in self.x[key]:
                                if (option[0].find(self.mPlus*8)<0):
                                    toFindPeriod = True
                        if toFindPeriod:
                            maxPlus = n + self.xPlusPeriod + 1
                            print (f"maxPlus changed to {n}({s}{phasedir})+{self.xPlusPeriod+1}={maxPlus}")
                            break
                toFindPlus = toFindPeriod
                toFindPeriod = False
                for n in range(minMinus,minMinus-self.xMinusPeriod+1):
                    if toFindPeriod:
                        break
                    for s in range(8):
                        if toFindPeriod:
                            break
                        toFindPeriod = True
                        key = f"[{s}]_{phasedir}{n}"
                        if key in self.x:
                            toFindPeriod = False
                            for option in self.x[key]:
                                if (option[0].find(self.mMinus+self.mPlus*6)<0):
                                    toFindPeriod = True
                        if toFindPeriod:
                            minMinus = n + self.xMinusPeriod - 1
                            print (f"minMinus changed to {n}({s}{phasedir})+{self.xMinusPeriod-1}={minMinus}")
                            break
                toFindPeriod = toFindPeriod or toFindPlus
                #toFindPeriod = toFindPlus

                self.xMinMinus[phasedir] = minMinus
                self.xMaxPlus[phasedir] = maxPlus

        #print ("get_xoptions tables:")
        #for phasedir in maxmin:
        #    for n in range(self.xMinMinus[phasedir]-10,self.xMaxPlus[phasedir]+11):
        #        row = f"{phasedir}_{n}"
        #        for s in range(8):
        #            alt = ""
        #            for xmove in self.get_xoptions(s,phasedir,n):
        #                alt += f",{self.pack_letters(xmove[0])}({xmove[1]},{xmove[2]})"
        #            row += " "+alt[1:]
        #        print(row)

    # let us do not consider building and destroying the simkin gun and expecting the recipe contain lanes not interacting with the simkin gun
    # do not use anchor moves and their destroyal at the first try
    # I am adding r direction, I really do not know how should I combine it with left direction.
    # I am copy pasting the code, but this would be terrible for maintainance ... :(
    def __init__(self, initial_direction='L'):

        self.direction = initial_direction # 'L'/'R' left/right outputing specified gliders
        self.recipes = {} # imported base recipes "{name}"->(exttimedeltas, description, delay, sgliders) -- delay, sgliders added by make_move_table
        self.m = {} # arm moves "[{sync}]_{delta}"->(letterstring, cost, sgliders)
        self.x = {} # single emissions which could be optimal "[{sync}]_{phasedir}{lanedist of correct parity}"->[(letterstring, cost, sgliders)]
        # delay = cost - 120*sgliders ... (change of cur)
        # the m,l would be formulated in recipe names as the "possibly optimal" recipes would be after some preperiod periodic with respect to both 
        #increasing/decreasing lane distances, the recipes would be tabulated till the period appears and function replaying to requests in the periodic portion would be answered
        #using a finite portion of the table. We know the most efficient move considering self.cur decrease would be "m2", so when m2 m2 apears in the recipe, we expect we are already
        #in the cycle. If it happened for 144 units (the move size) we are in the periodic portion of the function
        #synchronization wait would be on the other end of the table.
        #self.[X].minindex, self.[X].maxindex for X in "m", "l" would be used by the tablereading function.
        self.l2name = {} # translation from compact form to original names
        self.name2l = {}  # translation from original names to compact form
        # names2exttimedeltas transation to recipe including (a)[b]+c operands
        # jointimedeltas transation to recipe possibly including [b]+c only on start and (a) only on both ends
        self.load_recipes()
        self.make_move_table()
        self.extras=[]
        self.init_x_table() # both glider emissions left and block emissions left/right (block emission is determined div2 ... color is fixed)
        self.make_x_table() # computes from -infty to +infty (periodic function with a preperiod xMinMinus[phasedir],xMinusPeriod,xMaxPlus[phasedir],xPlusPeriod computed)
        self.recipes['build']=["[0] 126 102 100 195 90 91 95 98 105 90 101 141 94 159 92 146 99 90 152 139 144 92 161 131 116 101 114 111 112 93 127 98 102 114 107 157 90 90 90 91 91 243 113 139 108 95 127 121 99 257 144 94 218 148 226 111 119 100 (90)"]
        self.name2l['build']='='
        self.l2name['=']='build'
        self.recipes['clean_block']=["[2] 91 138 289 221 (90)"]
        self.name2l['clean_block']='<'
        self.l2name['<']='clean_block'
        self.recipes['clean']=["[2] 91 138 201 221 216 138 125 (90)"]
        self.name2l['clean']='\\'
        self.l2name['\\']='clean'

        self.prev_goptions = [] # used for combined emissions
        self.subresults = [""]
        #Following structures are "doubled", second version corresponds to switched o<>e phases on input % means we could use results from the other branch as well ... (current pattern is p1)
        self.cur = [0,0] # position of the collision of the simking glider with gliders sent at next_available_tick would happen in "1/8 fd"
        # glider lanes in recipes are relative to 2*(self.cur//8)
        self.delayed2, self.delayed1 = [{},{}], [{},{}] # portions of candidate salvas not yet decided to connect to the recipe, delayed2 does not contain last 2 gliders, delayed1 does not contain last glider (the glider currently considered)
        self.delayed1[0][0],self.delayed1[1][0]=("0:",0),("0:",0)
        self.output = ["",""] # to be made by self.flush from subresults
        # curAfterLastEmission -> (letterstring,cost)

        self.delayed = [{},{}] # curerntly working on similarly as self.delayed1, self.delayed2 to be "rotated" after all options are processed
        # curAfterLastEmission -> ((predecessorkey,predecessorlevel,letterstring after predecessor),cost)

    #(letterstring, cost, sgliders)
    def get_move(self, sync, dist):
        #print (f"get_move({sync},{dist})")
        if dist < self.mMinMinus:
            e = (dist - self.mMinMinus) // (-self.mMinusPeriod)
            moveBase = self.m[f"[{sync}]_{dist+e*self.mMinusPeriod}"]
            rpos = moveBase[0].find(self.mMinus+self.mPlus*6)
            move = moveBase[0][:rpos] + (self.mMinus+self.mPlus*6)*(-e) + moveBase[0][rpos:]
            cost = moveBase[1]-e*96
            sg = moveBase[2]-e*2
            ret=(move, cost, sg)
        elif dist > self.mMaxPlus:
            e = (self.mMaxPlus - dist) // self.mPlusPeriod
            moveBase = self.m[f"[{sync}]_{dist+e*self.mPlusPeriod}"]
            rpos = moveBase[0].find(self.mPlus*8)
            move = moveBase[0][:rpos] + (self.mPlus*8)*(-e) + moveBase[0][rpos:]
            cost = moveBase[1]-e*8
            sg = moveBase[2]-e*0
            ret=(move, cost, sg)
        else:
            ret = self.m[f"[{sync}]_{dist}"]
        return ret

    #[(letterstring, cost, sgliders)]
    def get_xoptions(self, sync, phasedir, lanedist):
        #print (f"get_move({sync},{dist})")
        options=[]
        if lanedist < self.xMinMinus[phasedir]:
            e = (lanedist - self.xMinMinus[phasedir]) // (-self.xMinusPeriod)
            key = f"[{sync}]_{phasedir}{lanedist+e*self.xMinusPeriod}"
            for o in self.x[key]:
                rpos = o[0].find(self.mMinus+self.mPlus*6)
                move = o[0][:rpos] + (self.mMinus+self.mPlus*6)*(-e) + o[0][rpos:]
                cost = o[1]-e*96
                sg = o[2]-e*2
                options.append((move,cost,sg))
        elif lanedist > self.xMaxPlus[phasedir]:
            e = (self.xMaxPlus[phasedir] - lanedist) // self.xPlusPeriod
            key = f"[{sync}]_{phasedir}{lanedist+e*self.xPlusPeriod}"
            for o in self.x[key]:
                rpos = o[0].find(self.mPlus*8)
                move = o[0][:rpos] + (self.mPlus*8)*(-e) + o[0][rpos:]
                cost = o[1]-e*8
                sg = o[2]-e*0
                options.append((move, cost, sg))
        else:
            options = self.x[f"[{sync}]_{phasedir}{lanedist}"]
        return options

    #[(letterstring, cost, sgliders, lane0)] to be shifted to p_lane latter
    def get_xxpreoptions(self, p_laneMod2, lanedif, pphasedir, phasedir):
        #there is curreently at most one match, so we do not bother with multiplicity
        #maybe TODO make it general rather the LL/RR case, but we do not have lr/rl moves in the simkinECCA.txt yet (not worth the minimal possible use) side switching now prevents emission pairs
        if pphasedir[-1]+phasedir[-1]=='LL':
            name = f"ll{p_laneMod2}_{lanedif}"
            if name in self.recipes:
                #simple match
                #print (f"ll match {name}")
                recipe = self.recipes[name]
                info = recipe[1].split(";l")
                if pphasedir[0]!="b" and pphasedir[0]!="eo"[int(info[1].split(",")[0])%2]:
                    #print (f"pphase wrong {pphasedir} {'eo'[int(info[1].split(',')[0])%2]}")
                    return []
                if phasedir[0]!="b" and phasedir[0]!="eo"[int(info[2].split(",")[0])%2]:
                    #print (f"phase wrong {phasedir} {'eo'[int(info[2].split(',')[0])%2]}")
                    return []
                return [(self.name2l[name],recipe[2]+120*recipe[3],recipe[3],int(info[1].split(",")[1]))]
            name = f"ll{p_laneMod2}" #there is only one such recipe, otherwise we should make changes there
            if name in self.recipes:
                #print (f"ll match {name}")
                recipe = self.recipes[name]
                info = recipe[1].split(";l")
                if pphasedir[0]!="b" and pphasedir[0]!="eo"[int(info[1].split(",")[0])%2]:
                    #print (f"pphase wrong {pphasedir} {'eo'[int(info[1].split(',')[0])%2]}")
                    return []
                baselanedif = int(info[1].split(",")[2]) # more general alternative would to have baselanedif in _o,_e parts
                if lanedif<baselanedif:
                    #print (f"lanedif too small")
                    return []
                name2 = f"ll{p_laneMod2}_{'eo'[lanedif%2]}"
                recipe2 = self.recipes[name2]
                info2 = recipe2[1].split(";l")
                if phasedir[0]!="b" and phasedir[0]!="eo"[int(info2[1].split(",")[0])%2]:
                    #print (f"phase wrong {phasedir} {'eo'[int(info2[1].split(',')[0])%2]}")
                    return []
                #print (f"lanedif {lanedif} baselanedif {baselanedif} dif//2 {(lanedif-baselanedif)//2}")
                minfo = self.get_move(0,8*((lanedif-baselanedif)//2)) #positive moves do not depend on sync (the sync is 0)
                return [(self.name2l[name]+minfo[0]+self.name2l[name2],
                    recipe[2]+120*recipe[3]+minfo[1]+recipe2[2]+120*recipe2[3],
                    recipe[3]+minfo[2]+recipe2[3],
                    int(info[1].split(",")[1]))]
        elif pphasedir[-1]+phasedir[-1]=='RR':
            name = f"rr{p_laneMod2}_{lanedif}"
            if name in self.recipes:
                #simple match
                #print (f"ll match {name}")
                recipe = self.recipes[name]
                info = recipe[1].split(";r")
                if pphasedir[0]!="b" and pphasedir[0]!="eo"[int(info[1].split(",")[0])%2]:
                    #print (f"pphase wrong {pphasedir} {'eo'[int(info[1].split(',')[0])%2]}")
                    return []
                if phasedir[0]!="b" and phasedir[0]!="eo"[int(info[2].split(",")[0])%2]:
                    #print (f"phase wrong {phasedir} {'eo'[int(info[2].split(',')[0])%2]}")
                    return []
                return [(self.name2l[name],recipe[2]+120*recipe[3],recipe[3],int(info[1].split(",")[1]))]
        return []

    #[(letterstring, cost, sgliders)]
    def get_xxoption(self, sync, lane0evendist, xxpreoption):
        #print (f"get_lloption({sync},{lane0evendist},({self.pack_letters(llpreoption[0])},{llpreoption[1]},{llpreoption[2]},{llpreoption[3]}))")
        os = int(self.recipes[self.l2name[xxpreoption[0][0]]][0][1])
        shift = lane0evendist - xxpreoption[3]
        minfo = self.get_move(sync,os-sync+4*shift)
        return (minfo[0]+xxpreoption[0],minfo[1]+xxpreoption[1],minfo[2]+xxpreoption[2])

    def pack_options(self,options):
        res=" "
        for option in options:
            res+=f"({self.pack_letters(option[0])},{option[1]},{option[2]}),"
        return res[:-1]

    def letters2names(self, letter_string):
        names = ""
        for l in letter_string:
            names += self.l2name[l]+" "
        return names[:-1]

    def names2exttimedeltas(self, names_string):
        names = names_string.split(" ")
        recipe = []
        for name in names:
            recipe.append(self.recipes[name][0])
        return " ".join(recipe)

    def jointimedeltas(self, ext_td, current_total=0):
        etds = ext_td.split(" ")
        current_parenthesis, current_plus = 0, 0
        synced, started = False, False
        tds = []
        for etd in etds:
            if etd[0]=="(":
                if not synced:
                    print("consecutive parenthesis!")
                else:
                    current_parenthesis = int(etd[1:-1])
                    synced = False
            elif etd[0]=="[":
                if synced:
                    print("consecutive square brackets!")
                if not started:
                    if current_parenthesis>0:
                        tds.append(f"({current_parenthesis + current_plus})")
                    elif current_plus>0:
                        tds.append(f"+{current_plus}")
                    tds.append(etd)
                    started = True
                else:
                    td = current_parenthesis + current_plus
                    psplit = (etd[1:-1]+"%8").split('%') # %8 default
                    rem = (int(psplit[0]) - td - current_total) % int(psplit[1])
                    tds.append(f"{td+rem}")
                    current_total += td+rem
                    current_parenthesis, current_plus = 0, 0
                synced = True
            elif etd[0]=="+":
                if synced:
                    print("+ at synced state!")
                current_plus += int(etd[1:])
            else:
                if not synced:
                    print("exact delay when not synced!")
                delay = int(etd)
                tds.append(etd)
                current_total += delay
        if current_parenthesis > 0:
            tds.append(f"({current_parenthesis + current_plus})")
            current_total += current_parenthesis + current_plus
            current_parenthesis, current_plus = 0, 0
        return " ".join(tds),current_total

    def pack_delayed1(self, delayed):
        res=""
        for key,value in delayed.items():
            res += f" {key}:({self.pack_letters(value[0])},{value[1]})"
        return res

    def pack_subresults(self):
        res=""
        for i in range(len(self.subresults)):
            s_sr=self.subresults[i].split(":")
            if len(s_sr)>1:
                res+=f"\n{i}={s_sr[0]}:{self.pack_letters(s_sr[1])}"
        return res[1:]

    def process_goptions(self,goptions):

        phaseswitching = {"b0L":"bL", "b1L":"bL", "o0L":"oL", "o1L":"eL", "e0L":"eL", "e1L":"oL", "r0L":"r_", "r1L":"r_", "l0L":"l_", "l1L":"l_", "t0L":"l_", "t1L":"l_",
                          "b0R":"bR", "b1R":"bR", "o0R":"oR", "o1R":"eR", "e0R":"eR", "e1R":"oR", "r0R":"r_", "r1R":"r_", "l0R":"l_", "l1R":"l_", "t0R":"r_", "t1R":"r_"
                          }
        for extra in self.extras:
            phaseswitching[f"{extra}0L"]=f"{extra}_"[0:2]
            phaseswitching[f"{extra}1L"]=f"{extra}_"[0:2]
            phaseswitching[f"{extra}0R"]=f"{extra}_"[0:2]
            phaseswitching[f"{extra}1R"]=f"{extra}_"[0:2]
        # b,e,o gliders l,r blocks

        self.delayed=[{},{}]
        def updateIfBetter(key, phaseswitch, move, cost):
            #print(f"uib {key},{self.pack_letters(move[2])},{cost}")
            if (not key in self.delayed[phaseswitch]):
                self.delayed[phaseswitch][key] = (move, cost)
                return True
            if self.delayed[phaseswitch][key][1] > cost:
                self.delayed[phaseswitch][key] = (move, cost)
                return True
            else:
                return False
        #print(f"process_options {goptions} len(delayed2) {len(self.delayed2[0])},{len(self.delayed2[1])} len(delayed1) {len(self.delayed1[0])},{len(self.delayed1[1])}")
        #print(f"subresults {self.pack_subresults()}")
        #print (f"delayed 2[0]{self.pack_delayed1(self.delayed2[0])}")
        #print (f"delayed 2[1]{self.pack_delayed1(self.delayed2[1])}")
        #print (f"delayed 1[0]{self.pack_delayed1(self.delayed1[0])}")
        #print (f"delayed 1[1]{self.pack_delayed1(self.delayed1[1])}")
        if goptions == ['l'] or goptions == ['r']:
            self.direction = 'L' if goptions[0]=='l' else 'R' # I have expected I should do other stuff as well and check "doing nothing" if already 'L' ... it 'L' side mode prevents llmove
            goptions=[]
            for phaseswitch in range(2):
                for key, value in self.delayed1[phaseswitch].items():
                    updateIfBetter(key, phaseswitch, (key, 1, ''), value[1]) # delayed correspond to doing nothing
        elif len(goptions) == 1 and goptions[0][0] == 'w':
            repeat = 1 if len(goptions[0])==1 else int(goptions[0][1:])
            goptions=[]
            for phaseswitch in range(2):
                for key, value in self.delayed1[phaseswitch].items():
                    wmove = 'A' * (16-(key%8)) + 'B' + ('A'*30+'B')*(repeat-1) + 'A' * (14+key%8) # requires A = +1, B = [0] (90)
                    #print(f"wmove {self.pack_letters(wmove)}")
                    ncost = value[1] + 120*repeat
                    ncur = key
                    nmove = (key, 1, wmove) # real update would be during step forward
                    updateIfBetter(ncur, phaseswitch, nmove, ncost)
        elif len(goptions) == 1 and goptions[0][0] == 'i':
            repeat = 1 if len(goptions[0])==1 else int(goptions[0][1:])
            goptions=[]
            for phaseswitch in range(2):
                for key, value in self.delayed1[phaseswitch].items():
                    wmove = 'A' * (120*repeat) # requires A = +1
                    ncost = value[1] + 120*repeat
                    ncur = key+120*repeat
                    nmove = (key, 1, wmove) # real update would be during step forward
                    updateIfBetter(ncur, phaseswitch, nmove, ncost)
        else:
            for phaseswitch in range(2):
                if self.prev_goptions != []:
                    for pgoption in self.prev_goptions:
                        if len(pgoption):
                            if pgoption[1]>'9':
                                pphasedir, p_lane = phaseswitching[f"{pgoption[0:2]}{phaseswitch}{self.direction}"], int(pgoption[2:])
                            else:
                                pphasedir, p_lane = phaseswitching[f"{pgoption[0]}{phaseswitch}{self.direction}"], int(pgoption[1:])
                            for goption in goptions:
                                if len(goption):
                                    if goption[1]>'9':
                                        phasedir, lane = phaseswitching[f"{goption[0:2]}{phaseswitch}{self.direction}"], int(goption[2:])
                                    else:
                                        phasedir, lane = phaseswitching[f"{goption[0]}{phaseswitch}{self.direction}"], int(goption[1:])
                                    lanedif = lane-p_lane
                                    xxpreoptions = self.get_xxpreoptions(p_lane%2,lanedif,pphasedir,phasedir)
                                    #print (f"xxpreoptions: {xxpreoptions}")
                                    for xxpreoption in xxpreoptions:
                                        #print(f"trying ({self.pack_letters(xxpreoption[0])},{xxpreoption[1]},{xxpreoption[2]},{xxpreoption[3]})")
                                        for key, value in self.delayed2[phaseswitch].items():
                                            xxoption = self.get_xxoption(key%8,p_lane-2*(key//8),xxpreoption)
                                            ncost = value[1] + xxoption[1]
                                            ncur = key + xxoption[1] - 120*xxoption[2]
                                            nmove = (key, 2, xxoption[0]) # real update would be during step forward
                                            updateIfBetter(ncur, phaseswitch, nmove, ncost)
                for goption in goptions:
                    if len(goption):
                        if goption[1]>'9':
                            phasedir, lane = phaseswitching[f"{goption[0:2]}{phaseswitch}{self.direction}"], int(goption[2:])
                        else:
                            phasedir, lane = phaseswitching[f"{goption[0]}{phaseswitch}{self.direction}"], int(goption[1:])
                        for key, value in self.delayed1[phaseswitch].items():
                            xoptions = self.get_xoptions(key%8, phasedir, lane-2*(key//8))
                            #print(f"xoptions [{key%8}]_{phasedir}{lane-2*(key//8)}: {self.pack_options(xoptions)}")
                            for xoption in xoptions:
                                ncost = value[1] + xoption[1]
                                ncur = key + xoption[1] - 120*xoption[2]
                                nmove = (key, 1, xoption[0]) # real update would be during step forward
                                updateIfBetter(ncur, phaseswitch, nmove, ncost)
        self.prev_goptions = goptions

    #extends the options DAG and if it is "tally" it outputs already known
    def preprocess_glider(self, glider):
        def updateIfBetter(cur,phaseswitch,movecost):
            if not cur in self.delayed1[phaseswitch]:
                self.delayed1[phaseswitch][cur]=movecost
            elif movecost[1] < self.delayed1[phaseswitch][cur][1]:
                self.delayed1[phaseswitch][cur]=movecost

        if glider == '%':
            keys0 = self.delayed1[0].keys()
            for key1 in self.delayed1[1].keys():
                updateIfBetter(key1,0,self.delayed1[1][key1])
            for key0 in keys0:
                updateIfBetter(key0,1,self.delayed1[0][key0])
            #print("done? %")
        else:
            goptions = glider.split("|")
            self.process_goptions(goptions)
            for phaseswitch in range(2):
                if len(self.delayed[phaseswitch])==1 and len(self.delayed1[phaseswitch])==1:
                    #we probably can flush known "history"
                    for key, value in self.delayed[phaseswitch].items():
                        if value[0][1] == 1: #mark of normal emission
                            #print (f"partial output[{phaseswitch}] {self.pack_delayed1(self.delayed1[phaseswitch])} {key} {value}")
                            self.cur[phaseswitch] = value[0][0]
                            joiner = len(self.subresults)
                            self.subresults.append(self.delayed1[phaseswitch][self.cur[phaseswitch]][0])
                            self.delayed1[phaseswitch][value[0][0]]=(f"{joiner}:",self.delayed1[phaseswitch][value[0][0]][1])
                for key, value in self.delayed[phaseswitch].items(): #let us include the history (if not cut)
                    if value[0][1] == 1:
                        self.delayed[phaseswitch][key]=(self.delayed1[phaseswitch][value[0][0]][0]+value[0][2], value[1])
                    else:
                        #print (f"self_delayed update2 (({value[0][0]},{value[0][1]},{self.pack_letters(value[0][2])}),{value[1]}) {self.pack_delayed1(self.delayed2[phaseswitch])}")
                        self.delayed[phaseswitch][key]=(self.delayed2[phaseswitch][value[0][0]][0]+value[0][2], value[1])

            self.delayed2,self.delayed1 = self.delayed1,self.delayed

    #selects the emission with minimal cost, not considering the final .cur
    def flush(self):
        for phaseswitch in range(2):
            bestkey = min(self.delayed1[phaseswitch], key=lambda x:self.delayed1[phaseswitch][x][1])
            print (f"finally {phaseswitch}: cost {self.delayed1[phaseswitch][bestkey][1]}")
            self.cur[phaseswitch] = bestkey
            output = self.delayed1[phaseswitch][bestkey][0].split(":")
            while int(output[0])>0:
                output = (self.subresults[int(output[0])]+output[1]).split(":")
            self.output[phaseswitch]=output[1]
        return

    def build_with_block (self):
        self.cur = [0,0]
        self.subresults = [""]
        self.delayed2, self.delayed1 = [{},{}], [{},{}] # portions of candidate salvas not yet decided to connect to the recipe, delayed2 does not contain last 2 gliders, delayed1 does not contain last glider (the glider currently considered)
        self.delayed1[0][0],self.delayed1[1][0]=("0:=<",0),("0:=<",0)
        self.prev_goptions = []
        self.output = ["",""]

    def build_clean (self):
        self.cur = [0,0]
        self.subresults = [""]
        self.delayed2, self.delayed1 = [{},{}], [{},{}] # portions of candidate salvas not yet decided to connect to the recipe, delayed2 does not contain last 2 gliders, delayed1 does not contain last glider (the glider currently considered)
        self.delayed1[0][0],self.delayed1[1][0]=("0:=\\",0),("0:=\\",0)
        self.prev_goptions = []
        self.output = ["",""]

    def convert_to_speboe(self):
        treshold = -1
        maintd,td="",""
        for phaseswitch in range(2):
            maintd = td
            if self.cur[phaseswitch] > treshold:
                names = self.letters2names(self.output[phaseswitch])
                #TODO we should make 2 letters for it to be able to incorporate the escaping gliders in the code
                escapes = 3 + self.cur[phaseswitch] // 120
                exttd = self.names2exttimedeltas(names) +f" [95%120] 180 204 96 (90){' [2] (90)'*(escapes//2)}{' [4] (90)'*(escapes%2)} [0%2] 91 90 104 121 121 96 99 112 94 102 94 115 231 111 118 91 92 108 129 (90)"
                td = self.jointimedeltas(exttd)
                print (f"{phaseswitch}: {td}")
        return maintd

    def compile(self, gliders):
        for x in gliders.split():
            self.preprocess_glider(x)
        self.flush()
        maintd,td="",""
        for phaseswitch in range(2):
            maintd = td
            print (f"{phaseswitch}: {self.pack_letters(self.output[phaseswitch])}")
            names = self.letters2names(self.output[phaseswitch])
            #print (names)
            exttd = self.names2exttimedeltas(names)
            #print (exttd)
            td = self.jointimedeltas(exttd)
            print (td)
        return maintd
User avatar
Hippo.69
Posts: 696
Joined: July 14th, 2020, 7:35 pm
Contact:

Re: Optimized Snarkmaker

Post by Hippo.69 »

Uff, after several days of continues searching finally I got a solution:

Code: Select all

x = 109, y = 110, rule = LifeHistory
14.2A$13.A.A$7.2A4.A$5.A2.A2.2A.4A$5.2A.A.A.A.A2.A$8.A.A.A.A$8.A.A.2A
$9.A2$22.2A$13.2A7.A$13.2A5.A.A$3A17.2A$2.A$.A4.A$5.A$5.3A3$10.2A$11.
A$8.3A$8.A6$27.2A$27.A.A21.3D.3D5.3D.3D6.D2.3D.3D$27.A23.D.D.D7.D.D.D
.D5.2D4.D3.D$51.3D.3D5.3D.3D6.D2.3D.3D$53.D3.D7.D.D.D6.D2.D5.D$51.3D.
2D6.3D.3D5.3D.3D.3D19$51.2A$50.2A$52.A22$76.A$75.2A$75.A.A29$106.2A$
106.A.A$106.A!
So now we can start a chain destruction in advance while the salvo still does its work, just after passing through the last snark in the building branch.
We can let the chain destruction continue, we can convert last snark to nothing or to speboe.
Having chain destruction converting the last snark to a speboe glider would be nice to allow long speboe pull, but the current step was much more important piece of the puzzle.

And here is the current kit:

Code: Select all

x = 844, y = 562, rule = LifeHistory
273.2A358.2A118.2A$272.A.A357.A.A117.A.A$266.2A4.A353.2A4.A113.2A4.A$
264.A2.A2.2A.4A347.A2.A2.2A.4A107.A2.A2.2A.4A$264.2A.A.A.A.A2.A347.2A
.A.A.A.A2.A107.2A.A.A.A.A2.A$267.A.A.A.A353.A.A.A.A113.A.A.A.A$267.A.
A.2A354.A.A.2A114.A.A.2A$268.A359.ED118.A$628.2D$281.2A358.2A118.2A$
272.2A7.A350.2A7.A110.2A7.A$272.2A5.A.A350.2A5.A.A110.2A5.A.A$279.2A
358.2A118.2A2$25.2E$25.2E247.A$263.3A7.2A348.3A117.3A$265.A7.A.A349.A
119.A$264.A359.A119.A10.3A$269.2A358.2A118.2A4.A$270.A359.A119.A5.A$
267.3A357.3A117.3A$267.A359.A119.A2$642.2A$641.2A$34.2A607.A$33.2A$
35.A$310.3D.3D5.3D.3D6.D2.3D.3D5.3D.3D314.D2.D.D2.D6.3D.3D97.3D.3D6.D
2.3D.3D$310.D.D.D.D5.D.D.D.D5.2D2.D.D.D.D5.D.D.D315.2D2.D.D.2D6.D.D3.
D97.D.D.D.D5.2D2.D.D3.D$288.D.D19.3D.D.D5.3D.3D6.D2.D.D.3D5.3D.3D314.
D2.3D2.D6.3D3.D97.3D.D.D6.D2.D.D3.D$289.2D21.D.D.D7.D.D.D6.D2.D.D3.D
7.D3.D314.D4.D2.D8.D2.D100.D.D.D6.D2.D.D2.D$289.D20.3D.3D5.3D.3D5.3D.
3D.3D5.3D.2D314.3D3.D.3D5.3D2.D98.3D.3D5.3D.3D2.D5$296.2A$295.2A$297.
A480.2A$778.A.A$778.A7$57.2A$57.A.A$57.A8$677.2A$677.A.A$677.A$321.A$
320.2A$320.A.A3$74.D.D728.2A$75.2D727.2A$75.D730.A3$80.2A$79.2A$81.A
9$702.A$701.2A$701.A.A5$347.3A$347.A$348.A9$110.A$109.2A$109.A.A$842.
2D$841.2D$724.2D117.D$723.2D$725.D5$372.A$371.2A$371.A.A5$273.2A118.
2A118.2A118.2A118.2A$272.A.A117.A.A117.A.A117.A.A117.A.A$266.2A4.A
113.2A4.A113.2A4.A113.2A4.A113.2A4.A$264.A2.A2.2A.4A107.A2.A2.2A.4A
107.A2.A2.2A.4A107.A2.A2.2A.4A107.A2.A2.2A.4A$264.2A.A.A.A.A2.A107.2A
.A.A.A.A2.A102.D4.2A.A.A.A.A2.A107.2A.A.A.A.A2.A107.2A.A.A.A.A2.A$
132.3A132.A.A.A.A113.A.A.A.A104.D.D6.A.A.A.A113.A.A.A.A113.A.A.A.A$
132.A134.A.A.2A114.A.A.2A105.D.D6.A.A.2A114.A.A.2A114.A.A.2A$133.A
134.A119.A110.D8.A119.A119.A$31.A$32.A248.2A118.2A118.2A118.2A118.2A$
30.3A239.2A7.A110.2A7.A110.2A7.A110.2A7.A110.2A7.A$272.2A5.A.A110.2A
5.A.A110.2A5.A.A110.2A5.A.A110.2A5.A.A$259.3A17.2A98.3A17.2A98.3A17.
2A98.3A17.2A98.3A17.2A$261.A119.A119.A119.A12.2D105.A$260.A4.A114.A4.
A114.A4.A114.A4.A8.2D104.A4.A$264.A119.A119.A119.A119.A$264.3A117.3A
117.3A117.3A117.3A2$755.3A$269.2A118.2A118.2A118.2A118.2A4.A$270.A
119.A119.A119.A119.A5.A$267.3A117.3A117.3A117.3A117.3A$267.A119.A119.
A119.A119.A$256.2A118.2A24.A93.2A24.A93.2A24.A93.2A$256.2A118.2A23.2A
93.2A23.2A93.2A23.2A93.2A$401.A.A117.A.A117.A.A3$286.2A$286.A.A21.3D.
3D5.3D.3D6.D2.3D.3D85.3D.3D.3D6.D2.3D.3D6.D2.3D.3D78.D2.3D.3D6.D2.3D.
3D5.3D2.D2.3D78.D2.3D.3D5.3D.3D6.D2.3D.3D82.D3.D2.3D5.3D.3D$286.A23.D
.D.D7.D.D.D.D5.2D4.D3.D87.D.D.D3.D5.2D2.D.D3.D5.2D4.D.D79.2D4.D.D7.2D
2.D.D.D9.D.2D2.D.D77.2D4.D.D7.D.D3.D5.2D2.D.D.D.D81.2D2.2D4.D5.D.D3.D
$310.3D.3D5.3D.3D6.D2.3D.3D85.3D.D.D3.D6.D2.D.D.3D6.D2.3D.3D78.D2.3D.
3D6.D2.D.D.3D5.3D2.D2.3D78.D2.3D.3D5.3D3.D6.D2.D.D.D.D82.D3.D2.3D5.3D
.3D$312.D3.D7.D.D.D6.D2.D5.D85.D3.D.D2.D7.D2.D.D3.D6.D2.D5.D78.D4.D3.
D6.D2.D.D.D.D5.D4.D4.D78.D4.D.D.D7.D2.D7.D2.D.D.D.D82.D3.D4.D7.D3.D$
310.3D.2D6.3D.3D5.3D.3D.3D85.3D.3D2.D6.3D.3D.3D5.3D.3D.2D78.3D.3D.2D
6.3D.3D.3D5.3D.3D.3D77.3D.3D.3D5.3D2.D6.3D.3D.3D81.3D.3D.3D5.3D.3D5$
61.A$62.A359.D$60.3A357.D.D$421.2D$171.A$170.2A$170.A.A2$784.2A$783.
2A$785.A4$310.2A$309.2A$311.A3$555.2A119.A$555.A.A117.2A$555.A119.A.A
6$193.2A128.D$192.2A130.2D$194.A128.2D$91.A$92.A714.2A$90.3A714.A.A$
807.A4$453.2A$335.A117.A.A$334.2A117.A$334.A.A4$699.3A$699.A$581.3A
116.A$581.A$582.A4$217.3A$217.A$218.A6$.A59.A59.A$2.A59.A59.A$3A57.3A
57.3A$479.2A$478.2A$480.A4$365.2A357.3A$365.A.A356.A$365.A359.A19$31.
A59.A59.A105.A$32.A59.A59.A103.2A$30.3A57.3A57.3A103.A.A2$510.2A$510.
A.A$510.A4$637.A$636.2A$636.A.A18$61.A59.A59.A105.A$62.A59.A59.A103.
2A$60.3A57.3A57.3A103.A.A11$187.3D6.D2.D.D.D.D6.D2.3D.3D6.D2.3D.3D6.D
2.3D.3D$187.D.2A4.2D2.D.D.D.D5.2D2.D3.D7.2D2.D.D3.D5.2D4.D.D$187.D2E
6.D2.3D.3D6.D2.3D.3D6.D2.D.D3.D6.D4.D.3D$189.DA5.D4.D3.D6.D4.D3.D6.D
2.D.D2.D7.D3.D2.D.D$187.2D6.3D3.D3.D5.3D.2D2.2D6.3D.3D2.D6.3D2.D2.3D
6$310.A$309.2A$309.A.A5$91.A59.A$92.A59.A$90.3A57.3A18$225.2A110.A$
224.2A110.2A$226.A109.A.A8$121.A59.A$122.A59.A$120.3A57.3A4$234.D$
235.D$233.3D3$178.D$179.2D$178.2D7.2DE5.3D.3D5.3D.3D6.D2.3D.3D6.D2.3D
.3D113.2A$188.AE5.D.D.D7.D.D.D7.2D2.D.D.D.D5.2D2.D.D.D.D112.2A$187.DE
DA4.3D.3D5.3D.3D6.D2.3D.3D6.D2.D.D.3D114.A$189.D7.D.D.D7.D.D.D6.D4.D
3.D6.D2.D.D.D.D$187.3D5.3D.3D5.3D.3D5.3D.3D.3D5.3D.3D.3D12$263.3A$
151.A111.A$152.A111.A$150.3A5$213.A$212.2A171.A$212.A.A169.2A$384.A.A
15$291.A$290.2A$290.A.A3$181.A$182.A54.A$180.3A53.2A$236.A.A10$187.3D
6.D2.D.D.3D6.D2.3D2.D6.3D2.D7.D2.3D.3D$187.D.2A4.2D2.D.D3.D5.2D4.D.2D
6.D.D.2D6.2D2.D.D.D$187.D2E6.D2.3D.3D6.D4.D2.D6.3D2.D7.D2.3D.3D$189.D
A5.D4.D.D8.D3.D3.D8.D2.D7.D2.D.D.D.D$187.2D6.3D3.D.3D5.3D2.D2.3D5.3D.
3D5.3D.3D.3D2$426.A$425.2A$425.A.A4$208.D$209.2D$208.2D12$335.A$334.
2A$334.A.A$448.2A$447.2A$449.A5$225.A$224.2A$224.A.A59.2A$286.A.A$
286.A13$472.2A$471.2A$473.A10$313.2A$313.A.A$313.A12$267.2A$267.A.A$
267.A21$290.2A$289.2A$291.A44$337.A$336.2A$336.A.A!
So what more we would like to have in the kit?
... Simkin (with starting trash) destruction directly to speboeglider (+/-4 hd shifted).

... 0hd shifted snark destruction to a speboe glider and a speboe on the left.
We can use speboe glider for 8hd shift to do rather cheap conversion to empty ... but we have a cheaper alternative.

There are no more low hanging fruits for me ... I have stopped searching regarding the snark destroyal.
I was searching for a while for better OGGCA destroyal methods, but I stopped.
I would like to split stopping the OGGCA and destroying it (the OGGCA without removable debrie).
I have recipe (long ago published) reducing active OGGCA without debrie through speboe to nothing so I can extend it by checkpoint + checkpoint destroyal to do required.
The method to stop the simkin and then use a salvo to remove the debrie (converting them to nothing/speboe/speboeglider/hd0 glider) would be nice. There is a lot of options how to stop the simkin ,,, and the debrie count is nontrivial ...
Post Reply