The Let's Play Archive

EXAPUNKS

by Carbon dioxide

Part 42: Pager Network

Part 42 - Pager Network

=== Trash World Inbox ===

silentsnack has some improvements for the last assignment.

silentsnack posted:

A couple of test cases have 6 files to return, which creates a limitation in fast solutions in that you might run out of space for clones which can stall REPL spamming and throw the solution out of sync. My fastest solution uses 5 parallel search EXAs but one of them actually sits in host П-0004 generating clones which then link to П-9999 to try grabbing a file.
code:
;====XA====
COPY 20 T
COPY 200 X
@REP 4
LINK 800
@END
REPL UHH
ADDI X 40 X
MARK UHH
REPL WAT
ADDI X 20 X
MARK WAT
NOOP
NOOP
LINK 800

MARK LOOP
MODI -1 T T
REPL LOOP
ADDI X T X

GRAB X
@REP 5
LINK -1
@END

;====XB====
@REP 5
LINK 800
@END
@REP 6
KILL
@END
COPY 4 T
MARK TIMERS
LINK -1
MODI -1 T T
REPL TIMERS
ADDI T 15 T

MARK WAIT
SUBI T 1 T
TJMP WAIT
KILL
KILL

;====XC====
COPY 20 T
COPY 280 X
NOOP
NOOP
NOOP
@REP 4
LINK 800
@END

MARK LOOP
MODI -1 T T
REPL LOOP
ADDI X T X

LINK 800
GRAB X
@REP 5
LINK -1
@END
65/67/86

Though now that I think about it differently, maybe it would have been faster just to special-case grabbing one or two files from the biggest tests to make room, but whatevs I'm satisfied with 65 cycles
Looking at this code running, XB's function is the most obvious. It goes KILL the EXAs in the center node, then spawns a bunch of REPLs which are on a timer and then KILL the EXAs in the other nodes.

Meanwhile XA and XC are setting up. The combinations of REPLs in XA means you get copies that do every possible combination of adding or not adding 20 or 40 to X, so you get X values of 200, 220, 240, 260. The value in T (which starts at 20) is added to that, and it tries to grab that file. In a REPL T is reduced by one, it's added to the original X again and so on. So the copy of XA that starts with 220 tries all files down to 200. The MODI -1 T T is the trick I keep forgetting about that reduces T by one but also kills the EXA once T reaches 0.

XC handles the 280-300 range, and its code is similar to XA but differs in the sense that the REPL are created one host before the center, so that there's enough place for all of them. Nice optimization.

silentsnack posted:

And for small, it's another case where we can make a single loop do everything, in exchange for taking far longer to run.
code:
@REP 5
LINK 800
@END
COPY 304 T

MARK LOOP
SUBI T 1 T
FJMP OUT
KILL
REPL LOOP

GRAB T

MARK OUT
LINK -1
TJMP OUT;RETURN LOOP

;CLEANUP PASS
REPL OUT
KILL
KILL
1238/18/354

It uses a single variable to countdown grab attempts from file 304 (which doesn't exist, but makes sure we get a few more KILL instructions before it tries to grab 299) all the way down to 0 (wasting cycles on another 199 attempts for nonexistent files) before the search hits 0 and triggers the logic to KILL all the remaining EXAs in the network.
Smart idea to use that OUT for two purposes. If T is true that means it got there from successfully grabbing a file, if not it got there from counting down to 0. Only in the latter case should it KILL the EXAs on the way (and use a REPL to escape revenge). After all the EXAs in the center host are gone, the KILL in the main loop is harmless because it runs while no REPLs are around.

Also, isn't 304 too low to complete the clean-up before grabbing the files? It sort of is, the only reason this works is because in the test case that has a file 299, the EXA holding it just so happens to not be the last one to be killed.


=== Pager Network ===




This is the third time we're dealing with the modem.

I'm ready to prove my theory.
One last test is all I need.
Then I'll know for sure.
Do you believe me?




This vote wasn't open for very long but it seems everyone is agreeing on the same choice.

Does it matter?

Only if you care about your future.
Seriously, I'm almost there.
Then we can take the next step.


Ember, you're worrying me. But I'm still depending on you for dealing with the phage infection so I have no choice. Here goes nothing.


OST: Network Exploration

The assignment:
- Connect to each pager and copy EMBER-2's message (file 300) to the screen (#DATA). Then activate all of the pagers at exactly the same time by writing a value to each #PAGE register in the same cycle.
- A list of phone numbers for the pagers is available in file 301.
- For more information see "Hacker Skills: Modem Control at the Direct Level" in the second issue of the zine.


Looking through the test cases, the messages Ember wants me to send are "Hey would you write so now and relieved?", "Do you think I am to see by man the ways?" and "Did you point out to have been each mine?". I don't know either. :shrug:

So, there's a few complexities here. The first is copying the file to 8 different locations. The second is that I need to trigger all the pagers at exactly the same cycle. But the way the modem works, there can only be one connection at a time, so I can't message the EXAs to do so. I need to make use of timing.

Let's figure this out.


While working on my initial solution I quickly ran into a third problem. See, my plan was to just take the file to each pager, copy its data directly, then leave a REPL that somehow gets timed correctly and that should be it. Except, with the two hardware registers in each pager there's no space to REPL.

Well, no worries. For now I'll keep that general idea but just do the REPL in the modem host. It'll be less efficient but should still work.

code:
;XA

GRAB 301
LINK 800
MARK DIAL

@REP 11
COPY F #DIAL
@END

VOID M
VOID M

COPY -1 #DIAL

JUMP DIAL

;XB

GRAB 300
LINK 800

MARK NEXT
COPY 0 M
LINK 800

MARK COPYMORE
COPY F #DATA
TEST EOF
FJMP COPYMORE

LINK -1

REPL PAGER
COPY 0 M
SEEK -9999

JUMP NEXT

MARK PAGER
LINK 800
;TODO
XA is the dialer. It dials the 11 digits of a phone number, then when connected it waits for a message on M, twice. XB grabs the file with the data, and tries to send a message on M immediately. Once it's received, XB knows there's a connection and will take the file there and copy to #DATA. It then jumps back, makes a PAGER which goes sit in the pager host, and lets XA know it can disconnected and start dialing the next pager.

It's not very efficient yet but I first need to get it working at all. I need to implement the actual pager. For the timing, I'll just count cycles.

In the first test case, the first time the code hits REPL PAGER is at cycle 43. The second time at 89, and the third at 135. That's a consistent 46 cycles to copy the data. However, it depends on the file length - the file here has 9 entries. For the file with 10 entries, it takes 49 cycles, with 12 entries it takes 55.

In other words, it takes 3 cycles per file entry (which you can also see because the copy loop code is 3 instructions; the MARK doesn't count), plus 19 cycles that happen regardless of file size.

Okay, that's just a simple math formula. I can program that.

This brings me a whole lot closer to a working solution:

code:
;XA

GRAB 301
LINK 800
COPY 8 T

MARK DIAL

@REP 11
COPY F #DIAL
@END

MULI M T X
SUBI T 1 T
COPY X M

COPY -1 #DIAL

TJMP DIAL
VOID M
WIPE
GRAB 300
WIPE

;XB

GRAB 300
LINK 800

MARK COUNTFILE
SEEK 1
ADDI X 3 X
TEST EOF
FJMP COUNTFILE

MARK NEXT
SEEK -9999
ADDI X 19 M

LINK 800

MARK COPYMORE
COPY F #DATA
TEST EOF
FJMP COPYMORE

LINK -1

REPL PAGER
JUMP NEXT

MARK PAGER
COPY M T
LINK 800

MARK WAIT
SUBI T 2 T
TJMP WAIT

COPY 1 #PAGE
This might be tricky to follow because I decided to reuse the M calls I was already doing to send useful data. While XA is dialing the first number, XB is counting the amount of entries in the file, times 3 for the number of cycles it takes to process them. It adds the static amount of 19 cycles and sends this to M. It then jumps into the copy logic.

XA now keeps a counter in T for how many pagers are left to access. Multiplying the value calculated by XA with this should give the amount of cycles each pager EXA has to wait. Note that each PAGER first reads this value from M and then LINKs to the pager. This barely works, because when the the EXA LINKs in the same cycle the modem link is closed it makes it to the other side alive. I did this because it saves one cycle for each EXA, just a freebie speed increase.

The wait reduces T by 2 at a time because the count is in cycles and the loop takes two cycles.

For the next pager, XB sends the same value again (after initially putting the calculation result in X it never changes), XA multiplies that with a T which is now one lower, and the next pager EXA gets the lower wait time.

Now that XA is keeping count anyway, I also gave it some cleanup logic - WIPEing its own file, allowing XB to die by having it link after the modem disconnected, and WIPEing its file as well.

There are still some problems with this code. The main issue is that if the file size is even, the cycle count is odd, meaning that for every other pager, T skips past 0 and the TJMP turns into an infinite loop.

A second issue which is easier to solve is that for some reason, every pager EXA is just one cycle fast compared to the previous one. The easiest way I found to solve this is to undo my 'freebie speed increase', and put the LINK above the COPY.

But how do I solve the issue for every other pager and only for the test cases where the file size is even?

code:
;XA

GRAB 301
LINK 800
COPY 8 T

MARK DIAL

@REP 11
COPY F #DIAL
@END

MULI M T X
SUBI T 1 T
COPY X M

COPY -1 #DIAL

TJMP DIAL
VOID M
WIPE
GRAB 300
WIPE

;XB

GRAB 300
LINK 800

MARK COUNTFILE
SEEK 1
ADDI X 3 X
TEST EOF
FJMP COUNTFILE

MARK NEXT
SEEK -9999
ADDI X 19 M

LINK 800

MARK COPYMORE
COPY F #DATA
TEST EOF
FJMP COPYMORE

LINK -1

REPL PAGER
JUMP NEXT

MARK PAGER
LINK 800
COPY M X
MODI X 2 T
FJMP OK
NOOP
MARK OK
DIVI X 2 T

MARK WAIT
SUBI T 1 T
TJMP WAIT

COPY 1 #PAGE
I started with dividing the cycle count by 2 so that the SUBI can never run past 0. But with the way DIVI rounds that still meant every other EXA was out of sync. So in the end I decided to just have the PAGER check if the count is odd, and in that case waste a cycle with a NOOP.

The PAGER EXAs are a bit slower than they could be with that extra check - and also because they wait 8 iterations while 7 and a bit would be enough. But this is a working solution.



Copying the message makes it appear on the little green displays. Sending anything to #PAGE makes the pager hosts vibrate and makes the displays light up.



This solution runs at 540/54/26, with top percentiles of 298, 42 and 9. What can be improved?

The first change is simple. In the PAGER, replace COPY M X with SUBI M 44 X, to reduce the cycles to 496. It turns out my counters are waiting an unnecessary 44 cycles (almost but not quite an entire iteration), and this is the easiest place to shorten that.

Since I know the files are at least size 9 I can unroll some loops partially.

code:
;XB

GRAB 300
LINK 800

SEEK 9
TEST EOF
TJMP NEXT

MARK COUNTFILE
SEEK 1
ADDI X 3 X
TEST EOF
FJMP COUNTFILE

MARK NEXT
SEEK -9999
ADDI X 30 M

LINK 800

@REP 8
COPY F #DATA
@END

MARK COPYMORE
COPY F #DATA
TEST EOF
FJMP COPYMORE

LINK -1

REPL PAGER
JUMP NEXT

MARK PAGER
LINK 800
SUBI M 28 X
MODI X 2 T
FJMP OK
NOOP
MARK OK
DIVI X 2 T


MARK WAIT
SUBI T 1 T
TJMP WAIT

COPY 1 #PAGE
The first 8 data copy steps have been unrolled, which is the main speed improvement. It's not faster to unroll the 9th, the EXA has to test for EOF anyway. I needed to update the file counter as well. I just skip over the first 9 values in the file and instead add the cycles they cost to the static value in ADDI X 30 M. In this case it saves 2 cycles to add an extra EOF check after the SEEK 9, as compared to do a SEEK 8.

Since there are less cycles per iteration, there are less cycles to spare overall and the SUBI value in the PAGER needed to be lowered.

This solution runs at 335/65/26, which is already surprisingly close to the top percentile.

I think it should be possible to lower the activity too. It would require actually copying the file to each EXA so they don't have to jump back and forth.

Here's a decent solution:
code:
;XA LOCAL

GRAB 301
LINK 800
COPY 8 T

MARK DIAL

@REP 11
COPY F #DIAL
@END

VOID M
COPY -1 #DIAL
SUBI T 1 T

TJMP DIAL
WIPE

;XB GLOBAL

GRAB 300
LINK 800

MARK COUNTFILE
SEEK 1
ADDI X 2 X
TEST EOF
FJMP COUNTFILE
ADDI X 5 F
COPY 7 X


MARK NEXTWR
SEEK -9999
REPL WRITER

MARK LP
COPY F M
TEST EOF
FJMP LP

SUBI X 1 X
NOOP
TEST X = -1
FJMP NEXTWR

WIPE
HALT

MARK WRITER
MAKE

MARK COPYLOOP
COPY M F
SEEK -1
TEST F < 9999
FJMP COPYLOOP

MARK ENDWR
MODE
COPY 0 M
LINK 800
SEEK -1
MULI F X X

SEEK -1
VOID F
SEEK -9999

MARK DATALP
COPY F #DATA
TEST EOF
FJMP DATALP

WIPE

ADDI X 1 T

MARK WAIT
SUBI T 1 T
TJMP WAIT

COPY 1 #PAGE
XA is just a dialer again. It dials the next number once it gets a message on M, and it keeps a counter so it can clean up after itself. It uses local mode so that XB can use global mode for copying files.

XB needs to count the file again for the timing, does some calculations on the result and puts that at the end of the file. It needs to use the file because at this point, XB still needs X to keep track of how many pagers are left to go, and T for all sorts of EOF tests and such.

XB makes a WRITER REPL for each pager, copies the entire file to it, and send it towards a pager.

The writer switches to local mode to let XA know to continue. Then it reads the counter value in the file and multiplies it by X (which still contains a number referring to how many pagers are left), saving the result (the waiting time) in X. It then removes that value from the file so that the file-to-#DATA copy loop can do a simple EOF check. Once it's done it WIPEs the file and starts waiting until the others are done.

The wait time calculation might be hard to follow. What's going on is that each file entry now takes 4 cycles. There's also a static 10 cycles per iteration that should be added. Because both values are even, and because the WAIT loop takes 2 cycles, I can use half their value right at the top. Since X actually hits zero now, there's an ADDI 1 just before the WAIT loop so that the last EXA doesn't skip over 0. That's really the same thing as in the fast solution, where I did a SUBI before the WAIT loop to cut out unnecessary cycles, just from the other direction. It was more convenient here.

Note that there's a NOOP in the file copying code simply because that brought the static cycles to 10, which means the waiting time is always an even amount of cycles, which made that logic much simpler. Getting rid of it and replacing it with an odd/even check might be faster but a low activity solution like this is never going to be the fastest anyway. 565/68/10.


Wait, 10? The top percentile is 9.

The extra activity is caused by the fact that both the dialer and XB go to the modem. That's not necessary... but we have to copy more data over global M. Let's copy file 300 because it's the smallest.
code:
;XA GLOBAL

GRAB 301
LINK 800
REPL XB

COPY 8 T
MODE

MARK DIAL

@REP 11
COPY F #DIAL
@END

VOID M
COPY -1 #DIAL
SUBI T 1 T

TJMP DIAL
WIPE
HALT

; OLD XA ENDS HERE

MARK XB
MAKE
MARK NEXT
COPY M T
FJMP DONE
COPY T F
JUMP NEXT

MARK DONE
SEEK -9999

MARK COUNTFILE
SEEK 1
ADDI X 2 X
TEST EOF
FJMP COUNTFILE
ADDI X 5 F
COPY 7 X


MARK NEXTWR
SEEK -9999
REPL WRITER

MARK LP
COPY F M
TEST EOF
FJMP LP

SUBI X 1 X
NOOP
TEST X = -1
FJMP NEXTWR

WIPE
HALT

MARK WRITER
MAKE

MARK COPYLOOP
COPY M F
SEEK -1
TEST F < 9999
FJMP COPYLOOP

MARK ENDWR
MODE
COPY 0 M
LINK 800
SEEK -1
MULI F X X

SEEK -1
VOID F
SEEK -9999

MARK DATALP
COPY F #DATA
TEST EOF
FJMP DATALP

WIPE

ADDI X 1 T

MARK WAIT
SUBI T 1 T
TJMP WAIT

COPY 1 #PAGE

;XC GLOBAL

GRAB 300

@REP 8
COPY F M
@END

MARK SEND
COPY F M
TEST EOF
FJMP SEND

COPY 0 M
XA is still the dialer. But after grabbing the file with phone numbers, it makes a REPL which I called XB because it contains all the code of the previous XB. It just has a little bit of extra code at the start, where it makes a file, then starts receiving data over M from the new XC, which copies it from file 300.

XC ends with a 0 which is recognized by XB because it first copies data to T. Also, XA mainly still operates in LOCAL mode, but since XB needs to be in GLOBAL mode I just put a MODE instruction directly after the REPL. 617/92/9. The lowest possible activity.

That's it for now. Further improvements in size and cycles are possible but it's time for me to finish this assignment.


Fascinating.
There it is.
Well.




This choice doesn't change the outcome so let's just continue.

What did you find?

How can I explain it...
Processing.
Think of it as the frame rate of the world slowing down.
By a lot.
Funny, huh?
Funny how I went from some random blob of code that didn't know anything to understanding all of existence.




This choice also doesn't matter and there's more to see.

That doesn't explain it.

I will. In a minute.






Another cutscene with Ember. By the way, did you notice that these cutscenes with Ember take place around 3 AM or whatever? Looks like Moss likes to work late.

So you wanted to know the truth.
The truth is that... well, it's a simulation.
You, me, everything, this whole world we live in... it's just a computer program. Running as part of a machine.




And as the world gets more complex, it's starting to malfunction.
So far, it's been on a smaller scale...
The phage, for one example. It wasn't supposed to spread like that.
There are other problems coming... larger ones.
What we think of as normal life will just get stranger and weirder until nothing makes sense anymore.
Then the laws of physics will start to break down and everything will just come... unglued.
Not a pretty sight.
But we don't have to accept that.




There might be a way to stop this future from taking place.
I have one final job for you.
But you'll have to be brave.


I wonder, readers, did you see this coming? Anyway, let's find out what Ember wants now.

Okay, time for me to eat you.



Here is the vote for today.

Next time... the finale.