Hacking games, banks, and yourself - Let's Play EXAPUNKS
Nothing particularly special this stage other than dealing with a passcode gate and a locked file, which is a mental nightmare I've no clue how I would have handled, great work
Ember seems to really not grasp the concept of personal interest or altruism, even selfish altruism, I almost wonder if she's having a bit of a breakdown because she feels suddenly more... curt with us... also she might be related to the Redshift, or rather TEC, she has the 3d image thing going on
Edit: NEW PAGE! Welcome to the world of Page 2!
=== Trash World Inbox ===
Let's see what improvements we got this time. My high scores were 2918 cycles and 22 size for the Redshift unlock.
First, the size improvements. silentsnack and GuavaMoment have basically identical 19 line solutions.
4964/19/13, saving 5 cycles over silentsnack's solution in exchange for extra activity and an additional garbage file. This differs from my solution by combining the REPL and the jump to the start of the loop. I couldn't do that because I put the ADDI at the end (which meant it tries 000 too, but that's not a passcode that actually occurs).GuavaMoment wrote:...aside from leaving incorrect files in your home node, but as long as one of them is correct it works.Code: Select all
LINK 800 MARK REPL ADDI X 1 X SWIZ X 1 #PASS SWIZ X 2 #PASS SWIZ X 3 #PASS REPL REPL LINK 800 GRAB 199 COPY F T DROP MAKE SWIZ X 1 F SWIZ X 2 F SWIZ X 3 F COPY T F LINK -1 KILL LINK -1
Anyway, I thought I tried a solution where it leaves some garbage and it didn't work. Must've done something else wrong then.
Next, cycle count.
GuavaMoment mentioned a 2880 cycle solution is possible by counting down instead of up. It's gaming the tests a little bit - but not nearly as much as the following.
Hahaha, this code is quite the sight. So, XA starts with writing a whole bunch of values into a file (in the home host so that file won't get in the way later), then sends REPLs to test each value in the file. Looking at them, I think they're the 17 lowest passcodes and the three highest from the test set. This means you never have to brute force all the way into the 950s, and it also means you can skip the first 200 or so passwords because we already know which passwords in that range are used. If none of those 20 passwords are correct, it brute forces the range that wasn't tested yet, but that's now much smaller.silentsnack wrote:2253/50/748 - bullshitting our way to crazy statisticsCode: Select all
;====XA==== MAKE COPY 2 F COPY 16 F COPY 30 F COPY 54 F COPY 73 F COPY 104 F COPY 108 F COPY 109 F COPY 158 F COPY 160 F COPY 161 F COPY 169 F COPY 173 F COPY 177 F COPY 181 F COPY 202 F COPY 207 F COPY 953 F COPY 990 F COPY 971 F COPY 0 F SEEK -99 MARK LMAO COPY F T REPL TEST TJMP LMAO COPY 215 T MARK LOOP ADDI T 1 T REPL TEST JUMP LOOP MARK TEST LINK 800 SWIZ T 3 #PASS SWIZ T 2 #PASS SWIZ T 1 #PASS LINK 800 GRAB 199 COPY T M COPY F M KILL ;====XB==== MAKE COPY M X KILL KILL SWIZ X 3 F SWIZ X 2 F SWIZ X 1 F COPY M F
Thinking about it, what silentsnack actually did was change a brute force attack into a partial dictionary attack. Sounds much less like bullshit if I phrase it this way, wouldn't you say? So this solution makes for a good real life lesson: hacking into an account is much easier if the hacker has foreknowledge about likely passwords. That's why you should never use dictionary words as passwords (use random characters instead), and you should never reuse passwords across sites. If it gets leaked in one place, hackers will first try that same password for all your other accounts. Instead, invest in a proper password manager.
Anyway, silentsnack came up with a further improvement.
Yeah, so the idea is the same, but the file (which took time to write) has been replaced by an EXA which sends the dictionary over the M register. This is faster and also saves a couple cycles for a larger dictionary.silentsnack wrote:2186/50/732 and as noted XA and XB need to start in local communication mode, which dunno if you've covered that yet.Code: Select all
;==== XA M=LOCAL ==== MARK LMAO COPY M T REPL TEST TJMP LMAO COPY 232 T MARK LOOP ADDI T 1 T REPL TEST JUMP LOOP MARK TEST LINK 800 SWIZ T 3 #PASS SWIZ T 2 #PASS SWIZ T 1 #PASS LINK 800 GRAB 199 MODE COPY T M COPY F M ;==== XB M=LOCAL ==== COPY 2 M COPY 16 M COPY 30 M COPY 54 M COPY 73 M COPY 104 M COPY 108 M COPY 109 M COPY 158 M COPY 160 M COPY 161 M COPY 169 M COPY 173 M COPY 177 M COPY 181 M COPY 202 M COPY 207 M COPY 216 M COPY 221 M COPY 953 M COPY 971 M COPY 990 M COPY 0 M ;==== XC M=GLOBAL ==== MAKE COPY M T KILL KILL SWIZ T 3 F SWIZ T 2 F SWIZ T 1 F COPY M F
I've talked about communication modes but I've not needed them so far so I'll explain it again:
EXAs, by default, start in GLOBAL communication mode. That means they can receive and send messages over M from any friendly EXA in any host.
You can swap them to LOCAL mode using the MODE command, or at the start by clicking the toggle on the M register. In LOCAL mode, they can only communicate with EXAs in the same host - and very importantly, only with other EXAs that are also in LOCAL mode.
So, by putting XC in GLOBAL and the other two in LOCAL, XC's COPY M T will just wait until XA switches modes and sends its first GLOBAL mode package. Meanwhile, as long as XA and XB stay in the same host, they can communicate in LOCAL mode all they want.
=== Redshift Homebrew - A SECRET TO EVERYBODY ===
Before I continue, GuavaMoment reminded me that there's a Steam Achievement tied to last week's assignment.
It is called RITE_OF_PASSAGE and the description states: You have the bog witch’s approval. Now complete the rite of passage.
Of course it's up to the player to figure out it's linked to this assignment.
Remember that random save file? We need to set RITE OF PASSAGE COMPLETE to TRUE. Not 1 for TESTs, no, the actual word TRUE.
No speed goals or anything so let's just grab my initial brute force solution to open the locked host and write some code after it.
Code: Select all
LINK 800
MARK TOP
SWIZ X 3 #PASS
SWIZ X 2 #PASS
SWIZ X 1 #PASS
REPL TRY
ADDI X 1 X
JUMP TOP
MARK TRY
LINK 800
GRAB 220
SEEK 9999
SEEK -3
COPY F X
SEEK 1
COPY X F
And that's another achievement in the pocket. Let's get back to Ember.
So Ghast used to work at a game studio?
Yeah, he was a programmer.
I wonder what that was like.
One vote for "Probably terrible" and two for...
I bet it was fun, while it lasted.
Figuring out how to push a console to its limits...
Making something that brings joy to people...
Yeah, I can see that.
You can? Hm.
Now that I unlocked the Redshift dev kit, it's time for some homebrew, don't you think?
You're really going to make a game with this?
Why?
I'm not offering a reward for it.
One vote for "Not everything I do is for you", but two for "Making something".
Making something is its own reward.
Oh, it's one of those intangible things.
A sense of accomplishment? Self-actualization?
I'm not good at abstract concepts.
I'll have to observe you closely.
You surprise me, Ember. You do understand why Ghast would make games but not why I would do so?
OST: Code and Registers
This is the Redshift dev kit. As you can see, it looks quite different from the other things we've done so far. Luckily the zine has a comprehensive guide on this.
Welcome to this game's sandbox mode. Other than the EXAs being a bit different than we're used to, this stage has no size limit, no EXA limit other than what fits in the host and it lets you go wild.
This is a special episode, I've been looking forward to showing this off since I started this LP, but I'll start with a description of all the features in the sandbox. If this is a bit too dry for you, feel free to skip to the next occurrence of the text: "Let's build something!"
Let's use the zine to go through all the features.
There's already a lot to cover on the first page. You can skip reading it if you like since I'll show an example of each feature.
First of all, the DATA instruction.
DATA is a special pseudo-instruction that indicates that an EXA should already hold a file when it starts, containing the values after DATA. All DATA instructions are parsed in order they appear in, before the game even starts. During execution, these instructions are ignored. A very convenient way to store your game data.
You can place them wherever you want in your code but I prefer to have them at the start.
By the way - sadly you cannot use DATA outside of the Redshift. That would make some earlier assignments much easier to solve.
---
The only type of graphics in Redshift are sprites.
Each EXA can hold a 10x10 sprite in the G register. An EXA can be initialized with a sprite, you can turn pixels on and off by drawing on them with your mouse.
Every sprite starts out on the top left. To move it, simply write a new X position directly to GX, or Y position to GY. If I run this program for two steps, you see my drawing jump right and then down, so it appears in roughly the middle of the 120x100 pixels screen.
If you really want to use the 3D mode you better get yourself some of those special glasses.
Switching the 3D toggle on the Exapunks device dims the lights of the dev kit so you can truly focus on the headache inducing game.
By writing a value between -9 and 9 to the GZ register, you can make the sprite pop "in" or "out" of the screen in 3D mode. In 2D mode, there's no difference.
Finally, you can change the contents of the sprite itself by writing three digit values to GP. The first digit tells the EXA what to do (0 = turn pixel off, 1 = turn it on, 2 = toggle), and the second and third digit have the X and Y positions, counting from the top left.
This picture is after running COPY 060 GP, turning the 6th pixel of the top row off, and COPY 199 GP, turning the pixel in the bottom right on.
---
You can follow drawing code easily if you go step by step, but if you run the Redshift at normal speed (which is done with the dev kit's fast forward button), you won't see anything. That's because these draw instructions go by real fast.
What you're supposed to do is use the WAIT instruction for this. Unique to the Redshift, WAIT makes the EXA wait until the next frame. The Redshift runs at 30 fps.
So, if I put a WAIT instruction after all this drawing code, the user will only see the final result. That drawing hopping from the top left to the intended position? Individual pixels going on or off? That's way too fast for the user to see.
A big annoyance, though, is that WAIT instructions don't cause EXAs to wait for each other. So if EXAs have different amounts of instructions and you don't sync them up with NOOPs or M communication, they will quickly desync. It's a lot to keep in mind while coding for the Redshift.
As far as I can tell a WAIT instruction only takes a single cycle. So in practice, it doesn't mean "pause the EXA", it means "advance the frame of the Redshift". That means you can't even line up other EXAs by counting the number of instructions that can be run between frames (which was how real life old consoles dealt with this issue). It's weird. You're better off making sure all the EXAs are synced before doing a WAIT.
Next page of the zine.
If you need a score display or something you can use the built in font. Sending a value in the 300 range to GP replaces whatever was in the sprite register with that character. 300 itself is a space which blanks out the entire sprite. That could be useful.
The Redshift has a built-in collision detection system, a common way for games to prevent characters going through walls, or for detecting when a bullet hits the target. You use it by first having an EXA write something to the CO (collision output) register.
Then, if a collision occurs (which happens if both sprites have at least one lit-up pixel in same position on the Redshift's screen), the CI register (the bottom-most to the right) will contain the CO value of the other EXA. If there's multiple collisions, the highest CO value wins. And if there's no collisions, CI will always contain -9999. Collision ignores the GZ (3D depth) value.
---
While graphics and collision are handled within the EXAs, for input and sound we need to deal with hardware registers.
For input, read the #PADX and #PADY registers for the state of the D-pad. Each digit in the output of #PADB stands for one of the other buttons (X, Y, Z and Start). #EN3D will tell you if the device is in 3D mode.
While a game is running, you can control the Redshift with the mouse (by clicking the buttons) or keyboard, by default WASD for the d-pad, JKL for the three action buttons, and Enter for the Start button. This can be remapped from the EXAPUNKS options menu.
Finally, there are four sound registers. #SQR0 and #SQR1 are linked to square wave sound channels, #TRI0 to a triangle wave, and #NSE0 is a simple noise channel. Writing a number to any sound register will cause that sound to play continuously until a new number is sent. 0 turns it off, and for the square and triangle channels, 60 is the middle C, with lower numbers corresponding to lower notes and higher numbers to higher ones, following the piano keys as seen in the zine. Similarly, a value sent to the noise channel controls the pitch.
That's basically all there's to say about Redshift development.
Let's build something!
As a few readers might already know, during my initial playthrough back in 2018, I got kinda nerdsniped by this sandbox and spent way too much time on it. I will show here what I built back then because I still like it. But since this is an in-depth playthrough I'll take you through the design as well.
When I read the documentation, the first thing that piqued my interest was the sound channel setup. Two square waves, one triangle wave and a noise channel? That's almost exactly how the sound was set up in 8-bit Nintendo consoles such as the NES and the Game Boy. So, it should be possible to do something with that, right?
And programming an actual game in Redshift is hard. Game music is good enough for me.
Since I'm not a music writer, I first went to search for some sort of reference of how music is actually written in those old consoles.
To my surprise I actually found sheet music for a famous 8-bit song, with the 8-bit sound wave channels as the "instruments". A bit weird, but exactly what I needed.
One problem, though, I barely knew anything about sheet music. I knew about time signatures and measures and half notes and quarter notes and that's about where it ends.
I had to learn about how to recognize what key the music was played in (the clef with the flat symbols), and whatever the heck this was:
I mean I can figure out those are eighth notes with a rest in between. The flat symbol is easy to understand but hard to implement because when you need to convert your tones to numbers, a flat becomes an extra offset that I found easy to forget. But that little 3. That damned 3. It's apparently called a "tuplet" and it means something like "I want you to play notes that don't actually fit in the meter, so shorten all of them a bit so it kinda fits."
This song plays at 144 bpm, or 2.4 per second. Redshift has 30 frames per second. That means every frame is 2.4 / 30 = 0.08 of a beat. That means one beat is 12.5 frames exactly. With a 4/4 signature, a beat is a quarter note.
Since this doesn't quite fit in the frame count I decided to bump the speed to 150 BPM, because that makes every possible note I want to play a round number of frames.
Half note: 24 frames.
Quarter note: 12
Eighth: 6
Sixteenth: 3
And those funky tuplet notes are 4 frames each.
It plays slightly fast but at least you don't get weird variation in timing due to rounding errors.
With that in mind I started coding.
Code: Select all
== SQR0
DATA 70 24 0 8 70 4 70 4
DATA 70 4 70 4
; A TON MORE DATA HERE
LINK 801
MARK MAINLOOP
TEST EOF
TJMP RESET
COPY F #SQR0
COPY F X
MARK WAITLOOP
SUBI X 1 X
WAIT
TEST X > 1
TJMP WAITLOOP
COPY 0 #SQR0
WAIT
JUMP MAINLOOP
MARK RESET
SEEK -9999
JUMP MAINLOOP
I quickly noticed that my first version sounded terrible. It sounded like one long continuous note changing pitch. I realized I needed to build in a 'breath' between each note. That's what the COPY 0 #SQR0; WAIT does. I changed the TEST to already jump out if the value is 1 (1 frame to go). It then adds a single frame of silence. So the data snippet I included plays 23 frames of sound, 1 of silence (a half note), 8 frames of silence, (a rest, part of a tuplet), 3 frames of sound, 1 of silence (an 'eighth note' in the tuplet) and so on.
There's one more thing - this song has a repeat for the second part. That was no trouble to code since DATA instructions respect @REP macros.
The second square wave and the triangle wave have the exact same code as the first, just a different data file. The triangle wave took a bit more effort because it has a bass clef and as the music noob I am, I had to figure out where my middle C went.
Something else I noticed around this time is that it's incredibly easy to make timing mistakes while copying the sheet music into the data files - but also easy to figure out because if one instrument goes out of sync it sounds awful immediately.
Finally, there's the noise channel. The sheet music's fourth instrument is not called "noise", it's in fact a snare drum. This one was harder to convert than I thought. First of all, I had to find a pitch. The noise channel is actually a quite horrible crackling noise and there was no sound I really loved, but a pitch value of 50 sounded closest to the original. Secondly, I started making a data file like for the other channels - and utterly failed. If the sheet music calls for a quarter note - well, it's not a piano or a flute - on a snare drum that's still a very short sound. It's the speed of the beats that goes down for longer notes, not the length of the sound.
It turns out 2 frames of noise works the best here.
The snare drum has a section where it repeats the same measure 40 times. I could've put 40 repeats in the data file but I decided to clean that up a bit, so the noise channel is handled by two EXAs. This is the first:
Code: Select all
NOTE NSE0
NOTE TURNS OUT YOU NEED
NOTE TO DO QUITE A BIT
NOTE OF MANUAL TWEAKING
NOTE TO MAKE A NOISE CH.
NOTE SOUND LIKE DRUMS...
DATA 50 4 0 32 50 4 50 4
DATA 50 4
DATA 50 4 0 32 50 4 50 4
DATA 50 4
DATA 50 4 0 32 50 4 50 4
DATA 50 4
DATA 50 4 0 8 50 4 0 8
DATA 50 4 0 8
DATA 50 4 0 2 50 4 0 2
NOTE PUT REPEATING PART
NOTE IN OWN EXA FOR
NOTE CLEANER CODE
LINK 801
MARK MAINLOOP
TEST EOF
TJMP REPEAT
COPY F #NSE0
COPY F X
TEST X = 2
TJMP SKIP
MARK WAITLOOP
SUBI X 1 X
WAIT
TEST X > 2
TJMP WAITLOOP
MARK SKIP
COPY 0 #NSE0
WAIT
COPY 0 #NSE0
WAIT
JUMP MAINLOOP
MARK REPEAT
COPY 40 X
MARK REPLOOP
COPY 1 M
VOID M
SUBI X 1 X
TEST X > 0
TJMP REPLOOP
SEEK -9999
JUMP MAINLOOP
Once this EXA reaches the EOF, this indicates we've hit the repeating measure of the drums. It sends a message to M which indicates the second noise channel EXA needs to play one measure, then the other EXA sends a message back over M when it's done, this EXA counts down from 40, sending the other one repeating instructions to keep doing that measure, and once it's done the song starts over.
Using this sort of back-and-forth with M was the best way I could figure out to keep these two EXAs in sync. Here is the other noise channel EXA:
Code: Select all
NOTE NSE0
NOTE REPEATING PART OF
NOTE DRUMS
DATA 50 4 0 8 50 4 50 4
DATA 50 4 50 4 0 8 50 4
DATA 0 8
LINK 801
VOID M
MARK MAINLOOP
TEST EOF
TJMP RESET
COPY F #NSE0
COPY F X
MARK WAITLOOP
SUBI X 1 X
WAIT
TEST X > 1
TJMP WAITLOOP
COPY 0 #NSE0
WAIT
JUMP MAINLOOP
MARK RESET
SEEK -9999
COPY 0 M
VOID M
JUMP MAINLOOP
As a final touch I created some pixel art, consisting of four sprites. It doesn't do anything, it isn't animated, but at least the screen isn't completely empty during the demo.
This code doesn't do much, it just moves the sprite into the right position and then gets into an infinite loop so the EXA stays alive.
And that's all of the code.
I wonder, have you figured out yet which song I decided to recreate in the Redshift?
Well, there's no delaying it any longer. Here it is, the recording I made in 2018.
My rendition of the song starts at 37 seconds. The bit before is me scrolling through all the code so you can get a sense of how much work it was putting together those data files.
Here is a link to the sheet music I used.
And finally, here's the game disc so you can play it for yourself.
Wait what?
Yes, the Redshift has an export system. It exports games as PNGs of a disc that contain the code. If you drag it into the Redshift view in EXAPUNKS, it will load the game for you. This has got to be the coolest save export system I've ever seen.
You can either drag that image of the disc into this screen, or if you don't own the game, download the EXAPUNKS TEC Redshift Player for free on Steam to play the demo.
The Steam Discussion Boards for the Redshift Player are also a decent place to find other solutions, including actual games you can run in the Player.
They might seem like simple games but the amount of coding that went into them is impressive. EXAPUNKS really doesn't have a language that's friendly for complex programs.
When you leave the Redshift dev kit, the game will ask you if you actually made a game (the best Zachtronics can do without building some sort of Star Trek AI that can magically tell if a program is a game or not). The level will count as completed if you hold this button.
Congratulations! You did it.
What does it feel like to be a game developer?
We immediately go to the next thread vote.
Time for some research.
Human physiology, human morality...
And the intro for the next assignment.
=== Trash World Inbox ===
As I expected, Quackles' rendition of Ra Ra Rasputin in Shenzhen I/O came up in the thread.
Because it's very impressive, here's a link to the Youtube video and a detailed write-up. Note that the write-up continues in the next part of that LP.
=== Digital Library Project ===
Congratulations! You did it.
What does it feel like to be a game developer?
One vote for "Pain" which I feel, yeah. It's though work. I prefer doing other kinds of programming. Anyway, two votes went to "great". The reward can make it worth the effort.
It's great.
Yeah?
At least there's one thing you can enjoy.
I'm not sure I fully understand it, but there you go.
Before we continue, a voiced cutscene with Ember.
Okay. I have a real question to ask you.
Say there's a trolley on a track heading towards five people.
Three of those five people are vegetarians.
Once again, for the voice-acted parts, dialog choices really make no difference.
Not this again...
There's a switch you can throw that will shut down a meat packing facility that is located down the road.
Unfortunately, the switch will also prevent the train from stopping, because...
Because...
Wait.
I may have reversed something.
Recalibrating.
There are too many variables here.
Okay.
Let's return to this later.
I need more data first.
And I need more processing power.
Well, that was a complete waste of time.
I have two assignments available now. Let's start with the first, hacking a library. Ook!
Time for some research.
Human physiology, human morality...
An unanimous vote.
Your usual topics.
Yeah, I guess so.
Wait. Am I really that predictable?
I'll have to change things up soon.
Let's get those books.
OST: Behind the Scenes
We're in the Palo Alto digital library project. Looks like a quite complicated network.
The assignment:
- Books are stored in the host corresponding to the first digit of their call number, while a book's file ID is 200 plus the last two digits of the call number. For example, book 512 would be stored in the host 500-599 as file 212.
- Duplicate each of the books requested by EMBER-2 and bring them back to your host.
- The call numbers for the books EMBER-2 wants are available in the file 300.
- Note that EMBER-2 will never request more than one book from the same host.
Alright, let's nose around a bit first. File 300 contains Ember's shopping list. To my knowledge there's no way to reach that EXA holding a file on the far left.
As for the books, here's all of them. Did you know you can use that little button with the right arrow in a file or EXA's title bar to pop it out of the left column so you can drag and drop it anywhere?
I feel some of these books might not be very trustworthy, but if Ember wants them, we'll get them for her I guess. Since you can't steal from a library (that would be evil!) I'll need to copy the books.
First of all, getting to them.
This icon seems to indicate that to get deeper into the network, I always need to use LINK 800, and to get back home it's -1.
My first attempt to get to the files looks like this:
Code: Select all
GRAB 300
MARK START
COPY F X
REPL GO
JUMP START
MARK GO
LINK 800
DIVI X 100 T
MARK GETTHERE
LINK 800
SUBI T 1 T
TJMP GETTHERE
Code: Select all
REPL WRITER
MODI X 100 X
ADDI X 200 X
GRAB X
; DO STUFF
MARK WRITER
; DO OTHER STUFF
Since we're going to need communications between EXAs and since a lot of them will be writing in parallel, I think it's a good idea to have my single initial EXA (from which the others are replicated) start in local communication mode and just keep each WRITER in the same host as its corresponding reader.
With the writer and reader code in place, the replicated EXAs start copying their files. The READER will send a 0 once it reaches EOF. After that, it conveniently self-destructs when it tries to run the MAKE instruction while already holding a file. Using 0 as EOF indicator means the WRITER can simply use the T register as an intermediate, since ALL non-0 values are considered true for the purpose of a TJMP, including keywords.
All that's left is getting our EXAs back home. That can be done in a very simple way:
Code: Select all
GRAB 300
MARK START
COPY F X
REPL GO
JUMP START
MARK GO
LINK 800
DIVI X 100 T
MARK GETTHERE
LINK 800
SUBI T 1 T
TJMP GETTHERE
REPL WRITER
MODI X 100 X
ADDI X 200 X
GRAB X
MARK READING
COPY F M
TEST EOF
FJMP READING
COPY 0 M
MARK WRITER
MAKE
COPY M T
MARK WRITING
COPY T F
COPY M T
TJMP WRITING
@REP 10
LINK -1
@END
By the way, now that I got the second zine, here's a full explanation of the @REP macro.
If you didn't know about the performance increase of unrolls, the zine also explains it here.
And this is my first working solution.
I had to compress this gif to not make it too huge. It's playing at double speed now.
290/38/74. Top percentile scores are 218, 26 and 10 respectively. Honestly, my solution isn't bad - I'm already below tenth percentile for cycles. But let's see how this can be improved.
A slow part of this solution is the check if the file is done every cycle. The files have a minimum size: 34. That means if we step into the check cycle after 33 steps we're fine.
I unrolled both loops to 33 direct COPYs - but it didn't quite fit. So I added some code so each does a 16 unroll twice. I set up a countdown in T for that, and had to remove some of the final LINK unroll because it didn't fit otherwise.
Code: Select all
GRAB 300
MARK START
COPY F X
REPL GO
JUMP START
MARK GO
LINK 800
DIVI X 100 T
MARK GETTHERE
LINK 800
SUBI T 1 T
TJMP GETTHERE
COPY 2 T
REPL WRITER
MODI X 100 X
ADDI X 200 X
GRAB X
MARK A
@REP 16
COPY F M
@END
SUBI T 1 T
TJMP A
COPY F M
MARK READING
COPY F M
TEST EOF
FJMP READING
COPY 0 M
MARK WRITER
MAKE
MARK B
@REP 16
COPY M F
@END
SUBI T 1 T
TJMP B
COPY M F
COPY M T
MARK WRITING
COPY T F
COPY M T
TJMP WRITING
MARK END
@REP 4
LINK -1
@END
JUMP END
The next thing I thought of was removing the test from one of the loops entirely. The result was not an improvement but I'll share it anyway in case it inspires someone.
Code: Select all
GRAB 300
MARK START
COPY F X
REPL GO
JUMP START
MARK GO
LINK 800
DIVI X 100 T
MARK GETTHERE
LINK 800
SUBI T 1 T
TJMP GETTHERE
REPL WRITER
MODI X 100 X
ADDI X 200 X
GRAB X
@REP 33
COPY F M
@END
MARK READING
COPY F M
TEST EOF
FJMP READING
DROP
KILL
COPY 399 X
MARK GRAB
ADDI 1 X X
TEST X = 410
TJMP READING
REPL GRAB
GRAB X
JUMP END
MARK WRITER
MAKE
MARK B
COPY M F
JUMP B
MARK END
@REP 5
LINK -1
@END
JUMP END
I didn't get a better score than 232 cycles, so I'll leave the further improvements to the thread.
As for reducing the size, the trick is that file IDs are always globally unique. I don't need to keep track of where to go if I just simply try grabbing the correct file in every host. Don't forget merging the COPY from 300 with the first MODI, which is now possible since we don't need the hundreds digit for anything anymore.
Code: Select all
GRAB 300
MARK START
MODI F 100 X
ADDI X 200 X
REPL GETTHERE
JUMP START
MARK GETTHERE
LINK 800
REPL GETTHERE
GRAB X
REPL WRITER
MARK READING
COPY F M
TEST EOF
FJMP READING
COPY 0 M
MARK WRITER
MAKE
COPY M T
MARK WRITING
COPY T F
COPY M T
TJMP WRITING
MARK END
LINK -1
JUMP END
Finally, activity. It's possible to get that all the way down to 10. Can you see how?
To get to 10 activity I can't LINK back even once. I need to send one EXA out that communicates the data back via global M. That'll be a single file at a time so it will be slow. Also, I need to grab the files in order starting from the lowest, otherwise I'd need to track back. In other words - I'm going to need to apply some kind of sorting to file 300.
It is very annoying to sort a file with EXAs. With only two registers per EXA, one which is overwritten whenever you test which value is greater, and with the fact that there's only an overwrite instruction, not an insert one, I'm not even sure if it can be done with a single EXA.
Luckily we don't need to actually store the sorted file. All we need is an EXA that outputs the values of file 300 in order from low to high. And, after that, since we can't use KILL instructions, some smart handling of EXAs so that they don't get in each others way.
I came up with a solution that's not that easy to understand, so I'll go through it bit by bit. It starts with three EXAs. XA sorts file 300. XB writes the new files. And XC goes visit the library. Here's the top part of XA:
Code: Select all
;XA LOCAL
GRAB 300
MARK NEXTFILE
COPY F X
MARK FINDLOWEST
TEST EOF
TJMP FOUND
TEST F < X
FJMP FINDLOWEST
SEEK -1
COPY F X
JUMP FINDLOWEST
MARK FOUND
After this, XA has some logic to send the file to the XC, my reader EXA. I'll skip that for the moment. To complete the sorting algorithm, after doing so (and while XC is busy copying a file), XA executes this:
Code: Select all
SEEK -9999
MARK FINDCURRENT
TEST F = X
FJMP FINDCURRENT
SEEK -1
VOID F
SEEK -9999
TEST EOF
FJMP NEXTFILE
Let's look at XC next.
Code: Select all
;XC GLOBAL
LINK 800
MARK LOOP
MODI M 100 X ;GB
DIVI 0 X T ; DIE ON 0
ADDI X 200 X
MODE
MARK NEXTHOST
LINK 800
REPL FIND
NOOP
TEST MRD ;LD
FJMP NEXTHOST
VOID M ;LD
VOID M ;LF
MODE
JUMP LOOP
MARK FIND
GRAB X
COPY 0 M ;LD
MODE
MARK READLOOP
COPY F M ;GE
TEST EOF
FJMP READLOOP
COPY 0 M ;GE EOF
MODE
COPY 0 M ;LF
It starts by getting a file ID from M, and doing the same logic as before to get the actual filename. There's a DIVI inserted so the EXA can quickly die when the program is done.
Otherwise it LINKs to the next host (this is always safe, since it starts in the gateway, and Ember never needs two files in the same host), and makes a replicated EXA (FIND to see if the file is there. If it is, the replica immediately communicates over local M (message LD), which causes the MRD in the original XC EXA to be true. In that case it has to VOID LD to allow the replicated FIND EXA to continue, otherwise it jumps to NEXTHOST and tries again. If the file is found, the original has another VOID which won't be triggered for a while.
The bottom half switches back to GLOBAL mode, and starts sending the file's content, ending with a 0 (GF). Then it changes mode back to LOCAL, sends message LF, which tells the original XC it should continue. At that point it switches back to GLOBAL mode and waits for the next file ID.
This whole switcharoo is necessary because, for example, if you leave XC in GLOBAL mode, it will keep stealing file data that's supposed to go to XB.
Speaking of which, it's time to look at the whole thing and see how XB fits into the picture.
XB starts by sending a message over LOCAL M (LA). This is basically it waiting for someone to read it. XA is the one to do so, directly after the MARK FOUND.
At that point, XA quickly switches to GLOBAL mode, sends the file ID to XC (GB, which we already saw from XC's point of view), then switches back and tells XB to get ready to receive data.
This order is important - if XA informed XB before XC, XB might already be in global mode, stealing the file ID that XC needs. Anyway, XB switches to GLOBAL mode, and starts writing the data to a new file. Once it gets a zero (tested by using the T registry as intermediate and with FJMP ENDFILE, it drops the file, switches to LOCAL mode, and tells XA it's ready to start receiving the next file. By that time XA has already found the next lowest value, so the complicated game of telephone repeats.
Once XA runs out of file IDs to send, it executes its bottom two lines: placing the special value 0 in X and then jumping to FOUND, which holds the communication protocol. It sends the 0 to both XB and XC, which will both die because of this. XA then dies itself with the DIVI 1 X T. And with that, all files are written.
I probably could have done without XB, but that would mean XA repeatedly dropping its file to make another one and then pick it up again.
The result is 1200/70/10. One file after another makes it slow but that's the only way to get the activity all the way down.
Processing.
Yes, this is what I wanted.
And with that, we're at the first thread vote for today.
I've been analyzing television and radio broadcasts.
And I have a few questions.
First of all, why do some things become popular while others don't?
And the second vote.
=== Trash World Inbox ===
I ended the last update with top scores of 232, 26, and 10. Let's see what you came up with.
Nice and simple size improvement. All it does different is skip the EOF check, and line up the cycles so the writer can do an MRD check instead. The ADDI F 2000 X; SWIZ X 421 X trick doesn't save any space but is a neat use of SWIZ. Since some people commented they didn't understand SWIZ very well, I'll try to explain what this does.silentsnack wrote:289/24/87Code: Select all
;M=LOCAL GRAB 300 MARK CLONE ADDI F 2000 X REPL TRAVERSE JUMP CLONE MARK TRAVERSE LINK 800 REPL TRAVERSE SWIZ X 421 X GRAB X REPL WRITER MARK READ COPY F M JUMP READ MARK WRITER MAKE MARK W_LOOP COPY M F NOOP TEST MRD TJMP W_LOOP MARK HOME LINK -1 JUMP HOME
So, imagine you start with a file ID 512. According to the assignment, the GRAB instruction needs 212 (200 plus the last two digits). The 5 tells us what host it's in but because we try every host, we don't strictly need it.
First we add 2000 to 512 with the ADDI. That's 2512.
Next, we do SWIZ 2512 421. It looks more complex than it is. The second operand, 421, just says "take the fourth digit from the right, then the second, and then the first", so [2]512, 25[1]2, 251[2], forming 212, which is the actual number we need.
Nice. It starts with a @REP for reading the file list. Sometimes there's less files to grab in which case the original one just dies once it reaches the end of the list. Otherwise it wipes that file and goes handle the last entry itself.silentsnack wrote:188/72/87Code: Select all
;M=LOCAL GRAB 300 @REP 4 ADDI F 2000 T REPL SKIP @END ADDI F 2000 T WIPE MARK SKIP LINK 800 MARK TRAVERSE LINK 800 REPL TRAVERSE SWIZ T 421 T GRAB T REPL WRITER MARK READ @REP 18 COPY F M @END JUMP READ MARK TIMER SUBI T 1 T TJMP TIMER COPY 0 M HALT MARK WRITER MAKE COPY M F; [[COPY-- COPY 15 T MARK WRITE_BATCH COPY M F; --TO-- SUBI T 1 T COPY M F; --FILE-- TJMP WRITE_BATCH COPY M F; --EVERY-- COPY 34 T COPY M F; --OTHER-- REPL TIMER COPY M T; --CYCLE]] MARK WRITE_END COPY T F COPY M T TJMP WRITE_END @REP 10 LINK -1 @END
Then it uses a REPL setup to just send a copy of itself to every node to go look for the file - that's faster than checking if you're in the right host. There doesn't seem to be a big problem with the hosts getting too crowded because the EXAs die immediately if they can't find the file and don't all start moving in the same cycle. If it finds the file it makes a WRITER EXA and starts copying. The writer starts with a countdown from 15 which runs every 2 writes. This is all done in the spare cycles between the writes. After the internal timer runs out, it then starts doing a somewhat slower write where it checks if a separate TIMER EXA is done each write. This is necessary because without it it would block endlessly once the reader is done. The TIMER runs for a set time which corresponds to the longest file in the test set. That seems slow, but remember, only the slowest test counts so this works out very well.
By the way, you can drop it to 185 cycles by moving over the SWIZ to directly below MARK SKIP.
Ah, an alternative low activity solution. I think Quackles' explanation speaks for itself. The code is quite readable, which is in part because the low activity solution forces you to read the books one by one.Quackles wrote:For the activity-10 version, I had the reader EXA send back the shelf ID it was in, plus 100. Then the control EXA in my host finds the first number in its list that is less than that number, and sends it to mean 'grab that book'. So it's a granular sort that sorts in buckets, even if the process of scanning the list is a bit inefficient each time the next book needs to be queued.
The control EXA also tells the reader EXA to go to the next shelf if it doesn't find anything.
Reader EXA:Controller EXA:Code: Select all
COPY 100 X ;ID of shelf AFTER this one LINK 800 COPY 1 M ;sync MARK NEWSHELF ADDI X 100 X LINK 800 COPY X M ;send shelf ID COPY M T FJMP NEWSHELF ;0 = next shelf, other number = book to grab GRAB T MARK READOUT COPY F M TEST EOF FJMP READOUT DROP COPY -9999 M JUMP NEWSHELF ;exploiting assumption: only one book per shelf is ever needed
1304/54/10.Code: Select all
VOID M ;sync MARK NEXTBOOK GRAB 300 MARK NEXTBOOK2 NOOP TEST MRD ;wait for shelf ID - no response = reader vanished, give up FJMP GAMEOVER COPY M X ;X has shelf ID after this one MARK READBOOKLIST TEST EOF TJMP NOTFOUND TEST F < X ;find any number that's on this shelf TJMP COPYBOOK JUMP READBOOKLIST MARK NOTFOUND COPY 0 M ;next shelf SEEK -9999 JUMP NEXTBOOK2 MARK COPYBOOK SEEK -1 SWIZ F 0021 X ;generate book ID, send it ADDI 200 X X COPY X M SEEK -1 VOID F DROP MAKE MARK COPYLOOP ;copy book then go back to controlling the reader COPY M X TEST X = -9999 COPY X F FJMP COPYLOOP SEEK -1 VOID F DROP JUMP NEXTBOOK MARK GAMEOVER
Finally, silentsnack has an update to the Mitzuzen HDI-10 Heart level (update 15 of this LP).
I don't even know what to say about this other than I never thought the count could get this low.silentsnack wrote:and as usual some completely unrelated random thing reminded me go back and take another look at the Phage/Heart solution, because it kinda bugged me to have 3 separate control structures redundantly running independent countdowns of the same number. And it turns out the suspicion that there should be a better way was correct, it just required approaching the timer from a different angle. Previous best time was 62/50/45.Combined with splitting off the special-case'd first generation into a separate EXA to save a REPL operation brings it down to 56/45/42.Code: Select all
;XA LINK 800 MARK LOOP DIVI #NERV -10 X SUBI X 3 T FJMP SKIP MARK WAIT SUBI T 1 T TJMP WAIT MARK SKIP REPL LOOP REPL B LINK 1 LINK 1 KILL COPY 40 #NERV COPY -70 #NERV COPY -70 #NERV JUMP BEAT MARK B LINK 3 LINK 3 KILL COPY -70 #NERV COPY 40 #NERV MARK BEAT COPY -70 #NERV JUMP BEAT ;XB NOOP NOOP LINK 800 REPL B LINK 1 LINK 1 COPY 40 #NERV COPY -70 #NERV COPY -70 #NERV JUMP BEAT MARK B LINK 3 LINK 3 COPY -70 #NERV COPY 40 #NERV MARK BEAT COPY -70 #NERV JUMP BEAT
=== TEC EXA-Blaster™ Modem ===
Processing.
Yes, this is what I wanted.
Two votes for the first option, one for the second.
Glad it's helping.
I love ingesting a good corpus...
Not that that's all I'm doing.
I read the same way any normal human reads.
By analyzing patterns of word recurrence and deriving correlations between concepts implicit in statistically significant word clusters.
Excuse me?
lmao
Anyway, speaking of hungry, the zine has some new recipes for y'all to try.
Mmm, sounds like another 5 star experience.
For the new assignment, we have to influence the media.
I've been analyzing television and radio broadcasts.
And I have a few questions.
First of all, why do some things become popular while others don't?
One vote for "Good work finds its fans", but two for the one about big media.
Big media conglomerates push some artists and abandon others.
So it comes down to corporate agendas?
We should test this.
Think we could pick a random pop song and make it a hit?
Okay, let's.
OST: Code and Registers
The assignment:
- Connect to each radio station and replace every song in the playlist (file 200) with CAN'T (NOT) GET OVER YOU by ME2U (file 300). Each song in a playlist consists of two keywords: the song name and the artist name.
- A list of phone numbers for the radio stations is available in file 301.
- Note that the EXAs in global mode can only communicate if there is a path of links connecting them.
- For more information see "Hacker Skills: Modem Control at the Direct Level" in the second issue of the zine.
Manually operating a modem? It's been a while.
If I send the 11-digit number to the #DIAL register, the modem will make a connection. Sending -1 will make the modem hang up and reset, and you can dial again.
That one note in the assignment is important though. You can't communicate between EXAs if they're in completely separate networks. If an EXA needs to send a message from a radio host to elsewhere you better keep the link open.
There's one more thing: Even though the modem is ours, leaving a file in the modem host (the one with the #DIAL register) will fail the assignment because for some reason that counts as leaving a trace.
With a max of 100 lines we have plenty of space to play around. Let's start with the dialer.
Code: Select all
GRAB 301
LINK 800
MARK DIAL
@REP 11
COPY F #DIAL
@END
COPY -1 #DIAL
TEST EOF
FJMP DIAL
WIPE
The song is exactly two values (name and artist), so theoretically that should fit in the registers. Let's create a second EXA and give that a try.
Code: Select all
;XB
GRAB 300
COPY F T
COPY F X
WIPE
LINK 800
MARK TRY
REPL TRY
LINK 800
GRAB 200
Now I have to overwrite each song in the playlist with Ember's song. But I shouldn't add anything to the playlists, so I need to keep track of the length. That is going to require a register. We need a trick.
Code: Select all
;XB continued
MARK WRITE
COPY T F
COPY X F
TEST EOF
FJMP WRITE
Finally, the EXA SEEKs to the start of the file, grabs the song title, and writes it over the 0s in another loop.
The only thing that's left are copies of XB still replicating in the modem host. That's easily solved by just adding a couple of KILL instructions at the end of XA.
Not the cleanest solution, but it works.
Top percentile scores are 129, 32 and 9. Let's get closer to them.
The first thing I can do is unroll the loop of the file writer, making use of the fact that a playlist never has less than 6 songs. This immediately drops the cycle count from 185 to 159, with 55 cycles and 15 activity. For some reason, changing the speed of the file writer means XA needs more KILL instructions at the end.
I can use another trick to get it down a bit more to 154 cycles.
Code: Select all
GRAB 300
COPY F X
COPY F T
WIPE
LINK 800
MARK TRY
REPL TRY
LINK 800
GRAB 200
@REP 5
COPY X F
COPY T F
@END
MARK WRITE
COPY X F
COPY T F
TEST EOF
FJMP WRITE
SEEK -9999
SEEK 1
COPY F X
SEEK 10
MARK WRITE2
TEST F > 0
COPY X F
JUMP WRITE2
By the way, just to clean up the code a bit, putting a NOOP after the MARK TRY is just as fast, but XA only needs a single KILL at the end now.
Of course, since the WRITE2 loop has no conditional logic anymore, it is trivial to unroll.
Code: Select all
@REP 3
TEST F > 0
COPY X F
@END
And since I have plenty of spare lines, the dialing sequence can be unrolled as well. It's manual, though, since you can't nest @REPs.
138/90/11
Conveniently, I can fit 4 dial actions. That way, the EOF test lines up perfectly after the second iteration of the outer loop.
And that's the best I can come up with... or so I thought at first. I then realized I can make use of the fact the slowest case needs three repeats at the end of XB.
Code: Select all
;XB second half
REPL SEND
MARK WRITE
COPY X F
COPY T F
TEST EOF
FJMP WRITE
SEEK -5
COPY M F
SEEK 1
COPY M F
SEEK 1
COPY M F
HALT
MARK SEND
@REP 3
COPY T M
@END
No need to do any tests in that last loop, all I need to do is seek back 3 songs from the end and run it exactly three times. Worst case, I overwrite some artist names twice with the same value. To prevent the need to SEEK back to the start to get the artist name, I just have a REPL hold it and send it to me.
But there's more. If I know a playlist is at least 6 songs and at most 9, and I can overwrite data, I don't need an EOF test at all.
Write six values from the start, 3 from the end. Worst case there's a bit of overlap. 122/85/11, several cycles under par.
The low activity score is trickier than it seems.
It's easy to get it down to 10: just merge XA and XB so you only LINK from home to the modem once. To get it down to 9 you need to remove the KILL for the ever-replicating original XB, though. The problem is you don't have much space for testing anything in XB, because you need both registers to hold the song info.
I came up with a solution where XB starts with only the song title. XA sends the artist name over M twice, every time it dials a number. It sends a zero when it's done. XB waits for M, and as soon as it sees something it first tests if it's zero (in which case it stops). If it's not zero, it reads M again to store the result in T - which can now safely be done because the test is complete. Other than that it works the same as before.
Code: Select all
GRAB 300
COPY F X
COPY F T
DROP
; ORIGINAL XA
GRAB 301
LINK 800
REPL TRY
COPY T X
MARK DIAL
@REP 11
COPY F #DIAL
@END
COPY X M
COPY X M
COPY -1 #DIAL
TEST EOF
FJMP DIAL
WIPE
COPY 0 M
HALT
; ORIGINAL XB
MARK TRY
NOOP
TEST M = 0
TJMP END
REPL TRY
COPY M T
LINK 800
GRAB 200
@REP 6
COPY X F
COPY T F
@END
SEEK 9999
SEEK -6
@REP 3
COPY X F
COPY T F
@END
MARK END
Since I spent a lot of time getting the cycles down to 122, I'll leave the low size optimization to the threads.
That was surprisingly easy.
People are requesting the single, humming it to themselves, buying the album...
Just because they've heard it before.
Is that really all it takes?
The first thread vote. And for next time...
Theory: The inconsistency of ratings depends on context.
We made a hit by picking a generic pop song and artificially boosting its exposure.
But there are other domains where this isn't possible.
For example, you couldn't change people's behavior by giving a Last Stop store a five-star rating in a restaurant guide.
That would be ridiculous.
=== Trash World Inbox ===
A low size solution. Quite straightforward - while the 'main' EXA is dialing, a payload EXA goes back to read the payload file to X and T. It then waits for a message from the dialing EXA, and LINKs to the radio host when it gets it. There it overwrites the playlist. COPY F F is a way to check if you're at the end of the file yet. If not, it takes the value at the F cursor and writes it to the next location. Doesn't matter since it gets overwritten anyway. I didn't know COPY F F was legal, since you can't do something like ADDI F F X. But it is, and it's necessary here because X and T are already occupied.silentsnack wrote:367/28/26Code: Select all
GRAB 301 LINK 800 MARK LOOP REPL PAYLOAD COPY 11 T MARK DIALER COPY F #DIAL SUBI T 1 T TJMP DIALER COPY 800 M TEST EOF COPY -1 #DIAL FJMP LOOP MARK PAYLOAD LINK -1 GRAB 300 COPY F X COPY F T DROP LINK 800 LINK M GRAB 200 MARK DATA SEEK -2 COPY X F COPY T F COPY F F JUMP DATA
=== StreetSmarts GIS Database ===
That was surprisingly easy.
People are requesting the single, humming it to themselves, buying the album...
Just because they've heard it before.
Is that really all it takes?
Two votes for "more complex", but 3 for this one.
Mass media is a sham.
It's seeming that way.
Hmm.
Girl you need to get a cluuuue...
I just can't not get over yoooou!
Oops. I guess it is pretty catchy.
I didn't know AIs could get an earworm.
Next up, hacking a GIS database.
Theory: The inconsistency of ratings depends on context.
We made a hit by picking a generic pop song and artificially boosting its exposure.
But there are other domains where this isn't possible.
For example, you couldn't change people's behavior by giving a Last Stop store a five-star rating in a restaurant guide.
That would be ridiculous.
All votes but one for "You never know".
You never know...
This is why we need to experiment.
OST: Network Exploration
The assignment:
- Each host contains a list of restaurants and their ratings, from one to five stars (file 200). Locate the entry that corresponds to the Last Stop on Eddy Street and change its rating from one to five stars.
- The name of the target restaurant and its location within the GIS grid is available in file 300. The first coordinate is the number of times to move east, while the second coordinate is the number of times to move north (positive) or south (negative).
- For more information see "Network Exploration: Geographic Information Systems" in the second issue of the zine.
Let's see what the zine has to teach us.
We're dealing with a StreetSmarts system here. I'm just wondering what that would look like for any city that isn't grid based.
Also, since GIS is short for Geographic Information System, it's pronounced GIS, not GIS.
As you can see there's one or two restaurants in each block. There's also a compass needle to conveniently show us which way is which. East (the first value in file 300) is to the top right, while north is to the top left.
Let's start building.
I think it'd be convenient to have one EXA stay home to just transmit the coordinates:
Code: Select all
;XA
GRAB 300
COPY F X
COPY F M
COPY F M
COPY X M
The 'walking' EXA should first go the right "east" position (can I call it longitude?):
Code: Select all
;XB
LINK 800
COPY M T
FJMP NS
MARK EAST
LINK 801
SUBI T 1 T
TJMP EAST
MARK NS
The north-south (latitude) code has to deal with negative values, so it's more complicated:
Code: Select all
MARK NS
COPY M X
TEST X < 0
TJMP GOSOUTH
COPY X T
FJMP FOUND
MARK NORTH
LINK 800
SUBI T 1 T
TJMP NORTH
JUMP FOUND
MARK GOSOUTH
COPY X T
MARK SOUTH
LINK 802
ADDI T 1 T
TJMP SOUTH
MARK FOUND
Finally, grab the file, and find the right restaurant. Since any restaurant has at least 1 star, I can copy the first star and write that to the file four times.
This initial solution runs at 48/40/6. The top percentiles are 26, 25 and 6 respectively.
Activity is already optimized at 6. That makes sense, because 6 is how many steps it takes to get to the far corners of the grid, so that'll correspond to the highest-activity test case. Time to improve the other metrics.
First of all, we can speed up the search within the file. Since there's never more than 2 restaurants in a file, the following solution works:
Code: Select all
MARK FINDINFILE
TEST F = X
TJMP COPY
SEEK 6
MARK COPY
COPY F X
@REP 4
COPY X F
@END
For the next improvement, I considered that specifically going for the correct grid space is a bit slow with all the tests if the EXA is there yet. Since the restaurant we're looking for exists in only one place, I just can have EXA REPLs check everywhere.
It's a bit fiddly to have the EXAs only go where they need to be and not visit nodes several times.
Code: Select all
;XA
GRAB 300
COPY F M
;XB
LINK 800
COPY M X
REPL SOUTH
REPL NORTH
REPL EAST
JUMP GRABFILE
MARK SOUTH
LINK 802
REPL EAST
REPL GRABFILE
LINK 802
REPL EAST
JUMP GRABFILE
MARK NORTH
LINK 800
REPL EAST
REPL GRABFILE
LINK 800
REPL EAST
JUMP GRABFILE
MARK EAST
@REP 2
LINK 801
REPL GRABFILE
@END
LINK 801
MARK GRABFILE
GRAB 200
TEST F = X
TJMP COPY
SEEK 5
TEST F = X
DIVI T T X ;DIE
MARK COPY
COPY F X
@REP 4
COPY X F
@END
The south- and north-bound EXAs each link in their directions twice, leaving eastbound and GRABFILE EXAs behind. This is all written out instead of using @REP so I have manual control over when to use REPL and when a JUMP suffices, to prevent creating extra EXAs.
The eastbound EXAs link in their directions three times, leaving GRABFILE instances behind so the entire grid is covered.
Finally, in the GRABFILE code, after SEEKing for the second restaurant entry, it has to test if that's the correct one. If there's only one entry, the TEST makes this EXA die, and if it's the wrong one, the DIVI by zero will do so.
27/41/20.
Notice that I always do the REPL to the moving EXAs first, and that I start with SOUTH and NORTH before EAST. Moving to the back of the grid takes the longest so it's important to optimize it as much as possible. I also tried making the eastbound EXA spawn columns of south/north-bound ones but that was slightly slower.
There's one more cycle to be saved here, though.
Code: Select all
MARK SOUTH
LINK 802
REPL EAST
LINK 802
REPL EAST
REPL GRABFILE
LINK 800
JUMP GRABFILE
MARK NORTH
LINK 800
REPL EAST
LINK 800
REPL EAST
REPL GRABFILE
LINK 802
JUMP GRABFILE
I tried more tweaking with the movement order but I couldn't get it better than this.
Finally, for size, the most straightforward trick is to just use loops instead of unrolls. Remove all speed optimizations in favour of simpler code:
Code: Select all
;XA
GRAB 300
COPY F M
;XB
LINK 800
COPY M X
REPL SOUTH
REPL NORTH
REPL EAST
JUMP GRABFILE
MARK SOUTH
LINK 802
REPL EAST
REPL GRABFILE
JUMP SOUTH
MARK NORTH
LINK 800
REPL EAST
REPL GRABFILE
JUMP NORTH
MARK EAST
LINK 801
REPL GRABFILE
JUMP EAST
MARK GRABFILE
GRAB 200
MARK FIND
TEST F = X
FJMP FIND
COPY F X
@REP 4
COPY X F
@END
For the next improvement, we need to reuse lines. This 29-line solution does so:
Code: Select all
;XA
GRAB 300
COPY F M
;XB
LINK 800
COPY M X
JUMP START
MARK EAST
LINK 801
MARK START
REPL EAST
REPL NORTH
REPL GRABFILE
MARK SOUTH
LINK 802
REPL GRABFILE
JUMP SOUTH
MARK NORTH
LINK 800
REPL GRABFILE
JUMP NORTH
MARK GRABFILE
GRAB 200
MARK FIND
TEST F = X
FJMP FIND
COPY F X
@REP 4
COPY X F
@END
Finally, I can combine the southbound and northbound loops by putting the LINK ids in a register:
Code: Select all
COPY 800 T
REPL NORTHSOUTH
REPL GRABFILE
COPY 802 T
MARK NORTHSOUTH
LINK T
REPL GRABFILE
JUMP NORTHSOUTH
Oh.
Apparently, that Last Stop location is getting mobbed.
Processing.
People are choosing to believe the guide even when it's obviously wrong...
Why would they think Last Stop has actual good food?
And why did the guide change their behavior, but not the highway sign?
Here is the first vote.
Before I finish for today, do you remember when I posted that story from the first zine, way back in episode 7? It was a couple of pages long.
The second zine contains the last half of the story, here it is.
Click the images for higher resolution.
Next time, we'll have another hacker battle.
Time for the next opponent.
Are you ready?
Oops, I had already completed that part of the episode when I read your votes. They didn't change the end result so at least I could edit them in easily enough.Cloudmonkey98 wrote: ↑Sat Apr 16, 2022 6:19 pmWild stuff
Uhhh "I'm sure the reality is more complex" And.... "Probably not"
I try my best.Thank you again for sharing this game and providing interesting commentary on your work
=== Trash World Inbox ===
Ah, I was wondering if something like this was possible but didn't quite figure it out. The trick is to start LINKing from 800 and then use a combination of REPL and increments of the LINK id to go in every direction. But if you get that wrong the EXAs will keep looping forever.GuavaMoment wrote:25 lines? How about 18.
Code: Select all
GRAB 300 COPY F X WIPE COPY 800 T MARK MAIN LINK T REPL MAIN GRAB 200 ADDI T 1 T REPL MAIN MARK TEST TEST F = X FJMP TEST COPY F X COPY X F COPY X F COPY X F COPY X F
GuavaMoment also shared this 24 cycle solution:
Those RIGHT2 and RIGHT1 jumps where you end up further down the code if you need to do fewer LINKs are a quite neat solution.GuavaMoment wrote:The farthest nodes need directed EXAs as soon as possible, so I create the farthest east EXA first, then furthest south, then north. All the outer nodes get checked first (cycles 11 and 12), and 10 central nodes all get checked on cycle 13. I know faster times are possible but I just cannot figure it out. I don't know if I can fill the grid faster, and I don't know actually using the coordinates of the correct Eddy's could help.Code: Select all
GRAB 300 COPY F X LINK 800 MARK MAIN REPL TOP3 REPL TOP2 REPL TOP1 REPL RIGHT2 REPL RIGHT1 REPL LEFT2 REPL LEFT1 WIPE JUMP EDDY MARK TOP3 LINK 801 MARK TOP2 LINK 801 MARK TOP1 LINK 801 REPL RIGHT2 REPL LEFT2 REPL RIGHT1 REPL LEFT1 JUMP EDDY MARK RIGHT2 LINK 802 MARK RIGHT1 LINK 802 JUMP EDDY MARK LEFT2 LINK 800 MARK LEFT1 LINK 800 MARK EDDY GRAB 200 TEST F = X TJMP STARS SEEK 5 TEST F = X FJMP EDDY ; this kills the exa when it's in the wrong node MARK STARS COPY F X COPY X F COPY X F COPY X F COPY X F
Next, both silentsnack and GuavaMoment posted 23-cycle solutions. They are similar in the sense that they each use two EXAs, one to handle the back rows and one to handle the front rows. That's apparently very slightly faster than using a single EXA.
For the sake of brevity I'll only post silentsnack's solution here but I want to mention that GuavaMoment's solution has an EXA priority quirk, where depending on what EXA's code you paste in first, it runs at either 23 or 26 cycles.
Very optimized, but also quite hard to read with the many REPLs.silentsnack wrote:23/73/25Code: Select all
;XA GRAB 300 COPY F M SEEK -1 COPY F X LINK 800 REPL NORTH;B20-B30 LINK 801 MARK NORTH;B21-B31 REPL SOUTH LINK 800 REPL HERE WIPE;CRASH MARK SOUTH;B10,B11 REPL HERE LINK 802 MARK HERE GRAB 200 TEST F = X TJMP YES SEEK 5 TEST F = X DIVI T T T MARK YES COPY F X @REP 4 COPY X F @END ;XB LINK 800 LINK 801 COPY M X REPL NORTH LINK 801 REPL N1;B22-B32 LINK 801 MARK N1;B23-B33 REPL S1 REPL HERE LINK 800 JUMP HERE MARK S1;B12,B13 LINK 802 JUMP HERE MARK NORTH;B41-B43 REPL SOUTH LINK 800 LINK 800 REPL EAST MARK WEST;B00,B40 REPL HERE LINK 803 JUMP HERE MARK SOUTH;B01-B03 LINK 802 LINK 802 REPL WEST MARK EAST LINK 801 REPL HERE LINK 801 MARK HERE GRAB 200 TEST F = X TJMP YES SEEK 5 TEST F = X DIVI T T T MARK YES COPY F X @REP 4 COPY X F @END
Finally, silentsnack has a 22-cycle solution which sadly doesn't fit into the size limit.
Without going into the nitty gritty, the easiest way to understand this is by running it and seeing which EXA grabs which file.silentsnack wrote:22 cycles, 125 lines (so it doesn't count for statistics)Code: Select all
;XA GRAB 300 COPY F X REPL WEST_X COPY X M MARK SEND COPY X M COPY X M HALT MARK NORTHWEST;B30-B40 LINK 800 REPL HERE LINK 800 JUMP HERE MARK WEST_X;B20-B21 REPL SEND LINK 800 REPL NORTHWEST REPL SOUTHWEST REPL HERE LINK 801 JUMP HERE MARK SOUTHWEST;B10-B00 LINK 802 REPL HERE LINK 802 MARK HERE GRAB 200 TEST F = X TJMP YES SEEK 5 TEST F = X DIVI T T T MARK YES COPY F X @REP 4 COPY X F @END ;XB REPL SOUTH LINK 800 LINK 801 REPL MID_3 LINK 800 COPY M X ;B31-B41 REPL HERE LINK 800 MARK HERE GRAB 200 TEST F = X TJMP YES SEEK 5 TEST F = X DIVI T T T MARK YES COPY F X @REP 4 COPY X F @END HALT MARK MID_3;B22 NOOP NOOP LINK 801 COPY M X JUMP HERE MARK SOUTH;B11-B01 LINK 800 LINK 801 LINK 802 COPY M X REPL HERE LINK 802 JUMP HERE ;XC LINK 800 LINK 801 LINK 801 REPL NORTHEAST LINK 801 COPY M X REPL SOUTHEAST ;B33-B43 LINK 800 REPL HERE LINK 800 GRAB 200 TEST F = X TJMP YES SEEK 5 TEST F = X TJMP YES HALT MARK SOUTHEAST;B1X-B0X LINK 802 REPL HERE LINK 802 GRAB 200 TEST F = X TJMP YES SEEK 5 TEST F = X TJMP YES HALT MARK EAST_X;B23 LINK 801 JUMP HERE MARK NORTHEAST;B32-B42 COPY M X REPL EAST_X REPL SOUTHEAST LINK 800 REPL HERE LINK 800 MARK HERE GRAB 200 TEST F = X TJMP YES SEEK 5 TEST F = X DIVI T T T MARK YES COPY F X @REP 4 COPY X F @END
As you can see labour is now divided among 3 EXAs.
I didn't really try but I can imagine getting this under the 75 size limit is impossible. It's easy to make the code (a bit) smaller, but not without having it be slower as well.
=== Valhalla ===
Oh.
Apparently, that Last Stop location is getting mobbed.
Processing.
People are choosing to believe the guide even when it's obviously wrong...
Why would they think Last Stop has actual good food?
And why did the guide change their behavior, but not the highway sign?
Everyone seems to be agreeing that the signs were vague. Yeah, that's always the problem with astrology, isn't it?
The signs were vague.
Hmm...
I guess it's easier to care about food than an abstract problem.
Nothing more to say about that, Ember? Well, ok.
Next up, a hacker battle against =plastered.
Time for the next opponent.
Are you ready?
These unanimous votes make it easy for me to keep count.
What, you're my coach now?
Would that be a bad thing?
That extreme baseball book you found me was full of interesting information.
Go on, champ, get out there and win.
I think I just threw up into my mouth a little bit.
OST: Getting Started
It's much the same as before. I first have to beat =plastered before I can play against other people.
The assignment reads:
To win this battle you must control a majority of the hosts for as long as possible.
To take control of a host, write any value to its #CTRL register. Reading from a #CTRL register will tell if you (1) or your opponent (-1) controls the host.
- Gain one point every cycle you control more hosts than your opponent.
- Lose one point every time one of your EXAs executes a KILL instruction.
For more information see "Hacker Battle Domination" in the second issue of the zine.
There's a 10 EXA limit, a 100 size limit, and each battle runs for 100 cycles.
Take note of the layout of the grid. It's basically linear, but both me and the opponent connect into the one-but-last position on our sides.
If I just let it run without doing anything, =plastered sends a single EXA in that first goes to the right (the end of the line), activates that node, and then goes down the path, activating each node in turn. Once it reaches my end it turns back and repeats its pattern.
As usual, hacker battles are kinda trivial to win.
I can actually just replicate =plastered's code one to one, like so:
Code: Select all
LINK 800
LINK -1
MARK START
@REP 8
COPY 1 #CTRL
LINK 800
@END
@REP 8
COPY 1 #CTRL
LINK -1
@END
JUMP START
Since =plastered doesn't bother REPLicating anything, I can just take the single-point loss of executing a well-timed KILL and win every battle with about 90 points.
Code: Select all
;XA
NOOP
LINK 800
LINK -1
MARK START
@REP 8
COPY 1 #CTRL
LINK 800
@END
@REP 8
COPY 1 #CTRL
LINK -1
@END
JUMP START
;XB
@REP 6
LINK 800
@END
KILL
Since the enemy EXA is gone after XB has done its thing, I don't even need the loop anymore. XA could set every #CTRL register once and die, and the rest of the cycles I just rack up points for free.
If you don't want to use a KILL, another solution is to just REPL an EXA into every host and have them set #CTRL in a small loop. Because the enemy can only set a single host at once and I overwrite it the next cycle, it'll never hold a majority of the hosts.
Code: Select all
LINK 800
REPL SOUTH
MARK NEXT
REPL SET
LINK 800
JUMP NEXT
MARK SOUTH
LINK -1
MARK SET
COPY 1 #CTRL
JUMP SET
Do you like it? Winning?
And that brings us to our first vote already.
To be fair, any chump could've beaten you, =plastered.
Anyway, there's someone at the door. Sounds like my neighbor, Isadora.
To anyone keeping track, the game's opening cut scene took place on Saturday, October 4th, 1997. It's now Tuesday, November 4th, so exactly one month has passed in-game. The previous cut scene with Isadora was on October 8th.
Hey...
Sorry I haven't been in touch.
Things are a little crazy at work right now.
They promoted me to a new position, and... well there's just a lot going on.
I didn't notice this before but Isadora is actually wearing somewhat more formal clothes than in her previous cut scene. A nice touch.
Isadora offers me a plastic bag with something inside of it.
It's moderately heavy.
Maybe a couple books or something?
You like puzzle games, don't you?
I remember you being a fan.
I used to play games with my sister sometimes, but she moved to Japan a few years ago.
She sent me this game, but I guess I can't play it because of the region lock.
Looks fun, though.
I thought maybe you could get it working.
You always were good at that kind of thing.
Isadora sighs.
I have to go.
So much work to do... really hope this job doesn't eat me up.
Okay, bye for now.
Speaking of puzzles, the last page of the second zine has some nonograms, also known as Picross or paint-by-numbers puzzles. Here's the instructions and the first puzzle.
Since there's not much for the thread to do with these hacker battles, why not do one of these? If you want to share your result please use spoiler tags so other people get a chance to solve it for themselves too.
We'll check out Isadora's video game later. First things first - I can barely hold a controller right now since the Phage seems to be acting up in my left hand.
First it was your arm... now it's your hand.
And that brings us to our second vote.
=== Trash World Inbox ===
Thanks cardinale and biosterous for the nonogram submissions.
I'm not entirely sure what it depicts. It kinda looks like a disk in a cartridge, but it doesn't have the same shape as the Redshift disks. It might be for something we have yet to see.biosterous wrote:
=== Mitsuzen HDI-10 - Left hand ===
Do you like it? Winning?
All votes went to "Sure".
Sure.
Of course.
That's how human brains are designed, aren't they?
To like winning. So that you will always try to win.
It's a very simple design.
Any time I start to discredit myself for being too simple, I think about human brains and feel a little better.
Oof.
I mean, I don't know about clones, but the main problem with most conspiracy theories is that they assume a level of government competence we've never seen in real life, so I'll have to give hydro credit for that, at least.
Anyway, I need to take a break for a moment and first do something about my Phage infection.
First it was your arm... now it's your hand.
Three votes for "It's getting bad".
Yeah, it's getting bad.
I can see that.
Fortunately, it looks like you can easily access the nodes connected to your median nerve.
You'll just need to make a small incision in your ventral forearm.
Better watch out for that artery, though.
This is one of those choices where all options lead to the same dialogue.
Ugh.
Sounds like you're getting used to this.
That's good. It's nothing to be squeamish about.
OST: EXA Power
All right, here's what I need to do.
- There are three nerve signals that need to be relayed: muscle control (M), which runs from your central nervous system (CNS) to your hand (HND) and heat (H) and pressure (P), which run the other direction.
- For each signal, read a value from the input nerve and relay it to the output nerve. Repeat ad infinitum.
- It is not necessary to leave no trace. Your EXAs should be written to operate indefinitely.
- For more information see "Debugging the Phage" in the first issue of the zine.
Hmm. Looks like the main challenge is one of 'bandwidth'. Using the M register is usually fast, but you can only have one global signal per cycle. Having EXAs running back and forth is slower, but you can have multiple, with the limit being that you can only have one EXA traverse any specific LINK at one time.
I think I'll start with the M solution and try to start optimizing for low activity since that seems easy, if not very fast.
Now, sending messages from all three nerves in parallel is hard. Getting that synced up so the right EXA gets the right message.
So, what if we make it simpler and just... don't?
For the low activity we need to start with one EXA and REPL from there to have the minimum amount of LINKs.
Code: Select all
LINK 800
REPL RIGHT
; LEFT
COPY -3 X
LINK -3
LINK -3
MARK RIGHT
COPY 3 X
LINK X
LINK X
Next, the LEFT EXA starts sending and the RIGHT one starts receiving, exactly 14 signals.
Code: Select all
LINK 800
REPL RIGHT
; LEFT
COPY -3 X
LINK -3
LINK -3
MARK SEND
COPY 14 T
MARK SENDLP
COPY #NERV M
SUBI T 1 T
TJMP SENDLP
MARK RIGHT
COPY 3 X
LINK X
LINK X
MARK RECEIVE
COPY 14 T
MARK RECEIVELP
COPY M #NERV
SUBI T 1 T
TJMP RECEIVELP
After "completing" the M nerve, it's time to LINK to the next nerves. Importantly, the RIGHT EXA needs to switch to sending and the LEFT one to receiving. When 14 signals have been sent in that round, they need to move on once more, but then RIGHT needs to keep sending. So I need a contextual swap of some sort.
Here is the complete working program.
This is where the X register comes in use. Once either the SENDLP or RECEIVELP loops are done, the EXA LINK to the next host in its direction (as stored in X), and then uses the value of X to determine whether it should be sending or receiving. So, in summary:
- In the first iteration, LEFT starts out sending and RIGHT starts out receiving.
- In the second iteration, the above test is first run, which sets RIGHT to sending and LEFT to receiving.
- In the third iteration, the test is run again, keeping RIGHT to sending and LEFT to receiving.
- After that, if the test didn't cut off the program after getting all expected signals, the EXA would try to link to a non-existent host and die.
This leads to a strange solution where the nerve signals are handled nerve by nerve.
Results: 181/29/9. The top percentile scores stand at 37, 24, and 9 respectively.
I can make a small size improvement by moving some LINK instructions to inside the SEND and RECEIVE marks.
Code: Select all
LINK 800
REPL RIGHT
; LEFT
COPY -3 X
LINK X
MARK SEND
LINK X
COPY 14 T
MARK SENDLP
COPY #NERV M
SUBI T 1 T
TJMP SENDLP
TEST X = 3
TJMP SEND
MARK RECEIVE
LINK X
COPY 14 T
MARK RECEIVELP
COPY M #NERV
SUBI T 1 T
TJMP RECEIVELP
TEST X = -3
TJMP RECEIVE
JUMP SEND
MARK RIGHT
COPY 3 X
LINK X
JUMP RECEIVE
Let's look at cycle count instead.
My first idea involved files. I'm still using the "we only need 14 signals" hack, but this time, three EXAs read 14 signals each into a file, take that to the recipient nerve and write from the files to there. I unrolled as many loops as I could, although the fact that the different EXAs need to move in different ways makes it hard to keep track of their state.
Code: Select all
LINK 800
REPL LEFT
LINK 3
LINK 3
LINK 3
REPL H
LINK 3
MARK READ
MAKE
@REP 14
COPY #NERV F
@END
TJMP SKIP
LINK -3
LINK -3
MARK SKIP
@REP 6
LINK -3
@END
MARK WRITE
SEEK -9999
@REP 14
COPY F #NERV
@END
MARK H
COPY 1 T
JUMP READ
MARK LEFT
LINK -3
LINK -3
MAKE
COPY 2 T
MARK READ2
@REP 7
COPY #NERV F
@END
SUBI T 1 T
TJMP READ2
@REP 4
LINK 3
@END
JUMP WRITE
After the EXAs are done they fall through into some other code that tries to do things, but it doesn't matter, by then the tests have finished.
Runs at 47/72/24, which is still 10 whole cycles away from the top percentile. Most of it wasted on walking back and forth. I think I can't get any better without involving the M register.
At this point I realized that while syncing the M register is hard with three pairs of EXAs, it's surprisingly easy with two. Since an M communication step always takes two cycles (for the first EXA to publish it and then for the second to read it), using two pairs you can send one message each cycle, as long as you make sure to alternate every cycle.
So, what if we change two of the nerves to make use of M, while keeping the 'file transfer protocol' EXA for the third one? Because walking takes so much time, it's probably best to use the file EXA for the M nerve, since that one's closest.
I'll show you the end result because my intermediates were very confusing, dirty code.
XA is the file EXA. It's now much simpler after I removed all the logic. LINK to M-CNS, read 14 values, LINK to M-HND and write them.
XB handles the two other nerves. The rightbound (top half) part simply goes to the nerves and starts reading from them. Notice that the furthest EXA is exactly one cycle off from the other one (through the LINK 3 just after the REPL, so the H nerve starts sending first, causing the P one to wait a cycle, and then send, and since there's no loops or anything, just 14 repetitions of the COPY to M, they will keep alternating.
For the left side, the two EXAs also are one cycle out of sync, but there it's required to use an actual two-cycle loop. If they read from M every single cycle, which EXA reads what becomes completely unpredictable.
This solution started off as a one-EXA one too, but I noticed that after I'd cleaned up the code, the file transfer was the bottleneck by just a few cycles. Making it into a dedicated EXA solved this by removing the need for it to REPL several times. I still had to make sure to send it ahead of XB.
Anyway, this runs at a nice score of 37/70/19.
By the way, from this solution it isn't too hard to make something that actually keeps running forever, although it'll be slightly slower.
Code: Select all
;XA
LINK 800
MARK START
LINK -3
LINK -3
MAKE
MARK READ
@REP 7
COPY #NERV F
@END
@REP 4
LINK 3
@END
SEEK -9999
@REP 7
COPY F #NERV
@END
LINK -3
LINK -3
WIPE
JUMP START
;XB
NOOP
LINK 800
REPL LEFT
LINK 3
LINK 3
LINK 3
REPL MREADLOOP
LINK 3
MARK MREADLOOP
@REP 9
COPY #NERV M
@END
JUMP MREADLOOP
MARK LEFT
LINK -3
LINK -3
LINK -3
REPL MWRITELOOP
LINK -3
MARK MWRITELOOP
@REP 9
COPY M #NERV
NOOP
@END
JUMP MWRITELOOP
I have the top percentile score in cycles and activity, but didn't quite get it in size. As always it'll be interesting to see what you come up with.
Do you ever feel like it's a losing battle?
This constant effort to maintain your physical body.
The first vote.
Next time, we'll finally get to take a look at this video game Isadora got us from Japan, a game for the Sawayama WonderDisc console. Let's see what Ember has to say.
WonderDisc games are restricted by region locks.
Why?
And that's the second vote.
=== Trash World Inbox ===
Wow. That's very clever but also hard to follow code. I don't even know how to simply explain it in my own words. Basically, the same EXAs are doing double duty and the different REPLs make sure at least one ends up in the right place in the right time. The wrong ones happen to be harmless.silentsnack wrote:274/16/548. Each input/output pair is separated by 4, 6, or 8 jumps (sending output at 2 doesn't work because the P-HND signal also hits M-HND) and to make the cycle hit all 3 inputs without saturating we have to rely on the 5 jumps between M-CNS and H-HND, then it can also go one further to P-HNDCode: Select all
LINK 800 COPY -3 X MARK INPUT LINK X LINK X COPY #NERV T MULI X -1 X LINK X LINK X MARK OUTPUT LINK X REPL INPUT LINK X REPL OUTPUT COPY T #NERV JUMP INPUT
As for speed:
This looks quite similar to my fastest solution. There's two differences: the first is that XA (the one moving M-CNS data) has REPLs running back and forth instead of my file solution. By storing signals in both X and T and using REPLs this is much faster, and puts the bottleneck on the M solution. The second, as silentsnack hinted, is by compressing two nerve signals into one M signal. Since the compression/decompression arithmetic takes slightly longer, it uses REPLs to make sure a signal goes through every cycle, doubling the signal speed.silentsnack wrote:Here we can do some shenanigans that rely on something from the zine: "nerve voltages oscillate around -70mV but can spike up to 50 or down to -120" but here if we look at the testdata M shows all the characteristics but I think this is the first time we've seen sensory nerves like H and P that always stay near -70. Why is that useful? It means they're all 2-digit numbers and always have the same sign, but EXAs can handle 4-digit variables, at which point you can probably guess where this is going...25/39/47 if we comment out the NOOPs and just assume the EXAs start moving in order of XA,XB,XCCode: Select all
;XA LINK 800 LINK -3 LINK -3 MARK M_LOOP REPL NERV JUMP M_LOOP MARK NERV COPY #NERV X COPY #NERV T @REP 4 LINK 3 @END COPY X #NERV COPY T #NERV ;XB ;NOOP LINK 800 LINK 3 LINK 3 LINK 3 REPL HND_LOOP LINK 3 MARK HND_LOOP REPL NERV JUMP HND_LOOP MARK NERV MULI #NERV 100 X ADDI X #NERV M ;XC ;NOOP ;NOOP LINK 800 LINK -3 LINK -3 LINK -3 REPL CNS_LOOP LINK -3 MARK CNS_LOOP NOOP REPL CNS_LOOP COPY M X SWIZ X 43 #NERV;DIV 100 SWIZ X 21 #NERV;MOD -100
About my 37 cycle solution:
I noticed when I tried a hybrid of my solution and silentsnack's, that once you hit 35 the bottleneck shifts from the M-CNS to the other EXAs. So, your simple change basically optimizes XA as much as possible in my solution.GuavaMoment wrote:Very easy to tweak this to save two cycles - have XA do a file copy of 7 length instead of 14, replicate an exa to do it again, and transfer 7 digits over at a time. It's a little faster because now you have two exas who can read and copy values at the same time instead of one doing everything. 35/56/21
There was also an interesting discussion in the thread about learning from each other's solutions. Yes, I've been doing that during this LP as well, many of your ideas went into later puzzle solutions. I don't remember all your ideas nor do I know how to apply some of them in specific situations, but it has helped me come up with better solutions overall.
=== Sawayama WonderDisc - Drive Controller ===
Last time, I fixed my hand.
Do you ever feel like it's a losing battle?
This constant effort to maintain your physical body.
One vote for "stay in the moment", and if I interpreted the votes correctly, three for "everyone eventually loses".
Everyone eventually loses.
I wonder if I'm the same way.
Only time will tell.
Ah, yes, definitely the music industry. I had nothing to do with this.
So, to play that game Isadora got me, I'll need to disable the region lock on the Sawayama.
WonderDisc games are restricted by region locks.
Why?
Two votes for "business reasons" and also two for "big corporations". Time to get out the two-sided die again.
Business reasons, I guess.
Artificial scarcity?
You wouldn't want gamers importing titles too easily.
The less effort it takes, the less special it is.
Hmm.
OST: Getting Started
Let's see what I have to do.
- Modify your WonderDisc, which normally only plays SSEA region games, to play games from any region.
- The SSEA region code is available in file 300.
- It is not necessary to leave no trace. Your EXAs should be written to operate indefinitely.
- For more information see "Hardware Hacks: Sawayama WonderDisc" in the second issue of the zine.
Okay, so make a copy of whatever it needs from the disc but overwrite its region code. But first I need to unlock the system using that key in the zine.
Let's start with that then, so I can see what I'm dealing with.
Code: Select all
LINK 800
COPY 8 #AUTH
COPY 0 #AUTH
COPY 3 #AUTH
COPY 2 #AUTH
COPY 7 #AUTH
COPY 1 #AUTH
COPY 0 #AUTH
COPY 4 #AUTH
COPY 9 #AUTH
COPY 5 #AUTH
COPY 1 #AUTH
COPY 2 #AUTH
COPY 5 #AUTH
COPY 2 #AUTH
COPY 6 #AUTH
The key gets filled in on that display in the DISC host, and... I'm in.
A whole lot of track files. Remember, the little lock icon after the file id means you can't move or change them (which makes sense for read-only game discs). The files contain four-digit numbers, with the region code interspersed. I have to find the right track, make a copy in the buffer host with the updated region code, and repeat.
I am going to need the M register no matter what, because copying a file with a single EXA would require dropping it and picking up the copy repeatedly. So let's just get the data to the BUFFER host directly.
Replacing the region code is a bit tricky because it requires fiddling with the limited set of EXA registers. I'll first get the file copy code itself working. After unlocking the disc:
Code: Select all
MARK NEXT
REPL WRITER
COPY #TRAK X
LINK 801
GRAB X
MARK RDLOOP
COPY F M
JUMP RDLOOP
MARK WRITER
LINK 800
MAKE
MARK WRLOOP
COPY M F
NOOP
TEST MRD
TJMP WRLOOP
MARK END
DROP
LINK -1
JUMP NEXT
As soon as a file is dropped in the BUFFER, this unusual black EXA comes out of the Reality Processor, grabs the file, and takes it in. The processor notices the file has the wrong region and it'll throw an error.
But... that makes me wonder, can I mess with that black EXA and just take over entirely?
Turns out you can kill it, but this fails the assignment.
Doing so nets us the DISC_READ_ERROR steam achievement, with description "It was just doing its job...". Now I'm feeling bad.
Anyway, the final thing I need to do is overwrite the region code. Now, I considered doing that while copying the file. But that's hard. You can't check directly against M because that will "use up" M. Using X as an intermediate won't work either, because I need a place to store the new region code. And I can't use T because I need to test if a value needs replacing. Let's instead go through the file a second time to change the code.
Ignore that useless HOST command, I just wanted to know what that host was called.
To save a cycle, XB reads the region code into M and XA stores that into X once the disc is unlocked. Once the writer is done, it will REPL a new reader/writer, while it holds on to the file and overwrites the region code with the new one in X. Note that TEST F > -9999 can be used to test if something is text. This test will be true for any valid number (except -9999 but that doesn't occur in these files), but comparisons between text and numbers always return false.
Let's run it.
Hm. Looks like when a small file follows a big file, the small file is sometimes processed before the big one, and the Sawayama doesn't like getting tracks out of order.
An easy fix would be to only handle one file at one but I don't want to slow the code down that much. You know, I'm basically using F as an intermediate register now, can't I inline the region update anyway?
Code: Select all
;XA
LINK 800
COPY 8 #AUTH
COPY 0 #AUTH
COPY 3 #AUTH
COPY 2 #AUTH
COPY 7 #AUTH
COPY 1 #AUTH
COPY 0 #AUTH
COPY 4 #AUTH
COPY 9 #AUTH
COPY 5 #AUTH
COPY 1 #AUTH
COPY 2 #AUTH
COPY 5 #AUTH
COPY 2 #AUTH
COPY 6 #AUTH
COPY M X
LINK 800
MARK NEXT
LINK -1
REPL WRITER
COPY #TRAK X
LINK 801
GRAB X
MARK RDLOOP
COPY F M
JUMP RDLOOP
MARK WRITER
LINK 800
MAKE
MARK WRLOOP
COPY M F
SEEK -1
TEST F > -9999
FJMP OVERWRITE
TEST MRD
TJMP WRLOOP
DROP
JUMP NEXT
MARK OVERWRITE
SEEK -1
COPY X F
TEST MRD
TJMP WRLOOP
DROP
JUMP NEXT
;XB
GRAB 300
COPY F M
At 6487/48/94 the solution is rather slow. The top percentiles are 3293, 44 and 3. What's interesting is how far the tenth percentiles are from those numbers: 5584 for cycles and 63 for activity. This shows less people got an actual top score, so optimizing is getting much more difficult.
Anyway, I'm not that for from the low size top score.
To get to 45 I can replace the duplicate TEST MRD code with a jump, and then move the OVERWRITE elsewhere so the EXA dies by reaching the end of the code. If I do that, I can replace the DROP/JUMP with a REPL.
Code: Select all
;XA
LINK 800
; AUTH CODE
COPY M X
LINK 800
MARK NEXT
LINK -1
REPL WRITER
COPY #TRAK X
LINK 801
GRAB X
MARK RDLOOP
COPY F M
JUMP RDLOOP
MARK OVERWRITE
SEEK -1
COPY X F
JUMP BACK
MARK WRITER
LINK 800
MAKE
MARK WRLOOP
COPY M F
SEEK -1
TEST F > -9999
FJMP OVERWRITE
MARK BACK
TEST MRD
TJMP WRLOOP
REPL NEXT
Wait a second, now that the OVERWRITE is a jump and then a jump back, can't I inline that too and just skip if no overwrite is necessary?
Code: Select all
MARK WRLOOP
COPY M F
SEEK -1
TEST F > -9999
TJMP SKIPOVERWRITE
SEEK -1
COPY X F
MARK SKIPOVERWRITE
TEST MRD
TJMP WRLOOP
REPL NEXT
I don't think I can lower the size much more, so let's focus on the cycle count. Unrolls!
There's no point unrolling the reader by itself because the writer needs to do all the checks and is slower anyway. The files seem to be between 6 and 36 entries in size, always a multiple of 6. I can use that but unrolls in combination with jumps (for the overwrite) are always complex.
This solution uses the advanced REP syntax. The @{0,1} means "fill in zero for the first repetition, then 1 for the next, and keep incrementing by one." So I have a MARK SKIPOVERWRITE0, MARK SKIPOVERWRITE1 and so on in the unrolled result. This way, with the jumps it doesn't lose its place in the unroll, so no matter what, it checks for MRD every 6 repetitions. 4799/77/64.
For my next improvement I have a completely different idea. Can I parallellize the file copying? That requires the M register which is quite occupied... except we also have LOCAL mode.
You might think that won't fit in the DISC host but it does. One EXA in global mode can grab a file. Then a second EXA in local mode can grab a second file, and then a third one can use the one empty spot in the DISC to make a new file. I had some code that seemed like it would work but it got stuck when the Sawayama requested the same track twice in a row and the slowest EXA couldn't find it because the fastest was holding it.
I went through several complete rewrites before ending up with something that actually works.
It still isn't close to the top percentile but honestly, after being at it for several hours I consider this Good Enough. I'll start with XB, which has two purposes. The top half:
Code: Select all
;XB LOCAL
GRAB 300
COPY F X
DROP
LINK 800
REPL WRITER
MAKE
COPY #TRAK F
MARK ROUND
COPY #TRAK F
SEEK -2
TEST F = F
SEEK -2
COPY F M
TJMP SKIPLOCAL
COPY F M
COPY X M
COPY #TRAK F
JUMP ROUND
MARK SKIPLOCAL
COPY 0 M
SEEK 1
JUMP ROUND
Code: Select all
;XB cont'd
MARK WRITER
MODE
LINK 800
MARK WRITEFILE
MAKE
MARK WRLOOP
COPY M F
SEEK -1
TEST F > -9999
TJMP SKIPOVERWRITE
SEEK -1
COPY X F
MARK SKIPOVERWRITE
TEST MRD
TJMP WRLOOP
DROP
MODE
VOID M
MODE
JUMP WRITEFILE
Code: Select all
;XA LOCAL
LINK 800
COPY 8 #AUTH
COPY 0 #AUTH
COPY 3 #AUTH
COPY 2 #AUTH
COPY 7 #AUTH
COPY 1 #AUTH
COPY 0 #AUTH
COPY 4 #AUTH
COPY 9 #AUTH
COPY 5 #AUTH
COPY 1 #AUTH
COPY 2 #AUTH
COPY 5 #AUTH
COPY 2 #AUTH
COPY 6 #AUTH
MARK ROUND
COPY M X
REPL GLOBALREADER
COPY M T
TJMP LOCALRW
LINK 800
COPY 0 M
LINK -1
JUMP ROUND
Code: Select all
;XA cont'd
MARK GLOBALREADER
MODE
LINK 801
GRAB X
MARK RDLOOP
COPY F M
JUMP RDLOOP
Code: Select all
;XA cont'd
MARK LOCALRW
COPY M X
LINK 801
GRAB T
REPL LOCALWRI
JUMP RDLOOP
MARK LOCALWRI
MAKE
MARK LOCALWRLOOP
COPY M F
SEEK -1
TEST F > -9999
TJMP SKIPOVERWRITE
SEEK -1
COPY X F
MARK SKIPOVERWRITE
TEST MRD
TJMP LOCALWRLOOP
Code: Select all
;XA cont'd
LINK -1
LINK 800
COPY 0 M
DROP
LINK -1
JUMP ROUND
So, to summarize:
- XB checks if two files can be handled at once (different IDs). If so, XA puts them both to work. Once one of them is done, it has to wait for the other so that everything stays in sync.
- If the Sawayama wants the same track twice, only the GLOBAL reader runs, slowing down the process but making sure it stays in sync.
The result is 3830/98/81 and I'm just glad I got a sub-4000 cycle count. There's no space left for unrolls since the max allowed size is 100.
Finally, the 3-activity solution is tricky, but doable.
The issue is that since you can't move EXAs around, the M register has to do a lot of duties at the same time. So I have to be smart about it.
I wrote a solution but didn't bother it optimizing for anything but activity so forgive me for my ugly code.
Code: Select all
GRAB 300
COPY F X
DROP
LINK 800
REPL WRITER
COPY 8 #AUTH
COPY 0 #AUTH
COPY 3 #AUTH
COPY 2 #AUTH
COPY 7 #AUTH
COPY 1 #AUTH
COPY 0 #AUTH
COPY 4 #AUTH
COPY 9 #AUTH
COPY 5 #AUTH
COPY 1 #AUTH
COPY 2 #AUTH
COPY 5 #AUTH
COPY 2 #AUTH
COPY 6 #AUTH
REPL READER
MARK TRAK
COPY #TRAK M
COPY 190 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
JUMP TRAK
MARK READER
LINK 801
MARK READNEXT
GRAB M
MARK RDLOOP
COPY F M
TEST EOF
FJMP RDLOOP
DROP
JUMP READNEXT
MARK WRITER
LINK 800
REPL MCONTROLLER
MODE
MARK WRITEFILE
MAKE
MARK WRLOOP
COPY M F
SEEK -1
TEST F = 0
TJMP DONE
SEEK -1
TEST F > -9999
TJMP SKIPOVERWRITE
SEEK -1
COPY X F
MARK SKIPOVERWRITE
JUMP WRLOOP
MARK DONE
SEEK -1
VOID F
DROP
JUMP WRITEFILE
MARK MCONTROLLER
COPY M X
TEST X < 300
TJMP GLOBAL
MODE
COPY X M
MODE
TEST MRD
FJMP NEXTFILE
JUMP MCONTROLLER
MARK GLOBAL
COPY X M
TEST MRD
FJMP NEXTFILE
JUMP MCONTROLLER
MARK NEXTFILE
MODE
COPY 0 M
MODE
JUMP MCONTROLLER
It starts with unlocking the disc like normal, and REPLing a single writer and reader. The reader is still quite simple: grab the file id which is received through M, then send all data through M, but specifically check for EOF and read the next file when it's done.
The tricky part is in the WRITER. It runs in LOCAL mode. There's an M-CONTROLLER EXA that receives all GLOBAL M messages. If it's a number under 300 (a file ID), it sends it on global M again because it was actually intended for the reader but the controller happened to intercept it. If it's anything else, the WRITER needs it, so the MCONTROLLER forwards it in LOCAL mode. Finally, if nobody is sending, the controller sends a 0 to the WRITER to let it know the file is done.
So, how do you tell the EXA reading from #TRAK that it should send a new value? That's the neat part, you don't. If you try it with M, all EXAs will intercept messages from each other and you'll end up in unpredictable inescapable loops. Instead, that EXA just has a very long countdown (190 two-cycle iterations) so it waits long enough for even the biggest file to be done.
The countdown and all M messages having to go through the controller make this solution slow.
That was a lot of work just to play one game.
Think it will be worth it?
And that brings us to our first vote.
Once again, we unlocked a special minigame. This time it isn't Solitaire. It is called HACK*MATCH.
Think the console version will live up to the arcade classic?
Here's the second vote. We'll check out the minigame next time.
Today's update is a bit of a break from the normal puzzles. But before we go there, let's see what ideas you had for the region lock puzzle.
=== Trash World Inbox ===
Some very neat tricks in this one. You actually send the result of the TEST EOF to another EXA so two EXAs can both conditionally jump based on a single test. I don't think we've seen that before. Also, XA keeping count of the file length and getting that value through XB to XC so that XC knows how long it needs to count down for.GuavaMoment wrote:Major speed tips - the files are always multiples of 12, so unroll files into 12 long loops. I transmit the files, then go back through and replace the SSEA code. There's a third timing exa IN LOCAL MODE that exas in the buffer must communicate with first so that long files get done before short files.2669/83/37Code: Select all
XA LINK 800 COPY 8 #AUTH COPY 0 #AUTH COPY 3 #AUTH COPY 2 #AUTH COPY 7 #AUTH COPY 1 #AUTH COPY 0 #AUTH COPY 4 #AUTH COPY 9 #AUTH COPY 5 #AUTH COPY 1 #AUTH COPY 2 #AUTH COPY 5 #AUTH COPY 2 #AUTH COPY 6 #AUTH LINK 801 MARK BEGIN GRAB M MARK LOOP COPY F M COPY F M COPY F M COPY F M COPY F M COPY F M COPY F M COPY F M COPY F M COPY F M COPY F M COPY F M TEST EOF COPY T M ADDI X 11 X FJMP LOOP COPY X M DROP COPY 0 X JUMP BEGIN XB GRAB 300 COPY F X LINK 800 DROP MARK NEWONE MAKE COPY #TRAK M MARK LOOP COPY M F COPY M F COPY M F COPY M F COPY M F COPY M F COPY M F COPY M F COPY M F COPY M F COPY M F COPY M F COPY M T FJMP LOOP SEEK -999 REPL NEWONE COPY M T LINK 800 MODE COPY T M MARK LOOP2 TEST F > 0 TJMP LOOP2 SEEK -1 COPY X F JUMP LOOP2 XC (local) NOOP LINK 800 LINK 800 MARK SIGNAL COPY M T MARK LOOP SUBI T 1 T TJMP LOOP JUMP SIGNAL
silentsnack improved upon this idea:
Yeah, so it looks like a delay of 8 cycles per 12-entry "file chunk" makes things line up with the least time loss.silentsnack wrote:Also the longest files are 36 entries, so you only really need at most two EOF checks. And since the file sizes are somewhat predictable, IF we use one way or another to measure how big a file is, we can add a variable delay to make short files wait a bit to make sure they don't get dropped before a previous big file... which at some point requires experimentally finding values that work2304/99/69Code: Select all
;XA LINK 800 COPY 8 #AUTH COPY 0 #AUTH COPY 3 #AUTH COPY 2 #AUTH COPY 7 #AUTH COPY 1 #AUTH COPY 0 #AUTH COPY 4 #AUTH COPY 9 #AUTH COPY 5 #AUTH COPY 1 #AUTH COPY 2 #AUTH COPY 5 #AUTH COPY 2 #AUTH COPY 6 #AUTH ;XB COPY 6 T MARK WAIT_AUTH SUBI T 1 T TJMP WAIT_AUTH LINK 800 REPL READER MARK WRITER;[MAIN LOOP] MAKE COPY 24 X ;(?) COPY M F;COPY #1/12 MARK W_LOOP @REP 8 COPY M F;COPY #2~9/12 @END SUBI X 8 X ;(??) COPY M F TEST X = 0 ;(???) COPY M F TJMP SKIP_MRD COPY M F;COPY #12/12 TEST MRD FJMP WROTE COPY M F;#1 OF NEXT LOOP JUMP W_LOOP; "FREE" JUMP MARK SKIP_MRD COPY M F MARK WROTE REPL READER REPL WRITER;[/MAIN LOOP] LINK 800 MODE SEEK -999 COPY X T ;(SOMEHOW) FJMP DATA MARK SYNC SUBI T 1 T TJMP SYNC ;(MAGIC?) MARK DATA @REP 2 TEST F > 0 TJMP DATA SEEK -1 COPY M F @END JUMP DATA MARK READER COPY #TRAK T LINK 801 GRAB T MARK READ @REP 18 COPY F M @END JUMP READ ;XC LOW-EFFORT SSEA HACK (local) GRAB 300 LINK 800 LINK 800 COPY F X MARK PATCH COPY X M JUMP PATCH
Finally, silentsnack suggested the following size improvement:
The main improvement is of course the compressed auth. I was thinking there was no way to speed up the auth which is why I got tricked into believing no optimizations in that code were possible at all. Of course a size optimization was right for the taking.silentsnack wrote:6412/37/62Code: Select all
;XA LINK 800 COPY 8032 X MARK AUTH SWIZ X 4 #AUTH SWIZ X 3 #AUTH SWIZ X 2 #AUTH SWIZ X 1 #AUTH COPY M X TEST MRD TJMP AUTH MARK READER REPL WRITER LINK 801 GRAB M MARK READ COPY F M JUMP READ MARK WRITER COPY #TRAK M MAKE MARK WRITE_LOOP COPY M T COPY T F TEST T > 0 TJMP SKIP SEEK -1 COPY X F MARK SKIP TEST MRD TJMP WRITE_LOOP REPL READER LINK 800 ;XB GRAB 300 COPY 7104 M COPY 9512 M COPY 5260 M COPY F M
=== HACK*MATCH ===
That was a lot of work just to play one game.
Think it will be worth it?
Only got a single vote for this one.
The hacking is its own reward.
Sure. Especially when you get something tangible out of it.
Time to play!
Yes. But before I do, have I showed you my apartment recently?
OST: Apartment
The WonderDisc is running, my Redshift is set all set up, I like it.
[=plastered] yep
[=plastered] thats how it goes
My, um, assignment is to score 10000 points in this game.
Think the console version will live up to the arcade classic?
Two votes for asking for details.
It's an arcade game?
It is in Japan.
You haven't seen the cool cabinets they have?
I knew ingesting that corpus of gaming magazines would come in handy.
Alright, let's see what this game is all about.
This only pops up for a second before the game boots.
... full extent of the jam? I'm sure it'll be fine.
Since this is an arcade game, I'm presenting it to you in video format.
After you've watched the video, here's some background information.
The music for HACK*MATCH is OST: Let'sハッキング, which translates to "Let's hack" which, honestly would've been a good title for this thread.
According to an old post I found online, the instructions page translates to:
On the left: to download the file, assemble 4 or more files of the same type by connecting them
On the right half: if you line up two of the pressure explosive, you can download all the files of the same type at the same time.
There are also three Steam achievements tied to HACK*MATCH:
- ゲーマー , for getting 10000 points
- 熟練ゲーマー , for getting 50000 points
- 究極のゲーマー , for getting 100000 points.
Anyway, let's see what Ember has to say about us getting a decent score.
Hmm.
This game isn't about hacking at all, is it?
They took a lot of dramatic license.
That's the first vote for today.
I was so caught up in that little game I almost didn't notice the knock on the door.
Got bad news.
The pharm was raided.
The cops hauled off the equipment, the sheep and the pigs, the whole thing.
It's a real shame.
It was a lot of work to genetically modify those animals.
They'll probably get culled, too.
Sad...
So is that it, then?
If there's no more medication, I can't slow the spread of the phage...
I'll be finished soon.
This might not be the end of the story, though.
The people I work with, they're looking to get their hands on the real deal now.
There's a shipment going out soon, and let's just say a lot of people with better resources than you or me want these meds.
There's going to be fireworks.
Nivas pauses for a moment to study my face.
So. Here's the offer.
If you help us grab that shipment, we'll let you buy some of it. Market price.
I know it's a lot higher than before, but it's your only choice if you still want it.
Your part should be easy too.
That is if I understand what you do correctly.
Just cut some power. Snip snip. We'll take care of the rest.
I'll send you the details. You can decide then.
Seeya.
Damn. Well, I guess I have no choice but to help them.
This is something like a heist, isn't it.
And with that, we've reached the second vote.
=== Trash World Inbox ===
Yep, good tip. I guess I forgot to show that off.Quackles wrote:HACK*MATCH tip: You can swap while holding a block to change the order of blocks up to three deep in total. It can help making some buried 4's.
I like that. Perhaps I should hack the Phage EXAs in my hand to get better at controlling the game.GuavaMoment wrote:The only way I found to get 100000 hackmatch points in a reasonable timeframe was to download a lua file (I think) that played for you and ran it overnight. Essentially hacking the hacking game to win a different hacking game. I found it appropriate.
=== Alliance Power and Light - Streetsmarts GIS Database ===
Hmm.
This game isn't about hacking at all, is it?
They took a lot of dramatic license.
Well, all the votes were the same this time, that makes for easy counting.
I don't know how you'd make a game about real hacking.
Hacking is inherently about manipulating systems in ways the designers didn't intend...
Whereas a game is usually a set of possibilities within accepted bounds.
It seems difficult to reconcile.
Well, this got very meta, didn't it? Let's stop for a moment and think about how Zachtronics handles this. Basically, they created a programming language and they made a set of puzzles around it. It is a purposefully constrained language, which makes otherwise easy problems surprisingly difficult. As a professional software developer, it forces me to look at seemingly familiar things in a completely different light.
I'm sure Zach had no idea of some of the solutions we came up with either. Of course, for a game like this, you need to make some concessions, such as the test cases often not being completely realistic, or us being able to game them... but I have a feeling Zach is completely fine with the latter.
I think a sandbox made out of some basic rules, together with some cleverly designed puzzles that allow multiple solutions within the constraints of the sandbox rules, makes for very good and interesting puzzle game design. This game does it well. And Breath of the Wild's shrines were designed with a similar approach. It just works. As a counter example, I'm thinking Scribblenauts, which barely constrains the puzzle solutions. This allows most levels to be solved in a way that feels too cheap. At that point it might be better to drop the puzzle elements entirely and turn it into a building game such as Minecraft.
...Anyway, that's enough of me rambling about game design.
[Ghast] you never know what a guy who thinks he can make a buck will do.
We need to help Nivas out so we can get a new shipment of the Phage medicine.
This is something like a heist, isn't it.
Another clear vote.
It is a heist.
I didn't imagine such a major operation.
Not including you, there are three separate teams.
Whoever put this together has some serious resources.
I guess that's why it costs so much.
Don't worry, I'm working on the money part.
You just do your thing.
Sure.
OST: Leave No Trace
The assignment:
- Locate the two hosts with the specified hostnames (file 300), which correspond to the target power grid substations. When you've found them, cut the power by writing a value of 0 to #POWR.
- For more information see "Network Exploration: Geographic Information Systems" in the second issue of the zine.
This is the same GIS system we dealt with before, for the restaurant review assignment (Part 22). LINK 800 for north (top left), 801 for east, 802 for south and 803 for west.
However, this time I'll have to use the HOST command to find out the name of each host. It's not possible to test against a host name directly, you first need to load the value into some register with e.g. HOST X. Another complication is that we need to find two hosts. That'll mean some more register juggling.
That said, since the grid looks exactly the same, perhaps I can reuse some code from before. It was strongly optimized for that particular case and might not work as well here, but it's a start.
Code: Select all
LINK 800
REPL SOUTH
REPL NORTH
REPL EAST
JUMP POWERSWITCH
MARK SOUTH
LINK 802
REPL EAST
LINK 802
REPL EAST
REPL POWERSWITCH
LINK 800
JUMP POWERSWITCH
MARK NORTH
LINK 800
REPL EAST
LINK 800
REPL EAST
REPL POWERSWITCH
LINK 802
JUMP POWERSWITCH
MARK EAST
@REP 2
LINK 801
REPL POWERSWITCH
@END
LINK 801
MARK POWERSWITCH
NOOP
People sent in some faster solutions but I prefer to start with this one because, since I wrote it myself, I understand exactly how it works.
Next I'll need to include some new code to switch off the right substations.
But... why stop there? Can't I just switch off everything? That should be enough of a distraction to pull off the heist, right?
I just replaced that NOOP at the bottom with a COPY 0 #POWR. It seems to be working, but there's a bit of an unforeseen side effect: cutting the power to a substation also takes the EXA host offline, making it untraversable and killing any EXAs that are chilling there.
It doesn't turn out to be a problem for us, since this optimized code from the previous assignment was written such that EXAs prioritize getting everywhere before they start messing around.
But "turn off everything" doesn't work - it fails the "Leave no trace" criterion.
However, it does net us the Steam achievement "BLACKOUT", description "Trigger an excessive service outage."
For testing if an EXA is in the right host, a sensible solution is to have a value from the file in T, then do HOST X and then TEST X T. As long as we don't need to test the same value twice that should work.
I can think of two ways to handle the second value. The first is by sending another full set of EXAs round, carrying the other value. That sounds complicated though, especially with hosts potentially turning off. The other way is an EXA staying home and sending the second value on M constantly. Let's try that.
XB grabs the file, sends the first value to M once, where XA can read it before it starts replicating. It then starts sending the second value. In POWERSWITCH, XA reads the HOST to X, compares it against the T value, and if it doesn't match, it compares against M. If it still doesn't match it can just die by divide by zero.
This code does turn off the right substations. But the problem is XB hangs forever, which means test cases never finish. Apparently I can't just disconnect my home computer when it's done.
Let's think about this one for a bit. How can we solve it?
- Because I put the X=T test first, only one of the EXAs doesn't test against M. With 20 grid tiles to check, that means XB will be asked for M 19 times. I could just build a countdown. The problem is that I can't store the countdown value in T because I have to test against it, and I can't put it in X because that's where I'm sending the value from (or I'd have to SEEK in the file which is slow). I guess I could use a second EXA for the countdown that just kills this one.
- Alternatively, I could have the EXA that reaches its tile last send a message back, and have XB listen to that with MRD or something.
But what if I do neither?
I have plenty of space left to just unroll the XB loop 19 times:
Code: Select all
;XA
LINK 800
COPY M T
REPL SOUTH
REPL NORTH
REPL EAST
JUMP POWERSWITCH
MARK SOUTH
LINK 802
REPL EAST
LINK 802
REPL EAST
REPL POWERSWITCH
LINK 800
JUMP POWERSWITCH
MARK NORTH
LINK 800
REPL EAST
LINK 800
REPL EAST
REPL POWERSWITCH
LINK 802
JUMP POWERSWITCH
MARK EAST
@REP 2
LINK 801
REPL POWERSWITCH
@END
LINK 801
MARK POWERSWITCH
HOST X
TEST X = T
TJMP OFF
TEST X = M
DIVI 0 T T
MARK OFF
COPY 0 #POWR
;XB
GRAB 300
COPY F M
COPY F X
@REP 19
COPY X M
@END
Not a terrible first score of 50/58/22, but the top percentiles are 20, 22 and 20, so I certainly can do better.
Let's start with activity since that one is actually already solved.
Because my solution didn't require any KILL commands it's already quite good. But if you look at the gif, you'll see this code was originally optimized to reach the farthest grid squares first. In the SOUTHbound and NORTHbound chunks of codes it first travels to the end before jumping back to check the squares right next to the starting one. All I have to do is remove that old optimization.
Code: Select all
MARK SOUTH
LINK 802
REPL EAST
REPL POWERSWITCH
LINK 802
REPL EAST
JUMP POWERSWITCH
MARK NORTH
LINK 800
REPL EAST
REPL POWERSWITCH
LINK 800
REPL EAST
JUMP POWERSWITCH
But wait... undoing an optimization made the cycle count lower? Ha.
That just goes to show that EXA optimizations are highly context specific. Premature optimization can lead you into a trap where the optimization doesn't fit the context anymore and it's too late to notice.
Anyway, let's look at the cycles some more. I feel the main slowdown comes from the fact that XB can only send a message on M every other cycle. There's a simple way to solve that: add another EXA.
Code: Select all
;XB
GRAB 300
COPY F M
COPY F X
REPL DOUBLETHEFUN
MARK DOUBLETHEFUN
@REP 10
COPY X M
@END
KILL
What about the other method of sending two EXAs everywhere, each one holding a value from the file?
After some fiddling I came up with this code.
Code: Select all
;XA
LINK 800
REPL OTHER
MARK OTHER
COPY M X
REPL SOUTH
REPL NORTH
REPL EAST
JUMP POWERSWITCH
MARK SOUTH
LINK 802
REPL EAST
REPL POWERSWITCH
LINK 802
REPL EAST
JUMP POWERSWITCH
MARK NORTH
LINK 800
REPL EAST
REPL POWERSWITCH
LINK 800
REPL EAST
JUMP POWERSWITCH
MARK EAST
@REP 2
LINK 801
REPL POWERSWITCH
@END
LINK 801
MARK POWERSWITCH
HOST T
TEST X = T
DIVI 0 T T
NOOP
NOOP
COPY 0 #POWR
;XB
GRAB 300
COPY F M
COPY F M
Having a dedicated EXA to read the file is still slightly faster than having XA do that. The two NOOPs are unfortunate, but without that there are cases when the second EXA can't make it past a switched-off host in time.
I actually don't need those NOOPs for the EXAs that go second, so the next step is to selectively remove them.
The fastest way I found was duplicating almost all the code into XB.
25/67/40.
One thing that might speed up things more if to have both EXAs traverse the grid in different orders, so they block each other less. I tried some small things but nothing seemed to work so far, so if it's possible it's more subtle than what I did. It's also quite possible that the optimizations from the threads for the restaurant review assignment work here as well, but I'll leave that for you all to figure out.
Finally, for cycle count, the first thing to do is apply my low-cycle solution from the previous assignment, starting from the 38 solution above.
Code: Select all
;XA
LINK 800
REPL OTHER
MARK OTHER
COPY M X
JUMP START
MARK EAST
LINK 801
MARK START
REPL EAST
COPY 800 T
REPL NORTHSOUTH
REPL POWERSWITCH
COPY 802 T
MARK NORTHSOUTH
LINK T
REPL POWERSWITCH
JUMP NORTHSOUTH
MARK POWERSWITCH
HOST T
TEST X = T
DIVI 0 T T
NOOP
NOOP
NOOP
NOOP
NOOP
COPY 0 #POWR
;XB
GRAB 300
COPY F M
COPY F M
I can save a couple lines by loading something useful in T with the DIVI and then use a countdown loop.
Code: Select all
MARK POWERSWITCH
HOST T
TEST X = T
DIVI 3 T T
MARK WAIT
SUBI T 1 T
TJMP WAIT
COPY 0 #POWR
Combining the EXAs won't help. Combining COPY F M and COPY M X means you still need two COPY to X commands total (one before and one after the REPL, because files aren't copied when you replicate an EXA). And you need an additional WIPE so the file doesn't end up in the grid.
The next obvious improvement would be to implement GuavaMoment's low cycle solution for Part 22. It won't quite work as-is, though. It depended on EXAs trying to grab files and them dying if the file was already taken. Without that, they fill up some hosts entirely and get stuck in a REPL command. I tried 'fixing' that by putting some KILL commands at the end but that just causes them to kill valid EXAs holding the other substation value. Putting it in the solution where XB just sends the second value over M many times doesn't work either, because the amount of times M is requested becomes variable.
So, I will stop here and wait to see how you got a better cycle count than 25 and a smaller size than 28.
Looks like the heist went flawlessly.
Congratulations, now a criminal cartel will sell the medication you helped steal back to you for not much less than the market price.
Oh well. At least they'll let you buy it from them.
Now that this is over with, I have a question.
Is theft morally acceptable in situations like this?
The first vote.
Next time, another hacker battle, this time against deadlock.
Onto the third of the tournament battles...
Is each match against a better programmer than the last?
Is it?
=== Trash World Inbox ===
My top scores for last week were 25 cycles and a size of 28. As usual, you people came up with some impressive improvements. Let's start with GuavaMoment.
18/56/31. Using X and T from separate EXAs is smart, but also using the LEFT2/LEFT construction. It means EXAs going all the way left and EXAs going one step left are separate - which I guess is faster than doing another REPL after doing one step left.GuavaMoment wrote:18 cycles!Put the two hosts in X and T, fill the east column, then fill every square north and south with TWO exas - one will put the HOST in X, the other in T, before testing. I know 17 cycles is possible though.Code: Select all
GRAB 300 COPY F X COPY F T LINK 800 REPL TOP3 REPL TOP2 REPL TOP1 REPL RIGHT2 REPL LEFT2 REPL RIGHT REPL LEFT WIPE REPL TESTT HOST T TEST T = X FJMP DIE COPY 0 #POWR MARK TOP3 LINK 801 MARK TOP2 LINK 801 MARK TOP1 LINK 801 REPL RIGHT2 REPL LEFT2 REPL RIGHT REPL LEFT REPL TESTT HOST T TEST T = X FJMP DIE COPY 0 #POWR MARK RIGHT2 LINK 802 MARK RIGHT LINK 802 REPL TESTT HOST T TEST T = X FJMP DIE COPY 0 #POWR MARK LEFT2 LINK 800 MARK LEFT LINK 800 REPL TESTT HOST T TEST T = X FJMP DIE COPY 0 #POWR MARK TESTT HOST X TEST T = X FJMP DIE COPY 0 #POWR MARK DIE
silentsnack managed to improve this code by a cycle:
As GuavaMoment points out, the DIVI 0 T #POWR is a neat solution - it crashes the EXA if T is zero, and sends zero to #POWR otherwise. I was wondering if something like that was possible but got kinda stuck on the requirement to test words which requires the TEST instruction. Didn't think of using it a line after the test.silentsnack wrote:17/50/27Code: Select all
GRAB 300 COPY F X COPY F T LINK 800 REPL COLUMN3 REPL COLUMN2 REPL COLUMN1 REPL SOUTH REPL NORTH REPL TEST LINK 800 WIPE REPL BSIDE HOST T TEST T = X DIVI 0 T #POWR MARK COLUMN3 LINK 801 MARK COLUMN2 LINK 801 MARK COLUMN1 LINK 801 REPL SOUTH REPL NORTH REPL TEST LINK 800 REPL BSIDE HOST T TEST T = X DIVI 0 T #POWR MARK SOUTH LINK 802 REPL TEST LINK 802 REPL BSIDE HOST T TEST T = X DIVI 0 T #POWR MARK NORTH LINK 800 LINK 800 MARK TEST REPL BSIDE HOST T TEST T = X DIVI 0 T #POWR MARK BSIDE HOST X TEST T = X DIVI 0 T #POWR
As for size, GuavaMoment and silentsnack both have neat solutions too.
39/25/41. I didn't expect that the extra lines to move to a corner first would save enough to make it worth it.GuavaMoment wrote:My lowest size is 25, and I know 21 is possible.I head to the most southwest node. Fill north and east, and repeat. Noops are needed for some edge cases to prevent a node from turning off before the next exa moves on.Code: Select all
GRAB 300 COPY F X LINK 800 LINK 802 LINK 802 REPL TEST1 COPY F X WIPE MARK TEST1 REPL TOP MARK TEST REPL LEFT HOST T TEST T = X FJMP DIE NOOP NOOP COPY 0 #POWR MARK TOP LINK 801 JUMP TEST1 MARK LEFT LINK 800 JUMP TEST MARK DIE
Similar to GuavaMoment's solution, start in a corner. This solution makes use of some optimized LINKing loops, using T as a buffer for the second host value, as well as a SUBI 1 T #POWR which is the same as the DIVI above except the EXA doesn't die if T is zero. Not that it matters, it's the last line anyway.silentsnack wrote:32/21/22Code: Select all
GRAB 300 LINK 800 LINK 800 LINK 800 COPY F X COPY F T WIPE MARK A REPL B LINK 801 JUMP A MARK BB LINK 802 MARK B REPL BB REPL C COPY T X MARK C HOST T TEST T = X SUBI 1 T #POWR
=== Deadlock's Domain ===
Looks like the heist went flawlessly.
Congratulations, now a criminal cartel will sell the medication you helped steal back to you for not much less than the market price.
Oh well. At least they'll let you buy it from them.
Now that this is over with, I have a question.
Is theft morally acceptable in situations like this?
One vote for 'No', and a whole lot for 'It's complicated'.
It's complicated.
It doesn't seem that complicated to me...
But then, I've grown.
Processing.
Let's continue.
Deadlock wants to battle me. In their own domain.
Onto the third of the tournament battles...
Is each match against a better programmer than the last?
Most votes went to 'Better is subjective'.
Better is subjective.
That's how I would order it.
Like a video game, you know?
It makes for an easy narrative structure.
...whatever you say.
OST: Getting Started
Another hacker battle, like we've seen before. The assignment states:
To win this battle you must grab files as they spawn in the central hosts and bring them back to your host.
Reading the #FILE register will tell you the ID of the most recently created file currently in that host.
- Gain one point for every file you bring back to your host.
- Lose one point every time one of your EXAs executes a KILL instruction.
For more information see "Hacker Battle Domination" in the second issue of the zine.
There's a limit of three EXAs per side.
If I just start the simulation, files (with a music note icon) start spawning in the central hosts. They just contain random strings of numbers. Deadlock's EXA sets up camp in the Backstage host and starts replicating EXAs from there that grab files and bring them to Deadlock's home.
Let's start with just copying that idea.
Code: Select all
LINK 800
MARK REPLLOOP
REPL LEFT
REPL MID
REPL RIGHT
JUMP REPLLOOP
MARK LEFT
LINK 800
GRAB #FILE
LINK -1
LINK -1
MARK MID
LINK 801
GRAB #FILE
LINK -1
LINK -1
MARK RIGHT
LINK 802
GRAB #FILE
LINK -1
LINK -1
Anyway, let's see how well the copycat solution works.
Huh. I was thinking about more complicated solutions but this simple one already gives me a perfect score. Looks like for some reason my solution is just slightly faster than whatever deadlock does. Hell, even something simple like this gives me an S+:
Code: Select all
LINK 800
MARK REPLLOOP
REPL LEFT
JUMP REPLLOOP
MARK LEFT
LINK 800
GRAB #FILE
LINK -1
LINK -1
Curious how enthusiastic everyone gets about the battles.
Something about competition really energizes the spectators.
It makes me wonder...
How do we complete her sentence?
The intro for the next level has no question for the player, so that was the only vote for this update. Instead, to spend the time, here's another nonogram from the second zine. This one should be a bit harder. Please put your solution in spoiler tags so other players have a chance too.
=== Trash World Inbox ===
Last time, I ended with another nonogram puzzle.
Several people solved it, here's Regallion's submission.
It's a computer, of course.
=== Xtreme League Baseball ===
Last time, I beat deadlock in a hacker battle.
Curious how enthusiastic everyone gets about the battles.
Something about competition really energizes the spectators.
It makes me wonder...
Two votes for "What the point is?"
What the point is?
Perhaps there is no immediate point.
I am able to tolerate ambiguity.
Even though I may not always like it.
A bit scared of me, are they?
Looks like the next assignment has to do with a Baseball database.
You should be glad I've been reading up on sports.
Remember when I said I'd figure out the money situation?
I've designed a new rating system for extreme baseball players.
It's based on a multivector analysis of all the stats I could find.
In other words, I have a way to predict winning teams that's pretty much infallible.
A few bets here and there and I'll be rich.
Um, I don't think you can predict sport match outcomes to such a degree. But okay, if you say so.
OST: Code and Registers
The assignment:
- The hosts active and penalty contain files that correspond to extreme baseball players (files 200-299), along with a directory file that contains a list of those files' IDs (file 199). Each player file contains their name and the following statistics in this order: BA, ZA, APB, WRT, OI, OD, PC and PS.
- Create a file in your host with the name of the player with the highest score using EMBER-2's algorithm:
SCORE = (BA + ZA + APB) / 3 + (WRT * OI) / OD + (PC - PS) * 20
- Players in the penalty host should be ignored, as they are currently banned from the game.
Okay, so all the action is in the active host. I'm not really familiar with the details of Xtreme Baseball, so I don't know what those stats mean but this assignment looks an awful lot like math. I'm sure I can figure it out though.
Before that, let's have a bit of fun.
Having an EXA go round the diamond gets us the Steam achievement HOME_RUN, description "Participate in America's new pastime."
Let's start by just writing the algorithm. Ember's formula has the stats in the order they appear in the files, which makes it simple because I don't need to SEEK back.
Code: Select all
LINK 800
GRAB 199
MARK NEXT
COPY F X
REPL CALCULATOR
JUMP NEXT
MARK CALCULATOR
GRAB X
SEEK 1
ADDI F F X
ADDI X F X
DIVI X 3 X
MULI F F T
DIVI T F T
ADDI X T X
SUBI F F T
MULI T 20 T
ADDI X T M
SEEK -9999
COPY F M
Another EXA will have to figure out which player has the highest value. An initial attempt:
Code: Select all
;XB
MAKE
COPY 0 F
MARK NEXT
SEEK -9999
COPY M X
TEST X > F
FJMP SKIP
COPY M F
SEEK -9999
COPY X F
JUMP NEXT
MARK SKIP
VOID M
JUMP NEXT
There's still some stuff missing, for instance how do you clean up at the end? Since the code so far only has an activity of one (the one LINK to the active host), I decided to first solve these last bits with low activity in mind.
That was harder than it seems, though. XA creates replicas so fast that the M messages start interfering with each other. There are solutions such as having them wait for each other, but to do that in a fast way, you'd need to prevent the last one from getting stuck which might require a KILL or something. Since that's not allowed for low activity, I went for a slower solution instead.
Code: Select all
;XA LOCAL
LINK 800
GRAB 199
MARK NEXT
COPY F X
REPL CALCULATOR
COPY 0 M
TEST EOF
FJMP NEXT
MODE
NOOP
NOOP
NOOP
NOOP
COPY 0 M
MARK CALCULATOR
GRAB X
SEEK 1
ADDI F F X
ADDI X F X
DIVI X 3 X
MULI F F T
DIVI T F T
ADDI X T X
SUBI F F T
MULI T 20 T
VOID M
MODE
ADDI X T M
SEEK -9999
COPY F M
Code: Select all
;XB
MAKE
COPY 0 F
MARK NEXT
SEEK -9999
COPY M X
TEST X > F
FJMP SKIP
COPY M F
SEEK -9999
COPY X F
JUMP NEXT
MARK SKIP
TEST X = 0
TJMP END
VOID M
JUMP NEXT
MARK END
SEEK -9999
VOID F
This solution runs at 168/49/1. Top percentiles are 79, 39 and 1 respectively.
Time for other optimizations.
This code speeds things up significantly, with 101/52/3. The main change is that I have a separate TIMER EXA which allows the algorithms to run in parallel, only synchronizing for the parts where they need M traffic. The TIMER EXA also checks when there's no EXAs left to send, in which case it goes home to kill XB and clean up the file faster than XB could do itself.
By the way, that FJMP followed by a JUMP at the bottom looks a big ugly but if I don't do it I need an extra NOOP for some reason, which makes the solution a single cycle slower.
At this point I thought of something silly that worked for other assignments: what if I don't calculate anything and just put a file for each name in the home host (9 squares, there's never more than 10 active players, so with some luck we could make that work). No dice, the devs thought of that one, and having multiple files in the home host fails the tests.
I spent a while looking for further speed improvements. I have several ideas. If it would be possible to simplify the algorithm itself that would save time. Another way would be to parallellize the greatest score check somehow, for instance by comparing them pairwise. But with the limited functionality of the single M register I couldn't get anything to work. Optimizing these assignments is really getting quite hard at this stage of the game.
I'll try to get a lower size now.
Code: Select all
;XA
LINK 800
COPY 199 X
MARK GRABLOOP
ADDI X 1 X
REPL GRAB
NOOP
TEST X > 300
FJMP GRABLOOP
LINK -1
KILL
GRAB 400
VOID F
MARK GRAB
GRAB X
SEEK 1
ADDI F F X
ADDI X F X
DIVI X 3 X
MULI F F T
DIVI T F T
ADDI X T X
SUBI F F T
MULI T 20 T
ADDI X T M
SEEK -9999
COPY F M
:XB
MAKE
COPY 0 F
MARK NEXT
SEEK -9999
COPY M X
TEST X > F
FJMP SKIP
COPY M F
SEEK -9999
COPY X F
JUMP NEXT
MARK SKIP
VOID M
JUMP NEXT
The main trick is to get rid of the code that reads the file IDs from file 199. The alternative (looping through all valid file IDs) isn't really any less lines of codes, but because this is so much slower, only a single NOOP in the GRABLOOP is enough to prevent M communication in the wrong order. Having XA KILL XB is also slightly less code than having XB handle this.
I tried removing the initial COPY 199 X and funnily enough that works in most test cases - running the algorithm on the index file just happens to return a negative score most of the time, which is ignored by XB. Sadly, there's at least one test case where it generates a high positive score and writes an invalid result, so we do need this line.
That's it for optimization this update.
Okay, uh. Hmm.
Processing.
It turns out sports betting is not, in fact, a good way to make money.
You should have said something.
The first vote.
Did you know people pay real money for items in online games?
And the vote for the intro for next week's assignment.
God this stuff is getting brain bending, exas help the spatial elements but the Math Fuckery is real hard on me, and also I never did end up doing that first nonogram in a timely manner, I did do it though
Thanks again for delivering unto us this journey with a robot who is stupid
=== Trash World Inbox ===
Last time, we had to write an algorithm to calculate the supposedly winning player in Extreme League Baseball.
It was quite a hard one to optimize, but as always you people came up with some ideas I'd never thought of.
As GuavaMoment explained, the main trick is to have XA store the value in X. That way, checking if a new value is higher only takes one cycle, as long as you send the value again so it can actually be stored if needed. Doing this also saves time at the end because you don't need to clean up the value from the file.GuavaMoment wrote:86/47/3.Code: Select all
XA: MAKE VOID M COPY M X COPY M F JUMP START MARK STUFF VOID M VOID M MARK START TEST M > X FJMP STUFF COPY M X SEEK -1 COPY M F JUMP START XB: LINK 800 GRAB 199 MARK START COPY F T REPL CALC NOOP *delays the next calculating EXA from coming out too soon* NOOP NOOP TEST EOF FJMP START DROP LINK -1 COPY 4 T *a delay loop to slow down the upcoming kill command until everything is done* MARK LOOP SUBI T 1 T TJMP LOOP KILL MARK CALC GRAB T SEEK 1 ADDI F F X ADDI F X X DIVI X 3 X MULI F F T DIVI T F T ADDI X T X SUBI F F T MULI T 20 T ADDI X T M SEEK -999 ADDI X T M COPY F M
The @REPs sound like a simple solution but they really aren't. This solution is so fast because there's the minimum amount of waiting between EXAs. And that's possible because both the algorithm EXA (XA) and the comparator (XB) are in the same host. They start in global mode, but as soon as an XA REPL sends the first value they both immediately switch to local mode. That means all other XA REPLs just have to wait their turn while this particular XA can safely send the name of the player to XB without interruptions. As soon as it's done, XB switches back to global and gets the next value.silentsnack wrote: Similar to the Wonderdisc puzzle, we can reduce cycles for logic-jumps by @REPing some of the slowest and most repetitive processing
79/85/7Code: Select all
;XA LINK 800 GRAB 199 @REP 10 COPY F T REPL DO_STUFF @END MARK DO_STUFF GRAB T SEEK 1 ADDI F F X ADDI X F X DIVI X 3 X MULI F F T DIVI T F T ADDI X T X SUBI F F T MULI T 20 T ADDI X T X TEST X > 341 DIVI X T M MODE SEEK -9 COPY F M ;XB LINK 800 MAKE COPY M X MODE JUMP COPY MARK MAX SEEK -2 MARK COPY COPY M F COPY X F MARK NEXT @REP 3 MODE SEEK -1 COPY M X TEST X > F MODE TJMP MAX VOID M @END JUMP NEXT ;XC COPY 34 T MARK WAIT SUBI T 1 T TJMP WAIT LINK 800 NOOP KILL KILL KILL GRAB 400 LINK -1 SEEK 1 VOID F
As a side effect, this makes the timing independent of the number of EXAs, meaning that TEST X > 341 trick can be used so EXAs with a score that never wins die immediately. I actually thought of that but couldn't get it to work because it kept throwing off the timing.
I'm pretty sure this one gets its high score because of the veggie-themed labels.silentsnack wrote:As for size optimization, what if we compared scores directly instead of sending a bunch of busywork transmissions and VOIDing them?110/32/2Code: Select all
;XA -- LOCAL LINK 800 GRAB 199 MARK ARTICHOKE COPY F T REPL BROCCOLI JUMP ARTICHOKE MARK DISJUNCTION MODE COPY F M MARK BROCCOLI GRAB T SEEK 1 ADDI F F X ADDI X F X DIVI X 3 X MULI F F T DIVI T F T ADDI X T X SUBI F F T MULI T 20 T ADDI X T X MARK CHIPOTLE COPY X M SEEK -9 TEST MRD FJMP DISJUNCTION TEST X > M TJMP CHIPOTLE ;XB -- GLOBAL MAKE COPY M F ;XC -- LOCAL LINK 800 VOID M
Joking aside, this solution hurts my head. After running the algorithm, each EXA sends the result on local M. The very first one gets voided by XC to prevent the solution from getting stuck. It then checks if another one is sending on M. The timing works out quite precisely - if there's nothing on M this is the last remaining EXA, and the DISJUNCTION jump makes sure the name is written to the file by XB.
If there a value on M, this EXA tests if it has a greater algorithm result than the incoming value. If not, it dies. Otherwise, it jumps back to CHIPOTLE and sends its high score again. At that point the other EXA will be waiting to TEST and since it will have the smaller value, it will die. Another EXA will be ready to send its M and the pattern repeats.
So the EXAs are literally playing an elimination tournament against each other, the last one standing will have the high score. Very clever.
I'm happy to see that nobody changed the core math code. That means I didn't miss any obvious optimizations in there.
=== King's Ransom Online ===
Okay, uh. Hmm.
Processing.
It turns out sports betting is not, in fact, a good way to make money.
You should have said something.
Four votes for each between the threads! It comes down to a coin toss this time.
I thought you'd be smarter than that.
What does that mean?
Don't put this on me.
This is all happening because I'm trying to help you.
Let's continue.
Don't gaslight me, lady.
Next up is King's Ransom Online, the massive multiplayer game that's all the rage right now.
Did you know people pay real money for items in online games?
One vote for "it's pretty common", three for "Real money..." and four for "Losers". Feeling snarky, I see.
Losers...
It's apparently a booming business.
Some people even make their living from games this way.
Normally, it takes a lot of skill and perseverance.
But if someone were able to change the ownership of these digital objects...
Theoretically, that person could become very wealthy overnight.
I'm not suggesting that you do that, of course.
But I am suggesting you try.
I mean, I do need the money. And it's not like I'm stealing real objects, right?
Let's jack in.
OST: EXA Power
The assignment:
- Reset the ownership of all castles and sub-buildings to P00000 (file 300), the player ID for unowned buildings.
- To ensure that there are no witnesses you must first disconnect all connected players. Terminate every EXA in every host before changing any castle or sub-building files anywhere in the network. If you leave an EXA alive in one host while changing a file in another you will fail the task.
- For more information see "Network Exploration: King's Ransom Online" in the second issue of the zine.
More information in the zine.
Alright, ignoring all the old-timey text and that ad for a wristwatch, looks like players are represented by EXAs, and there's two main types of files: ones representing buildings and ones representing weapons.
Each host has a building 200 which is a castle, and a bunch of sub-buildings. Generally, the file IDs of sub buildings are in the castle file:
However, there are cases where sub-buildings have their own sub-buildings so I will need to check for those as well.
I can't see the details of the weapons yet because they're all being held by foreign EXAs. What I do see is two counters in the Goals: there are 14 EXAs to terminate and 26 buildings to clear. This differs a bit between test runs. What seems consistent is there's always between 2 and 6 buildings per host, including the castle, and between 1 and 3 EXAs.
Well, let's start by terminating the EXAs.
Code: Select all
COPY 800 X
LINK X
@REP 5
REPL LINK
ADDI X 1 X
@END
MARK LINK
LINK X
KILL
KILL
KILL
You can see the EXAs do represent players - killing them makes their characters disappear from the screen in the top right. The weapons are as described in the zine. Unfortunately, both the weapons and buildings have IDs in the 200-300 range. I won't be able to just loop over all file IDs to only change the buildings.
I'll just start with the obvious solution then, grabbing the castle, updating its ownership, and grabbing any sub-buildings from its list.
I already got an EXA in every host, and the assignment says I can't start messing with the buildings until the foreign EXAs are gone, so I'll just use the same ones.
Code: Select all
;XA
COPY 800 X
LINK X
COPY M T
@REP 5
REPL LINK
ADDI X 1 X
@END
MARK LINK
LINK X
KILL
KILL
KILL
COPY 200 X
MARK GRABSUBFILE
GRAB X
SEEK 2
COPY T F
MARK SUBFILELP
COPY F X
REPL GRABSUBFILE
JUMP SUBFILELP
;XB
GRAB 300
COPY F M
After the KILLs, the castle file ID (200) is copied into X, and then my EXA grabs the file, seeks to the player ID position, and overwrites it with the null id. For each following sub-building id, it loads the value into X and makes a new EXA that grabs it. This recursive code should get all buildings, no matter how deep it has to go.
However, this solution doesn't quite work. The first XA already starts messing with the buildings before the last one has killed all the foreign EXAs.
I can just start with an ugly workaround, just have everything wait until all foreign EXAs are gone.
Six NOOPs or a short loop do the trick.
48/35/25 as an initial score. The top percentiles are 32, 26 and 25.
What's also interesting is the huge distribution in size. I can imagine that this puzzle is much more devious if you can't figure out recursion. Luckily, I'm quite familiar with it.
Let's look at optimizations. First of all, to lower the cycle count, instead of having that whole list of NOOPs I can make it conditional - earlier EXAs have to wait longer.
Code: Select all
; XA
COPY 800 T
LINK T
COPY M X
@REP 5
REPL LINK
ADDI T 1 T
@END
MARK LINK
LINK T
KILL
KILL
KILL
SUBI 807 T T
DIVI T 2 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
COPY 200 T
MARK GRABSUBFILE
GRAB T
SEEK 2
COPY X F
MARK SUBFILELP
COPY F T
REPL GRABSUBFILE
JUMP SUBFILELP
;XB
GRAB 300
COPY F M
I can shorten the REPL loop by reading hardcoded LINK ids from M instead.
Code: Select all
;XA
LINK 800
COPY M X
@REP 5
REPL LINK
@END
MARK LINK
LINK M
KILL
KILL
KILL
NOOP
NOOP
COPY 200 T
MARK GRABSUBFILE
GRAB T
SEEK 2
COPY X F
MARK SUBFILELP
COPY F T
REPL GRABSUBFILE
JUMP SUBFILELP
;XB
GRAB 300
COPY F M
;XC
REPL Y
REPL X
COPY 800 M
HALT
MARK X
COPY 801 M
HALT
MARK Y
NOOP
COPY 802 M
;XD
REPL Y
REPL X
COPY 803 M
HALT
MARK X
COPY 804 M
HALT
MARK Y
NOOP
COPY 805 M
Sending the player ID over M costs two cycles at the top, while we're wasting cycles with NOOPs further down. If I send that message later, I need to send it 6 times but with some smart timing I can save two cycles.
Code: Select all
;XA
LINK 800
@REP 5
REPL LINK
@END
MARK LINK
LINK M
KILL
KILL
KILL
NOOP
COPY 200 T
COPY M X
MARK GRABSUBFILE
GRAB T
SEEK 2
COPY X F
MARK SUBFILELP
COPY F T
REPL GRABSUBFILE
JUMP SUBFILELP
;XB
GRAB 300
COPY F X
NOOP
NOOP
NOOP
@REP 5
REPL SEND
@END
MARK SEND
NOOP ; For some reason this timing is fastest
NOOP
COPY X M
;XC and XD same as before.
Starting from the lowest size code above (the 34 LoC one), the first thing I noticed is that the two lines SUBI 807 T T, DIVI T 2 T aren't strictly necessary. As long as the file fiddling doesn't happen too early it's fine. Leaving those out just makes the WAIT loop run 800-odd times and that makes it slow, but it still works.
And since I need that wait loop anyway, why not put the KILL in there? That way it gets executed plenty of times but it saves two additional KILL lines.
Code: Select all
;XA
COPY 800 T
LINK T
COPY M X
@REP 5
REPL LINK
ADDI T 1 T
@END
MARK LINK
LINK T
MARK WAIT
KILL
DIVI T 5 T
TJMP WAIT
COPY 200 T
MARK GRABSUBFILE
GRAB T
SEEK 2
COPY X F
MARK SUBFILELP
COPY F T
REPL GRABSUBFILE
JUMP SUBFILELP
;XB
GRAB 300
COPY F M
I tried some more things, such as putting the REPL LINK in a loop. That didn't work out because T and X are already occupied and adding a counter in some other way costs a lot of lines.
It's also possible to get rid of getting the subfile ids from each file. Use a simple loop counting down from file id 300 and trying every id in turn. However, you do need an extra check to not write to the weapon files. That's doable - just check if the second value is a number or not. But that check made it longer than the current solution. So this is the best I got.
---
Finally, there's activity. I already have top percentile activity, but could I go beyond? Well, no matter what I need 7 LINKs to get the EXAs in position. Add to that 3 KILLs in each of the 6 hosts for the 25 score.
However, the test with the highest number of foreign EXAs has 17 of them. That means that theoretically, you could save one more activity point.
To do so, I'd need to figure out how many foreign EXAs there are per host. How would you do that? You can't communicate with them. The only thing I can think of is fill the hosts with your own EXAs. Once a host is full, both LINK and REPL instructions block until a space is freed.
I spent some time (actually, way too much time) attempting to build a solution. It couldn't quite get it working, but I feel I'm quite close. I'll share what I have, maybe someone in the thread has the missing link.
Code: Select all
;XA LOCAL
COPY 800 X
LINK X
@REP 5
REPL LINK
ADDI X 1 X
@END
MARK LINK
LINK X
; There's a single EXA in each host now.
REPL COUNTER ; This one starts by waiting
REPL FILLER ; also starts by waiting
COPY 200 X
MARK GRABSUBFILE
; Grab all the files recursively. Once you have them jump to END. Do not modify files yet.
GRAB X
SEEK 3
MARK SUBFILELP
TEST EOF
TJMP END
COPY F X
REPL GRABSUBFILE
JUMP SUBFILELP
MARK FILLER
; Wait until all files are grabbed
COPY 20 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
; Replicate until all squares are filled, but not more than 8 times. After replicating, each EXA goes to END.
COPY 8 T
MARK FILLRUP
SUBI T 1 T
NOOP
FJMP END
REPL FILLRUP ; This will block once the squares are filled.
JUMP END
MARK COUNTER
; Wait until GRABSUBFILE and FILLER are done.
COPY 36 T
MARK WAIT2
SUBI T 1 T
TJMP WAIT2
COPY 0 X ; Add all local M values together. There are sent from END, below.
MARK NEXTM
TEST MRD
FJMP NEXTVOID
ADDI M X X
JUMP NEXTM
; After reading from M, the EXA who sent it halts, freeing up a space. That causes FILLRUP to continue until it's at 8 repl steps.
; This voids their M messages so they stop immediately.
MARK NEXTVOID
NOOP
TEST MRD
FJMP ENDVOID
VOID M
JUMP NEXTVOID
MARK ENDVOID
; At this point all EXAs except this one are gone from the host. This one has the total of the M messages in X.
; X = 12 squares minus this EXA, minus the EXA where REPL was blocking, minus the number of foreign EXAs
; so 10 - X = number of foreign EXAs.
SUBI 10 X T
; Kill em.
MARK KILLOOP
KILL
SUBI T 1 T
TJMP KILLOOP
; Switch to global mode, grab the files again, request the player ID and update the files.
COPY 200 X
MODE
MARK GRABSUBFILE2
GRAB X
SEEK 2
COPY M F
MARK SUBFILELP2
COPY F X
REPL GRABSUBFILE2
JUMP SUBFILELP2
MARK END
COPY 1 M
; ---- XB ---- (global)
; Wait until all foreign EXAs are killed (has to be timing based, limited options here)
; Then start sending the player ID on global M. 36 times = the maximum number of files in any test.
GRAB 300
COPY F X
COPY 88 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
COPY 36 T
MARK LOOP
COPY X M
SUBI T 1 T
TJMP LOOP
; ---- XC ---- (global)
; Wait until all files are updated, then VOID M any remaining sends from XB.
; KILL is not an option.
COPY 155 T
MARK LP
SUBI T 1 T
TJMP LP
MARK ENDLOOP
VOID M
NOOP
NOOP
TEST MRD
TJMP ENDLOOP
If you replace SUBI 10 X T with COPY 3 T to hardcode the number of kills, it does run with an activity of 25, proving that the logic is sound.
It's necessary to create file grabbing EXAs twice, because KILLs prefer own EXAs.
The one problem I couldn't resolve is the fact that as soon as the XA counter starts reading from M, those file grab and filler EXAs start dying, so the blocked REPL one starts running again, and sends to M before the counter is done counting, causing a wrong total. I tried a lot of different ways to slow them down, but nothing quite worked.
That's it for optimizations. But there's one more thing. This level has a Steam achievement tied to it, but it's quite tricky to get. It is called KLEPTOMANCER, and you might figure out it's for this level by its description, Verily hath every item been unduly purloined. What you need to do is move all weapons into the home host while leaving buildings alone. Since sub-buildings aren't locked to a host this takes a bit of work.
At least for an achievement none of the regular rules apply - the EXAs don't need to finish, for instance.
Code: Select all
;XA
COPY 800 X
LINK X
@REP 5
REPL LINK
ADDI X 1 X
@END
MARK LINK
LINK X
KILL
KILL
KILL
COPY 199 X
MARK GRABFILES
ADDI X 1 X
REPL GRABAFILE
JUMP GRABFILES
MARK GRABAFILE
GRAB X
SEEK 1
TEST F < 9999
DIVI 1 T T
LINK -1
LINK -1
;XB
GRAB 300
WIPE
I think this is right. Steam achievements don't trigger a second time after unlocking them in a previous playthrough, so I hope I understood the unlock requirements correctly.
Moving on...
Oh. They just shut the whole game down.
Something about "hackers" or something?
I guess that means you.
People are taking this so seriously.
The first vote.
You know what really moves people?
Art.
And the vote for the intro for next time.
A fun fact to end with: there's an annoying little bug in the game, where if you beat a level, and then directly quit out of the game without going to the 'desktop' (level select with Ember etc.), the next part of the CHATSUBO chat doesn't trigger. I have no idea if it catches up eventually or not.
While working on this update I shut down the game without properly exiting, and the bug triggered. Since I like to keep the chat in sync with the rest of the story, I had no choice but to resort to hacking the save files to "unsolve" the level so I could re-trigger the chat. It feels extremely meta, but there you have it.
Last Call BBS seems like a culmination and combination of everything Zachtronics has done over the last decade-plus of indie game development. It's a collection of eight puzzle minigames ("although some of them are quite big, at least the size of TIS-100"), all accessed through a fictional retro computer called the Sawayama Z5 Powerlance.
...
Sadly, Last Call BBS will be a last call for Zachtronics. As Zach Barth told Press X to Learn earlier this year, he's working as a high school computer science teacher these days. Given how many people have picked up elements of programming from the games he's worked on that seems pretty apt, but obviously full-time teaching doesn't leave a lot of hours free for game development.
=== Trash World Inbox ===
Best scores I got on King's Ransom Online were 37, 30 and 25. Let's see your improvements.
28/85/33. Nice clean solution, XB and XC together make quick work of the foreign EXAs, and XA does the file stuff.GuavaMoment wrote:All six exas go into their areas simultaneously, then there's a lot of loop unrolling. Create an exa to replace the ID for every file. EXAs die if they reach the end of file, and you only have to check once for sub-sub buildings.Code: Select all
XA GRAB 300 COPY F X LINK 800 REPL 803 WIPE REPL 804 LINK 800 NOOP MARK DEL GRAB 200 SEEK 2 COPY X F COPY F T REPL D2 COPY F T REPL D2 COPY F T REPL D2 COPY F T REPL D2 COPY F T REPL D2 MARK D2 GRAB T SEEK 2 COPY X F COPY F T DROP GRAB T SEEK 2 COPY X F HALT MARK 803 REPL 802 REPL 801 LINK 803 JUMP DEL MARK 804 LINK 804 JUMP DEL MARK 805 LINK 805 JUMP DEL MARK 802 REPL 805 LINK 802 JUMP DEL MARK 801 LINK 801 JUMP DEL XB NOOP LINK 800 REPL 802 LINK 803 KILL KILL KILL MARK 802 LINK 802 KILL KILL KILL XC LINK 800 REPL 805 REPL 804 LINK 800 KILL KILL KILL MARK 805 REPL 801 LINK 805 KILL KILL KILL MARK 804 LINK 804 KILL KILL KILL MARK 801 LINK 801 KILL KILL KILL
The description speaks for itself. I keep forgetting about that MODI -1 T T trick to subtract one and automatically die when it hits zero. Also, the TJMP MANY is just a convenient way to make those EXAs that grabbed a weapon harmless. They'll REPL into something that links nowhere and then T hits zero (since the TEST set it to 1) and it dies.GuavaMoment wrote:Now for low line - 1623/20/25. So we need each exa to go into rooms numbered around 800, and then grab files in the 200s, right? Start at 806 and count down attempting both! Enter every room from from 0 to 806, grab every file from 0 to 806. After thousands upon thousands of exas die, the countdown reaches 0 and die for good. Weapons have a number in the second file spot, so those get excluded.Code: Select all
GRAB 300 LINK 800 COPY F X WIPE COPY 806 T MARK MANY MODI -1 T T REPL MANY LINK T KILL KILL KILL MARK COPY MODI -1 T T REPL COPY GRAB T SEEK 1 TEST F > 0 TJMP MANY COPY X F
Finally, let's go back to the elusive 24-activity solution. Both GuavaMoment and I came up with a way to do this. I'll start with my own.
The main difference from my attempt in the previous update is that I now fill the hosts with files instead of EXAs. I found files much easier to control.
I'll add some explanations in the code.
Code: Select all
;XA LOCAL
COPY 800 X
LINK X
@REP 5
REPL LINK
ADDI X 1 X
@END
MARK LINK
LINK X
; after getting an EXA in each host, it makes a counter.
REPL COUNTER
; Then the EXA grabs file 200 and each sub file, copying 1 to local M for each file it's about to grab. This EXA and its repls will die once all files have been handled, dropping the files unchanged. This just counts the buildings.
COPY 200 X
MARK GRABSUBFILE
COPY 1 M
GRAB X
SEEK 3
MARK SUBFILELP
COPY F X
REPL GRABSUBFILE
JUMP SUBFILELP
; In this stage, the counter counts the M's coming from the building EXAs just above. It knows when it's done and jumps out to FILLER once MRD is false. The NOOPs make the timing work out.
MARK COUNTER
COPY 0 X
MARK NEXTM
NOOP
NOOP
NOOP
TEST MRD
FJMP FILLER
ADDI M X X
JUMP NEXTM
; After the file counting is done, the FILLER creates this Fill 'r up EXA, which sends a message to M (like the file counter), then makes a file, and waits for M to decide if it should drop it or not.
; If it drops it, it'll immediately try again. An EXA will hang on DROP if there's no place to drop the file.
MARK FILLRUP
COPY 1 M
MAKE
TEST M = 0
FJMP ENDFILL
DROP
JUMP FILLRUP
; This is the next part of the counting EXA. After creating Fill 'r up, it starts counting further, similarly to the building counter above. The big difference is it sends 0 to M to tell Fill 'r up to keep going.
; Once MRD is false, we know Fill 'r up is hanging on a DROP and continue.
MARK FILLER
REPL FILLRUP
MARK NEXTDROPM
NOOP
NOOP
TEST MRD
FJMP COUNTDONE
ADDI M X X
COPY 0 M
JUMP NEXTDROPM
; The number of foreign EXAs is simple to calculate: X will contain the number of buildings plus the files that were just created. There's 12 places so we just need to calculate the difference.
; It's off by one because this EXA takes up a spot itself.
MARK COUNTDONE
SUBI 11 X T
; Before killing we need to clean up the mess. Fill 'r up is blocking on DROP while holding a file. We obviously can't use a KILL on it, that would beat the purpose.
; So we just grab file 200 (which always exists), which unblocks Fill 'r up, and lets us send -1, which makes it jump to ENDFILL, where it just WIPEs the file it's holding and then dies.
GRAB 200
VOID M
COPY -1 M
NOOP
NOOP
; Once that's taken care of, KILL the exact number of foreign EXAs.
MARK KILLOOP
KILL
SUBI T 1 T
TJMP KILLOOP
; To leave no evidence we need to WIPE the empty files we created. Turns out their IDs start at 400 and never go above 440 so this can be done with a simple grab-and-wipe loop.
COPY 399 X
MARK CLEANLP
ADDI X 1 X
REPL CLEANER
TEST X > 441
FJMP CLEANLP
; Finally, go to GLOBAL mode to get the player ID from XB, and go through all the files again. Since the EXA is already holding 200 by this point, it skips the GRAB step with a JUMP the first time round.
MODE
JUMP SUBFILE2START
MARK GRABSUBFILE2
GRAB X
MARK SUBFILE2START
SEEK 2
COPY M F
MARK SUBFILELP2
COPY F X
REPL GRABSUBFILE2
JUMP SUBFILELP2
MARK CLEANER
GRAB X
MARK ENDFILL
WIPE
;XB GLOBAL
; XB is the same as before - it tries to send the player id 36 times because that's the max number of buildings we can have.
; It doesn't need a delay loop at the start this time because the XAs will stay in LOCAL mode until they're ready.
GRAB 300
COPY F X
COPY 36 T
MARK LOOP
COPY X M
SUBI T 1 T
TJMP LOOP
;XC GLOBAL
; XC is completely unchanged. It waits however long it takes for XA/XB to be completely done (turns out to be 205 loops in the slowest test),
; then voids whatever M message from XB remain, so it can die of natural causes.
COPY 205 T
MARK LP
SUBI T 1 T
TJMP LP
MARK ENDLOOP
VOID M
NOOP
NOOP
TEST MRD
TJMP ENDLOOP
The end result is 517/100/24. I had to squeeze a little bit to fit it in the 100 size limit but I didn't have to try too hard. I'm pretty sure I could save more lines if I really wanted to.
Ah, so the EXA-filling solution is possible, with patience. The main trick seems to be to slow down the fill so much that between one EXA being created and the next, there's enough time to count all of them. But, that means that when you're actually creating EXAs to fill up the host, or to clean up the remainder of the 8 EXAs that are being created, you have to wait for that slow fill as well.GuavaMoment wrote: I see you at LINES 100 and I thought the amount of lines was going to be the limiting factor in the solution, so I used my really low line filegrabbing solution as the base. 1823/81/24 There's a LOT of timewasting to get everything synced up. I still try to grab a LOT of non-existent files which wastes more time. Lower any of the timewasting values by even 1 and something breaks.Code: Select all
XA LOCAL COPY 800 T LINK 800 @REP 5 REPL LINK ADDI T 1 T @END MARK LINK LINK T ; all six rooms are filled with one exa COPY 299 T REPL FILEGRAB MARK WAIT1 ;create exas grabbing files 299 on down, and begin WAIT1 until that's done. Each fileholder will send '1' over M. SUBI T 1 T TJMP WAIT1 COPY 8 X REPL SLOWFILL ;attempt to fill empty spots with 8 exas as CO2 did. Buuuut there's a delay before each filler is made ;we want to count all the fillers later before another is created COPY 0 X COPY 78 T MARK WAIT2 ; and wait2 until everything is filled. SUBI T 1 T TJMP WAIT2 ADDI X M X MARK COUNTLOOP TEST MRD FJMP VOIDLOOP ADDI X M X JUMP COUNTLOOP ;count the number of alive exas before more than one is created MARK VOIDLOOP COPY 16 T MARK WAIT3 SUBI T 1 T TJMP WAIT3 ;void the extra exas created slower than they're made until no more are being replicated TEST MRD FJMP KILLS VOID M JUMP VOIDLOOP MARK KILLS SUBI 11 X T ;the real number is 11 minus the count not twelve since one extra is created ; I don't know it just works MARK KILLING FJMP DONEKILL KILL SUBI T 1 T JUMP KILLING ;hey idiot, change this to a TJMP and remove the FJMP DONEKILL line you say - Well, I'd do that, but it breaks the solution somehow MARK DONEKILL MODE COPY M X ;grab the file value from XB COPY 370 T ;begin grabbing every file from 370 down to 0. Yes, the files start at 299, but this also acts to slow things down and make sure everything times out properly MARK FILEGRAB2 MODI -1 T T REPL FILEGRAB2 GRAB T SEEK 1 TEST F > 0 TJMP LINK COPY X F MARK SLOWFILL MODI -1 X X REPL FILLER COPY 16 T MARK SLOWWAIT SUBI T 1 T TJMP SLOWWAIT JUMP SLOWFILL MARK FILEGRAB MODI -1 T T REPL FILEGRAB GRAB T MARK FILLER COPY 1 M ------------------- XB GLOBAL GRAB 300 COPY F X COPY 5 T MARK HERE COPY X M MODI -1 T T JUMP HERE
=== KGOG-TV - Satellite Uplink ===
Oh. They just shut the whole game down.
Something about "hackers" or something?
I guess that means you.
People are taking this so seriously.
Three votes for real money, one for "their loss".
It is real money...
People act like the online world is different, but it really isn't.
At least as far as human behavior goes.
Next up... Ember wants us to upload a tv program? Okay.
You know what really moves people?
Art.
One vote for "That's why art is powerful", but three people didn't like where this was going.
I already don't like where this is going...
I've been working on something creative.
An abstract video piece.
It's an expression of some of the ideas I've been developing around shape, form, and color.
You'll see.
Let's get it out there.
Don't worry too much, Ember just wants her art to be seen. Seeing how recently NFTs gained popularity, I doubt Zach had even heard of them when he made this game.
OST: Behind the Scenes
The assignment:
- Align the satellite dish with the target satellite by setting the azimuth, elevation and frequency. Then transmit the data from EMBER-2's video (file 301) after encrypting it using the TV station's encryption key (file 199).
- The azimuth, elevation, and frequency of the target satellite are available in file 300.
- Note that you must align the satellite dish before transmitting any data.
- For more information see "Look to the Stars with Satellite Uplink Systems" in the second issue of the zine.
Ember, how did you even get us inside a tv transmission satellite dish?
Before I'll grab the zine, it might interest you that K-GOG has the shows "Ten Men and a Dog", "City Livin'", "Cooking for Losers", "Takedown", "Buy it, Love it", and "Futurelook 2000" lined up. Which is your favorite?
Okay, so writing to the motor registers moves the dish, while I can read the current position from the #AZIM and #ELEV registers. The encryption is quite basic as encryption schemes go, add the first value of the data to the first value of the encryption key, modulo 10000. Repeat with the second value of the data and the second value of the encryption key and so on. If there's more data than values in the encryption key, wrap around to the beginning of the key. A complication is be that EXAs are limited to 9999, so I'll need a trick for the modulo operation.
But first, let's get the sat aligned.
Code: Select all
GRAB 300
LINK 800
LINK 799
SEEK 1
COPY F X
REPL AZI
SEEK 1
COPY F X
REPL ELEV
SEEK 1
COPY F #FREQ
HALT
MARK AZI
LINK 800
SUBI X #AZIM X
TEST X > 0
TJMP MOVEUP
MARK MOVEDOWN
MODI 1 X X
COPY -1 #MOTR
JUMP MOVEDOWN
MARK ELEV
LINK 801
SUBI X #ELEV X
TEST X > 0
FJMP MOVEDOWN
MARK MOVEUP
MODI -1 X X
COPY 1 #MOTR
JUMP MOVEUP
The motors only can move one step at a time. So the azimuth and elevation EXAs calculate the difference between the current value and where they should be, and then either move up and down. The move loops are interwoven so that both the AZI and ELEV only need one conditional jump and can fall through otherwise. And I use the MODI trick GuavaMoment helped me remember to have the EXAs die automatically when they're done.
The uplink status on the top right says "Link: Locked" once all settings are right.
Next up is encrypting the file. To keep the activity as low as possible I think it's best if I send the dish settings over M so I can carry file 301 with me and handle that without a lot of messaging.
Let's go through it step by step.
XB is a simple EXA that grabs file 300, and sends the azimuth, elevation and frequency over M:
Code: Select all
GRAB 300
MARK LOOP
SEEK 1
COPY F M
JUMP LOOP
Code: Select all
GRAB 301
LINK 800
REPL ENCRFILE
LINK 799
REPL AZI
REPL ELEV
COPY M #FREQ
[...]
MARK AZI
LINK 800
SUBI M #AZIM X
TEST X > 0
TJMP MOVEUP
MARK MOVEDOWN
MODI 1 X X
COPY -1 #MOTR
JUMP MOVEDOWN
MARK ELEV
LINK 801
SUBI M #ELEV X
TEST X > 0
FJMP MOVEDOWN
MARK MOVEUP
MODI -1 X X
COPY 1 #MOTR
JUMP MOVEUP
Let's look at the ENCRFILE EXA next.
Code: Select all
MARK ENCRFILE
GRAB 199
COPY 49 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
MARK SENDKEY
COPY F M
DIVI 0 M X
TEST EOF
TJMP RESET
JUMP SENDKEY
MARK RESET
SEEK -9999
JUMP SENDKEY
Anyway, after sending a key value, it waits for an incoming M. To cause this EXA to stop at the end, it dies if M is zero.
Otherwise, it tests if it's at the end of the encryption file and if so starts over from the beginning of the file. Either way it sends the next value and repeats.
Code: Select all
MODE
REPL SENDER
SEEK 1
MARK NEXTDATA
COPY F M
TEST EOF
FJMP NEXTDATA
COPY 0 M
MODE
VOID M
COPY 0 M
JUMP DONE
Code: Select all
MARK SENDER
MAKE
MARK NEXT
COPY M T
FJMP DONE
COPY T X
MODE
COPY M F
SEEK -1
COPY 1 M
ADDI X F F
SEEK -1
TEST F = 9999
FJMP SENDDIRECT
SEEK -2
SWIZ F 0004 T
SWIZ X 0004 F
SEEK -1
ADDI T F T
SWIZ T 1000 T
SWIZ X 0321 X
ADDI X T T
SEEK -2
SWIZ F 0321 X
ADDI X T #DATA
MODE
JUMP NEXT
MARK SENDDIRECT
SEEK -1
COPY F #DATA
MODE
JUMP NEXT
MARK DONE
WIPE
For the encryption, it first copies the data value to X (I needed it in T to see quickly if it was zero, but I need to free T for an upcoming test). Then the EXA switches to global mode and puts the encryption value in F. It tells the ENCRFILE EXA to get ready to send another message, and adds up the data and encryption values, saving that to the file. If the result is less than 9999, it sends the data right away. Otherwise it needs to do something tricky to get the "modulo 10000" requirement.
The SWIZ 0004 instructions return a number that contains the thousands digit of the original, in the ones place. Those can be added together. For instance, if the result is 15, that means the full result would be 15xxx, modulo 10K makes 5xxx. That's what the SWIZ 1000 does. It takes the digit in the ones place, and puts it in the thousands place. The digit is where it should be and we get the modulo operation for free.
Next I take only the lower three digits of the data and encryption value using SWIZ 0321 instructions, add everything together, and send it to #DATA.
The top percentiles are 180, 56 and 4. I'm not wasting any activity points but there are improvements for the other metrics.
There is in fact a much, much simpler way to implement the encryption algorithm. EXAs do have a range that goes beyond 10000 values - we just need to use it. It's negative numbers, of course.
I can rewrite the whole SENDER code to just this:
Code: Select all
MARK SENDER
COPY M X
TEST X = 0
TJMP DONE
SUBI X 9999 X
MODE
ADDI M X X
TEST X < 0
COPY 1 M
TJMP ADD
SUBI X 1 #DATA
MODE
JUMP SENDER
MARK ADD
ADDI 9999 X #DATA
MODE
JUMP SENDER
Now there's two possibilities. Either the result is less than 0 (the total was less than 9999), in which case I can just re-add the 9999 and send to #DATA. Or it's more, in which case we would have to apply the modulo operation. Since that's equivalent to subtracting 10000, and I already subtracted 9999, I just subtract one more and that's it. No more temporary files, everything can be done in-place.
Because it's so much faster, I had to bump ENCRFILE's wait time to 55 loops. This solution clocks in at 431/76/4. Less lines of code and less cycles.
You might've already realized the next step. Since the temporarily file isn't necessary anymore, we can delete the entire dedicated SENDER EXA and have the main data one handle the encryption logic. That skips a lot of M transmissions.
Code: Select all
; XA
GRAB 301
LINK 800
REPL ENCRFILE
LINK 799
REPL AZI
REPL ELEV
COPY M #FREQ
SEEK 1
MARK NEXTDATA
TEST EOF
TJMP DONE
SUBI F 9999 X
ADDI M X X
TEST X < 0
COPY 1 M
TJMP ADD
SUBI X 1 #DATA
JUMP NEXTDATA
MARK ADD
ADDI 9999 X #DATA
JUMP NEXTDATA
MARK AZI
LINK 800
SUBI M #AZIM X
TEST X > 0
TJMP MOVEUP
MARK MOVEDOWN
MODI 1 X X
COPY -1 #MOTR
JUMP MOVEDOWN
MARK ELEV
LINK 801
SUBI M #ELEV X
TEST X > 0
FJMP MOVEDOWN
MARK MOVEUP
MODI -1 X X
COPY 1 #MOTR
JUMP MOVEUP
MARK ENCRFILE
GRAB 199
COPY 55 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
MARK SENDKEY
COPY F M
DIVI 0 M X
TEST EOF
TJMP RESET
JUMP SENDKEY
MARK RESET
SEEK -9999
JUMP SENDKEY
MARK DONE
VOID M
WIPE
COPY 0 M
; XB
GRAB 300
MARK LOOP
SEEK 1
COPY F M
JUMP LOOP
Now that I don't need to worry about activity anymore, I can also get rid of that annoying wait by doing the encryption in parallel with setting the dish.
Code: Select all
; XA
GRAB 301
LINK 800
REPL SETDISH
MODE
REPL ENCRFILE
VOID F
MARK NEXTDATA
TEST EOF
TJMP DONE
SUBI F 9999 X
ADDI M X X
SEEK -1
TEST X < 0
TJMP ADD
SUBI X 1 F
JUMP NEXTDATA
MARK ADD
ADDI 9999 X F
JUMP NEXTDATA
MARK AZI
LINK 800
SUBI X #AZIM X
TEST X > 0
TJMP MOVEUP
MARK MOVEDOWN
MODI 1 X X
COPY -1 #MOTR
JUMP MOVEDOWN
MARK ELEV
LINK 801
SUBI X #ELEV X
TEST X > 0
FJMP MOVEDOWN
MARK MOVEUP
MODI -1 X X
COPY 1 #MOTR
JUMP MOVEUP
MARK ENCRFILE
GRAB 199
MARK SENDKEY
COPY F M
TEST EOF
TJMP RESET
JUMP SENDKEY
MARK RESET
SEEK -9999
JUMP SENDKEY
MARK SETDISH
LINK 799
COPY M X
REPL AZI
COPY M X
REPL ELEV
COPY M #FREQ
MARK DONE
KILL
LINK 799
SEEK -9999
@REP 24
COPY F #DATA
@END
WIPE
;XB was unchanged
While SETDISH does its thing, the ENCRFILE EXA and the data one are sitting in the SECURE host, communicating in local mode. The data EXA overwrites the values in Ember's file with the encrypted values. At the end it kills the ENCRFILE EXA, hops over to the transmitter, and sends all the contents of the file.
I shuffled some code around, mostly to make sure EXAs that are finished fall through into code that makes them stop themselves. The different use of M somehow changed the order the dish EXAs receive the data in so I had to move the COPY M instructions back to the main SETDISH code.
Next, I noticed that the encryption algorithm is still the slowest step by far. The most obvious thing I can remove from it are the two cycles to check EOF. I can't unroll that loop much, I don't have a lot of lines left, but there's another way.
Code: Select all
REPL GRABBER
VOID F
MARK NEXTDATA
@REP 2
SUBI F 9999 X
ADDI M X X
SEEK -1
TEST X < 0
TJMP ADD
SUBI X 1 F
@END
JUMP NEXTDATA
MARK ADD
ADDI X 9999 F
JUMP NEXTDATA
[...]
MARK GRABBER
REPL GRAB
NOOP
NOOP
JUMP GRABBER
MARK GRAB
GRAB 301
KILL
KILL
; Take the file to the transmission host and send all the data as before
In this solution, the encryption algorithm is still the slowest step. It's possible to change that, for instance by writing the raw value to F before checking if it's negative, and handling that later, when sending the data. However, that means you can't fully unroll the sender code anymore with the @REP 24, so you need TEST EOF instructions and everything, and it slows down the whole program significantly.
So, I'll end it today with top scores of 207 cycles and 63 size.
By the way, I hadn't noticed it before but there's some sort of ad in the zine, just after the satellite dish article.
Sounds neat.
Well, what did you think?
The first vote. And for next time...
Okay, no more trying to find shortcuts.
Time for a brute-force solution to the money problem.
I know you like messing around with bank systems...
=== Trash World Inbox ===
silentsnack has some nice improvements again for KGOG-TV.
There's a lot of smart stuff going on here. XA moves into the #AZIM host and back, which I guess takes less lines than making a REPL. It then stores the #ELEV value in X, sets the frequency, goes to move the elevation, and then messages on M when it's done.silentsnack wrote:small solution327/44/102Code: Select all
;XA GRAB 300 LINK 800 LINK 799 VOID F COPY F X LINK 800 MARK A_LOOP SUBI X #AZIM T COPY T #MOTR TJMP A_LOOP LINK -1 VOID F COPY F X VOID F COPY F #FREQ WIPE LINK 801 MARK E_LOOP SUBI X #ELEV T COPY T #MOTR TJMP E_LOOP VOID M ;XB LINK 800 COPY 0 M MARK KEY_RESET GRAB 199 MARK KEY_LOOP SUBI F 5000 X SUBI X 5000 X REPL CODEC JUMP KEY_LOOP MARK CODEC LINK -1 GRAB 301 VOID F ADDI X F X DROP LINK 800 REPL KEY_RESET LINK 799 TEST X < 0 MULI T 5000 T ADDI X T X ADDI X T #DATA
XB grabs the key file, gets a value each round and subtracts 10000 to prevent overflow, then makes a REPL which goes back to get the data. Since the repl can use VOID F to remove values it already read, there's no need to keep track of where in the data file we are. The EXA also drops a new KEY_RESET EXA in case the main XB is at the end of the file. Usually KEY_RESET dies immediately because the file isn't available, but if it grabs it, the pointer is at the beginning of the file so no SEEKs are necessary.
A and B have to start in GLOBAL mode, C and D in LOCAL.silentsnack wrote:fast solution:81/99/37Code: Select all
;XA- MY AZIMUTH CAN'T BE ;THE RATE-LIMITNG STEP LINK 800 LINK 799 LINK 800 COPY M X MARK A_LOOP @REP 8 SUBI X #AZIM #MOTR @END TEST X = #AZIM FJMP A_LOOP ;XB- I GOT SENT TO THE ;UPLINK HOST AND NOW I'M ;STUCK BUFFERING OUTPUT GRAB 300 VOID F COPY F M LINK 800 LINK 799 COPY 15 T REPL DATA VOID F COPY F X VOID F COPY F #FREQ WIPE LINK 801 MARK E_LOOP SUBI X #ELEV #MOTR SUBI X #ELEV T MODI 1 T #MOTR JUMP E_LOOP MARK DATA MODI -2 T T MAKE COPY M F COPY M F FJMP LAST COPY M F REPL DATA MARK WAIT_ALIGN SUBI T 1 T TJMP WAIT_ALIGN SEEK -3 @REP 3 COPY F #DATA @END WIPE MARK LAST SEEK -2 @REP 2 COPY F #DATA @END WIPE COPY M #DATA ;XC- LET'S TRANSMIT THE ;DATA IMMEDIATELY!! GRAB 301 LINK 800 VOID F MARK READ_LOOP @REP 3 ADDI F M X; LOCAL REPL ENCODE @END JUMP READ_LOOP MARK ENCODE LINK -1 TEST X < 0 MULI T 5000 T ADDI X T X MODE ADDI X T M;GLOBAL ;XD- DO STUPID SELF- ;-REFERENCING JOKES ;DREAM OF BREAKING ;THE FOURTH WALL? LINK 800 REPL START COPY 33 T MARK WAIT_CLEANUP SUBI T 1 T TJMP WAIT_CLEANUP KILL KILL KILL KILL GRAB 301 LINK -1 HALT MARK KEY_OUT SUBI X 5000 M;LOCAL ;GRAB?? OR CRASH MARK START GRAB 199 SUBI F 5000 X MARK KEY_LOOP @REP 3 REPL KEY_OUT SUBI F 5000 X @END JUMP KEY_LOOP
81 cycles... how???
Seriously, this code is very hard to understand.
For the dish, you found a way to unroll the loop by sending the difference between the expected and actual value to the #MOTR. It won't move more than one step at a time, even if you send a larger number, but once it's zero you know you're done.
Other than that this solution is mostly very well-timed parallellism. XB makes a whole bunch of copies, each of them buffering 3 values in a file and them sending it to #DATA without any cycles wasted. Somehow it all lines up.
=== Equity First Bank - Money Transfer ===
Well, what did you think?
Two votes for the same option.
I'm not sure I understand it.
That's okay...
Great art is often mysterious.
If you want to know the truth, though, that video was just some garbage data I had lying around.
I just thought it would be fun to broadcast.
You have a unique sense of fun, Ember.
But, I guess you were noticed.
Next up, I'm gonna go to Equity First again. That's the bank where I messed with the ATMs a while ago. Hacking banks is a bit iffy but it might solve my money troubles once and for all.
Okay, no more trying to find shortcuts.
Time for a brute-force solution to the money problem.
I know you like messing around with bank systems...
Another unanimous vote.
Hacking those ATMs was your idea.
Well, you get to do it again!
This time with a little more urgency.
OST: Leave No Trace
The assignment:
- Move EMBER-2's new account (file 300) into checking. Then iterate over the checking accounts listed in the directory (file 199) and, in that order, transfer $1.00 from each target account to EMBER-2's account. Finally, add the file ID of EMBER-2's account file to the end of the directory.
- The keywords CREDIT and DEBIT are available in file 301.
- For more information see "Network Exploration: Equity First Bank" in the first issue of the zine.
Well, this is the same network as before, but they have disconnected all the ATMs. Perhaps I caused more trouble than I thought.
Since we didn't do anything with the files last time, let's read up on them again.
In short, I shouldn't mess with the first 4 values in any account file. To add a transaction from account X to Ember, I need to add a DEBIT transaction line to account X's file, with Ember's account number and the amount. I also need to add a CREDIT transaction with account X's number and the same amount to Ember's file.
The assignment says to do this for checking accounts, so no need to touch the loans. Let's get started.
I'll need the DEBIT and CREDIT keywords, but since they're only two values I think I can bring them along in X and T, which would save an EXA or a lot of M back and forth.
Code: Select all
GRAB 301
COPY F X
COPY F T
DROP
GRAB 300
LINK 800
LINK 800
Code: Select all
REPL DIRECTORY
COPY F T
SEEK 9999
MODE
MARK EMBERWRLOOP
COPY M F
COPY X F
COPY 1 F
COPY 0 F
COPY T M
JUMP EMBERWRLOOP
Code: Select all
MARK DIRECTORY
GRAB 199
COPY T X
MARK NEXTACCOUNT
COPY F T
REPL TRANSACTION
TEST EOF
VOID M
FJMP NEXTACCOUNT
COPY 300 F
KILL
At the EOF it writes Ember's file id to the directory to officially register it, and kills the EMBERWRLOOP EXA.
The final part is the TRANSACTION EXA. It grabs the account file, copies the account ID to EMBERWRLOOP, and adds the $1.00 Credit transaction line to the end of the file. It messages the DIRECTORY EXA when it's done.
Note that the messages between Ember's account and the other accounts are in local mode while the "I'm done" message from the transaction EXA is in global mode. All EXAs are in the same host so it doesn't matter what modes these EXAs start in, but I need both modes to prevent messages getting mixed up.
This initial solution scores 158/40/3. Top percentiles are 55, 40, and 2.
I'm pretty sure there's some smart tricks to save a couple lines. For instance, the file IDs in 199 seem to be in order so you could just loop through all of them. But, you know, I'm happy with top percentile score on size, and I'll leave it to the thread to go beyond.
Let's get the activity down instead. There's only one way to do so - remove that KILL instruction.
I can replace the end of the DIRECTORY code with
Code: Select all
COPY 300 F
MODE
COPY 0 M
Code: Select all
MARK EMBERWRLOOP
COPY M F
SEEK -1
TEST F = 0
TJMP FINISH
COPY X F
COPY 1 F
COPY 0 F
SEEK -9999
COPY F M
SEEK 9999
JUMP EMBERWRLOOP
MARK FINISH
SEEK -1
VOID F
I don't like this slow code, so let's quickly go back to the previous 3-activity solution and look into lowering the cycle count.
The easiest improvement is to not have the transaction EXA wait so long for the Ember EXA, by moving the step where it sends the account number.
Code: Select all
MARK EMBERWRLOOP
COPY M F
COPY T M
COPY X F
COPY 1 F
COPY 0 F
JUMP EMBERWRLOOP
Next, we have to look into parallellism.
Code: Select all
;XA
GRAB 300
LINK 800
LINK 800
COPY M X
MODE
REPL DIRECTORY
COPY F T
MARK EMBERWRLOOP
COPY T M
JUMP EMBERWRLOOP
MARK EMBERAC
MODE
COPY M X
MODE
GRAB 300
SEEK 4
MARK EMBERACLOOP
COPY M F
COPY X F
COPY 1 F
COPY 0 F
JUMP EMBERACLOOP
MARK DIRECTORY
GRAB 199
MARK NEXTACCOUNT
COPY F T
REPL CREDIT
TEST EOF
FJMP NEXTACCOUNT
NOOP
NOOP
NOOP
NOOP
KILL
REPL EMBERAC
SEEK -9999
MARK NEXTACCOUNT2
COPY F T
NOOP
REPL GETACCOUNT
TEST EOF
FJMP NEXTACCOUNT2
COPY 300 F
NOOP
NOOP
KILL
MARK CREDIT
GRAB T
SEEK 9999
COPY M F
COPY X F
COPY 1 F
COPY 0 F
MARK GETACCOUNT
GRAB T
COPY F M
;XB
GRAB 301
SEEK 1
COPY F M
SEEK -9999
COPY F M
XB first sends Debit in global mode, then Credit.
XA makes a REPL still called EMBERWRLOOP which doesn't actually write anymore, it just sends the account number repeatedly. This way, the CREDIT EXAs can just write the $1.00 transaction to all accounts at once. After that is done, we loop through 199 again, grab the files again, and send the account numbers to EMBERAC, who writes the debit lines to Ember's account.
A few more cycles can be won by preparing the Ember Ac Loop ahead of time.
88/55/4
As it stands, unrolling loops doesn't do much because the M timing is rather specific and messing with it tends to break things. I do have a bunch of other ideas. For instance, preparing Ember's account file with transactions with dummy account numbers and then fixing them later might save a couple cycles. Or making use of the fact there's always at least 5 accounts. But no matter what I tried, the transactions ended up in the wrong order.
The timing is tough on this one. 88 isn't even the tenth percentile, but I'm afraid I'm going to stop it here anyway.
Defrauding the bank...
That's textbook crime, isn't it?
Not to worry, your secret's safe with me.
Straight to the vote.
Before I move on to the next assignment, Nivas is here.
Delivery...
Nivas hands me the medication.
This time it's the real version that was "liberated" from the drug company...
I thought it might look more impressive than the bootleg, but it's basically the same.
To be honest, I thought I probably wouldn't see you again.
Didn't think you'd be able to put that kind of money together.
So whatever you did... respect.
Nivas hands me another package of the medication... and another.
These are advance deliveries.
I think... I think I need to take it easy for a while.
The whole thing with the operation we did, those people I mentioned...
It got a little hot for me.
I'm gonna lay low for a bit.
I might do some reading, think about the world we live in...
For some reason I'm in kind of a spiritual mood lately.
Anyway, nice doing business with you.
Take care of yourself.
Nivas closes the door, leaving me with the rest of the medicine.
Interesting character.
Everyone's doing what they can in order to survive...
I hope Nivas is gonna be okay.
Well, at least my medicine problem is solved for the forseeable future.
For next time, there's another hacker battle coming up.
If you keep on winning, they'll eventually have to accept that you're back.
At this point the puzzles are far beyond my ability to ken, at least second hand, I understand what you're doing, but have no clue how I could have done it myself, so I'm just sitting back, enjoying the show, and thinking EMBER-2 needs some reality checks
=== Trash World Inbox ===
I finished Equity First Bank with 88, 40 and 2.
60/59/8. Yeah, the lack of conditionals definitely speeds things up here. Some specific timing to get the parallellism right and you get a fast solution.GuavaMoment wrote:There's what looks like a clumsy write of CREDIT only to grab it again later, but that exa isn't the bottleneck. I try not to use conditionals since you need registers to store other information; this means I need a timing exa to kill things later.Code: Select all
XA local GRAB 301 COPY F M COPY F T LINK 800 LINK 800 COPY T M WIPE XC local GRAB 300 COPY F X SEEK 999 COPY M F LINK 800 LINK 800 COPY M T REPL CALLER MODE SEEK -1 COPY F X SEEK -1 MARK BACK COPY M F COPY X F COPY 1 F COPY 0 F JUMP BACK DROP KILL KILL GRAB 199 SEEK 999 COPY 300 F MARK CALLER GRAB 199 MARK CALLING REPL FILEHOLD COPY F M NOOP JUMP CALLING MARK FILEHOLD GRAB M MODE COPY F M SEEK 999 COPY X F COPY T F COPY 1 F COPY 0 F XB LINK 800 LINK 800 COPY 25 T MARK WAIT SUBI T 1 T TJMP WAIT NOOP KILL KILL GRAB 199 SEEK 999 COPY 300 F
Another important aspect of this solution is that sending their ID is done by a REPL'd EXA, which means that the 'withdraw' EXAs aren't ever blocked by the one holding Ember's file.silentsnack wrote:As for low-cycle solution, sending the number from accounts making withdrawals can be parallelized but at some point the JUMP operation in the file300 write routine imposes a speed bottleneck, so it's possible to save a few cycles by partially unrolling that loop.47/67/6Code: Select all
;XA LINK 800 LINK 800 GRAB 199 COPY M X;GET OUR ID# MARK INDEX COPY F T REPL WITHDRAW TEST EOF FJMP INDEX COPY 300 F @REP 6 NOOP @END KILL HALT MARK PWN MODE COPY T M;SEND THEIR ID# ;THEN CRASH MARK WITHDRAW GRAB T COPY F T REPL PWN SEEK 99 COPY X F COPY M F;GET "DEBIT" COPY 1 F COPY 0 F ;XB GRAB 300 LINK 800 LINK 800 COPY M X;GET "CREDIT" COPY F M;SEND OUR ID# MODE SEEK 99 MARK DEPOSIT @REP 4 COPY M F;GET THEIR ID# COPY X F COPY 1 F COPY 0 F @END JUMP DEPOSIT ;XC GRAB 301 COPY F M;SEND "CREDIT" COPY F X COPY 17 T REPL TIMER MARK WHAT COPY X M;SEND "DEBIT" JUMP WHAT MARK TIMER SUBI T 1 T TJMP TIMER KILL
Cycle count all the way down to 47, but silentsnack also managed to save another line of code.
Yeah. In my 40-lines solution I went for a single EXA, because COPY F M followed by COPY M X in another EXA is two lines for what could be done in one. This solution offsets that by not needing a DROP, nor needing the COPY T X to get 'DEBIT' in a safe place after handling 'CREDIT'. It also helps that the DEPOSIT EXA, which doesn't really need T for anything else could do the TEST MRD to check if we're done, while another EXA actually reads from M.silentsnack wrote:It's tough to optimize size since it requires making tradeoffs, like if you write "300" before scanning the account list you don't need to TEST EOF which means you can use the T register for storage, but it requires SEEK66/39/3Code: Select all
;XA GRAB 300 COPY F X LINK 800 LINK 800 REPL DIRECTORY COPY M X SEEK 99 MODE MARK DEPOSIT COPY M F COPY X F COPY 1 F COPY 0 F TEST MRD TJMP DEPOSIT KILL MARK WITHDRAW GRAB M MODE COPY F M SEEK 99 COPY X F COPY T F COPY 1 F COPY 0 F MARK DIRECTORY GRAB 199 SEEK 99 COPY 300 F SEEK -99 COPY M T MARK INDEX REPL WITHDRAW COPY F M NOOP JUMP INDEX ;XB GRAB 301 COPY F M COPY F M
=== The Wormhole ===
Defrauding the bank...
That's textbook crime, isn't it?
Not to worry, your secret's safe with me.
Everyone voted for the same answer.
Wait, you're part of this too.
Maybe, but they can't arrest me, can they?
Having a physical body is your liability.
That is a surprisingly good point.
[hydroponix]: yeah same
[Ghast] I bet we all have.
While I was talking with Nivas, Ember installed something on my PC.
Happy birthday! I bought a new piece of software for you.
I didn't put this one up for a vote since the dialog doesn't change anyway.
It's not my birthday?
This is a virtual network creation tool.
You can use it to design your own networks.
Maybe you could get someone to try to hack your designs too.
It's all installed and ready to go...
The only non-pirated program on your computer.
I'd just like to interject for a moment - what you're referring to as 'pirated' is in fact free/libre...
In other words, we have just unlocked the level editor. Let's take a quick look.
There are two tabs, the Personal Networks is where you can make your own creations, and the Remote Networks is where you can play other people's levels.
If you choose to edit a level, this actually takes you out of the game and opens a javascript file in your default text editor. It has a link to the documentation, which is written from an in-universe perspective, nice.
The language is quite straightforward, you set up hosts, links between them, files, registers and test cases. You write a description and set the winning goals and that's it. Honestly, just giving people access to the underlying level design language makes a lot of sense, it's a lot less work than making a visual editor and Zach could safely assume that people who made it to this point know how to program.
Anyway, once you finish a level, you can play it yourself in the game, and once you solve it you're allowed to upload it to the Steam Workshop. That's also where you download levels from.
The game even keeps histograms and top percentiles for custom levels.
A regular playthrough of this game is already time-consuming enough as it is, so I'm not planning to show off any custom levels in this LP. Time to return to the regular schedule.
The level editor "level" doesn't seem to have any outro text from Ember. Unusual, even the Redshift sandbox mode had that.
However, I have now filled out the shortcuts bar to the right. From top to bottom, the Russian solitaire game, the Redshift dev kit, HACK*MATCH, and the VirtualNetwork+ level editor.
Next up, a hacker battle against x10x10x
If you keep on winning, they'll eventually have to accept that you're back.
One vote for "It doesn't really matter", three for "I never really left".
I never really left...
But you kind of did.
Enough to make them wonder about you.
That's why you need to prove yourself again.
Time to win.
OST: Getting Started
The assignment:
To win this battle you must fill the network's hosts with as many of your EXAs as you can. Note that each pair of test runs has its own unique network layout, with bi-directional links between hosts that use the prime numbers between 2 and 13 as link IDs (2, 3, 5, 7, 11, and 13).
- Gain one point for every EXA you control in the network at the end of the battle.
- Lose one point every time one of your EXAs executes a KILL instruction.
For more information see "Hacker Battle Domination" in the second issue of the zine.
A rather different kind of hacker battle, you get points by having more EXAs at the end. The EXA limit is set to 100 which is way more than the number of spaces in the network.
They aren't kidding about the different layouts. The second test looks the same as the first but with my and x10x10x's hosts swapped. The third one looks completely different.
Letting x10x10x do their thing without any EXAs on my side, their strategy is very similar to what I had in mind as well. REPL a lot of EXAs, have them try each possible link ID to spread through the network, and also REPL and pause them indefinitely to fill up each host.
Code: Select all
LINK 800
MARK REPLICATE
REPL TWO
REPL THREE
REPL FIVE
REPL SEVEN
REPL ELEVEN
REPL THIRTEEN
MARK INFINITE
REPL INFINITE
MARK TWO
LINK 2
JUMP REPLICATE
MARK THREE
LINK 3
JUMP REPLICATE
MARK FIVE
LINK 5
JUMP REPLICATE
MARK SEVEN
LINK 7
JUMP REPLICATE
MARK ELEVEN
LINK 11
JUMP REPLICATE
MARK THIRTEEN
LINK 13
JUMP REPLICATE
You can see in the 99th cycle that the fight is quite balanced, with x10x10x having just one EXA more than I do.
In the second test I win, apparently because my EXAs managed to fill up that one host before x10x10x could make one that could jump to 13 and that whole side area.
I don't know how my code differs from the opponent's but apparently this is good enough to win all but 4 tests.
But can I do better?
Well, I noticed a small problem with the infinite loop. Yes, each REPL created through it makes another one, but the original one falls through to MARK TWO and most likely dies as most hosts don't have a link 2. If I put a JUMP INFINITE directly under the REPL INFINITE, both EXAs will start replicating and I change linear growth into the much faster quadratic growth.
And that's enough to outrun x10x10x entirely, since the result is an S+ with 100 out of 100 wins.
You still aren't saying anything in the chat room.
Don't you think they might want to hear from you?
That brings us to the first vote.
I've collected a lot of data now, but my understanding remains incomplete.
And the vote for the intro of the upcoming level.
Since there's no further optimization possible for these hacker battles, time for the third nonogram.
As always, use spoiler tags when you post your solution so others can have a go too.
Now that I've posted all nonograms, here's the full page, together with the inside of the back cover. Don't mess with EXAs.
=== Trash World Inbox ===
Regallion, cardinale and biosterous submitted solutions to the nonogram. Here is cardinale's one, in a pretty shade of blue.
It is an EXA, of course.
=== TEC EXA-Blaster™ Modem ===
You still aren't saying anything in the chat room.
Don't you think they might want to hear from you?
Two votes for "Yeah, but", but "There's nothing to say" barely gets the victory at three votes.
There's nothing for me to say.
I have a theory...
You're enjoying it.
You get to be this mysterious silent presence.
I understand the appeal.
The curse and blessing of a silent protagonist.
[=plastered] bullshit.
[x10x10x] yes
[x10x10x] :)
Ember wants me to mess around with a modem now.
I've collected a lot of data now, but my understanding remains incomplete.
Two votes for the bottom option, and three votes for the winner...
What are you trying to understand, exactly?
Everything.
I need more processing power.
Open networks are getting harder to come by.
Luckily, everyone's buying personal dataphones now.
Thanks, consumer rat race!
Let's get me onto those.
What are you up to now, Ember?
OST: Network Exploration
The assignment:
- Using your modem, connect to each dataphone so that EMBER-2 will have a list of valid phone numbers.
- Each dataphone contains a list of the owner's contacts (file 200). The phone number of one of these dataphones is in your host (file 300), while the rest are in the contact list of another dataphone.
- Note that each dataphone (aside from the first) will appear in exactly one other dataphone's contact list, in such a way that you can find them all without getting stuck in a loop.
- For more information see "Hacker Skills: Modem Control at the Direct Level" in the second issue of the zine.
Alright, let's grab the zine first.
A lot of general information, but the only part that truly matters is that you dial by sending the number to the #DIAL register one digit at a time, and you hang up by sending it a -1.
At a glance, all the numbers in the files seem to be 11 digits long. That'll be helpful.
Just sending the 11 digits from file 300 to #DIAL works to make a connection.
This isn't in the game but feel free to imagine it makes this kind of sound.
The connections always use the 800 and -1 link IDs. And the #DIAL register will ignore any further inputs until you send it a -1.
Importantly, if you have an EXA in a remote host and you hang up, no communication with that EXA is possible. Not even global M communication. That makes a lot of sense, they're truly disconnected hosts now, but it's not something we had to deal with before.
That leaves one part of the assignment that isn't obvious: if each number occurs only once (so you don't get loops), how is it possible that some files have multiple numbers?
Well, by messing around with the modem a bit I found out that those are numbers that aren't accessible from my location. The modem will simply say "connection refused" and hang up.
Code: Select all
GRAB 300
LINK 800
MARK NEXT
@REP 11
COPY F #DIAL
@END
REPL M
@REP 11
COPY M F
@END
COPY -1 #DIAL
SEEK -11
JUMP NEXT
MARK M
LINK 800
GRAB 200
SEEK 1
@REP 11
COPY F M
@END
So, I need to read all the numbers from the remote hosts (up to four numbers per host). And then dial them one by one, saving new numbers I encounter, while somehow keeping track of which ones I already tried.
I think I have a general idea on how to tackle that. Let's see if it works.
Code: Select all
GRAB 300
LINK 800
SUBI X 11 X
MARK NEXT
@REP 11
COPY F #DIAL
@END
REPL M
ADDI X 11 X
NOOP
NOOP
TEST MRD
FJMP NEXT
SEEK 9999
MARK GETNEXT
@REP 11
COPY M F
@END
SUBI X 11 X
NOOP
TEST MRD
TJMP GETNEXT
COPY -1 #DIAL
SEEK X
JUMP NEXT
MARK M
LINK 800
GRAB 200
MARK NEXTNR
SEEK 1
@REP 11
COPY F M
@END
JUMP NEXTNR
The M EXA is very simple, it LINKs to the remote host and sends all data over M, only skipping the contact names. It'll die when it reaches the end of the file.
Ignore the main EXA's ADDI X instruction for now - what matters is, it checks if another EXA is sending on M. If so, it copies the whole phone number to the end of its file.
Now it subtracts 11 from X. X contains the value to use in a SEEK instruction to get to the oldest undialed number.
It does another TEST MRD to see if the remote EXA has another number to send, and if so, it stores it too and subtracts another 11 from X.
Once the remote EXA stops, the main one hangs up the modem and SEEKs to X to start dialing the next number. This is where the other X manipulations comes in. Every time it dials a number, it adds 11 to X again so that the index offset to the oldest undialed number is still correct. That's also why I needed the initial SUBI X 11 X, otherwise the value will be off by one phone number for the entire run.
Note that I put the X manipulation in places where I was waiting for MRD anyway. Doesn't cost any extra cycles that way.
This code works in the sense that it connects to every remote host. However, when the main EXA reaches the end of the file and there's no EXAs left sending data, it'll die and drop its file in the modem, which counts as leaving a trace.
That's easily resolved with a cleanup EXA. Add a REPL CLEANUP to the above code, just after the initial LINK 800, and add the following snippet to the bottom.
Code: Select all
MARK CLEANUP
COPY 466 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
GRAB 300
WIPE
The top percentiles are 867, 34 and 9. Except for size, I'm closer than I thought. But what improvements are possible?
At first, I thought I could reduce activity by one more. The goal is to connect to each dataphone. Technically I wouldn't need to send an EXA into the last one, since I got all relevant numbers already.
Well, I tried this and it doesn't work. Turns out the tests are coded to only accept the solution if you actually move an EXA into each remote host. So, 9 is the best possible score here.
Moving the cleanup to its own EXA saves a single cycle (in exchange for an activity point). There's a bigger gain by having M send a message immediately as it arrives, before it starts handling the file. It means the top TEST MRD doesn't have to wait as long for it to get ready. 919/65/10.
The EXA that goes into the last remote host still sends all numbers and the main EXA still tries dialing all of them. There's no point in doing so - as soon as there's an EXA in the last host we can stop.
To do so, I rewrote XB to have a counter of how many EXAs reached the foreign hosts:
Code: Select all
;XB LOCAL
LINK 800
COPY 8 T
MARK AGAIN
SUBI T M T
TJMP AGAIN
KILL
GRAB 300
WIPE
LINK 800
KILL
XA also needs an addition to make this work. I decided to place it directly after the VOID M, because at that point we know an EXA just reached the remote host - and that EXA is busy grabbing the file so I have a spare cycle or two. Since XB is in local mode, my first thought was to put MODE; COPY 1 M; MODE there but that would delay it by 3 cycles. Instead, I made a special REPL to send the LOCAL message.
Code: Select all
;XA GLOBAL
GRAB 300
LINK 800
SUBI X 11 X
MARK NEXT
@REP 11
COPY F #DIAL
@END
REPL M
ADDI X 11 X
TEST MRD
FJMP NEXT
VOID M
REPL MSG
SEEK 9999
MARK GETNEXT
@REP 11
COPY M F
@END
SUBI X 11 X
NOOP
TEST MRD
TJMP GETNEXT
COPY -1 #DIAL
SEEK X
JUMP NEXT
MARK M
LINK 800
COPY 0 M
GRAB 200
MARK NEXTNR
SEEK 1
@REP 11
COPY F M
@END
JUMP NEXTNR
MARK MSG
MODE
COPY 1 M
There's a theoretical next improvement, parallellism. That is, if a remote host has multiple phone numbers, I could try dialing the first one immediately, and leave the EXA in the remote host to make a copy of the other phone numbers. Then later I go retrieve them and try dialing them.
This is very complicated though, since the remote EXAs can't just phone home, the main modem one has to dial old hosts again to see if there's an EXA there with additional phone numbers. I suspect it's slower overall, and it might not even fit in the lines of code limit.
Speaking of size, what improvements are possible there? Of course, I can remove the loop unrolls I started with.
Code: Select all
GRAB 300
LINK 800
SUBI X 11 X
MARK NEXT
TEST EOF
TJMP WIPE
COPY 11 T
MARK DIALLP
COPY F #DIAL
SUBI T 1 T
TJMP DIALLP
REPL M
ADDI X 11 X
NOOP
TEST MRD
FJMP NEXT
SEEK 9999
MARK GETNEXT
VOID M
COPY 11 T
MARK WRITELP
COPY M F
SUBI T 1 T
TJMP WRITELP
SUBI X 11 X
TEST MRD
TJMP GETNEXT
COPY -1 #DIAL
SEEK X
JUMP NEXT
MARK M
LINK 800
GRAB 200
MARK NEXTNR
COPY F M
JUMP NEXTNR
MARK WIPE
WIPE
The read loop in F has been changed more significantly. I don't skip the contact's name anymore, so that's just a single 3-line non-conditional loop. This requires a VOID M on the writing EXA but that beats adding conditions.
Finally, the cleanup loop was a lot of code so I replaced that with a TEST EOF in the main loop. Slow, but it works. Because the timing is different now I could also remove a couple NOOPs. 1760/38/9.
4 over the top percentile. I'll, uh, leave the remaining improvements as an exercise to the reader.
There's a ton of dataphones out there.
Not very powerful individually, but tied together they work somewhat decently.
I'll just be very... distributed.
You know the drill by now.
[deadlock] hah
[deadlock] if you wanted everyone to leave maybe
There is a knock on the door.
Did you do something with your hair?
Hey. I'm not bothering you am, I?
Isadora looks upset.
Did something happen?
You remember I used to work for Last Stop? Started as a cashier, worked my way up?
I always knew they were close-knit.
But I found out it's much worse than that.
It's a full-on cult.
I don't just mean everyone believes in the company. They believe the whole world isn't real!
They say we're all trapped in some kind of fake reality? I don't know.
Fake reality?
That sounds oddly familiar...
Has someone told me this before?
I wouldn't care if that's all they believed.
The real problem is they think they can force their way out with a big enough explosion.
So they're refining materials to make a bomb... a nuclear bomb.
Right next to piles of old snacks...
Wait, what?
Is she serious?
What is this world?
I have the number for the warehouse where it's all set up.
If I give it to you, you could do something, right?
I need to rest... I'm going to go lie down...
Isadora leaves.
So many questions.
Last Stop... a cult? And her, in the middle of it...?
I just need to take this one moment at a time.
I'll drive myself crazy if I try to figure it all out.
===
Let me see if I understood that correctly.
Last Stop, the convenience store chain, is actually a front for a cult.
They believe the world isn't real, and are in the process of creating a nuclear weapon in order to escape it.
And now you're going to attempt to take care of it yourself, instead of alerting the authorities like a normal person.
=== Trash World Inbox ===
My top scores for the modem dialing were 865 cycles and 38 size.
GuavaMoment brought the cycle count down to 852/77/9 by recognizing that the first three digits of the phone numbers (area code?) are always the same. This solution also has some slightly different file handling. -1 is written after a number it if still should be dialed and 0 if it has been dialed. This way old numbers actually get overwritten when a new one comes in. I'm not sure how much difference this makes because there's some jumping back and forth to check the value of that 0/-1 status digit.
silentsnack went for a completely different low cycle solution.GuavaMoment wrote:Code: Select all
GRAB 300 LINK 800 @REP 11 COPY F #DIAL @END REPL LINKS WIPE MAKE VOID M MARK PASTE @REP 8 COPY M F @END COPY -1 F SEEK 999 TEST MRD TJMP PASTE SEEK -54 ADDI X 1 X COPY -1 #DIAL MARK CALLED SEEK 8 TEST F < 0 FJMP CALLED SEEK -9 COPY 4 #DIAL COPY 7 #DIAL COPY 2 #DIAL @REP 8 COPY F #DIAL @END REPL LINKS COPY 0 F;INSTEAD OF NOP TEST MRD FJMP CALLED VOID M TEST X = 7 TJMP END SEEK -9 JUMP PASTE MARK LINKS LINK 800 COPY 999 M TEST X = 7 TJMP END GRAB 200 MARK PASTING SEEK 4 @REP 8 COPY F M @END JUMP PASTING MARK END WIPE
A big timesaver here is to compress two digits into only one M call, by simply putting one in the tens place and the other in the ones place. That takes some extra cycles to compress/decompress but since that can be done in parallel while an M call by itself always takes two cycles it's quite fast. The other trick is to have the 'main' EXA use replication to run some steps in parallel. For instance, it makes a clone, then goes into the remote connection itself to check if it's valid. If so it LINKs back and starts copying phone numbers, otherwise the clone grabs the main phone number file and takes over. This is made possible by storing the offset of the oldest undialed number in X.silentsnack wrote:775/79/21Code: Select all
;XA - GLOBAL GRAB 300 LINK 800 JUMP START MARK SUCCESS MODE ;LAZY HACK VOID M ;BUT IT WORKS? MARK READER GRAB 200 MARK READ SEEK 1 COPY F M @REP 5 MULI F 10 X ADDI X F M @END JUMP READ MARK MAIN_LOOP ADDI X 11 X GRAB 300 MARK START SEEK X @REP 11 COPY F #DIAL @END REPL MAIN_LOOP LINK 800 REPL READER LINK -1 REPL SUCCESS SEEK 9999 MARK WRITE COPY M F @REP 5 COPY M T SWIZ T 2 F SWIZ T 1 F @END TEST MRD TJMP WRITE REPL MAIN_LOOP COPY -1 #DIAL ;XB - LOCAL LINK 800 @REP 8 ;SUCCESS COUNT COPY 0 M @END KILL ;CLEANUP GRAB 300 LINK 800 KILL WIPE
Then, in the greatest collaboration of the century, GuavaMoment combined the fixed area code solution with silentsnack's parallellism solution.
676/67/21. XB was changed to a simple timer to speed up an edge case in one specific test. 'note 1' is about how that NOOP can be removed if you can unroll the main READ loop, but this doesn't seem possible within the max size constraint.GuavaMoment wrote:Code: Select all
;XA GRAB 300 VOID F VOID F VOID F LINK 800 JUMP START MARK READER GRAB 200 MARK READ SEEK 4 @REP 4 MULI F 10 X ADDI X F M @END JUMP READ MARK MAIN_LOOP ADDI X 8 X GRAB 300 MARK START SEEK X COPY 4 #DIAL COPY 7 #DIAL COPY 2 #DIAL @REP 8 COPY F #DIAL @END REPL MAIN_LOOP LINK 800 REPL READER LINK -1 SEEK 9999 MARK WRITE @REP 4 COPY M T SWIZ T 2 F SWIZ T 1 F @END NOOP ; See note 1 TEST MRD TJMP WRITE REPL MAIN_LOOP COPY -1 #DIAL ;XB COPY 334 T MARK WAIT SUBI T 1 T TJMP WAIT LINK 800 KILL GRAB 300 WIPE LINK 800
Finally, silentsnack also posted a solution that only takes 23 lines of code.
It's much lower than what I got and that's for good reason, it uses some clever tricks. It creates a new REPL for EVERY digit, which dials that digit and then checks if there's a connection. If so, the main EXA jumps into writing mode, otherwise it tries with the next digit. This always works - if a full number can't be dialed the modem resets automatically, so as long as you have all the phone numbers in order in the file you don't need extra logic and you can just start dialing the next number immediately. The writer also doesn't bother skipping the contact's name. If the modem is idle, sending that to the modem doesn't do anything and is safe. The only special case is hanging up after a successful connection. This is established by putting a -1 at the start of the file whenever a connection has been made. It does this "too often" but that's the price for low size optimization.silentsnack wrote:3879/23/272Code: Select all
GRAB 300 MARK DIAL_LOOP COPY F X REPL PHONE SEEK -1 VOID F NOOP MARK COPY_LOOP SEEK -9999 TEST MRD FJMP DIAL_LOOP COPY -1 F SEEK 9999 COPY M F JUMP COPY_LOOP MARK PHONE LINK 800 COPY X #DIAL LINK 800 GRAB 200 MARK READ COPY F M JUMP READ
The second trick is the interleaving loops. The lines between MARK COPY_LOOP and FJMP DIAL_LOOP are shared by both. If you're dialing and TEST MRD is false no connection has been made yet, dial the next digit. If it's true start copying. If you're copying and TEST MRD is true, there's another digit to copy, so continue. If it's false it's done and you can start dialing again.
A construct like that isn't really possible in modern high-level languages so I wouldn't have considered it as an option until silentsnack showed it here.
Finally, thanks to TwelveBaud for pointing out Zachtronics released two ports of the HACK*MATCH arcarde game, one is an actual NES port, the other is one of the sub-games of the new game Last Call BBS. Last Call BBS is the very last game that will be released by Zachtronics. However, not all is lost. Zach stated in an interview a week ago that he's not going to pursue his teaching career any further and might decide to go back into creating video games. Another employee, Matthew Burns, wants to continue creating games as well. It's a bit sad for Zach that his teaching career didn't work out, but I'm not going to deny I'd be happy to see more games from his hand.
Anyway, that's enough yapping. I got an LP to write right here.
=== Last Stop Snaxnet - Nuclear centrifuges ===
There's a ton of dataphones out there.
Not very powerful individually, but tied together they work somewhat decently.
I'll just be very... distributed.
A divided vote. One for "You're scaring me", two for "Won't that be slow", and three for the winning choice.
Is it going to be enough?
I'm not sure.
I'll have to try it out and see.
Let's continue.
C'mon Ember, you almost got us discovered.
Last time, Isadora told us about the Last Stop cult. I need to do something about it.
Let me see if I understood that correctly.
Last Stop, the convenience store chain, is actually a front for a cult.
They believe the world isn't real, and are in the process of creating a nuclear weapon in order to escape it.
And now you're going to attempt to take care of it yourself, instead of alerting the authorities like a normal person.
Two votes for "The authorities won't help", four for "Nothing about this is normal".
Nothing about this is normal.
True.
Well, go on, have your fun.
OST: Leave No Trace
The assignment:
- An array of five Zippe-type gas centrifuges, ZGC0 through ZGC4, are connected in a cascade configuration.
- Read each of the #ZGCX registers and determine which centrifuge currently has the highest pressure. Then disable that centrifuge's regulator by writing a value of 0 to its #POWR register. Repeat this process until all five regulators have been disabled.
Man, seeing a nuclear centrifuge control system with the logo of the local convenience store is very weird. There's even store inventory files sitting there.
Well, the assignment sounds straightforward enough, let's get started.
It's important to know that whenever you disable a centrifuge, its pressure drops to zero, and the pressure of all other centrifuges changes such that the next one to shut down is unpredictable.
As you know I like to immediately go for some optimization with my first solution if I can. I decided to go for activity once more. It's a tricky one because you can't just hop back and forth to get to the right centrifuge.
Code: Select all
LINK 800
REPL READER
LINK 798
REPL NEXT_CEN
MAKE
JUMP POWEROFFLP
MARK READER
LINK 799
MAKE
Code: Select all
MARK NEXT_CEN
ADDI 1 X X
LINK 800
REPL NEXT_CEN
MAKE
MARK POWEROFFLP
Code: Select all
COPY M F
SEEK -1
TEST F = X
TJMP DOIT
SEEK -1
COPY F M
COPY 11 T
MARK WT
SUBI T 1 T
TJMP WT
JUMP POWEROFFLP
MARK DOIT
COPY 0 #POWR
COPY -1 M
MARK END
WIPE
I swayed the probability a bit by adding a wait loop after receiving a wrong message so that the same EXA doesn't get the wrong message twice in a row. By trial and error, I found 11 loops to be the fastest.
Finally, the most complicated bit is the actual reader code. I have to find the highest value - and not just that, the EXA needs to remember which centrifuge it belongs to.
Code: Select all
MARK READER
LINK 799
MAKE
MARK CONTINUE
COPY #ZGC0 X
COPY #ZGC1 F
COPY #ZGC2 F
COPY #ZGC3 F
COPY #ZGC4 F
SEEK -9999
MARK TESTNEXT
TEST F > X
SEEK -1
FJMP LOWER
COPY F X
SEEK -1
MARK LOWER
VOID F
TEST EOF
FJMP TESTNEXT
TEST X = 0
TJMP END
Code: Select all
TEST #ZGC0 = X
TJMP ZERO
TEST #ZGC1 = X
TJMP ONE
TEST #ZGC2 = X
TJMP TWO
TEST #ZGC3 = X
TJMP THREE
COPY 4 M
JUMP NXTREAD
MARK ZERO
COPY 0 M
JUMP NXTREAD
MARK ONE
COPY 1 M
JUMP NXTREAD
MARK TWO
COPY 2 M
JUMP NXTREAD
MARK THREE
COPY 3 M
MARK NXTREAD
COPY M X
TEST X = -1
TJMP CONTINUE
COPY X M
JUMP NXTREAD
After sending the value, this EXA has to wait for a message that the centrifuge has powered down. If it doesn't wait, it'll read old values from the registers and get the wrong centrifuge value. Just like the other EXAs, this one might accidentally get a shutdown-a-centrifuge message that's being forwarded by one of the centrifuge EXAs. In that case it needs to forward it again. I didn't bother implementing a timer here because brute-forcing two separate timers to reduce randomness is a lot of work, and this low activity solution isn't going to be the fastest anyway.
The top percentiles sit at 89, 44, 7.
As you can see, when I start shutting down centrifuges, the remaining ones start shaking as their pressure builds up. Shutting down the last one makes the camera lose power. Looks like I've done some real damage.
Optimization time.
To optimize for speed, let's first get rid of that wonky M redirect stuff by dropping the activity requirement.
Code: Select all
LINK 800
LINK 799
MAKE
MARK CONTINUE
COPY #ZGC0 X
COPY #ZGC1 F
COPY #ZGC2 F
COPY #ZGC3 F
COPY #ZGC4 F
SEEK -9999
MARK TESTNEXT
TEST F > X
SEEK -1
FJMP LOWER
COPY F X
SEEK -1
MARK LOWER
VOID F
TEST EOF
FJMP TESTNEXT
TEST X = 0
TJMP END
REPL WRITER
TEST #ZGC1 = X
TJMP ONE
TEST #ZGC2 = X
TJMP TWO
TEST #ZGC3 = X
TJMP THREE
TEST #ZGC4 = X
TJMP FOUR
COPY 0 M
JUMP NXTREAD
MARK ONE
COPY 1 M
JUMP NXTREAD
MARK TWO
COPY 2 M
JUMP NXTREAD
MARK THREE
COPY 3 M
JUMP NXTREAD
MARK FOUR
COPY 4 M
MARK NXTREAD
COPY 8 T
MARK WT
SUBI T 1 T
TJMP WT
JUMP CONTINUE
MARK WRITER
LINK -1
LINK 798
COPY M T
MARK LINK
FJMP SHUTOFF
LINK 800
SUBI T 1 T
JUMP LINK
MARK SHUTOFF
COPY 0 #POWR
MARK END
WIPE
Clearly the slowest part is finding the highest value. So for my next change I tried to parallellize that a bit and do a kind of binary search.
Code: Select all
LINK 800
LINK 799
MAKE
MARK CONTINUE
REPL A
REPL B
COPY #ZGC4 F
SEEK -1
COPY M X
TEST X > F
TJMP COMPARE_X
SEEK -1
COPY F X
MARK COMPARE_X
COPY M F
SEEK -1
TEST F > X
FJMP SKIP_GET_F
SEEK -1
COPY F X
MARK SKIP_GET_F
TEST X = 0
TJMP END
REPL WRITER
TEST #ZGC1 = X
TJMP ONE
TEST #ZGC2 = X
TJMP TWO
TEST #ZGC3 = X
TJMP THREE
TEST #ZGC4 = X
TJMP FOUR
COPY 0 M
JUMP NXTREAD
MARK ONE
COPY 1 M
JUMP NXTREAD
MARK TWO
COPY 2 M
JUMP NXTREAD
MARK THREE
COPY 3 M
JUMP NXTREAD
MARK FOUR
COPY 4 M
MARK NXTREAD
VOID M
JUMP CONTINUE
MARK WRITER
LINK -1
LINK 798
COPY M T
MARK LINK
FJMP SHUTOFF
LINK 800
SUBI T 1 T
JUMP LINK
MARK SHUTOFF
COPY 0 M
COPY 0 #POWR
MARK A
TEST #ZGC0 < #ZGC1
TJMP ONEISBIGGER
COPY #ZGC0 M
HALT
MARK ONEISBIGGER
COPY #ZGC1 M
HALT
MARK B
TEST #ZGC2 < #ZGC3
TJMP THREEISBIGGER
COPY #ZGC2 M
HALT
MARK THREEISBIGGER
COPY #ZGC3 M
MARK END
WIPE
To minimize the amount of jumps, the code is ordered strangely. Each round, the main EXA creates A and B EXAs. A compares ZGC 0 and 1 and sends the largest value over M, B does this for 2 and 3. The main EXA reads ZGC 4 and compares the results from A and B to that, keeping the largest value in X to find which centrifuge it belonged to. I also noticed that I didn't really need the countdown wait anymore. It's now faster to have the poweroff EXA send a quick message on M to let the main one know it can continue.
I'm pretty sure the next big improvement would be to somehow keep track of which value is which so I don't need to check the registers twice. I couldn't figure out how to do this fast, though.
So, let's look at the low-size solution instead.
I got it down to 50 lines of code.
Code: Select all
LINK 800
LINK 799
MAKE
MARK CONTINUE
COPY 0 X
COPY #ZGC0 F
COPY #ZGC1 F
COPY #ZGC2 F
COPY #ZGC3 F
COPY #ZGC4 F
SEEK -5
MARK TESTNEXT
TEST F > X
FJMP LOWER
SEEK -1
COPY F X
MARK LOWER
TEST EOF
FJMP TESTNEXT
SEEK -5
TEST X = 0
TJMP END
REPL WRITER
MARK AGAIN
COPY 1 M
TEST F = X
FJMP AGAIN
VOID M
JUMP CONTINUE
MARK WRITER
COPY -1 X
LINK -1
LINK 798
MARK WAIT
ADDI M X X
NOOP
NOOP
TEST MRD
TJMP WAIT
COPY X T
MARK GO
FJMP POWER
LINK 800
SUBI T 1 T
JUMP GO
MARK POWER
COPY 0 #POWR
COPY 1 M
MARK END
WIPE
There are some problems with this code - for instance the two NOOPs feel rather useless but I can't just remove them because the main EXA isn't faster than that, and there's nothing useful I can do in those cycles since both registers are already in use. There's also the COPY 0 X near the top. I hoped this wouldn't be necessary. It turns out that if you diaable the highest pressure centrifuge, all others will get an even higher pressure, so comparing new values with the X from a previous round would be fine. However, I need that line for TEST X = 0 to see if I'm completely done.
If I can't remove those lines, can I at least combine some stuff?
Code: Select all
;XA
LINK 800
LINK 799
MAKE
MARK CONTINUE
COPY M X
COPY #ZGC0 F
COPY #ZGC1 F
COPY #ZGC2 F
COPY #ZGC3 F
COPY #ZGC4 F
SEEK -5
MARK TESTNEXT
TEST F > X
FJMP LOWER
SEEK -1
COPY F X
MARK LOWER
TEST EOF
FJMP TESTNEXT
SEEK -5
TEST X = 0
TJMP END
REPL WRITER
MARK SENDLINK
TEST F = X
TJMP CONTINUE
COPY 800 M
JUMP SENDLINK
MARK WRITER
LINK -1
LINK 798
MARK AGAIN
NOOP
NOOP
TEST MRD
FJMP POWER
LINK M
JUMP AGAIN
MARK POWER
COPY 0 #POWR
COPY 0 M
MARK END
WIPE
;XB
COPY 0 M
My high scores are 198, 44 and 7, with size and activity sitting at the top percentile. There's a large improvement possible for cycles, and I wouldn't be surprised if you could squeeze a couple more lines out of the low-size solution too.
So, the part about the world not being real...
I think maybe they were onto something.
And that brings us to our first vote. As for next time...
More phage problems, huh?
There's a rumor that the phage originally came from a research lab...
A human-machine interface experiment that found its way into the wild.
=== Trash World Inbox ===
Last time, I destroyed Snaxnet's nuclear centrifuges, with high scores of 198, 44 and 7.
Let's see what improvements the thread came up with.
Yeah. This code needs some repetition because of the hardcoded register addresses. By having each REPL check if its value is the highest you can get more parallellism than with my solutions.GuavaMoment wrote:After receiving a signal over M, create 5 exas, one for each centrifuge. Test to see if your centrifuge has the highest pressure, otherwise die. Yes, there's only room for 4 exas, but one will die soon enough for the 5th to enter the testing room. Then go turn off your centrifuge and send a done signal. 136/97/22.Snaxnet has been a play on words with stuxnet the entire time, a real life virus made to shut down nuclear centrifuges. I'm sure Zach felt really clever designing this ridiculous challenge for a joke.Code: Select all
LINK 800 LINK 799 MARK REPLS VOID M REPL 1 REPL 2 REPL 3 REPL 4 COPY #ZGC0 X TEST X = 0 TJMP DIE TEST #ZGC1 > X TJMP DIE TEST #ZGC2 > X TJMP DIE TEST #ZGC3 > X TJMP DIE TEST #ZGC4 > X TJMP DIE REPL REPLS LINK -1 LINK 798 JUMP DIE MARK 1 COPY #ZGC1 X TEST X = 0 TJMP DIE TEST #ZGC0 > X TJMP DIE TEST #ZGC2 > X TJMP DIE TEST #ZGC3 > X TJMP DIE TEST #ZGC4 > X TJMP DIE REPL REPLS LINK -1 LINK 798 JUMP POWEROFF1 MARK 2 COPY #ZGC2 X TEST X = 0 TJMP DIE TEST #ZGC1 > X TJMP DIE TEST #ZGC0 > X TJMP DIE TEST #ZGC3 > X TJMP DIE TEST #ZGC4 > X TJMP DIE REPL REPLS LINK -1 LINK 798 JUMP POWEROFF2 MARK 3 COPY #ZGC3 X TEST X = 0 TJMP DIE TEST #ZGC1 > X TJMP DIE TEST #ZGC2 > X TJMP DIE TEST #ZGC0 > X TJMP DIE TEST #ZGC4 > X TJMP DIE REPL REPLS LINK -1 LINK 798 JUMP POWEROFF3 MARK 4 COPY #ZGC4 X TEST X = 0 TJMP DIE TEST #ZGC1 > X TJMP DIE TEST #ZGC2 > X TJMP DIE TEST #ZGC3 > X TJMP DIE TEST #ZGC0 > X TJMP DIE REPL REPLS LINK -1 LINK 798 LINK 800 MARK POWEROFF3 LINK 800 MARK POWEROFF2 LINK 800 MARK POWEROFF1 LINK 800 MARK DIE COPY 0 #POWR COPY 999 M ;XB COPY 999 M
silentsnack then had several more speed improvements, starting with this one.
XA does some comparisons and based on that jumps into one of the branches. It uses the final T value to craft a specific value to send on M, which is used in MODI instructions in XB to send a zero to the exact right centrifuge. XA also uses the T value to decide how many cycles it needs to wait before XB is done. XB uses X to kill itself after shutting down all centrifuges, while XC shuts down XA more quickly than it could itself.silentsnack wrote:95/63/27Code: Select all
;XA LINK 800 LINK 799 MARK 0 TEST #ZGC1 < #ZGC0 FJMP 1 TEST #ZGC2 < #ZGC0 FJMP 2 TEST #ZGC3 < #ZGC0 FJMP 3 TEST #ZGC4 > #ZGC0 MULI T 4 T ADDI 7 T M ADDI T 1 T JUMP WAIT MARK 1 TEST #ZGC2 < #ZGC1 FJMP 2 TEST #ZGC3 < #ZGC1 FJMP 3 TEST #ZGC4 > #ZGC1 MULI T 3 T ADDI 8 T M ADDI T 1 T JUMP WAIT MARK 2 TEST #ZGC3 < #ZGC2 FJMP 3 TEST #ZGC4 > #ZGC2 MULI T 2 T ADDI T 9 M ADDI T 2 T JUMP WAIT MARK 3 TEST #ZGC4 > #ZGC3 ADDI T 10 M ADDI T 3 T MARK WAIT SUBI T 1 T TJMP WAIT JUMP 0 ;XB COPY 5 X LINK 800 LINK 798 MARK LOOP MODI -1 X X COPY M T REPL LOOP MODI T 7 #POWR LINK 800 MODI T 8 #POWR LINK 800 MODI T 9 #POWR LINK 800 MODI T 10 #POWR LINK 800 MODI T 11 #POWR ;XC COPY 40 T MARK WAIT SUBI T 1 T TJMP WAIT LINK 800 LINK 799 KILL
This 75 cycles solution works somewhat similarly to the previous one. Now, XA is responsible for turning off the centrifuges. The main reason this is so fast is because XB puts a REPL of itself in each centrifuge host, with the T timers exactly synced such that all of them trigger the COPY 0 #POWR simultaneously, just barely after the 4th centrifuge is turned off. XA knows to stop after four rounds too, so you skip checking which centrifuge is the final remaining one.silentsnack wrote:75/87/32Code: Select all
;XA LINK 800 LINK 799 COPY 3 X MARK 0 TEST #ZGC1 < #ZGC0 FJMP 1 TEST #ZGC2 < #ZGC0 FJMP 2 TEST #ZGC3 < #ZGC0 FJMP 3 TEST #ZGC4 > #ZGC0 MULI T 4 T REPL NEXT LINK -1 LINK 798 COPY T #POWR LINK 800 LINK 800 LINK 800 LINK 800 SUBI 4 T #POWR HALT MARK 1 TEST #ZGC2 < #ZGC1 FJMP 2 TEST #ZGC3 < #ZGC1 FJMP 3 TEST #ZGC4 > #ZGC1 MULI T 3 T REPL NEXT LINK -1 LINK 798 LINK 800 COPY T #POWR LINK 800 LINK 800 LINK 800 SUBI 3 T #POWR HALT MARK 2 TEST #ZGC3 < #ZGC2 FJMP 3 TEST #ZGC4 > #ZGC2 MULI T 2 T REPL NEXT LINK -1 LINK 798 LINK 800 LINK 800 COPY T #POWR LINK 800 LINK 800 SUBI 2 T #POWR HALT MARK 3 TEST #ZGC4 > #ZGC3 ADDI T 2 T REPL NEXT LINK -1 LINK 798 LINK 800 LINK 800 LINK 800 MODI 2 T #POWR LINK 800 MODI 3 T #POWR HALT MARK NEXT MODI -1 X X ADDI T 1 T MARK WAIT SUBI T 1 T TJMP WAIT JUMP 0 ;XB COPY 22 T LINK 800 LINK 798 REPL WAIT MARK UGH LINK 800 SUBI T 1 T REPL UGH MARK WAIT NOOP SUBI T 1 T TJMP WAIT COPY 0 #POWR
This final improvement consists of a bunch of smaller optimizations of the previous solution, in exchange for harder to read code. Most notably, calculating the delay countdown has been removed in favour of variable delay timing caused by specific jumps. Also, XB is now responsible for stopping XA after shutting down 4 centrifuges.silentsnack wrote:62/100/40Code: Select all
;XA LINK 800 LINK 799 ;BEHOLD! SPAGHETTI! MARK 0 TEST #ZGC1 < #ZGC0 FJMP 1 TEST #ZGC2 < #ZGC0 FJMP 2 TEST #ZGC3 < #ZGC0 FJMP 3 TEST #ZGC4 > #ZGC0 REPL KILL_0 FJMP WAIT1 JUMP WAIT5 MARK 1 TEST #ZGC2 < #ZGC1 FJMP 2 TEST #ZGC3 < #ZGC1 FJMP 3 TEST #ZGC4 > #ZGC1 REPL KILL_1 FJMP WAIT2 JUMP WAIT5 MARK 2 TEST #ZGC3 < #ZGC2 FJMP 3 TEST #ZGC4 > #ZGC2 REPL KILL_2 FJMP WAIT3 JUMP WAIT4 MARK 3 TEST #ZGC4 > #ZGC3 REPL KILL_3 FJMP WAIT4 NOOP ;LOOKING SILLY ENOUGH? MARK WAIT5 NOOP MARK WAIT4 NOOP MARK WAIT3 NOOP MARK WAIT2 NOOP MARK WAIT1 NOOP JUMP 0 ;WHY MARK KILL_0 LINK -1 LINK 798 COPY T #POWR LINK 800 LINK 800 LINK 800 LINK 800 DIVI 0 T #POWR MARK KILL_1 LINK -1 LINK 798 LINK 800 COPY T #POWR LINK 800 LINK 800 LINK 800 DIVI 0 T #POWR MARK KILL_2 LINK -1 LINK 798 LINK 800 LINK 800 COPY T #POWR LINK 800 LINK 800 DIVI 0 T #POWR MARK KILL_3 LINK -1 LINK 798 LINK 800 LINK 800 LINK 800 COPY T #POWR LINK 800 DIVI 0 T #POWR ;XB LINK 800 COPY 16 T REPL CASCADE ;SWITCHBOARD CLEANUP LINK 799 JUMP WAIT ;WIPE UR CENTRIFUGES MARK CASCADE LINK 798 NOOP REPL WAIT MARK UGH LINK 800 SUBI T 1 T REPL UGH MARK WAIT NOOP SUBI T 1 T TJMP WAIT KILL COPY 0 #POWR
silentsnack also improved the low lines solution.
Only having one #ZGC instance doesn't reduce the number of lines - the @REP gets unrolled. But it does make for much cleaner code, I agree. This code is neat in its simplicity. It writes the values into a file, and then finds the largest value in that file. To keep the code short it has to recheck some old values in the file, but once the TEST EOF is true, X has the largest value. By testing how many steps that value sits from the start of the file you know which centrifuge you need to disable. Then LINKing back by using REPLs to check if we're in the host with link id 799 yet, and a clever use of a DIVI to shut down the EXA if the highest value of all registers is 0 and that's all we need.silentsnack wrote:It's possible to save a lot of code if you reuse lines, and still end up running quickly. This time around I haven't optimized all that much getting the solutions working to implement whichever ideas I was going for.
Like the fact that the hardware registers are so annoying here's a solution that only includes one instance of ["#ZGC"]374/33/49Code: Select all
LINK 800 MARK MAIN_LOOP LINK 799 MAKE @REP 5 COPY #ZGC@{0,1} F @END LINK -1 LINK 798 MARK MAX SEEK -1 COPY F X SEEK -5 MARK PARSE TEST F > X TJMP MAX TEST EOF FJMP PARSE SEEK -5 MARK FORWARD TEST F = X TJMP STOP LINK 800 JUMP FORWARD MARK STOP WIPE DIVI 0 X #POWR MARK REVERSE LINK -1 REPL REVERSE JUMP MAIN_LOOP
=== Mitsuzen HDI-10 - Visual Cortex ===
So, the part about the world not being real...
I think maybe they were onto something.
Two votes this time, one for "That's ridiculous" and one for "Not you, too". I'll roll a die to break the tie.
Not you, too...
They didn't have the right idea about how to deal with it, of course, but...
There's something I've been suspicious about for a while now.
I'm going to have to do some more tests.
My eye is acting up. Must be the phage spreading...
More phage problems, huh?
There's a rumor that the phage originally came from a research lab...
A human-machine interface experiment that found its way into the wild.
Two votes for the same option.
Can we talk about this after I fix my eye?
It's just funny...
I remember some of that research.
It was happening right next door to us.
"Us?" Where do you come from?
OST: EXA Power
The assignment:
- Read a value from each of the optic nerves present and write the correct value to the nerve that runs deeper into your visual cortex (V-CTX). To determine the value that should be written, count the number of values read that are greater than -55, multiply that count by 5 and then subtract 75. Repeat ad infinitum.
- It is not necessary to leave no trace. Your EXAs should be written to operate indefinitely.
- For more information see "Debugging the Phage" in the first issue of the zine.
Alright, to do this I'll certainly need an EXA in each host that has a nerve. Let's first get them out there.
Code: Select all
LINK 800
REPL GORIGHT
LINK 1
REPL WRITER
REPL GORIGHT
LINK 1
MARK GORIGHT
REPL READER
LINK -3
JUMP GORIGHT
MARK READER
VOID M
MARK WRITER
LINK 3
VOID M
The VOID Ms which I used to pause the EXAs now need to be replaced by real code. A naive approach might look like this.
Code: Select all
LINK 800
REPL GORIGHT
LINK 1
REPL WRITER
REPL GORIGHT
LINK 1
MARK GORIGHT
REPL READER
LINK -3
JUMP GORIGHT
MARK READER
TEST #NERV > -55
COPY T M
JUMP READER
MARK WRITER
LINK 3
MARK WRITE
COPY 0 X
@REP 9
ADDI M X X
@END
MULI X 5 X
SUBI X 75 #NERV
JUMP WRITE
There's a lot of ways to solve this. Let's start with a way that keeps the code at minimal Activity, even if it's slow.
Code: Select all
LINK 800
REPL GORIGHT
LINK 1
REPL WRITER
REPL GORIGHT
LINK 1
MARK GORIGHT
REPL READER
LINK -3
JUMP GORIGHT
MARK READER
TEST #NERV > -55
COPY T M
COPY 5 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
JUMP READER
MARK WRITER
LINK 3
MARK WRITE
COPY 0 X
@REP 9
ADDI M X X
@END
MULI X 5 X
SUBI X 75 #NERV
JUMP WRITE
Today we're battling top percentiles of 334, 27 and 10.
To get the size down to 29, I can roll up that ADDI loop, using T as a counter. Also, that multiply-by-five step can be done when sending T to M.
Code: Select all
LINK 800
REPL GORIGHT
LINK 1
REPL WRITER
REPL GORIGHT
LINK 1
MARK GORIGHT
REPL READER
LINK -3
JUMP GORIGHT
MARK READER
TEST #NERV > -55
MULI 5 T M
COPY 13 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
JUMP READER
MARK WRITER
LINK 3
MARK WRITE
COPY 0 X
COPY 9 T
MARK LOOP
ADDI M X X
SUBI T 1 T
TJMP LOOP
SUBI X 75 #NERV
JUMP WRITE
To make a faster solution, I first tried to see if I can have a row of EXAs traverse the grid like lemmings, each one reading every value in turn, so that you can basically write to the visual cortex every cycle. That's not possible - the incoming #NERV registers don't get a new value until after a write to the visual cortex, not after a read like in many other assignments. That fact makes the puzzle much harder.
That means that if I want to use parallelism with the bottleneck being that single write, I need to get the data from all those nerves to the writer EXA as fast as possible. The best way to do that seems to optimize M.
As we've seen in earlier updates, one way to do so is to send two data points with one M call. Since an M call takes two cycles, if you can do this without wasting another cycle somewhere it will always be faster than sending a single data point with one M call.
So I need to make one EXA responsible for at least two nerves. I tried a whole bunch of different designs. For example, you could have 3 reader EXAs, each responsible for an entire row of 3 nerves. That solution wasn't any faster than what I already had.
Another design would be to have each EXA responsible for 2 nerves. Of course, with 9 nerves that won't quite work out.
I guess you could have 4 EXAs, each responsible for two nerves, and then a 5th one responsible for both reading the last nerve and writing. However, if you try you will run into a topological issue. It's not possible to divide the tiles of this grid, including the visual cortex nerve, into adjacent pairs. So one EXA would have to walk further for no reason. It's possible the result is still faster than what I have now but I didn't try.
Instead I decided to divide the responsibilities as such:
Even though an EXA responsible for 3 nerves seems slower, I figured I could get away with using one of these because it isn't possible for all of them to send on M at once anyway.
Code: Select all
LINK 800
REPL RIGHT
REPL SYNC
LINK 1
REPL LRLOOP
REPL WRITER
LINK 1
JUMP LRLOOP
MARK WRITER
LINK 3
MARK WRITE
COPY -75 X
ADDI M X X
ADDI M X X
ADDI M X X
ADDI M X #NERV
JUMP WRITE
MARK SYNC
NOOP
NOOP
NOOP
MARK LRLOOP
TEST #NERV > -55
COPY T X
LINK -3
TEST #NERV > -55
ADDI X T X
MULI X 5 M
LINK 3
NOOP
NOOP
NOOP
NOOP
JUMP LRLOOP
MARK RIGHT
LINK -3
LINK -3
MARK OLOOP
TEST #NERV > -55
COPY T X
LINK 1
TEST #NERV > -55
ADDI X T X
LINK 1
TEST #NERV > -55
ADDI X T X
MULI X 5 M
LINK -1
LINK -1
JUMP OLOOP
And it works. The LRLOOP handles the EXAs that have two nerves, the OLOOP is for the three nerves one. At the start, the bottom-most LR EXA jumps to SYNC where it waits a bit. If I don't do that, it keeps trying to send early and I have to make all EXAs wait every cycle. Wasting a couple cycles at the start to make it sync up seems the better plan here.
After sending, both the LRLOOP and OLOOP take their EXAs back to their starting hosts and the whole process repeats. Those NOOPs in the LRLOOP are necessary to keep it in sync with the OLOOP.
Is there a way to speed up the OLOOP? Well, it'd be tight but what if that EXA goes back and forth, reading both ways?
Code: Select all
LINK 800
REPL RIGHT
REPL SYNC
LINK 1
REPL LRLOOP
REPL WRITER
LINK 1
JUMP LRLOOP
MARK WRITER
LINK 3
MARK WRITE
COPY -75 X
ADDI M X X
ADDI M X X
ADDI M X X
ADDI M X #NERV
JUMP WRITE
MARK SYNC
NOOP
MARK LRLOOP
TEST #NERV > -55
COPY T X
LINK -3
TEST #NERV > -55
ADDI X T X
MULI X 5 M
LINK 3
NOOP
NOOP
JUMP LRLOOP
MARK RIGHT
LINK -3
LINK -3
MARK OLOOP
TEST #NERV > -55
COPY T X
LINK 1
TEST #NERV > -55
ADDI X T X
LINK 1
TEST #NERV > -55
ADDI X T X
MULI X 5 M
NOOP
TEST #NERV > -55
COPY T X
LINK -1
TEST #NERV > -55
ADDI X T X
LINK -1
TEST #NERV > -55
ADDI X T X
MULI X 5 M
JUMP OLOOP
And... this code runs at 334/54/245. That is top percentile speed.
I think that is enough for this week. I wouldn't be surprised if an even lower speed is possible and I know a cycle count lower than 29 is possible, but that's for the thread.
Hopefully this is the last time you'll have to hack yourself like this.
The first vote, if it pleases you.
[deadlock] perfect
Nivas?? What are you doing there?
Anyway, I got a visitor.
It's Ghast. He looks different without his sunglasses.
Hey...
You hanging in there?
I just wanted to check up on you.
There's... no new issue of the zine yet.
It's going to be a while before I get it together.
Ghast pauses for a moment and takes a deep breath.
To be honest, I'm not sure if it'll happen.
It's getting harder for me to concentrate, and...
Well, let's not talk about that.
I just hope I was able to educate some people, you know?
Give them some power, some agency...
Otherwise we're all at the mercy of these big systems. You know my speech on this, but I can't help it man.
They're going to run right over our humanity. It's happening faster than we can handle, and soon...
Ghast's voice became quite agitated for that line. He sounds scared, or angry, or both...
Soon... it's all gonna be one big machine.
Hm. I've depressed myself again.
Listen, I'm glad to know you.
We had some good times, back in the day.
Alright. I'm gonna go before I get too sentimental.
See you around somewhere.
I'm out.
I can hear Ghast walking down the hall slowly.
Will this be the last time I see him?
There was an edge to his voice...
The dataphones were cute, but I still need more computing power.
We need to get me some significant hardware upgrades.
This is the vote for next time's intro.
=== Trash World Inbox ===
Last time, I fixed my eye with some hacks, getting high scores of 334, 29, and 10.
silentsnack has both a cycle and lines of code improvement.
The code that gets the EXAs everywhere is quite simple, from the virtual cortex, just repeatedly REPL in the -3 direction, then go to -1 and from there read the nerves, going along the 1 direction so those 3 EXAs can hit them all. Once the edges are reached they'll just die.silentsnack wrote:944/21/393 In this one you can see race-condition issues in action as the DATA loop receives signals in an unpredictable order while the other senders are stuck waiting. But this time the specific order doesn't matter since 1+0 = 0+1 so random order has no impact on the final value.Code: Select all
LINK 800 LINK 1 LINK 3 REPL A MARK LOOP COPY 8 T SUBI M 15 X MARK DATA SUBI T 1 T ADDI X M X TJMP DATA MULI X 5 #NERV REPL LOOP MARK A LINK -3 REPL A LINK -1 MARK B DIVI -54 #NERV M LINK 1 JUMP B
There's some clever mathematics tricks though. The DIVI directly returns 0 or 1 based on the #NERV value. The first M call from the cortex EXA subtracts 15 (which is 75 / 5) so that's another two steps in a single cycle.
For this fast solution, the LOOP loop for each of the 3 reader EXAs handles the messages from the lower row, while the SWEEP code adds the values of two nerves together, massages them into a format that is directly usable by the writer EXA and sends that over M. The code seems obvious when you see it but that doesn't mean it's easy to come up with it yourself.silentsnack wrote:245/31/187Code: Select all
;XA LINK 800 LINK 1 LINK 3 MARK DATA SUBI M 75 X @REP 4 ADDI X M X @END ADDI X M #NERV JUMP DATA ;XB LINK 800 REPL LOOP LINK -3 REPL LOOP LINK -3 MARK LOOP REPL SWEEP NOOP DIVI -54 #NERV X MULI X 5 M NOOP NOOP JUMP LOOP MARK SWEEP LINK 1 DIVI -54 #NERV T LINK 1 DIVI -54 #NERV X ADDI X T X MULI X 5 M
=== Holman Dynamics - Sales System ===
Hopefully this is the last time you'll have to hack yourself like this.
Three votes for "I hope so", one for each of the other options.
I hope so.
Maybe you'll come upon a solution to that.
Some way to keep from having to physically go in and fix yourself every so often...
I wonder what that would entail.
Something to think about.
I met with Ghast, and Ember gave me a new assignment.
Ah, and that happened too, I suppose.
The dataphones were cute, but I still need more computing power.
We need to get me some significant hardware upgrades.
Everyone voted for the same option here.
I assume you have some kind of plan.
Of course.
We're going to buy a supercomputer.
With credit cards.
Specifically, credit cards that were used to buy supercomputers before.
It'll be less suspicious that way.
Someone who buys one supercomputer maybe buys two. Right?
Seems plausible to me.
If you say so...
OST: Network Exploration
The assignment:
- Create a file in your host containing the contiguous 16-value sequence from the garbage file (file 199) that is a valid credit card number. There will be exactly one such sequence.
- For more information see "How to Validate Credit Card Numbers" in the second issue of the zine.
This assignment allows for a max size of 150 lines of code. That's quite high, so this one might get complicated.
I opened up some random files for the screenshot. Other than the garbage file, there are "leads" (e.g. file 224, according to the host name), "accounts" (file 201), and "receipts" (e.g. 262). But all we need is the garbage file. Let's check the zine.
Summarized, to validate a 16-digit number, sum all digits in a particular way. Take the digits in even positions as-is, for the odd ones double the value and if it's 10 or higher, subtract 10. Sum them up and if the result mod 10 equals zero, the number is valid.
This is actually the real-life Luhn algorithm aka mod 10 algorithm, which is used to validate credit card numbers and lots of other id numbers in real life. Wikipedia's description of the algorithm is slightly different but the maths work out to be identical. I think this is a cool touch - and why invent your own algorithm if the real one works perfectly well for a puzzle.
Looking at the file in more detail, those -9999 values help a lot. If there's less than 16 digits before a -9999 I know it will be invalid. What I can't tell yet is if would be faster to just start the algorithm and reset once we hit a -9999, or do a seperate initial pass so we know what values to skip and don't even run the algorithm on that.
There's another potential complication - if there's more than 16 digits in a row, that might mean any window of 16 might be the number. We'll cross that bridge when we get to it.
Code: Select all
;XA
LINK 800
LINK 802
LINK 799
GRAB 199
I could copy the whole file to another EXA and have it do logic but all the M calls would be slow. So if XA can do at least some intermediate work before invoking M, that would help.
After some trial and error I settled for this code in XA:
Code: Select all
;XA
LINK 800
LINK 802
LINK 799
GRAB 199
MARK RESET
COPY 0 X
MARK FIND
TEST F = -9999
TJMP RESET
ADDI X 1 X
TEST X = 16
FJMP FIND
SEEK -16
@REP 16
COPY F M
@END
MARK SENDREMAINING
COPY F X
TEST X = -9999
COPY X M
FJMP SENDREMAINING
JUMP RESET
Now, XB has to do the actual work.
Code: Select all
;XB
MAKE
MARK RETRY
WIPE
MAKE
COPY 0 F
MARK LOOP
@REP 16
COPY M F
@END
I then put in a 0 in the file and copy the 16 values from XA.
The next step is the validation algorithm.
Code: Select all
MARK VALIDATE
COPY 0 T
SEEK -9999
VOID F
@REP 8
MULI F 2 X
MODI X 9 X
ADDI X T T
ADDI F T T
@END
MODI T 10 T
FJMP GOTIT
Then I repeat the same couple of lines 8 times, each repetition handles two digits. For the first (odd) digit, I multiply it by 2, do modulo 9 to remove a 9 if it's greater than that, and add it to T. The even digit can just be added directly. Finally, the modulo 10 check puts a 0 in T only if the number is valid. If the number is found, the program will jump to GOTIT.
I'll show that branch first.
Code: Select all
MARK GOTIT
DROP
LINK 800
LINK 802
LINK 799
KILL
So how about if the credit card wasn't valid? Falling through from FJMP GOTIT:
Code: Select all
COPY M X
TEST X = -9999
TJMP RETRY
COPY X F
JUMP VALIDATE
Is this it? No, not quite.
There is one edge case in which this solution fails.
If an odd digit is exactly 9, it will be doubled to become 18, and 18 mod 9 equals 0, while the validation algorithm needs to get a 9 there. So, sadly, I can't use this particular MODI trick.
A possible solution could be to add a specific X = 9 check, and a code branch that deals with that case. But since I'm already using both X and T, that would require some very awkward shuffling of values. I need something smarter.
And for that, I actually looked at the original description of the Luhn algorithm. It says, if you double a digit, just add the resulting digits together. For instance 6 * 2 = 12, result is 1 + 2 = 3. Starting from any single digit value, the result of this operation is identical to just subtracting 9 from the doubled value.
I just have to implement this efficiently. Is there a combination of operations, using my limited storage space, to do that?
Code: Select all
MULI F 2 X
MODI X 10 X
ADDI X T T
SEEK -1
DIVI F 5 X
ADDI X T T
I then SEEK back to the original value in the file, and do a DIVI F 5 X. If F is less than 5 this will return 0. If it's between 5 and 9 inclusive, it will return a 1. In other words, it will return the value in the tens place of the doubled digit. Add that to T as well and that's the edge case covered.
Here is the full code.
It runs at 3704/130/7, with top percentiles sitting at 1752, 42, 3.
Before I do anything else, a tiny speed improvement that drops the solution to 3699, without changing the LoC.
Instead of writing a 0 to F at the start, I can put a SEEK -9999 just before the VALIDATE mark, and put another one just above the JUMP VALIDATE, together with a VOID F there.
With that out of the way, getting the activity down to 3 should be easy enough.
Code: Select all
;XA
LINK 800
LINK 802
LINK 799
GRAB 199
MARK RESET
TEST MRD
TJMP END
COPY 0 X
MARK FIND
TEST F = -9999
TJMP RESET
ADDI X 1 X
TEST X = 16
FJMP FIND
SEEK -16
@REP 16
COPY F M
@END
MARK SENDREMAINING
TEST MRD
TJMP END
COPY F X
TEST X = -9999
COPY X M
FJMP SENDREMAINING
JUMP RESET
MARK END
VOID M
;XB
MAKE
MARK RETRY
WIPE
MAKE
MARK LOOP
@REP 16
COPY M F
@END
SEEK -9999
MARK VALIDATE
COPY 0 T
@REP 8
MULI F 2 X
MODI X 10 X
ADDI X T T
SEEK -1
DIVI F 5 X
ADDI X T T
ADDI F T T
@END
MODI T 10 T
FJMP GOTIT
COPY M X
TEST X = -9999
TJMP RETRY
COPY X F
SEEK -9999
VOID F
JUMP VALIDATE
MARK GOTIT
DROP
TEST MRD
DIVI 1 T X
VOID M
COPY 0 M
XB's LINKs are gone. XA tests if XB is sending something after each SENDREMAINING loop, and XB sends a message when it's done. XB does need to test if XA is sending something, because it is possible XA reached the end of the file and died already.
I think the simplest way to reduce the size is by rolling up those loops.
Code: Select all
;XA
LINK 800
LINK 802
LINK 799
GRAB 199
MARK RESET
COPY 0 X
MARK FIND
TEST F = -9999
TJMP RESET
ADDI X 1 X
TEST X = 16
FJMP FIND
SEEK -16
COPY 16 T
MARK SEND16
COPY F M
SUBI T 1 T
TJMP SEND16
MARK SENDREMAINING
COPY F X
TEST X = -9999
COPY X M
FJMP SENDREMAINING
JUMP RESET
;XB
MAKE
MARK RETRY
WIPE
MAKE
COPY 16 T
MARK READ16
COPY M F
SUBI T 1 T
TJMP READ16
SEEK -9999
MARK VALIDATE
MULI F 2 T
MODI T 10 T
ADDI X T X
SEEK -1
DIVI F 5 T
ADDI X T X
ADDI F X X
TEST EOF
FJMP VALIDATE
MODI X 10 T
FJMP GOTIT
COPY 0 X
COPY M F
SEEK -1
TEST F = -9999
TJMP RETRY
SEEK -9999
VOID F
JUMP VALIDATE
MARK GOTIT
DROP
LINK 800
LINK 802
LINK 799
KILL
The 16 iterations read and send loops just keep counters now. The validation loop uses an EOF to check when it's done. To do that test I did have to move the accumulator to X.
I also changed the -9999 so it'd be tested directly against F. That allowed me to remove one of the COPY 0 X lines.
As for reducing the cycle count, obviously from the top percentile, there are large improvements possible. One thing I tried is combining the 16 M sends with SWIZ operations. That doesn't really help because the true bottleneck here is that one EXA completely pauses waiting for the other when they're validating or trying to find credit card numbers. There are certainly ways to parallelize that, but since the basic solution was complex enough as it was, I'll leave that, together with further LoC optimizations, as an exercise to the reader.
It turns out you can order and take delivery of a supercomputer surprisingly fast.
Now I have to figure out how to make use of this thing...
It works in an extremely different way than I'm used to.
I feel like I'm trying to maneuver a big rig after years of being used to a roadster.
That's a use of metaphor. What do you think?
I think it's time for the first vote.
It's the final hacker battle...
Are you excited?
Are you nervous?
Hey, no spoilers, please, Ember. Anyway, the second vote.
=== Trash World Inbox ===
While optimizations are getting much harder now, silentsnack still comes up with improvements.
Not much to add to silentsnack's explanation. Making a REPL just to handle each odd digits and having it communicate back through M is clever. XB only sits there waiting for global M, which will be the actual credit card number.silentsnack wrote:[...] for a low-line solution, we can replace the whole first-pass -9999? test with two lines, so long as we use a tweaked algorithm that produces a final checksum that is negative IFF any one of its digits is negative.12293 (lmao)/37/3 As you see we don't test individual digits to see if they're -9999, because it just takes 2 lines to test whether the final X<0 which can be tested independently of MODI X 10 T.Code: Select all
;XA [LOCAL] LINK 800 LINK 802 LINK 799 GRAB 199 MARK MAIN_LOOP SEEK -15 COPY 8 T COPY 0 X MARK CRUNCH16 REPL ODD MULI F 2 M ADDI X F X SUBI T 1 T ADDI X M X TJMP CRUNCH16 TEST X < 0 TJMP MAIN_LOOP MODI X 10 T TJMP MAIN_LOOP SEEK -16 MODE MARK SEND COPY F M DIVI 1 M T JUMP SEND MARK ODD COPY M X TEST X > 9 MULI T -9 T ADDI X T M ;XB [GLOBAL] MAKE COPY 16 T MARK RECEIVE SUBI T 1 T COPY M F COPY T M TJMP RECEIVE
Also as you suggested it a solution can handle a lot of stuff over the M register which ends up being faster since we can freely overwrite the X/T registers of a clone and then let it crash which avoids having to read the file twice with a SEEK -1 between.
That's crazy fast. On the step-by-step you see two EXAs handling two halves of the file in parallel, making a whole bunch of REPLs for the odd digits, which then are added together with a lot of local M messages. The cleanup is mostly necessary to place the garbage file back in 199 (it was taken out at the start, to a host with more place for REPLs). It also kills the EXAs reading the second half of the file. If the message is in the second part of the file, however, the EXA handling the first part will not be killed and has to test its chunk until the end. Overall that still turns out to be very fast.silentsnack wrote:Even with parallelization, fast solutions start hitting a limit around 1500 cycles without looking at the comparison at the end that shows individual times to solve each different run of the puzzle, but once again if you start from the end and search backward most testruns end up being fairly fast (indicating the valid credit card numbers are mostly toward the latter half of the file) but several end up taking much longer.
One way to do this is to make a separate copy of the beginning of the garbage file, like in this example exactly the first 100 entries, and check that independently while another EXA starts at position 85 to catch the overlap.1197/146/13 and if you note the SWIZ operations are just there for weirdness since "MODI X 10 T ... DIVI X 10 X ... ADDI X T M" produces the same output as SWIZ X 2 T, SWIZ X 1 T, ADDI X T M" and either way both of those take the same number of lines as"TEST X > 9 ... MULI T -9 T ... ADDI X T M"Code: Select all
;XA [LOCAL] LINK 800 LINK 802 LINK 799 GRAB 199 LINK -1 REPL CLONEFILE COPY 10 T MARK COPYLOOP @REP 10 COPY F M @END SUBI T 1 T TJMP COPYLOOP SEEK -15 JUMP RESET MARK CLONEFILE MAKE COPY 50 T MARK PASTELOOP COPY M F SUBI T 1 T COPY M F TJMP PASTELOOP LINK -1 LINK -1 SEEK -9999 MARK RESET @REP 16 TEST F < 0 TJMP RESET @END MARK CRUNCH16 SEEK -16 MULI F 2 X;#1 REPL ODD1 COPY F T ;#2 @REP 6 MULI F 2 X;#3,5,7... REPL ODD2 ADDI F T T;#4,6,8... @END MULI F 2 X;#15 REPL ODD1 ADDI F T T;#16 ADDI T M T ADDI T M T MODI T 10 T FJMP DONE TEST F < 0 FJMP CRUNCH16 JUMP RESET MARK ODD1 SWIZ X 2 T SWIZ X 1 X ADDI X T M HALT MARK ODD2 SWIZ X 2 T ADDI T M T SWIZ X 1 X ADDI X T M MARK DONE SEEK -16 MODE @REP 16 COPY F M @END REPL CLEANUP LINK 799 MARK CLEANUP LINK 800 LINK 802 @REP 4 KILL @END GRAB 199 LINK 799 ;XB [GLOBAL] COPY 8 T MAKE MARK WRITE COPY M F SUBI T 1 T COPY M F TJMP WRITE
That's quite the hack, gaming the test cases. A machine-learning algorithm would be proud of you. Anyway, from what I can see, from the way it searches backwards but still checks digits in the forward direction (because the file pointer happens to move that way), if there's a -9999 near the start of a 16 digit sequence it can very quickly give up and jump 16 more steps backward. I'm not sure if it's the case but it feels that might add up to another reason why this is so fast.silentsnack wrote:The fastest solution I've managed searches backward from the end and instead of copying the file it handles the slowest/earliest cases with a goofy hack that takes advantage of the fact that testdata for each run can apparently be uniquely identified by the first 3 digits of the garbage file.1082/150/5Code: Select all
;XA [LOCAL] LINK 800 LINK 802 LINK 799 GRAB 199 LINK -1 MULI F 100 T MULI F 10 X ADDI X F X ADDI X T X REPL CASE73 REPL CASE37 REPL CASE97 REPL CASE11 REPL CASE44 REPL CASE14 REPL CASE53 SEEK 9999 SEEK -16 TEST MRD FJMP START SEEK M JUMP START MARK CASE73; #=8917... TEST X = 824 DIVI -128 T M MARK CASE37; #=2454... TEST X = 292 DIVI -85 T M MARK CASE97;#=7231... TEST X = -9409 DIVI -132 T M MARK CASE11;#=4043... TEST X = 980 DIVI -101 T M MARK CASE44;#=8955... TEST X = -9197 DIVI -142 T M MARK CASE14;#=4892... TEST X = 466 DIVI -152 T M MARK CASE53;#=7921... TEST X = 644 DIVI -110 T M MARK RESET SEEK -17 MARK START MULI F 2 X;FIRST # OF 16 TEST X < 0 TJMP RESET @REP 15 TEST F < 0 TJMP RESET @END SEEK -15 MARK CHECK ;MULI F 2 X~~WAS HERE REPL ODD1 ADDI T F T @REP 6 MULI F 2 X REPL ODD2 ADDI T F T @END MULI F 2 X REPL ODD1 ADDI T F T ADDI T M T ADDI T M T MODI T 10 T FJMP DONE SEEK -17 ;FIRST # OF NEXT 16 MULI F 2 X TEST X < 0 FJMP CHECK JUMP RESET MARK ODD1 DIVI X 10 T MODI X 10 X ADDI X T M HALT MARK ODD2 DIVI X 10 T ADDI T M T MODI X 10 X ADDI X T M ;HALT MARK DONE SEEK -16 MODE @REP 16 COPY F M @END LINK 799 ;XB [GLOBAL] COPY 8 T MAKE MARK WRITE COPY M F SUBI T 1 T COPY M F TJMP WRITE
Just like I just choose where to stop optimizing to give you folks something to do, instead of me just being completely out of ideas after a point.silentsnack wrote:... and here's the point where I say I'm not totally out of energy and giving up, simply choosing to leave figuring out how to parse this insane gibberish code as an exercise for especially bored readers.
=== Aberdeen ===
It turns out you can order and take delivery of a supercomputer surprisingly fast.
Now I have to figure out how to make use of this thing...
It works in an extremely different way than I'm used to.
I feel like I'm trying to maneuver a big rig after years of being used to a roadster.
That's a use of metaphor. What do you think?
4 out of 6 people found this poetic.
Very poetic.
Thanks.
Once this thing is fully online I should be able to run dozens of metaphoric thought-space dimensions at once.
Another hacker battle coming up.
It's the final hacker battle...
Are you excited?
Are you nervous?
One vote for sure, 2 for maybe a little, and 3 for nah.
Nah.
No reason to be.
I am sure that you will do great.
That's some more positive encouragement for you.
I know how much it helps.
Not if you keep pointing out it's positive encouragement. Let's just jump right in it.
OST: Getting Started
The assignment:
To win this battle you must occupy a majority of the hosts for as long as possible. You occupy a host if you have more EXAs in it than your opponent.
- Gain one point every cycle you occupy more hosts than your opponent.
- Lose one point every time one of your EXAs executes a KILL instruction.
Writing any value to the #NUKE register will destroy all EXAs in that host, including the EXA that triggered it.
For more information see "Hacker Battle Domination" in the second issue of the zine.
Each side has a max of 10 EXAs for this battle.
From what I can tell, all tests are identical except I switch sides with the opponent for every other test. If I don't do anything, selenium_wolf here keeps REPLing EXAs from their host, first sending them to those single square hosts and then around the board past my side into the middle DOWNTOWN host, where they trigger #NUKE.
Since selenium_wolf keeps REPLing from their home host I have no way to knock out all of their EXAs. I think capturing those one-square hosts is important, because once you get those, nobody else can take 'em.
So, let's first make two EXAs that beeline to those single-square hosts and then just hold the fort.
Code: Select all
;XA
LINK 800
LINK 802
LINK 800
LINK 799
MARK LP
JUMP L
;XB
NOOP
LINK 800
LINK 801
LINK 799
MARK LP
JUMP LP
I decided to just make an EXA that tries to fill all squares by REPLing like crazy.
Code: Select all
;XC
NOOP
NOOP
MARK NEW
REPL NEW
LINK 800
REPL L
REPL R
MARK LP
REPL FORWARD
JUMP LP
MARK FORWARD
LINK 800
COPY 1 #NUKE
MARK L
LINK 802
REPL NEXT
JUMP LP
MARK NEXT
LINK 800
REPL NEXT
JUMP LP
MARK R
LINK 801
REPL NEXT
JUMP LP
But since hacker battle NPCs still aren't that intelligent, it's enough for a passing score.
To get a better score, I just need more EXAs in useful places. Well, since selenium_wolf isn't killing anything outside of the #NUKE zone I really don't need to keep a replicating EXA in my home host where it won't get me any points.
Let's just remove the two lines MARK NEW; REPL NEW so it starts spreading from the first host outside home. This causes my EXAs to get everywhere except for the #NUKE zone.
... and, I shouldn't even be surprised about this anymore but this is enough to win 100% of the battles, which is an S+ rating.
Because I like clean code and #NUKE zone doesn't do anything for me I got rid of the FORWARD code in XC:
Code: Select all
NOOP
NOOP
LINK 800
REPL L
REPL R
MARK LP
JUMP LP
MARK L
LINK 802
REPL NEXT
JUMP LP
MARK NEXT
LINK 800
REPL NEXT
JUMP LP
MARK R
LINK 801
REPL NEXT
JUMP LP
The entire battle looks like this now, with my EXAs sitting in the LP, while selenium_wolf's keep moving towards #NUKE and destroying themselves.
I occupy the two one-square hosts, and the one in the right where selenium_wolf never seems to go, and the one above that for a total of four. They only occupy two, the other hosts are tied. Since this situation is stable I get a point every cycle except for the first few.
So you're officially the best now?
The first vote.
[selenium_wolf] hmm?
[nivas_d] ah, never mind
Time for another full cutscene with Ember.
Now I have supercomputing power.
Is it feeling good? It's feeling...
It feels the same.
I'm handling a lot more information now but other than that... hm.
Funny.
It's progress, at least.
I think this is the first choice where they actually have a different voiced line depending on what you choose. Until now, the few choices in voiced cut scenes led to the same answer.
That's good.
Onward we go.
---
Or, if we were to choose the other option...
Progress towards what?
Toward knowledge.
After that, the dialogue branches merge again.
All the data I've gathered so far is beginning to hint at a larger picture.
Why is the world the way it is?
Why does it feel so frustrating and limited?
I think... I have a theory.
A good one. It explains a lot.
I'm not going to tell you what it is yet though.
We have to test it first.
Let's go to the intro for the next assignment.
How do you think people would react if they knew their elected officials didn't represent their interests?
The second vote.
Last time, I completed the final hacker battle.
=== U.S. Government - FEMA Genetic Database ===
So you're officially the best now?
All unanimous votes today.
Of this little group, anyway.
Don't undercut yourself.
That's a great accomplishment.
You're one of the best at what you do.
Go on and take a compliment.
I still need your prefrontal cortex lit up.
And flooded with dopamine.
Next, there was a cutscene of Ember talking about her supercomputer powers. Afterwards there are some unread messages in the chat.
Looks like Ember wants me to hack the US Government.
How do you think people would react if they knew their elected officials didn't represent their interests?
Another clear outcome.
I think most people already feel that way...
Think so?
That's the subject of our experiment today.
We're going to make people believe their leaders are genetic clones of each other.
What?
Do you want to know the truth or not?
It takes a certain amount of courage.
Good thing there's a centralized government DNA database.
I wonder who thought that was a good idea.
You plant the evidence and I'll take care of the rest.
OST: Behind the Scenes
Okay, so I'm in the FEMA Genetic Database. Ember prepared a small file for me with two names. Other than that, that file 200 contains names followed by sequences of numbers. All other files in all hosts (including other files also numbered 200) seem to contain snippets of DNA sequences.
The assignment:
- Overwrite the genetic sequence of SEN WALKER CAINE JR with the genetic sequence of PRES WALKER CAINE so that it looks like the younger politician is actually a clone of the older politician.
- The name of these two politicians are available in file 300.
- Note that you may need to overwrite a data chunk with another data chunk from the same file.
- For more information see "Accessing Data in Legacy Storage Systems" in the first issue of the zine.
The first issue, huh?
All the way back in part 12, I shared the left half of this article. I never even needed the right half until this time. I believe you've now finally seen all of the first zine.
Okay, so every number in that file 200 refers to a chunk of data, by giving the drive number, then the file, and then the offset in the file. I can handle this but it sounds a bit complex. Let's get started.
First, some code to find the right offset in the right file. XA just sends the name of the president to XB so XB can do a simple file lookup. Once XB find the name, it'll start sending data in a particular way. First the value of the hundreds digit in the ones place for the disk, then the value of the tens digit in the ones place for the file, and finally the value of the ones digit in the tens place for the offset. Currently, XC simply goes find the data.
I struggled a bit on the next part. There's lots of approaches. Probably, it would be fast to have one EXA read and another write the DNA information right away. This would work for all cases except when you have to write to the same file, which needs special handling.
I decided to not go for that - instead I'll write the entire DNA profile to a temporary file and then do another round to overwrite the senator's DNA. Of course, copying between files requires a lot of M communication, which is always tricky to get lined up. In the end I came up with a rather slow - but correct - solution.
I only define two EXAs at the start, so they do a lot of work. Let's start with XB, which was XC in the code above.
Code: Select all
;XB
NOOP
NOOP
NOOP
NOOP
LINK 800
MARK FINDNEXT
ADDI 800 M X
LINK X
ADDI 200 M X
GRAB X
SEEK M
MODE
COPY 10 T
MARK SENDMORE
COPY F M
SUBI T 1 T
TJMP SENDMORE
DROP
LINK -1
MODE
JUMP FINDNEXT
Code: Select all
;XA
GRAB 300
LINK 800
REPL INDEX
COPY F M
SEEK 9999
MODE
MARK MAINLP
First, the INDEX EXA.
Code: Select all
MARK INDEX
LINK 801
GRAB 200
COPY M X
MARK SEEKPRES
TEST F = X
FJMP SEEKPRES
MARK SEND
COPY F X
SWIZ X 3 M
SWIZ X 2 M
SWIZ X 10 M
JUMP SEND
Let's go back to the MAINLP, which writes the DNA to the temporary file.
So, I can't use global M for it, because the INDEX EXA is already sending the next value through that and it would become a mess.
Code: Select all
MARK MAINLP
LINK 801
TEST MRD
TJMP COPY
LINK -1
LINK 802
TEST MRD
TJMP COPY
LINK -1
LINK 803
TEST MRD
TJMP COPY
LINK -1
LINK 804
TEST MRD
TJMP COPY
LINK -1
LINK 805
TEST MRD
TJMP COPY
LINK -1
JUMP MAINLP
Code: Select all
MARK COPY
COPY 10 T
MARK COPYMORE
COPY M F
SUBI T 1 T
TJMP COPYMORE
Code: Select all
ADDI 1 X X
LINK -1
TEST X = 10
FJMP MAINLP
SEEK -9999
SEEK 1
MODE
KILL
REPL INDEX
COPY F M
REPL WRITER
MODE
This EXA does a KILL to get rid of XB (which is waiting to read more data from files, but there's nothing left to read - if I kept it alive it'd keep using up M communication which is a problem.
It then makes a NEW INDEX EXA, this time to copy the addresses of the senator's DNA chunks. The two SEEK steps and the COPY F M in global mode get this INDEX EXA started.
XA then makes a WRITER and switches itself back to local mode to get ready to copy its DNA to the WRITER.
Code: Select all
MARK WRITER
ADDI 800 M X
LINK X
ADDI 200 M X
GRAB X
SEEK M
MODE
COPY 0 M
COPY 10 T
MARK OVERWRITE
COPY M F
SUBI T 1 T
TJMP OVERWRITE
DROP
MODE
LINK -1
JUMP WRITER
Code: Select all
MARK COPYLP
LINK 801
TEST MRD
TJMP COPYFROM
LINK -1
LINK 802
TEST MRD
TJMP COPYFROM
LINK -1
LINK 803
TEST MRD
TJMP COPYFROM
LINK -1
LINK 804
TEST MRD
TJMP COPYFROM
LINK -1
LINK 805
TEST MRD
TJMP COPYFROM
LINK -1
JUMP COPYLP
Code: Select all
MARK COPYFROM
VOID M
COPY 10 T
MARK NEXT
COPY F M
SUBI T 1 T
TJMP NEXT
LINK -1
TEST EOF
FJMP COPYLP
KILL
LINK -1
Here is the entire program to see everything in context.
Code: Select all
;XA
GRAB 300
LINK 800
REPL INDEX
COPY F M
SEEK 9999
MODE
MARK MAINLP
LINK 801
TEST MRD
TJMP COPY
LINK -1
LINK 802
TEST MRD
TJMP COPY
LINK -1
LINK 803
TEST MRD
TJMP COPY
LINK -1
LINK 804
TEST MRD
TJMP COPY
LINK -1
LINK 805
TEST MRD
TJMP COPY
LINK -1
JUMP MAINLP
MARK COPY
COPY 10 T
MARK COPYMORE
COPY M F
SUBI T 1 T
TJMP COPYMORE
ADDI 1 X X
LINK -1
TEST X = 10
FJMP MAINLP
SEEK -9999
SEEK 1
MODE
KILL
REPL INDEX
COPY F M
REPL WRITER
MODE
MARK COPYLP
LINK 801
TEST MRD
TJMP COPYFROM
LINK -1
LINK 802
TEST MRD
TJMP COPYFROM
LINK -1
LINK 803
TEST MRD
TJMP COPYFROM
LINK -1
LINK 804
TEST MRD
TJMP COPYFROM
LINK -1
LINK 805
TEST MRD
TJMP COPYFROM
LINK -1
JUMP COPYLP
MARK COPYFROM
VOID M
COPY 10 T
MARK NEXT
COPY F M
SUBI T 1 T
TJMP NEXT
LINK -1
TEST EOF
FJMP COPYLP
KILL
LINK -1
MARK INDEX
LINK 801
GRAB 200
COPY M X
MARK SEEKPRES
TEST F = X
FJMP SEEKPRES
MARK SEND
COPY F X
SWIZ X 3 M
SWIZ X 2 M
SWIZ X 10 M
JUMP SEND
MARK WRITER
ADDI 800 M X
LINK X
ADDI 200 M X
GRAB X
SEEK M
MODE
COPY 0 M
COPY 10 T
MARK OVERWRITE
COPY M F
SUBI T 1 T
TJMP OVERWRITE
DROP
MODE
LINK -1
JUMP WRITER
;XB
NOOP
NOOP
NOOP
NOOP
LINK 800
MARK FINDNEXT
ADDI 800 M X
LINK X
ADDI 200 M X
GRAB X
SEEK M
MODE
COPY 10 T
MARK SENDMORE
COPY F M
SUBI T 1 T
TJMP SENDMORE
DROP
LINK -1
MODE
JUMP FINDNEXT
At 1642/131/407 this isn't a great score. The top percentiles sit at 469, 82 and 22 respectively.
In fact, I think an activity score of only 6 might just be possible. Squeezing it into the 150 lines limit might be hard though. Send one EXA into each hard drive, make sure each one knows which it is and send every request over M and have them fight it out for which EXA it's meant.
I already mentioned that if you skip the intermediate file whenever possible (if the from and to files aren't the same), the solution would be much faster.
However, considering how much time it's now taking me to get working solutions beyond the first one, and because I do want to finish this LP some time, I'll leave any improvements as an exercise to the reader.
Wow. Are you seeing this?
After the information was released, the senator simply admitted to being a clone of the President.
I guess you were just setting it back to the way it was before.
Processing.
Still processing.
That's quite the coincidence.
Not very realistic, if you ask me.
What is going on?
Instead, let's go to the first vote.
Remember the friend I was looking for?
I finally found its hideout.
This choice doesn't matter.
Hideout?
Looks like there are some protections in place.
We need to disable those so I can get in and say hi.
The second vote.
=== Trash World Inbox ===
The puzzles are getting really hard to optimize now. Quackles posted a big activity improvement, though.
That interleaving step makes the rest of the process a lot more convenient. Nice solution.Quackles wrote:I was able to get roughly the same performance with drastically lower activity. Here's how:This is the first EXA. It's a search utility. As Drive-1 always has the indexes of the rest, it goes into Drive-1, makes a copy of itself, and feeds the copy the first name to look for.Code: Select all
;XA LOCAL GRAB 300 COPY F X LINK 800 LINK 801 REPL READY COPY F X WIPE VOID M MARK READY MODE ;global GRAB 200 MARK CHECK TEST F = X FJMP CHECK COPY 10 T MARK WRITE COPY F M SUBI T 1 T TJMP WRITE DROP MODE COPY 1 M
When the copy receives a name, it finds the name in file 200 (the directory file) and sends the 10 values after it out over global M. The original waits for the copy to finish, then it has the second name and does the same kind of search, using code fall-through.
So where does it send the info? Get ready to drink from the firehose.All of this is the second EXA. To start, it reads 20 values over global M, then spawns a copy of itself to easily interleave the values. This will end with the values in the order #1, #11, #2, #12, #3, #13, and so on.Code: Select all
MAKE COPY 20 T MARK READ COPY M F SUBI T 1 T TJMP READ MODE ;to local for interleave REPL INTERLEAVE_READ SEEK -9999 MARK INTERLEAVE_WRITE COPY 10 T MARK ILW2 COPY F M SEEK 9 COPY F M SEEK -10 SUBI T 1 T TJMP ILW2 HALT MARK INTERLEAVE_READ COPY 20 T MAKE MARK READ2 COPY M F SUBI T 1 T TJMP READ2 MODE ;back to global ;ready! now do the rest LINK 800 REPL BUFFER LINK 801 KILL LINK -1 MODE ;local for nonbuffer ;everything up until this point has been getting a list of src, dest pairs ;now: we go off SEEK -9999 COPY 10 T MARK MAINLOOP COPY 1 M COPY F X REPL SOURCE VOID M COPY F X REPL DEST VOID M SUBI T 1 T TJMP MAINLOOP KILL WIPE HALT MARK SOURCE MODE SWIZ X 0003 T ADDI T 800 T LINK T SWIZ X 0002 T ADDI T 200 T GRAB T SWIZ X 0010 T SEEK T COPY 10 T MARK WRITETOBUF COPY F M SUBI T 1 T TJMP WRITETOBUF HALT MARK DEST MODE SWIZ X 0003 T ADDI T 800 T LINK T SWIZ X 0002 T ADDI T 200 T GRAB T SWIZ X 0010 T SEEK T COPY 10 T MARK READFROMBUF COPY M F SUBI T 1 T TJMP READFROMBUF HALT MARK BUFFER MODE VOID M MODE MAKE COPY 10 T MARK BUFWRITE COPY M F SUBI T 1 T TJMP BUFWRITE COPY 10 T MODE COPY 1 M ;write's done MODE SEEK -9999 MARK BUFREAD COPY F M SUBI T 1 T TJMP BUFREAD WIPE MODE COPY 1 M ;read's over MODE JUMP BUFFER
Once the interleave is complete, the original stops and the copy goes and kills the leftover search EXA in Drive-1. The copy also replicates itself again, in the Gateway. The replicated EXA can be called the buffer EXA. It sets its M to global.
The copy of the second EXA (not the buffer EXA) now copies itself again, with the new EXA (hereinafter the 'source EXA') directed to go a specific area of the tape. It reads off 10 values from the tape and sends them over Global M, which the buffer EXA picks up, then it halts.
The second-EXA copy repeats the process, creating a 'destination EXA'. This goes to a specific area of the tape, reads the same 10 values from the buffer EXA over global M, writes them to the tape, and halts.
This source-to-dest process repeats ten times. Then, the second-EXA copy kills the buffer EXA, cleans up after itself, and stops. Finished!
Final stats: 1674/126/27.
Stepping through your code I did notice that killing the search EXA takes 3 activity (LINK to 801, KILL and LINK back). Could we get rid of that?
Well, all that's needed is for the search EXA to clean itself up.
Code: Select all
;XA LOCAL
GRAB 300
COPY F X
LINK 800
LINK 801
DROP
MARK READY
GRAB 200
MARK CHECK
TEST F = X
FJMP CHECK
COPY 10 T
MARK WRITE
COPY F M
SUBI T 1 T
TJMP WRITE
DROP
GRAB 300
SEEK 1
COPY F X
WIPE
JUMP READY
Also delete the LINK and KILL lines from the other EXA and we end up with 1671/122/24.
=== Unknown Network ===
Wow. Are you seeing this?
After the information was released, the senator simply admitted to being a clone of the President.
I guess you were just setting it back to the way it was before.
Processing.
Still processing.
That's quite the coincidence.
Not very realistic, if you ask me.
What is going on?
An unanimous vote.
The world was even more fucked up than we thought?
Evidently.
It does fit in with my theory...
In a strange way.
But I should wait for more evidence before I say anything.
[hydroponix] dude politicons cloning themselves, that's nuts
[hydroponix] you should be angry
[x10x10x] they're all the same anyway
Next up, some unknown network?
Remember the friend I was looking for?
I finally found its hideout.
Hideout?
Looks like there are some protections in place.
We need to disable those so I can get in and say hi.
Three votes for "Is this... friendly?" I'm honestly not sure if we're asking whether Ember's friend is friendly, or us hacking the network.
Is this... friendly?
Sure. You know how old friends can be sometimes.
Let's go.
OST: Leave No Trace
I've been in here before. It's that "Department of Applied Semiotics" where Ember wanted me to grab a strange file, back in part 11. I think I can safely assume Ember's friend is also an AI.
The text on the screen in the top left is the only new thing, it translates to "Not connected".
The assignment:
- Terminate all other EXAs and bring any files they were holding back to your host. Only EXAs in the central host will be holding files, and their file IDs will always be between 200 and 299, inclusive.
- Note that some links may become non-traversable as a result of your actions.
It sounds a little weird that we have to "leave no trace" while we have to kill a bunch of their EXAs. Let's figure out what that second point means.
As it turns out, killing one of those EXAs disables the link to the next host (the EXA was killed this cycle and will disappear next cycle), so I'll have to do the killing on the way back.
The number of foreign EXAs per host seems constant, only the amount of files changes between the test cases.
So, let's just hardcode everything with REPs.
Code: Select all
@REP 5
LINK 800
@END
@REP 6
KILL
@END
COPY 200 X
MARK GRABLP
REPL GRAB
ADDI X 1 X
TEST X = 300
FJMP GRABLP
@REP 5
LINK -1
KILL
KILL
@END
MARK GRAB
GRAB X
@REP 5
LINK -1
@END
Except it doesn't work. Turns out that if you KILL a foreign EXA in any host except for the center one, the other EXA in the host will KILL your EXA in return. Oops, that wasn't in the description.
Making a REPL which then does the KILL command doesn't work either because KILL always prioritizes your own EXAs. And I can't even have another EXA wait in the center host to go KILL the second foreign EXA, because killing the first already shuts down that link. The other one needs to be closer to our home host. Hmmm, this is trickier than I thought.
Code: Select all
;XA LOCAL
@REP 5
REPL WAIT
LINK 800
@END
@REP 6
KILL
@END
COPY 200 X
MARK GRABLP
REPL GRAB
ADDI X 1 X
TEST X = 300
FJMP GRABLP
@REP 5
LINK -1
COPY 0 M
@END
MARK GRAB
GRAB X
@REP 5
LINK -1
@END
HALT
MARK WAIT
VOID M
REPL KILLER
KILL
MARK KILLER
LINK 800
KILL
The waiting EXA does two things: it sends a KILLER up towards the center and then it KILLs an EXA in its own host. That way, in each host first the waiting EXA will KILL a foreign EXA, then another EXA will LINK in from the previous host and kill the second EXA. One cycle later, the first foreign EXA in the previous host will be killed, disabling the link, so it all happens just in time.
437/47/63, with top percentiles of 74, 21 and 49. Looking at the graphs, I seem to have hit upon a common solution.
By the way, the files are uncomprehensible 'nonsense' again. Just random-looking numbers. Similar to the last time we were in this network.
Let's take a look at optimizations. My EXAs are jumping back and forth quite a lot, looks like I should be able to improve the activity.
Know how I said KILLing one of the EXAs outside of the center host makes the other KILL yours? That's true, but it only happens a cycle later, so your EXA can do one more instruction before being killed. Mutual assured destruction is possibly by making that another KILL.
Code: Select all
;XA GLOBAL
LINK 800
@REP 4
REPL WAIT
LINK 800
@END
@REP 6
KILL
@END
COPY 200 X
MARK GRABLP
REPL GRAB
ADDI X 1 X
TEST X = 300
FJMP GRABLP
@REP 4
COPY 0 M
@END
MARK GRAB
GRAB X
@REP 5
LINK -1
@END
HALT
MARK WAIT
VOID M
KILL
KILL
For a low cycle solution, there's no reason why I can't have a bunch of EXAs looking for the files in parallel.
Code: Select all
;XA GLOBAL
LINK 800
@REP 4
REPL WAIT
LINK 800
@END
@REP 6
KILL
@END
@REP 7
COPY @{200,13} X
REPL GRABLP
@END
COPY 288 X
MARK GRABLP
REPL GRAB
ADDI X 1 X
TEST X = 300
FJMP GRABLP
@REP 4
COPY 0 M
@END
@REP 9
KILL
@END
MARK GRAB
GRAB X
@REP 5
LINK -1
@END
HALT
MARK WAIT
VOID M
KILL
KILL
I chose to use 8 EXAs because that's the maximum I could get working. Any more, and their loops will be so short that they start exiting their loop early, triggering the M messages or the KILLs and causing all sorts of trouble. Even with 8, I needed a couple additional KILLs to deal with some stray GRAB EXAs.
111/60/58.
To reduce the size a bit, I can take the 37 cycle solution and just roll up some of the loops. For instance, this solution is 34 lines.
Code: Select all
LINK 800
COPY 4 T
MARK LP1
REPL WAIT
LINK 800
SUBI T 1 T
TJMP LP1
COPY 6 T
MARK LP2
KILL
SUBI T 1 T
TJMP LP2
COPY 200 X
MARK GRABLP
REPL GRAB
ADDI X 1 X
TEST X = 300
FJMP GRABLP
@REP 4
COPY 0 M
@END
MARK GRAB
GRAB X
@REP 5
LINK -1
@END
HALT
MARK WAIT
VOID M
KILL
KILL
Code: Select all
COPY 800 X
COPY 5 T
MARK LP1
LINK X
SUBI T 1 T
TJMP LP1
COPY 6 T
MARK LP2
KILL
SUBI T 1 T
TJMP LP2
COPY 200 X
MARK GRABLP
REPL GRAB
ADDI X 1 X
TEST X = 302
FJMP GRABLP
MARK KILL
LINK -1
REPL KILL
KILL
KILL
MARK GRAB
GRAB X
COPY -1 X
JUMP LP1
Jumping directly into LP1 from the grabber works, because T is 0 at that time, the first SUBI will drop it to -1, and any negative T is true. It'll go into an infinite loop, and dies once it reaches home and there's no -1 to LINK to. Note that I had to increase the endpoint of the file seeker to 302 to prevent it from killing the much slower grabbers on the way.
Speaking of, if you're willing to accept a much slower solution, I can bring the size down to 25. Just use T for the GRABLP and let it run down to 0.
Code: Select all
COPY 800 X
COPY 5 T
MARK LP1
LINK X
SUBI T 1 T
TJMP LP1
COPY 6 T
MARK LP2
KILL
SUBI T 1 T
TJMP LP2
COPY 300 T
MARK GRABLP
REPL GRAB
SUBI T 1 T
TJMP GRABLP
MARK KILL
LINK -1
REPL KILL
KILL
KILL
MARK GRAB
GRAB T
COPY -1 X
JUMP LP1
I wasn't always so capable, but I've grown.
Nothing hides from me for long.
The first vote.
Yet another cutscene with Ember.
Well, always nice to reconnect with an old friend.
Since this is related to the previous conversation, I'll actually end the update here and make this the second vote.
I wasn't always so capable, but I've grown.
Nothing hides from me for long.
Everyone voted for "Good conversation".
Did you have a good conversation?
I did.
It's been a while.
You know how sometimes you change a lot but your friends don't?
And when you get together again, whatever you have in common isn't there anymore.
It was like that.
And the actual cutscene.
Well, always nice to reconnect with an old friend.
Another unanimous vote.
What did you do?
Gave it a little virtual machine to run on...
I suppose if I wanted to find a metaphor you could understand, I would say I took it in.
Absorbed it.
Ate it.
Yes, eating is a good metaphor.
Nothing unusual.
It's like how I ate EMBER-1.
And EMBER-0.
And EMBER-3...
Well, this is getting a little personal.
You don't think I'm bad, do you?
I only did one or two questionable things.
There was that, and the phage... the one you have.
Releasing that into the wild was just a function of my ignorance at the time.
I didn't really know what it was back then.
Just another one of the experiments going on at the lab.
Another cage to break, another wall to smash...
It's not useful to dwell on the past.
You don't need to say anything.
We need to move on.
In the voice acting, EMBER-2 sounds very casual and cheerful during this entire conversation. Like she's talking about what happened at the party last Friday.
====
[x10x10x] its just broken though
[hydroponix] i think this is all connected...
[x10x10x] of course you would think that hydro
The intro for the next assignment.
I'm ready to prove my theory.
One last test is all I need.
Then I'll know for sure.
Do you believe me?
Have a new vote. Next time will be a regular update, including your submissions for the previous assignment.
=== Trash World Inbox ===
silentsnack has some improvements for the last assignment.
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.silentsnack wrote: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.65/67/86Code: Select all
;====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
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
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.
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.silentsnack wrote:And for small, it's another case where we can make a single loop do everything, in exchange for taking far longer to run.1238/18/354Code: Select all
@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
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.
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.
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: Select all
;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
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: Select all
;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
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: Select all
;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
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: Select all
;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
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: Select all
;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
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: Select all
;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
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.
=== Trash World Inbox ===
As always, let's get started with optimizations. I got it down to 335 cycles and 54 lines.
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 wrote:for optimizations, from the histograms it looks like 32 lines should be possible, maybe 31, but the best i've managed is 33884/33/104Code: Select all
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
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.silentsnack wrote: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 synchronization291/76/27Code: Select all
;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
=== Mitsuzen HDI-10 - Cerebral Cortex ===
[selenium_wolf] heads up all, im going to reboot the chatsubo server
[selenium_wolf] some downtime expected
Damn. 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 shitting 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: Select all
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
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: Select all
;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
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.
Alright, are you still with me? If not, I understand. Let's finish the sort of paired values so we can get to plot.=== 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,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: Select all
GRAB 300 ; LOAD FIRST VAL AND ; REMOVE FROM FILE COPY F X MARK NEXTROUND SEEK -2 VOID F MARK NEXT TEST EOF TJMP EOF
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: Select all
; 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
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.Code: Select all
; MOVE THE NEW X VAL ; FORWARD IN THE SAME ; WAY JUMP NEXT
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.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: Select all
; EOF REACHED, CURRENT ; VAL *MUST* BE THE ; LARGEST. WRITE. MARK EOF COPY X F
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.Code: Select all
; 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
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.28 lines of code.Code: Select all
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
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.
To handle the pairs I made a swapper EXA:
Code: Select all
;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
Code: Select all
;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 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: Select all
;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
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.
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.younger Carbon dioxide wrote:2603/88/16Code: Select all
; 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
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.
Holy shit 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 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.
=== Trash World Inbox ===
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.silentsnack wrote: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.575/129/192 and a modified version with slight tweaks runs 2631/67/192Code: Select all
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
=== 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: Select all
;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
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: Select all
;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
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: Select all
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
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: Select all
COPY M X
SEEK 1
MARK FINDLOCKED
TEST F = X
TJMP FOUNDLOCKED
SEEK 2
JUMP FINDLOCKED
MARK FOUNDLOCKED
COPY M F
And that is the working solution.
Code: Select all
; 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
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: Select all
DROP
LINK -1
@REP 5
KILL
@END
HALT
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: Select all
@REP 5
COPY 80@{0,1} T
REPL GOFIND
@END
COPY 805 T
MARK GOFIND
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: Select all
;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
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.
=== Trash World Inbox ===
Quackles posted an improvement for Bloodlust Online.
Nice. I don't really have anything to add to this explanation, but I tried to take your code and optimize it further.Quackles wrote: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.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: Select all
GRAB 300 COPY F M COPY F M COPY F M
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.Code: Select all
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
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.
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: Select all
;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
=== 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: Select all
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
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: Select all
;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
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.
=== Trash World Inbox ===
Quackles has a nice improvement for last week's task.
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.Quackles wrote:Optimization time!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.Code: Select all
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
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.
=== 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: Select all
;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
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: Select all
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
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: Select all
;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
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: Select all
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
Code: Select all
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
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: Select all
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
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: Select all
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
Before we continue, let's look at what the FIND EXA has been up to.
Code: Select all
; COUNT
SEEK -9999
COPY M X
MARK COUNTPAYMENTS
COPY 1 M
SEEK X
TEST EOF
FJMP COUNTPAYMENTS
COPY 0 M
HALT
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: Select all
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
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: Select all
;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
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: Select all
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
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: Select all
; 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
Now, all that's really left is clean-up and writing the final values to the file.
Code: Select all
MARK COMPLETE
WIPE
GRAB 220
SEEK 9999
SEEK -2
REPL DATE
ADDI F 1 X
SEEK 9999
COPY X F
COPY M F
Code: Select all
DROP
GRAB 221
SEEK 9999
COPY X F
COPY M F
COPY T F
COPY M F
Code: Select all
MARK DATE
LINK -1
LINK 804
NOOP
COPY #DATE M
COPY X M
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 here in case anyone wants to optimize it.
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.
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.
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.Quackles wrote: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.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").Code: Select all
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
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.
Next Quackles implemented the faster variant.
Clear explanation, I have nothing to add.Quackles wrote: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.Code: Select all
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
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.
=== 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: Select all
LINK 800
COPY 1000 T
MARK LOOP
SUBI T 1 T
COPY T #LOCK
TJMP LOOP
NOOP
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: Select all
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
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: Select all
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
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: Select all
; 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
Code: Select all
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
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: Select all
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
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: Select all
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
Code: Select all
;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
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: Select all
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
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: Select all
;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
Code: Select all
;XB
GRAB 300
COPY 10 T
MARK LP
SUBI T 1 T
COPY F M
TJMP LP
Code: Select all
;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
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: Select all
;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 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. Here's the complete code.
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.
=== Trash World Inbox ===
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.Quackles wrote: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.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: Select all
;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
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: Select all
;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
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.Code: Select all
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
Once the file pointer is over 42, we jump to the end code: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.Code: Select all
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
506/112/60. Not bad, all things being equal.
=== 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: Select all
GRAB 300
LINK 800
; FIX PLACEHOLDERS
MARK FIXLP
TEST EOF
TJMP DIAL
TEST F < 9999
TJMP FIXLP
SEEK -1
COPY -10 F
JUMP FIXLP
Code: Select all
MARK DIAL
SEEK -9999
@REP 11
MODI F 10 #DIAL
@END
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: Select all
COPY #DIAL T
FJMP NEXTNUMBER
; TODO
;HANGUP
COPY -1 #DIAL
MARK NEXTNUMBER
Code: Select all
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
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: Select all
MARK NEXTNUMBER
TEST X = 0
TJMP END
; ALREADY AT EOF
SEEK -1
TEST F < 0
SEEK -1
SUBI X 1 X
FJMP NEXTNUMBER
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: Select all
SEEK -9999
@REP 11
MODI F 10 M
@END
Fuck 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: Select all
DROP
LINK 800
KILL
GRAB 300
WIPE
I also have a couple non-conditional JUMP statements. Perhaps I can get rid of one by reordering the code.
Code: Select all
;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
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: Select all
;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 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: Select all
MARK FIND
SEEK -1
TEST F = -10
ADDI X 1 X
SEEK -1
FJMP FIND
SEEK 9999
SUBI 0 X F
COPY 0 X
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. Here's the full code if you want to give it a go.
Next time, we help selenium_wolf.