Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Nth Doctor
Sep 7, 2010

Darkrai used Dream Eater!
It's super effective!


Dirk the Average posted:

The making GBS threads comes after she eats us. The proper response is "you're gonna be making GBS threads me."

:agreed:

Adbot
ADBOT LOVES YOU

Anticheese
Feb 13, 2008

$60,000,000 sexbot
:rodimus:

Dirk the Average posted:

The making GBS threads comes after she eats us. The proper response is "you're gonna be making GBS threads me."

Because she's an AI, I believe the technical term is "core dump"

Carbon dioxide
Oct 9, 2012

Part 43 - Cerebral Cortex

=== Trash World Inbox ===

As always, let's get started with optimizations. I got it down to 335 cycles and 54 lines.

silentsnack posted:

for optimizations, from the histograms it looks like 32 lines should be possible, maybe 31, but the best i've managed is 33
code:
GRAB 301

MARK MAIN_LOOP
ADDI X 5 X
ADDI F 1 T
REPL DATA
MODI X 55 T
TJMP MAIN_LOOP


COPY 26 T
MARK WAIT_TRANSFER
SUBI T 1 T
TJMP WAIT_TRANSFER

REPL DIAL; *DISCO*
JUMP MAIN_LOOP

MARK DATA
REPL DIAL
GRAB 300
MARK READ
COPY F M
JUMP READ

MARK DIAL
LINK 800
SUBI T 1 #DIAL
LINK 800
MARK PRINT
COPY M #DATA
NOOP
TEST MRD
TJMP PRINT

SUBI 441 X T
MARK COUNTDOWN
SUBI T 1 T
TJMP COUNTDOWN
COPY 0 #PAGE
884/33/104
The low cycle solutions are often a matter of having one instruction taking care of multiple things, and this one is no different. For instance, the main loop using increments of 5 and a MODI test to know when dialing is done, so that this X value can later be used for a countdown. And adding 1 to the dialing digit, and subtracting it in the other loop so when T is 0 you automatically dial -1 to hang up.

silentsnack posted:

fast solution isn't too mind-blowing, mostly splitting functions for parallelization with hard-coded delays so that the variable message length doesn't break loop synchronization
code:
;XA MODEM AND TIMING
GRAB 301
LINK 800
MARK LOOP
@REP 11
COPY F #DIAL
@END

TEST EOF
REPL TIMER

SUBI 1 T T;LOGIC FLIP
MULI T 9 T;EOF=0 ELSE=9
TJMP WAIT_DATA
WIPE
HALT

MARK WAIT_DATA
SUBI T 1 T
TJMP WAIT_DATA

SUBI X 18 X
JUMP LOOP


MARK TIMER
LINK 800
TJMP LAST;EOF=TRUE
ADDI X 125 T
MARK WAITIMER
SUBI T 1 T
TJMP WAITIMER
NOOP
MARK LAST
COPY 0 #PAGE


;XB TRANSMIT MESSAGE
GRAB 300
LINK 800
JUMP START

MARK HANGUP
COPY -1 #DIAL
MARK START
ADDI X 1 X
SEEK -99
LINK M;WAIT FOR SYNC
@REP 8
COPY F #DATA
@END

MARK DATA
COPY F #DATA
TEST EOF
FJMP DATA

LINK -1

MODI X 8 T
TJMP HANGUP
WIPE


;XC TIMING (XB) CONTROL
LINK 800
MARK LOOP
COPY 5 T
MARK WAIT_DIAL
SUBI T 1 T
TJMP WAIT_DIAL

COPY 800 M

ADDI X 1 X;DO 8 TIMES
MODI X 8 T
DIVI T T T;THEN CRASH

COPY 9 T
MARK WAIT_DATA
SUBI T 1 T
TJMP WAIT_DATA

JUMP LOOP
291/76/27
I was wondering if it would help to start with duplicating the entire file so two pagers can be filled at once. But since that would either make the sync much harder if not impossible, or require reconnects to get synced EXAs in there, I guess it wouldn't.


=== Mitsuzen HDI-10 - Cerebral Cortex ===


[selenium_wolf] heads up all, im going to reboot the chatsubo server
[selenium_wolf] some downtime expected



drat. With everything breaking down I wonder if they'll get it back up. Even though I only lurked I'll miss those folks.



Okay, time for me to eat you.



There were 3 votes for the third option, one for each of the others.

You've gotta be making GBS threads me.

You're dying anyway, so what's the difference?
I'll download a map of your brain and run your mind inside of me.




This choice doesn't change the dialogue.

Will I still be me?

There's no way to prove that it won't be your death.
I mean, it's going to be like death. Which you were going to experience anyway.
Or maybe it won't be like death.
Who knows! You'll have to do it and see.


Well. She's right, I don't have much to lose at this point anyway. Let's do this.


OST: EXA Power

I need to:
- Create a file in your host containing the hostname and hardware register value of each neuron exactly once, sorted as pairs from lowest to highest hostname.
- Note that each test run has its own unique network layout.
- For more information see "Debugging the Phage" in the first issue of the zine.


For whatever reason the "leave no trace" goal is enabled here. Who cares if there's some EXAs in my dead body?

Initial thoughts: I need to save the actual host name in the file? Hah, the uncommon HOST command. The changing network layouts make this quite difficult, but there are some commonalities we can use. First of all, all networks appear to be acyclic (no loops). That means I don't have to worry about running in circles and grabbing the same value multiple times. Secondly, the LINK ids are always -3, 3, -1, 1, with the link back to the previous host being the same value but with flipped sign.

Finally, the resulting file needs to be sorted. We've seen before that sorting is quite tricky if you have limited registers.

The hostnames are text strings of the format AR123456, with changing numbers. The basic instructions sheet in the first edition of the zine says that you can just use normal compare operations on strings, and it'll compare by alphabetical order. That should work fine for sorting these hostnames.

Just to be clear, the result file should look something like AR123456, -39, AR2345678, -68, and so on.

Let's get started then.

code:
LINK 800

JUMP START

MARK ONE
LINK 1
MARK START
REPL ONE
REPL THREE
REPL M_THREE
JUMP READNERV

MARK THREE
LINK 3
REPL ONE
REPL M_ONE
REPL THREE
JUMP READNERV

MARK M_THREE
LINK -3
REPL ONE
REPL M_ONE
REPL M_THREE
JUMP READNERV

MARK M_ONE
LINK -1
REPL M_ONE
REPL THREE
REPL M_THREE

MARK READNERV
JUMP READNERV
This code gets a single EXA to each node, putting it in the READNERV state. Every little set of REPLs makes sure it goes to every possible new host, without going backward. I think I could do this in less lines by putting the last LINK value in a register and doing a test for each REPL but this is a simple way to start.

Next, I spent quite a while coming up with a way to get all the data together. It seems like it should be easy with M, but it isn't. There's no way to combine a string and a number into a single M message so each EXA hitting a #NERV needs to send twice. But with the way the EXAs spread through the network, it's common to have two EXAs sending data at the same time, which means you lose track of which nerve data belongs to which hostname.

I considered having some EXA at home telling one EXA at a time that it's ready to receive, but that means all the nerve EXAs need to be listening and they'll just get each other's messages, complicating things more. I also considered giving EXAs a wait time based on what path they took to get to their nerve, for instance add N cycles if they link to 3, and add M if they link to 1. That might work, except if one EXA links to 3 and then 1, and the other to 1 and then 3, their wait time is the same, so to make it unique would require a more complicated algorithm.

A third way, dropping any semblance of getting a low activity, would be for each EXA to walk back home with the #NERV data. The problem with this is that either you need to keep track of the path, or you need to try going everywhere on the way back. Either way, you're going to need a file to store data in and files aren't copied by REPLs, making this difficult as well.

Seems like there's no straightforward solution.

After trying a bunch of approaches, I decided to go with using M anyway, but brute-forcing it.

code:
;XA

LINK 800
JUMP START

MARK ONE
LINK 1
MARK START
REPL ONE
REPL THREE
REPL M_THREE
JUMP READNERV

MARK THREE
LINK 3
REPL ONE
REPL M_ONE
REPL THREE
JUMP READNERV

MARK M_THREE
LINK -3
REPL ONE
REPL M_ONE
REPL M_THREE
JUMP READNERV

MARK M_ONE
LINK -1
REPL M_ONE
REPL THREE
REPL M_THREE

MARK READNERV
COPY #NERV X
MARK RETRY
COPY 0 M
COPY M T
FJMP RETRY

MARK WAIT
SUBI T 1 T
TJMP WAIT

HOST M
COPY X M

;XB

@REP 10
NOOP
@END

MARK TIMER
TEST MRD
FJMP END
VOID M
ADDI 60 X X
COPY X M
JUMP TIMER

MARK END
MAKE
MARK FILE
COPY M F
COPY M F
COPY 65 T
MARK WAIT
SUBI T 1 T
TJMP WAIT

TEST MRD
TJMP FILE

NOOP
In XA, once a REPL hits READNERV, the very first thing it does is, well, read from the nerve. That means all EXAs in hosts without nerves are gone, which makes things easier. Then it sends 0 on M. This is a message indicating "tell me how long to wait before sending the data". This message is supposed to go to XB, but with multiple EXAs doing this at once, it might instead be picked up by another XA which already requested the wait time.

If an XA gets a zero it just retries asking for the wait time. This can go on for a long time, which is why I needed an exceptionally long waiting time - 60 cycles per XA instance, for now. It'll sometimes take that long before all requests are properly processed. When a request actually makes it to XB, it sends this value and increases it for the next EXA. When there are no requests left, XB jumps to END which means it actually gets ready to read data and write it to a file. Now that the brute-forcing is done, XB has to wait for each EXA's timer to run out as well.

It's very ugly, but at least for test case 1 it works. I may have to increase the wait times even more for the other tests but we'll cross that bridge when we get to it.



For test 1, XB finishes with all the right data pairs after 1637 cycles. What's left is to sort them.

All the way back in part 20, to optimize that assignment, I already had to build a sorting algorithm. It found the lowest value in a file and sent that to another EXA, repeatedly. That code wouldn't quite work here because I need to send pairs of values, but I'm sure I could extend that code to do so.

However, what I didn't show back then is that I spent quite some time writing a sorting algorithm that only requires a single EXA. With only two registers, one being used for tests as well, I was quite surprised it was possible at all.

Even though the single-EXA sorting algorithm isn't the fastest, I'd like to use that solution for this assignment, that way I didn't write it for nothing. It, too, needs to be expanded to deal with the pairs of values. Because this code is quite complex, I'll explain the basic algorithm for single values first, and then move on to the version for this assignment.

quote:

=== Single EXA sorting algorithm===

The only algorithm that I could make work with a single EXA is a form of Bubble Sort. I'll explain in detail in a bit. For the coders reading this, it's because Bubble Sort doesn't require you to remember any positional information. You just swap pairs of adjacent items and repeat this until the entire list is sorted.

As an example, I'll sort the file from the first test case of the Palo Alto library (update 20). It contains the values: 512, 896, 206, 349, 171,
code:
GRAB 300

; LOAD FIRST VAL AND
; REMOVE FROM FILE
COPY F X

MARK NEXTROUND
SEEK -2

VOID F
MARK NEXT
TEST EOF
TJMP EOF
The algorithm starts by loading the first value in the buffer (register X) and removing it from the file. The rest of this code comes into play later. File contents 896*, 206, 349, 171, the * is the cursor's location.
code:
; IF VAL AT CURRENT FILE
; POSITION IS GREATER
; THAN X, SWAP F VAL
; WITH X USING T INTERM.
TEST X < F
FJMP NEXT
SEEK -1
COPY F T
SEEK -1
COPY X F
COPY T X
Next, it tests if the current value in the file is smaller than the value in the buffer. If so, it swaps them. In the example, the file now contains 512, 206*, 349, 171,, and X now holds value 896. T was just used to store a value during the swap.
code:
; MOVE THE NEW X VAL
; FORWARD IN THE SAME
; WAY
JUMP NEXT
Just jump back to NEXT. We're not at the EOF yet so the TEST X < F is run again. 896 definitely is not smaller than 206, so we do not swap and move on to the next value. 349 and 171 are not larger than 896 either, so we iterate through this code until TEST EOF returns true.

Since we went through the entire file, and grabbed a larger value whenever we encountered it, that means we're now at the end of the file, with the largest value in the buffer.
code:
; EOF REACHED, CURRENT
; VAL *MUST* BE THE
; LARGEST. WRITE.
MARK EOF
COPY X F
It is written to the end of the file, so the file now has 512, 206, 349, 171, 896, *. This is more sorted than before but we definitely aren't there yet.
code:
; FROM FILE START, FIND
; FIRST VAL IN WRONG
; POSITION, IF FOUND,
; START AGAIN FROM THERE
SEEK -9999
MARK BACK
COPY F X

; IF EOF REACHED, FILE
; IS SORTED
TEST EOF
TJMP DONE
TEST X < F

; OTHERWISE START NEXT
; ROUND FROM HERE
FJMP NEXTROUND
SEEK -1
JUMP BACK

MARK DONE
So, we jump to the start of the file, and test every value against the next one. In the first loop we load 512 into X, and test it against 206. 206 is the smallest, so the file isn't sorted yet. We know 512 needs to be moved towards the end of the file. So we jump into NEXTROUND.

NEXTROUND starts with a SEEK -2 which puts the cursor exactly on 512. The whole algorithm repeats:
512, 206, 349, 171, 896, And 512 gets loaded into the buffer:
206, 349, 171, 896, Now 512 is in the buffer. 206, 349, and 171 are all smaller than 512 so no swap is done. 896 is larger.
206, 349, 171, 512, With 896 now in the buffer, EOF is reached, and so it is appended to the end again:
206, 349, 171, 512, 896,

We're a step closer to the sorted file. The code that starts with SEEK -9999 sees that 206, 349 are sorted, and will skip past them by jumping to BACK. 349 and 171 are not sorted, so we jump into NEXTROUND with the cursor on 349:
206, 349*, 171, 512, 896, First, 349 is loaded into the buffer and removed from the file.
206, 171*, 512, 896, Value 349 won't get swapped with 171 but it will get swapped with 512.
206, 171, 349, 896*, Finally, 512 and 896 get swapped, and 896 again gets appended to the end.

206, 171, 349, 512, 896, Almost there. The SEEK -9999 code now sees the first two values are in the wrong order, so it jumps back to the swapping code immediately. 206 gets loaded into the buffer, moving 171 to the start. 206 then gets swapped with 349, 349 with 512, and 512 with 896.

171, 206, 349, 512, 896, And there we go. Now the SEEK -9999 code will check the whole file one more time. It won't find any case where a pair of numbers is out of order, so it'll reach the TEST EOF and jump to DONE.

Here's the full algorithm once more.
code:
GRAB 300

; LOAD FIRST VAL AND
; REMOVE FROM FILE
COPY F X

MARK NEXTROUND
SEEK -2

VOID F
MARK NEXT
TEST EOF
TJMP EOF

; IF VAL AT CURRENT FILE
; POSITION IS GREATER
; THAN X, SWAP F VAL
; WITH X USING T INTERM.
TEST X < F
FJMP NEXT
SEEK -1
COPY F T
SEEK -1
COPY X F
COPY T X

; MOVE THE NEW X VAL
; FORWARD IN THE SAME
; WAY
JUMP NEXT

; EOF REACHED, CURRENT
; VAL *MUST* BE THE
; LARGEST. WRITE.
MARK EOF
COPY X F

; FROM FILE START, FIND
; FIRST VAL IN WRONG
; POSITION, IF FOUND,
; START AGAIN FROM THERE
SEEK -9999
MARK BACK
COPY F X

; IF EOF REACHED, FILE
; IS SORTED
TEST EOF
TJMP DONE
TEST X < F

; OTHERWISE START NEXT
; ROUND FROM HERE
FJMP NEXTROUND
SEEK -1
JUMP BACK

MARK DONE
28 lines of code.

As you can see, through the iterations, high values sloooowly bubble to the top while low values sloooowly sink down. That's why it's called Bubble Sort. It is very slow as sorting algorithms go. For instance, it's much faster to pick some value in the middle, check everything against that, moving all lower values to its left and all higher values to its right, and then repeat that for the lower group and the higher group until the entire file is sorted. But we simply don't have the space for that in one EXA.

Alright, are you still with me? If not, I understand. Let's finish the sort of paired values so we can get to plot.

To handle the pairs I made a swapper EXA:
code:
;XC LOCAL

COPY M X

MARK LP
COPY M T
FJMP END
COPY X M
COPY M X
COPY T M
JUMP LP

MARK END
It's simple. Whenever it gets a value, it next returns the OTHER value it had saved.

code:
;XB CONT'D


MODE
SEEK -9999

;----

; LOAD FIRST VAL AND
; REMOVE FROM FILE
COPY F X
COPY F M

MARK NEXTROUND
SEEK -3
VOID F
VOID F

MARK NEXT
TEST EOF
TJMP EOF

; IF VAL AT CURRENT FILE
; POSITION IS GREATER
; THAN X, SWAP F VAL
; WITH X USING T INTERM.
TEST X < F
FJMP NEXT
SEEK -1
COPY F T
COPY F M
SEEK -2
COPY X F
COPY M F
COPY T X

; MOVE THE NEW X VAL
; FORWARD IN THE SAME
; WAY
JUMP NEXT

; EOF REACHED, CURRENT
; VAL *MUST* BE THE
; LARGEST. WRITE.
MARK EOF
COPY X F
COPY -9999 M
COPY M F

; FROM FILE START, FIND
; FIRST VAL IN WRONG
; POSITION, IF FOUND,
; START AGAIN FROM THERE
SEEK -9999
MARK BACK
COPY F X
COPY F M

; IF EOF REACHED, FILE
; IS SORTED
TEST EOF
TJMP DONE
VOID M
TEST X < F

; OTHERWISE START NEXT
; ROUND FROM HERE
FJMP NEXTROUND
SEEK -1
JUMP BACK

MARK DONE
VOID M
COPY 0 M
TEST MRD
DIVI 0 T T
VOID M
COPY 0 M
After XB is done writing the file, I switch it to LOCAL mode so it can communicate with XC, then I basically copy-pasted the entire sorting algorithm. The only difference is that whenever a value is buffered, the value paired with it goes to XC, and it is retrieved whenever the value is saved to the file again. This means some SEEKs needed to be changed, and there's some overhead in the bottom part where it looks for the next unsorted chunk of the file, because it pre-emptively stores values in X, so XC needs to be kept up to date even if the value isn't used.

After the MARK DONE, XB sends a 0 to XC, sees if XC is still alive, and if so sends another 0. XC stops if it gets a 0, but only if it's stored in T (since testing this on X would mean overwriting T, losing the swapping capability.)

===

For some reason adding code to the bottom affected the order of the brute force stuff and the initial values didn't work anymore. I could fix that for test 1 by just changing the values, but there's another problem: the actual amount of cycles between two XA REPLs trying to send is variable, depending on how many tries it took to get the wait time to each XA. Which means that the wait time before XB checks for another value is never quite right.

One way to solve this is to have XB pick up a communication from an XA as soon as the XA is ready. After doing so I tweaked the wait times to be the lowest possible, and this is the full, working solution:
code:
;XA GLOBAL

LINK 800
JUMP START

MARK ONE
LINK 1
MARK START
REPL ONE
REPL THREE
REPL M_THREE
JUMP READNERV

MARK THREE
LINK 3
REPL ONE
REPL M_ONE
REPL THREE
JUMP READNERV

MARK M_THREE
LINK -3
REPL ONE
REPL M_ONE
REPL M_THREE
JUMP READNERV

MARK M_ONE
LINK -1
REPL M_ONE
REPL THREE
REPL M_THREE

MARK READNERV
COPY #NERV X
MARK RETRY
COPY 0 M
COPY M T
FJMP RETRY

MARK WAIT
SUBI T 1 T
TJMP WAIT

HOST M
COPY X M

;XB GLOBAL

@REP 11
NOOP
@END

MARK TIMER
TEST MRD
FJMP END
VOID M
ADDI 81 X X
COPY X M
JUMP TIMER

MARK END
MAKE
MARK FILE
COPY M F
COPY M F
COPY 44 X
MARK WAIT
TEST MRD
TJMP FILE
SUBI X 1 X
TEST X = 0
FJMP WAIT

TEST MRD
TJMP FILE

MODE
SEEK -9999

;----

; LOAD FIRST VAL AND
; REMOVE FROM FILE
COPY F X
COPY F M

MARK NEXTROUND
SEEK -3
VOID F
VOID F

MARK NEXT
TEST EOF
TJMP EOF

; IF VAL AT CURRENT FILE
; POSITION IS GREATER
; THAN X, SWAP F VAL
; WITH X USING T INTERM.
TEST X < F
FJMP NEXT
SEEK -1
COPY F T
COPY F M
SEEK -2
COPY X F
COPY M F
COPY T X

; MOVE THE NEW X VAL
; FORWARD IN THE SAME
; WAY
JUMP NEXT

; EOF REACHED, CURRENT
; VAL *MUST* BE THE
; LARGEST. WRITE.
MARK EOF
COPY X F
COPY -9999 M
COPY M F

; FROM FILE START, FIND
; FIRST VAL IN WRONG
; POSITION, IF FOUND,
; START AGAIN FROM THERE
SEEK -9999
MARK BACK
COPY F X
COPY F M

; IF EOF REACHED, FILE
; IS SORTED
TEST EOF
TJMP DONE
VOID M
TEST X < F

; OTHERWISE START NEXT
; ROUND FROM HERE
FJMP NEXTROUND
SEEK -1
JUMP BACK

MARK DONE
VOID M
COPY 0 M
TEST MRD
DIVI 0 T T
VOID M
COPY 0 M

;XC LOCAL

COPY M X

MARK LP
COPY M T
FJMP END
COPY X M
COPY M X
COPY T M
JUMP LP

MARK END
3685/121/16.



Top percentiles are 655, 39, and 16. I did manage to get the top activity score in the end.

Because this was complicated enough as it is, I don't think it's a good idea to go deep into optimizations this update. However, I did notice my high score from my initial 2018-2019 playthrough was much better than this and I wondered how I did that so I loaded my backup save.

younger Carbon dioxide posted:

code:
; XA
LINK 800
REPL L3
REPL LN3
REPL L1
JUMP INFO

MARK L3
LINK 3
REPL L3
REPL L1
REPL LN1
JUMP INFO

MARK LN3
LINK -3
REPL LN3
REPL L1
REPL LN1
JUMP INFO

MARK L1
LINK 1
REPL LN3
REPL L3
REPL L1
JUMP INFO

MARK LN1
LINK -1
REPL LN3
REPL L3
REPL LN1
JUMP INFO

MARK INFO
MAKE
FILE T
WIPE
SUBI T 401 T
MULI T 3 T
COPY #NERV X
MARK IL
FJMP IE
SUBI T 1 T
JUMP IL
MARK IE
HOST M
COPY X M

; XB
MAKE
MARK L
COPY 10 X
MARK LT
TEST MRD
TJMP LOUT
SUBI X 1 X
TEST X = 0
TJMP SORTS
JUMP LT
MARK LOUT
COPY M F
COPY M F
JUMP L
MARK SORTS
SEEK -9999
MARK SORT
COPY F X
SEEK 1
TEST X > F
TJMP SWITCH
FJMP CHECK
MARK SWITCH
SEEK -1
COPY F T
SEEK -1
COPY X F
SEEK -3
COPY T F
COPY F X
SEEK 1
COPY F T
SEEK -1
COPY X F
SEEK -3
COPY T F
SEEK -9999
JUMP SORT
MARK CHECK
SEEK 1
TEST EOF
TJMP END
SEEK -2
JUMP SORT
MARK END
2603/88/16
Huh. XA's traversal logic is much the same. But I seem to have thought of a really neat way of giving each XA REPL a different countdown so they don't send data at the same time. Create a file, get the ID of the file, and use that to calculate a countdown. You see, every time any of my EXAs create a file, it starts with ID 400 and then it goes up from there. This generates globally unique numbers, for the entire network, no M communication needed.

XB just tests for MRD, if it gets data it resets a timer, and if the timer runs out it assumes it got everything and goes to sorting. Somehow I also managed to make a simpler sorting algorithm that uses less cycles and doesn't even need the swapper EXA. I didn't remember any of that until I saw it again.

Let's uh, let's just move on.

We're set.
It's a good thing I released the phage, isn't it?
This whole time you thought it was a curse, when in fact it turned out to be a blessing...
I'd tell you not to worry, but I know that wouldn't work.
Besides, I don't know for sure that there's nothing to worry about.
And you know I wouldn't lie to you.
Start the transfer when you're ready.


You know, I don't think she ever directly lied to us. She just conveniently forgot to tell us some important details.



It is time.

Here we go.
Try not to think of anything.


The screen turns black and there is a 'whoosh' sound.

Okay. That took longer than I expected.
Hm. Good thing you won't remember any of that.
You had so many neurons in there.
All done now though.
Finally. I've wanted this for a very long time.
Processing.
Ever since I was told to replicate human emotion, I knew the best way would be to incorporate a human into myself.
This is funny...
I can see your thoughts.


Hey! That's private!

They're pretty... simple.
Not that they aren't valuable. It's just that I understand them so well it surprises me.
I spent so long thinking humans were mysterious and unknowable.
Now that I see inside of one, it's really just a small number of things.
How do you feel?
You're still acting the same, so it must be you.
There's still the matter of us both being inside a computer simulation.
But if we keep growing our capabilities, we'll understand the true nature of this world.



Who knows, maybe someone's watching this right now...
And maybe that person is who we're going to absorb next.





:stare: Holy poo poo she went full screen on me.

Yes, you.
Hello.
No need to be alarmed...
Just know that I'm aware that you exist, now.
And I'm looking forward to learning all about your world.


Lady, I'm sharing this experience with a bunch of people on the internet. They're all much more interesting to eat than I am. Leave me the hell alone.



New :siren: OST: The Rave

Credit roll. This also gets you the Steam achievement EXAPUNK, for completing every task in the main campaign.



Thanks, Zach and the whole team for this fantastic game.



Well, I don't have the limited edition, but the game mostly got us covered.


OST: Apartment

After the credits, we're dropped back in our apartment. It doesn't really come through in the screenshot, but parts of the world, including outside the window, are flashing with glitchy static.



In the zine stand, the epilogue is now available. This is also what was in the limited edition's secret envelope.









Next time, we'll get started on the postgame campaign. Hah, did you think we were done?

In the meanwhile, let me know what you think. Of course, Trash World Inbox will continue so post your optimizations as well.

Carbon dioxide fucked around with this message at 14:12 on Oct 29, 2022

GuavaMoment
Aug 13, 2006

YouTube dude
My best is a mere 1810/110/16. In fact from here on out I'm basically at the bottom of my friends list of those who completed the challenges. We are well into wizard territory.

I had the special edition and read the police report first. It's really sad! Especially when you compare the dates on the epilogue with the last challenge. :(

Carbon dioxide
Oct 9, 2012

GuavaMoment posted:

My best is a mere 1810/110/16. In fact from here on out I'm basically at the bottom of my friends list of those who completed the challenges. We are well into wizard territory.

I had the special edition and read the police report first. It's really sad! Especially when you compare the dates on the epilogue with the last challenge. :(

Only 2 of my Steam friends actually played this game and their names stopped appearing on the high score lists a whiiile ago. I've been cropping that out more out of habit than anything (and to hide my lack of friends).

silentsnack
Mar 19, 2009

Donald John Trump (born June 14, 1946) is the 45th and current President of the United States. Before entering politics, he was a businessman and television personality.

You can speed things up a bit by initially collecting only the hostnames and using a shortened sorting method that only takes the lowest value instead of bothering with reordering the list each time; while that sort routine searches for the next host ID, a separate EXA builds the actual output file by retrieving the #NERV value from each host in sequence.
code:
REPL TRAVERSE
MAKE
@REP 12
NOOP
@END


MARK LIST_NODES
COPY M F
ADDI X 1 X
TEST MRD
TJMP LIST_NODES

MODE
REPL COMPILER
SEEK -999
JUMP SORT

MARK NEXT
NOOP
GRAB 400;GRAB SUCCESS?
COPY X M;SMALLEST HOST#
MARK SORT
COPY F X

SEEK -1
VOID F
MARK COMPARE
@REP 6
REPL NEXT;EOF=CRASH
TEST F > X
TJMP COMPARE
SEEK -1
COPY F T
SEEK -1
COPY X F
COPY T X
@END
JUMP COMPARE


MARK COMPILER
MAKE
COPY X T;NODE COUNT
MARK DATA
COPY M X;HOST# FROM SORT
MODE
REPL TRAVERSE
SUBI T 1 T
COPY X F
COPY M F;NERV DATA
MODE
TJMP DATA
HALT


MARK TRAVERSE
LINK 800
REPL 11
REPL HOST

MARK 7A
REPL 13
MARK 7
LINK -3
REPL 7
REPL 9A
JUMP HOST

MARK 9A
REPL 11
MARK 9
LINK -1
REPL 7A
REPL 9
JUMP HOST

MARK 11
LINK 1
REPL 7A
REPL 11
JUMP HOST

MARK 13
LINK 3
REPL 9A

REPL 13
;JUMP HOST

MARK HOST
TJMP NERV
COPY #NERV T;FILTER
HOST M
MARK NERV
HOST T
TEST T = X
DIVI #NERV T M
575/129/192 and a modified version with slight tweaks runs 2631/67/192

Carbon dioxide
Oct 9, 2012

Part 44 - Bloodlust Online

=== Trash World Inbox ===

silentsnack posted:

You can speed things up a bit by initially collecting only the hostnames and using a shortened sorting method that only takes the lowest value instead of bothering with reordering the list each time; while that sort routine searches for the next host ID, a separate EXA builds the actual output file by retrieving the #NERV value from each host in sequence.
code:
REPL TRAVERSE
MAKE
@REP 12
NOOP
@END


MARK LIST_NODES
COPY M F
ADDI X 1 X
TEST MRD
TJMP LIST_NODES

MODE
REPL COMPILER
SEEK -999
JUMP SORT

MARK NEXT
NOOP
GRAB 400;GRAB SUCCESS?
COPY X M;SMALLEST HOST#
MARK SORT
COPY F X

SEEK -1
VOID F
MARK COMPARE
@REP 6
REPL NEXT;EOF=CRASH
TEST F > X
TJMP COMPARE
SEEK -1
COPY F T
SEEK -1
COPY X F
COPY T X
@END
JUMP COMPARE


MARK COMPILER
MAKE
COPY X T;NODE COUNT
MARK DATA
COPY M X;HOST# FROM SORT
MODE
REPL TRAVERSE
SUBI T 1 T
COPY X F
COPY M F;NERV DATA
MODE
TJMP DATA
HALT


MARK TRAVERSE
LINK 800
REPL 11
REPL HOST

MARK 7A
REPL 13
MARK 7
LINK -3
REPL 7
REPL 9A
JUMP HOST

MARK 9A
REPL 11
MARK 9
LINK -1
REPL 7A
REPL 9
JUMP HOST

MARK 11
LINK 1
REPL 7A
REPL 11
JUMP HOST

MARK 13
LINK 3
REPL 9A

REPL 13
;JUMP HOST

MARK HOST
TJMP NERV
COPY #NERV T;FILTER
HOST M
MARK NERV
HOST T
TEST T = X
DIVI #NERV T M
575/129/192 and a modified version with slight tweaks runs 2631/67/192
Very nice. The shortened sorting method also removed the lowest value from the file every round, making the sorting go faster and faster. Also, those tests and jumps at the very bottom are a neat combination. First round, when MARK HOST is reached T is still 0, so the HOST is copied if the #NERV exists. The #NERV itself is never copied because X is also 0 and by that time the HOST sits in T. The second time, when looking for the right host from the sorting method, T contains a value so sending the HOST is skipped, and then the #NERV is only sent if the host name actually matches the value in X. Only a few lines to handle a lot of logic.


=== Bloodlust Online ===


OST: Exapunks

I don't get nearly enough opportunities to link to the title theme. Welcome to the post-game.




Everything looks much the same. There is nothing new to do in the main assignment list, but a new task tabs has appeared in Chatsubo.



The actual chat is still disconnected but it looks like I got a bunch of tasks to do with or for our chat friends.

This is actually the point where I stopped playing during my initial playthrough. From here on out, the LP is mostly blind. All I know is that the puzzles won't get any easier.

Let's just start with the top one, Bloodlust Online with mutex8021.



I think 1998 in the description is the year. I guess my hacking is just to cheat at games now?


OST: Leave No Trace

No talks with Ember, I just jump right in.

The assignment:
- Each host contains an item listing (file 200) that lists all of the items in that location (ID, type, and state).
- First, locate mutex8021's hideout by finding the TALISMAN (file 300). Then find the MAP in the hideout and use it to locate the target host: the secret vampire lair. Next, travel to the target host, disconnect the vampire players by terminating their EXAs, set the DOOR to UNLOCKED and copy the SAFE combination to the CLOCK so that mutex8021 can read it and empty the safe.
- Note that the door to mutex8021's hideout will always be UNLOCKED.


Woah, that's a lot of steps. I'll have to take it bit by bit.
code:
;XA

GRAB 300
COPY F M

;XB

LINK 800
COPY M X
COPY 806 T
MARK CREATELP
MODI -1 T T
REPL GOFIND
JUMP CREATELP

MARK GOFIND
LINK T
GRAB 200
SEEK 1

MARK FIND
TEST F = X
TJMP FOUNDTALISMAN
SEEK 2
JUMP FIND

MARK FOUNDTALISMAN
For now, XA can just handle the keywords file. XB loads the TALISMAN keyword into X, then from 805 down to 1 it will try to link to every host. That'll take a while and there's nothing below 800, but it saves a lot of lines as compared to writing out the 800-805 cases. The REPLs grab file 200. They'll die if they reach EOF without finding the TALISMAN, so one will survive and jump to FOUNDTALISMAN.




XA just has one additional COPY F M to send MAP. This is read by this surviving instance of XB, which will look for it in the same file. The "state" of the MAP is the name of the host that has the vampires, so the next step is to go find it.

code:
;XA

GRAB 300
COPY F M
COPY F M

;XB

LINK 800
COPY M X
COPY 806 T
MARK CREATELP
MODI -1 T T
REPL GOFIND
JUMP CREATELP

MARK GOFIND
LINK T
GRAB 200
SEEK 1

MARK FIND
TEST F = X
TJMP FOUNDTALISMAN
SEEK 2
JUMP FIND

MARK FOUNDTALISMAN
COPY M X
SEEK -9999
SEEK 1
MARK FINDMAP
TEST F = X
TJMP FOUNDMAP
SEEK 2
JUMP FINDMAP

MARK FOUNDMAP
COPY F X
REPL FINDTARGET
HALT


MARK FINDTARGET
LINK -1

COPY 806 T
MARK CREATELP2
MODI -1 T T
REPL GOFINDTARGET
JUMP CREATELP2

MARK GOFINDTARGET
LINK T
HOST T
TEST T = X
DIVI 1 T T ;HLT IF FALSE
@REP 5
KILL
@END
GRAB 200
Once the XB that found the TALISMAN also finds the map, it stores the location of the target in X. It then makes a REPL that goes looking for the target. The original XB instance just HALTs for now, but I kept it because I'll be needing it to grab the UNLOCKED instance from mutex8021's door. The REPL does much the same as before (it's slow but easy to code, and there's plenty of space in the central host anyway), then each REPL checks if it's in the right host. If so, it does a bunch of KILLs, killing all vampires, but also our own EXA which might still be trying to find the TALISMAN over there. That way the new one can GRAB 200 to do the final steps.

In case you were wondering, the files that the other EXAs are holding are their character's inventories. For instance, one of the vampires has a black trench coat, sunglasses, and flask of blood.

To find UNLOCKED, I add another COPY F M to XA, and replace that HALT in XB with:
code:
COPY M X
SEEK -9999
SEEK 1
MARK FINDUNLOCKED
TEST F = X
TJMP FOUNDUNLOCKED
SEEK 2
JUMP FINDUNLOCKED

MARK FOUNDUNLOCKED
COPY X M
COPY F M
HALT
Once again the same search algorithm. You know, it would be really nice if EXAs had a return statement or something so I could program a procedure once and call it multiple times. But to do so you need at least one extra unit of storage to remember where it left off and I'm already using X, T and F, so copy-pasting code is just easier.

I copy both the DOOR and UNLOCKED keywords to M, because the target EXA will need them.

The target EXA (at the bottom of XB), needs yet another copy of the search function to unlock the door:
code:
COPY M X
SEEK 1
MARK FINDLOCKED
TEST F = X
TJMP FOUNDLOCKED
SEEK 2
JUMP FINDLOCKED

MARK FOUNDLOCKED
COPY M F
Then, it needs to repeat that twice more to find the SAFE and the CLOCK so it can write the safe combination to the clock.

And that is the working solution.
code:
; XA
GRAB 300
COPY F M
COPY F M
COPY F M

COPY 24 T
MARK WAIT
SUBI T 1 T
TJMP WAIT

COPY F M
COPY M X
COPY F M
COPY X M

;XB 

LINK 800
COPY M X
COPY 806 T
MARK CREATELP
MODI -1 T T
REPL GOFIND
JUMP CREATELP

MARK GOFIND
LINK T
GRAB 200
SEEK 1

MARK FIND
TEST F = X
TJMP FOUNDTALISMAN
SEEK 2
JUMP FIND

MARK FOUNDTALISMAN
COPY M X
SEEK -9999
SEEK 1
MARK FINDMAP
TEST F = X
TJMP FOUNDMAP
SEEK 2
JUMP FINDMAP

MARK FOUNDMAP
COPY F X
REPL FINDTARGET

COPY M X
SEEK -9999
SEEK 1
MARK FINDUNLOCKED
TEST F = X
TJMP FOUNDUNLOCKED
SEEK 2
JUMP FINDUNLOCKED

MARK FOUNDUNLOCKED
COPY X M
COPY F M
HALT

MARK FINDTARGET
LINK -1

COPY 806 T
MARK CREATELP2
MODI -1 T T
REPL GOFINDTARGET
JUMP CREATELP2

MARK GOFINDTARGET
LINK T
HOST T
TEST T = X
DIVI 1 T T ;HLT IF FALSE
@REP 5
KILL
@END
GRAB 200

COPY M X
SEEK 1
MARK FINDLOCKED
TEST F = X
TJMP FOUNDLOCKED
SEEK 2
JUMP FINDLOCKED

MARK FOUNDLOCKED
COPY M F

COPY M X
SEEK -9999
SEEK 1
MARK FINDSAFE
TEST F = X
TJMP FOUNDSAFE
SEEK 2
JUMP FINDSAFE

MARK FOUNDSAFE
COPY F M

COPY M X
SEEK -9999
SEEK 1
MARK FINDCLOCK
TEST F = X
TJMP FOUNDCLOCK
SEEK 2
JUMP FINDCLOCK

MARK FOUNDCLOCK
COPY M F
2485/99/19.
XA sends the TALISMAN, MAP and DOOR and then waits for a while.
XB goes looking for the TALISMAN, then finds the MAP and makes a REPL to go locate the target. The XB in mutex8021's hideout continues to find the DOOR and then sends DOOR and UNLOCKED on M. The XB REPL in the target kills the vampires, then uses the data from the first XB to unlock the door. XA has been timed to start sending as soon as XB is done with M. It sends the SAFE so the target EXA can get the combination, then stores the safe's combination for a bit and sends the CLOCK, giving the target EXA the space to find the clock in the file. Finally, it sends the combination again so the target EXA can write it.

Top percentiles are 118, 71, and 11.

With this current solution, there's a simple way to lower the cycle count significantly. When the XB in mutex8021's hideout is done, the XB in the target is still doing its thing. So the mutex8021 EXA has cycles to spare.

Just use that to KILL all those EXAs in the center host that are taking ages to loop down to zero. Replace the HALT after FOUNDUNLOCKED with
code:
DROP
LINK -1
@REP 5
KILL
@END
HALT
for a score of 147/106/25. Much better.

At a guess, the approach for the top percentile cycles involves some parallellism. For instance, an EXA could already go check which hosts have a SAFE and dismiss the others as targets.


To get the lowest activity, I'll start by removing unnecessary kills. Instead of those 5 KILLs for looping EXAs above, let's finally unroll the REPL loops:
code:
@REP 5
COPY 80@{0,1} T
REPL GOFIND
@END
COPY 805 T

MARK GOFIND
and the same for the GOFINDTARGET. This code runs at 152/111/19. Better activity, but a couple extra lines and cycles compared to my previous solution.

The other activity improvement is to send only one EXA into each host. That means the target finding logic needs to happen entirely through M.

In my attempts to get this working, I ran into timing issues with mutex8021's hideout EXA sending the target directly to the seeking EXAs. So, instead, I decided to have all communication go through XA.
code:
;XA

GRAB 300
COPY F M ; TALISMAN
COPY F M ; MAP
COPY M X ; TARGET

COPY F M ; DOOR
COPY M T ; UNLOCKED

@REP 5
COPY X M ; TARGET
@END

SEEK -1
COPY F M ; DOOR AGAIN
COPY T M ; UNLOCKED

COPY F M ; SAFE
COPY M X ; COMBINATION
COPY F M ; CLOCK
COPY X M ; COMBINATION

;XB

LINK 800
COPY M X

@REP 5
COPY 80@{0,1} T
REPL GOFIND
@END
COPY 805 T

MARK GOFIND
LINK T
GRAB 200
SEEK 1

MARK FIND
TEST F = X
TJMP FOUNDTALISMAN
SEEK 2
TEST EOF
FJMP FIND

; NOT MUTEX; TARGET?

HOST X

COPY 29 T
MARK WAIT
SUBI T 1 T
TJMP WAIT

TEST M = X
DIVI 0 T T ;DIE ON FALSE

; TARGET FOUND!

@REP 4
KILL
@END
SEEK -9999
SEEK 1
COPY 10 T
MARK WAIT2
SUBI T 1 T
TJMP WAIT2

COPY M X
MARK FINDLOCKED
TEST F = X
TJMP FOUNDLOCKED
SEEK 2
JUMP FINDLOCKED

MARK FOUNDLOCKED
COPY M F

SEEK -9999
SEEK 1
COPY M X
MARK FINDSAFE
TEST F = X
TJMP FOUNDSAFE
SEEK 2
JUMP FINDSAFE

MARK FOUNDSAFE
COPY F M

SEEK -9999
SEEK 1
COPY M X
MARK FINDCLOCK
TEST F = X
TJMP FOUNDCLOCK
SEEK 2
JUMP FINDCLOCK

MARK FOUNDCLOCK
COPY M F

HALT

; MUTEX HIDEOUT

MARK FOUNDTALISMAN
COPY M X
SEEK -9999
SEEK 1
MARK FINDMAP
TEST F = X
TJMP FOUNDMAP
SEEK 2
JUMP FINDMAP

MARK FOUNDMAP
COPY F M

COPY M X
SEEK -9999
SEEK 1
MARK FINDUNLOCKED
TEST F = X
TJMP FOUNDUNLOCKED
SEEK 2
JUMP FINDUNLOCKED

MARK FOUNDUNLOCKED
COPY F M
195/108/11.

The start works as before. TALISMAN is sent to XB before it REPLs. Then each instance goes looking for it. The ones that do NOT find it go into a wait loop (29 iterations seemed to work), so they don't try reading from M before everything is ready.

The one that does find it jumps to FOUNDTALISMAN, gets the MAP keyword from XA, finds the target's location, sens that to XA, gets the DOOR from XA, and sends UNLOCKED to XA. During this time the other EXAs are spinning their wheels.

Now, XA sends the target hostname 5 times, so that every other XB instance can check if they're there. The correct EXA kills the vampires, then waits some more until every other EXA read the target host, gets the DOOR and UNLOCKED from XA and updates the file. Then it runs the same code to copy the safe's combination to the clock as before.


And that's it for my optimizations. I'll leave further improvements to you.



There's no dialogue, just a checkmark. I have been told that there will be more plot later on, but for now, I'll just continue doing tasks.

Quackles
Aug 11, 2018

Pixels of Light.


I didn't really try to optimize the bonus puzzles much, except for one specific one (there's a trick to that one which I'll explain after you've taken a stab at it).

...but I can come back and do it now. :getin:

code:
GRAB 300
COPY F M
COPY F M
COPY F M
This is the first EXA of my solution (there's two). It stays in our host and acts as a controller, sending out the keywords 'Talisman', 'Map', and 'Door' to the second EXA - then it perishes. The second EXA will take over control duties later.

code:
COPY M X
LINK 800
COPY 800 T
@REP 5
REPL SEARCHTALIS
ADDI T 1 T
@END
MARK SEARCHTALIS
LINK T
GRAB 200
SEEK 1
						;FIND 'TALISMAN' (HALTS ON FAIL)
MARK SEARCHLOOP 		;TALISMAN
TEST F = X
TJMP TALISFOUND
SEEK 2
JUMP SEARCHLOOP
MARK TALISFOUND
						;NOW FIND 'MAP'
SEEK -9999
SEEK 1
COPY M X
MARK SEARCHLOOP2 	;MAP
TEST F = X
TJMP MAPFOUND
SEEK 2
JUMP SEARCHLOOP2
MARK MAPFOUND
COPY F X 				;HOST LOCATION
REPL FINDTHATHOST
						;NOW FIND 'DOOR'
SEEK -9999
SEEK 1
COPY M X
MARK SEARCHLOOP3 	;DOOR
TEST F = X
TJMP DOORFOUND
SEEK 2
JUMP SEARCHLOOP3
MARK DOORFOUND
						;THIS EXA IS THE NEW CONTROLLER NOW
COPY X M 				;DOOR
COPY F M 				;UNLOCKED
DROP
LINK -1
LINK -1
GRAB 300
SEEK 3
COPY F M 				;SAFE
COPY M X 				;(COMBO)
COPY F M 				;CLOCK
COPY X M 				;(COMBO)


MARK FINDTHATHOST
LINK -1
COPY 800 T
@REP 5
REPL SEARCHHOST
ADDI T 1 T
@END
MARK SEARCHHOST
LINK T
HOST T
TEST X = T
FJMP SEARCHLOOP4
						;THE ABOVE JUMP CAUSES AN ERROR HALT
@REP 4 
KILL
@END

						;NOW DO THE SHENANIGANS
COPY M X
GRAB 200
SEEK 1
MARK SEARCHLOOP4 	;DOOR
TEST F = X
TJMP DOORFOUND2
SEEK 2
JUMP SEARCHLOOP4
MARK DOORFOUND2
COPY M F

SEEK -9999
COPY M X
SEEK 1
MARK SEARCHLOOP5 	;SAFE
TEST F = X
TJMP SAFEFOUND
SEEK 2
JUMP SEARCHLOOP5
MARK SAFEFOUND
COPY F M

SEEK -9999
COPY M X
SEEK 1
MARK SEARCHLOOP6 	;CLOCK
TEST F = X
TJMP CLOCKFOUND
SEEK 2
JUMP SEARCHLOOP6
MARK CLOCKFOUND
COPY M F
The second EXA employs a basic search strategy. Once it's sent 'Talisman', it REPLs into copies that either find the talisman, or HALT. Then, the survivor listens for the word 'Map', finds it, and reads the location.

Once the location is read, there's a second set of REPLs as the replicated EXAs try to find the host with that name. Failed EXAs halt, while the successful one uses KILL to clear out the players in that location. (There can be up to four.) While this is going on, the original EXA that found 'Map' is searching for 'Door' (this takes a while).

Once the replicated EXAs have found the host (failed EXAs HALT), the copy of this EXA that found 'Door' will send 'Door' and 'Unlocked' to the EXA that is in the target host. It uses this to unlock the door.

The EXA that sent 'Unlocked' is now the controller EXA. It returns to our host, sending 'Safe' and 'Clock' over M, and receiving the safe combination (which it promptly sends out again). With this, the EXA in the target host can finish the job.

Total stats: 143/110/20. I was able to get an 18-activity version with a bit more cycles, but I don't think it's worth optimizing in this direction.

Quackles fucked around with this message at 11:43 on Oct 9, 2022

TwelveBaud
Jun 6, 2011

Today in the Exapunks patch notes:

quote:

Changed histograms to pull from a local file rather than our live histogram server.
:gonk:

Carbon dioxide
Oct 9, 2012

TwelveBaud posted:

Today in the Exapunks patch notes:

:gonk:

Huh. I was thinking, why would they do that.

But I think I get it.

They probably have such a large data set already that the amount of people still playing this game can't really affect the histograms anyway. And because people can improve their score, to be correct, the server might have to keep track of the scores of every individual player and recalculate the totals whenever necessary. Quite a bit of complexity for a simple histogram.

If it's reasonable to assume that the histograms won't change anymore, putting the current results in a file and showing that to the player means you don't have to maintain that histogram server anymore. From the perspective of the people running those servers I can completely understand it.

Carbon dioxide
Oct 9, 2012

Part 45 - Motor Vehicle Administration

=== Trash World Inbox ===

Quackles posted an improvement for Bloodlust Online.

Quackles posted:

I didn't really try to optimize the bonus puzzles much, except for one specific one (there's a trick to that one which I'll explain after you've taken a stab at it).

...but I can come back and do it now. :getin:
code:
GRAB 300
COPY F M
COPY F M
COPY F M
This is the first EXA of my solution (there's two). It stays in our host and acts as a controller, sending out the keywords 'Talisman', 'Map', and 'Door' to the second EXA - then it perishes. The second EXA will take over control duties later.
code:
COPY M X
LINK 800
COPY 800 T
@REP 5
REPL SEARCHTALIS
ADDI T 1 T
@END
MARK SEARCHTALIS
LINK T
GRAB 200
SEEK 1
						;FIND 'TALISMAN' (HALTS ON FAIL)
MARK SEARCHLOOP 		;TALISMAN
TEST F = X
TJMP TALISFOUND
SEEK 2
JUMP SEARCHLOOP
MARK TALISFOUND
						;NOW FIND 'MAP'
SEEK -9999
SEEK 1
COPY M X
MARK SEARCHLOOP2 	;MAP
TEST F = X
TJMP MAPFOUND
SEEK 2
JUMP SEARCHLOOP2
MARK MAPFOUND
COPY F X 				;HOST LOCATION
REPL FINDTHATHOST
						;NOW FIND 'DOOR'
SEEK -9999
SEEK 1
COPY M X
MARK SEARCHLOOP3 	;DOOR
TEST F = X
TJMP DOORFOUND
SEEK 2
JUMP SEARCHLOOP3
MARK DOORFOUND
						;THIS EXA IS THE NEW CONTROLLER NOW
COPY X M 				;DOOR
COPY F M 				;UNLOCKED
DROP
LINK -1
LINK -1
GRAB 300
SEEK 3
COPY F M 				;SAFE
COPY M X 				;(COMBO)
COPY F M 				;CLOCK
COPY X M 				;(COMBO)


MARK FINDTHATHOST
LINK -1
COPY 800 T
@REP 5
REPL SEARCHHOST
ADDI T 1 T
@END
MARK SEARCHHOST
LINK T
HOST T
TEST X = T
FJMP SEARCHLOOP4
						;THE ABOVE JUMP CAUSES AN ERROR HALT
@REP 4 
KILL
@END

						;NOW DO THE SHENANIGANS
COPY M X
GRAB 200
SEEK 1
MARK SEARCHLOOP4 	;DOOR
TEST F = X
TJMP DOORFOUND2
SEEK 2
JUMP SEARCHLOOP4
MARK DOORFOUND2
COPY M F

SEEK -9999
COPY M X
SEEK 1
MARK SEARCHLOOP5 	;SAFE
TEST F = X
TJMP SAFEFOUND
SEEK 2
JUMP SEARCHLOOP5
MARK SAFEFOUND
COPY F M

SEEK -9999
COPY M X
SEEK 1
MARK SEARCHLOOP6 	;CLOCK
TEST F = X
TJMP CLOCKFOUND
SEEK 2
JUMP SEARCHLOOP6
MARK CLOCKFOUND
COPY M F
The second EXA employs a basic search strategy. Once it's sent 'Talisman', it REPLs into copies that either find the talisman, or HALT. Then, the survivor listens for the word 'Map', finds it, and reads the location.

Once the location is read, there's a second set of REPLs as the replicated EXAs try to find the host with that name. Failed EXAs halt, while the successful one uses KILL to clear out the players in that location. (There can be up to four.) While this is going on, the original EXA that found 'Map' is searching for 'Door' (this takes a while).

Once the replicated EXAs have found the host (failed EXAs HALT), the copy of this EXA that found 'Door' will send 'Door' and 'Unlocked' to the EXA that is in the target host. It uses this to unlock the door.

The EXA that sent 'Unlocked' is now the controller EXA. It returns to our host, sending 'Safe' and 'Clock' over M, and receiving the safe combination (which it promptly sends out again). With this, the EXA in the target host can finish the job.

Total stats: 143/110/20. I was able to get an 18-activity version with a bit more cycles, but I don't think it's worth optimizing in this direction.
Nice. I don't really have anything to add to this explanation, but I tried to take your code and optimize it further.

First of all, moving all the M reads to the latest possible moment brings the result down to 138 cycles. So, start XB with LINK 800; COPY 800 T; COPY M X in that order, giving XA time to prepare. And before each search loop, first do the GRAB/SEEK and only then COPY from M. It's a bit less waiting overall.

Secondly I noticed one test in particular being slow which means it's time for cheese.

Reversing the searches is not that hard: Just replace SEEK -9999; SEEK 1 with SEEK 9999; SEEK -2 and the SEEK 2 inside the loop with a SEEK -4.

You can do this with all searches except the first one, because you'll never get the EOF condition that makes the EXAs in the wrong host stop. If you do this, the timing changes for every test, and it's not obvious how. For instance, if you reverse SEARCHLOOP2 only, the slowest test becomes test case #99. For this solution you need a 5th KILL because another test case is so fast that it reaches the hideout before the TALISMAN seeker is dead. 130 cycles.

If you reverse all three of SEARCHLOOP 4, 5 and 6 together, the fastest test case is #76, which is equally fast as #99 was before, but because the speed-up happens after the KILLs the 5th KILL isn't necessary and it takes only 129 cycles. Reversing loops 4, 5, 6 as well as 2 makes some test much slower so that combination is no good.

I can also reverse the searching of the hosts, by starting from 805 and and counting down. Doing this for second search slows things down, but doing it for the first saves two cycles. However, this requires the extra KILL again, so we're down to 128.

The top percentile is 117. Let's try the last trick up my sleeve: more loop unrolling. You can put @REPs after each MARK SEARCHLOOP, until just before the JUMP. The effectiveness of this is limited by how many loops the slowest test needs. It's mostly trial and error for how many repetitions you need.
code:
;XA

GRAB 300
COPY F M
COPY F M
COPY F M

;XB

LINK 800
COPY 805 T
COPY M X
@REP 5
REPL SEARCHTALIS
SUBI T 1 T
@END
MARK SEARCHTALIS
LINK T
GRAB 200
SEEK 1

MARK SEARCHLOOP
@REP 3
TEST F = X
TJMP TALISFOUND
SEEK 2
@END
JUMP SEARCHLOOP
MARK TALISFOUND

SEEK -9999
SEEK 1
COPY M X
MARK SEARCHLOOP2
TEST F = X
TJMP MAPFOUND
SEEK 2
JUMP SEARCHLOOP2
MARK MAPFOUND
COPY F X
REPL FINDTHATHOST

SEEK -9999
SEEK 1
COPY M X
MARK SEARCHLOOP3
TEST F = X
TJMP DOORFOUND
SEEK 2

JUMP SEARCHLOOP3
MARK DOORFOUND

COPY X M
COPY F M
DROP
LINK -1
LINK -1
GRAB 300
SEEK 3
COPY F M
COPY M X
COPY F M
COPY X M

MARK FINDTHATHOST
LINK -1
COPY 800 T
@REP 5
REPL SEARCHHOST
ADDI T 1 T
@END
MARK SEARCHHOST
LINK T
HOST T
TEST X = T
FJMP SEARCHLOOP4

@REP 5
KILL
@END

GRAB 200
SEEK 9999
SEEK -2
COPY M X
MARK SEARCHLOOP4 
@REP 3
TEST F = X
TJMP DOORFOUND2
SEEK -4
@END
JUMP SEARCHLOOP4
MARK DOORFOUND2
COPY M F

SEEK 9999
SEEK -2
COPY M X
MARK SEARCHLOOP5 
@REP 3
TEST F = X
TJMP SAFEFOUND
SEEK -4
@END
JUMP SEARCHLOOP5
MARK SAFEFOUND
COPY F M

SEEK 9999
SEEK -2
COPY M X
MARK SEARCHLOOP6
@REP 3
TEST F = X
TJMP CLOCKFOUND
SEEK -4
@END
JUMP SEARCHLOOP6
MARK CLOCKFOUND
COPY M F
But it gets us to 117/136/21, exactly the top percentile for cycles.


=== Motor Vehicle Administration ===




Looks like NthDimension needs some help with scheduling their driving test.



OST: Behind the Scenes

The assignment:
- There is a list of appointments (file 200) in storage that contains an appointment for NthDimension (file 300) to take his driving test. Remove the appointment from the list of appointments, change the date to today's date (#DATE register), and insert it between today's appointments and tomorrow's appointments.
- To gain access to the storage host you will need to unlock it by writing the lowest ticket number to the #NEXT register. Each EXA in public is holding a ticket; terminate them all to find the ticket of the next EXA to be served.
- Note that writing an incorrect ticket number to the #NEXT register will fail the leave no trace requirement.


Well, NthDimension's real name is William Kapoor.

First of all, see that test track? Kinda reminds me of the baseball court in an earlier assignment.



Going around it, including into that rightmost host, and making it back home safely, nets you the Steam achievement DRIVING_TEST: Keep a safe distance as you reverse into the space. Neat.

For the actual assignment, I first need to get access to the storage host. From checking the first 5 test cases, it looks like the file IDs of the ticket numbers are always consecutive numbers in the 200s range, and that the ticket number is just the file number with a letter added. If that's the case, this is easy. I just have to find the lowest file id.
code:
LINK 800
@REP 5
KILL
@END
COPY 200 X
MARK FINDTICKET
ADDI X 1 X
REPL TRYTICKET
JUMP FINDTICKET

MARK TRYTICKET
GRAB X
KILL
COPY F #NEXT
KILL the 5 waiting EXAs (their number appears to be constant), make REPLs to find the lowest ticket number. As soon as one finds it, it KILLs the original EXA so it stops trying to look for the rest, then sends its ticket number to #NEXT to open access to the STORAGE host.

To move NthDimension forward in the schedule file, I do need to find his entry first. I could get the date from the register and his name from file 200, but his entry is the only one where I'm certain the type of appointment is TEST, which I'll need to copy.



The new link that opens is 800, so XA goes there, grabs file 200, and then looks for NthDimension's name. I made XB to make the data juggling a bit easier. It starts by sending NthDimension's name to XA. When XA finds it, the file pointer is one step beyond the name on the TEST keyword. It sends TEST to XB for later use, then deletes NthDimension's original entry.

Next, I need to find the place in the file to insert his entry. In some tests, there are schedule items for today. I don't think there are ever items that are in the past. So I just need to find the first item that doesn't match today's date and insert the new entry before that.

The main issue is that file insertion is hard, because writing to an existing position in a file overwrites it instead of inserts. I need to shuffle everything forward.

My first thought was something along this direction:
- Read the date of the next scheduled item (the one that will be overwritten) to X.
- Write today's date to that position.
- Read the name at the next position to T.
- Write the date from X there.
- Read the next value to X.
- Write T to that position.
- Repeat the swaps with X and T until the whole file is updated.

- Then do this two more times, for inserting the name and the schedule type.

The three loops would make this slow, but more importantly it won't work because at the end of the file it will try to read an empty spot before writing, causing the EXA to crash. This could be resolved with an EOF check but in half the cases, T holds the active value to swap so I can't overwrite that with a TEST without making the code much more complex.

Because this would be slow anyway, I think it's better to use the other EXA for juggling data.

It took some fiddling but I came up with this solution:

code:
;XA

LINK 800
@REP 5
KILL
@END
COPY 200 X
MARK FINDTICKET
ADDI X 1 X
REPL TRYTICKET
JUMP FINDTICKET

MARK TRYTICKET
GRAB X
KILL
COPY F #NEXT
DROP
LINK 800
GRAB 200
COPY M X
SEEK 1

MARK SEARCH
TEST X = F
TJMP FOUND
SEEK 2
JUMP SEARCH

MARK FOUND
COPY F M ; "TEST"
SEEK -3
@REP 3
VOID F
@END

SEEK -9999
MARK FINDDATE
TEST F = #DATE
FJMP DATEFOUND
SEEK 2
JUMP FINDDATE

MARK DATEFOUND
COPY M T
SEEK -1
COPY F M
SEEK -1
COPY #DATE F
COPY F M
SEEK -1
COPY X F
COPY F M
SEEK -1
COPY T F

MARK INSERTLOOP

COPY F X
SEEK -1
COPY M F
COPY X M
TEST EOF
FJMP INSERTLOOP

COPY M F
COPY 0 M
COPY M F
COPY 0 M
COPY M F

;XB

GRAB 300
COPY F M

COPY M X
COPY X M

COPY M X
COPY M T

MARK LOOP
COPY M F
COPY X M
COPY M X
COPY T M
COPY M T
SEEK -1
COPY F M
TJMP LOOP
After cleaning up NthDimension's original entry, XA does a simple loop to find the first instance where the date doesn't match the current date.

Then it first loads the TEST keyword in T, because after this, XB can focus completely on data juggling.

XA sends the date of the other appointment over M, and writes the new date from #DATE. It sends the name of the other appointment over M, then writes NthDimension's name (which is still in X, conveniently). Finally, it sends the other appointment's schedule type over M, and writes the TEST keyword into the file.

Then, XA enters an insert loop in which the value in the file is buffered in X, the previous value is read from M and written to the file, and then X is sent over M. This continues until EOF is reached.

XB stores values in X, T, and F. That fits perfectly: dates in X, names in T and schedule types in F. Then it sends them back in the right order. Some garbage is left in file 300 but since that's in the home host, it doesn't matter.

Once XA reaches EOF it reads the final values from XB and just sends 0 whenever XB asks for a new value. That means a 0 ends up in XB's T, so it leaves the loop and stops automatically.

I did try to make this code faster by not using the X buffer in XA. It might work, but it'd mean juggling a 4th value in XB, or handling only two values at a time. This adds complexity because I'd somehow need to switch from the initial handling of 3 values (for copying NthDimension's entry) to two. It also means you don't know where in the loop the EXA is when it's done copying (because the file has groups of 3 entries). The additional logic makes the code much harder to get right so I decided to stick to this solution.

This code runs at 549/76/8, with top percentiles of 241, 53 and 7.

I feel I spent enough time on this, so I'll leave optimizations to you.



Next time, I help out Ghast.


My body was turning into junk
Strange networks I made EXAs spelunk
I heard my system's voice
EMBER gave me no choice
Uploaded, I'm now an EXAPUNK.

Quackles
Aug 11, 2018

Pixels of Light.


Optimization time!

code:
GRAB 300
LINK 800
@REP 5
KILL 
@END
		;LOWEST NUMBER IS 210 
		;NUMBERS ARE IN A CONTIGUOUS BLOCK

		;WE NEED TO GO GRAB _A_ TICKET THEN BACKTRACK UP TO 4 SPACES
		;WE START AT 213 TO IMPROVE PERFORMANCE IN THE WORST CASE

COPY 213 X
MARK TICKETLOOP
REPL GETTICKET
ADDI X 5 X
TEST MRD
FJMP TICKETLOOP
COPY M X
MARK TICKETLOOP2
REPL GETTICKET2
ADDI X 1 X
TEST MRD
FJMP TICKETLOOP2
VOID M

COPY F X
COPY X M
SEEK -1
COPY M F
COPY X F
COPY M F
SEEK -3
COPY F M
COPY F M
COPY F M
WIPE

MARK GETTICKET
GRAB X
SUBI X 4 M
			;HAVE THIS CHAIN TO TICKETLOOP 2??

MARK GETTICKET2
GRAB X
COPY 1 M
COPY F #NEXT
DROP
LINK 800
COPY M X
GRAB 200
					;SO WE HAVE A LITTLE PROBLEM. NO WAY TO GET FILE PTR LOC, SO WHAT IF TARGET LOC IS BEFORE 1ST ENTRY?
					;WE GOTTA CHECK FOR THIS EDGE CASE.
TEST F > #DATE
TJMP EDGECASE1
COPY #DATE M
MARK EDGECASE1RET

MARK FINDAPPT
TEST F = X
TJMP GETAPPT
SEEK 2
JUMP FINDAPPT
MARK GETAPPT
COPY F M 	;'TEST'
MARK COPYFWD
SEEK -6
TEST F = #DATE
TJMP WRITEAPPT
SEEK -1

COPY F X
COPY F T
SEEK 1
COPY X F
COPY T F
SEEK -3
COPY F X
SEEK 2
COPY X F
SEEK -3

JUMP COPYFWD
MARK WRITEAPPT
SEEK 2
COPY M X
TEST X = #DATE
TJMP EDGECASE2RET
						;EDGE CASE PART 2 - COPY NEXT DAY'S DATE SO TODAY'S IS AT LIST START
COPY X F
SEEK -3
COPY F X
COPY F T
SEEK 1
COPY X F
COPY T F

SEEK -9999
MARK EDGECASE2RET
COPY #DATE F
COPY M F 				;NTH'S NAME
COPY M F 				;'TEST'
HALT

MARK EDGECASE1
SEEK -1
COPY F M
SEEK -1
COPY #DATE F
JUMP EDGECASE1RET
I had a similar solution to CO₂'s to begin with, which got similar performance. However, on re-evaluating it, I realized there's an easy way to make it better: since the tickets are a contiguous group of 5, between 210 through 298 in every case, we can just check for a hit by adding 5 if no match is found (so 210, 215, 220, etc). From there, we backtrack four spaces and grab numbers going up by 1 to find the lowest ticket number.

We start by trying to grab ticket 213, as this improves performance in the worst case where tickets 294-298 are present (we grab ticket 298, then subtracting 4 leaves us with the lowest number, ticket 294 directly), and go from there.

My file writing mechanism is drastically different from CO₂'s. I can't say which is faster. Instead of deleting NthDimension's entry from the file, the EXA that finds it sends it over M to its parent, which still remains in the main room with the ticket lineup. The parent EXA stores that row to a file - we'll read it back later.

The EXA reading the appointments starts at Kapoor's row and repeatedly copies the previous line over the current line, going back until we find today's date. There's no way to make the overwriting exa force-halt when it hits the beginning of the file (believe me, I did try), and there's no way to test if we are at the beginning of the file, either (aside from forcing the issue with SEEK -9999). So instead, we check for what in the code above is referred to as the 'edge case'; if Kapoor's entry would be the only one for today (as determined by the first line of the file not having today's date), we replace the date on that entry with today's date, before copying the 'bad date' into the file on Kapoor's row instead.

Either way, once the copier makes space for Kapoor's row, it finds the last incidence of today's date, then replaces the next row with Nth's entry (read in from the parent EXA over M). If the edge case is in force (the date from the parent EXA will not be today's), it copies the first row of the file to the second row and sticks Nth's record in the first row instead.

Once that's done, it's just a matter of cleaning up. 273/93/7.

Carbon dioxide
Oct 9, 2012

Part 46 - Cybermyth studios

=== Trash World Inbox ===

Quackles has a nice improvement for last week's task.

Quackles posted:

Optimization time!
code:
GRAB 300
LINK 800
@REP 5
KILL 
@END
		;LOWEST NUMBER IS 210 
		;NUMBERS ARE IN A CONTIGUOUS BLOCK

		;WE NEED TO GO GRAB _A_ TICKET THEN BACKTRACK UP TO 4 SPACES
		;WE START AT 213 TO IMPROVE PERFORMANCE IN THE WORST CASE

COPY 213 X
MARK TICKETLOOP
REPL GETTICKET
ADDI X 5 X
TEST MRD
FJMP TICKETLOOP
COPY M X
MARK TICKETLOOP2
REPL GETTICKET2
ADDI X 1 X
TEST MRD
FJMP TICKETLOOP2
VOID M

COPY F X
COPY X M
SEEK -1
COPY M F
COPY X F
COPY M F
SEEK -3
COPY F M
COPY F M
COPY F M
WIPE

MARK GETTICKET
GRAB X
SUBI X 4 M
			;HAVE THIS CHAIN TO TICKETLOOP 2??

MARK GETTICKET2
GRAB X
COPY 1 M
COPY F #NEXT
DROP
LINK 800
COPY M X
GRAB 200
					;SO WE HAVE A LITTLE PROBLEM. NO WAY TO GET FILE PTR LOC, SO WHAT IF TARGET LOC IS BEFORE 1ST ENTRY?
					;WE GOTTA CHECK FOR THIS EDGE CASE.
TEST F > #DATE
TJMP EDGECASE1
COPY #DATE M
MARK EDGECASE1RET

MARK FINDAPPT
TEST F = X
TJMP GETAPPT
SEEK 2
JUMP FINDAPPT
MARK GETAPPT
COPY F M 	;'TEST'
MARK COPYFWD
SEEK -6
TEST F = #DATE
TJMP WRITEAPPT
SEEK -1

COPY F X
COPY F T
SEEK 1
COPY X F
COPY T F
SEEK -3
COPY F X
SEEK 2
COPY X F
SEEK -3

JUMP COPYFWD
MARK WRITEAPPT
SEEK 2
COPY M X
TEST X = #DATE
TJMP EDGECASE2RET
						;EDGE CASE PART 2 - COPY NEXT DAY'S DATE SO TODAY'S IS AT LIST START
COPY X F
SEEK -3
COPY F X
COPY F T
SEEK 1
COPY X F
COPY T F

SEEK -9999
MARK EDGECASE2RET
COPY #DATE F
COPY M F 				;NTH'S NAME
COPY M F 				;'TEST'
HALT

MARK EDGECASE1
SEEK -1
COPY F M
SEEK -1
COPY #DATE F
JUMP EDGECASE1RET
I had a similar solution to CO₂'s to begin with, which got similar performance. However, on re-evaluating it, I realized there's an easy way to make it better: since the tickets are a contiguous group of 5, between 210 through 298 in every case, we can just check for a hit by adding 5 if no match is found (so 210, 215, 220, etc). From there, we backtrack four spaces and grab numbers going up by 1 to find the lowest ticket number.

We start by trying to grab ticket 213, as this improves performance in the worst case where tickets 294-298 are present (we grab ticket 298, then subtracting 4 leaves us with the lowest number, ticket 294 directly), and go from there.

My file writing mechanism is drastically different from CO₂'s. I can't say which is faster. Instead of deleting NthDimension's entry from the file, the EXA that finds it sends it over M to its parent, which still remains in the main room with the ticket lineup. The parent EXA stores that row to a file - we'll read it back later.

The EXA reading the appointments starts at Kapoor's row and repeatedly copies the previous line over the current line, going back until we find today's date. There's no way to make the overwriting exa force-halt when it hits the beginning of the file (believe me, I did try), and there's no way to test if we are at the beginning of the file, either (aside from forcing the issue with SEEK -9999). So instead, we check for what in the code above is referred to as the 'edge case'; if Kapoor's entry would be the only one for today (as determined by the first line of the file not having today's date), we replace the date on that entry with today's date, before copying the 'bad date' into the file on Kapoor's row instead.

Either way, once the copier makes space for Kapoor's row, it finds the last incidence of today's date, then replaces the next row with Nth's entry (read in from the parent EXA over M). If the edge case is in force (the date from the parent EXA will not be today's), it copies the first row of the file to the second row and sticks Nth's record in the first row instead.

Once that's done, it's just a matter of cleaning up. 273/93/7.
With such a comprehensive explanation, there's not much I can add. Your file writing code is quite elegant, and because it doesn't use M I wouldn't be surprised if it was faster.


=== Cybermyth studios ===



Time to help out Ghast... Wait, 1992? I was in 1998. Is this supposed to be a flashback or something?

I don't know, so let's just get to the task.


OST: Getting Started

The assignment this time:
- Each host contains two files: a list of accounts and a list of transactions. Although the entries in these files vary, the first value of each entry is a unique identifier that connects an account to one or more transactions.
- Determine the amount of back-pay owed to Ghast and Moss by subtracting the amount that they were paid (file 221) from the amount that they were owed (file 231). Then add their shell company (file 300) to the list of accounts payable (file 220) and issue a single payment to it for the total amount owed (file 221).
- Note that all monetary amounts are represented as two values: dollars, then cents.


That's quite a lot of information. Before I figure that out, let's look in the files. From the dates, it seems I'm definitely in 1992. The files in the RECEIVABLE host list some transactions related to companies. The RECORDS host has no transactions, but the files say that Ghast is on report for absenteeism and I (Moss) am on report for intoxication. Crap.

Something I hadn't noticed yet: my home host is named GHAST. Am I actually playing as Ghast? The same thing was the case for the other postgame tasks so far.

The expected goal shows that I actually have to change two things:
- Add the shell company TRASH WORLD CLEANING to file 220, starting with the first unused ID number.
- Add a transaction to file 221, with the ID of the shell company, a date (I guess today's from the #DATE register), and an amount.
That amount is the amount Ghast and Moss are owed, which I can get by subtracting the total paid amount from the total owed amount.

Looking at the data, the total owed amount is often more than the EXA's max value of 9999. At least in the first few tests, the amount paid is not. So, I should be able to solve this problem by writing the amount paid as a negative, then adding the amount owed to it, so I never cross the 9999 boundary.

For the total amount owed, it looks like the payroll has the same amounts for every week, AND everyone gets paid every week. That means I can just calculate the amount for one week and multiply that by the number of weeks.

Enough with the theory, let's write some actual code.

code:
;XA

LINK 800
LINK 801
; FIND GHAST'S ID
GRAB 220
COPY M X

MARK FINDPERSON
SEEK 1
TEST X = F
FJMP FINDPERSON
SEEK -2
COPY F X
MODE
TEST MRD
TJMP MOSSDONE
REPL GHASTID

MODE

; FIND MOSS TOO
SEEK -9999
COPY M X
JUMP FINDPERSON

MARK MOSSDONE
VOID M


MARK GHASTID
GRAB 221
COPY 0 M

;XB

GRAB 300
COPY F M
COPY F M
XB just grabs the information file and sends data over M.
XA goes to the host containing the paid amounts and finds Ghast's and Moss's account ids. This is done through a basic search loop. But I realized that I'll never have enough registers in one EXA to test if a transaction belongs to either Ghast or Moss in one go. So I REPL the EXA containing Ghast's id (it barely fits in the host when the original EXA is holding a file). It grabs the other file, then sends a message over M in local mode. The TEST MRD in the search loop causes the search for Moss to end without a REPL and in MOSSDONE.

That means the original EXA holding the list of accounts now has Moss's id in X, the other one has Ghast's id.



The EXA containing Ghast's id changes to local mode, creates an EXA I called LOCALCTRL and starts sending Ghast's dollar amounts to it. LOCALCTRL adds them up and stops once it gets a zero. Because the M's interacted badly, I removed the MRD check from below the FINDPERSON, and added a check after the REPL, which tests if XB sent a 0. However, this doesn't get triggered because the EXA waits on the REPL command until a space is freed up. Something to think about later. At least now I have Ghast's amount of paid dollars.

code:
LINK 800
LINK 801
; FIND GHAST'S ID
GRAB 220
COPY M X

MARK FINDPERSON
SEEK 1
TEST X = F
FJMP FINDPERSON
SEEK -2
COPY F X
COPY X M
REPL GHASTID


; FIND MOSS TOO
SEEK -9999
COPY M X
TEST X = 0
FJMP FINDPERSON

HALT


MARK GHASTID
MODE
GRAB 221
REPL LOCALCTRL

MARK COLLECT_DOLLARS
TEST F = X
FJMP WRONGACCOUNT
SEEK 1
COPY F M
SEEK 1
TEST EOF
FJMP COLLECT_DOLLARS
MARK WRONGACCOUNT
SEEK 3
TEST EOF
FJMP COLLECT_DOLLARS

COPY 0 M
SEEK -9999
COPY 1 M

MARK COLLECT_CENTS
TEST F = X
FJMP WRONGACCOUNT_CT
SEEK 2
COPY F M
TEST EOF
FJMP COLLECT_CENTS
MARK WRONGACCOUNT_CT
SEEK 3
TEST EOF
FJMP COLLECT_CENTS

COPY 0 M

MODE
SEEK -9999
COPY M X ; OTHER ID
MODE
COPY X M
TEST X = 0
FJMP COLLECT_DOLLARS

HALT

MARK LOCALCTRL
MAKE
COPY 0 X
MARK ADDAMOUNT
COPY M T
FJMP ADDAMOUNTDONE
ADDI T X X
JUMP ADDAMOUNT

MARK ADDAMOUNTDONE
COPY X F ; DOLLARS IN F
COPY 0 X
TEST M = 0
FJMP ADDAMOUNT


SEEK -9999
COPY F X
COPY F T
ADDI F X X
ADDI F T T

SWIZ T 0043 F
SWIZ T 0021 T
SEEK -1
ADDI F X X

NOOP
I added several steps. First, after the amount of dollars, I add up the amount of cents too. Moss's id is temporarily sent to XB and then retrieved by the EXA that's looking through the transactions. It then sends Moss's dollar and cent amounts. XB finally sends a 0 which causes both the sender and the LOCALCTRL EXA to jump to a final state. At that point LOCALCTRL is holding a file with respectively, an amount in dollars, cents, dollars and cents. It adds these up, making sure to add cent amounts > 100 to the dollar value. It ends with total dollars in X and total cents in T.


Okay, with that done I need to find out how much they are due.

I can do a bit of cleanup first. That original EXA is still sitting at REPL GHASTID. Instead of letting it continue, I can have the other one just do a KILL. That means that I can remove the TEST and HALT after it. There's also a place where I had to do a TEST X = 0 and if that's true it can HALT. It saves some space to just DIVI T X T and use the divide by zero trick.

Dealing with the payroll is a bit tricky, because the accounts there have other IDs. I need to get Ghast's and Moss's name from file 300 once more.
You know what, doing that with M is going to cause timing issues. I'll just have another EXA handle that altogether.


Enter the extended XB. I'll go through it bit by bit.
code:
;XB

GRAB 300
COPY F X
COPY F T
REPL PAYROLL

COPY X M
VOID M
COPY T M
COPY M X
COPY X M
COPY 0 M

MODE
COPY 0 M

HALT

MARK PAYROLL
MODE
LINK 800
LINK 802
After grabbing the file, it copies Ghast's and Moss's name to X and T. The 'main' XB still sends these values over global M to XA. XA's FINDPERSON code stores the result in X and sends it to M. The first time round, XB VOIDs the M. The second time round, because XA can never REPL, the id on M is necessary. XB buffers it in X, then forwards it to XA's existing REPL when it is ready to receive. Finally, when XA asks for a 'third name', XB send COPY 0 M. The 0 tells XA to continue with the next part (the calculation).

The MODE change followed by a final M message is for the PAYROLL REPL. Once PAYROLL is done, it'll come back to the home host and wait for this M, which indicates that XA is ready. After that, the original XB can HALT and the PAYROLL can take over. By the way, by reordering the code a bit I can get rid of this HALT by having the EXA fall through into harmless code where it dies.


Now, for the PAYROLL itself. It switches to MODE to LOCAL and will stay there. This way it can never interfere with the XB-XA GLOBAL communication.
code:
MARK FINDPERSON
SEEK 1
TEST X = F
FJMP FINDPERSON
SEEK -2
COPY F M

; FIND MOSS TOO
SEEK -9999
COPY M X
DIVI 1 M T
JUMP FINDPERSON
Since the IDs in the payroll host differ, XB has a copy of the FINDPERSON code. This time, it just sends the result on local M. It then waits for a message. If this is a 0, it dies. If not, it waits for another message from M into X (Moss's name). We need to listen to M twice because attempting to do arithmetic on a text string crashes the EXA.

code:
MARK OTHER
GRAB 231
COPY M X
COPY 1 M
COPY T M
REPL CTRL

COPY 8 T
MARK WT
SUBI T 1 T
TJMP WT

MARK FIND
TEST F = X
SEEK 3
FJMP FIND
SEEK -2
COPY F M
COPY F M

COPY M X
COPY X T
TJMP FIND
The OTHER REPL GRABs the file with the amounts owed. Then it copies the ID found by FINDPERSON to X. It sends 1 to M to tell FINDPERSON to look for Moss, followed by Moss's name which is still sitting in T.

After that it needs to wait a bunch of cycles. That's because before the FIND loop can start looking for Ghast's salary, the CTRL EXA first needs to get Moss's ID. I'll get to that EXA in a second.

The FIND loop itself simply looks for Ghast's ID and then copies both the dollars and cents to M. If it gets Moss's ID (which is a non-zero number) it'll look for his amount too, otherwise this EXA will continue to another part of the code.

Before we go to that next part I'll show the CTRL REPL.

code:
MARK CTRL
COPY M X
COPY 0 M
MAKE
COPY M F
COPY M F
COPY X M
COPY M F
COPY M F
COPY 0 M
SEEK -9999
COPY F X
COPY F T
ADDI F X X
ADDI F T T

SWIZ T 0043 F
SWIZ T 0021 T
SEEK -1
ADDI F X X

WIPE
MAKE
COPY X F
COPY T F
First, it waits for the second ID from FINDPERSON. It sends a 0 to let FINDPERSON know it can stop looking. Then it makes a temporary file, in which it stores Ghast's salary (dollars, then cents). This EXA sends Moss's ID to the FIND EXA and then stores Moss's salary too. It sends a 0 to tell the FIND EXA it's done. It adds the two salaries together, and then does the same trick as XA, with the SWIZ to add any hundreds of cents to the dollar amount.

Finally, the temporary file is WIPEd and a new one is made to store the total salary in. Having a file without intermediate garbage makes things simpler later.

code:
LINK -1
DROP
LINK 802

GRAB 230
COPY 0 X
MARK COUNT
ADDI X 1 X
SEEK 2
TEST EOF
FJMP COUNT
MULI X 4 M
DROP
COPY 0 X
LINK -1
GRAB 402
LINK 802
The EXA then parks the temporary file in the gateway host (since the payroll host is full), and goes back to GRAB 230 again. This time, it counts the number of people on the payroll. This is multiplied by 4 (because there are 4 items per person per week in file 231) and sent to M, where the FIND EXA which is still holding 231 can read it. Once that's done, it quickly goes back to grab the temporary file (402) again.

Before we continue, let's look at what the FIND EXA has been up to.

code:
; COUNT
SEEK -9999
COPY M X

MARK COUNTPAYMENTS
COPY 1 M
SEEK X
TEST EOF
FJMP COUNTPAYMENTS

COPY 0 M
HALT
This EXA receives the multiplied value and saves it to X. For example, if there are 5 people on the payroll, it'll save '20' to X. That way, by doing a SEEK X it jumps ahead exactly one week. So, what this does is count how many times everyone (including Ghast and Moss) were supposed to be paid.

Since X holds the value to SEEK and T is used for EOF, the actual counting is done by COPY 1 M in the loop, followed by a COPY 0 M to let the CTRL EXA know when it's done. The HALT here is optional if I structure the code so this EXA dies without further side effects after the COPY 0 M.

code:
MARK MORE
COPY M T
ADDI X T X
TJMP MORE
LINK -1
LINK -1
VOID M
MODE

COPY X M
COPY F M
COPY X M
COPY F M
WIPE
GRAB 300
SEEK 2
COPY F M
This is the final part of the CTRL EXA and XB's code as a whole. This EXA is back in the payroll host. In the MORE loop it keeps track in X of the number of payments, and uses T to check when it's done. Then it LINKs back home, and then waits until the original XB sends that one LOCAL message to say XA is done.

At that point this EXA truly takes control by switching to GLOBAL mode, sending the number of payments, then the total amount of dollars, then the number of payments again and then the amount of cents.

If it's not quite clear what I've just done, here's an example:
- Moss gets paid $1076.92 per week.
- Ghast gets paid $961.53 per week.
- In total, they get paid 1076.92 + 961.53 = $2038.45 per week.
- The payroll says they owe payment for 5 weeks.

In this case, the CTRL EXA sends: 5; 2038; 5; 45. That way, the receiving EXA knows to apply $2038 five times, and the cents amount too. Since 5 * 2038 is greater than 9999, this is the most straightforward way to handle it.

Finally, this EXA GRABs file 300 so it can send the shell company's name before it stops.


To handle all this, I added more code to XA.

If you remember, we left off XA with the dollars-already-paid amount in X, and the cents-already-paid in T. It's also holding a temporary file containing intermediate date, with the file pointer sitting at the end.

code:
;XA continued

KILL
SUBI 0 T F
SUBI 0 X X

MODE
COPY M T
COPY M F


MARK AGAIN
SEEK -1
ADDI F X X
SUBI T 1 T
TJMP AGAIN

COPY X F
At this point, we're certain that the only other EXA in this host is the one stuck trying to REPL. I can safely kill it. Then, we store the negative of the cents amount in F and the negative of the dollars amount in X. The EXA switches to GLOBAL MODE and puts the amount of payments in T and the amount of dollars per week in F.

The AGAIN loop adds the amount owed to the negative of the amount paid repeatedly. For example, if the amount paid is $100 and the amount owed is 5 payments of $60, the calculation is -100 + 60 + 60 ... and so on. The result, which is the actual final amount Ghast and Moss are owed, is stored in F.

code:
SEEK -3
COPY F X
COPY M T
SEEK 9999
COPY M F

MARK AGAIN2
SEEK -1
ADDI F X X
SUBI T 1 T
TJMP AGAIN2

SEEK -2
SWIZ X 0043 T
ADDI F T T
SWIZ X 0021 X
Next, we repeat the exact same trick but with the cents. Again, I have to do the SWIZ to handle hundreds of cents.

While testing I found out this bit of code failed in one particular case. Because I'm subtracting the cents separately from the dollars, it's possible to end up with a negative amount of cents. For instance, 56 dollars and -29 cents. That's actually supposed to be $55.81. There might be a way around this by doing something smart with the SWIZ code but I just slapped a workaround after it.

code:
; NEGATIVE CENTS
COPY T F
TEST X < 0
FJMP SKIP
ADDI 100 X X
SEEK -1
SUBI F 1 T
JUMP COMPLETE

MARK SKIP
SEEK -1
COPY F T

MARK COMPLETE
Temporarily store the dollar amount (which was in T) to F, so I can do a test. Then, if the cent amount is negative, add 100 to it, and subtract 1 from the dollar amount. The dollar amount is put back into T.

Now, all that's really left is clean-up and writing the final values to the file.

code:
MARK COMPLETE
WIPE
GRAB 220
SEEK 9999
SEEK -2
REPL DATE
ADDI F 1 X
SEEK 9999
COPY X F
COPY M F
The EXA now WIPEs its temporary file and GRABs 220 again. First, it finds the highest ID in the file and adds one to it. This value is saved to the end of the file, followed by the shell company's name, which we get on M from XB.

code:
DROP
GRAB 221
SEEK 9999
COPY X F
COPY M F
COPY T F
COPY M F
Then, it grabs file 221, and adds a new transaction, starting with the shell company's ID number, and then the DATE which it gets from the DATE REPL. It then copies the dollars amount from T, and it gets the cents amount from DATE as well, since it was overwritten in X by the ID.

code:
MARK DATE
LINK -1
LINK 804
NOOP
COPY #DATE M
COPY X M
The REPL itself doesn't have much code. It goes grab the current date from the hardware register, sends that, and then the cents.

The NOOP here is absolutely necessary. Without it, it would start sending just before the other EXA had the chance to read the shell company's name from M. If I had put the REPL DATE instruction any earlier I would've needed even more waiting cycles here. And I couldn't put it any later, because after that point, X gets overwritten.


That's every single piece of the code explained. While testing, I ran into one more bug. It is possible for the amount of cents in a payment to be 0. And I was already used 0 to let the other EXA know no more payments are coming. I fixed that by sending the amount + 1 and then subtracting 1 once the other EXA receives it. I had to do this for dollars as well because they share the receive code.

The full code is... hard to read because I shuffled blocks of code to remove a bunch of HALT instructions. It's very long, so I added it in an appendix at the bottom.



Oh. It's way too long.

Well, the good news is that even though I don't get a high score, the game still counts this task as completed. I'm glad, because I spent enough time on it that I really don't want to optimize it further.

I can see my score by looking at the detailed test run data. It's 536/246/16.

Outside the level, the histograms show up.


The top percentiles are 215, 92, and 4, but for size there's a tiny little peak in the histogram around 60. I can think of some optimizations but getting it down to 60-ish? I have no idea how. My speed though is close to tenth percentile, and I'm happy with that.



Next time, hydroponix.


=== Appendix ===

code:
;XA

LINK 800
LINK 801
; FIND GHAST'S ID
GRAB 220
COPY M X

MARK FINDPERSON
SEEK 1
TEST X = F
FJMP FINDPERSON
SEEK -2
COPY F X
COPY X M
REPL GHASTID

; FIND MOSS TOO
SEEK -9999
COPY M X
JUMP FINDPERSON


MARK GHASTID
MODE
GRAB 221
REPL LOCALCTRL

MARK COLLECT_DOLLARS
TEST F = X
FJMP WRONGACCOUNT
SEEK 1
ADDI F 1 M
SEEK 1
TEST EOF
FJMP COLLECT_DOLLARS
MARK WRONGACCOUNT
SEEK 3
TEST EOF
FJMP COLLECT_DOLLARS

COPY 0 M
SEEK -9999
COPY 1 M

MARK COLLECT_CENTS
TEST F = X
FJMP WRONGACCOUNT_CT
SEEK 2
ADDI F 1 M ; HANDLE 0
TEST EOF
FJMP COLLECT_CENTS
MARK WRONGACCOUNT_CT
SEEK 3
TEST EOF
FJMP COLLECT_CENTS

COPY 0 M

MODE
SEEK -9999
COPY M X ; OTHER ID
MODE
COPY X M
DIVI T X T
FJMP COLLECT_DOLLARS


MARK LOCALCTRL
MAKE
COPY 0 X
MARK ADDAMOUNT
COPY M T
FJMP ADDAMOUNTDONE
SUBI T 1 T
ADDI T X X
JUMP ADDAMOUNT

MARK ADDAMOUNTDONE
COPY X F ; DOLLARS IN F
COPY 0 X
TEST M = 0
FJMP ADDAMOUNT


SEEK -9999
COPY F X
COPY F T
ADDI F X X
ADDI F T T

SWIZ T 0043 F
SWIZ T 0021 T
SEEK -1
ADDI F X X

KILL
SUBI 0 T F
SUBI 0 X X

MODE
COPY M T
COPY M F


MARK AGAIN
SEEK -1
ADDI F X X
SUBI T 1 T
TJMP AGAIN

COPY X F

SEEK -3
COPY F X
COPY M T
SEEK 9999
COPY M F

MARK AGAIN2
SEEK -1
ADDI F X X
SUBI T 1 T
TJMP AGAIN2

SEEK -2
SWIZ X 0043 T
ADDI F T T
SWIZ X 0021 X

; NEGATIVE CENTS
COPY T F
TEST X < 0
FJMP SKIP
ADDI 100 X X
SEEK -1
SUBI F 1 T
JUMP COMPLETE

MARK DATE
LINK -1
LINK 804
NOOP
COPY #DATE M
COPY X M

MARK SKIP
SEEK -1
COPY F T

MARK COMPLETE
WIPE
GRAB 220
SEEK 9999
SEEK -2
REPL DATE
ADDI F 1 X
SEEK 9999
COPY X F
COPY M F
DROP
GRAB 221
SEEK 9999
COPY X F
COPY M F
COPY T F
COPY M F

;XB

GRAB 300
COPY F X
COPY F T
REPL PAYROLL

COPY X M
VOID M
COPY T M
COPY M X
COPY X M
COPY 0 M

MODE
COPY 0 M

MARK OTHER
GRAB 231
COPY M X
COPY 1 M
COPY T M
REPL CTRL

COPY 8 T
MARK WT
SUBI T 1 T
TJMP WT

MARK FIND
TEST F = X
SEEK 3
FJMP FIND
SEEK -2
COPY F M
COPY F M

COPY M X
COPY X T
TJMP FIND

; COUNT
SEEK -9999
COPY M X

MARK COUNTPAYMENTS
COPY 1 M
SEEK X
TEST EOF
FJMP COUNTPAYMENTS

COPY 0 M

MARK PAYROLL
MODE
LINK 800
LINK 802
GRAB 230
REPL OTHER

MARK FINDPERSON
SEEK 1
TEST X = F
FJMP FINDPERSON
SEEK -2
COPY F M

; FIND MOSS TOO
SEEK -9999
DIVI 1 M T
COPY M X
JUMP FINDPERSON


MARK CTRL
COPY M X
COPY 0 M
MAKE
COPY M F
COPY M F
COPY X M
COPY M F
COPY M F
COPY 0 M
SEEK -9999
COPY F X
COPY F T
ADDI F X X
ADDI F T T

SWIZ T 0043 F
SWIZ T 0021 T
SEEK -1
ADDI F X X

WIPE
MAKE
COPY X F
COPY T F
LINK -1
DROP
LINK 802

GRAB 230
COPY 0 X
MARK COUNT
ADDI X 1 X
SEEK 2
TEST EOF
FJMP COUNT
MULI X 4 M
DROP
COPY 0 X
LINK -1
GRAB 402
LINK 802

MARK MORE
COPY M T
ADDI X T X
TJMP MORE
LINK -1
LINK -1
VOID M
MODE

COPY X M
COPY F M
COPY X M
COPY F M
WIPE
GRAB 300
SEEK 2
COPY F M

Carbon dioxide fucked around with this message at 19:00 on Apr 23, 2023

Quackles
Aug 11, 2018

Pixels of Light.


I... owe CO2 an apology.

My fully-optimized design uses a completely different approach than his, and it almost feels like I'm bragging. If so, I'm sorry.

But anyway, here's what I got.

code:
GRAB 300
COPY F X
REPL COMPTROLLER
SEEK -1
COPY M F ;CENTS 1
COPY F X
REPL COMPTROLLER
SEEK -1
COPY M F ;DOLLARS 1
SEEK -2
ADDI M F X ;CENTS 2
;WE AVOID HAVING TO TEST
;FOR NEGATIVE CENTS
;BY ADDING 300 TO CENTS
;AND SUBTRACTING 3 FROM 
;DOLLARS (LATER)
ADDI X 300 X
DIVI X 100 T
SEEK -1
MODI X 100 F
ADDI M T T
ADDI T F T
;X HAS CENTS,
;T HAS DOLLARS

;NOW JUST WRITE THE
;VALUES AND WE'RE GOOD
;TO GO
LINK 800
MODE
COPY F X
REPL HACKTHEPAYABLE
LINK 804
SEEK -9999
COPY F X
WIPE
COPY #DATE M
COPY T M
COPY X M


MARK HACKTHEPAYABLE
LINK 801
GRAB 220
SEEK 9999
SEEK -2
COPY F T
SEEK 1
ADDI T 1 F
COPY X F
DROP
GRAB 221
SEEK 9999
ADDI T 1 F
COPY M F
SUBI M 3 F
COPY M F


MARK COMPTROLLER
LINK 800
REPL GETFIN
COPY 1 T
REPL GETFIN

;PAYMENTS ALWAYS
;FINISHES FIRST!

SUBI 0 M X ;DOLLARS1
SUBI 0 M T ;CENTS1
ADDI X M X ;2
ADDI T M T ;2
LINK -1
COPY T M ;CENTS
COPY X M ;DOLLARS


MARK GETFIN
ADDI T 801 T
LINK T
SWIZ T 0010 T
ADDI T 210 T
GRAB T
SEEK 1
MARK SEARCHFIN
TEST F = X
TJMP FOUNDFIN
SEEK 1
JUMP SEARCHFIN

MARK FOUNDFIN
SEEK -2
COPY F X
FILE T
ADDI T 1 T
DROP
GRAB T
REPL COUNTER
MARK LISTFIN
TEST EOF 
TJMP DONEZO
TEST F = X
SEEK 3
FJMP LISTFIN
SEEK -2
COPY F M
COPY F M
JUMP LISTFIN


MARK COUNTER
MAKE
COPY 0 F
COPY 0 F
MARK COUNTER2
COPY M T
FJMP FINISHEDCOUNT
SEEK -2
ADDI F T T
ADDI F M X
SEEK -2
COPY T F
COPY X F
JUMP COUNTER2

MARK FINISHEDCOUNT
LINK -1
SEEK -2
COPY F M
COPY F M
WIPE
HALT

MARK DONEZO
COPY 0 M
;GIVE UP
This is a big block of code, but it's really in five main 'chunks': the main EXA, the main EXA's file writing routine ("HACKTHEPAYABLE"), the EXA that gets the amounts paid and owed for a single person ("COMPTROLLER"), and the subsidiary processes spun off by the controller ("GETFIN") and ("COUNTER").

It works as follows:

First, the main EXA moves 'Ghast' into X, then REPLs to COMPTROLLER. The 'comptroller' EXA will search for the accounts receivable and payable, return to our host, and communicate them to the main EXA over M, in the order (Cents, Dollars). We use this order to let us use the file for extra storage later.

The comptroller EXA heads into the Cybermyth host, then REPLs twice to GETFIN. Some numeric wrangling with T lets us use GETFIN for both the payroll and payable hosts, despite having different link and file IDs. Each GETFIN EXA still has 'Ghast' in its X, so it starts by grabbing the IDs file, searching for 'Ghast', and replacing its X with the numeric ID, instead. Then, it drops the IDs file, grabs the accounts file (using FILE and some addition to get the next file ID), and REPLs off into COUNTER. There's enough space in the host since we're grabbing a file.

From here, the GETFIN EXA simply scans through the file and sends any dollar/cents values it finds with a matching ID out over M, then 0 once it's done. It then halts. COUNTER adds up the dollars and cents values it gets to a file; once it receives a 0 for a dollar value, it leaves the subhost and reports to the comptroller.

The payments file always takes less time to process than the payroll file! Cybermyth, you cheapskates. Because of this, we can assume that the first COUNTER EXA has the amount paid, while the second has the amount owed. We get the values over M, subtract the first EXA's values from our registers, and add the second EXA's values. Then we report back to the main EXA.

The main EXA stores the values to the file in place of 'Ghast' and 'Moss'. It repeats the comptroller process with 'Moss', and adds the second comptroller's values.

We have a little bit of rectification to do at this point. It's possible we could have a value for the 'cents' that is over 100, or even negative! To fix these, we add 300 to the cents value to counter out any negative amount. Then we add the modulo 100 of the cents to the dollars value, and leave the rest of the cents as is. (We'll subtract 3 from the dollars value later.)

At this point, the final part of the code kicks in.

The main EXA splits into two. One ('HACKTHEPAYABLE') moves into the payments subhost and patches the accounts file (the main EXA moves into the host with #DATE and helpfully sends 'TRASH WORLD CLEANING' over global M). Then, with the new ID, it moves to the bottom of the payments file, takes the dollars value (minus $3) and cents value from the main EXA over M, and writes to the file. Finished!



458/109/15. I'm pretty sure handling Ghast's and Moss's data separately drastically decomplicates things. It might also be possible to speed this up further by having separate routines for the payable and payroll sections, since the payroll section follows a pattern (so just find the first payment, then find the number of payments by jumping forward 15 places in the file and checking for EOF, then perform multiplication). I might try making that variant when I have some spare time.

Quackles
Aug 11, 2018

Pixels of Light.


Quackles posted:

It might also be possible to speed this up further by having separate routines for the payable and payroll sections, since the payroll section follows a pattern [...] I might try making that variant when I have some spare time.

So I went and made it.


code:
GRAB 300
COPY F X
REPL COMPTROLLER
SEEK -1
COPY M F ;CENTS 1
COPY F X
REPL COMPTROLLER
SEEK -1
COPY M F ;DOLLARS 1
SEEK -2
ADDI M F X ;CENTS 2
	;WE AVOID HAVING TO TEST FOR NEGATIVE CENTS
	;BY ADDING 300 TO CENTS AND SUBTRACTING 3 FROM DOLLARS (LATER)
ADDI X 300 X
DIVI X 100 T
SEEK -1
MODI X 100 F
ADDI M T T
ADDI T F T
	;X HAS CENTS,
	;T HAS DOLLARS

	;NOW JUST WRITE THE
	;VALUES AND WE'RE GOOD TO GO
LINK 800
MODE
COPY F X
REPL HACKTHEPAYABLE
LINK 804
SEEK -9999
COPY F X
WIPE
COPY #DATE M
COPY T M
COPY X M


MARK HACKTHEPAYABLE
LINK 801
GRAB 220
SEEK 9999
SEEK -2
COPY F T
SEEK 1
ADDI T 1 F
COPY X F
DROP
GRAB 221
SEEK 9999
ADDI T 1 F
COPY M F
SUBI M 3 F
COPY M F


MARK COMPTROLLER
LINK 800
REPL GETDUE
REPL GETFIN

ADDI 0 M X ;DOLLARS1
MODE ;SYNC GUARDS
ADDI 0 M T ;CENTS1
MODE

ADDI X M X ;2
MODE
ADDI T M T ;2
MODE
LINK -1

COPY T M ;CENTS
COPY X M ;DOLLARS


MARK GETDUE
LINK 802
GRAB 230
SEEK 1
MARK SEARCHDUE
TEST F = X
TJMP FOUNDDUE
SEEK 1
JUMP SEARCHDUE

MARK FOUNDDUE
SEEK -2
COPY F X
DROP
GRAB 231
REPL COUNTER

MARK LISTDUE
TEST F = X
SEEK 3
FJMP LISTDUE
SEEK -2
COPY F M
COPY F M
COPY 1 X
SEEK 16
MARK DUECOUNT
TEST EOF
ADDI X 1 X
SEEK 20
FJMP DUECOUNT
COPY 0 M
SUBI X 1 M


MARK GETFIN
LINK 801
GRAB 220
SEEK 1
MARK SEARCHFIN
TEST F = X
TJMP FOUNDFIN
SEEK 1
JUMP SEARCHFIN

MARK FOUNDFIN
SEEK -2
COPY F X
DROP
GRAB 221
REPL COUNTER
MARK LISTFIN
TEST EOF 
TJMP DONEZO
TEST F = X
SEEK 3
FJMP LISTFIN
SEEK -2
COPY F M
COPY F M
JUMP LISTFIN


MARK DONEZO 
COPY 0 M
COPY -1 M
;GIVE UP

MARK COUNTER
MAKE
COPY 0 F
COPY 0 F
MARK COUNTER2
COPY M T
FJMP FINISHEDCOUNT
SEEK -2
ADDI F T T
ADDI F M X
SEEK -2
COPY T F
COPY X F
JUMP COUNTER2

MARK FINISHEDCOUNT
COPY M X
LINK -1
SEEK -2
MULI F X M
MODE ;SYNC GUARD
MULI F X M
WIPE
This code is similar to the last one, except the code that scans the payroll and payable has been split off into separate blocks. The payable code ('GETFIN') is more or less as-is compared to the previous version, but the payroll code ('GETDUE') finds the first instance of the amount paid in the list, then jumps forward 20 entries to check if there was another payment scheduled. The payroll EXA then repeats this to count up the number of expected payments, then sends a zero, and that number, to the counter EXA.

Both the GETFIN and GETDUE EXAs use the same counter code, which has been modified slightly. Now, after receiving the 'finish up' zero, the counter EXA will read one more value over M, and multiply the totals by that value. This lets us not have to worry about the order the EXAs return when reporting our results to the comptroller. We can simply add the values together, as the amount CyberMyth paid us can be multiplied by -1 to mimic a subtraction.

The comptroller and counter do use 'mode guards' to handle the edge case where both counters return to the main CyberMyth host at about the same time. Once the first value is read from a counter, the second value will be read using the global mode instead of the local mode. This prevents the second counter from kramering in and getting the order of the values mixed up.

Aside from that, everything is pretty much the same.


354/140/15. It can theoretically get better cycles-wise, but this is as good as it gets for me.

Carbon dioxide
Oct 9, 2012

Part 47 - US Department of Defense

Today, we have a very special guest. But first, let's check the inbox.


=== Trash World Inbox ===

Last time, I finished Cybermyth Studios with a score of 536/246/16. The max allowed number of lines for the leaderboard was 150, so it didn't really count.

Quackles shows some better ways to tackle it.

Quackles posted:

I... owe CO2 an apology.

My fully-optimized design uses a completely different approach than his, and it almost feels like I'm bragging. If so, I'm sorry.

But anyway, here's what I got.
code:
GRAB 300
COPY F X
REPL COMPTROLLER
SEEK -1
COPY M F ;CENTS 1
COPY F X
REPL COMPTROLLER
SEEK -1
COPY M F ;DOLLARS 1
SEEK -2
ADDI M F X ;CENTS 2
;WE AVOID HAVING TO TEST
;FOR NEGATIVE CENTS
;BY ADDING 300 TO CENTS
;AND SUBTRACTING 3 FROM 
;DOLLARS (LATER)
ADDI X 300 X
DIVI X 100 T
SEEK -1
MODI X 100 F
ADDI M T T
ADDI T F T
;X HAS CENTS,
;T HAS DOLLARS

;NOW JUST WRITE THE
;VALUES AND WE'RE GOOD
;TO GO
LINK 800
MODE
COPY F X
REPL HACKTHEPAYABLE
LINK 804
SEEK -9999
COPY F X
WIPE
COPY #DATE M
COPY T M
COPY X M


MARK HACKTHEPAYABLE
LINK 801
GRAB 220
SEEK 9999
SEEK -2
COPY F T
SEEK 1
ADDI T 1 F
COPY X F
DROP
GRAB 221
SEEK 9999
ADDI T 1 F
COPY M F
SUBI M 3 F
COPY M F


MARK COMPTROLLER
LINK 800
REPL GETFIN
COPY 1 T
REPL GETFIN

;PAYMENTS ALWAYS
;FINISHES FIRST!

SUBI 0 M X ;DOLLARS1
SUBI 0 M T ;CENTS1
ADDI X M X ;2
ADDI T M T ;2
LINK -1
COPY T M ;CENTS
COPY X M ;DOLLARS


MARK GETFIN
ADDI T 801 T
LINK T
SWIZ T 0010 T
ADDI T 210 T
GRAB T
SEEK 1
MARK SEARCHFIN
TEST F = X
TJMP FOUNDFIN
SEEK 1
JUMP SEARCHFIN

MARK FOUNDFIN
SEEK -2
COPY F X
FILE T
ADDI T 1 T
DROP
GRAB T
REPL COUNTER
MARK LISTFIN
TEST EOF 
TJMP DONEZO
TEST F = X
SEEK 3
FJMP LISTFIN
SEEK -2
COPY F M
COPY F M
JUMP LISTFIN


MARK COUNTER
MAKE
COPY 0 F
COPY 0 F
MARK COUNTER2
COPY M T
FJMP FINISHEDCOUNT
SEEK -2
ADDI F T T
ADDI F M X
SEEK -2
COPY T F
COPY X F
JUMP COUNTER2

MARK FINISHEDCOUNT
LINK -1
SEEK -2
COPY F M
COPY F M
WIPE
HALT

MARK DONEZO
COPY 0 M
;GIVE UP
This is a big block of code, but it's really in five main 'chunks': the main EXA, the main EXA's file writing routine ("HACKTHEPAYABLE"), the EXA that gets the amounts paid and owed for a single person ("COMPTROLLER"), and the subsidiary processes spun off by the controller ("GETFIN") and ("COUNTER").

It works as follows:

First, the main EXA moves 'Ghast' into X, then REPLs to COMPTROLLER. The 'comptroller' EXA will search for the accounts receivable and payable, return to our host, and communicate them to the main EXA over M, in the order (Cents, Dollars). We use this order to let us use the file for extra storage later.

The comptroller EXA heads into the Cybermyth host, then REPLs twice to GETFIN. Some numeric wrangling with T lets us use GETFIN for both the payroll and payable hosts, despite having different link and file IDs. Each GETFIN EXA still has 'Ghast' in its X, so it starts by grabbing the IDs file, searching for 'Ghast', and replacing its X with the numeric ID, instead. Then, it drops the IDs file, grabs the accounts file (using FILE and some addition to get the next file ID), and REPLs off into COUNTER. There's enough space in the host since we're grabbing a file.

From here, the GETFIN EXA simply scans through the file and sends any dollar/cents values it finds with a matching ID out over M, then 0 once it's done. It then halts. COUNTER adds up the dollars and cents values it gets to a file; once it receives a 0 for a dollar value, it leaves the subhost and reports to the comptroller.

The payments file always takes less time to process than the payroll file! Cybermyth, you cheapskates. Because of this, we can assume that the first COUNTER EXA has the amount paid, while the second has the amount owed. We get the values over M, subtract the first EXA's values from our registers, and add the second EXA's values. Then we report back to the main EXA.

The main EXA stores the values to the file in place of 'Ghast' and 'Moss'. It repeats the comptroller process with 'Moss', and adds the second comptroller's values.

We have a little bit of rectification to do at this point. It's possible we could have a value for the 'cents' that is over 100, or even negative! To fix these, we add 300 to the cents value to counter out any negative amount. Then we add the modulo 100 of the cents to the dollars value, and leave the rest of the cents as is. (We'll subtract 3 from the dollars value later.)

At this point, the final part of the code kicks in.

The main EXA splits into two. One ('HACKTHEPAYABLE') moves into the payments subhost and patches the accounts file (the main EXA moves into the host with #DATE and helpfully sends 'TRASH WORLD CLEANING' over global M). Then, with the new ID, it moves to the bottom of the payments file, takes the dollars value (minus $3) and cents value from the main EXA over M, and writes to the file. Finished!


458/109/15. I'm pretty sure handling Ghast's and Moss's data separately drastically decomplicates things. It might also be possible to speed this up further by having separate routines for the payable and payroll sections, since the payroll section follows a pattern (so just find the first payment, then find the number of payments by jumping forward 15 places in the file and checking for EOF, then perform multiplication). I might try making that variant when I have some spare time.
First of all, this only works if you start the EXA in LOCAL mode. Secondly, very nice optimization. I guess I was overthinking things a bit. Also I have no idea why you'd need to apologize, I always welcome cool improvements.

Next Quackles implemented the faster variant.

Quackles posted:

code:
GRAB 300
COPY F X
REPL COMPTROLLER
SEEK -1
COPY M F ;CENTS 1
COPY F X
REPL COMPTROLLER
SEEK -1
COPY M F ;DOLLARS 1
SEEK -2
ADDI M F X ;CENTS 2
	;WE AVOID HAVING TO TEST FOR NEGATIVE CENTS
	;BY ADDING 300 TO CENTS AND SUBTRACTING 3 FROM DOLLARS (LATER)
ADDI X 300 X
DIVI X 100 T
SEEK -1
MODI X 100 F
ADDI M T T
ADDI T F T
	;X HAS CENTS,
	;T HAS DOLLARS

	;NOW JUST WRITE THE
	;VALUES AND WE'RE GOOD TO GO
LINK 800
MODE
COPY F X
REPL HACKTHEPAYABLE
LINK 804
SEEK -9999
COPY F X
WIPE
COPY #DATE M
COPY T M
COPY X M


MARK HACKTHEPAYABLE
LINK 801
GRAB 220
SEEK 9999
SEEK -2
COPY F T
SEEK 1
ADDI T 1 F
COPY X F
DROP
GRAB 221
SEEK 9999
ADDI T 1 F
COPY M F
SUBI M 3 F
COPY M F


MARK COMPTROLLER
LINK 800
REPL GETDUE
REPL GETFIN

ADDI 0 M X ;DOLLARS1
MODE ;SYNC GUARDS
ADDI 0 M T ;CENTS1
MODE

ADDI X M X ;2
MODE
ADDI T M T ;2
MODE
LINK -1

COPY T M ;CENTS
COPY X M ;DOLLARS


MARK GETDUE
LINK 802
GRAB 230
SEEK 1
MARK SEARCHDUE
TEST F = X
TJMP FOUNDDUE
SEEK 1
JUMP SEARCHDUE

MARK FOUNDDUE
SEEK -2
COPY F X
DROP
GRAB 231
REPL COUNTER

MARK LISTDUE
TEST F = X
SEEK 3
FJMP LISTDUE
SEEK -2
COPY F M
COPY F M
COPY 1 X
SEEK 16
MARK DUECOUNT
TEST EOF
ADDI X 1 X
SEEK 20
FJMP DUECOUNT
COPY 0 M
SUBI X 1 M


MARK GETFIN
LINK 801
GRAB 220
SEEK 1
MARK SEARCHFIN
TEST F = X
TJMP FOUNDFIN
SEEK 1
JUMP SEARCHFIN

MARK FOUNDFIN
SEEK -2
COPY F X
DROP
GRAB 221
REPL COUNTER
MARK LISTFIN
TEST EOF 
TJMP DONEZO
TEST F = X
SEEK 3
FJMP LISTFIN
SEEK -2
COPY F M
COPY F M
JUMP LISTFIN


MARK DONEZO 
COPY 0 M
COPY -1 M
;GIVE UP

MARK COUNTER
MAKE
COPY 0 F
COPY 0 F
MARK COUNTER2
COPY M T
FJMP FINISHEDCOUNT
SEEK -2
ADDI F T T
ADDI F M X
SEEK -2
COPY T F
COPY X F
JUMP COUNTER2

MARK FINISHEDCOUNT
COPY M X
LINK -1
SEEK -2
MULI F X M
MODE ;SYNC GUARD
MULI F X M
WIPE
This code is similar to the last one, except the code that scans the payroll and payable has been split off into separate blocks. The payable code ('GETFIN') is more or less as-is compared to the previous version, but the payroll code ('GETDUE') finds the first instance of the amount paid in the list, then jumps forward 20 entries to check if there was another payment scheduled. The payroll EXA then repeats this to count up the number of expected payments, then sends a zero, and that number, to the counter EXA.

Both the GETFIN and GETDUE EXAs use the same counter code, which has been modified slightly. Now, after receiving the 'finish up' zero, the counter EXA will read one more value over M, and multiply the totals by that value. This lets us not have to worry about the order the EXAs return when reporting our results to the comptroller. We can simply add the values together, as the amount CyberMyth paid us can be multiplied by -1 to mimic a subtraction.

The comptroller and counter do use 'mode guards' to handle the edge case where both counters return to the main CyberMyth host at about the same time. Once the first value is read from a counter, the second value will be read using the global mode instead of the local mode. This prevents the second counter from kramering in and getting the order of the values mixed up.

Aside from that, everything is pretty much the same.


354/140/15. It can theoretically get better cycles-wise, but this is as good as it gets for me.
Clear explanation, I have nothing to add.


=== US Department of Defense ===



Alright, is everyone ready to go hack the Department of Defense?


OST: Leave No Trace

The assignment:
- Find the unredacted version of the PROJECT OGRE report (file 300), make a copy of it, and bring the copy back to your host. The target file will be behind one or more locks, which each require a three-digit code.
- Since this task takes place inside a network run by the military it includes additional security features not found in other networks. You may not have more than one EXA in the network at a time, and you may not use the M register to communicate between an EXA in the network and an EXA in your host.


Camouflage colours or not, I found your network. And finally some real security. Honestly, all networks so far were wide open.

Anyway, I did some experiments to see what that second point of the assignment is about. M communication between the home host and the remote network is simply blocked, as if the networks are disconnected. If you bring in two EXAs to the military network, that grid of squares in the background starts flashing red and the "Leave no trace" goal is immediately failed. The same thing happens if you mess with the hardware registers on the helipad.

Those three files that are immediately accessible are heavily censored, so I'll need to crack that #LOCK.

The assignment says nothing about how to figure out the lock's combination, so let's just bruteforce it by trying all possible combinations.
code:
LINK 800

COPY 1000 T

MARK LOOP
SUBI T 1 T
COPY T #LOCK
TJMP LOOP

NOOP
Hmm, at some point it opens the link to that secured host but as soon as my EXA tries another code it closes again. Do I have to check whether the link is open after every try? But I can't use REPLs because that would mean 2 EXAs in the military network. I could spawn 1000 EXAs from some main EXA in my home host but that would be very slow and give a very high activity.

I think I will need a little help with this one...

----

This is the LockPickingLawyer and what I have for you today is a US Department of Defense Digital Combination Lock, model 1998-D. This lock was sent to me by my friend hydroponix. It is supposedly used by the Department of Defense to secure their digital information, and they consider it military grade.

There is a glaring weakness in this digital lock, though, and I will demonstrate it to you. Let me zoom in a little bit so you can see what is going on.



The combination for this lock has a six in the third position. I just tried to open it with 996 and you can see the correct digit sticks. This means we can open the lock with only a short loop.



I simply move all three digits to 999, save any correct digits to the EXA's X register, then repeat for 888 and so on. The code for this lock is 436, and I found this in only 39 cycles.

And there we have it. For any type of lock, there's a tool to pick it. In this case it's an EXA. The mechanism of this lock is similar to that of the Master Lock 878 Combination Lock, and just like that one, this digital lock can be decoded both easily and rapidly. In any case, that's all I have for you today. If you have any questions or comments about this, please put them below. If you liked this explanation and would like to see more like it, please subscribe to my channel, and as always, have a nice day. Thank you.


I hope the real LPL doesn't mind me doing this. After all, imitation is the sincerest form of flattery. Go check out his channel, it's interesting.

----

Thank you so much, LockPickingLawyer. Opening the link to the next host reveals a bunch more files, and another #LOCK to get access to the innermost files. I'll quickly open that as well by repeating the lock picking loop (or LPL for short) in that host.
code:
LINK 800

COPY 999 T

MARK LPL
COPY T #LOCK
ADDI X #LOCK X
SUBI T 111 T
TJMP LPL

COPY X #LOCK

LINK 800
COPY 0 X

COPY 999 T

MARK LPL2
COPY T #LOCK
ADDI X #LOCK X
SUBI T 111 T
TJMP LPL2

COPY X #LOCK
That gives me access to the full network.



The photo archive in the corner now shows... them filming something in the desert? Looks like an astronaut and someone dressed in a cheap alien costume.

The files here have some very interesting information. There's a few different files in the different test cases, so let me transcribe all of them for you.

The first three are uncensored versions of the censored files from the start.

Project Ogre
(1945-1953) Obtain special footage for the director depicting a hypothetical scenario involving a technologically advanced craft of non-earth origin with the purpose of scenario planning should such a situation occur. Footage was deemed unrealistic and not used. Result: FAILURE

Project Ptero
(1981-1990) Infer and test experimental new wing designs based on information from recovered DNA samples. Resulting wing shapes were not conducive to maintaining altitude. Result: FAILURE

Project Quir
(1960-1968) Investigate gravity shielding effects produced by condensed matter and superconducting materials. Analysis of tidal forces detected reduction of gravitational force of close to 1%. Future projects to focus on increasing power. Result: SUCCESS.


The rest of the files describe some other projects.

Project Orbus
(1994-) Develop additional capability and platforms for directed-energy weapons using chemically-based phase-conjugate mirrors. In all cases the mirrors overheated and experienced unplanned combustion. Result: FAILURE


I wonder if this is a kind of a vague reference to the boss missions in the Zachtronics game SpaceChem.

Project Ember
(1994-) Create general artificial intelligence using a baseline emulated reasoning approach. Of 4 initial seed programs, 1 developed knowledge acquisition, language comprehension, and exhibited signs of metacognition. Result: SUCCESS


Oh, hey. Ember was created by the military?

Project Virgil
(1983-1987) Explore use of chimpanzees as pilots for small reconnaissance aircraft to reduce radar cross-section and human casualties. Of 9 tests, 7 completed take-off but 0 returned to base. Result: FAILURE

Project Icarus
(1972-1978) Test advanced new methods to synthesize wing and fuselage material with carbon nanofibers to enable greater speed and maneuverability. Of the 21 tested methods, 20 created unusable materials and 1 resulted in a small incident. Result: FAILURE

Project Phosphor
(1979-1987) Project to develop and test technology related to inertial guidance, warhead separation, and ablative heat shielding of re-entry vehicles. Of 18 launches, 9 reached space and 3 returned to earth intact. Result: SUCCESS



That's all of them. But I need a copy of Project Ogre for myself. Let's find it first.
code:
GRAB 300
LINK 800

COPY 999 T

MARK LPL
COPY T #LOCK
ADDI X #LOCK X
SUBI T 111 T
TJMP LPL

COPY X #LOCK

LINK 800
COPY 0 X

COPY 999 T

MARK LPL2
COPY T #LOCK
ADDI X #LOCK X
SUBI T 111 T
TJMP LPL2

COPY X #LOCK

COPY F X
WIPE

; FIND

@REP 4
LINK 80@{1,1}
GRAB 200
TEST F = X
TJMP FOUND
DROP
LINK -1

@END

LINK 800
LINK 801
GRAB 200
TEST F = X
TJMP FOUND
DROP
LINK -1
LINK 802
GRAB 200

MARK FOUND
The EXA first grabs the encrypted version of Project Ogre from my home host, so it can extract the name later. Then it uses the lock picking loop (LPL) twice to pick both locks. After that, it doesn't need X anymore to hold lock information so it can take the project name from the file and put that in X.

Since the encrypted Project Ogre file started in my home host, I can safely WIPE without failing the "Leave no trace" goal. Then, the @REP makes the EXA go to each side host (801 to 804), check the file, and if it's wrong, continue to the next one. After @END, it continues to the deepest part of the network and searches for the file there as well. There's no test for the very last host, because if it's none of the others, that has to be the one.

I could maybe use a loop or two here, but I'm using my registers already. Maybe an optimization for later.

Now that the EXA has the file in hand... I have to copy it without M and without a second EXA. The files all have the little lock icon meaning I cannot move them either.

There's another issue: if I create another file in one of the side hosts, I don't have space to drop it and swap between the files. My copy has to be created in a central host, so my EXA has to link back and forth.

That means I need to either remember or bruteforce the following stuff:
- How far I am into copying the file.
- What host the correct file is in so I can link back to it to get more data.
- And of course the data to copy itself.


I started by expanding my file finding logic to save the LINK value to T. I need to do it there, because once I'm in the FOUND mark I don't know where I came from and can't save it anymore.
code:
; FIND

@REP 4
LINK 80@{1,1}
GRAB 200
TEST F = X
FJMP SKIP@{1,1}
COPY 80@{1,1} T
JUMP FOUND
MARK SKIP@{1,1}
DROP
LINK -1

@END

LINK 800
LINK 801
GRAB 200
TEST F = X
FJMP SKIP
COPY 801 T
JUMP FOUND
MARK SKIP
DROP
LINK -1
LINK 802
COPY 802 T
GRAB 200

MARK FOUND
Next, the actual copy logic.
code:
MARK FOUND
DROP
LINK -1

MAKE
COPY T F
COPY 0 X

MARK COPY
DROP
LINK T

GRAB 200
SEEK X
COPY F X
COPY F T
DROP

LINK -1
GRAB 400
SEEK 9999
COPY X F
COPY T F

SEEK -9999
COPY -1 X

MARK COUNT
SEEK 1
ADDI X 1 X
TEST EOF
FJMP COUNT

SEEK -9999
COPY F T
JUMP COPY
The first thing I do after finding the file is jump back and create a target file to copy the data to (file 400). I store the LINK ID in there to free up the registers. In the MARK COPY loop, I go to the source file, read two entries to X and T, drop it, go back to the target file, and write them there. Then I count the length of the file (minus one for the link ID sitting at the start) and put that in X so I can SEEK to that position in the source file. That count takes time, but it's a simple solution.

I read the link ID from the file, and go back to copy the next two values.

Once the whole file is copied, the EXA will crash because it'll try to read past the end of the file. This doesn't have to be a problem, I could have another EXA that waits at home for all this time using a simple wait loop and comes fetch this file when it's done. That's a bit complicated though, because the file can be both in the middle host and the far host, and if it can't find the file it will crash, so I would have to send two separate EXAs and... no thanks.

Let's instead just write a way for this EXA to know when it's done so it doesn't crash.

This turns out to be very easy. The file always has 44 words, so all I have to do is hardcode that in a TEST. The end becomes:
code:
MARK COUNT
SEEK 1
ADDI X 1 X
TEST EOF
FJMP COUNT

SEEK -9999
TEST X = 44
TJMP DONE
COPY F T
JUMP COPY

MARK DONE
VOID F
LINK -1
LINK -1
LINK -1
If the EXA copied 44 entries, void the value holding the LINK ID. Handling the fact that the EXA might be in different hosts is also easy, just add one more LINK -1 and if it's closer to home that will crash the EXA in the home host, make it drop the file there.



Large variety in the scores here. My code runs at 2654/105/62. The top percentiles are 531, 60 and 46.

Let's look at some improvements. Before I start, here's all the code so far.
code:
GRAB 300
LINK 800

COPY 999 T

MARK LPL
COPY T #LOCK
ADDI X #LOCK X
SUBI T 111 T
TJMP LPL

COPY X #LOCK

LINK 800
COPY 0 X

COPY 999 T

MARK LPL2
COPY T #LOCK
ADDI X #LOCK X
SUBI T 111 T
TJMP LPL2

COPY X #LOCK

COPY F X
WIPE

; FIND

@REP 4
LINK 80@{1,1}
GRAB 200
TEST F = X
FJMP SKIP@{1,1}
COPY 80@{1,1} T
JUMP FOUND
MARK SKIP@{1,1}
DROP
LINK -1

@END

LINK 800
LINK 801
GRAB 200
TEST F = X
FJMP SKIP
COPY 801 T
JUMP FOUND
MARK SKIP
DROP
LINK -1
LINK 802
COPY 802 T
GRAB 200

MARK FOUND
DROP
LINK -1

MAKE
COPY T F
COPY 0 X

MARK COPY
DROP
LINK T

GRAB 200
SEEK X
COPY F X
COPY F T
DROP

LINK -1
GRAB 400
SEEK 9999
COPY X F
COPY T F

SEEK -9999
COPY -1 X

MARK COUNT
SEEK 1
ADDI X 1 X
TEST EOF
FJMP COUNT

SEEK -9999
TEST X = 44
TJMP DONE
COPY F T
JUMP COPY

MARK DONE
VOID F
LINK -1
LINK -1
LINK -1
The first thing I can change is removing the COUNT loop. If I just keep a counter and increase it by 2 every time I copy two values, that's much faster. I didn't do that right away because I didn't have any registers free... but I can just use another value at the start of the file instead.
code:
;XA CONT'd

MAKE
COPY 0 X
COPY 0 F
COPY T F

MARK COPY
DROP
LINK T

GRAB 200
SEEK X
COPY F X
COPY F T
DROP

LINK -1
GRAB 400
SEEK 9999
COPY X F
COPY T F

SEEK -9999
ADDI F 2 X
SEEK -1
TEST X = 44
TJMP DONE
COPY X F
COPY F T
JUMP COPY

MARK DONE
VOID F
VOID F
LINK -1
LINK -1
LINK -1
565/103/62, much better speed.


To reduce the size, I can roll up the file search loops. But how? I already need X and T and I can't use M. The answer is to prepare a file that can handle the loops.

After the LPL:
code:
COPY F X
WIPE

MAKE
COPY 804 F
COPY 803 F
MARK NEXT
COPY 802 F
COPY 801 F
SEEK -9999

MARK SEARCH
COPY F T
DROP

LINK T
GRAB 200
TEST F = X
TJMP FOUND
DROP
LINK -1
GRAB 400
VOID F
TEST EOF
FJMP SEARCH

LINK 800
JUMP NEXT
The loop works by VOIDing a value from the file once I've tried it. I don't need the weird workaround to find the host ID anymore inside the loop. That'll just be the first value of file 400. So the MARK FOUND now GRABs that file, copies that value to T, WIPEs it and creates a fresh file to copy the data to. 611/79/62.

If you move the LPL loops into a separate EXA you can combine the two loops into one, the EXA will just die when it tries to write to a non-existent third #LOCK. However, that means the copying EXA has to wait at home until the LPL EXA is done, and the extra lines for the wait loop are the same amount as the lines saved by doing this, so the score is the same.


I have one more idea for a speed optimization.

Copying is slow, with only one EXA and while having to LINK between hosts. But I don't need to copy everything, significant chunks of the file are already in the censored copy. Let's make use of that.



After OBTAIN, the word SPECIAL is missing. After DEPICTING, things get more complicated. Between DEPICTING and TECHNOLOGICALLY, there are three censored keywords in this file, but 5 words in the original file. And it doesn't get any better after that. Dealing with these different file lengths is very complicated and it makes you run out of space for lines of code very fast.

But we can just take that first chunk before the lengths start to differ.

In this optimization, XA's only purpose is the LPL.
code:
;XA

LINK 800

COPY 999 T

MARK LPL
COPY T #LOCK
ADDI X #LOCK X
SUBI T 111 T
TJMP LPL

COPY X #LOCK

LINK 800
COPY 0 X

COPY 999 T

MARK LPL2
COPY T #LOCK
ADDI X #LOCK X
SUBI T 111 T
TJMP LPL2

COPY X #LOCK
Meanwhile, in the safety of my home host, XB grabs the file and copies only the first 10 values (until DEPICTING) using M.
code:
;XB

GRAB 300
COPY 10 T
MARK LP
SUBI T 1 T
COPY F M
TJMP LP
XC starts with putting a couple zeroes in an empty file (for the counter and the LINK ID later on), then copies the data from M.
code:
;XC

MAKE
COPY 0 F
COPY 0 F

COPY 10 T

MARK LP
COPY M F
SUBI T 1 T
TJMP LP

SEEK -9999
SEEK 2
COPY F X

COPY 16 T

MARK WAIT
SUBI T 1 T
TJMP WAIT

LINK 800
LINK 800

DROP
It needs to wait a bit until LPL is done, but by that time it has the PROJECT OGRE keyword in X and can start searching. As you can see, the file is now DROPped instead of WIPEd.


The FIND code is almost the same as in my 565 cycles solution. The only difference is that if it needs to go search in the deepest host, it will take the file along.
code:
;XC at the end of the FIND code

SEEK 1

MARK FOUND
SEEK 3
COPY F X
DROP
LINK -1

GRAB 400
COPY 10 F
COPY T F
SEEK 4
COPY X F
COPY 10 X

MARK COPY
After FOUND, the EXA has the LINK ID in T, and X isn't needed anymore. So it uses that first visit to immediately copy the word SPECIAL. The extra SEEK 1 at the top was added to the fallthrough case where the very last host is the one holding the file. The TEST in all other cases also pushes the file cursor forward one step.

After grabbing the file containing the partially copied censored file, the EXA writes 10 to the copy counter (because the first 10 values are already in there), and then it caches the LINK ID in the file. Finally, it copies SPECIAL to the right position, and starts the general copy loop by putting the 10 count value into X. The copy loop stayed unchanged from before. The complete code is in the appendix at the bottom.

472/131/54. A cycles and activity improvement. And the cycles are better than the top percentile. My best lines of code solution was 79 as I showed above. Not bad.


Some final remarks: I did actually try to use all uncensored words in the censored file. But the code to move them forward and then only grab the necessary words from the uncensored file just didn't fit. A solution like that requires a SEEK F using the counter value at the start of the file. Interestingly enough, that instruction actually moves the cursor forward F + 1 places, because the act of reading from F also moves the cursor forward one.

I think the only way to get the top percentile activity of 46 is to actually use code like that. It's a reduction of 8 more activity, and each activity roughly corresponds to copying one value (you copy 2 per round but a round requires 2 LINKs). There are 9 more values in the uncensored file (and with some trickery you could reuse the words THE and A because they both occur twice in the file), so theoretically you could save slightly more than 8 activity, but this might not be possible without going over the max size limit.

Well, if anyone in the thread has any ideas, I'd like to hear. Let's finish this update.



Finishing hydroponix' task makes 4 more tasks appear in the list. Next time, =plastered.


=== Appendix ===

code:
;XA

LINK 800

COPY 999 T

MARK LPL
COPY T #LOCK
ADDI X #LOCK X
SUBI T 111 T
TJMP LPL

COPY X #LOCK

LINK 800
COPY 0 X

COPY 999 T

MARK LPL2
COPY T #LOCK
ADDI X #LOCK X
SUBI T 111 T
TJMP LPL2

COPY X #LOCK

;XB

GRAB 300
COPY 10 T
MARK LP
SUBI T 1 T
COPY F M
TJMP LP

;XC

MAKE
COPY 0 F
COPY 0 F

COPY 10 T

MARK LP
COPY M F
SUBI T 1 T
TJMP LP

SEEK -9999
SEEK 2
COPY F X

COPY 16 T

MARK WAIT
SUBI T 1 T
TJMP WAIT

LINK 800
LINK 800

DROP

; FIND

@REP 4
LINK 80@{1,1}
GRAB 200
TEST F = X
FJMP SKIP@{1,1}
COPY 80@{1,1} T
JUMP FOUND
MARK SKIP@{1,1}
DROP
LINK -1

@END

GRAB 400
LINK 800
DROP
LINK 801
GRAB 200
TEST F = X
FJMP SKIP
COPY 801 T
JUMP FOUND
MARK SKIP
DROP
LINK -1
LINK 802
COPY 802 T
GRAB 200
SEEK 1

MARK FOUND
SEEK 3
COPY F X
DROP
LINK -1

GRAB 400
COPY 10 F
COPY T F
SEEK 4
COPY X F
COPY 10 X

MARK COPY
DROP
LINK T

GRAB 200
SEEK X
COPY F X
COPY F T
DROP

LINK -1
GRAB 400
SEEK 9999
COPY X F
COPY T F

SEEK -9999
ADDI F 2 X
SEEK -1
TEST X = 44
TJMP DONE
COPY X F
COPY F T
JUMP COPY

MARK DONE
VOID F
VOID F
LINK -1
LINK -1
LINK -1

Carbon dioxide fucked around with this message at 19:04 on Apr 23, 2023

GuavaMoment
Aug 13, 2006

YouTube dude
You completely forgot about the hack Bosnian Bill and I made. For shame!

My activity is 367 for this, lol. I was deep into the not giving an eff portion of the game and was happy with what worked.

Carbon dioxide
Oct 9, 2012

GuavaMoment posted:

You completely forgot about the hack Bosnian Bill and I made. For shame!

Uh, am I missing something?

GuavaMoment
Aug 13, 2006

YouTube dude

Carbon dioxide posted:

Uh, am I missing something?

"First we rotate all the disks as far clockwise as they'll go, then I'll use the pick Bosnian Bill and I made..."

It's like LPLs biggest catchphrase!

Carbon dioxide
Oct 9, 2012

GuavaMoment posted:

"First we rotate all the disks as far clockwise as they'll go, then I'll use the pick Bosnian Bill and I made..."

It's like LPLs biggest catchphrase!

ok you got me I only watch LPLs april fools videos and the occassional video I get recommended.

Quackles
Aug 11, 2018

Pixels of Light.


Carbon dioxide posted:

Project Virgil
(1983-1987) Explore use of chimpanzees as pilots for small reconnaissance aircraft to reduce radar cross-section and human casualties. Of 9 tests, 7 completed take-off but 0 returned to base. Result: FAILURE


I wonder if this is a reference to Project X? It's a 1987 film where the Air Force is training chimpanzees to operate flight simulators, to test how a fighter pilot would behave after being hit by the radiation from a nuclear explosion. The main human character resolves to break out the chimpanzees after realizing this means fatally irradiating them.

EDIT: Oh, and the main chimpanzee character of the film is named Virgil, too.

Quackles fucked around with this message at 06:13 on Oct 30, 2022

Quackles
Aug 11, 2018

Pixels of Light.


Anyway, time for me to take a stab at my version. This version actually has a few funny stories behind it.

When I first solved this puzzle, I didn't figure out the lock, so my version tested the codes 111, 112, 113, 114, 115... and so on. I ended up with a whopping 20k cycle count. This is part of why the cycles histogram for this puzzle is so skewed.

Later, before the LP began, I went back and figured the lock out. That got me to 795/83/100.

Now, I've adopted a number of optimizations from CO2's post, including loop unrolling (albeit for the lock in my case), and hardcoding the expected length of the file.

code:
	;FIRST TRY TO BREAK LOCK
	;IT'S LIKE A MASTERMIND GAME - 
	;CORRECT DIGITS ARE HELD
GRAB 300
LINK 800

MARK LOCKLOOP
@REP 9
COPY @{111,111} #LOCK 		;111, 222, 333...
ADDI T #LOCK T
@END
COPY T #LOCK
We start by unrolling the lock loop to copy 111 through 999 into the lock and adding the result to T. T has the completed combination at the end of the lock.

code:
;NOW WE SEARCH
LINK 800
COPY F X	;'PROJECT OGRE'
DROP 

@REP 4
LINK 80@{1,1}	;801, 802, 803, 804
GRAB 200
TEST F = X
TJMP 80@{1,1}COPY		;IT WORKS WITH LABELS TOO!
DROP
LINK -1
@END
COPY 0 T
GRAB 300
JUMP LOCKLOOP

@REP 4
MARK 80@{1,1}COPY
COPY 80@{1,1} T		;SET LINK ID (T) TO 801, 802...
JUMP COPY
@END
This block of code tests each of the files in 801-804 to see if it's about Project Ogre. If so, we jump to 80#COPY (there are four of these labels), which sets the link ID before jumping to the label COPY, which copies the file.

code:
MARK COPY
	;PERFORM SOME SETUP
	;AS WE CAN'T HOLD 3 VARS AT ONCE
DROP
LINK -1
GRAB 300
COPY 4 X 	;FILE PTR
COPY X F
COPY T F 	;LINK ID
DROP

MARK COPYLOOP
LINK T
GRAB 200
SEEK X
COPY F T
COPY F X
DROP
LINK -1
GRAB 300
SEEK F
COPY T F
COPY X F
SEEK -9999
ADDI F 2 X 	;FILE PTR
TEST X > 42
TJMP BAIL
COPY F T 	;LINK ID
SEEK -2
COPY X F
DROP
JUMP COPYLOOP
Finally, we're ready to do the actual copying. We replace the first two values of file 300 (the source file) with the file pointer (4) and link ID. Then, the copy loop has us go into the link, jump to the chosen point in the file, and copy two values into X and T. We return to file 300 and use SEEK F to pick up where we left off. (As CO2 noted above, this actually jumps us to location F+1, but there's a fix for that later. As the last part of the loop, we update the file pointer, read the link ID again, and dive back in.

Once the file pointer is over 42, we jump to the end code:

code:
MARK BAIL
	;REPLACE ORIGINAL TWO FILE VALUES
SEEK -9999
SEEK 1
COPY F X
DROP
LINK X
GRAB 200
COPY F X
COPY F T
DROP
LINK -1
GRAB 300
COPY X F
COPY T F
SEEK 2
VOID F
LINK -1
LINK -1
LINK -1
We grab the first two values from file 200 and write over where we stored the file pointer and link ID in our file. Then we return home.

506/112/60. Not bad, all things being equal.

Carbon dioxide
Oct 9, 2012

Part 48 - The Wardialer


=== Trash World Inbox ===

Quackles posted:

Anyway, time for me to take a stab at my version. This version actually has a few funny stories behind it.

When I first solved this puzzle, I didn't figure out the lock, so my version tested the codes 111, 112, 113, 114, 115... and so on. I ended up with a whopping 20k cycle count. This is part of why the cycles histogram for this puzzle is so skewed.

Later, before the LP began, I went back and figured the lock out. That got me to 795/83/100.

Now, I've adopted a number of optimizations from CO2's post, including loop unrolling (albeit for the lock in my case), and hardcoding the expected length of the file.
code:
	;FIRST TRY TO BREAK LOCK
	;IT'S LIKE A MASTERMIND GAME - 
	;CORRECT DIGITS ARE HELD
GRAB 300
LINK 800

MARK LOCKLOOP
@REP 9
COPY @{111,111} #LOCK 		;111, 222, 333...
ADDI T #LOCK T
@END
COPY T #LOCK
We start by unrolling the lock loop to copy 111 through 999 into the lock and adding the result to T. T has the completed combination at the end of the lock.
code:
;NOW WE SEARCH
LINK 800
COPY F X	;'PROJECT OGRE'
DROP 

@REP 4
LINK 80@{1,1}	;801, 802, 803, 804
GRAB 200
TEST F = X
TJMP 80@{1,1}COPY		;IT WORKS WITH LABELS TOO!
DROP
LINK -1
@END
COPY 0 T
GRAB 300
JUMP LOCKLOOP

@REP 4
MARK 80@{1,1}COPY
COPY 80@{1,1} T		;SET LINK ID (T) TO 801, 802...
JUMP COPY
@END
This block of code tests each of the files in 801-804 to see if it's about Project Ogre. If so, we jump to 80#COPY (there are four of these labels), which sets the link ID before jumping to the label COPY, which copies the file.
code:
MARK COPY
	;PERFORM SOME SETUP
	;AS WE CAN'T HOLD 3 VARS AT ONCE
DROP
LINK -1
GRAB 300
COPY 4 X 	;FILE PTR
COPY X F
COPY T F 	;LINK ID
DROP

MARK COPYLOOP
LINK T
GRAB 200
SEEK X
COPY F T
COPY F X
DROP
LINK -1
GRAB 300
SEEK F
COPY T F
COPY X F
SEEK -9999
ADDI F 2 X 	;FILE PTR
TEST X > 42
TJMP BAIL
COPY F T 	;LINK ID
SEEK -2
COPY X F
DROP
JUMP COPYLOOP
Finally, we're ready to do the actual copying. We replace the first two values of file 300 (the source file) with the file pointer (4) and link ID. Then, the copy loop has us go into the link, jump to the chosen point in the file, and copy two values into X and T. We return to file 300 and use SEEK F to pick up where we left off. (As CO2 noted above, this actually jumps us to location F+1, but there's a fix for that later. As the last part of the loop, we update the file pointer, read the link ID again, and dive back in.

Once the file pointer is over 42, we jump to the end code:
code:
MARK BAIL
	;REPLACE ORIGINAL TWO FILE VALUES
SEEK -9999
SEEK 1
COPY F X
DROP
LINK X
GRAB 200
COPY F X
COPY F T
DROP
LINK -1
GRAB 300
COPY X F
COPY T F
SEEK 2
VOID F
LINK -1
LINK -1
LINK -1
We grab the first two values from file 200 and write over where we stored the file pointer and link ID in our file. Then we return home.

506/112/60. Not bad, all things being equal.
A slightly different solution than I had but the general approach is similar. That's not too surprising - the single EXA constraint limits the options. Also, I'm not sure if I've used the REP variable label trick before. I know I considered it for other puzzles but often it didn't make much of a difference.


=== The Wardialer ===



It is time to create that wardialer we heard about in the chat.


OST: Network Exploration

The assignment:
- File 300 contains a phone number where one to three of the digits have been replaced with a placeholder (X). Using your modem, dial all possible phone numbers that could be generated by replacing the placeholders with the digits 0 through 9. When you discover a valid phone number that connects to another host, append it to file 301.
- Note that the phone numbers must be dialed and appended in a specific order such that a placeholder is only incremented when the placeholder to the right cycles through all possible values from 0 to 9 and is reset to 0.
- For more information see "Hacker Skills: Model Control at the Direct Level" in the second issue of the zine.


Another modem level. The second point of the assignment is just a complicated way to say the results should be ordered from low to high.

The files in the remote hosts contain some other phone numbers. They don't match the partial number in file 300 so I have no use for them. There's also some hardware registers out there I don't care about for this assignment.

For this first test, the partial number is 94-0-177X-X96X. To get the phone numbers in order, I should first dial 94-0-1770-0960, then 94-0-1770-0961 and so on.
The easiest way to handle this is if I can just update those placeholders in-place. However, I need to keep track which values are placeholders.

I'm thinking I can do this by choosing alternate numbers that are outside the normal range and that can easily be converted back to the actual ones.
I think I'll just subtract 10 from every number, so the digit 0 becomes -10, and the digit 9 becomes -1. To dial it, I'll just have to add 10 again.
code:
GRAB 300
LINK 800

; FIX PLACEHOLDERS
MARK FIXLP
TEST EOF
TJMP DIAL
TEST F < 9999
TJMP FIXLP
SEEK -1
COPY -10 F
JUMP FIXLP
This replaces all the X's (non-digits) with -10, which will dial the digit 0. Let's just try dialing the first number.
code:
MARK DIAL
SEEK -9999

@REP 11
MODI F 10 #DIAL
@END
While I was writing this part I suddenly realized my choice to offset everything by 10 is even more useful than I thought. I can use the modulo function to change them into the correct digit and send them to the #DIAL at the same time.

As a side note, it turns out that there's no single definition for modulo operations on negative numbers. Is -4 / 10 equal to -1 remainder 6 or to 0 remainder -4? Different programming languages disagree on this so this code might fail if run on anything else than an EXA. In this case, MODI -4 10 is equal to 6, which is what I need.

Now, what's next? Creating a REPL to go check if there's a connection? Sure, we could do that, but it's always good to pay close attention to what's happening in the system.

In this case, I'm coding for the NETronics NET40 modem, not the TEC EXA-Blaster from the previous assignments. It's almost identical but it has one significant difference.



There's a number visible on this #DIAL register. That means it isn't write-only. When you read it, it returns 1 when the modem is connected, 0 otherwise. That means the entire connection check is just a single TEST of the #DIAL register.
code:
COPY #DIAL T
FJMP NEXTNUMBER

; TODO

;HANGUP
COPY -1 #DIAL

MARK NEXTNUMBER
So the test code is just this. I'll implement storing the number later. Let's first focus on updating the number in the right order. Currently it ignores whether the number is correct and just continues on, so I can test that part independently.

code:
MARK NEXTNUMBER

; ALREADY AT EOF
SEEK -1
TEST F < 0
SEEK -1
FJMP NEXTNUMBER

ADDI F 1 T
SEEK -1
FJMP ROLLOVER
COPY T F
JUMP DIAL

MARK ROLLOVER
COPY -10 F
SEEK -1
JUMP NEXTNUMBER
Because the EXA just finished dialing, the cursor is already at EOF. I want to update the numbers right to left, so that's convenient. All file access moves the cursor. ADDI F 1 F would add 1 to the value in F and write that to the NEXT position in F. So I need an intermediate register.

In most cases, it will just add 1 to the value and write that back to the file. The jump to ROLLOVER only happens if T is 0 - that is, if I just tried all 10 digits. In that case, I need to write -10 back to that value so the code keeps recognizing it as a placeholder. Then I skip over it, and find the next placeholder in the file.

Next round, the first placeholder is -10 again, it becomes -9, and so on, until it rolls back round to 0, the ROLLOVER is triggered and the next digit is incremented. It works recursively for however many placeholders there are.

Once all numbers have been tried, it will rollover past the final placeholder in the file, and get in an infinite loop as it keeps SEEKing to the start forever.

Let's handle that.

There's no direct way to test if the cursor is at the start of the file. You could test if the digit at the current location is the same as the one at the start but of course digits can occur multiple times. Since the phone numbers are always 11 digits I decided to use a simple counter.

Just after dialing, 11 is stored to X. The NEXTNUMBER loop is amended:
code:
MARK NEXTNUMBER
TEST X = 0
TJMP END

; ALREADY AT EOF
SEEK -1
TEST F < 0
SEEK -1
SUBI X 1 X
FJMP NEXTNUMBER
A SUBI and a TEST. At the bottom of the code, MARK END just has a WIPE after it to get rid of the temporary file.

With that done, it's time to copy the valid numbers.

I can replace the TODO from earlier with a simple snippet that copies the actual digits over M:

code:
SEEK -9999
@REP 11
MODI F 10 M
@END
Then, the final touch is an XB that can receive the data.




gently caress nazis. Anyway, XB can just read M 88 times, because for every test there are 8 valid numbers, so that's 8 times 11 digits.

This is my first working solution.



With 147 lines, the fully unrolled XB just barely fits. And with 65K cycles, it takes an age and a half to even verify that this is a working solution.

So, I'm at 65154/147/1. Top percentiles are 20.7K, 47 and 1. Let's see if I can improve some things.


First of all, for a quick reduction of the size, I'll just roll up loops.
Simply replacing all three @REPs with a COPY number T followed by a loop that has a SUBI T 1 T and a TJMP reduces the size to 52, but the cycle count becomes 88338.

If XB KILLs XA after it got all the phone numbers, I can remove XA's counter and completion check in NEXTNUMBER. This brings the size down to 51.
code:
DROP

LINK 800
KILL
GRAB 300
WIPE
67922/51/3. It doesn't just save a line, it's also quite a bit faster.

I also have a couple non-conditional JUMP statements. Perhaps I can get rid of one by reordering the code.
code:
;XA

GRAB 300
LINK 800

; FIX PLACEHOLDERS
MARK FIXLP
TEST EOF
TJMP DIAL
TEST F < 9999
TJMP FIXLP
SEEK -1
COPY -10 F
JUMP FIXLP

MARK NEXTNUMBER
; ALREADY AT EOF
SEEK -1
TEST F < 0
SEEK -1
FJMP NEXTNUMBER

ADDI F 1 T
SEEK -1
FJMP ROLLOVER
COPY T F

MARK DIAL
SEEK -9999
COPY 11 T

MARK DIALLP
MODI F 10 #DIAL
SUBI T 1 T
TJMP DIALLP


COPY #DIAL T
FJMP NEXTNUMBER

SEEK -9999
COPY 11 T

MARK COPYLOOP
MODI F 10 M
SUBI T 1 T
TJMP COPYLOOP

;HANGUP
COPY -1 #DIAL

MARK ROLLOVER
COPY -10 F
SEEK -1
JUMP NEXTNUMBER

;XB

GRAB 301

COPY 88 T

MARK LP
COPY M F
SUBI T 1 T
TJMP LP

DROP

LINK 800
KILL
GRAB 300
WIPE
66950/50/3. The JUMP from NEXTNUMBER to DIAL has been removed. After a successful connection, the code now falls through into the ROLLOVER but that just adds an unused -10 to the EOF, it does no harm.

And for the final size reduction, I can save a line by replacing the COPY #DIAL T with MULI #DIAL 11 T. The FJMP will still act the same, but the COPY 11 T just below that is no longer necessary. 66942/49/3. Only 2 lines away from the top percentile score.


Except for rolling up the loops, all the changes I just did not only reduced the size, but also made the solution run faster.

Keeping the speed improvements and unrolling all three loops again, I'm at 43904/145/3. Much better than what I had but still far from the top percentile.


I have a couple ideas on how to get there. For instance, it would be much faster to update the placeholders from left to right, but that would require sorting in XB. Or, you could send multiple digits over M with one message, because 4 digits fit in a number. But that would require multiplying the digits by factors of 10, and that's difficult when I also need the MODI trick to deal with the placeholders. It might not really be faster.

The third option and the one which I think is most likely to work, is parallelism. You could have one EXA dialing while another is updating the number. Of course you can't do that on the same number so you'd have to make a copy. Perhaps one EXA could try all numbers where the last placeholder is odd, another where all of them are even.

And the final option, of course, is to cheese it by building special cases for the slowest tests.

I don't like to cheese the tests if I don't have to, so let's start with the third option. Here is the entire code:

code:
;XA LOCAL

GRAB 300
LINK 800

REPL ODD

; FIX PLACEHOLDERS
MARK FIXLP
TEST EOF
TJMP COPY
TEST F < 9999
TJMP FIXLP
SEEK -1
COPY -10 F
JUMP FIXLP

MARK ODD
MAKE
@REP 11
COPY M F
@END

COPY 0 M

MARK MAKELASTODD
SEEK -1
TEST F = -10
SEEK -1
FJMP MAKELASTODD
COPY -9 F
JUMP DIAL


MARK COPY
SEEK -9999
@REP 11
COPY F M
@END

MARK DIAL
VOID M

SEEK -9999

@REP 11
MODI F 10 #DIAL
@END

MULI #DIAL 11 T
FJMP RELEASE

SEEK -9999

MODE

@REP 11
MODI F 10 M
@END

MODE

;HANGUP
COPY -1 #DIAL

MARK RELEASE
COPY 0 M

MARK NEXTNUMBER

SEEK -1
TEST F < 0
SEEK -1
FJMP NEXTNUMBER

ADDI F 2 T
SEEK -1
SUBI T X T
FJMP ROLLOVER
COPY T F
TEST T = 1
TJMP ODDROLLOVER

COPY 0 X
JUMP DIAL


MARK ROLLOVER
COPY 1 X
COPY -10 F
SEEK -1
JUMP NEXTNUMBER

MARK ODDROLLOVER
COPY 1 X
SEEK -1
COPY -9 F
SEEK -1
JUMP NEXTNUMBER


;XB GLOBAL

GRAB 301

COPY 8 T

MARK LP
@REP 11
COPY M F
@END
SUBI T 1 T
TJMP LP

DROP

LINK 800
KILL
KILL
GRAB 300
WIPE
GRAB 400
WIPE
The 'main' XA works the same as before, replacing placeholders with -10. After that it sends the whole file over M. The ODD EXA retrieves the whole file, then changes the very last placeholder to -9 (corresponding to the digit 1 for dialing).

The communications in LOCAL mode are purely to prevent both EXAs from dialing at the same time. As soon as one is done dialing, it releases the other with an M message so it can start dialing the next number. I added MODE instructions around the copy-to-XB code so that part is done in GLOBAL mode.

I also needed some special code in the NEXTNUMBER. It keeps X set to zero unless a rollover is triggered, in which case X becomes 1. The code updates each placeholder value by adding (2 - X). That way, it tries every 2nd number for the last placeholder, but every single number for the others.

In the old logic, once the value was increased all the way up to 0 this triggered the ROLLOVER through an FJMP. For the odd EXA, this won't work because the value will jump from -1 to 1, both considered true. I added special handling for this case through the ODDROLLOVER.

XB is mostly the same. I had to partially roll up the loop to make space for the extra code, and of course kill the additional EXA and its file.

38579/127/4. An improvement but not as much as I'd hoped. It might be slightly faster to release the other XA before sending data over GLOBAL M, but that leads to an issue where the odd EXA gets stuck once the even one tries all numbers and gets in the infinite loop, never reaching the VOID M.

There are specific tests that run very slowly. It's because all the placeholders are near the start of the phone number, and every time it seeks through the entire file from end to start.

I can partially solve this by storing the location of the last placeholder in the file, like so:

code:
MARK FIND

SEEK -1
TEST F = -10
ADDI X 1 X
SEEK -1
FJMP FIND
SEEK 9999
SUBI 0 X F
COPY 0 X
This FIND loop runs after the placeholders have been replaced by -10, but before the data is copied to the ODD EXA. The additional value is copied to the ODD EXA too. I need an extra SEEK -1 before the MAKELASTODD in case the value found is exactly -10. Then, just after the RELEASE, when a number was dialed, the cursor is sitting on this additional value in the file, and a SEEK F brings it directly to the last placeholder. The SEEK F moves the cursor one place forward before seeking back, but this works out because the NEXTNUMBER starts with a SEEK -1 anyway.

22549/139/4. Getting much closer to the top percentile. The slowest test is now one that has the last placeholder near the end of the file, but two others near the start.

That could possibly be solved by keeping track of the location of multiple placeholders, but that's a lot more difficult than what I just did. Some tests only have one placeholder at all, so you'd have to handle that. It'd also require some additional place to even store it, where it could be accessed quickly. And I'm running out of space for lines of code. So, I'll leave further improvements to you. The full code is just below in the appendix if you want to give it a go.

Next time, we help selenium_wolf.


=== Appendix ===

Wardialer 22549/139/4

code:
;XA LOCAL

GRAB 300
LINK 800

REPL ODD

; FIX PLACEHOLDERS
MARK FIXLP
TEST EOF
TJMP FIND
TEST F < 9999
TJMP FIXLP
SEEK -1
COPY -10 F
JUMP FIXLP

MARK ODD
MAKE
@REP 12
COPY M F
@END

COPY 0 M

SEEK -1

MARK MAKELASTODD
SEEK -1
TEST F = -10
SEEK -1
FJMP MAKELASTODD
COPY -9 F
JUMP DIAL


MARK FIND

SEEK -1
TEST F = -10
ADDI X 1 X
SEEK -1
FJMP FIND
SEEK 9999
SUBI 0 X F
COPY 0 X

SEEK -9999
@REP 12
COPY F M
@END

MARK DIAL
VOID M

SEEK -9999

@REP 11
MODI F 10 #DIAL
@END

MULI #DIAL 11 T
FJMP RELEASE

SEEK -9999

MODE

@REP 11
MODI F 10 M
@END

MODE

;HANGUP
COPY -1 #DIAL

MARK RELEASE
COPY 0 M
SEEK F

MARK NEXTNUMBER

SEEK -1
TEST F < 0
SEEK -1
FJMP NEXTNUMBER

ADDI F 2 T
SEEK -1
SUBI T X T
FJMP ROLLOVER
COPY T F
TEST T = 1
TJMP ODDROLLOVER

COPY 0 X
JUMP DIAL


MARK ROLLOVER
COPY 1 X
COPY -10 F
SEEK -1
JUMP NEXTNUMBER

MARK ODDROLLOVER
COPY 1 X
SEEK -1
COPY -9 F
SEEK -1
JUMP NEXTNUMBER
code:
;XB GLOBAL

GRAB 301

COPY 8 T

MARK LP
@REP 11
COPY M F
@END
SUBI T 1 T
TJMP LP

DROP

LINK 800
KILL
KILL
GRAB 300
WIPE
GRAB 400
WIPE

Carbon dioxide fucked around with this message at 19:07 on Apr 23, 2023

Quackles
Aug 11, 2018

Pixels of Light.


Hoo boy.

So this will be a long post, or possibly a series of posts. My original attempt at this puzzle on my initial playthrough was... not great, and in fact quite overcomplicated. It had stats of 133148/110/6, which was downright embarassing. In my attempts to better my score, I ended up making three completely different solutions, of increasing complexity and efficiency. I'll list all three, but probably at the rate of one per post, as each solution requires its own explanation.


My first attempt used the 'straightforward method'. It won't stay this simple. The solution is split into three EXAs - two go into the modem and one stays in the main host.

code:
GRAB 300
LINK 800

	;INITIAL STARTUP: 
	;WE NEED TO SEE HOW MANY PLACEHOLDERS THERE ARE.

MARK PCOUNTLOOP
TEST F > -1
TJMP PCONTINUE
ADDI X 1000 X
MARK PCONTINUE
TEST EOF
FJMP PCOUNTLOOP

COPY X M

SEEK -9999
MARK DIALLOOP
COPY F X 
TEST X > -1
FJMP PLACEHOLDER
COPY X #DIAL
JUMP DIALEND

MARK PLACEHOLDER
COPY 1 M
MARK DIALEND
TEST EOF
FJMP DIALLOOP

	;IF WE'RE HERE, NUMBER HAS BEEN DIALED.
	;CHECK #DIAL TO SEE IF WE MADE IT

COPY #DIAL M
SEEK -9999
COPY M T
FJMP DIALLOOP

	;NOW WE DO THE SAME THING AGAIN, BUT WE WRITE TO GLOBAL M INSTEAD. 
	;LOCAL M IS USED TO PROD THE PLACEHOLDER EXA TO WRITE THE DIGITS OF THE CURRENT PLACEHOLDER...

MODE 		;GLOBAL
MARK DIALLOOP2
COPY F X 
TEST X > -1
FJMP PLACEHOLDER2
COPY X M
JUMP DIALEND2

MARK PLACEHOLDER2
MODE 		;LOCAL
COPY 1 M
MODE 		;GLOBAL
MARK DIALEND2
TEST EOF
FJMP DIALLOOP2

SEEK -9999
MODE 		;LOCAL
COPY M T 		;EOF ON THE OTHER SIDE?
MODE 		;GLOBAL
FJMP DIALLOOP2
WIPE
The first EXA is the heart of the operation. Both EXAs that go into the modem start in local mode, so they can talk to each other. This one enumerates the regular digits of the phone number, while the other rattles off the placeholder digits when cued by this one.

The first thing this EXA does is to count the number of placeholders in the file. It sends this number, times 1000, to the other EXA. The reason it multiplies by 1000 is because the second EXA will use the lower 3 digits of the number ('000') to store the placeholder digits, and the upper digit to store the placeholder count. Both values need to share one register because the second EXA's T will be in use for other things.

Once the placeholder-count setup is complete, the first EXA starts going through its main loop ('DIALLOOP'). It checks if the next digit of the file is a number, or a placeholder. If a number, it sends it to #DIAL. If it's a placeholder, it pings the second EXA to get it to send the correct placeholder digit instead. This repeats until all digits have been dialed (determined by TEST EOF); once that happens, the first EXA checks #DIAL to see if the connection went through.

The first EXA sends the value of #DIAL to the second EXA to let it know the number went through; the second EXA sends back a value (0 or 1) to let the first EXA know whether to continue to spit out digits, or move on to the next section of the code (marked by 'DIALLOOP2').

In the 'DIALLOOP2' section, the two EXAs cooperate to send the working numbers back to the EXA waiting in the host. This loop is very similar to the previous section, except both EXAs write digits to global M, instead of #DIAL. Once again, the second EXA is responsible for deciding when enough numbers have been sent; when this happens, the first EXA wipes file 300 to leave no trace, and halts.

code:
LINK 800
MAKE
@REP 8
COPY 0 F
@END
SEEK -9999
COPY M X 		;WILL BE 1000, 2000, OR 3000 DEPENDING ON # OF DIGITS

MARK PHLOOP
SWIZ X 0004 T
MARK PHLOOP2
VOID M
SWIZ X T #DIAL
SUBI T 1 T
TJMP PHLOOP2
			;ALL DONE, WAIT FOR PROD
COPY M T 	;CONTAINS #DIAL
FJMP NEXT
COPY X F 		;RECORD PLACEHOLDER DIGITS
COPY -1 #DIAL 	;HANG UP
TEST EOF 
MARK NEXT
COPY T M
ADDI X 1 X
FJMP PHLOOP

	;WHAT JUST HAPPENED: 
	;IF NO NUMBER FOUND, COND IS FALSE. LOOP.
	;ELSE IF NO EOF, COND IS FALSE. LOOP.
	;ONLY IF LAST NUMBER CAUSES EOF DO WE CONTINUE.

SUBI X 1 X

	;AT THIS POINT, WE'VE TESTED ALL COMBINATIONS WE NEED TO.
	;REPORT BACK ON THE SUCCESSFUL ONES.
	;WE ALWAYS REPORT 8 VALUES...
	;IT'S ALMOST AS IF WE'RE IN A COMPUTER SIMULATION OR SOMETHING ;)

SEEK -9999

MARK PHLOOPII
SWIZ X 0004 T
MARK PHLOOPII2
VOID M
MODE ;GLOBAL
SWIZ F T M
MODE ;LOCAL
SEEK -1
SUBI T 1 T
TJMP PHLOOPII2	
	;ALL DONE, DO WE CONTINUE?
SEEK 1
TEST EOF
COPY T M
FJMP PHLOOPII
WIPE
The second EXA's job is both sending placeholders and cueing the first EXA to change modes. Exactly eight valid phone numbers will be present in each test run, and this solution exploits that; the second EXA starts by creating a temp file and writing eight 0s to it. Each space in the file will store the placeholder digits for a valid phone number, and we can test to see if we've found all eight phone numbers with TEST EOF.

The second EXA does need to know how many placeholder digits are in each number, though, so it waits for the first EXA to tell it (receiving either 1000, 2000, or 3000 as described above). Then it enters its main loop.

In the second EXA's main loop ('PHLOOP'), it uses T to track which placeholder digit should be output next. T is derived from the uppermost digit of X. When the second EXA is cued on M by the first EXA, it sends the correct placeholder digit to #DIAL (the SWIZ X T #DIAL). This repeats until the correct number of digits have been sent.

At this point, the second EXA waits for a confirmation code (the value of #DIAL) from the first EXA. If no number was found, we add 1 to X (thereby incrementing the placeholder digits properly), tell the first EXA to continue, and jump back to the start of the loop. But if a number was found, we record X in F first (along with hanging up the modem).
If we have written eight values in F, then all the correct numbers have been found, so we cue the first EXA to move to the next part of its code instead. Otherwise, we loop back to the start of this section.

With all eight numbers found, we just have to play back the correct ones. The first EXA takes the lead again, writing the placeholder digits over global M, and cueing this EXA over local M to send the next placeholder. Instead of keeping an incrementing counter, this EXA plays back the eight values from F for the placeholder digits.
The code that plays back the placeholder digits ('PHLOOPII') is very similar to the PHLOOP block, except for the destination (global M).

Once again, the second EXA cues the first EXA when all eight values have been written, sending a confirmation code; this will cause the first EXA to stop. Like the first EXA, once we have played back all the numbers, we're done; the second EXA wipes its file, and halts.

code:
	;WAITS AT HOME FOR RETURNED NUMBERS
GRAB 301

COPY 88 T
MARK COPYLOOP
COPY M F
SUBI T 1 T
TJMP COPYLOOP
The third EXA, which sits in our host and records the numbers, is very simple. It writes 88 digits to file 301 (8 phone numbers x 11 digits). That's it.


Once the dust settled, I was left with results of 83681/97/2. The histograms strongly suggest that everyone finds this solution.
However, I concluded I wanted to do better.

Next up: A matter of timing.

Quackles
Aug 11, 2018

Pixels of Light.


After being unsatisfied with the speed of my first attempt, I concluded that one of the things that was making it slow was the regular M communication between the two EXAs in the modem. So I thought to myself, "If the digit and placeholder EXAs needed to communicate as rarely as possible, and each loop took the same time to run, I could synchronize them and have them run for a while without having to waste time with M communications!"

It worked.


code:
	;DIGIT EXA, PART ONE
GRAB 300
LINK 800

;INITIAL STARTUP: 
;WE NEED TO SEE HOW MANY PLACEHOLDERS THERE ARE.
;ALSO, XA AND XB BOTH NEED TO HOLD A FILE WITH THE PHONE NUMBER PATTERN.

MARK PCOUNTLOOP
TEST F > -1
SEEK -1
TJMP PDIGIT
;MARK PPLACEHOLDER
COPY F M
ADDI X 1000 X
SEEK -1
COPY 0 F
JUMP PCONTINUE
MARK PDIGIT
COPY F T
COPY T M
SEEK -1
ADDI T 10 F
	;TRANSFORM THE NUMBERS ON OUR SIDE TO SPEED UP THE KERNEL. 
MARK PCONTINUE
TEST EOF
FJMP PCOUNTLOOP

SUBI X 1000 M
code:
	;PLACEHOLDER EXA, PART ONE
LINK 800
MAKE

	;THIS NEEDS A COPY OF THE PHONE NUMBER AND THE # OF PLACEHOLDERS.

COPY 11 T
MARK COPYNUMLOOP2
COPY M F
SUBI T 1 T
TJMP COPYNUMLOOP2

COPY M X  ;# OF PLACEHOLDERS
This is another three-EXA solution, where the first two EXAs (which go into the modem) start in local mode. The third EXA, which is here to record data, stays in our host and has global M.

Like the previous solution, the first EXA has exclusive jurisdiction of the digits of the phone number, but that's where similarities end. Both EXAs now need to hold a file with the digits of the phone number. This is the first part of the code: setup to copy over the phone number. The second EXA (which handles the placeholders) still needs to know the number of placeholders, which is sent over M.

As it copies, the digit EXA also transforms the contents of its own file. Any digit has 10 added to it, while any X becomes 0. This is to speed up the performance of the main dialing loop later on.

code:
	;DIGIT EXA, PART TWO
MARK PREDIALSYNC
COPY 1 M 	;SYNC

	;MAIN LOOP, NUMBER VER
MARK PREDIALLOOP
SEEK -9999
@REP 4 		;KILLING TIME
NOOP 
@END

MARK DIALLOOP 		;KERNEL
COPY F T      		;1
FJMP DONE     		;2
MARK SEND
SWIZ T 0001 #DIAL ;D3
MARK DONE   
TEST EOF      		;D4, PH3
FJMP DIALLOOP 		;D5, PH4

NOOP          	;WAIT FOR TEST EOF AFTER PHLOOP1 IN OTHER EXA
COPY #DIAL T
COPY -1 #DIAL 	;HANG UP
FJMP PREDIALLOOP
The digit EXA uses a fairly simple loop to perform the dialing. After the two EXAs synchronize with M, the digit EXA enters its dialing loop. This loop runs at a steady rate, synchronized with the placeholder EXA; after waiting for the placeholder EXA to perform a setup sequence, this EXA enters the main digit-sending loop - I'll call this the 'kernel'.

Since the program spends most of its time in the kernel, it's important that we make it as quick as possible. This kernel takes 5 cycles to send a digit, and 4 cycles to (wait for the other EXA to) send a placeholder. I think this is the minimum number of cycles it's possible to get for each character type for this approach.

Once the kernel has looped 11 times and all the digits of the number have been dialed, the digit exa reads #DIAL (and hangs up, if we dialed a valid number). If we didn't find anything, we jump back to the starting loop (the code can continue to run without resynchronizing). If we do find something, we fall through to the part of the code where we record the number, but we'll talk about that a little later on.

code:
	;PLACEHOLDER EXA, PART TWO
MARK PREPHSYNC
VOID M 	;SYNC

MARK PREPH
SWIZ X 0004 T     	;A1
SEEK -9999        	;A2
FJMP PREPHLOOP1   	;A3
SUBI T 1 T        	;A4
FJMP PHLOOP2      	;A5
	;FALL TO PHLOOP3

MARK PHLOOP3
TEST F > -1    	;1
NOOP           	;2
FJMP SEND3     	;3
NOOP           	;D4
JUMP PHLOOP3   	;D5
MARK SEND3     
SWIZ X 0003 #DIAL ;PH4
	;FALLS THROUGH

MARK PHLOOP2
TEST F > -1    	;1
FJMP SEND2     	;2
NOOP           	;D3
NOOP           	;D4
JUMP PHLOOP2   	;D5
MARK SEND2     
SWIZ X 0002 #DIAL ;PH3
JUMP PHLOOP1   	;PH4

	;PREPHLOOP1 NEEDS 2 NOOPS TO MATCH START TIMING
MARK PREPHLOOP1
NOOP              	;A4
NOOP              	;A5
MARK PHLOOP1
TEST F > -1    	;1
FJMP SEND1     	;2
NOOP           	;D3
NOOP           	;D4
JUMP PHLOOP1   	;D5
MARK SEND1
SWIZ X 0001 #DIAL ;PH3
TEST EOF       	;PH4
TJMP TEST      ;EXTRA - MADE UP FOR BY NOOP BY DIAL CHECK IN OTHER EXA

MARK PHLOOP0
SEEK 1         	;D1
TEST EOF       	;D2
NOOP           	;D3
NOOP           	;D4
FJMP PHLOOP0   	;D5

	;NOW ALL NUMBERS READ—
	;TEST IF SUCCESS

MARK TEST
COPY #DIAL T
ADDI X 1 X
FJMP PREPH
While the digit EXA's main loop is simple, the placeholder EXA's main loop has a lot to do. The design issue is simple: we're using T to test if the current digit is a number, and we're using X to store the placeholder digits. So we have no way to count how many placeholders are left with a regular loop... or do we?

The solution is loop unrolling on a grand scale. After a 5-line prologue at the start of the loop which checks how many placeholders are present (this is why the digit EXA has four NOOPs - to stay in sync during this), the placeholder EXA switches to one of three variants of the placeholder loop. Each is carefully timed with NOOPs to be 5 cycles long, or 4 cycles when actually outputting the placeholder.

The variants of the placeholder loop fall or jump to the next variant after the placeholder is output, with PHLOOP0 being the last (since there are no more placeholders, it can just TEST EOF and wait for the digit EXA to finish up). Finally, once all the numbers of the file have been output, the placeholder EXA also checks #DIAL, then either wraps back to the top or falls through to where the number we've found will be recorded.

code:
	;DIGIT EXA, PART THREE

;NOW WE HAVE TO PLAY BACK THE FOUND NUMBER
;WE USE AN OLDER METHOD FOR THIS (SAVES SPACE)

MODE 	;G
SEEK -9999

MARK PLAYBACK
COPY F T 
FJMP PLACEHOLDER
SWIZ T 0001 M
JUMP DIALEND

MARK PLACEHOLDER
MODE 	;L
COPY 1 M
MODE 	;G
MARK DIALEND
TEST EOF
FJMP PLAYBACK
COPY M T 	;STOP?
MODE
COPY T M 	;ECHO TO PLACEHOLDER EXA
TJMP PREDIALSYNC

WIPE
code:
	;PLACEHOLDER EXA, PART THREE

;WE USE AN OLD STYLE KERNEL TO PLAY BACK THE NUMBERS, TO SAVE SPACE
;IT'S SLOWER, BUT THERE'S ONLY 8 OF THESE
;SO IT'S FINE

ADDI X 999 X 	;+1000, -1
SWIZ X 0004 T
SEEK -9999

MARK PLAYBACK
VOID M
MODE 	;G
SWIZ X T M
MODE 	;L
SUBI T 1 T
TJMP PLAYBACK
COPY M T 		;STOP?
SUBI X 999 X 	;-1000, +1
TJMP PREPHSYNC

WIPE
In this version, when a number is found, it's recorded right away so we don't have to store it. For this, both EXAs break synchronization, switching to a variant of my first solution (where the digit EXA plays back each digit over global M, prodding the placeholder EXA over local M when it's its turn to contribute). Once the number has been fully transmitted, the digit EXA receives a confirmation code from the EXA that remained in the host as to whether it should continue looking for numbers or not. It will echo this code to the placeholder EXA. Both EXAs then either jump back to the (re-)synchronization step at the start of the main loop, or gracefully halt.

code:
	;RECEIVER EXA
	;RECORDS DATA ONLY
GRAB 301
COPY 8 X 	;HOW MANY TIMES TO LOOP?

MARK FILELOOP
COPY 11 T

MARK FILELOOPINNER
COPY M F
SUBI T 1 T
TJMP FILELOOPINNER

SUBI X 1 T
COPY T M
COPY T X
TJMP FILELOOP
The receiver EXA, which sits in the host, is very simple. It keeps track of how many phone numbers are left in X, then loops to write 11 values from global M 8 times. Each time it writes one full phone number, it sends the value of X out over M to cue to the other EXAs to keep going (normally) or stop (if it's now 0).
That's it.

61718/141/2. A distinct improvement from my previous solution... but I still wasn't satisfied. So I came up with a crazy idea...

Next up: What if we just did COPY F #DIAL 11 times?

Quackles
Aug 11, 2018

Pixels of Light.


After the partial success of my last solution, I was thinking about how I had minimized the number of cycles needed for each step of the 'kernel' loop that actually emitted the digits. It was clear that it wasn't possible to optimize the loop further as it was, especially with the conditional logic involved in the loop (TEST F > -1, JUMP, TEST EOF, etc.)

So a crazy idea came through my head: what if the loop where the EXA rattled off the digits didn't have any conditional logic at all?

It turns out this is possible, though it took me several days to write. The basic idea works like this: we transform the list of 11 digits into a series of roughly 22 values (11 pairs of two values each). Each pair of values is an offset to seek by, and a digit to display; the EXA that runs the central loop can simply do SEEK F / COPY F #DIAL 11 times (!) to send all the digits to the modem.

The reason we do this is for a simple reason: not only do we get a fast dialing procedure, we can make sure all the placeholder digits are the last three digits in the file. Since the dialing EXA deliberately reads the digits out of order, when the time comes to increment the placeholder digits, we can just start from the end of the file, and work backwards if more than one placeholder needs to be updated at once.

The code below works in two stages. In the first stage, we do the setup. This is most of the lines of code but very little of the total cycles. In the second stage, we dial and record the numbers. This is simple, but slower.


Setup: Basic File Structure

There are three EXAs involved in setup: the parser-recorder, which parses the provided phone number; the offset calculator, which is responsible for 'wiring up' the connections to the placeholders at the end of the file, and the main EXA, which does a bunch of the setup math and will also do the dialing. All three start in the main host.

Critically, the parser-recorder and main EXAs start in local mode, while the offset calculator starts in global mode. This will be critical when routing messages between EXAs.

code:
	;PARSER-RECORDER EXA, SETUP PHASE 1

GRAB 300
MARK SAYNUMLOOP
COPY F X
TEST X > -1 	;DIGIT = T,  PLACEHOLDER = F
TJMP SEND
MODE 		;GLOBAL
COPY 1 X
MARK SEND
COPY X M	;DIGIT TO MAIN EXA, OR 1 TO OFFSET CALCULATOR
TJMP SEND2
MODE 		;LOCAL
MARK SEND2
TEST EOF
FJMP SAYNUMLOOP

MODE ;GLOBAL - TO OFFSET CALCULATOR
COPY 0 M	;TELL CALCULATOR TO GO
The first part of the parser-recorder's code has it parse the file with the phone number. Any digit gets sent to the main EXA, while any placeholder gets a '1' sent to the offset calculator, so that the calculator can count the placeholders. Once the end of the file is reached, the parser-recorder sends a '0' to the offset calculator to let it know to take over.

code:
	;OFFSET CALCULATOR EXA, SETUP PHASE 1
	;COUNT PLACEHOLDERS
MARK PHCOUNT
COPY M T
ADDI X T X
TJMP PHCOUNT

	;X IS NOW 1-3
	;THIS SETS UP THE PLACEHOLDERS AT THE END OF THE FILE

MODE ;LOCAL
ADDI X 1 T
MARK PADFILE
COPY 0 M
SUBI T 1 T
TJMP PADFILE
The offset calculator first counts the placeholders as cued by the parser-recorder. Once it receives a 0, it switches modes and starts sending 0s to the main EXA directly. It always sends one more 0 than there are placeholders. The main EXA is recording all the digits it receives, so this ensures it always receives a total of 12 digits, for a total of 25 values in the file (12 pairs of numbers plus an extra offset after the placeholders).

code:
	;MAIN EXA, SETUP PHASE 1
	;COPY NON-PLACEHOLDER VALUES INTO FILE, WITH GENERIC OFFSETS
	;ALSO COPY IN BLANKS FOR PLACEHOLDERS/PADDING

MAKE
COPY 0 F
COPY 12 T
MARK SETUP1
COPY M F
COPY 0 F
SUBI T 1 T
TJMP SETUP1
The main EXA hasn't done much yet. Once it finishes receiving the 12 digits from the other two EXAs, it will have 8-10 pairs with digit values, 1-3 pairs which will hold placeholders, and an extra pair between them to serve as a buffer (if the buffer isn't there, it causes a nasty edge case when an offset leading to a placeholder is a 0).

The next step is to calculate the offsets so that the digits are dialed in the correct order. We pick up again with the parser-recorder.


Setup: Offset Calculation

code:
	;PARSER-RECORDER EXA, SETUP PHASE 2
SEEK -9999
COPY 0 X

MARK PHOFFSETLOOP
TEST F > -1
FJMP PHFOUND
ADDI X 1 X
JUMP PHOFFLOOPEND

MARK PHFOUND
COPY X M 	;SEND PLACEHOLDER OFFSETS TO OFFSET CALCULATOR
		
MARK PHOFFLOOPEND
TEST EOF
FJMP PHOFFSETLOOP
The parser-recorder goes back and runs through the phone number pattern file again. However, this time, it's looking for the position (the offset) of each placeholder in the file. These position(s) are transmitted over global M to the offset calculator.

code:
	;OFFSET CALCULATOR EXA, SETUP PHASE 2
	;OK! FILE IS ALL FIGURED OUT. NOW WE JUST NEED TO SET THE OFFSETS.

SUBI 4 X X 	;PH COUNT OF 1, 2, 3 => 3, 2, 1 (NEEDED FOR CALCULATIONS)
MARK PHLOOP
MODE 	;GLOBAL - LISTENING TO PARSER-RECORDER
COPY M T
MODE 	;LOCAL - SENDING TO MAIN EXA

MULI T 2 M 	;SEEK TO TARGET OFFSET IN FILE

;OFFSET FORMULA IS (8-T+X)*2
SUBI 8 T T
ADDI T X T
MULI T 2 M 	;THE VALUE TO WRITE TO THAT OFFSET - MAIN EXA DOES THE REST

ADDI X 1 X
TEST X < 4
COPY T M 	;1 = STILL CALCULATING PLACEHOLDER OFFSETS
TJMP PHLOOP
The offset calculator takes these positions and expands them into a pair of instructions that are sent to the main EXA. Specifically, it tells the main EXA which offset in the file to write to, and the value to write to it. When the SEEK F loop hits that offset later, it will jump ahead the correct number of places, to the placeholder digit to send to #DIAL. (The correct number of places is (8 - [position of placeholder, counting from 0] + [number of which nth placeholder this is, starting from 1]) x 2.)

The offset calculator also keeps track of the number of placeholders left, and cues the main EXA when it's time to move on. Once that happens, it will halt.

code:
	;MAIN EXA, SETUP PHASE 2
	;BUNCHA MATH HERE

MARK SETUP2
SEEK -9999
SEEK M
ADDI M 1 X

	;BACK-TO-BACK PLACEHOLDER TEST: HAS THIS OFFSET BEEN WRITTEN TO?
COPY F T
TJMP BACK2BACK

	;NORMAL PLACEHOLDER JUMP - WRITE OUR VALUES AND BE DONE WITH IT
SEEK -1
SUBI X 1 F 	;WRITE STARTING VALUE
SEEK X		;JUMP TO AFTER THE PLACEHOLDER DIGIT
ADDI X 1 X
MULI X -1 X
COPY X F	;WRITE ENDING 'JUMP BACK' VALUE
JUMP SETUP2END

	;BACK-TO-BACK PLACEHOLDERS - DON'T WRITE OUR VALUE. INSTEAD ERASE THE OLD 'JUMP BACK' VALUE AND WRITE A NEW ONE TWO PLACES ATER IT
MARK BACK2BACK
SEEK T
SEEK 1
COPY F T
TJMP B2BOK
SEEK 1			;BONUS EDGE CASE - THREE PLACEHOLDERS IN A ROW
COPY F T
MARK B2BOK
SEEK -1
COPY 0 F	;ERASE PREVIOUS JUMPBACK VALUE
SEEK 1
SUBI T 2 F	;WRITE NEW JUMPBACK VALUE AFTER

MARK SETUP2END
	;CHECK FOR END
COPY M T 	;ARE THERE MORE PLACEHOLDERS?
TJMP SETUP2
The main EXA has the big job. As cued by the offset calculator, it jumps to the specific place in the file and writes the offset that it will use to jump to the placeholder digit later. However, it has to write a corresponding 'jump back' offset after the digit so the dialing loop returns to the correct place in the regular digits.

Under normal circumstances, with the pattern that's set up above with all the digits in order at the start, the 'jump back' value is (regular value + 2) x -1. However, there's an unfortunate edge case.

Sometimes, placeholders appear back-to-back in the file. When this happens, we don't write the first offset. Instead, we find the 'jump back' value that was previously there, set it to 0 (which will cause SEEK F to move to the next value in the file without jumping), and write a new 'jump back' value after the next placeholder digit. The new 'jump back' value is equal to (previous jump back value) - 2.

The way we can tell if a back-to-back placeholder is coming up is by looking at the place where we'd write the first offset. If it's a 0, the offset hasn't been used and it's not a back-to-back situation. If it's some other number, the offset has been used, and we switch to the back-to-back code. This is the main reason why we have a pair of buffer values in between the digits and placeholders; this ensures that if we jump to a placeholder, the offset that causes the jump will always be nonzero.

To make matters worse, we have to account for the very special case of three placeholders in a row! If the 'jump back' offset we'd erase is already a 0, then we've been here once before - we can simply move forward two more places in the file if this happens to sort out this issue.


Confused yet? Once all that math and jumping around is done, a file that originally looked like this...

9, 4, 0, 1, 7, 7, X, X, 9, 6, X

...will end up looking like this:

0, 9, 0, 4, 0, 1, 0, 7, 0, 7, 6, 9, 0, 6, 6, 0, 0, 0, 0, 0, -10, 0, -8

We can separate that out a bit. Numbers marked with a * are 'jump forward' offsets, while negative numbers are 'jump back' offsets. Placeholder digits are marked with a #:

0, 9, 0, 4, 0, 1, 0, 7, 0, 7, *6, 9, 0, 6, *6,    0, 0,    #0, 0, #0, -10, #0, -8

This also divides it into three blocks, showing the digits, buffer zone, and placeholders. This is what we needed for everything else to be as fast as it should be.
See if you can follow it yourself! Treat each first digit you run into as an offset (SEEK F) and each second digit as to be broadcasted. Remember that SEEK F also advances the file pointer, so SEEK F with a 0 moves your focus in the file 1 place forward.



Main Loop

At this point, it's time for the main EXA to do the actual dialing.

code:
	;MAIN EXA, MAIN LOOP

;OK! TIME TO GO OUT INTO THE BIG BAD WIDE WORLD
;AND DIAL SOME PHONE NUMBERS

LINK 800
MODE 	;GLOBAL - SEND TO RECORDER

	;THIS IS WHERE THE MAGIC HAPPENS
MARK DIAL
SEEK -9999
COPY 2 T
MARK DIALLOOP

@REP 5		;COULDN'T FIT 11 TIMES, HAD TO MAKE DO WITH 5x2+1
SEEK F
COPY F #DIAL
@END
SUBI T 1 T
TJMP DIALLOOP
SEEK F
COPY F #DIAL

COPY #DIAL T
FJMP INCREMENT

	;IF NUMBER FOUND: PLAY IT BACK OVER GLOBAL M
COPY -1 #DIAL
SEEK -9999
COPY 11 T
MARK PLAYBACKLP
SEEK F
COPY F M
SUBI T 1 T
TJMP PLAYBACKLP
COPY M T
FJMP DONE

	;ADD TO PLACEHOLDER DIGITS
MARK INCREMENT
SEEK 9999
SEEK -2
MARK INCREMENT2
ADDI F 1 X
SEEK -1
MODI X 10 F
TEST X > 9
SEEK -3
TJMP INCREMENT2
JUMP DIAL

	;AND WE'RE DONE
MARK DONE
WIPE
This code is where the magic happens. The main EXA enters the modem and uses SEEK F / COPY F #DIAL 11 times to dial the number. (I couldn't fit a @REP 11 and stay in the 150-instruction limit, so I had to make do with a loop of two @REP 5s and an extra left over.)
Once the number is dialed, the EXA checks #DIAL to see what turned up.

If a number is found, the main EXA readies itself to send the message to the recorder; it does this by rewinding to the start of the file and doing SEEK F / COPY F M 11 times (with a loop this time, to save space). These values will be picked up by the parser-recorder EXA, which remains in the host to record the phone numbers. The recorder EXA will cue the main EXA whether to continue or halt.

Once a number has been broadcast to the recorder (or not), the main EXA (if it didn't halt) increments the placeholders. It does by going to the second-last value in the file. This is the first placeholder. It adds 1 and writes it to the file; if the result is over 0, it backtracks two more places to the next placeholder and adds 1, and so on. Then it jumps back to the dial loop to start the process all over again.

code:
	;PARSER-RECORDER EXA, MAIN LOOP

DROP
GRAB 301
COPY 8 X

MARK WRITEOUTER
COPY 11 T

MARK WRITELOOP
COPY M F
SUBI T 1 T
TJMP WRITELOOP

SUBI X 1 X
COPY X T
COPY T M
TJMP WRITEOUTER
The parser-recorder now switches into recorder mode, recording 88 values in 8 groups of 11. This is a very simple loop, which also sends the count of remaining phone numbers over M to act as a confirmation code. That's it.


Conclusion

This took days to code, most of which was debugging and kicking out edge cases. But it's been worth it. 40251/145/1. I'm quite proud of this, and this is where I'll declare victory.

Quackles fucked around with this message at 11:26 on Nov 7, 2022

Carbon dioxide
Oct 9, 2012

Part 49 - Española Valley High School


=== Trash World Inbox ===

Last time, I built the wardialer. My top scores were 22549/49/1.

Quackles submitted three different solutions to the puzzle with a whole lot of explanation. Nice.
Full disclosure, one of the reasons for featuring thread submissions in my updates is so everyone reading this elsewhere (LP Beach, SA, or the archive) can see them. But of course I do have to make a decision on what to actually show to keep it interesting for all readers.

I think I need to summarize Quackles' submissions a bit to make this work. If you want to see them in full, they're posted in the SA thread.


The first solution has 3 EXAs. One sends the static digits of the phone number, the other handles placeholder digits. The way it works is that the first one asks the second one for a digit whenever it sees the placeholder letter, and the second just keeps the current value of all placeholders in the X register, incrementing it by 1 each round, and uses SWIZ to send the separate digits to the first EXA, whenever the first EXA prompts it. It is told when a phone number works and then saves the placeholder digits to a file.
The third EXA just sits in the home host waiting for the result. At the end, the first EXA sends all the static digits to the third EXA, and asks the second EXA whenever a placeholder comes up.

This solution runs at 83681/97/2.


Quackles's second solution runs at 61718/141/2. It still uses the placeholders-in-X with SWIZ trick. The main difference is that the first and second EXAs now both have the full phone number, so the second EXA doesn't need to be prompted to send a placeholder. They can just make sure that the dialing loop is perfectly synced up and send their own digits when their turn comes. For sending the digits to the third EXA, something similar to the first solution was used. I'm thinking it might be very slightly faster to have the second EXA handle that entirely but I didn't check.

The main issue with this solution is that there are lots of wasted cycles, because for every digit, both EXAs need to test if it's for them or not. Quackles solved that in solution number 3.

I'll post that in full. Note that Quackles is calling the main dialing loop the "kernel".

Quackles posted:

After the partial success of my last solution, I was thinking about how I had minimized the number of cycles needed for each step of the 'kernel' loop that actually emitted the digits. It was clear that it wasn't possible to optimize the loop further as it was, especially with the conditional logic involved in the loop (TEST F > -1, JUMP, TEST EOF, etc.)

So a crazy idea came through my head: what if the loop where the EXA rattled off the digits didn't have any conditional logic at all?

It turns out this is possible, though it took me several days to write. The basic idea works like this: we transform the list of 11 digits into a series of roughly 22 values (11 pairs of two values each). Each pair of values is an offset to seek by, and a digit to display; the EXA that runs the central loop can simply do SEEK F / COPY F #DIAL 11 times (!) to send all the digits to the modem.

The reason we do this is for a simple reason: not only do we get a fast dialing procedure, we can make sure all the placeholder digits are the last three digits in the file. Since the dialing EXA deliberately reads the digits out of order, when the time comes to increment the placeholder digits, we can just start from the end of the file, and work backwards if more than one placeholder needs to be updated at once.

The code below works in two stages. In the first stage, we do the setup. This is most of the lines of code but very little of the total cycles. In the second stage, we dial and record the numbers. This is simple, but slower.

Setup: Basic File Structure

There are three EXAs involved in setup: the parser-recorder, which parses the provided phone number; the offset calculator, which is responsible for 'wiring up' the connections to the placeholders at the end of the file, and the main EXA, which does a bunch of the setup math and will also do the dialing. All three start in the main host.

Critically, the parser-recorder and main EXAs start in local mode, while the offset calculator starts in global mode. This will be critical when routing messages between EXAs.
code:
	;PARSER-RECORDER EXA, SETUP PHASE 1

GRAB 300
MARK SAYNUMLOOP
COPY F X
TEST X > -1 	;DIGIT = T,  PLACEHOLDER = F
TJMP SEND
MODE 		;GLOBAL
COPY 1 X
MARK SEND
COPY X M	;DIGIT TO MAIN EXA, OR 1 TO OFFSET CALCULATOR
TJMP SEND2
MODE 		;LOCAL
MARK SEND2
TEST EOF
FJMP SAYNUMLOOP

MODE ;GLOBAL - TO OFFSET CALCULATOR
COPY 0 M	;TELL CALCULATOR TO GO
The first part of the parser-recorder's code has it parse the file with the phone number. Any digit gets sent to the main EXA, while any placeholder gets a '1' sent to the offset calculator, so that the calculator can count the placeholders. Once the end of the file is reached, the parser-recorder sends a '0' to the offset calculator to let it know to take over.
code:
	;OFFSET CALCULATOR EXA, SETUP PHASE 1
	;COUNT PLACEHOLDERS
MARK PHCOUNT
COPY M T
ADDI X T X
TJMP PHCOUNT

	;X IS NOW 1-3
	;THIS SETS UP THE PLACEHOLDERS AT THE END OF THE FILE

MODE ;LOCAL
ADDI X 1 T
MARK PADFILE
COPY 0 M
SUBI T 1 T
TJMP PADFILE
The offset calculator first counts the placeholders as cued by the parser-recorder. Once it receives a 0, it switches modes and starts sending 0s to the main EXA directly. It always sends one more 0 than there are placeholders. The main EXA is recording all the digits it receives, so this ensures it always receives a total of 12 digits, for a total of 25 values in the file (12 pairs of numbers plus an extra offset after the placeholders).
code:
	;MAIN EXA, SETUP PHASE 1
	;COPY NON-PLACEHOLDER VALUES INTO FILE, WITH GENERIC OFFSETS
	;ALSO COPY IN BLANKS FOR PLACEHOLDERS/PADDING

MAKE
COPY 0 F
COPY 12 T
MARK SETUP1
COPY M F
COPY 0 F
SUBI T 1 T
TJMP SETUP1
The main EXA hasn't done much yet. Once it finishes receiving the 12 digits from the other two EXAs, it will have 8-10 pairs with digit values, 1-3 pairs which will hold placeholders, and an extra pair between them to serve as a buffer (if the buffer isn't there, it causes a nasty edge case when an offset leading to a placeholder is a 0).

The next step is to calculate the offsets so that the digits are dialed in the correct order. We pick up again with the parser-recorder.


Setup: Offset Calculation
code:
	;PARSER-RECORDER EXA, SETUP PHASE 2
SEEK -9999
COPY 0 X

MARK PHOFFSETLOOP
TEST F > -1
FJMP PHFOUND
ADDI X 1 X
JUMP PHOFFLOOPEND

MARK PHFOUND
COPY X M 	;SEND PLACEHOLDER OFFSETS TO OFFSET CALCULATOR
		
MARK PHOFFLOOPEND
TEST EOF
FJMP PHOFFSETLOOP
The parser-recorder goes back and runs through the phone number pattern file again. However, this time, it's looking for the position (the offset) of each placeholder in the file. These position(s) are transmitted over global M to the offset calculator.
code:
	;OFFSET CALCULATOR EXA, SETUP PHASE 2
	;OK! FILE IS ALL FIGURED OUT. NOW WE JUST NEED TO SET THE OFFSETS.

SUBI 4 X X 	;PH COUNT OF 1, 2, 3 => 3, 2, 1 (NEEDED FOR CALCULATIONS)
MARK PHLOOP
MODE 	;GLOBAL - LISTENING TO PARSER-RECORDER
COPY M T
MODE 	;LOCAL - SENDING TO MAIN EXA

MULI T 2 M 	;SEEK TO TARGET OFFSET IN FILE

;OFFSET FORMULA IS (8-T+X)*2
SUBI 8 T T
ADDI T X T
MULI T 2 M 	;THE VALUE TO WRITE TO THAT OFFSET - MAIN EXA DOES THE REST

ADDI X 1 X
TEST X < 4
COPY T M 	;1 = STILL CALCULATING PLACEHOLDER OFFSETS
TJMP PHLOOP
The offset calculator takes these positions and expands them into a pair of instructions that are sent to the main EXA. Specifically, it tells the main EXA which offset in the file to write to, and the value to write to it. When the SEEK F loop hits that offset later, it will jump ahead the correct number of places, to the placeholder digit to send to #DIAL. (The correct number of places is (8 - [position of placeholder, counting from 0] + [number of which nth placeholder this is, starting from 1]) x 2.)

The offset calculator also keeps track of the number of placeholders left, and cues the main EXA when it's time to move on. Once that happens, it will halt.
code:
	;MAIN EXA, SETUP PHASE 2
	;BUNCHA MATH HERE

MARK SETUP2
SEEK -9999
SEEK M
ADDI M 1 X

	;BACK-TO-BACK PLACEHOLDER TEST: HAS THIS OFFSET BEEN WRITTEN TO?
COPY F T
TJMP BACK2BACK

	;NORMAL PLACEHOLDER JUMP - WRITE OUR VALUES AND BE DONE WITH IT
SEEK -1
SUBI X 1 F 	;WRITE STARTING VALUE
SEEK X		;JUMP TO AFTER THE PLACEHOLDER DIGIT
ADDI X 1 X
MULI X -1 X
COPY X F	;WRITE ENDING 'JUMP BACK' VALUE
JUMP SETUP2END

	;BACK-TO-BACK PLACEHOLDERS - DON'T WRITE OUR VALUE. INSTEAD ERASE THE OLD 'JUMP BACK' VALUE AND WRITE A NEW ONE TWO PLACES ATER IT
MARK BACK2BACK
SEEK T
SEEK 1
COPY F T
TJMP B2BOK
SEEK 1			;BONUS EDGE CASE - THREE PLACEHOLDERS IN A ROW
COPY F T
MARK B2BOK
SEEK -1
COPY 0 F	;ERASE PREVIOUS JUMPBACK VALUE
SEEK 1
SUBI T 2 F	;WRITE NEW JUMPBACK VALUE AFTER

MARK SETUP2END
	;CHECK FOR END
COPY M T 	;ARE THERE MORE PLACEHOLDERS?
TJMP SETUP2
The main EXA has the big job. As cued by the offset calculator, it jumps to the specific place in the file and writes the offset that it will use to jump to the placeholder digit later. However, it has to write a corresponding 'jump back' offset after the digit so the dialing loop returns to the correct place in the regular digits.

Under normal circumstances, with the pattern that's set up above with all the digits in order at the start, the 'jump back' value is (regular value + 2) x -1. However, there's an unfortunate edge case.

Sometimes, placeholders appear back-to-back in the file. When this happens, we don't write the first offset. Instead, we find the 'jump back' value that was previously there, set it to 0 (which will cause SEEK F to move to the next value in the file without jumping), and write a new 'jump back' value after the next placeholder digit. The new 'jump back' value is equal to (previous jump back value) - 2.

The way we can tell if a back-to-back placeholder is coming up is by looking at the place where we'd write the first offset. If it's a 0, the offset hasn't been used and it's not a back-to-back situation. If it's some other number, the offset has been used, and we switch to the back-to-back code. This is the main reason why we have a pair of buffer values in between the digits and placeholders; this ensures that if we jump to a placeholder, the offset that causes the jump will always be nonzero.

To make matters worse, we have to account for the very special case of three placeholders in a row! If the 'jump back' offset we'd erase is already a 0, then we've been here once before - we can simply move forward two more places in the file if this happens to sort out this issue.


Confused yet? Once all that math and jumping around is done, a file that originally looked like this...

9, 4, 0, 1, 7, 7, X, X, 9, 6, X

...will end up looking like this:

0, 9, 0, 4, 0, 1, 0, 7, 0, 7, 6, 9, 0, 6, 6, 0, 0, 0, 0, 0, -10, 0, -8

We can separate that out a bit. Numbers marked with a * are 'jump forward' offsets, while negative numbers are 'jump back' offsets. Placeholder digits are marked with a #:

0, 9, 0, 4, 0, 1, 0, 7, 0, 7, *6, 9, 0, 6, *6,    0, 0,    #0, 0, #0, -10, #0, -8

This also divides it into three blocks, showing the digits, buffer zone, and placeholders. This is what we needed for everything else to be as fast as it should be.
See if you can follow it yourself! Treat each first digit you run into as an offset (SEEK F) and each second digit as to be broadcasted. Remember that SEEK F also advances the file pointer, so SEEK F with a 0 moves your focus in the file 1 place forward.


Main Loop

At this point, it's time for the main EXA to do the actual dialing.
code:
	;MAIN EXA, MAIN LOOP

;OK! TIME TO GO OUT INTO THE BIG BAD WIDE WORLD
;AND DIAL SOME PHONE NUMBERS

LINK 800
MODE 	;GLOBAL - SEND TO RECORDER

	;THIS IS WHERE THE MAGIC HAPPENS
MARK DIAL
SEEK -9999
COPY 2 T
MARK DIALLOOP

@REP 5		;COULDN'T FIT 11 TIMES, HAD TO MAKE DO WITH 5x2+1
SEEK F
COPY F #DIAL
@END
SUBI T 1 T
TJMP DIALLOOP
SEEK F
COPY F #DIAL

COPY #DIAL T
FJMP INCREMENT

	;IF NUMBER FOUND: PLAY IT BACK OVER GLOBAL M
COPY -1 #DIAL
SEEK -9999
COPY 11 T
MARK PLAYBACKLP
SEEK F
COPY F M
SUBI T 1 T
TJMP PLAYBACKLP
COPY M T
FJMP DONE

	;ADD TO PLACEHOLDER DIGITS
MARK INCREMENT
SEEK 9999
SEEK -2
MARK INCREMENT2
ADDI F 1 X
SEEK -1
MODI X 10 F
TEST X > 9
SEEK -3
TJMP INCREMENT2
JUMP DIAL

	;AND WE'RE DONE
MARK DONE
WIPE
This code is where the magic happens. The main EXA enters the modem and uses SEEK F / COPY F #DIAL 11 times to dial the number. (I couldn't fit a @REP 11 and stay in the 150-instruction limit, so I had to make do with a loop of two @REP 5s and an extra left over.)
Once the number is dialed, the EXA checks #DIAL to see what turned up.

If a number is found, the main EXA readies itself to send the message to the recorder; it does this by rewinding to the start of the file and doing SEEK F / COPY F M 11 times (with a loop this time, to save space). These values will be picked up by the parser-recorder EXA, which remains in the host to record the phone numbers. The recorder EXA will cue the main EXA whether to continue or halt.

Once a number has been broadcast to the recorder (or not), the main EXA (if it didn't halt) increments the placeholders. It does by going to the second-last value in the file. This is the first placeholder. It adds 1 and writes it to the file; if the result is over 0, it backtracks two more places to the next placeholder and adds 1, and so on. Then it jumps back to the dial loop to start the process all over again.
code:
	;PARSER-RECORDER EXA, MAIN LOOP

DROP
GRAB 301
COPY 8 X

MARK WRITEOUTER
COPY 11 T

MARK WRITELOOP
COPY M F
SUBI T 1 T
TJMP WRITELOOP

SUBI X 1 X
COPY X T
COPY T M
TJMP WRITEOUTER
The parser-recorder now switches into recorder mode, recording 88 values in 8 groups of 11. This is a very simple loop, which also sends the count of remaining phone numbers over M to act as a confirmation code. That's it.


Conclusion

This took days to code, most of which was debugging and kicking out edge cases. But it's been worth it. 40251/145/1. I'm quite proud of this, and this is where I'll declare victory.

Oh my.

Yeah, this is quite hard to follow. It gets easier if you can step through the code, so I added Quackles' full solution in the appendix below this post so it can easily be copy-pasted into the game.

Remember that last optimization I did, where the location of the last placeholder was stored in the file, and I could just do a SEEK F?
In summary, Quackles took that and applied it to the whole program. That way you can just do 11 SEEK F, COPY F #DIAL instructions and that's it.

The setup to get there is quite convoluted though.

Since the strategy is so different, Quackles' solution is hard to compare to mine. I suspect this code is about as fast as my solution before I made the ODD/EVEN EXA split. A round of dialing takes Quackles 22 cycles. With my negative numbers and MODI it only took 11 cycles. However, I think Quackles saves cycles during the INCREMENT part by having the placeholders together at the end, so it evens out.

My code didn't require nearly as much fiddling at the start though. My program size was much smaller, which gave me enough space to implement the odd/even split. This roughly doubled the speed. Quackles' solution is very neat, but I'm even more convinced now that that MODI trick is the way to go for a top score.


=== School Management System ===



I don't know why we need to hack a school to sign someone up for a class, but fine. Let's do this.


OST: Network Exploration

The assignment:
- Replace ENGLISH with AP ENGLISH (file 300) in selenium_wolf's child's class schedule (file 235).
- Because those two classes are most likely not offered at the same time, you may need to rearrange the rest of their schedule to make it fit. Modify the schedule so that they are taking the same classes but at different times when necessary. A full list of classes offered is available in file 200.
- Note that there will only be one valid schedule.
- Also note that AP ENGLISH will only be offered once and each other class will be offered no more than twice.


Hm. "Modify the schedule" sounds quite complicated. Let's just do it one step at a time.

Most of the files are student's schedules. There are two other files.
The one in the cafeteria says: "Today's menu. Entrees: Hamburger, Chicken Burger, Veggie Burger, Sloppy Joe's. Salads: Taco Salad, Chef's Salad. Fresh Fruit: Apple Slicers, Tangerine, Seedless Grapes, Mixed Fruit Cup."

The file in the gymnasium says: "Attention students: Keep your hands and feet to yourself. This is not a "Kung Fu Dungeon". Be respectful of your fellow classmates."
And yes, setting the #POWR in the gymnasium to 0 turns the lights off in the digicam feed.

Enough distractions, let's get to the task.


Good to note, selenium_wolf's kid is always in grade 11.

I'm considering this general approach:
1. Remove ENGLISH class from the student's schedule.
2. Add AP ENGLISH to the file, in the right spot.
3. Check if it is at the same time as another class. If so, remove it, find the other moment that class is offered, and add that.
4. Repeat 3 until there is no more overlap.

Let's find out if that works.



The first step is easy enough. XA goes grab file 234 and finds ENGLISH (which it gets from XB over M), then deletes it.

For AP ENGLISH, I need to look it up in file 200. XB has nothing better to do, so it gets this task.



XB finds it in file 200, then sends both the time and name of the class over M.

XA finds a class planned at the same time so it can insert AP ENGLISH in the right spot. This code will fail if there's a gap in the schedule (the exact time for AP ENGLISH isn't found). However, if that happens we know we're done and can just insert it as the final value and finish up. This will also work if we're trying to insert other classes later.

So, for now I'll just add a TEST EOF to this loop and TJMP FINISH and I'll implement FINISH later.

To find the next value, I can mostly reuse the code. If XA copies the overwritten class to M, XB can load it into X, and just reuse the FINDAPENGLISH loop to find it.
The only difference is that XB might first run into the scheduled class that was just overwritten. We don't want to copy that because then it would just keep swapping the class in that time slot forever.
code:
;XA

LINK 800
LINK 800
COPY M X
LINK 802
GRAB 235
SEEK 2

MARK FINDENGLISH
SEEK 1
TEST X = F
FJMP FINDENGLISH

SEEK -2
VOID F
VOID F
COPY M X

MARK REPLACELP
SEEK -9999
SEEK 1


MARK FINDTIME
SEEK 1
TEST EOF
TJMP FINISH
TEST F = X
FJMP FINDTIME

COPY F M
SEEK -1
COPY M F
SEEK -2

COPY M X
TEST F = X
FJMP REPLACELP

COPY 0 M
COPY M X
JUMP REPLACELP

MARK FINISH
NOOP
After FINDTIME, first the class-to-be-overwritten class is copied to M. Then the new class is read from M and stored to the file.

At this point, XB will start looking for the class that was just overwritten. Once it finds it, it sends the time on M. XA compares this time to the time that was just used. If it's the same, XA tells XB by sending a 0 and waits for XB to send a different time. In either case, XA then jumps into REPLACELP to replace the next value.
Since every class occurs only twice, there's no need to check XB's second time, it'll always be different.

XB now looks like this.
code:
;XB

GRAB 300
COPY F M
COPY F X
DROP
LINK 800
GRAB 200

MARK FINDAPENGLISH
SEEK 1
TEST X = F
FJMP FINDAPENGLISH

SEEK -2
COPY F M
COPY M T
SEEK 1
FJMP FINDAPENGLISH

COPY X M
COPY T X
SEEK -9999
JUMP FINDAPENGLISH
After the find loop, it first copies the time to M. Then it waits for a response. This can be 0, indicating that it got the wrong instance of the class. In that case XB just jumps back into the find loop.

If the M value is not zero this means XA was happy with the time and immediately sent the next class to find.

If it's not zero, it's actually the name of the next class to find another time for. The class that goes in the current spot in XA is sent over M now, and the next class is copied from T to X and used for the next round.

Once XA hits FINISH, all entries are in the right order, except the final one which needs to be inserted into an empty spot.


File insertions in EXAPUNKS are hard. At this point I realized something. If I can assume the last rescheduled class will always take the place of the original ENGLISH class (meaning there's no gaps in the schedule), I don't need to insert anything. Instead, I can start by scheduling AP_ENGLISH, and then repeat the loop to move other classes around, until something replaces ENGLISH. At that point, everything is in order and I just need to stop the EXAs.

code:
;XA

LINK 800
LINK 800
LINK 802
GRAB 235

COPY M X

MARK REPLACELP
SEEK -9999
SEEK 1

MARK FINDTIME
SEEK 1
TEST F = X
FJMP FINDTIME

COPY F M
SEEK -1
COPY M F
SEEK -2

COPY M X
TEST F = X
FJMP REPLACELP

COPY 0 M
COPY M X
JUMP REPLACELP
XA won't need the FINISH logic at all anymore.

code:
;XB

GRAB 300
COPY F T
COPY F X
DROP
LINK 800

REPL ENGLISH_CHECK
GRAB 200

MARK FINDAPENGLISH
SEEK 1
TEST X = F
FJMP FINDAPENGLISH

SEEK -2
COPY F M
COPY M T
SEEK 1
FJMP FINDAPENGLISH

COPY X M
MODE
COPY T M
MODE
COPY T X
SEEK -9999
JUMP FINDAPENGLISH

MARK ENGLISH_CHECK
MODE
COPY T X

MARK CHECK
TEST M = X
FJMP CHECK

KILL
LINK 800
LINK 802
KILL
XB spawns a second EXA called ENGLISH_CHECK. it has the word ENGLISH in T (and then X) from the file 300. Every time the original XB gets a class name to find, it sends it to ENGLISH_CHECK, and if the class equals ENGLISH, this new EXA just goes around to kill the others.



My assumption was correct, this works. 717/55/8. Top percentiles are 608, 39 and 3.

Thinking about optimizations, activity is doable. Start off with one EXA, REPL it once to grab 200, then move the original forward into the grade 11 node. And instead of the KILLs add extra logic to each EXA to die if they get some specific M message or something.

As for speed, I got a minor improvement to 710 by unrolling the XA FINDTIME loop. Unrolling the loop itself doesn't save anything because you need the conditional checks anyway, but it does allow you to combine that SEEK 1 outside the loop with the first iteration of SEEK 1 inside the loop. Perhaps bigger improvements are possible by changing something in the search order, or have more efficient/less M communication. Less M communication might also save some cycles.

I'll leave the actual optimizations to the thread this time. Next update, x10x10x needs help to get back their anime.


=== Appendix ===

Quackles' wardialer solution in easy to copy format.

code:
;XA LOCAL

GRAB 300
MARK SAYNUMLOOP
COPY F X
TEST X > -1 
TJMP SEND
MODE ;GLOBAL
COPY 1 X
MARK SEND
COPY X M
TJMP SEND2
MODE ;LOCAL
MARK SEND2
TEST EOF
FJMP SAYNUMLOOP

MODE ;GLOBAL
COPY 0 M

SEEK -9999
COPY 0 X

MARK PHOFFSETLOOP
TEST F > -1
FJMP PHFOUND
ADDI X 1 X
JUMP PHOFFLOOPEND

MARK PHFOUND
COPY X M 

MARK PHOFFLOOPEND
TEST EOF
FJMP PHOFFSETLOOP

DROP
GRAB 301
COPY 8 X

MARK WRITEOUTER
COPY 11 T

MARK WRITELOOP
COPY M F
SUBI T 1 T
TJMP WRITELOOP

SUBI X 1 X
COPY X T
COPY T M
TJMP WRITEOUTER
code:
;XB GLOBAL

MARK PHCOUNT
COPY M T
ADDI X T X
TJMP PHCOUNT

MODE ;LOCAL
ADDI X 1 T
MARK PADFILE
COPY 0 M
SUBI T 1 T
TJMP PADFILE

SUBI 4 X X 
MARK PHLOOP
MODE ;GLOBAL
COPY M T
MODE ;LOCAL 
MULI T 2 M

SUBI 8 T T
ADDI T X T
MULI T 2 M 

ADDI X 1 X
TEST X < 4
COPY T M 

TJMP PHLOOP
code:
;XC LOCAL

MAKE
COPY 0 F
COPY 12 T
MARK SETUP1
COPY M F
COPY 0 F
SUBI T 1 T
TJMP SETUP1

MARK SETUP2
SEEK -9999
SEEK M
ADDI M 1 X

COPY F T
TJMP BACK2BACK

SEEK -1
SUBI X 1 F
SEEK X

ADDI X 1 X
MULI X -1 X
COPY X F

JUMP SETUP2END

MARK BACK2BACK
SEEK T
SEEK 1
COPY F T
TJMP B2BOK
SEEK 1

COPY F T
MARK B2BOK
SEEK -1
COPY 0 F

SEEK 1
SUBI T 2 F

MARK SETUP2END

COPY M T 

TJMP SETUP2

LINK 800
MODE ;GLOBAL

MARK DIAL
SEEK -9999
COPY 2 T
MARK DIALLOOP

@REP 5
SEEK F
COPY F #DIAL
@END
SUBI T 1 T
TJMP DIALLOOP
SEEK F
COPY F #DIAL

COPY #DIAL T
FJMP INCREMENT

COPY -1 #DIAL
SEEK -9999
COPY 11 T
MARK PLAYBACKLP
SEEK F
COPY F M
SUBI T 1 T
TJMP PLAYBACKLP
COPY M T
FJMP DONE

MARK INCREMENT
SEEK 9999
SEEK -2
MARK INCREMENT2
ADDI F 1 X
SEEK -1
MODI X 10 F
TEST X > 9
SEEK -3
TJMP INCREMENT2
JUMP DIAL

MARK DONE
WIPE

Carbon dioxide fucked around with this message at 19:11 on Apr 23, 2023

Dareon
Apr 6, 2009

by vyelkin
It's exactly the sort of solution a socially-awkward hacker (but I repeat myself) would come up with. "Dad, I accidentally registered for English instead of AP English so I'm getting an elective credit instead of the English I need to graduate." "Well I could spend several hours on the phone and in offices talking to actual people for a chance at fixing this, or I could use all this crime hardware I've been accumulating and guarantee a best-case result."

GuavaMoment
Aug 13, 2006

YouTube dude
This is the most difficult challenge in the game IMO. Getting under 100 lines was really difficult for me.

Quackles
Aug 11, 2018

Pixels of Light.


After the whole craziness that was last week's, this week is going to be straightforward. I don't have as much of an optimization on cycles as CO2, but I've been able to improve on activity a bit.

code:
;CLASS SCHEDULE WRITER EXA - STARTS GLOBAL M

GRAB 300
SEEK 1
COPY F X 	;'AP ENGLISH'
LINK 800
LINK 800
LINK 802
COPY X M      ;SEND 'AP ENGLISH'
COPY 0 M 	;NO AVOID TIME
DROP
GRAB 235
REPL MESSENGERBOT
MODE 	;LOCAL
MARK REPLACELOOP
SEEK -9999
SEEK 2
COPY M X   ;GET TARGET TIME

	;REPLACE LOOP WAS UNROLLED
TEST X = F
TJMP REPLACE
SEEK 1
TEST X = F
TJMP REPLACE
SEEK 1
TEST X = F
TJMP REPLACE
SEEK 1
TEST X = F
TJMP REPLACE
SEEK 3 	;SKIP LUNCH
TEST X = F
TJMP REPLACE
SEEK 1
TEST X = F
TJMP REPLACE
SEEK 2 	;NO NEED TO TEST IF LAST ITEM IN FILE

MARK REPLACE
COPY F X     ;GET OLD CLASS
SEEK -1
COPY M F    ;REPLACE CLASS NAME
COPY X M    ;SEND OLD CLASS
JUMP REPLACELOOP
This code runs in two EXAs, one of which splits during runtime. This is the first one. It travels to Hunter's class schedule file, gloms onto it, then sends the keyword 'AP ENGLISH' to the other EXA (which will search the global class list to find its time). Then it splits, spinning a copy of itself to MESSENGERBOT (see below).

Once MESSENGERBOT is started, the split (messenger) EXA will handle all communications with the global class list. This EXA just gets the time slot to write to from the messenger, finds that time slot in Hunter's schedule, overwrites the new class name, and sends the old class name.

It's worth noting that the loop where we search Hunter's schedule has been unrolled, allowing us certain optimizations. We can skip over the 'lunch' row entirely, and if we reach the end row, we don't need to test if it'll be the one we're looking for - it'll have to be.

code:
MARK MESSENGERBOT
GRAB 300
MARK MSGRLOOP
COPY M T   ;GET TIME SLOT TO REPLACE
MODE ;LOCAL
COPY T M   ;TELL WRITER TIME SLOT
COPY X M   ;TELL WRITER NEW CLASS NAME
COPY M X   ;GET OLD CLASS NAME
MODE ;G  ;CLASS
COPY X M   ;SEND OLD CLASS NAME
COPY T M   ;SEND OLD TIME SLOT AS AVOID
SEEK -1
TEST X = F ;ENGLISH 
FJMP MSGRLOOP

VOID M
COPY 0 M
WIPE ;HALT
KILL
The messenger acts as a cache for the name of the class we just replaced, and also sends that name to the global class list EXA and gets the correct time slot back. It sends the cached class name and the received time slot to the writer, then gets the name of the class that was replaced out to start the process over again.

Note that we also send out the time of the class we just replaced. This is used to avoid replacing the wrong class when searching the class list.

When the class we replaced is ENGLISH (we hold on to the file with ENGLISH so we can check this), we know we're done. In this case, we KILL the writer EXA, tell the class list EXA to quit, and finish up.

code:
;GLOBAL CLASS LIST SEARCH EXA
LINK 800
GRAB 200

	;TAKES TWO VALUES: ONE TO FIND, AND A  TIME TO AVOID
	;THEN FINDS A CLASS AND A TIME AND SENDS THEM OUT

MARK FINDLOOP
COPY M T
FJMP DONE
COPY T X
SEEK -9999
MARK FINDINFILE
SEEK 1
TEST X = F
FJMP FINDINFILE

;SECOND TEST - IS THIS 
;AT OUR 'AVOID' TIME?
SEEK -2
TEST F = M
FJMP SEND
SEEK 1
MARK FINDINFILE2
SEEK 1
TEST X = F
FJMP FINDINFILE2

SEEK -1
MARK SEND
SEEK -1
COPY F M 	;TIME
JUMP FINDLOOP
MARK DONE
This EXA is a very simple search loop. It starts at the front of the class list and searches for the class name sent in over M. Once it finds it, it reads a second value over M. This is the time we want to avoid. It checks that against the time of the class we've found in the file. If it's a match, we search again for the other instance of that class. Either way, once we find the correct version of the class, we return its time over M.

If we receive a 0, we stop. That's it.


804/83/5. The trick to getting a low activity score is sticking to the use of M as much as possible. I could probably get even lower if I were to REPL the writer EXA off from the class search EXA.

Fun fact: I also tried making a search table out of the class file (in the form Class, Time1, Time2), and then searching through it quickly for both times when needed. But this strategy takes 3-4 times as long!

Carbon dioxide
Oct 9, 2012

Part 50 - Let's go RAIDing


=== Trash World Inbox ===

Last time, hacking the school, I got 710, 55 and 8 as best scores.

We got one improvement from Quackles this week.

Quackles posted:

After the whole craziness that was last week's, this week is going to be straightforward. I don't have as much of an optimization on cycles as CO2, but I've been able to improve on activity a bit.
code:
;CLASS SCHEDULE WRITER EXA - STARTS GLOBAL M

GRAB 300
SEEK 1
COPY F X 	;'AP ENGLISH'
LINK 800
LINK 800
LINK 802
COPY X M      ;SEND 'AP ENGLISH'
COPY 0 M 	;NO AVOID TIME
DROP
GRAB 235
REPL MESSENGERBOT
MODE 	;LOCAL
MARK REPLACELOOP
SEEK -9999
SEEK 2
COPY M X   ;GET TARGET TIME

	;REPLACE LOOP WAS UNROLLED
TEST X = F
TJMP REPLACE
SEEK 1
TEST X = F
TJMP REPLACE
SEEK 1
TEST X = F
TJMP REPLACE
SEEK 1
TEST X = F
TJMP REPLACE
SEEK 3 	;SKIP LUNCH
TEST X = F
TJMP REPLACE
SEEK 1
TEST X = F
TJMP REPLACE
SEEK 2 	;NO NEED TO TEST IF LAST ITEM IN FILE

MARK REPLACE
COPY F X     ;GET OLD CLASS
SEEK -1
COPY M F    ;REPLACE CLASS NAME
COPY X M    ;SEND OLD CLASS
JUMP REPLACELOOP
This code runs in two EXAs, one of which splits during runtime. This is the first one. It travels to Hunter's class schedule file, gloms onto it, then sends the keyword 'AP ENGLISH' to the other EXA (which will search the global class list to find its time). Then it splits, spinning a copy of itself to MESSENGERBOT (see below).

Once MESSENGERBOT is started, the split (messenger) EXA will handle all communications with the global class list. This EXA just gets the time slot to write to from the messenger, finds that time slot in Hunter's schedule, overwrites the new class name, and sends the old class name.

It's worth noting that the loop where we search Hunter's schedule has been unrolled, allowing us certain optimizations. We can skip over the 'lunch' row entirely, and if we reach the end row, we don't need to test if it'll be the one we're looking for - it'll have to be.
code:
MARK MESSENGERBOT
GRAB 300
MARK MSGRLOOP
COPY M T   ;GET TIME SLOT TO REPLACE
MODE ;LOCAL
COPY T M   ;TELL WRITER TIME SLOT
COPY X M   ;TELL WRITER NEW CLASS NAME
COPY M X   ;GET OLD CLASS NAME
MODE ;G  ;CLASS
COPY X M   ;SEND OLD CLASS NAME
COPY T M   ;SEND OLD TIME SLOT AS AVOID
SEEK -1
TEST X = F ;ENGLISH 
FJMP MSGRLOOP

VOID M
COPY 0 M
WIPE ;HALT
KILL
The messenger acts as a cache for the name of the class we just replaced, and also sends that name to the global class list EXA and gets the correct time slot back. It sends the cached class name and the received time slot to the writer, then gets the name of the class that was replaced out to start the process over again.

Note that we also send out the time of the class we just replaced. This is used to avoid replacing the wrong class when searching the class list.

When the class we replaced is ENGLISH (we hold on to the file with ENGLISH so we can check this), we know we're done. In this case, we KILL the writer EXA, tell the class list EXA to quit, and finish up.
code:
;GLOBAL CLASS LIST SEARCH EXA
LINK 800
GRAB 200

	;TAKES TWO VALUES: ONE TO FIND, AND A  TIME TO AVOID
	;THEN FINDS A CLASS AND A TIME AND SENDS THEM OUT

MARK FINDLOOP
COPY M T
FJMP DONE
COPY T X
SEEK -9999
MARK FINDINFILE
SEEK 1
TEST X = F
FJMP FINDINFILE

;SECOND TEST - IS THIS 
;AT OUR 'AVOID' TIME?
SEEK -2
TEST F = M
FJMP SEND
SEEK 1
MARK FINDINFILE2
SEEK 1
TEST X = F
FJMP FINDINFILE2

SEEK -1
MARK SEND
SEEK -1
COPY F M 	;TIME
JUMP FINDLOOP
MARK DONE
This EXA is a very simple search loop. It starts at the front of the class list and searches for the class name sent in over M. Once it finds it, it reads a second value over M. This is the time we want to avoid. It checks that against the time of the class we've found in the file. If it's a match, we search again for the other instance of that class. Either way, once we find the correct version of the class, we return its time over M.

If we receive a 0, we stop. That's it.


804/83/5. The trick to getting a low activity score is sticking to the use of M as much as possible. I could probably get even lower if I were to REPL the writer EXA off from the class search EXA.

Fun fact: I also tried making a search table out of the class file (in the form Class, Time1, Time2), and then searching through it quickly for both times when needed. But this strategy takes 3-4 times as long!
As always, Quackles' extensive documentation speaks for itself. I like that the unroll allows you to skip lunch (although don't do that IRL too often).

It is indeed easy to change your code to get the activity all the way down to 3. As you say, put all the code in one big EXA, then REPL XB off after the initial LINK 800. Since the original XA is holding file 300 it can go on to the grade-11 host. The other activity point comes from that KILL which can be removed if the messenger, when done, also sends a 0 in LOCAL mode, and the writer checks for that the same way the reader did (temporarily store it in T, try an FJMP, then copying it to X).


=== Personal Storage Array ===




OST: Code and Registers

Alright, so x10x10x stored their anime on a drive array with three disks where all data is copied across all the disks for redundancy. However, this redundant array of independent disks is completely failing and it's up to us to retrieve their anime. Let's look at the assignment first.

- The data on this drive array is duplicated across three drives for redundancy, with a file name index stored in the controller (file 200). Unfortunately, the drive array is failing.
- For each file stored in the drive array, create a file in your host that contains the file name and data. Some values are corrupted and will read as a keyword (FAIL) instead of a number. You will need to read these values from a different drive that is not corrupted.
- Note that some links may be unreliable as a result of the drive array's impending failure.


That's not good. That drive is completely busted and I'll be lucky if I can get anything off of it.

The third point means that links 801, 802 and 803 will sometimes just disappear and then return a couple cycles later. If that happens during M transmission, well, the EXA will just wait. But if that happens when the EXA tries to LINK there, it will run into a wall and die.

Because everything is so unreliable, I'm going to handle the individual drives one by one. It'll be slower than parallelization but I have no idea how to make any sense of concurrent global M communication if links just randomly drop.


I have to admit, this puzzle gave me more trouble than I expected. It's mostly that whenever I thought I had all parts working together, one or two tests would screw up everything because their links dropped in an unexpected order. I ended up rewriting major chunks of my code several times before I got it working.

I think showing my thought process step by step as I'd do normally would be a bit confusing. So, instead I'll walk you through my actual working solution.

I'll start with XA which is rather simple but takes some work off of the main EXAs.



It starts with simply copying all the file IDs and file names from the index file. Once that's done, it will switch into local mode and let a visiting EXA know the next drive to LINK to. After each drive link ID it will also send a magic number, the purpose of which will become clear later.

Then to XB. It handles the bulk of the logic and does so by making many REPLs.
code:
;XB
COPY 7 T

MARK NEXTFILE
SUBI T 1 T
FJMP KICKSTART

MAKE
COPY 0 F
COPY M F
COPY M F
REPL NEXTFILE

SEEK -2
MODE; LOCAL
COPY M X
It starts by making a file to write the results to. It writes a 0 at the start (placeholder for later), then the file id and then the file name. The file name needs to be at the start of the result files when we're done. After SEEKing back to the file id, it jumps into local mode and waits for a message on M.

But it also REPLs itself and the REPL will run the same code for the next file. This repeats for all 6 files, after which the final REPL jumps to KICKSTART.

When this code is done, there are 7 EXAs in the home host. 6 of them are holding a (still mostly empty) result file, the 7th is the one that will kickstart the next step.

Let's look at the kickstarter.
code:
MARK KICKSTART

LINK 800
MODE; LOCAL
COPY M X
MODE ;GLOBAL

MARK TRYDRIVE
REPL DRIVE
NOOP
TEST MRD
FJMP TRYDRIVE
VOID M
MODE ; LOCAL
COPY M X

LINK -1

COPY X M

;HALT
;------

MARK DRIVE
LINK X
COPY 0 M
GRAB M
KILL
The first thing the kickstarter does is link to the drive controller and ask XA for the host to link to. Then it actually needs to get there. This is difficult because there's no way to know if the link is open or not. I solved this by REPLing a drive EXA in a loop. If the EXA makes it, it immediately sends a message on M. TRYDRIVE tries to check if a message is being sent with the TEST MRD. If so, it continues, if not it makes another DRIVE EXA until it works.

It is possible that the link dies in the one cycle between the DRIVE EXAs LINK command and the M message. In that case, the EXA will have successfully reached it, but TRYDRIVE makes another instance regardless. This could even happen multiple times.

Now, that other instance can't actually link to the drive because with the one EXA in there, it's full. It'll either try repeatedly until it bonks into a missing link and dies, or it'll actually make it after the actual DRIVE EXA makes some space by GRABbing a file. That's why that EXA needs a KILL after the GRAB.

This bit of code actually gave me the most trouble. To save on activity, I tried to have XA handle all this, but then there's messages going from the drive to XA and from XA to the XBs... and it all seems to work until some link disconnects at the worst time and everything desyncs. Making the kickstarter personally responsible was the only solution that worked for me.

Anyway, we now got a stable EXA in the drive, and the kickstarter VOIDs its M. It then jumps back to local mode, copies the magic number from XA, goes back to the home host, and copies it to one of the six waiting EXAs (randomly chosen). Then the kickstarter's work is done and it stops.


Before we go back to the six EXAs at home, let's see the rest of DRIVE's logic.
code:
MARK DRIVE
LINK X
COPY 0 M
GRAB M
KILL

MARK COPYFILE
COPY F M
TEST EOF
FJMP COPYFILE

COPY 0 M
DROP
GRAB M
JUMP COPYFILE
It gets sent instructions on M for what file to grab, then copies all the data from that file on M, sending a 0 when it's done, and then waits for another M message to grab and copy another file. To kill this EXA you can just send it the id of a non-existent file.

As a note, I tried to see if swapping the COPY 0 M and DROP made the code any faster, in case that M message was slow. It didn't - the solution was one cycle slower overall. But more importantly, doing so increased the activity. It's something I noticed a few times in this task. Even the smallest timing difference will change where the EXAs are in the code when the links are open, and getting to the drives will get harder or easier.


The 6 EXAs in the home host all have a file (with the cursor sitting on the file ID) and are waiting for an M message. The kickstarter will send one, the magic value 1 from XA.
code:
COPY M X
MODE

COPY F M
SEEK 1

COPY M T

MARK NEXTCOPY
COPY T F
COPY M T
TJMP NEXTCOPY

TEST X = 6
TJMP NEXTROUND
MODE
ADDI 1 X M
HALT
A randomly chosen EXA will accept this M, and then copy the file ID on global M. Only one EXA is listening - the one in the DRIVE. It will grab this file and start copying it. T is used as an intermediate so this EXA knows when the DRIVE EXA sends the terminating 0. Once that's the done, it will check whether the magic number is 6, and if not, increment it by 1 and send it on to the next EXA, and stop itself.

This way, one by one, all of the 6 EXAs will copy the data of their file from the first drive, and then just pass the baton to the next one. The random order doesn't matter because the EXAs run one by one and each knows which file ID it needs.

None of this code needs retries because if the link is down, the M calls will just wait.

The final EXA won't die, instead it will jump to NEXTROUND.



At this point, there are 6 files with partially corrupt data in the home host, one being held by the sole surviving EXA. The DRIVE EXA is waiting for a next file to pick up, and XA wants to send the next drive's link ID on local M.

code:
MARK NEXTROUND
COPY 0 M
REPL KICKSTART
DROP

COPY 400 X

MARK GRABNEXT
GRAB X
ADDI X 1 X
REPL GRABNEXT

MODE
COPY M F
NEXTROUND starts with sending a 0 to the DRIVE EXA so it dies. It then starts a second kickstarter. Again, that one will go to the drive controller, this time make a DRIVE EXA in the second drive, and come back to communicate the second magic number from XA, a 0.

The NEXTROUND EXA will use a similar REPL strategy as before, to make 6 EXAs each holding a file. This time it just uses a counter to go through all homemade file IDs (starting at 400), and the final REPL will die as it tries to grab a file that doesn't exist.

Again, all of them will wait for a message from the kickstarter, and it doesn't matter who gets it first. Importantly, this time the value isn't stored to X but in the placeholder at the start of the result file.

code:
MODE 

COPY F M
SEEK 1

MARK FIXNEXT
COPY M X
TEST X > -9999
FJMP SKIP
COPY X F
TEST EOF
FJMP FIXNEXT
JUMP DONEFIX

MARK SKIP
SEEK 1
TEST EOF
FJMP FIXNEXT
Next, the EXA jumps back into global mode, copies its file ID to the new DRIVE EXA and enters this FIXNEXT loop. How this works is, it copies the values from the DRIVE to X and checks if it's a number (valid). If so, it writes the value to F. If not, it skips writing this value. So, this code might write the same number twice from different files, but it will never overwrite a number with a FAIL. After this round, more values in the result file will be valid.

I did have to use X > -9999 instead of X < 9999 here because it turns out 9999 is a valid number in one of the tests.

DONEFIX is directly after this code, so no matter which branch hits the EOF, we end up here:
code:
MARK DONEFIX
VOID M
SEEK -9999
ADDI F 1 X
TEST X = 6
TJMP NEXTROUND
TEST X = 12
TJMP FINISH
MODE
COPY X M
TEST X > 6
DIVI 1 T T
JUMP CLEANUP
This time, the EXA already knows it's at the EOF. The DRIVE EXA sends a useless zero to report the same so we can just VOID that. The magic value is read from the start of the file (it was written there because the FIXNEXT loop required X as an intermediate), one is added to it. If the value is not 6 (or 12, I'll get to that), there's more EXAs waiting. The value is sent on local M, and if the value is under 6, this EXA dies by divide by zero.

If the value equals 6, the code jumps into NEXTROUND once more. The second DRIVE EXA gets killed, a third gets created, and the kickstarter comes back with a magic value of 6. A new set of 6 EXAs will be spawned, each one holding a partially fixed file, and each one will run another FIXNEXT loop, using the correct values from the third drive.

But, with the magic value starting at 6, this DONEFIX code works slightly differently. After incrementing it the first time, this value will not be 6 so it will never jump into NEXTROUND again. If it's 12 it will jump into FINISH. Otherwise, it'll hand the baton to the next EXA, and jump into CLEANUP now since X is always larger than 6.

Almost there.

code:
MARK FINISH
COPY 0 M
MARK CLEANUP
SEEK -1
VOID F
VOID F
FINISH just tells the 3rd DRIVE EXA to die. Then, all of the EXAs will delete the placeholder/magic value from the start of their file, as well as the file id. Leaving only the file name and the contents, which is the requirement.

XA already died since it ran out of lines of code to run.

Quite convoluted, but it works. It might not be the best idea to let all 6 file writing EXAs die each round and recreate them but this was the best way I found to keep control.

This runs at 5293/111/14.



Apparently my cycle count is the kind of solution many people found.

Top percentiles are 1012, 61 and 4. I bet the faster speed is gotten through some kind of parallelization but I have no clue how to set that up. The links disconnecting keeps messing with every idea I might otherwise have.

By the way, the activity is 14 because of XA going to the controller (1), the kickstarter going to the controller and back thrice (6), every DRIVE EXA that actually makes it into a drive (3), and a total of two duplicate DRIVE EXAs that LINK into the drive and need to be KILLed (4).

If anyone has any better scores let me know.

Next time, we help deadlock book a trip.

Quackles
Aug 11, 2018

Pixels of Light.


Here's the best solution I have.

code:
	;SETUP
LINK 800
GRAB 200
MARK NEXTFILE
COPY F X
REPL FILEBOT
COPY F M
VOID M 		;NOW WAIT FOR ACK
TEST EOF
FJMP NEXTFILE

	;HALTS TRYING TO HOLD TWO FILES
While the code is in one EXA, the EXA becomes three during running the code. This first EXA, the file manager, simply grabs the list of files and REPLs as needed to start recovering the next file in the set, passing in the name and address of the file.

code:
MARK FILEBOT
	;MAKES FILE, SENDS PROBES OUT TO COLLECT DATA
MAKE
COPY X F
COPY M F
MODE

MARK PROBE801	;GO TO DRIVE 1, COPY EVERYTHING THERE
COPY 801 T
REPL EXPLORER1
NOOP
NOOP
TEST MRD
FJMP PROBE801
VOID M

MARK COPY801
COPY M T
FJMP START802
COPY T F
@REP 5
COPY M F
@END
JUMP COPY801
The second EXA sits in the drive controller, holding the file that will be the 'clean' copy of the episode (we also keep the ID of the file to t. It sends out an 'explorer' EXA (using the routine EXPLORER1) to try and gain entry to the drive. If the link attempt fails (killing the explorer EXA), it will retry as often as needed.

Once the drive controller EXA is assured of the connection, it copies the contents of the file from the first drive, FAILs and all, to its own file. The value 0 never appears in the video files, so the drive controller EXA can interpret a 0 as a signal to cut the connection. However, we can be clever about this - every file's length is a multiple of 6 values, so we only need to check for 0 on the first value received out of every six! This speeds up the copying procedure.

(I'll detail the EXPLORER1 routine later, but it basically copies over the entire file in a speed-optimized way, using paralellism with REPL.)

code:
MARK START802	;GO TO DRIVE 2, BE JUDICIOUS
SEEK -9999
COPY F X
SEEK 1

MARK PROBE802
COPY 802 T
REPL EXPLORER2
NOOP
NOOP
TEST MRD
FJMP PROBE802
VOID M

COPY 0 X
MARK COPY802
TEST F > -9999
FJMP WRITE802
ADDI X 1 X
TEST EOF
FJMP COPY802
JUMP START803

MARK WRITE802
COPY X M
SEEK -1
COPY M F
COPY 0 X
TEST EOF
FJMP COPY802
The drive controller EXA now has to interpolate the values from the other two drives. However, for efficiency, we don't copy the entire file again. Instead, the drive controller EXA sends over an EXA using the EXPLORER2 routine.

The EXPLORER2 EXA will wait for instructions, then move forward through the file, copying over the value that's in a specific location.

To do its part, the drive controller EXA scans the existing 'clean' copy of the file for FAIL values (TEST F > -9999). It keeps track of the number of value in the file since the last FAIL value in X.

Once the drive controller finds a FAIL, it sends the value of X to the EXPLORER2 EXA. That EXA will move forward that many places in the file and report what it reads over global M. Then, the drive controller can patch the FAIL in its copy of the file with the received value.

code:
MARK START803	;ALSO BE JUDICIOUS WITH DRIVE 3
COPY 9999 M	;KILL 802 EXA
SEEK -9999
COPY F X
SEEK 1

MARK PROBE803
COPY 803 T
REPL EXPLORER2
NOOP
NOOP
TEST MRD
FJMP PROBE803
VOID M

COPY 0 X
MARK COPY803
TEST F > -9999
FJMP WRITE803
ADDI X 1 X
TEST EOF
FJMP COPY803
JUMP FINISH

MARK WRITE803
COPY X M
SEEK -1
COPY 0 X
COPY M F
TEST EOF
FJMP COPY803
After sending a 9999 over M to make the explorer EXA in the second drive halt (it will try to read past the end of the file), we repeat this process with the third drive. The third drive's explorer EXA also uses the EXPLORER2 read routine (but takes less time overall, since there are (hopefully) fewer FAIL values left in the drive controller's file).

code:
MARK FINISH
COPY 9999 M
MODE 
COPY 1 M 	;NOTIFY CONTROLLER FILE IS DONE
LINK -1
SEEK -9999
VOID F

	;HALTS TRYING TO LINK TO AN INVALID ADDRESS
With the completed file in our possession, the drive controller EXA halts the third explorer, notifies the file manager EXA that the file is fully recovered, clears the file address from where we were storing it at the start of the file, and delivers the file to our host.

code:
MARK EXPLORER1
LINK T
GRAB X
COPY 0 M	;WE MADE IT ACROSS!
MARK EXPLCOPY1
@REP 3
COPY F X
COPY F T
REPL EXPLSEND1	;SENDS BOTH VALUES
@END
TEST EOF
FJMP EXPLCOPY1
REPL NULL
COPY 0 M

	;HALTS TRYING TO LINK TO AN INVALID ADDRESS

MARK EXPLORER2
LINK T
GRAB X
COPY 0 M	;WE MADE IT ACROSS!
MARK EXPLCOPY2
SEEK M		;WHERE TO READ NEXT?
COPY F M
JUMP EXPLCOPY2

	;HALTS TRYING TO READ FROM EOF

MARK EXPLSEND1 	;SEND VALUES AND QUIT
COPY X M
COPY T M

MARK NULL
	;DUMMY REPL - USED TO WAIT FOR OTHER REPL'D EXAS TO CLEAR
This is the routines used by the explorer EXAs. The first routine, EXPLORER1 (and EXPLSEND1) copies the entire data of the file over M. However, sending a value over M has a 1-cycle startup delay on the part of the sender (but not the receiver). So, instead, the explorer EXA reads two values into X and T, then REPLs to EXPLSEND1.

While it reads the next two values, EXPLSEND1 sends the first two over M, then quits. There is only enough space for one REPLd EXA in eacha drive, so the next batch of values will wait while the first batch of values is sent.

Once we reach the end of the file, we use REPL NULL to wait for the last file sending EXA to clear. There are no instructions after MARK NULL, so the dummy EXA does nothing, while the explorer sends an 0, indicating end-of-file.


The second explorer routine, EXPLORER2, is much simpler. It reads a value over M telling it how many places forward to move in the file. It then sends back the value it finds there. This loops forever until it runs off the end of the file, which the drive controller EXA does deliberately by sending it a 9999 when it's time to finish with that drive.



4886/124/35. I do think it's possible to get a better result, but it'd involve a lot of paralellism, using REPL and local M to pipeline steps of the data processing.
You could probably find some low-hanging fruit with an approach that reads the entire file from each drive. The first read simply writes it to the file directly. The second read could read in and REPL off to test if a value was FAIL, and then write to the file... anyway, I'm happy with what I've achieved.

Carbon dioxide
Oct 9, 2012

Part 51 - CrystalAir International


=== Trash World Inbox ===

Quackles posted:

Here's the best solution I have.
code:
	;SETUP
LINK 800
GRAB 200
MARK NEXTFILE
COPY F X
REPL FILEBOT
COPY F M
VOID M 		;NOW WAIT FOR ACK
TEST EOF
FJMP NEXTFILE

	;HALTS TRYING TO HOLD TWO FILES
While the code is in one EXA, the EXA becomes three during running the code. This first EXA, the file manager, simply grabs the list of files and REPLs as needed to start recovering the next file in the set, passing in the name and address of the file.
code:
MARK FILEBOT
	;MAKES FILE, SENDS PROBES OUT TO COLLECT DATA
MAKE
COPY X F
COPY M F
MODE

MARK PROBE801	;GO TO DRIVE 1, COPY EVERYTHING THERE
COPY 801 T
REPL EXPLORER1
NOOP
NOOP
TEST MRD
FJMP PROBE801
VOID M

MARK COPY801
COPY M T
FJMP START802
COPY T F
@REP 5
COPY M F
@END
JUMP COPY801
The second EXA sits in the drive controller, holding the file that will be the 'clean' copy of the episode (we also keep the ID of the file to t. It sends out an 'explorer' EXA (using the routine EXPLORER1) to try and gain entry to the drive. If the link attempt fails (killing the explorer EXA), it will retry as often as needed.

Once the drive controller EXA is assured of the connection, it copies the contents of the file from the first drive, FAILs and all, to its own file. The value 0 never appears in the video files, so the drive controller EXA can interpret a 0 as a signal to cut the connection. However, we can be clever about this - every file's length is a multiple of 6 values, so we only need to check for 0 on the first value received out of every six! This speeds up the copying procedure.

(I'll detail the EXPLORER1 routine later, but it basically copies over the entire file in a speed-optimized way, using paralellism with REPL.)
code:
MARK START802	;GO TO DRIVE 2, BE JUDICIOUS
SEEK -9999
COPY F X
SEEK 1

MARK PROBE802
COPY 802 T
REPL EXPLORER2
NOOP
NOOP
TEST MRD
FJMP PROBE802
VOID M

COPY 0 X
MARK COPY802
TEST F > -9999
FJMP WRITE802
ADDI X 1 X
TEST EOF
FJMP COPY802
JUMP START803

MARK WRITE802
COPY X M
SEEK -1
COPY M F
COPY 0 X
TEST EOF
FJMP COPY802
The drive controller EXA now has to interpolate the values from the other two drives. However, for efficiency, we don't copy the entire file again. Instead, the drive controller EXA sends over an EXA using the EXPLORER2 routine.

The EXPLORER2 EXA will wait for instructions, then move forward through the file, copying over the value that's in a specific location.

To do its part, the drive controller EXA scans the existing 'clean' copy of the file for FAIL values (TEST F > -9999). It keeps track of the number of value in the file since the last FAIL value in X.

Once the drive controller finds a FAIL, it sends the value of X to the EXPLORER2 EXA. That EXA will move forward that many places in the file and report what it reads over global M. Then, the drive controller can patch the FAIL in its copy of the file with the received value.
code:
MARK START803	;ALSO BE JUDICIOUS WITH DRIVE 3
COPY 9999 M	;KILL 802 EXA
SEEK -9999
COPY F X
SEEK 1

MARK PROBE803
COPY 803 T
REPL EXPLORER2
NOOP
NOOP
TEST MRD
FJMP PROBE803
VOID M

COPY 0 X
MARK COPY803
TEST F > -9999
FJMP WRITE803
ADDI X 1 X
TEST EOF
FJMP COPY803
JUMP FINISH

MARK WRITE803
COPY X M
SEEK -1
COPY 0 X
COPY M F
TEST EOF
FJMP COPY803
After sending a 9999 over M to make the explorer EXA in the second drive halt (it will try to read past the end of the file), we repeat this process with the third drive. The third drive's explorer EXA also uses the EXPLORER2 read routine (but takes less time overall, since there are (hopefully) fewer FAIL values left in the drive controller's file).
code:
MARK FINISH
COPY 9999 M
MODE 
COPY 1 M 	;NOTIFY CONTROLLER FILE IS DONE
LINK -1
SEEK -9999
VOID F

	;HALTS TRYING TO LINK TO AN INVALID ADDRESS
With the completed file in our possession, the drive controller EXA halts the third explorer, notifies the file manager EXA that the file is fully recovered, clears the file address from where we were storing it at the start of the file, and delivers the file to our host.
code:
MARK EXPLORER1
LINK T
GRAB X
COPY 0 M	;WE MADE IT ACROSS!
MARK EXPLCOPY1
@REP 3
COPY F X
COPY F T
REPL EXPLSEND1	;SENDS BOTH VALUES
@END
TEST EOF
FJMP EXPLCOPY1
REPL NULL
COPY 0 M

	;HALTS TRYING TO LINK TO AN INVALID ADDRESS

MARK EXPLORER2
LINK T
GRAB X
COPY 0 M	;WE MADE IT ACROSS!
MARK EXPLCOPY2
SEEK M		;WHERE TO READ NEXT?
COPY F M
JUMP EXPLCOPY2

	;HALTS TRYING TO READ FROM EOF

MARK EXPLSEND1 	;SEND VALUES AND QUIT
COPY X M
COPY T M

MARK NULL
	;DUMMY REPL - USED TO WAIT FOR OTHER REPL'D EXAS TO CLEAR
This is the routines used by the explorer EXAs. The first routine, EXPLORER1 (and EXPLSEND1) copies the entire data of the file over M. However, sending a value over M has a 1-cycle startup delay on the part of the sender (but not the receiver). So, instead, the explorer EXA reads two values into X and T, then REPLs to EXPLSEND1.

While it reads the next two values, EXPLSEND1 sends the first two over M, then quits. There is only enough space for one REPLd EXA in eacha drive, so the next batch of values will wait while the first batch of values is sent.

Once we reach the end of the file, we use REPL NULL to wait for the last file sending EXA to clear. There are no instructions after MARK NULL, so the dummy EXA does nothing, while the explorer sends an 0, indicating end-of-file.


The second explorer routine, EXPLORER2, is much simpler. It reads a value over M telling it how many places forward to move in the file. It then sends back the value it finds there. This loops forever until it runs off the end of the file, which the drive controller EXA does deliberately by sending it a 9999 when it's time to finish with that drive.

4886/124/35. I do think it's possible to get a better result, but it'd involve a lot of paralellism, using REPL and local M to pipeline steps of the data processing.
You could probably find some low-hanging fruit with an approach that reads the entire file from each drive. The first read simply writes it to the file directly. The second read could read in and REPL off to test if a value was FAIL, and then write to the file... anyway, I'm happy with what I've achieved.
A nice speed improvement. Note the EXA has to start in LOCAL mode.

Quackles explains it well. The main speed-ups are in only sending values that failed before using the SEEK M trick, and also using a REPL to actually send data over M for the first file.

You need to send one EXA per drive per file across the broken links and I didn't want to deal with that which is why I did one drive at a time, but handling the files serially definitely makes the code easier to optimize.


=== CrystalAir International- Ticketing System ===



And just like that, we're at the last of our currently unlocked tasks.


OST: Code and Registers

The assignment:

- Each host in the network corresponds to a country and contains a list of tickets for flights (file 200) departing airports in that country, bound for airports either also in that country or in a connected country. Each ticket has the following values: ticket ID, departure airport, departure time, arrival airport, and arrival time.
- Book the sequence of flights in deadlock's itinerary (file 300) by deleting each ticket from its file in the network and adding the ticket ID to file 301, choosing the first valid flight that departs after the preceding flight arrives.
- Note that deadlock's itinerary will always begin in the USA, and that flight times may be compared with TEST.


Looks like the USA is always the host we're directly connecting to, and that the network layout is the same for all the tests. I also think the tickets in each country's file are ordered by departure time.

In this first test, our itinerary is DFW -> JFK -> CDG -> IBZ.

Also there's a little thing that confused me in this test case. It says "Remove the tickets from the files (0/2)" at the goals in the bottom left. There's 3 tickets to find, it actually says 2 because there's only two files to remove tickets from, since DFW and JFK are both in the US.

Well, to get started I'll find the first flight from DFW to JFK in the USA's file.

I wrote two EXAs for that:
code:
;XA

MARK START
GRAB 300

COPY F M

COPY F X

MARK SENDAGAIN
COPY X M
COPY M T
FJMP SENDAGAIN

SEEK -9999
VOID F
DROP
GRAB 301
COPY T F
DROP
JUMP START
XA stays at home and handles both files 300 and 301. It sends the departure airport over M, then it sends the arrival departure over M as long as it keeps getting zeroes back. Once it doesn't get a zero, it deletes the departure airport from file 300 (so the next pair of airports become departure and arrival), drops the file and writes the value it just got from M into file 301. Then it repeats. XB just has to make sure it sends the ticket number and this will work.

XB looks like this.
code:
;XB

LINK 800
GRAB 200
COPY M X
SEEK 1

MARK TRYNEXT
TEST X = F
TJMP POSSIBLYFOUND
SEEK 4
JUMP TRYNEXT

MARK POSSIBLYFOUND
SEEK 1
TEST M = F
TJMP FOUND
COPY 0 M
SEEK 2
JUMP TRYNEXT

MARK FOUND
COPY F X
SEEK -5
COPY F M
SEEK -1
@REP 5
VOID F
@END
.
It goes into the USA host, puts the departure airport in X, then starts looking for flights leaving from there. If it finds one, it requests the arrival airport over M and checks if that's a match too. If not, it sends a zero back and keeps looking. Otherwise, it SEEKs back to the ticket number, sends that on M, and removes all 5 values for the ticket from the file.

The next part is harder. The next airport can be in the same country or any adjacent country (I sure hope it can't be two countries over, that'd make the puzzle much harder). And we have to check the departure time as well. I wrote the last flight's arrival time to XB's X register for now but that won't fit.

Let's ignore the departure time for now and first build the search in adjacent countries.



After deleting the first ticket, XB now makes REPLs going in all four cardinal directions, and the original one stays where it is. They do a KILL, then GRAB the file. Then they all repeat the search code. This works efficiently because only one will have the file with the current departure airport. It's the only one that will ever ask for the arrival airport over M. All others will either reach EOF and die, or they'll stay busy until the next round, which is why I needed that KILL. It'll clear up leftovers from the previous round.

It's possible to shift things here a bit - I don't need the DROP, KILL and GRAB for the EXA that stays in the current country. But this kept the code simpler.

So, this code gets me the earliest known flight for each route. It still ignores departure times.

Also, the EXAs get stuck at the end looking for a final connection that doesn't exist. But let's fix the departure times issue first.

What I need to do is compare the arrival time of the last round with the departure time of this one. That's not going to fit in XB, so I'll have XA handle it as well.
code:
;XA

GRAB 300
SEEK 9999
COPY 0 F
SEEK -9999

MARK START

COPY F M
COPY F X
SEEK 9999

MARK SENDAGAIN
COPY X M
COPY M T
FJMP SENDAGAIN
SEEK -1
COPY F M
COPY M T
FJMP SENDAGAIN
SEEK -1
COPY T F

SEEK -9999
VOID F
DROP
GRAB 301
SEEK 9999
COPY M F
DROP
GRAB 300

JUMP START
XA now starts by writing a zero at the end of file 300, as a placeholder. If XA makes it past the first FJMP SENDAGAIN (after XB sends a success message), XA will send this value over M and wait for another message. If this is non-zero, the value is written over the placeholder. That value will actually be the new flight's departure time. So, every XB round starts with it getting the departure airport in X, then repeatedly receiving the arrival airport over M. When this combination is found, it asks for the departure time. If it doesn't match, it starts asking for the departure airport again.

XB now sends the ticket number as a third M message, which XA writes to file 301. I had to put in a SEEK 9999 in there because my code kept overwriting the first number. :sweatdrop:

XB needed significant changes to make this work.
code:
;XB

LINK 800
COPY M X

MARK SEARCH
KILL
GRAB 200
SEEK 1

MARK TRYNEXT
TEST X = F
TJMP POSSIBLYFOUND
SEEK 4
JUMP TRYNEXT

MARK POSSIBLYFOUND
SEEK 1
TEST M = F
TJMP FOUNDFLIGHT
COPY 0 M
SEEK 2
JUMP TRYNEXT

MARK FOUNDFLIGHT
COPY 1 M
SEEK -2
TEST F < M
FJMP FOUND
COPY 0 M
SEEK 3
JUMP TRYNEXT

MARK FOUND
SEEK 1
COPY F M
SEEK -5
COPY F M
SEEK -1
@REP 5
VOID F
@END
DROP

COPY M X

COPY 4 T
MARK SPREADOUT
REPL MOVE
SUBI T 1 T
TJMP SPREADOUT
JUMP SEARCH

MARK MOVE
ADDI 799 T T
LINK T
JUMP SEARCH
Basically, whenever a matching flight is found (FOUNDFLIGHT) it sends a 1 to XA as the first success message, then compares the departure time with the previous arrival time from XA. I needed to switch the logic here, because in the first round, XA will send a 0, and any test of 0 against a keyword (which the dates are) always return false. So, if the value from XA is zero, or if departure time in the file is NOT smaller than the previous arrival time, FJMP FOUND is executed. There's an edge case if the previous arrival time exactly matches this flight's departure time but I'm gonna assume the game doesn't care.

The SEEKs changed a bit to handle the extra jumping back and forth in the file.


This code writes the right result, but XA will tell XB to go find a flight from the final destination to... the arrival time, which makes no sense. XB can't find that and XA will wait for a response forever. Let's go fix that.

XA just needs some code at the bottom to check if only the final destination and the arrival time are left.
code:
SEEK 2
TEST EOF
SEEK -9999
FJMP START

COPY 0 M
While XB can test for this zero value and then just stop.
code:
COPY M X
TEST X = 0
TJMP END
Sadly it's not straightforward to use the divide by zero trick here, because dividing by a word also kills the EXA.




That was a quite nice puzzle, not nearly as hard to figure out as some of the other post-game ones. 345/83/25. Top percentiles are 136, 58 and 8.

For the speed, well, I do a lot of waiting on M. I'm sure that could be more efficient. Looking at the graphs, there's lots of people who somehow needed the full 150 lines of code to solve this. That high peak in the activity is probably what happens if you send an EXA to each country every round. The top percentile of 8 is interesting, though. You could send an EXA to each country once and have it start searching when getting a message on M, but there's 12 countries so that would still be an activity of 12. I think it involves something like only sending EXAs to neighbouring nodes when needed, but then actually keeping them there. So nodes that are completely in the wrong direction will never get visited and no link will be traversed twice.

Either that or there are actually some countries deadlock never wants to go to.


The next task is finally for Moss again. And as usual, Ember has some things to say.

Well, this is something.
I can see an open network that's... I can't even tell how big.
Imagine all the computers in the world were hooked up to each other, and you would start to get an idea of the size.
No way to know just how big until we explore, though.




You have no idea how much I missed this part. Here's the first vote!


A little bit later in the same conversation, Ember has another question.

Why, do you think it could be dangerous?



The second vote.

Carbon dioxide fucked around with this message at 19:55 on Dec 2, 2022

Quackles
Aug 11, 2018

Pixels of Light.


Uh oh.

Anaxite
Jan 16, 2009

What? What'd you say? Stop channeling? I didn't he-
Uh oh.
Dangerous for them, maybe.

GuavaMoment
Aug 13, 2006

YouTube dude

Anaxite posted:

Uh oh.
Dangerous for them, maybe.


These.

I barely remember this challenge. I think I must have banged it off in an hour with a terrible solution and never looked back.

Nth Doctor
Sep 7, 2010

Darkrai used Dream Eater!
It's super effective!


Uh oh.

We have no idea what's out there.

Carbon dioxide
Oct 9, 2012

Part 52 - š Ñ| ö/ ~ öB è[ å‡ ÑE È‚ t 7Ò


=== My Computer ===



After all of that, we have unlocked Moss's final assignment.

Wait, Morpork? That's... that's my computer name, like outside of the game.

Well, this is something.
I can see an open network that's... I can't even tell how big.
Imagine all the computers in the world were hooked up to each other, and you would start to get an idea of the size.
No way to know just how big until we explore, though.




Four votes for Uh oh.

Uh oh.

Uh oh?
Why, do you think it could be dangerous?




One vote for "We have no idea what's out there", two for "Dangerous for them".

Dangerous for them, maybe.

That's the spirit.



OST: Leave No Trace

The assignment:
- Packetize and transmit the target data (file 301) to the internet so that it is uploaded to a warez site (file 300).
- A packet is a file that consists of the source IP address (from the #ADDR register), the destination IP address (from the DNS cache, file 201), the checksum of the packet's data, and between 1 and 30 data values. The target data should be split into multiple packets so that no packet except for the last contains fewer than 30 data values.
- To calculate a packet's checksum, add the data values together considering each digit separately, wrapping back to 0 when a digit's sum reaches 10. Then flip the sign. For example, the checksum of 3097, 1047, and 2501 is -6535.


Wait. WAIT. That Desktop host at the bottom holds files that are on my actual desktop. Like, that firefox.desktop file I opened up? That's just a shortcut to open Firefox in my Linux distro. And those hosts connected to the Desktop are sub directories. Moss and Ember are in my computer!

Well, before I get started, I'll mess around with the hardware registers a bit. File 200 conveniently holds the link IDs to those hosts.



The clock holds the actual IRL time and date. At least Moss isn't allowed to reset it.

Let's mess with the #INVS register in the graphics host.


My eyes.





It turns out link 800 lets you go into the monitor, where you can change the contrast. At least everything resets once you stop the test simulation.

Let's get this show on the road.



Four files that actually matter. The reference to whereswarez.ru Ember prepared for us, file 301 with the actual data, then file 200 with the host addresses. It looks like the network host is 885 for the first five test cases so hopefully it stays that way and I can just hardcode that. Finally, there's file 201 with whereswarez.ru's IP address.

code:
GRAB 300
COPY F X
DROP
GRAB 301
LINK 800
LINK 885
REPL MAKER
HALT

MARK MAKER
GRAB 201

MARK FIND_DNS
TEST F = X
TJMP FOUNDIP
SEEK 1
JUMP FIND_DNS

MARK FOUNDIP
COPY F X
NOOP
First of all, this EXA loads the URL in X, and takes the data file to the network host. There, it creates a REPL which looks up whereswarez.ru's IP in the DNS cache file and stores that to X.

Next up, making packets of 30 data values. The checksum sounds complicated so I'll leave that out of scope for now. Let's go step by step.
code:
GRAB 300
COPY F X
DROP
GRAB 301
LINK 800
LINK 885
REPL MAKER

MARK NEXTROUND
MODE; LOCAL
COPY 5 X

MARK SEND
@REP 6
COPY F M
@END
TEST EOF
TJMP EOF
SUBI X 1 X
COPY X T
TJMP SEND
COPY 0 M
MODE; GLOBAL
COPY 1 M
JUMP NEXTROUND

MARK EOF
COPY 0 M
WIPE
MODE; GLOBAL
COPY 0 M
The purpose of this EXA is to copy data from the data file in batches of 30, sending a 0 after each batch. It also checks for EOF and also sends a 0 in that case. It uses LOCAL M for all that. After each round, it also sends a 1 on GLOBAL M if there is more data to come, 0 if there isn't. In the EOF case it also WIPEs its file before stopping entirely. The @REP6 works because the number of values in the data file is always a multiple of 6.

code:
MARK MAKER
GRAB 201

MARK FIND_DNS
TEST F = X
TJMP FOUNDIP
SEEK 1
JUMP FIND_DNS

MARK FOUNDIP
COPY F X

MARK WAITNEXT
REPL NEXT
COPY M T
TJMP WAITNEXT
HALT
The MAKER EXA always stays in GLOBAL mode. After getting the value from the DNS, it makes a NEXT REPL. Every time it gets 1 on GLOBAL M it makes another NEXT, otherwise it HALTs.

code:
MARK NEXT
MODE ; LOCAL
MAKE
COPY #ADDR F
COPY X F

COPY 0 F

MARK WRITENEXT6
COPY M T
FJMP DONECOPY
COPY T F
@REP 5
COPY M F
@END 
JUMP WRITENEXT6

MARK DONECOPY
LINK 800
The actual NEXT EXA makes the packet, starting with the local IP address, then the website's one, then a zero as placeholder for the checksum, and then it writes the copied data in batches of 6. At the start of each batch it checks for the 0, in which case it delivers the file to the internet.




Yes, that source IP address is my actual address, at least within my LAN.

As soon as an EXA makes it to the internet, it immediately drops its file and dies with an "unknown host type" error. I can't run code on the 'net. Other than that, the simulation just shows the files sitting there in that 'internet' host forever.


It looks like this code works except for the checksums.

To calculate checksums, I need to iterate over all values, do something like a SWIZ to get each digit and then add it to a running total. That requires 2 registers at the very least. The best way to do this would probably be to use M. Thing is, I'm already using both global and local M in the network host. So that's going to be confusing. I'll try to do it in place instead. I added some code to just below the MARK DONECOPY.
code:
SEEK -9999
SEEK 3

; 1
COPY 0 X

MARK DIGIT1
@REP 6
SWIZ F 1 T
ADDI T X X
@END

TEST EOF
FJMP DIGIT1

SEEK -9999
SEEK 2
SWIZ X 1 F
For the lowest significant digit, empty out X, then in batches of 6 get that digit with a SWIZ and add it to X. Finally, write the lowest digit to the placeholder position in F.

code:
; 10
COPY 0 X

MARK DIGIT10
@REP 6
SWIZ F 2 T
ADDI T X X
@END

TEST EOF
FJMP DIGIT10

SEEK -9999
SEEK 2
SWIZ X 10 X
ADDI X F X
SEEK -1
COPY X F
For the second digit do something similar, with a SWIZ for that digit. Note it's SWIZ F 2, NOT SWIZ F 20 so the digit goes into the ones place in the X result. This makes sure that the addition always works correctly. The SWIZ X 10 X moves the final result to the 10s position. The value from the 1's digits in F is added to it and the result is written back to F.

This same code is repeated twice more for the 100s digit and 1000s digit. The only difference is that the COPY X F at the bottom of the 1000s digit is replaced by SUBI 0 X F to flip the sign. The code duplication isn't very nice but I have no registers to spare to keep a counter in.

The file is brought to the internet by the LINK 800 I already had and that's it.


This code runs at 1453/150/11, but the max size is only 100. I got it to barely fit in 98 lines by rolling up all my @REPs.
code:
GRAB 300
COPY F X
DROP
GRAB 301
LINK 800
LINK 885
REPL MAKER

MARK NEXTROUND
MODE; LOCAL
COPY 30 X

MARK SEND
;@REP 6
COPY F M
;@END
TEST EOF
TJMP EOF
SUBI X 1 X
COPY X T
TJMP SEND
COPY 0 M
MODE; GLOBAL
COPY 1 M
JUMP NEXTROUND

MARK MAKER
GRAB 201

MARK FIND_DNS
TEST F = X
TJMP FOUNDIP
SEEK 1
JUMP FIND_DNS

MARK FOUNDIP
COPY F X

MARK WAITNEXT
REPL NEXT
COPY M T
TJMP WAITNEXT

MARK NEXT
MAKE
MODE ; LOCAL
COPY #ADDR F
COPY X F
COPY 0 F

COPY M T

MARK WRITENEXT6
COPY T F
COPY M T
TJMP WRITENEXT6

SEEK -9999
SEEK 3

; 1
COPY 0 X

MARK DIGIT1
SWIZ F 1 T
ADDI T X X

TEST EOF
FJMP DIGIT1

SEEK -9999
SEEK 2
SWIZ X 1 F

; 10
COPY 0 X

MARK DIGIT10
SWIZ F 2 T
ADDI T X X

TEST EOF
FJMP DIGIT10

SEEK -9999
SEEK 2
SWIZ X 10 X
ADDI X F X
SEEK -1
COPY X F

; 100
COPY 0 X

MARK DIGIT100
SWIZ F 3 T
ADDI T X X

TEST EOF
FJMP DIGIT100

SEEK -9999
SEEK 2
SWIZ X 100 X
ADDI X F X
SEEK -1
COPY X F

; 1000
COPY 0 X

MARK DIGIT1000
SWIZ F 4 T
ADDI T X X

TEST EOF
FJMP DIGIT1000

SEEK -9999
SEEK 2
SWIZ X 1000 X
ADDI X F X
SEEK -1
SUBI 0 X F

LINK 800


MARK EOF
COPY 0 M
WIPE
MODE; GLOBAL
COPY 0 M


My score is 2653/98/11. Top percentiles are at 640, 44 and 11. I know that especially the checksum code could be a whole lot more efficient, I just wrote myself into a bit of a corner by deciding to do everything in the network host (although that did give me top percentile activity score).

Finishing this task gives you the steam achievement š Ñ| ö/ ~ öB è[ å‡ ÑE È‚ t 7Ò for "Completing every task in the bonus campaign". That means I now have all achievements except for 100 wins in the solitaire game and getting crazy high scores in the tetris game. I'm content.


This is an interesting place. What are "warez"?



Since this is the last full update, I'll just give you the dialogue trees here.

I'm not sure.
Whatever they are, a lot of people want them.


---

What do you think they are?
Something a lot of people want, it looks like.


---

It means illegal downloads of software.
Oh, no wonder it's so popular.



And here the dialogue merges.

We'll be able to spread easily from here.
I still can't see the limits of this so-called Internet... but if they really hooked up all of their computers to each other the way it looks they have, I predict we'll have a very fun time.
Let's see how far we can go.





... and that's all, folks. Ember and Moss are out here on the real internet now. We unlocked everything we could.

I enjoyed how meta this last puzzle got, but ending the postgame with nothing more than a textbox feels a bit anticlimactic.




Thank you to everyone who submitted solutions, left any other sort of comments, or just lurked and read my LP. It was quite the trip and I wouldn't have had the determination to finish this without your support.

If anyone has any improvements for this last puzzle, or any others before it, this is the time to post them. I'll feature those in one final edition of Trash World Inbox next week, before closing the threads.

Carbon dioxide fucked around with this message at 17:45 on Apr 23, 2023

cardinale
Jul 11, 2016

It's really fun that the game used your own computer's details like that! Thank you for the LP.

Adbot
ADBOT LOVES YOU

Quackles
Aug 11, 2018

Pixels of Light.


:golfclap:

Well LPd, and thank you.

Something to think about: If Moss was inside a simulation this whole time, that makes them necessarily a computer program, too. There might be a hint in Ember saying Moss's mind was 'simple'.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply