The connector idea is great. It makes it really easy to increase/decrease the new loop size, because you can expand or decrease the buffer zone without affecting the actual loop size (Aside from the signals needed to construct it obviously)
I spent some time today crunching down the machine we've come up with. I made this first one before I saw your 3rd edit with the simplified connector. Its loop size is 82268, and its replication time is 493,752 cycles.
Code: Select all
#CXRLE Pos=31,58
x = 61, y = 36, rule = Devore2
8A$A6.A$A6.A$A3.A2.A16.5A$8A16.A3.A5.4A2.4A$A3.A2.8A9.A2.2A5.A2.4A2.A
$A6.A6.A9.A2.5A2.A2.A2.A2.A$A6.A6.A9.4A3.A2.A2.A2.A2.5A11.A$A6.5A2.A
12.A2.2A2.A2.A2.4A2.2A11.A$A6.A2.5A12.A2.5A2.A9.A10.2A$A6.A2.A16.4A3.
4A9.13A$A6.A2.A16.A11.6A14.A$A6.A15.2A2.A11.A3.4A12.A$A2.2A2.A15.A3.
4A5.A2.A3.A2.A11.2A$A2.A3.17A3.A2.A2.4A2.A3.A2.A11.3A$A2.A6.A6.A5.5A
5.A2.A2.A3.A2.A10.2A$A2.A2.2A2.A6.A5.A3.A5.A2.4A2.2A2.A$A2.A3.A2.A2.A
2.2A2.A2.A3.10A2.A2.6A$A2.A3.A2.4A2.5A2.2A2.A11.4A16.2A$A2.5A4.5A6.A
3.A29.3A$A6.A8.A6.A2.5A2.5A8.A11.2A$A6.A8.A2.5A2.A3.4A3.7A2.A12.A$A2.
2A2.A8.A3.A5.A3.A12.4A12.A$A2.A3.A8.A3.A4.6A4.A15.A2.6A$A2.A3.14A4.A
3.A3.9A6.4A2.2A$A2.5A4.A12.A3.A3.A.A2.A2.5A2.A2.A2.A$A23.2A3.A3.A4.A
2.A2.2A2.A2.A2.A$A11.A6.7A2.2A8.A2.A3.7A2.A$A2.15A.A3.A4.A6.4A2.A9.A
2.A$4A8.A3.4A3.A4.A2.5A2.4A8.2A2.A$16.A2.A3.A4.A2.A3.A14.5A$12.A3.A2.
5A4.A2.A2.2A14.A$11.6A2.A7.2A2.A2.17A$12.A3.A10.8A$16.A10.A$16.12A!
A notable thing here is that "loop stub" on the right isn't actually connected - that's because I used the two ends of the connector to join the near ends of the loop. It's a lot more compact than squeezing in the first connector you posted, and imo it's just pretty fun conceptually.

- looprep_connected.png (5.56 KiB) Viewed 3561 times
This second one uses the smaller connector you posted. I was hoping that it would require a smaller "extend buffer" and lead to a smaller loop, but its loop size is 83028 and it copies itself in 498,318 cycles. I suspect that the extra "6" you referred to is actually an *advantage* for this use case, because it eats the subsequent extend signal, which would have otherwise gone through, and the whole point of this setup is to eat extend signals. The second connector definitely seems like it might have useful applications elsewhere though.
Code: Select all
#CXRLE Pos=31,58
x = 62, y = 36, rule = Devore2
8A$A6.A$A6.A$A3.A2.A16.5A$8A16.A3.A5.4A2.4A$A3.A2.8A9.A2.2A5.A2.4A2.A
$A6.A6.A9.A2.5A2.A2.A2.A2.A$A6.A6.A9.4A3.A2.A2.A2.A2.5A12.A$A6.5A2.A
12.A2.2A2.A2.A2.4A2.2A12.A$A6.A2.5A12.A2.5A2.A9.A11.2A$A6.A2.A16.4A3.
4A9.14A$A6.A2.A16.A11.6A15.A$A6.A15.2A2.A11.A3.4A13.A$A2.2A2.A15.A3.
4A5.A2.A3.A2.A12.2A$A2.A3.17A3.A2.A2.4A2.A3.A2.A12.3A$A2.A6.A6.A5.5A
5.A2.A2.A3.A2.A8.5A$A2.A2.2A2.A6.A5.A3.A5.A2.4A2.2A2.A12.A$A2.A3.A2.A
2.A2.2A2.A2.A3.10A2.A2.6A7.A2.2A$A2.A3.A2.4A2.5A2.2A2.A11.4A15.4A$A2.
5A4.5A6.A3.A30.A$A6.A8.A6.A2.5A2.5A8.A7.5A$A6.A8.A2.5A2.A3.4A3.7A2.A
7.A$A2.2A2.A8.A3.A5.A3.A12.4A7.A$A2.A3.A8.A3.A4.6A4.A15.A2.A$A2.A3.
14A4.A3.A3.9A6.4A2.A$A2.5A4.A12.A3.A3.A.A2.A2.5A2.A2.A2.A$A23.2A3.A3.
A4.A2.A2.2A2.A2.A2.A$A11.A6.7A2.2A8.A2.A3.7A2.A$A2.15A.A3.A4.A6.4A2.A
9.A2.A$4A8.A3.4A3.A4.A2.5A2.4A8.2A2.A$16.A2.A3.A4.A2.A3.A14.5A$12.A3.
A2.5A4.A2.A2.2A14.A$11.6A2.A7.2A2.A2.17A$12.A3.A10.8A$16.A10.A$16.12A
!
And here's a python script with cleaner code for generating the starter loop, and it generates the correct shape+stub now!
Code: Select all
import golly
extend = [7,6]
extend_left = [4,4,5,6]
extend_right = [5,5,4,6]
retract = [4,5,6,6]
retract_left = [5,6,6,6]
retract_right = [4,6,6,6]
mark = [7,6,4,5,7,6]
inject = [1,1,1,1,7,7]
signal_cells = {
1: [1],
4: [4,0,1,1,1],
5: [5,0,1,1], # if the sequence ends up ever being passed through an "on" switch, we need an extra '1' cell at the end of this list, for extra buffer
6: [6,0,1,1],
7: [7,0,1,1],
}
sequence_loop_delay = 58
rect = golly.getrect()
x = rect[0]
y = rect[1]
width = rect[2]
height = rect[3]
instruction_sequence = []
for row in reversed(xrange(y, y+height)):
# find the rightmost 1 bit in this row
far_right = x+width-1
while golly.getcell(far_right,row) != 1:
far_right -= 1
if far_right<x:
golly.exit('Empty rows forbidden: '+str(row))
# prepare to start printing out cells from this row
instruction_sequence.extend([extend_right, extend, extend])
# loop over each cell in the row and add mark instructions for cells that are marked
for col in xrange(x, far_right + 1):
instruction_sequence.append(extend)
if golly.getcell(col, row) == 1:
instruction_sequence.extend([extend_right, mark, retract_right])
# retract as far as we extended, then move up one to start the next row
instruction_sequence.extend([retract]*(far_right - x + 3))
instruction_sequence.extend([retract_right, extend])
# we're done. the most recent thing in the list should be an extend. if so, get rid of it, because we're just going to retract immediately
if len(instruction_sequence) > 0 and instruction_sequence[-1] == extend:
instruction_sequence.pop()
# move the construction arm to the injection point and inject
instruction_sequence.extend([retract]*3)
instruction_sequence.extend([extend_right, extend, extend, inject])
#now that we have the full list of instructions, conver them down to a list of signals, and then down to a list of cells
signal_sequence = [signal for instruction in instruction_sequence for signal in instruction]
cell_sequence = [cell for signal in signal_sequence for cell in signal_cells[signal]]
cell_sequence += [1] * sequence_loop_delay
# our cell sequence must have even length, since we're going to be creating a loop
if len(cell_sequence) % 2 == 1:
cell_sequence.append(1)
# to write out the starter loop, we're going to keep track of loop cells and loop neighbor cells. After writing the loop cells, we'll go back and mark all the neigbor cells as the sheath
loop_cells = set()
loop_neighbor_cells = set()
def mark_loop_cell(x,y,value):
golly.setcell(x, y, value)
loop_cells.add((x,y))
loop_neighbor_cells.add((x-1,y))
loop_neighbor_cells.add((x+1,y))
loop_neighbor_cells.add((x ,y-1))
loop_neighbor_cells.add((x ,y+1))
# Write out the actual cell sequence. First, write out the
mark_loop_cell(-1,0,cell_sequence[-1])
mark_loop_cell(-2,0,cell_sequence[-2])
mark_loop_cell(-2,1,cell_sequence[-3])
mark_loop_cell(-3,1,cell_sequence[-4])
mark_loop_cell(-4,1,cell_sequence[-5])
mark_loop_cell(-4,2,cell_sequence[-6])
mark_loop_cell(-4,3,cell_sequence[-7])
mark_loop_cell(-4,4,cell_sequence[-8])
mark_loop_cell(-4,5,cell_sequence[-9])
mark_loop_cell(-3,5,cell_sequence[-10])
mark_loop_cell(-2,5,cell_sequence[-11])
mark_loop_cell(-1,5,cell_sequence[-12])
mark_loop_cell(-1,4,cell_sequence[-13])
short_leg_length = 3
long_leg_length = (len(cell_sequence) - len(loop_cells) - short_leg_length) // 2
# draw the top long leg
for x in xrange(0, long_leg_length):
mark_loop_cell(x, 0, cell_sequence[x])
# draw the far short leg
for y in xrange(0, short_leg_length):
mark_loop_cell(long_leg_length-1, y+1, cell_sequence[long_leg_length + y])
# draw the bottom long leg
for x in xrange(long_leg_length):
mark_loop_cell(x, short_leg_length+1, cell_sequence[long_leg_length + short_leg_length + long_leg_length - x - 1])
# finally, draw a stub circuit for the starting point of our construction arm
stub_height = 7
for i in xrange(0, stub_height):
mark_loop_cell(-1, -(i+1), 1)
# now that all loop cells have been drawn, go back and fill out the sheath cells
for neighbor_cell in loop_neighbor_cells:
if neighbor_cell not in loop_cells:
(x,y) = neighbor_cell
golly.setcell(x, y, 2)
golly.show("Instruction sequence count: {}, Signal sequence count: {}, cell sequence count: {}".format(len(instruction_sequence), len(signal_sequence), len(cell_sequence)))
Reflecting on the experience of making this vs making the turing machine replicator, I've learned that there's a constraint on the turing machine's circuits that I wasn't even aware of: They need to be re-usable. you have to assumethat the machine will do any particular thing over and over again, so everything needs to reset itself when it finishes. Compare that to this thing, where almost everything is only used once. So several of my crunching techniques involved leaving the circuits in a broken state, because why not? It's a very different mindset.