The Let's Play Archive

EXAPUNKS

by Carbon dioxide

Part 36: Visual Cortex

Part 36 - Visual Cortex

=== Trash World Inbox ===

Last time, I destroyed Snaxnet's nuclear centrifuges, with high scores of 198, 44 and 7.

Let's see what improvements the thread came up with.

GuavaMoment posted:

After receiving a signal over M, create 5 exas, one for each centrifuge. Test to see if your centrifuge has the highest pressure, otherwise die. Yes, there's only room for 4 exas, but one will die soon enough for the 5th to enter the testing room. Then go turn off your centrifuge and send a done signal. 136/97/22.
code:
LINK 800
LINK 799
MARK REPLS
VOID M
REPL 1
REPL 2
REPL 3
REPL 4

COPY #ZGC0 X
TEST X = 0
TJMP DIE
TEST #ZGC1 > X
TJMP DIE
TEST #ZGC2 > X
TJMP DIE
TEST #ZGC3 > X
TJMP DIE
TEST #ZGC4 > X
TJMP DIE
REPL REPLS
LINK -1
LINK 798
JUMP DIE
MARK 1
COPY #ZGC1 X
TEST X = 0
TJMP DIE
TEST #ZGC0 > X
TJMP DIE
TEST #ZGC2 > X
TJMP DIE
TEST #ZGC3 > X
TJMP DIE
TEST #ZGC4 > X
TJMP DIE
REPL REPLS
LINK -1
LINK 798
JUMP POWEROFF1
MARK 2
COPY #ZGC2 X
TEST X = 0
TJMP DIE
TEST #ZGC1 > X
TJMP DIE
TEST #ZGC0 > X
TJMP DIE
TEST #ZGC3 > X
TJMP DIE
TEST #ZGC4 > X
TJMP DIE
REPL REPLS
LINK -1
LINK 798
JUMP POWEROFF2
MARK 3
COPY #ZGC3 X
TEST X = 0
TJMP DIE
TEST #ZGC1 > X
TJMP DIE
TEST #ZGC2 > X
TJMP DIE
TEST #ZGC0 > X
TJMP DIE
TEST #ZGC4 > X
TJMP DIE
REPL REPLS
LINK -1
LINK 798
JUMP POWEROFF3
MARK 4
COPY #ZGC4 X
TEST X = 0
TJMP DIE
TEST #ZGC1 > X
TJMP DIE
TEST #ZGC2 > X
TJMP DIE
TEST #ZGC3 > X
TJMP DIE
TEST #ZGC0 > X
TJMP DIE
REPL REPLS
LINK -1
LINK 798
LINK 800
MARK POWEROFF3
LINK 800
MARK POWEROFF2
LINK 800
MARK POWEROFF1
LINK 800
MARK DIE
COPY 0 #POWR
COPY 999 M

;XB
COPY 999 M
Snaxnet has been a play on words with stuxnet the entire time, a real life virus made to shut down nuclear centrifuges. I'm sure Zach felt really clever designing this ridiculous challenge for a joke.
Yeah. This code needs some repetition because of the hardcoded register addresses. By having each REPL check if its value is the highest you can get more parallellism than with my solutions.

silentsnack then had several more speed improvements, starting with this one.

silentsnack posted:

code:
;XA
LINK 800
LINK 799
MARK 0
TEST #ZGC1 < #ZGC0
FJMP 1
TEST #ZGC2 < #ZGC0
FJMP 2
TEST #ZGC3 < #ZGC0
FJMP 3
TEST #ZGC4 > #ZGC0
MULI T 4 T
ADDI 7 T M
ADDI T 1 T
JUMP WAIT

MARK 1
TEST #ZGC2 < #ZGC1
FJMP 2
TEST #ZGC3 < #ZGC1
FJMP 3
TEST #ZGC4 > #ZGC1
MULI T 3 T
ADDI 8 T M
ADDI T 1 T
JUMP WAIT

MARK 2
TEST #ZGC3 < #ZGC2
FJMP 3
TEST #ZGC4 > #ZGC2
MULI T 2 T
ADDI T 9 M
ADDI T 2 T
JUMP WAIT

MARK 3
TEST #ZGC4 > #ZGC3
ADDI T 10 M
ADDI T 3 T

MARK WAIT
SUBI T 1 T
TJMP WAIT
JUMP 0

;XB
COPY 5 X
LINK 800
LINK 798

MARK LOOP
MODI -1 X X
COPY M T
REPL LOOP

MODI T 7 #POWR
LINK 800
MODI T 8 #POWR
LINK 800
MODI T 9 #POWR
LINK 800
MODI T 10 #POWR
LINK 800
MODI T 11 #POWR

;XC
COPY 40 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
LINK 800
LINK 799
KILL
95/63/27
XA does some comparisons and based on that jumps into one of the branches. It uses the final T value to craft a specific value to send on M, which is used in MODI instructions in XB to send a zero to the exact right centrifuge. XA also uses the T value to decide how many cycles it needs to wait before XB is done. XB uses X to kill itself after shutting down all centrifuges, while XC shuts down XA more quickly than it could itself.

silentsnack posted:

code:
;XA
LINK 800
LINK 799
COPY 3 X
MARK 0
TEST #ZGC1 < #ZGC0
FJMP 1
TEST #ZGC2 < #ZGC0
FJMP 2
TEST #ZGC3 < #ZGC0
FJMP 3
TEST #ZGC4 > #ZGC0
MULI T 4 T
REPL NEXT

LINK -1
LINK 798
COPY T #POWR
LINK 800
LINK 800
LINK 800
LINK 800
SUBI 4 T #POWR
HALT

MARK 1
TEST #ZGC2 < #ZGC1
FJMP 2
TEST #ZGC3 < #ZGC1
FJMP 3
TEST #ZGC4 > #ZGC1
MULI T 3 T
REPL NEXT

LINK -1
LINK 798
LINK 800
COPY T #POWR
LINK 800
LINK 800
LINK 800
SUBI 3 T #POWR
HALT

MARK 2
TEST #ZGC3 < #ZGC2
FJMP 3
TEST #ZGC4 > #ZGC2
MULI T 2 T
REPL NEXT

LINK -1
LINK 798
LINK 800
LINK 800
COPY T #POWR
LINK 800
LINK 800
SUBI 2 T #POWR
HALT

MARK 3
TEST #ZGC4 > #ZGC3
ADDI T 2 T
REPL NEXT

LINK -1
LINK 798
LINK 800
LINK 800
LINK 800
MODI 2 T #POWR
LINK 800
MODI 3 T #POWR
HALT

MARK NEXT
MODI -1 X X
ADDI T 1 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
JUMP 0

;XB
COPY 22 T
LINK 800
LINK 798
REPL WAIT

MARK UGH
LINK 800
SUBI T 1 T
REPL UGH

MARK WAIT
NOOP
SUBI T 1 T
TJMP WAIT

COPY 0 #POWR
75/87/32
This 75 cycles solution works somewhat similarly to the previous one. Now, XA is responsible for turning off the centrifuges. The main reason this is so fast is because XB puts a REPL of itself in each centrifuge host, with the T timers exactly synced such that all of them trigger the COPY 0 #POWR simultaneously, just barely after the 4th centrifuge is turned off. XA knows to stop after four rounds too, so you skip checking which centrifuge is the final remaining one.

silentsnack posted:

code:
;XA
LINK 800
LINK 799

;BEHOLD! SPAGHETTI!
MARK 0
TEST #ZGC1 < #ZGC0
FJMP 1
TEST #ZGC2 < #ZGC0
FJMP 2
TEST #ZGC3 < #ZGC0
FJMP 3
TEST #ZGC4 > #ZGC0
REPL KILL_0
FJMP WAIT1
JUMP WAIT5

MARK 1
TEST #ZGC2 < #ZGC1
FJMP 2
TEST #ZGC3 < #ZGC1
FJMP 3
TEST #ZGC4 > #ZGC1
REPL KILL_1
FJMP WAIT2
JUMP WAIT5

MARK 2
TEST #ZGC3 < #ZGC2
FJMP 3
TEST #ZGC4 > #ZGC2
REPL KILL_2
FJMP WAIT3
JUMP WAIT4

MARK 3
TEST #ZGC4 > #ZGC3
REPL KILL_3
FJMP WAIT4
NOOP

;LOOKING SILLY ENOUGH?
MARK WAIT5
NOOP
MARK WAIT4
NOOP
MARK WAIT3
NOOP
MARK WAIT2
NOOP
MARK WAIT1
NOOP
JUMP 0

;WHY
MARK KILL_0
LINK -1
LINK 798
COPY T #POWR
LINK 800
LINK 800
LINK 800
LINK 800
DIVI 0 T #POWR

MARK KILL_1
LINK -1
LINK 798
LINK 800
COPY T #POWR
LINK 800
LINK 800
LINK 800
DIVI 0 T #POWR

MARK KILL_2
LINK -1
LINK 798
LINK 800
LINK 800
COPY T #POWR
LINK 800
LINK 800
DIVI 0 T #POWR

MARK KILL_3
LINK -1
LINK 798
LINK 800
LINK 800
LINK 800
COPY T #POWR
LINK 800
DIVI 0 T #POWR

;XB
LINK 800
COPY 16 T
REPL CASCADE

;SWITCHBOARD CLEANUP
LINK 799
JUMP WAIT

;WIPE UR CENTRIFUGES
MARK CASCADE
LINK 798
NOOP
REPL WAIT

MARK UGH
LINK 800
SUBI T 1 T
REPL UGH

MARK WAIT
NOOP
SUBI T 1 T
TJMP WAIT

KILL
COPY 0 #POWR
62/100/40
This final improvement consists of a bunch of smaller optimizations of the previous solution, in exchange for harder to read code. Most notably, calculating the delay countdown has been removed in favour of variable delay timing caused by specific jumps. Also, XB is now responsible for stopping XA after shutting down 4 centrifuges.


silentsnack also improved the low lines solution.

silentsnack posted:

It's possible to save a lot of code if you reuse lines, and still end up running quickly. This time around I haven't optimized all that much getting the solutions working to implement whichever ideas I was going for.

Like the fact that the hardware registers are so annoying here's a solution that only includes one instance of ["#ZGC"]
code:
LINK 800
MARK MAIN_LOOP
LINK 799
MAKE
@REP 5
COPY #ZGC@{0,1} F
@END
LINK -1
LINK 798

MARK MAX
SEEK -1
COPY F X
SEEK -5
MARK PARSE
TEST F > X
TJMP MAX
TEST EOF
FJMP PARSE

SEEK -5
MARK FORWARD
TEST F = X
TJMP STOP
LINK 800
JUMP FORWARD

MARK STOP
WIPE
DIVI 0 X #POWR
MARK REVERSE
LINK -1
REPL REVERSE
JUMP MAIN_LOOP
374/33/49
Only having one #ZGC instance doesn't reduce the number of lines - the @REP gets unrolled. But it does make for much cleaner code, I agree. This code is neat in its simplicity. It writes the values into a file, and then finds the largest value in that file. To keep the code short it has to recheck some old values in the file, but once the TEST EOF is true, X has the largest value. By testing how many steps that value sits from the start of the file you know which centrifuge you need to disable. Then LINKing back by using REPLs to check if we're in the host with link id 799 yet, and a clever use of a DIVI to shut down the EXA if the highest value of all registers is 0 and that's all we need.


=== Mitsuzen HDI-10 - Visual Cortex ===

So, the part about the world not being real...
I think maybe they were onto something.




Two votes this time, one for "That's ridiculous" and one for "Not you, too". I'll roll a die to break the tie.

Not you, too...

They didn't have the right idea about how to deal with it, of course, but...
There's something I've been suspicious about for a while now.
I'm going to have to do some more tests.






My eye is acting up. Must be the phage spreading...

More phage problems, huh?
There's a rumor that the phage originally came from a research lab...
A human-machine interface experiment that found its way into the wild.




Two votes for the same option.

Can we talk about this after I fix my eye?

It's just funny...
I remember some of that research.
It was happening right next door to us.


"Us?" Where do you come from?


OST: EXA Power

The assignment:
- Read a value from each of the optic nerves present and write the correct value to the nerve that runs deeper into your visual cortex (V-CTX). To determine the value that should be written, count the number of values read that are greater than -55, multiply that count by 5 and then subtract 75. Repeat ad infinitum.
- It is not necessary to leave no trace. Your EXAs should be written to operate indefinitely.
- For more information see "Debugging the Phage" in the first issue of the zine.


Alright, to do this I'll certainly need an EXA in each host that has a nerve. Let's first get them out there.

code:
LINK 800
REPL GORIGHT
LINK 1
REPL WRITER
REPL GORIGHT
LINK 1

MARK GORIGHT
REPL READER
LINK -3
JUMP GORIGHT

MARK READER
VOID M

MARK WRITER
LINK 3
VOID M
This is a decently clean way to do that. I move the main EXA northeast, while REPLing a GORIGHT one in each host. Those will each create a reader and then make right-going REPLs in a loop. By choosing this order I can also drop in a REPL WRITER to get an EXA in the visual cortex as well.

The VOID Ms which I used to pause the EXAs now need to be replaced by real code. A naive approach might look like this.

code:
LINK 800
REPL GORIGHT
LINK 1
REPL WRITER
REPL GORIGHT
LINK 1

MARK GORIGHT
REPL READER
LINK -3
JUMP GORIGHT

MARK READER
TEST #NERV > -55
COPY T M
JUMP READER

MARK WRITER
LINK 3

MARK WRITE
COPY 0 X
@REP 9
ADDI M X X
@END
MULI X 5 X
SUBI X 75 #NERV
JUMP WRITE
Sending the count of values that meet a condition is easy, just add up the results from T. But can you see why this code won't work? It's because the READERs aren't waiting for anything, so the first reader will be sending again before the writer has polled all the readers. You get incorrect, messy data.

There's a lot of ways to solve this. Let's start with a way that keeps the code at minimal Activity, even if it's slow.

code:
LINK 800
REPL GORIGHT
LINK 1
REPL WRITER
REPL GORIGHT
LINK 1

MARK GORIGHT
REPL READER
LINK -3
JUMP GORIGHT

MARK READER
TEST #NERV > -55
COPY T M

COPY 5 T
MARK WAIT
SUBI T 1 T
TJMP WAIT

JUMP READER

MARK WRITER
LINK 3

MARK WRITE
COPY 0 X
@REP 9
ADDI M X X
@END
MULI X 5 X
SUBI X 75 #NERV
JUMP WRITE
All that's needed to make everything line up again is a wait loop in the READER. 453/34/10



Today we're battling top percentiles of 334, 27 and 10.

To get the size down to 29, I can roll up that ADDI loop, using T as a counter. Also, that multiply-by-five step can be done when sending T to M.

code:
LINK 800
REPL GORIGHT
LINK 1
REPL WRITER
REPL GORIGHT
LINK 1

MARK GORIGHT
REPL READER
LINK -3
JUMP GORIGHT

MARK READER
TEST #NERV > -55
MULI 5 T M

COPY 13 T
MARK WAIT
SUBI T 1 T
TJMP WAIT

JUMP READER

MARK WRITER
LINK 3

MARK WRITE
COPY 0 X
COPY 9 T

MARK LOOP
ADDI M X X
SUBI T 1 T
TJMP LOOP

SUBI X 75 #NERV
JUMP WRITE
This low cycle solution is much slower, at 934/29/10.


To make a faster solution, I first tried to see if I can have a row of EXAs traverse the grid like lemmings, each one reading every value in turn, so that you can basically write to the visual cortex every cycle. That's not possible - the incoming #NERV registers don't get a new value until after a write to the visual cortex, not after a read like in many other assignments. That fact makes the puzzle much harder.

That means that if I want to use parallelism with the bottleneck being that single write, I need to get the data from all those nerves to the writer EXA as fast as possible. The best way to do that seems to optimize M.

As we've seen in earlier updates, one way to do so is to send two data points with one M call. Since an M call takes two cycles, if you can do this without wasting another cycle somewhere it will always be faster than sending a single data point with one M call.


So I need to make one EXA responsible for at least two nerves. I tried a whole bunch of different designs. For example, you could have 3 reader EXAs, each responsible for an entire row of 3 nerves. That solution wasn't any faster than what I already had.
Another design would be to have each EXA responsible for 2 nerves. Of course, with 9 nerves that won't quite work out.

I guess you could have 4 EXAs, each responsible for two nerves, and then a 5th one responsible for both reading the last nerve and writing. However, if you try you will run into a topological issue. It's not possible to divide the tiles of this grid, including the visual cortex nerve, into adjacent pairs. So one EXA would have to walk further for no reason. It's possible the result is still faster than what I have now but I didn't try.

Instead I decided to divide the responsibilities as such:


Even though an EXA responsible for 3 nerves seems slower, I figured I could get away with using one of these because it isn't possible for all of them to send on M at once anyway.

code:
LINK 800

REPL RIGHT
REPL SYNC
LINK 1
REPL LRLOOP
REPL WRITER
LINK 1
JUMP LRLOOP

MARK WRITER
LINK 3

MARK WRITE
COPY -75 X
ADDI M X X
ADDI M X X
ADDI M X X
ADDI M X #NERV
JUMP WRITE

MARK SYNC
NOOP
NOOP
NOOP


MARK LRLOOP
TEST #NERV > -55
COPY T X
LINK -3
TEST #NERV > -55
ADDI X T X
MULI X 5 M
LINK 3
NOOP
NOOP
NOOP
NOOP
JUMP LRLOOP

MARK RIGHT
LINK -3
LINK -3

MARK OLOOP
TEST #NERV > -55
COPY T X
LINK 1
TEST #NERV > -55
ADDI X T X
LINK 1
TEST #NERV > -55
ADDI X T X
MULI X 5 M
LINK -1
LINK -1
JUMP OLOOP
392/50/304

And it works. The LRLOOP handles the EXAs that have two nerves, the OLOOP is for the three nerves one. At the start, the bottom-most LR EXA jumps to SYNC where it waits a bit. If I don't do that, it keeps trying to send early and I have to make all EXAs wait every cycle. Wasting a couple cycles at the start to make it sync up seems the better plan here.

After sending, both the LRLOOP and OLOOP take their EXAs back to their starting hosts and the whole process repeats. Those NOOPs in the LRLOOP are necessary to keep it in sync with the OLOOP.

Is there a way to speed up the OLOOP? Well, it'd be tight but what if that EXA goes back and forth, reading both ways?

code:
LINK 800

REPL RIGHT
REPL SYNC
LINK 1
REPL LRLOOP
REPL WRITER
LINK 1
JUMP LRLOOP

MARK WRITER
LINK 3

MARK WRITE
COPY -75 X
ADDI M X X
ADDI M X X
ADDI M X X
ADDI M X #NERV
JUMP WRITE

MARK SYNC
NOOP

MARK LRLOOP
TEST #NERV > -55
COPY T X
LINK -3
TEST #NERV > -55
ADDI X T X
MULI X 5 M
LINK 3
NOOP
NOOP
JUMP LRLOOP

MARK RIGHT
LINK -3
LINK -3

MARK OLOOP

TEST #NERV > -55
COPY T X
LINK 1
TEST #NERV > -55
ADDI X T X
LINK 1
TEST #NERV > -55
ADDI X T X
MULI X 5 M
NOOP

TEST #NERV > -55
COPY T X
LINK -1
TEST #NERV > -55
ADDI X T X
LINK -1
TEST #NERV > -55
ADDI X T X
MULI X 5 M

JUMP OLOOP
A single NOOP is needed before OLOOP starts reading again, so that the WRITER has time to finish up. That same cycle is wasted after the return by the JUMP OLOOP. This change also means both the initial SYNC and the NOOPs in the LRLOOP change to keep things synced up.

And... this code runs at 334/54/245. That is top percentile speed.

I think that is enough for this week. I wouldn't be surprised if an even lower speed is possible and I know a cycle count lower than 29 is possible, but that's for the thread.


Hopefully this is the last time you'll have to hack yourself like this.



The first vote, if it pleases you.


[deadlock] perfect

Nivas?? What are you doing there?



Anyway, I got a visitor.



It's Ghast. He looks different without his sunglasses.

Hey...
You hanging in there?
I just wanted to check up on you.
There's... no new issue of the zine yet.
It's going to be a while before I get it together.

Ghast pauses for a moment and takes a deep breath.

To be honest, I'm not sure if it'll happen.
It's getting harder for me to concentrate, and...
Well, let's not talk about that.
I just hope I was able to educate some people, you know?
Give them some power, some agency...
Otherwise we're all at the mercy of these big systems. You know my speech on this, but I can't help it man.
They're going to run right over our humanity. It's happening faster than we can handle, and soon...


Ghast's voice became quite agitated for that line. He sounds scared, or angry, or both...

Soon... it's all gonna be one big machine.
Hm. I've depressed myself again.
Listen, I'm glad to know you.
We had some good times, back in the day.
Alright. I'm gonna go before I get too sentimental.
See you around somewhere.
I'm out.

I can hear Ghast walking down the hall slowly.
Will this be the last time I see him?
There was an edge to his voice...


:ohdear:

The dataphones were cute, but I still need more computing power.
We need to get me some significant hardware upgrades.




This is the vote for next time's intro.