Hacking games, banks, and yourself - Let's Play EXAPUNKS

An archival space for the threads that made it.
User avatar
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 wrote: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: Select all

	;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: Select all

	;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: Select all

	;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: Select all

	;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: Select all

	;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: Select all

	;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: Select all

	;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: Select all

	;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 uploaded Quackles' full solution here 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 ===

Image

Image I don't know why we need to hack a school to sign someone up for a class, but fine. Let's do this.

Image
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.

Image

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.

Image

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: Select all

;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: Select all

;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: Select all

;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: Select all

;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.

Image

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.

User avatar
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 wrote: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: Select all

;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: Select all

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: Select all

;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 ===

Image

Image
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.

Image

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: Select all

;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: Select all

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: Select all

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: Select all

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.

Image

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: Select all

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: Select all

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: Select all

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: Select all

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.

Image

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.

User avatar
Part 51 - CrystalAir International


=== Trash World Inbox ===
Quackles wrote:Here's the best solution I have.

Code: Select all

	;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: Select all

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: Select all

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: Select all

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: Select all

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: Select all

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 ===

Image

And just like that, we're at the last of our currently unlocked tasks.

Image
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: Select all

;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: Select all

;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.

Image

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: Select all

;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: Select all

;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: Select all

SEEK 2
TEST EOF
SEEK -9999
FJMP START

COPY 0 M
While XB can test for this zero value and then just stop.

Code: Select all

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.


Image

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.


Image The next task is finally for Moss again. And as usual, Ember has some things to say.

Image Well, this is something.
Image I can see an open network that's... I can't even tell how big.
Image Imagine all the computers in the world were hooked up to each other, and you would start to get an idea of the size.
Image No way to know just how big until we explore, though.


Image

Image You have no idea how much I missed this part. Here's the first vote!


Image A little bit later in the same conversation, Ember has another question.

Image Why, do you think it could be dangerous?

Image

Image The second vote.

User avatar
Part 52 - š Ñ| ö/ ~ öB è[ å‡ ÑE È‚ t 7Ò


=== My Computer ===

Image

Image After all of that, we have unlocked Moss's final assignment.

Image Wait, Morpork? That's... that's my computer name, like outside of the game.

Image Well, this is something.
Image I can see an open network that's... I can't even tell how big.
Image Imagine all the computers in the world were hooked up to each other, and you would start to get an idea of the size.
Image No way to know just how big until we explore, though.


Image

Image Four votes for Uh oh.

Uh oh.

Image Uh oh?
Image Why, do you think it could be dangerous?


Image

Image One vote for "We have no idea what's out there", two for "Dangerous for them".

Dangerous for them, maybe.

Image That's the spirit.


Image
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.


Image 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.

Image

Image 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.

Image
My eyes.


Image

Image
It turns out link 800 let's 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.

Image

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: Select all

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: Select all

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: Select all

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: Select all

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.


Image

Image 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: Select all

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: Select all

; 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: Select all

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
Image

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).

Image 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.


Image This is an interesting place. What are "warez"?

Image

Image Since this is the last full update, I'll just give you the dialogue trees here.

I'm not sure.
Image Whatever they are, a lot of people want them.


---

What do you think they are?
Image Something a lot of people want, it looks like.


---

It means illegal downloads of software.
Image Oh, no wonder it's so popular.



Image And here the dialogue merges.

Image We'll be able to spread easily from here.
Image 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.
Image Let's see how far we can go.





Image ... and that's all, folks. Ember and Moss are out here on the real internet now. We unlocked everything we could.

Image I enjoyed how meta this last puzzle got, but ending the postgame with nothing more than a textbox feels a bit anticlimactic.

Image


Image 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.

User avatar
Part 53 - Trash World Roundup


=== Trash World Inbox ===

I finished the last puzzle with a score of 2653/98/11. Lurker Above posted a nice improvement.
Lurker Above wrote: I greatly enjoyed reading this LP. (And, admittedly, cribbed from it for postgame puzzles 5-8, which were a bit much for me.) Since my own solution for the final puzzle is (technically) an improvement for once, I figured I'd go ahead and post it:

Code: Select all

;XA (LOCAL)
MARK WRITEBOT
GRAB 300
COPY F X
DROP
MAKE
COPY X F
COPY X F
COPY X F
MARK PACKING
COPY M X
TEST X > 0
FJMP SENDING
COPY X F
JUMP PACKING
MARK SENDING
ADDI X 1 T ;0 OR -9998
FILE X
DROP
REPL DELIVERBOT
FJMP WRITEBOT;-9998 != 0
;HALT

MARK DELIVERBOT
GRAB X
LINK 800
LINK 885
COPY F X
REPL DNSBOT
COPY M F
SEEK 1
;THE BIG CHECKSUM THING
COPY 0 X
MARK SWIZONE
SWIZ F 0001 T
ADDI X T X
TEST EOF
FJMP SWIZONE
SWIZ X 0001 X
SEEK -9999
COPY #ADDR F;TIMESAVE
SEEK 1
SUBI 0 X F
COPY 0 X
MARK SWIZTEN
SWIZ F 0002 T
ADDI X T X
TEST EOF
FJMP SWIZTEN
SWIZ X 0010 X
SEEK -9999
SEEK 2
SUBI F X X
SEEK -1
COPY X F
COPY 0 X
MARK SWIZHUND
SWIZ F 0003 T
ADDI X T X
TEST EOF
FJMP SWIZHUND
SWIZ X 0100 X
SEEK -9999
SEEK 2
SUBI F X X
SEEK -1
COPY X F
COPY 0 X
MARK SWIZTHOU
SWIZ F 0004 T
ADDI X T X
TEST EOF
FJMP SWIZTHOU
SWIZ X 1000 X
SEEK -9999
SEEK 2
SUBI F X X
SEEK -1
COPY X F
;END CHECKSUMMARY
LINK 800
;SEE YOU SPACE COWBOY

MARK DNSBOT
GRAB 201
MARK DNSLOOP
TEST F = X
FJMP DNSLOOP
COPY F M

Code: Select all

;XB (LOCAL)
GRAB 301
MARK READBOT
COPY 30 X
MARK READING
COPY F M
TEST EOF
TJMP FINALE
SUBI X 1 X
TEST X > 0
TJMP READING
COPY -1 M
JUMP READBOT
MARK FINALE
COPY -9999 M
There are basically four "programs" here: Writebot (XA), Readbot (XB), Deliverbot (XA:0), and DNSbot (XA:0:0).
Writebot reads the target domain name, then creates the first packet, filling all three header values with the domain name string. It then receives a chunk of the data from Readbot to append to the packet, ending with either -1 (end of chunk) or -9999 (end of file). When Writebot gets a negative number, it adds 1 to it and stores it in T, then stores the packet's file ID in X, drops it, and then REPLs a Deliverbot. It then either FJMPs back to the beginning, or if EOF was reached it falls through to an invalid command and crashes because FJMP conveniently doesn't consider -9998 to be "false".
When spawned, a Deliverbot immediately grabs its assigned packet, moves over to Network, reads the domain name from the packet into X, and then REPLs a DNSbot, which grabs the DNS file, finds the domain's IP address, and sends that back to the Deliverbot, which in turn stores it in its proper place in the packet header. The Deliverbot then does four loops to calculate each digit of the checksum, adding the local IP address to the packet after one post-loop SEEK -9999 for efficiency's sake. Finally, with the header complete, it immediately delivers the packet.

2566/96/27. It's by no means optimal, but it's what I came up with. Anyway, I would definitely be interested in seeing solutions for the other two top percentiles, especially a 44-line solution.
Very nice. I don't have anything to add to your explanation, other than that there's a quick improvement to prevent having the Deliverbot grab the file again. Instead of REPLing the deliverbot, REPL the writebot. That means the writebot has to start with a TJMP DNSBOT or something so it dies when there's -9998 in T, but you can get rid of the FILE X, DROP, MARK DELIVERBOT, and GRAB X lines, giving 2549/93/27.

Since the number of values is always a multiple of 6, you can then use the freed lines to unroll loops a little bit, e.g. do a @REP 2 around the SWIZ F mask T / ADDI X T X for 3 of the deliverbot's SWIZ loops, to drop the cycles score to 2459. I don't know if that's the best place to unroll, since I didn't try all possibilities.

To make it even faster it might also help to somehow cache the DNS data or the domain name in an additional EXA but finding available lines for that might be though. I didn't really attempt this, though.


There are some good guides for this game on the internet. I mostly kept away from them because I felt like copy-pasting from them wasn't in the spirit of this LP, but if you're interested in how to get the most optimal scores, you could check those out.


Image
OST: Exapunks


Image Well, this was truly my final update. I'll keep the threads open, it's just that any further suggestions won't make it into the LP Archive.

Locked