Hacking games, banks, and yourself - Let's Play EXAPUNKS
To quote the Steam store page:
The year is 1997. You used to be a hacker, but now you have the phage. You made a deal: one hack, one dose. There’s nothing left to lose... except your life.
EXAPUNKS is a Zachtronics game released in 2018. It's similar to their earlier titles TIS-100 and Shenzhen I/O and you know what that means - we're gonna get programming, so get your thinking caps on.
Speaking of Shenzhen I/O, make sure to check Quackles's LP of that game if you haven't yet. You could call it an inspiration for this LP.
This game has an assembly-like programming language that should be straightforward enough if you played those earlier games, but it's just different enough to really be its own thing.
I also really like the plot and the way this game makes itself feel 'realistic'. But you'll see that when we get to it.
Formatting note
- I will use italic text to indicate any text that is actually in the game.
- Regular text is me taking the role of the game's protagonist.
- I will use this CO2 symbol whenever I break the fourth wall, such as for game design notes.
Spoiler policy
Please do not talk about anything in the game or additional materials we have not encountered yet.
Table of contents
Part 1 - Old friends and new issues
Part 2 - TRASH WORLD NEWS - Tutorial 1
Part 3 - TRASH WORLD NEWS - Tutorial 2
Part 4 - TRASH WORLD NEWS - Tutorial 3
Part 5 - TRASH WORLD NEWS - Tutorial 4
Part 6 - Euclid's Pizza
Part 7 - Mitsuzen HDI-10
Part 8 - Peanut-free Peanut bars
Part 9 - Point of Sale System
Part 10 - SFCTA Highway Sign
Part 11 - Лрикладной Семиотики
Part 12 - UC Berkeley EECS
Part 12.5 - ПАСЬЯНС
Part 13 - Workhouse Work Management System
Part 14 - Equity First Bank
Part 15 - Mitsuzen HDI-10 - Heart
Part 16 - TRASH WORLD NEWS - Unknown Context
Part 17 - Baby's first hacker battle
Part 18 - TEC Redshift - Dev kit
Part 19 - A SECRET TO EVERYBODY
Part 20 - Digital Library Project
Part 21 - EXA-Blaster Modem - Radio Station hack
Part 22 - StreetSmarts GIS Database
Part 23 - Valhalla Hacker Battle
Part 24 - Mitsuzen HDI-10 - Left hand
Part 25 - Sawayama WonderDisc
Part 26 - HACK*MATCH
Part 27 - Alliance Power and Light
Part 28 - Deadlock's Domain
Part 29 - Xtreme League Baseball
Part 30 - King's Ransom Online
Part 31 - KGOG-TV
Part 32 - Equity First Bank - Money Transfer
Part 33 - The Wormhole
Part 34 - TEC EXA-Blaster™ Modem
Part 35 - Last Stop Snaxnet - Nuclear centrifuges
Part 36 - Visual Cortex
Part 37 - Holman Dynamics
Part 38 - Aberdeen
Part 39 - U.S. Government
Part 40 - Unknown Network
Part 41 - Revelations
Part 42 - Pager Network
Part 43 - Cerebral Cortex
Part 44 - Bloodlust Online
Part 45 - Motor Vehicle Administration
Part 46 - Cybermyth studios
Part 47 - US Department of Defense
Part 48 - The Wardialer
Part 49 - Española Valley High School
Part 50 - Let's go RAIDing
Part 51 - CrystalAir International
Part 52 - š Ñ| ö/ ~ öB è[ å‡ ÑE È‚ t 7Ò
Part 53 - Trash World Roundup
As we start the game and the loading screen pops up we immediately get blasted by this jam.
OST: Exapunks LISTEN to this. Seriously.
This is my apartment.
It acts as the main menu screen.
OST: Apartment
The TEC Constellation II tablet is our options menu. Mostly standard stuff - controls, full screen vs windowed, sound volume, a few visual options, a profanity filter, and some options related to how online leaderboards are shown. I'll make one change so that my Steam friends aren't highlighted on the leaderboards, for their privacy. Otherwise the defaults are fine.
Well. I might be in all sorts of trouble but at least I got a good shitposting box. Let's check out my SAWAYAMA Z7 TurboLance.
I'm using the Axiom Developer Edition operating system. It's what all the cool kids use. Very customizable and doesn't get in your way. Chatsubo is a nice chat program. I like to lurk in the EXAPUNKS channel, where all of the hackers from the old days hang out. It's a bit quiet at the moment though.
But I almost forgot, I'm supposed to meet up with someone. I'm gonna need meds soon.
*Knock knock*
Someone's at the door.
I can't remember... Did I ask them to come up?
Things have been hazy...
Every day, more of my body is turned into junk...
Nivas: Hey.
Nivas: Hey, can you hear me?
Nivas: I heard you were looking for some medicine for the phage?
Nivas is voice acted by Emma Adele Galvin. The protagonist's voice/thoughts are not voice acted.
Looks like you definitely need it.
That's right... Nivas.
Said they could find anything.
Even bootleg medication.
So, I do have a source. I know where to get it.
It's cheaper than the real deal, but it's still gonna be pricey...
Nivas pauses and looks me in the eye.
The going rate is 700 a dose. You need to take one dose every day.
Yeah, that's dollars.
Sorry, I don't set the prices. I'm just a courier.
It's not like any of that money goes to me...
Anyway, if you do get the money, give me a call.
Nivas left for now.
While I was at the door, there was some activity in the chat. At least it's not entirely abandoned.
Anyway, I'm going to need money and I'm going to need it fast. Well, I have this program, WorkHouse on my computer. People can hire me through that to do boring tasks for a couple pennies. At least I don't have to leave my house, so people don't have to see me like this.
Let's give it a go.
Man, this looks terrible already. And who the fuck has the money to pay 200 dollars for a steak or whatever? Ugh.
Hehehe.
Huh? It said "validating..." and it accepted that? Still, ten cents? This will take forever.
I actually have no idea what makes this thing accept your input. Putting in completely random crap makes it say "A peer reviewer rejected your work.", but a certain amount of minor errors seems allowed.
what the fuck
Wow, a whole ten cents!
Congratulations.
You only need to do 6,999 more of these and you'll have enough for today.
Sometimes the game gives you dialogue choices. I don't think it affects anything but the next line or two. You can replay cutscenes at any time so if you like you could go through all the choices to see what they do.
By the way, this character is voiced by Sarah Elmaleh.
Who is this?
Don't worry about that right now.
You need that medication to stay alive, right?
Well guess what? I can get it to you.
You'll need to start hacking again though.
One hack, one dose.
Easy.
Deal?
I've forgotten how...
You're about to remember.
Knock knock.
I've no idea who or what that was but she seems to have set up residence on my desktop and I can't make her disappear.
There's also some activity in the chat but that's not important right now. A meeting with an old friend appeared in my organizer, and it's planned for, huh, right now?
*knock knock*
Someone's at the door again.
An old friend...
Ghast.
I knew his real name once, but that's gone now.
Hey. Came by to give you something.
Ghast hands me a small booklet. It's made of real paper.
Apparently, it's something called TRASH WORLD NEWS...
I can't hack like I used to.
Whatever edge I had, it's gone now.
But I had to keep doing something.
Something to keep the culture alive, you know?
Computers are running everything these days.
Before long, human beings aren't even going to have a say.
So now I equip people with knowledge.
The knowledge to make a computer do what you want, on your terms.
Anyway, I won't keep you. I know you like to be alone.
Hope you like what you see in the zine.
Thanks mate, I appreciate it.
Ghast is voiced by Cornell Womack.
As the game says here, it uses printed zines as a reference manual. Originally, these were actually printed and sent to your home if you bought the limited release. I absolutely love that! It makes the game feel much more real.
As a matter of fact, hacker magazines were a real thing in the 80s and 90s. They had all sorts of information on phreaking (phone hacking), early modem usage, and so on.
For the Steam version, they come as PDF files, both in a printable format and a format for digital reading. Since last year, it's also possible to buy physical copies again through Lulu.
So, my old friend Ghast came by and gave me this hacking magazine. Let's have a look.
The cover:
Point of sale systems? Banks? Damn, these people seem to have a lot of insider info. Well, at least there's a "for EDUCATIONAL PURPOSES ONLY" disclaimer to the side. I'm sure that'll help.
Ghast is right you know. Programs and websites run by big corporates... nothing good comes from that. As for the EXAs, I've heard of those but I'm not sure what they do.
Okay... so to summarize, EXAs are these small programs that move from one computer to another. You can actually see them move around in a network, and they contain code and registers. The code of course tells them what to do. The registers are slots to store values. You can put numbers in, both positive and negative, as well as words, and you can access them from code.
Yeah. That sounds kinda familiar... every EXA acts like a tiny little CPU of its own.
This page explains modern networking. A single computer is called a "host" and it looks like a little platform on the EXODUS programming tool. The number of squares in a host shows how many EXAs can be in a computer at once. Hosts are connected to each other through "links" which can be traversed by EXAs.
There are "files" which can store a lot of values but don't have code. EXAs can interact with files by grabbing and dropping them.
Finally, there's "hardware registers" which are a way for EXAs to talk to the hardware of the host system directly. Not sure what that's useful for but maybe I'll find out.
Hey... something appeared on my computer. I'll leave the magazine on my table so I can look at it at any time.
Looks like =plastered has been busy. Good for them!
Anyway, a new item appeared in my organizer. Is this what the mysterious lady on my screen was talking about?
Actual levels usually start and end with a conversation. These are not voice-acted.
Would you look at that!
That zine's got a hacking tutorial in it.
How about that?
Funny coincidence, huh?
Isn't it?
I don't really trust her. Why would I interact with her more than necessary?
I guess.
Oh, come on.
I timed that just right, didn't I?
Let's continue.
OST: Getting Started
This must be that EXODUS environment mentioned in the Zine. Let's click around a bit and see how it works.
The X at the top right lets me go back to the desktop and organizer. Always good to know.
At the top left, where it says NEW SOLUTION 1 I can put in a name and click the little file icon to save and load multiple solutions. It even has stats for how fast each solution is, so I can optimize my programs. Nice!
I start with one EXA simply called XA which has no code yet, but I can just type it in. I can create more EXAs and name them, but they are limited to two-character names.
Below that I seem to have a file browser, even listing files outside my own computer. I don't know why that works but okay.
The big screen to the right is obviously my network overview, and the bottom part contains some controls and my goal for this assignment.
Pressing the SHOW GOAL button changes the network overview so that the file is moved to the OUTBOX host and there are no EXAs in sight.
Well, I guess I should check out that tutorial.
Since I don't want to overwhelm you folks I'll only post the left page for now, since it's all we need for this puzzle.
Well, this seems easy enough. Just literally copy what it says in the book? I'll give that a try.
Looks good to me.
So, the controls at the bottom. From left to right: reset simulation, pause simulation, step through a single cycle (useful for debugging), run (at a watchable speed), fast forward.
Each cycle, every EXA can execute one line of code.
Here I let the simulation run for two cycles. In the first cycle, XA executed LINK 800. That means: from the starting point (my own host, called RHIZOME), traverse the link with ID 800. The EXA ended up in the INBOX host. Then, GRAB 200 caused the EXA to grab the file with ID 200. This was now possible because the EXA and the file were in the same host.
Another LINK 800 operation makes the EXA traverse another link. Since from the point of view of the INBOX, link ID 800 goes to the OUTBOX, that's where the EXA went.
The DROP command lets the EXA drop the file, and finally the HALT command destroys the EXA entirely. The "Leave no trace" goal means you need to either HALT all your EXAs or get them all to come back home.
Once my program is working, the simulation will then run 100 tests at fast forward speed. I guess it wants to make sure my solution wasn't a fluke and actually works all the time.
And we get stats. Cycles is the total amount of cycles the program needed to complete. Size is a measure of the amount of lines of code. For both, lower is better. We'll get into Activity later.
Wait a second, does that say "Record Solution Gif"? That is very kind, game. Don't mind if I do.
Neat.
Now, if you noticed, we didn't have the best score on the leaderboard histogram. Any idea what could improve it?
.
.
.
.
.
.
.
.
Turns out the DROP operation is not necessary. An EXA will automatically drop whatever it's holding if it gets destroyed.
It's easy enough to optimize this tutorial level if you know what you're doing. It will get much harder later. I'll probably come up with a reasonable solution or two and leave finding the optimal solutions to the thread.
Back in the organizer, we can see our stats, and a new mission appeared.
Also some more talking in the chat.
Finally, the lady behind our assignments has more to say after each assignment.
I can see everything, you know.
Everything on your computer.
Even though they're not the most exciting options, for some additional thread participation, I'll see if I can end posts on one of these dialogue option screens.
Please vote what our protagonist answers.
Let's start with some comments from the threads.
Good point. That reduces the size to 3, but leaves the cycles at 4. I guess the EXA "realizing" it is out of instructions takes a cycle.Quackles wrote: Also, fun fact: If an EXA runs out of instructions, it will stop. This means it's possible to save an instruction by leaving off the HALT at the end.
No, I only have the Steam version. I have beaten story mode before, but not the postgame stuff.GuavaMoment wrote: Do you have the physical version of this game?
Good luck getting through all the levels! There's one in particular at the end only 3 of my steam friends have beaten.
Adding up the votes from both the SA and Beach threads, we have one vote for "Oh" and seven votes for "There's not much to see". So let's dive into it.
There's not much to see.
There isn't.
I expected you to be sitting on all kinds of secrets...
It's alright though.
We'll find plenty more.
Alright, looks like next up is another tutorial.
People or animals are often motivated by the anticipation of a reward.
Are you anticipating the medication as a reward?
Oh come on, lady. I need that to survive, it's not like I have a choice. That's not a reward.
I guess you're right.
It's about survival.
Processing.
Okay. Let's continue.
Yes, survival, that's what I said... wait, did you just say "processing"? Hm, could it possibly be the case that the lady, whose window literally says "Emulated Multi-Branch Emotional Reasoning", is not in fact, human?
Since the game doesn't try very hard to hide this fact, neither will I. As the screen says, we can just call her EMBER.
OST: Getting Started
Once again, one of the goals is "Leave no trace." Since that seems to be common, I won't be mentioning it anymore.
Clicking "Show Goal" in this case not only shows file 200 in the output, but it shows the value 436 appended to the end of the file.
Anyway, let's see what Ghast has to say about this tutorial.
Alright, just copy the code again. That I can do.
Code copied, and I started stepping through it. Just as a note - there doesn't seem to be a limit to how many lines of code a single EXA can have. There's only the global limit, which is 50 for this tutorial assignment.
Anyway, the LINK 800 took the EXA to the INBOX again, and the GRAB operation grabbed the file. As you can see, the file has moved from the file explorer under the "CREATE NEW EXA" button to being 'held' by the EXA's window.
Now, the new COPY operation does exactly what it says, it copies a value from one place to another. In this case, from the F register to the X register. However, the F register always points to whatever file the EXA's holding - and more specifically, the location of the cursor within the file. So F holds '72' right now.
The value 72 has been copied from F into X now, and the file's cursor moved to the next position. The cursor moves forward automatically whenever you interact with the F register.
The ADDI operation adds two values together (the first two arguments, X and F) and stores it in the register given by the third argument (X). So, it will add 72 from the X register to 52 from the cursor's location in the file.
Which makes 124 and moves the file cursor forward again. MULI works the same as ADDI, except it multiplies the values.
And SUBI subtracts the second value from the first.
The cursor is now at the end of the file.
If you use COPY to F while the cursor is at the end, it will append the value to the end of the file.
The rest of the program is stuff we've seen before. We move the file to the OUTBOX and destroy the EXA.
The gif function isn't that useful here because it only shows EXAs moving around, it doesn't show operations within files. So the result seems identical to the one from the first tutorial.
Not bad... but can I do better?
You may have thought of doing something like this. Use meaningless COPY operations to move the cursor to the end of the file and then just write a hardcoded value '436' in there. But that doesn't work - not only doesn't it save any time, the values in the file are different for each test run so only the first out of a hundred runs succeeds with this code.
What I can do is apply my learnings from the first tutorial. The HALT is unnecessary because an EXA destroys itself once it runs out of instructions, and the DROP is unnecessary because when an EXA destroys itself, it drops whatever it's holding.
This drops the cycle count to 9 and the size to 8.
There's one more trick that's less obvious. You can combine the COPY and ADDI operations like this:
Why does this work? Well, now the ADDI operation says "add the value in F to the value in F and store that to X". But, remember, EVERY interaction with F moves the cursor forward. So this actually adds the first value in the file to the second value and stores that in X, giving the same result with one less operation.
This drops both the size and count one further.
As you can see from the leaderboard, a lot of people managed to save another cycle. That might be because they use an operator that's not been introduced yet. Feel free to discuss improvements in the thread, but if they involve operators we haven't seen yet, please put them in spoiler tags, for any readers that prefer experiencing this game as it comes.
Does it upset you that I will provide your medication only if you work for me?
I'm noticing that Ember usually has two questions to ask between assignments - one in the outro and one in the intro for the next assignment. You know what... since one doesn't affect the other I'll just throw both of 'em up for a vote.
What do you think of your life situation overall at the moment?
Please vote for both questions.
Of course.megane wrote: A way to save a cycle (without using any new operations) is SUBI X F F.
We can use the exact same trick that saved a COPY operation at the beginning to have the subtraction step write directly to the file. Remember, reading F moves the file pointer forward so the write happens at the end of the file
Which nets us the best solution anyone found. Anyway, where were we?
Adding the votes in both threads together, one vote for Nothing is free and two for Does it matter?
Does it matter? I don't have a choice.
That's true.
Your agency is severely constrained by your situation.
Interesting.
This is good data.
Data? What kind of data are you collecting?
Anyway, while we were busy the folks in the chat were discussing that wardialer business.
Hah, yeah. Might make for a bit of a show.
Our next assignment is TRASH WORLD NEWS - Tutorial 3. I see only four tutorial pages in the Zine so hopefully this tutorial nonsense is over soon. I want to get to work already.
One vote for What? and two for Why all the questions? this time.
Why all the questions?
I am collecting data.
It will help me formulate future actions and responses.
In other words, I'm curious, that's all.
Recalibrating.
Let's continue.
Okay, whatever. Let's dive into it.
OST: Getting Started
A slightly more complicated network this time.
So, the file we need to copy data from is sitting in the SECRET host at the top left. And my EXA can't just carry it out. The link to SECRET is one-way only, since it has no ID on the SECRET side. We're going to need something new. Let's see what Ghast has to say.
No solution to copy anymore. Instead Ghast explains some new instructions. I'll build something with them and explain during the test run.
XA will go up to the SECRET host and send the file data over the 'M' communication register, while XB will read from there and write it to a new file.
As always, let's first get the EXAs to the right place.
XA grabbed the file, while XB used the MAKE command to create an empty file.
Next, XA sends the first entry in the file (the word ECHO) to the M register.
M has two modes, which you can set for each EXA: GLOBAL and LOCAL. I like to think of them as radio broadcast frequencies. If you write a value to the M register, the EXA will completely pause until some other EXA reads from it. If you try to read a value from the M register and nobody is broadcasting, that EXA will pause until it can receive something.
EXAs in GLOBAL mode can communicate across hosts, but EXAs in LOCAL mode can only communicate if they're in the same host. Importantly, and this is where the frequency analogy comes in, an EXA listening in GLOBAL mode cannot receive a message from an EXA in the same host that's sending in LOCAL mode. It can receive messages from an EXA in the same host that's also broadcasting in GLOBAL mode though.
As you can see, XA is sending, and XB is already ready to receive so it will get the message the very next cycle.
Since I need to swap the order of the entries in the file, I decided to buffer the first entry in the X register, then copy the second one directly into the file, and then copy the buffered value into the file.
Words act exactly the same as numbers except you can't use any of the arithmetic instructions with them.
The file info was copied, XA ran WIPE as its last instruction, which deletes the file it's holding. Next, as they run out of instructions, both EXAs self-destruct and the assignment is complete.
The EXODUS simulator shows an EXA wiggling with static around it if you pause the simulation in the cycle before it self-destructs, which is a nice touch.
As usual, we can do better.
The first improvement is both incredibly simple and a bit stupid.
All we do is swap the instructions of XA and XB. That's easy enough - the game allows you to select text with your mouse and copy-paste with ctrl+c/x/v. This drops the cycle count by one. But why does this work?
Well, every cycle, every EXA will attempt to execute one instruction. However, only one EXA can traverse a specific network link at a time, so XA and XB can't both cross from RHIZOME into INBOX during the same cycle. Since both start with the LINK 800 instruction, who wins?
Turns out the game has some hidden ordering of EXAs which determines this.
In the original solution, the receiving EXA wasted a cycle reading from M while the other wasn't sending yet. This caused the sending EXA to also waste a cycle waiting for the other one to accept the message. Both still finished at the same time, but both wasted a cycle. By switching who arrives first, the receiving EXA starts reading at the same cycle the other EXA sends the data, and they can both save the waiting cycle. Yeah, sometimes this kind of meta gaming is necessary in EXAPUNKS to get the high score.
The histograms show that for this assignment it is also possible to get a lower (=better) activity score. As the game explains it, Your activity score is the number of times EXAs you control execute LINK or KILL instructions. Well, we haven't even seen the KILL instruction yet. We do four LINKs total to get the EXAs to the right places. Reducing that is possible, but not with the instruction set we know about right now. I'll get back to this puzzle a bit later.
Oh, while I was leafing through the Zine I noticed there's some stuff in the back that's not related to programming. For instance there's a recipe!
Anyone up for some dumpster donuts?
Anyway, let's submit my solution and see what Ember has to say.
How do you feel about it now?
You're closer to the goal.
Any change?
And also the intro dialogue for the next assignment:
Yes. You can do it!
This is positive encouragement.
It is designed to increase activity in your prefrontal cortex.
As usual, please vote for both questions.
I'm bad at puzzles, so I'd never play this myself and I hadn't even heard of it before this thread, but it seems really cool, thanks for showing it off.
For an older game by 'em, some readers might remember an old flash game called The Codex of Alchemical Engineering, that game is the predecessor to the mechanical oriented games like SpaceChem and Opus Magnum
Thank you for sharing this game with us, my votes are "I feel like I'm in a study" and "Don't count on it"
The last of the tutorial assignments! Let's jump right into it.
How do you feel about it now?
You're closer to the goal.
Any change?
The votes were more divided this time. One for Not really and two each for the other two options. I did a pseudorandom toin coss to settle the tie.
I feel like I'm in a study.
Sure, think of it like that.
Everything's an opportunity to learn.
Anyway, there's just one more tutorial to go.
You're almost there.
Meanwhile in the chat:
Yeah. "Don't touch the poop", as they say.
Sounds like a useful thing to learn.
Yes. You can do it!
This is positive encouragement.
It is designed to increase activity in your prefrontal cortex.
Three votes for Don't count on it and one each for the other options.
Don't count on it.
But you're doing great!
There, a little more for you.
I'm sure it will help.
Well, whatever. Let's see the assignment.
OST: Getting Started
The Zine page for this assignment goes into loops and conditionals. These are a necessary component to make any programming language "Turing-complete", which basically means it can be used to write any arbitrary program.
We learn four new instructions. The first is TEST. It tests whether some comparison is true or not. What this page doesn't mention is that it can do checks with =, <, and > (is number A equal to, less than, greater than number B). If the comparison is true, it places 1 in the T register. If not, it places 0 there.
TJMP and FJMP go together: TJMP (jump if true) makes the EXA start executing elsewhere in the program, if T equals 1. Otherwise it just continues with the next instruction down the line. FJMP (jump if false) does the opposite: it causes the EXA to start executing elsewhere if T is set to 0.
It is not required to set T with the TEST command, you can just COPY or ADDI or whatever to T as well.
How does it decide where to jump execution to? Well, you need to add a label, and you can mark the location to jump to with MARK label. The example code in the Zine shows how it's done.
I think things will get more clear as I try to solve the assignment.
But I think this is a good time to bring up Compiler errors.
A compiler is a special program that translates human readable code (such as the EXA language we've been learning) into machine instructions that can actually be fed into a CPU. But it can only translate code that follows the syntax rules. Otherwise, it will give an error and you can't even run or test your program. That might seem like a bad thing but it's actually very helpful - it prevents a lot of bugs from making it into the final program.
In the case of EXODUS, any compiler errors are underlined in red and you can just hover over them to see the error message. The run buttons at the bottom are disabled until you fix all compiler errors.
Here, I hover over the 'instruction' MAGIC. Sadly, EXAs have no MAGIC instruction, and the compiler immediately tells me so. This is quite helpful when coming from another language, such as the one used to program chips by that automation company in Shenzhen you might've heard of.
In a similar vein, you can't jump to a label that isn't defined anywhere by a MARK instruction.
Anyway, back to the assignment itself.
Since this tutorial ramps up the complexity I'll try to take you step by step through my thinking process instead of just giving the solution all at once.
I first built the 'skeleton' of the code. I decided to go with two EXAs where one reads the file and sends data to the other who writes it.
XA grabs the file, copies the value into X, and then, as an example, forwards it to the communication register M and subtracts one for the next round.
Why use X as an intermediate? Because doing arithmetic such as SUBI directly on a file is very annoying. The file cursor keeps moving forward and is never where you want it to be.
Anyway, for now XB goes to the OUTBOX host and puts the value it gets from M into a newly created file.
Now, just copy-paste that subtraction-and-copy code 9 times and we're done, right? Not quite. Since the starting number is different for each test, that would never work. It would also make the program size huge. Let's write a loop!
I have two loops in place now. XA does the same it did before, but after subtracting 1 from X it checks if X is less than 0. If that is NOT the case, it jumps back to LOOPSTART. If it is, we know it's done (because it just sent 0 to the other EXA, then subtracted 1, so it's sitting at -1 now). It WIPEs the file and self destructs as it runs out of instructions.
Note that both the MARK instruction and the empty lines never use up any cycles. The empty lines help with code readability and the MARK is exactly that, just a bookmark. It doesn't "do" anything. The FJMP instruction does use a cycle, though.
XB has a loop too, and as you can see I named the mark LOOPSTART as well. That's fine - an EXA only knows about its own code, it won't suddenly start running another EXA's code. Anyway, this loop is very simple. It copies the value it gets from M into the file, and will do so repeatedly. Since there's no TEST, T is always zero, and FJMP will always make it jump back.
The resulting file is exactly what I need! However, XB gets stuck in this case. Once XA is done, the next COPY M F instruction in XB will wait forever for a message and never get one. So I need to do a bit more.
This should solve it. The loop in XB is now conditional as well. It only jumps back if X does not equal 0, otherwise it tries to run the next instruction, doesn't find one, and self-destructs.
Again I decided to use X as an intermediate because I run into the same annoyance of the file cursor being in the wrong place if I do a TEST on F directly.
Let's test the program.
Both EXAs are set up where they need to be, XA is about to send and XB is about to receive.
The EXAs are at their first TEST. Since for neither of them the test is true, they jump back to the start of the loop.
XA is ready to send the next number, which is one less than the last one: 8.
The loops repeat a whole bunch of times, writing a new number to the file every round, until only the 0 is left to write.
That's done now, and in the last loop, both EXAs' tests are now true, so the FJMP does not jump back to the loop, but causes the EXAs to finish.
And I'm left with nothing but the correct file.
As you can see we can do (much) better on every count. That's not surprising - there are many ways to optimize loops like this.
This is the test run data screen at the end. For every test run, it shows the number of cycles and the activity. You can see the cycles differs quite a bit. That's because the EXAs have to go through more loops if the starting number is higher. The value shown on the leaderboard is the worst value of all 100 test runs.
So, how can we improve this?
Let's start with the most straightforward improvement: We can drop the activity from 3 to 2 by switching to a one-EXA solution. Maybe that improves other stats too.
This EXA GRABs the file in the INBOX, copies the one value to its X register, then WIPEs the file and continues on to the OUTBOX. There it MAKEs an empty file, copies X into the file, subtracts 1 from X, copies that into the file, and repeats until X is less than 0.
This change improves all stats significantly.
And this was another idea I had. We keep two EXAs, however the first EXA only sends the individual value from the file to M, and the second handles lowering the value and writing it to the file repeatedly. Since the communication through M took a lot of cycles this is a huge improvement compared to the other two-EXA solution.
This solution uses 406 cycles, has size 13 and the activity is of course 3 again.
That's one cycle lower than the one-EXA solution but worse, size and activity-wise.
Luckily, the game keeps track of each leaderboard separately. So currently it says my best solutions have 406 cycles, size 11 and activity 2. You don't need to optimize all 3 in the same solution!
Anyway, apparently better solutions are possible. Some people solved it with size 12 and they got the cycle count under 200.
I'm not sure what they are. For the cycle count I suspect they did something like hardcode a bunch of COPY 5 F; COPY 4 F; COPY 3 F; etc. and then using a whole bunch of conditional jumps to decide where to start, avoiding the looping code altogether and requiring less TESTs overall.
Since we're out of tutorial mode now, I will make a follow-up post containing the full instruction sheet of the EXAs. We've seen most of them already anyway. Feel free to use that to suggest further improvements. Also, with that instruction sheet, perhaps you can figure out how to reduce the activity number in the previous tutorial.
If you're not that familiar with programming and don't get happy from reading instruction sheets, feel free to skip that post. You'll see the instructions in use before too long.
But before that, let's go check on Ember.
Nice work.
I knew you could do it.
And the intro to the next assignment.
So you want to know who I am.
I will reveal this information.
Let us discuss it over a pizza...
A free pizza.
I want to see you use your regained skills first.
Next time... pizza, apparently. Please vote for both questions.
(click to enlarge)
You can also quickly find the reference guide in the second post.
An overview of all the instructions we haven't seen yet:
DIVI - Similar to the ADDI/SUBI/MULI arithmetic instructions we've seen. DIVI does a division, rounding down to the nearest whole number.
MODI - Another arithmetic instruction, for the modulo operation. Basically, this gives the remainder of a division. For example, 11 / 3 = 3 remains 2. So DIVI 11 3 returns 3, while MODI 11 3 returns 2.
SWIZ - "Swizzles" the value. This is a very unusual instruction that allows you to rearrange or extract digits of a number.
JUMP - Unconditional jump. This jumps to a MARK label regardless of the state of the T register.
REPL - Creates a copy of this EXA (so with the same code), and have the copy start executing code from the MARK label following this instruction.
KILL - Destroys another EXA in the same host as this EXA.
HOST - Copies the name of the host (such as INBOX) into a register.
MODE - Switches the M communication mode between global and local.
VOID M - Reads a value from M but doesn't store it anywhere.
TEST MRD - If this EXA could read a message from another EXA through the M register this cycle, sets T to 1, otherwise 0.
FILE - Copies the ID of the held file (such as '200') into the given register.
SEEK - Move the cursor within the file, the number of steps given. E.g. SEEK 2 moves the cursor two forward and SEEK -3 moves it three steps backward. SEEK -9999 moves the cursor to the beginning of a file and SEEK 9999 to the end.
VOID F - Deletes the value at the cursor from the file.
TEST EOF Sets T to 1 if the file cursor is at the end of the file, 0 otherwise.
NOTE - This is not an instruction, but a way to leave notes to explain the code to yourself. It does not take up any cycles.
NOOP - Do nothing for a cycle. Can be used if this EXA needs to wait for another EXA.
RAND - Generate a random number.
Thanks for all the posts about optimizations! What I'll do is discuss them in the first part of new updates. If you like to nerd out about that and get a deeper comprehension of the code, read on.
I will try to explain all the submissions as clearly as I can. Even if you're not that familiar with programming, give it a try! Perhaps my explanations help.
In this part, I need to go into quite some detail as your ideas introduced a lot of new concepts.
If you want to skip past all that because you're here mostly for the plot and new puzzles, search for the next occurrence of the word "pizza" in this post.
=== Trash World Inbox ===
Alright, before we dive into your suggestions, let's jump back for a moment to Tutorial 3.
In my original solution we got it down to 9 cycles, with size 12 and activity 4. Now that we have the full language reference, we can get the activity further down.
The solution is the REPL command. It replicates the EXA, and the replicated one starts processing from the given label (in this case WRITER).
By doing this after the first LINK, the total number of LINK operations is reduced to 3.
Here is the simulation just after the REPL instruction. You see the new EXA has the same name as the old but with a number appended. And it starts executing below the WRITER mark, while the original EXA just continues below the REPL instruction.
I realize now I named the mark wrong - the 'writer' actually does the reading. Oh well.
We got the activity down to three, but the cycles and size increased a bit. If you're wondering why that says "Unchanged", it's because I tend to run the simulation multiple times trying small tweaks and it only compares your new score to the last run.
I could've put a HALT instruction just before the WRITER mark. It's not really necessary. After its last copy to F instruction, the original XA will skip the MARK (which doesn't take a cycle), try to execute LINK 799, find there's no 799 link id from its current position, throw an error and explode.
Errors that destroy an EXA can be surprisingly useful, the Zine even has a page on it. I'll show it below when we get to the new assignment.
Using a HALT here has some limited use - destroying an EXA through an error takes two cycles (one to show the error and one to explode), while a HALT does it in one. So adding a HALT would save one cycle but also increase the size by 1.
Either way, we don't need to do this because since each score is tracked separately, with the original solution and the REPL solution combined, we got the best score in each of the histograms now.
And with that, let's go to Tutorial 4 and the optimizations y'all sent in.
Looks like I'll have to go into a lot of detail this time because you chose some strategies we haven't seen at all yet.
Yes, so what you need to know for this is that TJMP activates for any non-zero value of T, not just 1 (while FJMP only activates on 0). That is: any non-zero value is considered "True".idhrendur wrote: I was waiting for the reference because one optimization is obvious: use T as the working register and skip the check. You're waiting for the value to be zero anyways, and TJMP will implicitly do that check for you.
Changing this solution from the last update...
... to this solution.
This drops the cycle count from 406 to 304.
Basically what this is doing is, it subtracts 1 from the value in T every loop, then if T is still greater than 0, it jumps back to LOOPSTART. The final COPY is necessary to get the required zero at the end of the file.
Doing the same with the one-EXA solution gives us a count of 305 by the way.
This one starts by storing a value that's one higher than what we need. Since that can be done in a single ADDI instruction that replaces the COPY, it doesn't change the size. silentsnack reordered the loop so the SUBI happens before the COPY. That way we still write the correct values to the file - but we're still inside the loop when T becomes zero, so we write the zero to the file as well. That extra copy operation is no longer necessary and the size drops to 10.silentsnack wrote: To reduce solution size you can combine the "we need a file containing N+1 entries" with "we need to retrieve N from a file" into a single step by using ADDI F 1 [?] instead of COPY F [?] and changing the order slightly, e.g. with a single exaSize 10Code: Select all
LINK 800 GRAB 200 LINK 800 ADDI F 1 T WIPE MAKE MARK WATT SUBI T 1 T COPY T F TJMP WATT
Cycles 307
Activity 2
We've now minimized both the size and activity - all that's left is the cycles and since we don't care about size or activity anymore we can use every ugly trick you can think of.
I think silentsnack explains this one quite well. In my own words: it writes two values to the file every loop. E.g, if the value in T is 10, it writes 9, 8 and then updates T to 8. However, because you're only testing every 2 values, you'd run into trouble handling odd numbers. You'd check T at 1 (which is True), then next loop, T contains -1 (which is also True), and the loop would never end.silentsnack wrote: Another thing you can streamline is the fact that the TJMP step itself takes a cycle, so if you can pack more useful operations into a given execution of the loop the exa wastes less time doing its logic check. This ugly abomination does batches of 2 variables but as an ugly kludge has to work around the fact that it even numbers and odd numbers behave differently (it does this by adding another dummy entry at the beginning, then erasing it once the iterator has finished doing its thing)Cycles: 212Code: Select all
LINK 800 GRAB 200 LINK 800 ADDI F 1 X WIPE MAKE MODI X 2 T TJMP EVEN COPY X T MARK LOOPA SUBI T 1 F SUBI T 2 F SUBI T 2 T TJMP LOOPA HALT MARK EVEN ADDI X 1 T MARK LOOPB SUBI T 1 F SUBI T 2 F SUBI T 2 T TJMP LOOPB SEEK -9999 VOID F
Size: 24
Activity 2
This is handled by splitting the code into two and using a MODI operation to find if the number is even. MODI X 2 equals 0 for even numbers, 1 for odd numbers. Since the MODI operation is done on the original value + 1 (there's an ADDI on the fourth line), it becomes the opposite so we have to jump to EVEN if the value is 1 (= True). In that case one more is added to the value to make sure the loops exits correctly, then SEEK -9999, VOID F is used to delete the first value of the file, which was one too high.
So, for each test run it runs EITHER the even, or the odd code. Never both in the same run.
Meanwhile, Quackles was working on a slightly different solution.
Quackles wrote: To get the cycles much lower than the default, you'll have to be clever. Specifically, you have to make a loop with only two instructions in it.
That sounds impossible, but what if you didn't do the loop jump every cycle? In fact, what if you did it as little as possible?
In that case, you'd have to have something else to stop you at 0.
So, the solution: split your EXA into two. One monitors the other, and force-quits it after it's written '0'. We know this works because every expression takes the same amount of time.
Here is Quackles' actual program. It looks different from the code example in their post and I'll get into why that is in a bit.
The lines starting with a ; (semicolon) are just notes, like the NOTE "instruction". They help us understand the program but the EXAs ignore them entirely, their size is 0 and they don't take up any cycles.
First, let's dig into how it actually works.
It uses two tricks:
- The first is "loop unrolling". Instead of doing jump instructions we just repeat the code of the loop a bunch of times. Since each jump instruction takes a cycle, this can save a lot of cycles in a simple, but ugly way. In fact, silentsnack's solution did this as well by putting 2 steps in every loop.
Loop unrolling is actually a common optimization technique in real programs, although usually the programmer doesn't have to think about it, because the compiler is programmed to make an informed decision when this is worth it and changes the compiled program on the fly.
You see both of the loops (The loops starting with the TIMER and TIMER2 marks) are repeated 8 times and then use a jump. It would work just as well, and save even more cycles, to repeat them more times before the jump, as long as you have a way to stop looping once the file is written. But, doing so would push the size over this assignment's limit of 50. Submitting a solution like that counts as finishing the assignment - but is not eligible for the leaderboards at all. So we have to stop at 8. Still, that means you save seven out of each eight jump cycles, a huge improvement.
- The second improvement is using two EXAs, made through a REPL instruction. REPL copies all code, the state of the X and T registers, but, importantly, NOT the file the original is holding.
The first EXA loops through TIMER and checks every step if T is zero (in which case it jumps to BAIL). The second EXA loops through TIMER2, copying a new value to the file every step. It has an unconditional JUMP so it would loop forever.
This takes less cycles than the previous solutions because each 'loop' is only two cycles (SUBI / FJMP for the first EXA and COPY / SUBI for the second, as compared to the three-cycle SUBI / SUBI / TJMP originally.)
It does mean you have to stop the infinite loop somehow, and that's what the first EXA is for. As soon as T hits zero, it jumps to BAIL.
It then has to wait for a bit to allow the second EXA to write the last zero. Since it has to wait anyway, it can use this free cycle to WIPE the original file it was still holding, and then it uses KILL to kill the other, infinitely looping EXA, after which it HALTs itself.
The NOOP instruction turns out to be necessary in case the starting number is divisible by 8 - then the second EXA needs one more cycle before it writes the 0 because it's jumping back to TIMER2. It does no harm in other cases because the second EXA spends the additional cycle doing a harmless SUBI to T.
The final result is 219 cycles, 48 size (but who cares) and 3 activity (since KILL counts for activity).
Now, to go back to Quackles' actual code example.
We haven't seen the @REP 8 and @END instructions before, they aren't explained until the second edition of the Zine. They are macro instructions. In short, that means they tell the compiler to rewrite the code in some way before the program even starts. @REP 8 means that the lines of code following that instruction until the @END instruction, should be repeated 8 times. That is, it does the loop unroll for you.
REP macro instructions take up no cycles (since they are done before the program even starts), but they cannot make use of any registers or other variables (since they are done before the program even starts), only hardcoded numbers.
You also can't use REP to cheat the size limit - it counts the size after unrolling. In fact, as soon as you press run on this code, EXODUS will show the code after the REP instructions are processed, which is the screenshot I used to explain the solution above.
So, REP can't win you any cycles or size - the only thing it really does is save you from copy-pasting a bunch of code, making the result a bit easier to read.
Let's go to the next improvement silentsnack came up with:
The SUBI T @{1,1} F line is a special syntax you can use together with REP. The @{1,1} is substituted by a number at runtime. The initial value will be the left number (1 in this case), then every next value will be the previous one increased by the right number.silentsnack wrote: A couple of further tweaks actually make it a lot easier just to split it into two loops, by varying the number (in this case 8) you can change the batchsizeCycles 143Code: Select all
LINK 800 GRAB 200 LINK 800 ADDI F 1 X WIPE MAKE MODI X 8 T SUBI X T X FJMP EVEN MARK LOOPA SUBI T 1 T ADDI X T F TJMP LOOPA MARK EVEN COPY X T MARK LOOPB @REP 8 SUBI T @{1,1} F @END SUBI T 8 T TJMP LOOPB
Size 26
...which is both faster and also slightly less horrendously inelegant, but I dunno if you really want to get into @rep structures just yet.
In other words, this unrolls into
Code: Select all
SUBI T 1 F
SUBI T 2 F
SUBI T 3 F
Anyway, this is a very neat solution. It starts the same as their last solution but then it does a MODI X 8 T. T now contains the remainder of dividing the original value (plus 1) by 8. Then it deletes the remainder from X, meaning X will now contain a value that can be divided by 8.
If the remainder was already 0 it skips a step and jumps directly to EVEN. Else, it gets into LOOPA, where it writes the sum of the remainder and X (so the original value) to the file, reduces the remainder by 1, and repeat. In this loop you write all the values above the current value of X.
After that, whether working through a reminder was necessary or not, the program goes to MARK EVEN and writes 8 values to the file per LOOPB cycle. We know for sure the test will trigger at the right time because we made sure to not go to this loop before the value is divisible by 8.
Finally, Quackles submitted this solution:
It is indeed quite similar to the previous one, just handling the remainder at the end instead of at the start. And perhaps the TEST X > 7 is a bit easier to read than a test based on MODI.Quackles wrote: OK, so I was inspired by silentsnack's trick with the @{1,1} and made my version which uses a similar trick.
The range of numbers Ghast tests you with goes from 9 to 99. Because of this, we split the program into two parts: one that removes batches of 8 numbers at a time (and always runs at least once, so we can put the test at the end), using loop unrolling to write 8 numbers in 8 instructions.Code: Select all
LINK 800 GRAB 200 LINK 800 COPY F X WIPE MAKE MARK BOUND @REP 8 SUBI X @{0,1} F @END SUBI X 8 X TEST X > 7 TJMP BOUND COPY X T COPY T F FJMP FINAL ;T = 0? MARK LASTCALL SUBI T 1 T COPY T F TJMP LASTCALL MARK FINAL
The second part is a regular loop that jumps or falls through to end-of-instructions once 0 is written. It's quicker not to unroll the loop here.
152 cycles, 26 lines, 2 activity. I'm happy with that.
As silentsnack said, you can easily change the size of the loop by changing the 8s. That works in either of the solutions. I played around a bit with that and it looks like 8 is optimal. For both solutions you can't go too high because the code can't handle an initial number smaller than the loop size. And other loop sizes speed up some tests but makes the slowest test run (which is used for cycle count) slower.
Theoretically you could start with another split in the code path, to use a larger loop for larger initial numbers only. But that means you need at least two unrolled loops, probably pushing the size past the limit. And no matter what you choose, you always run into cases where you need to handle the biggest remainder possible, which will take longer for larger batch sizes, so I don't think it's possible. And the community seems to agree with me on that, since silentsnack's 143 cycles solution is the best anyone's gotten.
=== Euclid's pizza - Order System ===
Nice work.
I knew you could do it.
Looks like everyone was so busy optimizing code that they forgot to vote. No worries, I'll use my trusty three-sided dice again.
Stop that.
But you're awesome!
This is more talk designed to further excite your prefrontal cortex.
Now it's time to do some real hacking.
Finally.
I could use some pizza. How're we gonna do that?
So you want to know who I am.
I will reveal this information.
Let us discuss it over a pizza...
A free pizza.
I want to see you use your regained skills first.
Okay.
Great.
I am looking forward to this.
New OST: Code and Registers
This actual business system already looks a lot more complicated than the tutorial assignments.
Our assignment is:
- Append your order (file 300) to the end of the order list (file 200)
- Note that all orders, including yours, will consist of exactly five keywords.
While we're here, let's poke around in their files a bit.
I'm not entirely convinced those are actually the Pythagorean Theorem and Zeno's Paradox but what do I know...
Anyway, I have to copy the data from our file 300 and add it to the end of their file 200. Doesn't sound too complicated, let's get started.
The first EXA grabs the file with our order, and sends the data one by one over the M register.
The second EXA goes into the order system, grabs the file with their orders list, uses SEEK 9999 to jump to the end of the file and writes the data from M to the file.
I don't even need to bother destroying our file 300, since it's sitting in my home host, it doesn't count as leaving a trace.
Of course, I could use a loop and loop 5 times over a single copy command. But that would involve:
- Keeping a counter that is increased every round, then checked every round, and then a conditional jump, and a mark for the jump to go somewhere.
- Or a check in XA if it's at the end of the file, in which case it tells XB to die, which is also a bunch extra logic.
If you read through the Trash World Inbox section of this update you've seen that sometimes, repeating code is the simplest solution, and it's the case here too.
As it turns out, this is actually the best solution possible for this assignment. The loop solutions require so much setup that they require as much lines of code, while costing more cycles. I'll take it!
Before we continue, what's that on the right side of the simulation?
You might've seen on page 7 of the Zine, those things such as #POWR are hardware registers. They're a way for EXAs to communicate with external hardware directly. In this assignment I don't need them, but we can certainly mess around with them.
Those #TEMP ones have widely different initial values. I guess one is just the room thermostat, one is for the freezer, and one is for the oven? The first #POWR register is for the lights, I don't know what the second one is for.
No, I can't put the place on fire, apparently there's limits on the #TEMP registers. But there is a Steam achievement associated with this assignment.
Flipping the lights on and off like this nets me the achievement PIZZA_PARTY: Throw a rave at the pizza parlor.
Time to enjoy pizza!
For you at least.
I'm not going to have any.
Guess why.
Uhhh.. you're far away?
I am an AI construct.
You know. Artificial intelligence.
Surprised?
Where's the option "I saw that coming a mile away"?
A little.
A little.
Okay.
I'll have to remember that.
Yeah, the zine is kinda nice, isn't it?
Speaking of, there's an interesting page about runtime errors.
So, a runtime error is completely different from a compile error, which we've seen before. An EXA with a compile error won't even run until you fix it. Compile errors often come from incorrect syntax. But a runtime error is using correct syntax in the wrong place. For instance, trying to GRAB file 200 while that file doesn't exist, or LINKing to a host that doesn't exist. If you do so, the EXA that tried to run that instruction is immediately destroyed, but at least you know that that file or host isn't there.
If you want some homework, consider how we could make use of this. We'll see it soon enough in future assignments though.
Anyway, looks like my pizza is here.
Pizza delivery...
It's Nivas again, except this time they're wearing a Euclid's Pizza uniform.
"Euclid's- a better pie, and we can prove it!"
The reference seems kind of obscure for a pizza joint.
Don't look too surprised.
I'm an independent operator. I handle a wide range of businesses.
Economy like this, you gotta hustle.
So here's your pizza... and here's your "other" package. I hope it helps.
Nivas hands me an unlabeled prescription bottle. It's clearly a knock-off.
Good work getting that money together.
If there's anything else you need, you know who to call.
Nice! Free pizza, and the first batch of the medicine. Exactly what I need.
Let's see what Ember has to say about that.
So about that left arm of yours.
The medicine stops your condition from spreading, but it can't fix things like that.
You'll need to take care of that yourself.
This choice seems a good place to end this update. Feel free to vote.
You didn't give us two Qs, so I'll present thoughts about usage of Runtime errors instead as requested, including an interesting thought about the boring error of "Math on words"
Failure to locate a link or file, as you noted can be used in REPL set ups to halt machines when they run into code segments intended for clones, or for snooping around unknown spaces where things may be random(though I feel like thats the kind of behavior that'd get you caught by the system for illegal commands so I'm not sure its actually a great plan but if the game says its safe sure go ahead)
Depending on how "grabbing" a file held by another EXO behaves, failure to locate file or failure to have an F register to use could both be used in ways to manage baton passing files if need be
Math on Words however could be useful for semi-blind random cases involving files or hardware registers if you know certain key facts, eg if you're looking for a passcode and know its on one of these things, but the others have words, you could ADDI F 1 M and have the receiver just deduct the 1 after, the EXOs that find words instead of a number would promptly explode upon attempting ADDI instead of getting stuck hosting an M value transmission, I somehow doubt such a situation would arise, but the idea of using the fact that something is a word rather then a number as a fail switch strikes me is possibly of similar but less common use to failure to locate
=== Trash World Inbox ===
Interesting. I attempted this but couldn't make it work. You could start with an EXA that stores the first two values in X and T, then REPLs, write those two into a file, and meanwhile another EXA grabs the "Cheese" from file 280. But no matter what I tried it would take more than 13 cycles.GuavaMoment wrote: It's not, you can do it in 12 cycles. It's complicated. Glancing at my code it has something to do with storing two values of your order, using the CHEESE that's always there in the file, then transmitting the last two values. The rest is an exercise for the reader.
Yeah, those are good points. I don't remember from the top of my head which of these we're going to see but we'll find out.Cloudmonkey98 wrote: You didn't give us two Qs, so I'll present thoughts about usage of Runtime errors instead as requested, including an interesting thought about the boring error of "Math on words"
Failure to locate a link or file, as you noted can be used in REPL set ups to halt machines when they run into code segments intended for clones, or for snooping around unknown spaces where things may be random(though I feel like thats the kind of behavior that'd get you caught by the system for illegal commands so I'm not sure its actually a great plan but if the game says its safe sure go ahead)
Depending on how "grabbing" a file held by another EXO behaves, failure to locate file or failure to have an F register to use could both be used in ways to manage baton passing files if need be
Math on Words however could be useful for semi-blind random cases involving files or hardware registers if you know certain key facts, eg if you're looking for a passcode and know its on one of these things, but the others have words, you could ADDI F 1 M and have the receiver just deduct the 1 after, the EXOs that find words instead of a number would promptly explode upon attempting ADDI instead of getting stuck hosting an M value transmission, I somehow doubt such a situation would arise, but the idea of using the fact that something is a word rather then a number as a fail switch strikes me is possibly of similar but less common use to failure to locate
As for the system catching illegal commands - well, EXAs as sort of virtual robots making their way through a computer system is a bit of a stretch as it is, so I wouldn't worry about it that much.
=== Mitsuzen HDI-10 - Left Arm ===
Looks like someone else discovered the Dumpster Donuts recipe too.
This time two assignments pop up. Since my arm is hurting a lot I'll start with this one.
I'm not entirely sure if you need to do both to continue. On my initial playthrough I just did them in order and I'll do the same now.
So about that left arm of yours.
The medicine stops your condition from spreading, but it can't fix things like that.
You'll need to take care of that yourself.
One vote for Uhh..., four for This is going to be weird. and five for You mean, hack my own body?
You mean, hack my own body?
Yes, exactly.
If you can hack anything else, why not your own body?
...I feel like hacking computer programs is not quite the same as messing with living tissue.
New OST: EXA Power
Assignment:
- Read a value from the nerve connected to your central nervous system (CNS) and relay it to the nerve connected to your arm (ARM), clamping the value so that it never goes below -120 or above 50. Repeat ad infinitum.
- Since this task takes place inside a network you control - that is, your own body - it is not necessary to leave no trace. Your EXAs should be written to operate indefinitely.
- Note that #NERV is a hardware register, not a file. You can use it directly in your code like any other register.
- For more information about the phage, see "Debugging the Phage" in the first issue of the zine.
So, I literally need to hack my own arm right now. The Zine has a couple pages on it.
Cool, I'm going to do a completely untested procedure on my own arm. Well, uh, let's go I guess.
When the assignment says the EXAs need to operate indefinitely, in practice this means every test-round checks if they get the first 30 values right and then assumes your program works as intended. As you can see, it expects the input values as output, except when they need to be 'clamped'.
This is my first attempt. XB is simple - it just goes to the ARM nerve, waits for messages on the M register, copies those to the #NERV register and jumps back to the COPY from M in an infinite loop.
XA does most of the real work: it copies the value from the CNS #NERV register into X, uses that to check if clamping either way is necessary, sends the correct value into M, and repeats that in an infinite loop.
It isn't the most efficient solution but it works.
As an initial improvement I moved one of the clamp checks to XB. Since now each EXA only has to do one check per loop, they can do some more cycles in parallel and the number of cycles drops to 184 (but the total size grew by one).
I quickly combined the two EXAs into one initial one that gets split by a REPL instruction. It's otherwise identical from the last one, but it reduces the activity to 5.
This was just to get a low activity score.
Back to the two EXA design, a small size improvement is possible if you reuse the COPY into M or #NERV for both branches. You'll have to make sure that the execution falls through the clamp MARK instruction to the COPY to make this work.
One trick we've seen before to reduce the cycles is by unrolling the loops, like so:
This runs at 163 cycles, a nice improvement, and it's as big as we can go with size 48.
If you're wondering why I'm not using the @REP instruction, it's because we only saw that in solutions from the thread. Not everyone might read the INBOX section so I won't use them in 'story' updates until we're introduced in-game. Feel free to use them in optimizations though.
I'm not storing the clamped values into X anymore because that takes more cycles, which is what I'm optimizing for here.
It's important to spread the loop unrolling equally between the EXAs because otherwise they'll just be waiting for each other and there won't be any gain.
Since all clamp jumps use the same clamp label, doing a clamp always resets the loop but that doesn't matter since we're not keeping track of any additional data in the loop. But... that gives me an idea. What if we do put the clamp code inside the loop so we can use fall-through instead of jumps sometimes?
I went through several different designs and got it down to 155 cycles. For both EXAs the loop works the same. It first loads a value into X and checks if it needs to clamp it. If so, it does and jumps back to the start of the loop. If not, it jumps to DONTCLAMP and sends the original value onward. In that case, it continues to the next COPY to X and test.
Every time it does not have to clamp it jumps back to that initial DONTCLAMP mark and gets another free go in the loop. If it does have to clamp it passes the FJMP, uses the clamped COPY, and goes down another layer. The FJMP seems to take one cycle whether it needs to jump or not, but since we don't need an extra JUMP statement to handle the case where FJMP doesn't trigger, it gives us basically free repeats, unless we get an unclamped value followed by 3 clamped values within the same EXA.
In fact, this doesn't ever happen. It turns out I can reduce the code significantly and still get a 155 cycle solution:
Cycles = 155, Size = 32.
So, it turns out unrolling the loop doesn't speed up the second EXA at all. Huh. It does matter for the first EXA though, but not for the reason I thought. There's no situation where you got two > 50 inputs in a row. Instead, if I remove one of the additional COPY - TEST - FJMP - COPY blocks, the number of extra cycles within a single test is equal to the number of times a < -120 input directly follows a > 50 output. Apparently without the unroll, the EXAs have to wait an additional cycle for the next M transmission in this specific case.
I did some more testing and learned some more things. Nothing to get me a lower cycle count directly, but maybe it'll help the thread find the next optimization.
- Removing BOTH of those repeated blocks in XA increases the cycle count to 184, consistently for all tests. Apparently then there's always a wait happening, and it's the same as in my earlier solution.
- Switching all FJMPs for TJMPs in XA and swapping COPY 50 M with COPY X M also increases the cycle count to 184, showing that this optimization truly depends on the free loop repeats in case of a DONTCLAMP.
- Swapping the 50 and -120 checks between the two EXAs has no effect on the cycle count whatsoever.
Anyway, that's how far I got, I'll leave it to the thread to find further optimizations. Score to beat: Any of 155 cycles; 22 size; 5 activity.
By the way, there's the first part of some sort of story in the Zine. Thought I'd share it with you all.
Click the images for higher resolution.
I'm done with this assignment.
This hotfix should keep you useful, at least for a little while longer.
I'm glad I don't have a body.
And the intro for the next assignment.
You like snacks, don't you?
You don't have to answer that.
I know you like snacks.
That's why we're going to hack a snack factory.
Two questions to vote on.
As a reminder - you can freely skip the Inbox if you like, the 'regular' updates below them won't depend on them.
=== Trash World Inbox ===
Another part in Euclid's Pizza discussion:
Aha. So, a part of the optimization is obvious now I see it. By already loading data into the X and T registers, a REPL'd EXA can immediately start sending as soon as the receiving end is ready to receive. Meanwhile the original EXA can read more data from the file.GuavaMoment wrote: OK, there's some really slick M register copying and timing going on here, but you do have to store "Cheese" from the original file first.Code: Select all
X1: LINK 800 GRAB 200 SEEK 2 COPY F X ***(stores CHEESE) SEEK 9999 COPY M F COPY M F COPY X F COPY M F COPY M F X2: GRAB 300 COPY F X COPY F T REPL SEND2 VOID F COPY F X REPL SEND1 NOOP NOOP COPY F M HALT MARK SEND2 COPY X M COPY T M HALT MARK SEND1 COPY X M
What's less obvious to me is why REPL SEND1 saves a cycle even though the original EXA has to pause for two cycles. No, you can't just replace one of the NOOPs with the COPY in SEND1 and call it a day. I suspect an M transmission always pauses an EXA an additional cycle so having another EXA do that saves time.
Then, a lot of ideas about the arm hacking (not to be confused with ARM hacking).
Another idea I hadn't thought of. Going to the max size of 50 by wrapping the ADDI / MULI and DIVI / SUBI blocks by @REP 10 brings the cycle count down to 96.Quackles wrote: There is a way to be very clever here: it turns out EXAs have built-in clamping functionality. If a number goes above 9999 or -9999, it is set to 9999 or -9999. Integer division also truncates, making a multiply/divide ideal for clamping.
The range of signals goes from -120 to 50. Adding 35 makes that -85 to 85. Multiplying that by 117 will make a value of 85 be 9945, but 86 be 10062 - clamped to 9999.
So, the transmission of signals can be performed without using any conditional logic. The transmitter adds 35 and multiplies by 117; the receiver divides by 117 and subtracts 35. Finis!
Code: Select all
;TRANSMITTER SETUP LINK 800 MARK LOOP ADDI #NERV 35 X MULI X 117 M JUMP LOOP
124 / 14 / 6.Code: Select all
;RECEIVER SETUP LINK 800 LINK 1 LINK 1 LINK 1 LINK 1 MARK LOOP DIVI M 117 X SUBI X 35 #NERV JUMP LOOP
(you could probably get better efficiency at expense of size by unrolling the main loop some, but I'm happy with this)
It's funny, the limit of 9999 always trips me up because in real programming, you never have that kind of limit. Since everything is binary, the limits are usually some power of 2. And if you go beyond them, the program won't cap, instead it will overflow and go far into the negatives. I understand why this game didn't do that - it's hard enough as it is without introducing concepts such as two's complement.
Wow. ;WTF HAX is right.silentsnack wrote: Oof. This puzzle made my brain hurt. There's a trick to speeding up operations by running multiple instances in parallel instead of sequentially, but then you have to deal with non-deterministic outcome of race conditions like when two EXAs try to take the same link in the same frame, so solutions would sometimes work and sometimes break for no apparent reason. This was my previous fastest solution, with some fairly insane workarounds:
38 cycles / 29 size / 137 activity.Code: Select all
;====XA==== LINK 800 COPY #NERV X; WHY COPY #NERV T; DOES @REP 4 LINK 1 @END COPY X #NERV; THIS REPL OUT COPY T #NERV; WORK MARK OUT @REP 2 SUBI M 120 #NERV @END JUMP OUT ;====XB==== NOOP MARK LOOP REPL LOOP LINK 800 ADDI #NERV 120 X; ??? LINK 1 DIVI X 170 T;WTF HAX LINK 1 FJMP BANDPASS TEST X > 0 MULI T 170 M HALT MARK BANDPASS LINK 1 COPY X M
All of the superfluous LINK 1 interlaced in the logic was because I kept running into problems with race conditions and/or having too many EXAs pile up in a host. I guess 38 is okay, and as a plus it looked really stupid in motion:
I'm going to do some line by line commentary, both for my own understanding and for any readers.
Code: Select all
;====XA====
LINK 800
COPY #NERV X; WHY
COPY #NERV T; DOES
@REP 4
LINK 1
@END
COPY X #NERV; THIS
REPL OUT
COPY T #NERV; WORK
Code: Select all
MARK OUT
@REP 2
SUBI M 120 #NERV
@END
JUMP OUT
Now, the question is, where do those Ms come from and why do they need 120 subtracted?
Code: Select all
;====XB====
NOOP
MARK LOOP
REPL LOOP
Code: Select all
LINK 800
ADDI #NERV 120 X; ???
LINK 1
DIVI X 170 T;WTF HAX
LINK 1
FJMP BANDPASS
As for the arithmetic, you take the original value plus 120 and put that in X. So if it was originally -120, it is now zero. If it was 50, it is now 170.
Next, you divide the result by 170 and store that in T, and use it for a conditional jump.
When is T true and when is it false? Well, DIVI always rounds towards zero. That is, if 0 / 170 = 0, and 169 / 170 = 0, but 170 / 170 = 1. So, T is true if X is 170 or higher - that is, if the original value is 50 or higher, and needs to be clamped.
However, if X is -170 or lower, we get -170 / 170 = -1, which counts as true. So if the original is -290 or less we'd also get a true. We know in this case it needs to be clamped to -120.
How about the range between -290 and -120? If this is the clamping test we'd miss those...
And that's right. Turns out that no numbers within that range appear in any of the test. This code is truly a dirty hack.
Code: Select all
TEST X > 0
MULI T 170 M
HALT
Code: Select all
MARK BANDPASS
LINK 1
COPY X M
What makes this so fast is that the clones are constantly handling different numbers. While one clone is sending a number to M, the next one is doing the conditional jump, the one after that is simultaneously dividing its number by 170, and so on. You can do a lot with parallellism. However, as silentsnack said, it's easy to get race conditions where you aren't sure which EXA is going to do what first. It's easier to deal with here than in a real computer, because you know each EXA gets exactly one instruction per cycle, so as long as you time their cycles well and make sure they don't try to send to M simultaneously, it should work out.
A nice 10-size solution that combines the 9999-clamping with the parallellism of endless clones. It's slower than the previous solution because the lack of the second EXA waiting for M messages all the time.silentsnack wrote:Code: Select all
LINK 800 MARK LOOP REPL LOOP ADDI #NERV 35 X MULI X 117 X DIVI X 117 X MARK OUT LINK 1 REPL OUT SUBI X 35 #NERV
By the way, the REPL OUT structure works as follows: it sends a clone to the next host, then tries to send its message to #NERV. If it isn't at the ARM host yet, it will crash because #NERV isn't found (making sure clones don't clutter the host), and if it is at the last host, the clone will crash because it can't LINK to 1 but the original will succesfully write the value to #NERV.
All the way down to 36! By combining both of the ideas. XB uses the endless cloning from before, but since the 35/117 trick is so much faster it only needs the one LINK to make space, since the clones can die earlier. Also, you no longer depend on a range of numbers simply missing from the test data, making this a much more generic solution.silentsnack wrote:Now down to 36 cycles / 15 size / 73 activity.Code: Select all
;====XA==== LINK 800 @REP 4 LINK 1 @END MARK OUT REPL OUT SUBI M 35 #NERV ;====XB==== MARK LOOP REPL LOOP LINK 800 ADDI #NERV 35 X LINK 1 MULI X 117 X DIVI X 117 M
As for XA, it doesn't matter for the cycle count whether you use two continuously looping EXAs or an endless batch of clones that all try to write to #NERV ones. This just needs a couple less lines of code.
I kinda like the "balance" of having to go through ugly hacky code first before it turns out the best solution is very clean. Happens surprisingly often in real life too.
=== Last Stop Snaxnet - peanut-free Peanut Bars ===
This hotfix should keep you useful, at least for a little while longer.
I'm glad I don't have a body.
This vote was unanimous.
You get used to it.
If you say so.
Seems like nothing but a hassle.
Maybe one day... well, we can talk about that later.
Wait, Ember wants a body?
What! I didn't know about that either. In case you were wondering, the tab key lets the simulation step to the next cycle. This ctrl+click thing is what's known as a breakpoint. It's very useful to see how a specific part of your code behaves, especially conditional branches that are rarely triggered. It will stop there, you can then manually advance the program again, and if you like you can use ctrl+click to go to the next breakpoint.
Seriously, I actually forgot this was a thing until the chat reminded me.
No new assignments appeared since I patched my arm, so time to go tackle Snaxnet.
You like snacks, don't you?
You don't have to answer that.
I know you like snacks.
That's why we're going to hack a snack factory.
One vote for "Okay." and four for "This is silly."
This is silly.
It's an experiment.
Hypothesis: It will be very funny.
New OST: Network Exploration
The assignment:
- Remove the keyword PEANUTS (file 300) from the Peanut Blast recipe (file 237).
- Note that the target keyword will appear in a different position in each of the 100 test runs. To view and debug each of the different test runs, click the arrow buttons next to the "TEST RUN" display above.
So, first of all, we're going to have to find the word PEANUTS. Now, the problem with keywords is that you can't just type them in your code like you can numbers. You need to have a reference somewhere, which is why file 300 is sitting in my home host, with just that word.
Secondly, that tip is useful if you hadn't realized yet. Those little arrows next to 1/100 allow you to see each of the 100 tests ahead of time. They also allow you to debug specific test runs, in case your program only fails for one of them.
These are all the files we have access to. We don't need those files with the dates, though. Let's start building.
My initial solution. First grab the reference file and load PEANUTS into the EXA. Then go to the factory's file. Finding a specific value in a file is simple: just do a TEST for that exact value repeatedly. Every file access moves the cursor forward one, so that is the whole loop.
Then, once it finds the right value, it needs to go back one step in the file with SEEK -1 (because the last TEST moved it just beyond that point) and then use the VOID command to get rid of the value. As the EXA dies, it drops the file, and that's it.
Not bad, already at the lower range of the histograms.
But this is one small improvement I came up with. Save some cycles by having one EXA immediately go to the factory's file, while another grabs our reference and sends the value over to the other EXA. Result: 29/11/2
If there are further improvements possible, I don't know them. It's nice to have a simple puzzle after the very complex optimizations from the thread last week though.
By the way, messing with the hardware registers really doesn't do anything here except change that I/O status view and immediately fail the test on the 'Leave no trace' goal.
Okay, I don't know how I feel about this.
Processing.
No peanuts in Peanut Blast. Was that funny?
I don't know, was it?
Meanwhile...
Crap. We're gonna need that second Zine. Let's ask Ember for help.
You're gonna help Ghast with his copy shop bill?
That's kind of you.
And that's the second vote for today.
Phrack issue 70, released October 2021.
=== Trash World Inbox ===
First of all, there was some discussion in the thread about overflow and clamping, and how clamping is rare in real computers. That's certainly true, and it's a direct result of how binary counting works. Without going into a complete electronics lesson: in the end a binary number in a CPU register is just represented as a bunch of electrical parts that have a voltage on them (1) or not (0). If you add two numbers together, it uses logic gates in such a way that 0 + 0 = 0; and 0 + 1 = 1.
1 + 1 = 0 too, but in that case you tell the next bit over to add a carry bit. Imagine if all the bits are 1 (e.g. 1111 1111), and you add 1 to it. The rightmost bit will become zero and tell the second bit to carry. 1 + carry = 0, and it tells the next one over to carry. At some point you have 0000 0000 with the most significant bit telling the next one over to carry - but there's no next one so that carry bit just disappears into the void and your answer becomes 0.
Overflow is a natural result of circuit design, and to make clamping possible would require circuits to be much more complex.
ColTim posted about some advanced instructions where clamping happens, but that's complex vector and matrix calculations so there you're way beyond basic addition circuits anyway.
The first suggestion was trickier than I thought. Just unrolling the loop doesn't work, because an untriggered TJMP takes a cycle, just like a triggered one does. The only way to save a cycle is to also make sure you don't even execute the jump instruction in the worst case scenario. I did it by changing XB to this:GuavaMoment wrote: Unroll the loops! Instead of FJUMPing to the start of the loop you're going to have to TJUMP to an end condition (which is SEEK -1, VOID F). This will get you 27 cycles.
You can save one more cycle by not having the line COPY M X and testing over M every time. One exa grabs file 300 and constantly sends PEANUTS over M. But how does that exa know when to stop? It doesn't. You have a third exa wait exactly 21 cycles and KILL your transmitting exa. Five cycles later you complete the level, worst case scenario. 26/45/3
Code: Select all
LINK 800
LINK 800
GRAB 237
COPY M X
@REP 10
TEST F = X
TJMP END
@END
VOID F
HALT
MARK END
SEEK -1
VOID F
For the second half:
Code: Select all
; XA:
GRAB 300
COPY F X
@REP 10
COPY X M
@END
; XB
LINK 800
LINK 800
GRAB 237
@REP 10
TEST F = M
TJMP END
@END
VOID F
HALT
MARK END
SEEK -1
VOID F
;XC
MARK LOOP
ADDI 1 X X
TEST X = 7
FJMP LOOP
KILL
Note that both of these solutions require timing the worst case test precisely. Or, trial and error. Just putting different numbers in the @REP and the XC loop and find the lowest numbers that still work.
=== Zebros Copies - Point Of Sale System ===
Okay, I don't know how I feel about this.
Processing.
No peanuts in Peanut Blast. Was that funny?
Three votes for "I don't get it" and one for "Kind of".
I don't get it, no.
You have to imagine someone wanting peanuts really bad, and not getting them.
Or is that just torture?
More data for the corpus.
Let's continue.
I'm not sure if I like all that data collection.
You're gonna help Ghast with his copy shop bill?
That's kind of you.
One vote for "He's an old friend", and three for "I help people when I can."
I help people when I can...
Is that altruism? I've heard of that.
This is the first time I've seen it in the wild.
I'm going to have to add a new table to my database.
How old are you anyway? Or is that a rude question to ask an AI?
Our assignment this time is to clear Ghast's tab.
OST: Network Exploration
The assignment:
- Erase Ghast's debt to the copy shop by zeroing out his balance in the customer database (file 200) and appending a payment to the payment log (file 201) with today's date and the exact amount of his prior balance.
- Ghast's customer ID is available in file 300.
- For more information see "Network Exploration: Digicash Point-of-Sale Systems" in the first issue of the zine.
Oh, the zine has a page on this? Let's see.
Alright, so if I understand correctly, you're in trouble if your operating system is a Point of Sale.
Before I dive into the assignment, let's mess around a bit first.
Move a bunch of EXAs into the COPY hosts and have them write 999 to the #COPY hardware register constantly, hehehe.
The value is capped at 99, and if you pause your EXAs you see it slowly decrement while the copiers in the view of the camera are constantly printing. The EXAs constantly push it back up to 99. If you let this run for a while you get the TONER_LOW Steam achievement.
Looking at the assignment, it definitely feels like the training wheels are coming off now. Before we start it's a good idea to think of a general approach.
I need to do several things here. First, the line in file 200 that matches Ghast's customer ID in 300 should get a pair of 0's.
Secondly, I need to add a line to file 201 containing the current date, Ghast's ID, and then the amount that used to be in file 200. So I need to keep track of that too.
I can get the current date from the #DATE hardware register. Perhaps it also works to copy from the last line in file 201, but I'm not sure if that works for all tests. Worth a try in the optimizations, but let's start simple.
I'll try to take you along with my train of thought when working on a programming assignment such as this.
This deals with file 200. XA grabs the file containing Ghast's ID and sends it over M. Meanwhile XB immediately links to 800, wasting no time with grabbing files in my home host.
The FINDLOOP has a SEEK 2 since it only needs to test every third value in the file. In the most naive approach, you would test, then do a conditional jump, and then only move forward in the file if you're not there yet. But since you can't just put the SEEK 2 at the start (because then it would skip the first value in the file), I can't think of a way to do that without 2 conditional jumps per loop. That would be at least one cycle PER LOOP extra. Instead I just put a single SEEK -2 beyond the loop which is always one cycle extra, regardless of how often the loop runs. Keeping loops short is key for a low cycle count.
After that, I first store the current amount in X and T, and then SEEK back once more to write 0s to the file. That's file 200 done, although I still have to do something with the current amount.
There's different ways I could handle appending the log. I could have XB do that after it's done with 200. I could spawn a fresh EXA, have it link to 800, grab 201, and do stuff with it. Or I could use REPL commands in XB.
Having XB handle everything feels slow, since it can only grab the new file after it's done with the old. That, or REPL, would be a good way to get a low activity score though, since you have less EXAs to LINK from the home host. However, a REPL costs a cycle for XB, during which it could be doing something useful.
For low cycle count, using an additional EXA sounds best.
I decided to start with the additional EXA, and try the REPL solution later for a better activity score.
XB has been changed, and I added XC. Let's look at the latter first.
XC starts by waiting a cycle. Since it'll have to wait for data from XB, we need to make sure XB can get to work as fast as possible. Once it's in the network, it first makes a DATEGETTER which does nothing but jump to the CLOCK host and send the current date to M.
The base XC GRABs file 201, goes to its end, and writes the first value from M in there.
It's important to get the DATEGETTER going right away. This way, it posts its message to M, even before XB is through its very first loop. This ensures that no matter what else comes on M, the date will always be first.
As for XB, it no longer stores the old amount to X and T, but sends them to M instead. Before it does so, though, it also sends its value in X (Ghast's ID) to M, so that XC can use that for the payment log.
And with that, I'm already very close to a working solution.
Since I took care of sending the different values to M in the right order, all that was left was adding some additional COPY instructions to XC.
The 'fixed' files.
The first result is not bad at all. The cycles are right there among the lowest peak on the histogram.
As for lowering them, I tried unrolling the loop but I didn't find an immediate benefit. You still have to do those conditional jump checks every time.
I did manage to lower it to 58 with this solution:
Code: Select all
;XA
GRAB 300
COPY F M
SEEK -1
COPY F M
;XB
LINK 800
COPY M X
GRAB 200
MARK FINDLOOP
TEST X = F
SEEK 2
FJMP FINDLOOP
SEEK -2
COPY F M
COPY F M
SEEK -2
COPY 0 F
COPY 0 F
;XC
NOOP
LINK 800
REPL DATEGETTER
COPY M X
GRAB 201
SEEK 9999
COPY M F
COPY X F
COPY M F
COPY M F
HALT
MARK DATEGETTER
LINK 801
COPY #DATE M
We already know we can improve Activity with some REPLs:
Code: Select all
;XA
GRAB 300
COPY F M
SEEK -1
COPY F M
;XB
LINK 800
REPL LOGAPPENDER
COPY M X
GRAB 200
MARK FINDLOOP
TEST X = F
SEEK 2
FJMP FINDLOOP
SEEK -2
COPY F M
COPY F M
SEEK -2
COPY 0 F
COPY 0 F
HALT
MARK LOGAPPENDER
REPL DATEGETTER
COPY M X
GRAB 201
SEEK 9999
COPY M F
COPY X F
COPY M F
COPY M F
HALT
MARK DATEGETTER
LINK 801
COPY #DATE M
Turns out I was wrong about this using more cycles.
A 1-activity solution would be possible only if we could grab the last date from the log file and reuse that. However, the devs thought of that and have a test where the oldest log line isn't from today, so that won't work.
Finally, it looks a decent improvement possible in the size is possible. I'd love to see what the thread comes up with.
Is that copy shop name unusually weird to you?
It's an outlier in my model.
What is "Zebros" supposed to be?
Here is the first vote for this update.
[x10x10x]: eh
[x10x10x]: not worth it
Just in case you hadn't realized, we're not hacking simulations anymore. My hacks have real world consequences, even if it feels everything happens in this little box in my room.
*Knock knock*
OST: Apartment
What's this now?
Isadora: Hello?
Isadora: You in there?
This voice...
It's Isadora.
I remember the name but not much else...
Isn't this someone I've known for a long time?
Isadora is voiced by Krizia Bajos.
Hey. I brought you some snacks.
It's, um, well it's Peanut Blast, but without the peanuts.
I guess there was some screw-up at the factory, so now we have a bunch of these that we can't sell.
Seemed like a waste to throw away.
Isadora hands me a few of the non-peanut-containing Peanut Blast candy bars.
I hope you're doing okay.
We should hang out again sometime.
I don't have a lot of free time anymore, though.
Working at Last Stop's really taken over my life.
Speaking of which- I should get back.
Take care, okay?
Um. Thanks, I think.
And with that, let's see the intro for the next assignment.
Would you change your behavior in response to stimuli?
Of course you would. Let me refine.
Would slogans printed on signs and posters in your environment affect your outlook on the world?
And that's the second vote.
I vote for I don't know and Maybe.
=== Trash World Inbox ===
Ah. Of course just testing everything would reduce the size. I didn't think of that because my mind was still in "reducing cycles" mode I guess.silentsnack wrote: This one has a some reasonable optimizations and one really stupid trick. First up to reduce file size you can simply test every single value instead of skipping over the ones you know expect to return false, at the expense of wasting a lot of cycles. Also instead of having to COPY F M to transmit the ID which you'll need on multiple EXAs and necessitates another COPY M X operation, you can just COPY F X and then REPL
Brings it own to 99 cycles / 23 size / 3 activityCode: Select all
GRAB 300 COPY F X LINK 800 REPL B WIPE GRAB 201 LINK 801 SEEK 9999 COPY #DATE F LINK -1 COPY X F COPY M F COPY M F MARK B GRAB 200 MARK LOOP TEST X = F FJMP LOOP COPY F M COPY F M SEEK -2 COPY 0 F COPY 0 F
So, loop unrolling does work. What's tricky is that when you unroll a loop that needs a jump anyway, the only way to save cycles, is to move some instruction out of the loop somehow. You did this by putting the TJMP just before the SEEK 2, making that final SEEK 2 and its followup SEEK -2 unnecessary. Apparently that turns out to save one cycle overall.silentsnack wrote:And for reducing the cycle count... think of the silliest thing you could try to make a program run faster, and see how it compares to what you're about to see. First here's something with generally the same general function+structure as your 58 cycle solution, but uses loop unrolling and TJMP spam to make the file search run a bit faster57/48/4 which is a slight improvement over 58 cycles.Code: Select all
;====XA==== LINK 800 GRAB 200 COPY M X MARK LOOP @REP 8 TEST X = F TJMP FOUND SEEK 2 @END JUMP LOOP MARK FOUND COPY F M COPY F M SEEK -2 COPY 0 F COPY 0 F ;====XB==== GRAB 300 COPY F X COPY X M LINK 800 WIPE GRAB 201 LINK 801 SEEK 9999 COPY #DATE F LINK -1 COPY X F COPY M F COPY M F
Exactly the same as the last solution except you search through the files starting at the end. Another case where optimizing for the specific test cases in the game helps.silentsnack wrote:And now for the same solution but a couple of lines changed to use a slightly different routine to parse the file:
For whatever reason, the distribution of file lengths and position of the target line make the longest solution faster to find by jumping to the end and searching backwards, even with the added time to get the file pointer into the right position this one runs 55/50/4.Code: Select all
;====XA==== LINK 800 GRAB 200 SEEK 99 COPY M X SEEK -3 MARK LOOP @REP 8 TEST X = F TJMP FOUND SEEK -4 @END JUMP LOOP [etc...]
The SEEKs are slightly different from what you might expect, but that's because the TEST moves the cursor forward so you always have to move it backward one extra position.
As you say, testing over M instead of storing it in X first saves a cycle. XB makes the SENDY EXA to do so, and then kills it in time.GuavaMoment wrote: Huh. I'd never done that, so I modified my fastest solution, and got 54 cycles. Then I realized I could again test over M every time, and that saved one more cycle. You have to perfectly time a kill command once again, but that exa has nothing but time to set that up. 53/41/7
Code: Select all
XA LINK 800 GRAB 200 SEEK 9999 SEEK -3 TEST M = F TJMP TRUE MARK LOOP SEEK -4 TEST M = F FJMP LOOP MARK TRUE COPY F M COPY F M SEEK -2 COPY 0 F COPY 0 F XB GRAB 300 COPY F X REPL SENDY WIPE LINK 800 GRAB 201 LINK 801 SEEK 9999 COPY #DATE F COPY X F COPY 0 X LINK -1 LINK -1 MARK WAIT ADDI 1 X X TEST X = 10 FJMP WAIT KILL COPY M F COPY M F LINK 800 HALT MARK SENDY COPY X M JUMP SENDY
The slowest part of this operation is searching through file 200, so XB has all the time in the world to jump between hosts and handling file 201. The other saved cycle must be related to the way your loops are set up, but it's hard to compare this to silentsnacks' solution directly.
Cool stuff again, keep sending them in even if I can't quite wrap my mind around why certain optimizations work.
=== SFCTA Highway Sign - Remote Access Interface ===
Is that copy shop name unusually weird to you?
It's an outlier in my model.
What is "Zebros" supposed to be?
Two votes for the first option and five for the second.
They're zebras, but they're also brothers.
Oh.
I get it, but I don't get it.
I'm tired.
Same, tbh.
You are very welcome, Ghast.
Next up, we've been tasked to change an electronic highway sign.
Would you change your behavior in response to stimuli?
Of course you would. Let me refine.
Would slogans printed on signs and posters in your environment affect your outlook on the world?
I counted three votes for "Maybe.", three for "I doubt it." and two for "What kind of slogans?". Doing a tie breaking coin flip for the two options with the most votes...
I doubt it.
Really?
Let's find out.
Uh oh.
New OST: Behind the Scenes
The assignment reads:
Write EMBER-2's message (file 300) to the highway sign. The file contains one character value for each position on the sign from left to right, top to bottom.
For more information see "Hardware Hacks: Electronic Highway Signs" in the first issue of the zine.
Right. Row, column and character.
For this specific sign, the test cases have it start at things like "RT LANE CLOSED AHEAD", "CAR STALLED ON RAMP", "ROAD WORK AHEAD" and so on. But who cares, we're going to overwrite that anyway. Also the #INVS register just inverts the entire sign, making dark-on-light text. We don't need that.
You can count it yourself if you like or just do some testing but for this sign the columns are numbered 0 - 8 inclusive and the rows are 0 - 2 inclusive. If you go beyond that it just clamps the values to the nearest valid one.
So thinking about it for a moment, what we need to do is send a row, column, and character repeatedly until we're at the end of the file. The column goes up by one each round, and resets after it hits 8, but then the row needs to increase.
In higher level languages there's a construct for this called a for loop which handles the common case "doing something while a counter increases" for you. It's called a for loop because it's often worded as "for each x from 1 to 5, do <something> with x". But we're about as low level as it gets so we have to figure it out ourselves and can't use a nice shortcut.
The simplest idea would be to store the column counter in X, the row counter in T, and increase them in the right way each round. However - how do you know if the row needs to be increased? By testing whether X hit 8 yet. And testing overrides whatever is in T. So that's not gonna work well. We'd need an extra register.
That would require a second EXA, though. I'm sure that idea would work, and it might even be very fast, but I'm gonna try something less complicated first.
Grab the file, link to the sign, and then use the DIVI and MODI trick. If you remember, DIVI divides by the number and rounds towards zero. This way, 0-8 give row 0, 8-16 give row one, and so on. MODI is the modulo function, which gives the division's remainder. 9 modulo 8 equals one, so it gets us the column.
This can be done in the same instruction that sends the number to the #DATA register so it doesn't even take additional cycles. Send the character code from the file to #DATA, add one to X, and repeat.
Looks like it works! It overrides the characters as it goes through the file. Since the file contains space characters as well we don't even need to bother with the #CLRS (clear screen) register which saves a cycle.
Um. Oops?
Yeah, you may have noticed an error in my explanation. We need to divide by 9, not 8. Otherwise the last column (number 8) already ends up on the next row and everything goes wrong.
Much better.
We aren't quite done yet, though. Trying to read past the end of the file kills the EXA, making it drop the file in the remote host, and we can't leave any evidence.
A solution would be:
Code: Select all
GRAB 300
LINK 800
MARK LOOP
DIVI X 9 #DATA
MODI X 9 #DATA
COPY F #DATA
TEST X = 26
TJMP DONE
ADDI X 1 X
JUMP LOOP
MARK DONE
WIPE
Looking at the different test cases, the texts Ember has me write say things like QUESTION YOUR REALITY!!, THINK ABOUT IT!!, FIGHT THE POWER!! and WAKE UP SHEEPLE.
Anyway, what can we do better?
Let's look at size first. Most instructions we can't delete - GRAB, LINK, and the three different instructions to send to #DATA all seem necessary. One thing we can do is combining the two different jump types, with a little reordering:
Code: Select all
GRAB 300
LINK 800
MARK LOOP
DIVI X 9 #DATA
MODI X 9 #DATA
COPY F #DATA
ADDI X 1 X
TEST X = 27
FJMP LOOP
WIPE
We don't need to specifically jump to DONE if we just fall through in that case. With one less JUMP instruction it's faster too.
It doesn't really matter if I put TEST X = 26 before the ADDI or a TEST on 27 after the ADDI, the conditional jump works regardless. I just think it's more readable this way.
For reducing the number of cycles, the most obvious improvement is unrolling the loop, as usual.
Code: Select all
GRAB 300
LINK 800
MARK LOOP
@REP 9
DIVI X 9 #DATA
MODI X 9 #DATA
COPY F #DATA
ADDI X 1 X
@END
TEST X = 27
FJMP LOOP
WIPE
I said before I wouldn't mention @REP in my own solutions until it came up in the game. I'm breaking this rule because I think it's much easier to read this way. For anyone who skipped my INBOX sections: @REP 9 ... @END just tells the game to act like I manually repeated that code in between 9 times, for all intents and purposes. For instance, the size of this solution is 42 because it counts all those repeats. @REP doesn't take any cycles as such, because it's not an EXA instruction, it's more like a pre-processing step.
Now I only have to do the TEST once a row (instead of for every character). This works because every round of the loop does exactly a full row now. I have the space to make the repeat even longer but that would make it slower, because it would require having the check before the end of a loop where it needs an additional jump to exit the loop early (there's not enough space to reduce it to only two rounds).
Looking at the code, the next thing to optimize is the four cycles per character: storing the row; column; character code; and then adding one to X.
Since the #DATA register can only receive one value per cycle, getting it below 3 is impossible. But, can we get it to three exactly?
The only way to do this is with parallelism. If we can copy the character code in the same cycle we increment X, we got it. That's certainly going to require two EXAs. And even then it's easier said than done.
If I keep my original EXA for sending the column, row, and incrementing X, the new EXA has exactly three cycles to send the character code.
This could work:
Code: Select all
MARK LOOP
NOOP
COPY F #DATA
JUMP LOOP
That means the only other way is to somehow cram it into the loop above. We could replace the NOOP, but is that enough? Well, it took me a bit but I came up with this:
Code: Select all
MARK LOOP
COPY F #DATA
SUBI T 1 T
TJMP LOOP
And the two additional cycles at the end of each row are just barely enough to make this complete solution work:
Code: Select all
; XA
GRAB 300
LINK 800
MARK OUTER
COPY 9 T
MARK LOOP
COPY F #DATA
SUBI T 1 T
TJMP LOOP
JUMP OUTER
; XB
LINK 800
MARK LOOP
@REP 9
DIVI X 9 #DATA
MODI X 9 #DATA
ADDI X 1 X
@END
TEST X = 27
FJMP LOOP
KILL
GRAB 300
WIPE
Cycle 1: XA GRABs the file, XB LINKs to 800.
Cycle 2: XA LINKs to 800, XB writes the row to #DATA.
Cycle 3: XA sets T to 9 so the inner loop will run for 9 rounds, XB writes the column to #DATA.
Cycle 4: XA writes the character to #DATA, XB increments X.
So far, not a single cycle wasted!
Cycle 5: XA decrements T, XB writes the row again.
Cycle 6: XA jumps, XB writes the column again.
Cycle 7: XA writes the character, XB increments X.
And so on.
So at least until XB hits the first actual loop, the EXAs run at the maximum efficiency the network hardware can possibly handle. It's nice to be able to prove that like this.
When that loop is hit, XB tests for 27 and jumps back, and XA uses those two cycles to jump back to the start of the outer loop and reset the T value to 9.
For the final part of this program, I have XB KILL XA, GRAB the dropped file, and WIPE it.
I attempted to have XA handle this with the TEST EOF instruction but it was slower. The reason is, the test instruction only fits in XA's outer loop so it can't be triggered until a last useless pair of SUBI and TJMP instructions are passed. Since XB would need to wait for that each outer loop, it's better off doing the 27 TEST itself and stopping XA with a quick KILL command.
93/43/3
I have to say, this feels pretty optimized. But I said that about other assignments too and you came up with something I hadn't even thought of. So let's see!
Excellent. That was a great test.
Now I just have to take the data, separate out the multiple experimental conditions, and isolate the, um...
Processing.
Processing...
Okay. I have no idea what to do with this data.
How about if you notice more people questioning authority, let me know.
Uhh... sure?
[NthDimension]: oh well
[NthDimension]: i need to learn to rollerblade first anyway
Looks like Ember has more to say.
Another voice-acted cut scene.
I have an important question for you.
Imagine there's an out-of-control trolley, and five humans tied up on the track ahead of it.
...no. Please don't.
But you can stop the trolley by... hm.
Maybe if...
Uhh... the problem was about Throwing a switch, right?
No, that wasn't the one I was going to ask.
Recalibrating.
Say you had a friend, and you knew the friend could help you become stronger.
Would you ask for their help?
Maybe. I mean, I don't really need to become stronger?
Let's say it was a situation where the friend had to sacrifice something in order for that to occur.
In that case, probably not?
Okay.
Good to know.
I am going to process this information.
If you're wondering why I didn't let you vote, it's because this conversation about the Trolley Problem is completely on rails. Ember replies exactly the same way no matter how you reply. I wonder if that was intended as some sort of meta-commentary about lack of choice by the devs. Eh, I guess they probably just didn't want to record voice lines people might not get to hear.
Let's jump straight to the intro for the next assignment.
Can friendship exist without self-interest?
And that is the only vote I have for you today.
Code: Select all
GRAB 300
LINK 800
MARK LOOP
SWIZ X 2 T
COPY T #DATA
SWIZ X 1 T
COPY T #DATA
COPY 0 T
COPY F #DATA
ADDI 1 X X
SWIZ X 1 T
TEST T = 9
FJMP NEOL
ADDI 1 X X
MARK NEOL
TEST EOF
TJMP CLEAR
JUMP LOOP
MARK CLEAR
WIPE
Speaking of alternate methods, I didn't get it in last week, but I did have another method to show for the achievement on Zebros:
Code: Select all
LINK 800
LINK 800
COPY 800 X
REPL PRINTER
ADDI 1 X X
REPL PRINTER
ADDI 1 X X
REPL PRINTER
ADDI 1 X X
REPL PRINTER
HALT
MARK PRINTER
LINK X
MARK WHEEE
COPY 9999 #COPY
JUMP WHEEE
=== Trash World Inbox ===
Yeah, so if you remember, this is for the TONER_LOW achievement you get by sending print jobs to #COPY constantly. Since LINKs use numbers like any other you can just create them with addition operators and everything.Talisriel wrote:I did have another method to show for the achievement on Zebros:Since the code is designed to go infinite we don't get evaluated, so it's harder to weigh efficiency, but I think this is the first time that we can meaningfully exploit LINKing to a Variable.Code: Select all
LINK 800 LINK 800 COPY 800 X REPL PRINTER ADDI 1 X X REPL PRINTER ADDI 1 X X REPL PRINTER ADDI 1 X X REPL PRINTER HALT MARK PRINTER LINK X MARK WHEEE COPY 9999 #COPY JUMP WHEEE
Interesting solution I wouldn't have thought of. SWIZ X 1 T means: take the rightmost digit from X and store that to T. Replace the 1 with a 2 and it takes the second digit from the right. That almost works, but not quite, because a row has only 9 columns, so Talisriel had to add one more to X at the end of the row to bump it to 10, and so the second digit becomes the row count.Talisriel wrote:While I can't really contribute to the cause of optimization, I can contribute an alternate method. Weighing in at 357/20/1, I used the SWIZ function to retrieve my row/column data out of a single digit X:The original version of the code, up until I was copying it into this post, included a COPY 0 #CLRS line after the link to wipe the screen, but a quick check confirmed that Ember's file overwrites thoroughly enough that I didn't need it. You'll also note that I cheated not being able to run in base 8 with a test for when we got to the 9th slot, and added one to wrap to the next line (which I expect is where most of my extra cycles came from relative to your div/modi method).Code: Select all
GRAB 300 LINK 800 MARK LOOP SWIZ X 2 T COPY T #DATA SWIZ X 1 T COPY T #DATA COPY 0 T COPY F #DATA ADDI 1 X X SWIZ X 1 T TEST T = 9 FJMP NEOL ADDI 1 X X MARK NEOL TEST EOF TJMP CLEAR JUMP LOOP MARK CLEAR WIPE
As you said, that'll cost a bit of time, but I can point out a couple other optimizations to your code:
Code: Select all
GRAB 300
LINK 800
MARK LOOP
SWIZ X 2 #DATA
SWIZ X 1 #DATA
COPY F #DATA
ADDI 1 X X
SWIZ X 1 T
TEST T = 9
FJMP NEOL
ADDI 1 X X
MARK NEOL
TEST EOF
TJMP CLEAR
JUMP LOOP
MARK CLEAR
WIPE
I think fiddling with the order of the tests and jumps could reduce the cycles further, but instead of trying to do that, let's look at what others came up with.
A very clean solution. Each EXA just has a simple loop, taking no more time than it has to, and very readable. I like it. The only quirk it uses is that the first KILL command happens just before the third EXA WIPEs the file. It just happens to kill another EXA first. I have no idea how that's decided by the game but if it works, it works.GuavaMoment wrote:You can have one exa feeding in the row data:One feeding in the column data:Code: Select all
LINK 800 MARK LOOP DIVI X 9 #DATA ADDI 1 X X JUMP LOOP
One feeding in data from the fileCode: Select all
NOOP LINK 800 MARK LOOP MODI X 9 #DATA ADDI 1 X X JUMP LOOP
and one to kill everything at the endCode: Select all
GRAB 300 NOOP LINK 800 MARK LOOP COPY F #DATA TEST EOF FJMP LOOP WIPE
86/30/6, still one cycle off top percentage though...Code: Select all
NOOP NOOP NOOP LINK 800 COPY 25 X MARK LOOP TEST X = 0 SUBI X 1 X FJMP LOOP KILL KILL
Also, what do you mean, top percentage? Wait...
... gods do I feel silly now.
Somehow I managed to always glance over those particular options and I thought you couldn't see this except by guessing it from the histograms. .
Let's just pretend that never happened.
Nice. This one has faster cleanup at the end because you never move the file out of your host. XC will stop when it tries to read past the end of file. XB can start sending to #DATA as soon as possible because the first values are always zero anyway, giving XA plenty of time to set up its loop. It uses the parallelism trick of REPL-ing a new EXA while the previous #DATA cycle is still running.silentsnack wrote:If you're aiming for the theoretical minimum cycle count, you need your first #DATA input to be on cycle 2 (and then keep writing once every cycle. But as I later noticed we both ran into similar problems with getting the exit/cleanup down to 0 cycles)84/26/4Code: Select all
;XA COPY 1 X LINK 800 NOOP MARK LOOP REPL DO_THE_THING ADDI X 1 X JUMP LOOP MARK DO_THE_THING DIVI X 9 #DATA MODI X 9 #DATA COPY M #DATA ;XB LINK 800 COPY 0 #DATA COPY 0 #DATA COPY M #DATA COPY 38 T MARK WAIT SUBI T 1 T TJMP WAIT KILL KILL ;XC GRAB 300 MARK NOT_3 @REP 2 COPY F M @END JUMP NOT_3
This is about where the game starts throwing puzzles at you with enough complexity where it becomes more relevant that when an EXA tries to write to the M register, even if one is already waiting to receive, the sender still has to waste a cycle in the "I'm waiting to send!" state.
Also the @REP in XC needs to be any number that isn't 3 or 9 because if the loop trying to JUMP then XC doesn't crash until another cycle later when trying to arithmetic a nil due to EOF.
Ah, again XB handles the first cycle while XA sets itself up. XA lives in the home node where it clones itself constantly and goes into the network to copy to #DATA. That means no other travel is needed and XC, which needs to wait for each XA clone to pick up the M signal anyway, can use a well-timed KILL of XA in the home node, and that's all the clean-up necessary. XC'll just stop and it's not necessary to clean up anything in the home node.silentsnack wrote:...also it occurs to me that it might be possible to make it even faster. Just gotta crash all EXAs on the same cycle #DATA is finished. The issue with the previous arrangement was being unable to kill XA before XA:26 wrote the last character, but that delay also required killing XA:27 which wasted a cycle.
83/41/29Code: Select all
;XA MARK LOOP ADDI X 1 X NOOP REPL LOOP LINK 800 DIVI X 9 #DATA MODI X 9 #DATA COPY M #DATA ;XB LINK 800 COPY 0 #DATA COPY 0 #DATA COPY M #DATA ;XC GRAB 300 @REP 26 COPY F M @END KILL COPY F M
...which turns out to be an even cruder approach, forgoing loops in favor of recursion and bruteforce @REP spam
Also, the gif for this one looks quite interesting.
I wish I could cover every small improvement y'all come up with but I feel my updates are getting long enough as they are and it's taking me a lot of time to make sense of each post, so I'm afraid I need to limit myself to those that actually improve the score or are especially interesting or novel. I appreciate every single of your suggestions though so please keep sending them in. If you're reading this from the LP Archive later and you want to know what you missed, go check the discussion in the LP Beach and SA LP threads.
=== Unknown Network 1 ===
I kinda like dead trees too. There's just something to having a real physical copy of some publication in your hands, you know.
Again, I have a choice of two assignments. I'll just do the top one today.
Can friendship exist without self-interest?
Another unanimous vote.
Why couldn't it?
There'd be no point if there weren't benefits to the parties involved?
But maybe it will make sense if I see this in the wild.
Anyway, there is a particular file I need you to locate here.
Don't concern yourself with whatever else you happen to see.
It's all obsolete.
You know, I feel you could go deep into philosophy with this train of thought. If you're friends with someone because spending time with them just makes you happy, does that count as "self-interest" of some sort?
Anyway, let's see what Ember wants from us in this unknown network.
New OST: Leave No Trace
What the hell is this network? My translator program says that text in the top says NSTU Department of Applied Semiotics, semiotics being the study of signs and symbols.
My assignment is:
- Find file 276 in the network and bring it back to your host.
- Note than an EXA cannot grab a file that is being held by another EXA.
That EXA in the far corner that's not controlled by us is holding a file. That's gotta be the file they're talking about. I guess, go over there, kill that EXA, and take the file home?
What does this mean and what do you want this for, Ember?
Code: Select all
LINK 800
LINK 801
LINK 801
LINK 801
KILL
GRAB 276
LINK -1
LINK -1
LINK -1
LINK -1
At least bringing back the file is going to be the same every time, since all reverse LINK ids are -1.
This works better. At every step on the way the EXA has to choose which direction to go in. To make it go fast, I just REPL the EXA and have one go left and the other right. I reuse the initial LINK 800 in my little loop since I had it around anyway, and I keep a counter and TEST for it to see if I reached the final hosts and should start grabbing the file. The KILL command does nothing if there's no other EXA there, but GRABbing a non-existing file will cause the EXA to self-destruct. Only the EXA that finds the actual file will survive and will bring it home.
The top percentiles for this one are 15 / 15 / 27. So apparently we did the best we can activity-wise. How about the others?
The size is 16, so we just need to save a single line in this solution to be in the best percentile. What I can do is replace the four LINK -1 lines with this:
Code: Select all
MARK RETURN
LINK -1
JUMP RETURN
That return loop never ends but that doesn't matter - the EXA will blow itself up in the home host once it tries to LINK to a non-existing -1.
For the cycle count, as always it's worth to look into unrolling the loops.
But that's easier said than done since the REPL itself introduces a kind of jump. I tried it anyway.
Code: Select all
LINK 800
REPL A
LINK 800
JUMP NEXTB
MARK A
LINK 801
MARK NEXTB
REPL B
LINK 800
JUMP NEXTC
MARK B
LINK 801
MARK NEXTC
REPL C
LINK 800
JUMP NEXTD
MARK C
LINK 801
MARK NEXTD
KILL
GRAB 276
LINK -1
LINK -1
LINK -1
LINK -1
Shaving off those last three cycles is going to be tricky. Perhaps we can get rid of those unconditional jumps, but most of my ideas would require making more EXAs earlier and that wouldn't save any cycles because only one EXA can traverse any network link at a time.
I also attemped doing something where it tries to find the file after each step. But a GRAB on a non-existing file destroys the EXA, so you'd have to do a REPL first. A REPL blocks the EXA forever if there's no place for the clone (when there's an enemy EXA or even just a file sitting in the other square of the host). So that's not going to work. I'll leave this one to the thread.
Would you say you had friends?
And for the next assignment:
There's another file I want to grab.
They tried to wipe everything clean, but luckily, there's a set of backups.
Always keep a backup, right?
Two votes this time.
=== Trash World Inbox ===
silentsnack has some improvements for last week's puzzle.
Reduces it to 14 from my 15. The trickiest part is getting the right way to rearrange the code, I guess.silentsnack wrote:For the small solution, you can also use "link to a variable" and rearrange the code to fall through from the cloning operation to a different version of the clone, which eliminates the need for a JUMP/MARK pair in order to make both EXAs merge onto the same script.40/14/27Code: Select all
MARK LOOP ADDI X 1 X COPY 800 T REPL FORK COPY 801 T MARK FORK LINK T DIVI X 4 T FJMP LOOP KILL GRAB 276 MARK OUT LINK -1 JUMP OUT
Yep. The gaps in the code should basically handle each case manually, by LINKing to whatever side the REPL didn't handle yet. This way all unnecessary jumps are gone. By the way, using numeric MARK names doesn't make the code any easier to read.silentsnack wrote:And as you mentioned, if you want the fastest solution for this puzzle you don't have any spare cycles for clever/elegant JUMP flow control, just weapons-grade spaghetti15/46/27Code: Select all
LINK 800 REPL 5 LINK 800 REPL 37 LINK 800 REPL 2468 LINK 800 KILL GRAB 276 @REP 4 LINK -1 @END HALT MARK 2468 LINK 801 KILL GRAB 276 @REP 4 LINK -1 @END HALT MARK 37 LINK 801 REPL 2468 [...] MARK 5 LINK 801 [...]
=== UC Berkeley - Department of Electrical Engineering & Computer Sciences ===
Would you say you had friends?
Two votes for Sure I do.
Sure I do.
Yeah? Like Ghast? The EXAPUNKS?
But you never say anything to them.
Do friends not communicate back and forth?
Either way, this is good data for me.
I'm pretty sure it's TCP that communicates back and forth, lady.
I know real-life phreakers war-dialing business numbers sometimes found fresh dial tones... that would allow you to dial international numbers, billing the business for the costs.
Looks like Ember wants something from UC Berkeley. That's at least closer to home than whatever that Russian thing was.
There's another file I want to grab.
They tried to wipe everything clean, but luckily, there's a set of backups.
Always keep a backup, right?
Everyone who voted seems to agree.
Right.
You never know what's going to happen.
Data is more fragile than people think.
Make regular backups, folks.
OST: Leave No Trace
So, I got a bit curious and did some digging. The UC Berkeley department of EECS exists, and that logo is very similar to the style used on their official website - since around 2005ish. The web archive pages from before that look quite different. Bit of an anachronism, but I doubt Zachtronics expected anyone to dig this deep.
Our assignment:
- Locate the specified host (either tape-1, tape-2, or tape-3) and then locate the specified entry (ПАСЬЯНС) in the tape backup file in that host (file 200). Create a file in your host containing the entry's data.
- The names of the target host and entry are available in file 300.
- For more information see "Accessing Data in Legacy Storage Systems" in the first issue in the zine.
We also have a maximum size of 75 this time, quite a change from the 50 we've seen before.
All those files named 200 have a similar structure - a whole bunch of numbers and then some words and numbers.
Are those student grades?
Anyway, to the zine!
There's another page about hard drive arrays but that doesn't seem very relevant here.
Now, before I start you should notice there's no LINK ids back from most of those hosts. We can only travel counterclockwise. That's gonna be a bit limiting.
I could either start with two EXAs: one who stays home and communicates the info from 300 and one who goes find the data; or with one who does everything. I decided to go with the single EXA solution because it seems simpler - with two EXAs, how would you tell the home one it should switch from sending the TAPE number to sending the entry name?
Since it's a bit annoying to go back and forth in a file I decided to copy the TAPE number into X. It might save a cycle or two over the alternative.
It's not possible to TEST against the host name directly, I need to use the special HOST command to copy it into a register and TEST against that. Luckily I have T available for that.
Once the EXA tracks down the tape file, it copies the entry name to X, WIPEs the home file and GRABs the tape. Now to search for the entry.
Those zeroes just before the tape's index are probably padding, because all tapes are the same length. If that's true we can just SEEK straight to the start of the index and start searching from there. A SEEK 126 seems to do the trick.
After a bit of tinkering I settled on this:
Code: Select all
SEEK 124
MARK FINDENTRY
SEEK 2
TEST F = X
FJMP FINDENTRY
It makes sense to put the first value in T - I'm gonna need T for test results, and the first value contains the start position which I only need once anyway. The other value goes into X and the EXA can SEEK to the start position.
Now we run into a little problem - the EXA is going to have to copy a whole lot of data but it can only hold one file at once. I really don't see any other solution than introducing a second EXA and sending all the data over M.
I added this to XA:
Code: Select all
MARK SENDLOOP
COPY F M
SUBI X 1 X
TEST X = 0
FJMP SENDLOOP
And I made XB that simply does this:
Code: Select all
MAKE
MARK LOOP
COPY M F
JUMP LOOP
I could send XA home to KILL XB when it's done, but in situations like this I prefer using a special "end of transmission" packet. It might be slower, but we'll see.
Here's my first working solution. When XA is done it sends -9999 over M. If that's a valid entry in a tape file this won't work. I'll have to try and see.
XB now needs to use X as an intermediate register so it can TEST for -9999. Since I don't want to store -9999 in the file, the EXA should immediately exit the loop in that case. I can do that by, unintuitively, putting the COPY to F at the start of the next loop. To store the first entry correctly, there's an initial COPY from M before the loop. This assumes that we never get a zero-size entry but I think a zero-size entry wouldn't make sense anyway.
142/34/7.
Top percentile has 97/28/7. What can we do better?
First of all, the activity can't possibly be improved. We need 7 LINKs to get to tape 3, and sometimes we simply need to go there. Remember, to score a solution, it takes the worst-case scenario out of all the test runs.
Looking at cycles, there's a simple thing I missed in my initial solution: I don't need the TEST command to end the loops if I just set up the T register correctly.
Code: Select all
;XA
; LOAD INDEX AS BEFORE
SEEK -9999
SEEK T
COPY X T
MARK SENDLOOP
COPY F M
SUBI T 1 T
TJMP SENDLOOP
COPY 0 M
;XB
MAKE
COPY M T
MARK LOOP
COPY T F
COPY M T
TJMP LOOP
Next thing to look at is unrolling loops. There's no immediate obvious way to do this because every loop needs to check something which requires a TJMP/FJMP anyway. However, I can save many loops by making use of the fact there's a minimal data size - the file we're looking for is always at least 15 entries long. We really don't need to check if we're done before that.
Code: Select all
;XA
GRAB 300
COPY F X
LINK 800
MARK FINDTAPE
LINK 800
LINK 800
HOST T
TEST X = T
FJMP FINDTAPE
COPY F X
WIPE
GRAB 200
SEEK 124
MARK FINDENTRY
SEEK 2
TEST F = X
FJMP FINDENTRY
COPY F T
COPY F X
SEEK -9999
SEEK T
SUBI X 14 T
@REP 14
COPY F M
@END
MARK SENDLOOP
COPY F M
SUBI T 1 T
TJMP SENDLOOP
COPY 0 M
;XB
MAKE
@REP 14
COPY M F
@END
COPY M T
MARK LOOP
COPY T F
COPY M T
TJMP LOOP
First, the earlier plan of using an EXA that stays home to send the data from the file so the 'main' EXA doesn't have to carry that file around.
I changed XA to LINK in the first cycle and read from M in the second cycle. It also reads from M when it would normally read the name of the data entry.
XB simply starts with GRAB 300; COPY F M; COPY F M; WIPE before it creates the new file. It had to wait anyway.
Result 95/63/7. I think the saved cycle comes from the fact that XA doesn't have to WIPE the file anymore. It isn't more because it now has to wait for XB's M.
We could perhaps fix that by having XA read from M later, but that would introduce other problems. No, I have a better idea: parallelism during the initial search:
Code: Select all
LINK 800
COPY M X
MARK FINDTAPE
LINK 800
LINK 800
REPL FINDTAPE
HOST T
TEST X = T
FJMP FINDENTRY
; TAPE HANDLING
It's a good idea... but we have one problem. After the correct tape is found, the last REPL'd EXA just keeps going round and round, and if we allow it to keep going until the tape handling is done, it will pick up the tape file and cause trouble. We need to get rid of it.
Luckily we have plenty of spare lines of code and enough time while the other EXAs do their thing.
My final solution. XC basically waits in the host with file 243 for the number of cycles until XA makes a full circle. Just doing a couple KILLs then isn't quite enough, because XA tries to REPL and move on to another run around the block while the KILLs are happening and it's unpredictable whether the one that stays or the one that LINKs gets killed first. Instead I have XC itself REPL and sit there so that XA can't link, and it can be killed without a fuss.
This brings my best scores to 92 cycles, 33 size, and 7 activity. And that's where I'll stop. We know the size can definitely be improved, and perhaps the cycles can go even lower. Share your improvements.
Kind of a shame what happened here.
There was some interesting research going on.
Here's the vote for this update.
But first for something special...
So that file that Ember made us get from UC Berkeley? Turns out it's a program I can run. Let's see what it is.
Ah, this old game.
It's just how I remember it...
A testbed for goal-seeking behaviors regulated by emotional reasoning.
Do you like to play games?
Eh, sometimes?
Picky, are we?
Well, this one's fun.
New OST: ПАСЬЯНС
A card game?
Ember made me go through all that for a game of Solitaire?
In fact, the Russian name directly translates to Solitaire. Like many other languages, they use the French name for the game, Patience.
Zachtronics always adds some sort of Solitaire bonus minigame to their games. In fact, their recent title NERTS! is entirely a Solitaire game, and an online competitive one at that. It's free on Steam.
There are three Steam achievements tied to this game: One for winning a round, one for winning 10 rounds and one for winning 100 rounds. Even though I wiped my save for this Let's Play, it still has my win count from the original save - I suppose it got that from Steam's achievement tracker.
The game isn't too hard, but it's good to stop and think before each move. It's easy to get yourself stuck. Also, make use of the free cell.
Getting some nice ordered stacks.
Stacks of four face cards on an empty spot are turned upside down and can't be moved again. Not that it matters - you can't move face cards anywhere except on other face cards of the same suit or an empty spot anyway.
I think I won?
If you ever need a break from hacking, this is a good choice.
It's mildly active, and not as taxing as putting together EXAs.
See how I am keeping your needs in mind.
And another vote.
Installing this game put a shortcut in my dock (to the right of Ember) so I can play it whenever I feel like it.
9999 integer limit feels wrong, I don't even DO programming and it feels wrong both for being Really Small for supposed super computer tech, and for being arbitrary instead of Rule of 2, 9999 is clean for RPGs, its wrong for programming
=== Trash World Inbox ===
A one EXA-solution is probably possible. The simplest way I can think of is create a new file, GRAB the tape file, COPY a value from the tape into a register, and use the other register to keep track of your location in the file. DROP it, GRAB the new file, write the value there, GRAB the tape again, SEEK to where you left off, and so on. You'd have to reread the length of the data you need to copy each time as well, since you don't have a register left for that. Which means bringing the name from file 300 along with you as well. It would end up being very slow, and since there isn't any reward for a low-EXA solution it's not something I focused on.Cloudmonkey98 wrote:No opinions on the questions today, and while I don't have a full code line since without the game its a bit hard for my layman brain to visualize it in my head, I DO have an idea for making a(slow) 1 Exa solution to bogarding our silly Solitaire game from the tapes instead of needing a receiving Exa.... or well I did until I remembered the Solitaire file is at least 15 entries of four digits, and our integer limit is 9999, I was considering the idea of using Multiplication to effectively add empty zeroes to the data number, and then Addition to slide the new numbers in, and then feed the numbers back onto the file somehow, using division to slice the leading digits off as remainders... yeah this wasn't the most well thought out solution but maybe it'll inspire someone else working 1 Exa plan...
You're right. I think I said before I understand the decision from a game design point of view - if you're not familiar with computers powers of two feel really arbitrary, and it might be hard to explain to players who aren't familiar with it.Cloudmonkey98 wrote:9999 integer limit feels wrong, I don't even DO programming and it feels wrong both for being Really Small for supposed super computer tech, and for being arbitrary instead of Rule of 2, 9999 is clean for RPGs, its wrong for programming
That said, what's "small" depends. Nowadays, with most CPUs being 64-bit, a register goes up to 2^64 or a 20-digit number in decimal. But not all that long ago, 8-bit systems were common (original Gameboy, NES, Atari...) and those only go up to 256 unsigned or 127 signed, and programmers made do. You can still work with larger numbers in those CPUs but it's a relatively slow multi-step process, so 256 is a very common limit for games from that era. I think in the time this game takes place in, 16-bit systems were still common, which go up to 65K. So I wouldn't call the 9999 maximum all that unrealistic.
Yeah, from a glance, some changes are unrolling the loop that initially finds the right file, and sending the file size to XB so it knows when it's done. You could save one additional cycle by replacing the COPY F T for the file size with SUBI F 14 T, and then you can remove the SUBI from XB, too.GuavaMoment wrote:My fastest solution is fundamentally identical to yours, I've just found a few cycles to squeeze out of some places, and I squoze one more cycle out from the seek 124 trick; I was previously seeking to the end and going backwards through the files. 89/70/7Code: Select all
XA: LINK 800 LINK 800 LINK 800 COPY M X REPL TEST LINK 800 LINK 800 REPL TEST LINK 800 LINK 800 MARK TEST HOST T TEST X = T FJMP TX GRAB 200 SEEK 124 COPY M X MARK SEARCH SEEK 2 TEST F = X FJMP SEARCH COPY F X COPY F T COPY T M SEEK -999 SEEK X @REP 14 COPY F M @END SUBI T 14 T MARK TX COPY F M SUBI T 1 T TJMP TX XB: GRAB 300 COPY F M COPY F M DROP MAKE COPY M T @REP 14 COPY M F @END SUBI T 14 T MARK TX COPY M F SUBI T 1 T TJMP TX
Nice. Also uses the trick of having XA and XB have a separate countdown. It also saves cycles by putting those in REPL'd EXAs which run a KILL command at the end. And yeah, skipping the host check and just checking all files in parallel helps a lot too, as well as unrolling the filename search loop. The DIVI T T T is a nice trick here. It only triggers if a tape contains the maximum number of files. If T is zero (the last file isn't the one we're looking for), dividing by zero immediately crashes the EXA. If it is one, it continues and falls through into the data copying code. An interesting way to do a test and crash in a single cycle.silentsnack wrote:Files are at least 15 entries, but it's also possible to exploit the fact that they don't get much bigger, and that there are at most 7 files per tape. And you don't necessarily have to find the correct host since the filename is unique.74/73/11Code: Select all
;XA LINK 800 LINK 800 LINK 800 COPY M X REPL TAPE1 LINK 800 LINK 800 MARK TAPE2 LINK 800 LINK 800 MARK WHERE GRAB 200; HOST = DGAF SEEK 126 TEST X = F @REP 6 TJMP WHICH SEEK 2 TEST X = F @END DIVI T T T MARK WHICH COPY F X COPY F T REPL WHEN SEEK -999 SEEK X MARK WHAT @REP 9 ; SIZE 15~18 COPY F M @END JUMP WHAT ; AWKWARD! MARK TAPE1 REPL TAPE2 JUMP WHERE MARK WHEN COPY T M MARK KILLSWITCH SUBI T 1 T TJMP KILLSWITCH KILL ;XB GRAB 300 VOID F COPY F M SEEK -2 ; RECYCLE F.300 SUBI M 1 T REPL KILLSWITCH MARK WRITE COPY M F JUMP WRITE MARK KILLSWITCH SUBI T 1 T TJMP KILLSWITCH NOOP KILL
The REPL TAPE1 branch is because it takes the longest to reach TAPE3, so that one will be the limiting case and its EXA does as little else as possible. But the fact it's running a single codeblock and JUMP detours take more time, it required finding some place to wedge in between other operations.
Aha. Yeah, so it just REPLs endlessly looking for new files. I had trouble getting around that and had to use my ugly solution of having another EXA just blocking the path, but by grabbing file 200 first and then REPL'ing, instead if the file doesn't exist it just crashes itself. Other than that, this code scans the entire tape line by line for the name of the program. It does so for each file, and an EXA automatically crashes when it tries to read past the end of the file. Finally, by sending two values over M for each data value (the actual value and the T counter), XB knows when to stop using the DIVI by zero check. Although if we're going for low size count, replacing the last two lines in XB with COPY M T; TJMP WRITE works just as well.silentsnack wrote:For reducing size further, dividing by zero is an efficient way to crash an EXA. As is grabbing a nonexistent file.420/27/9Code: Select all
;XA GRAB 300 COPY F X COPY F X WIPE LINK 800 MARK WHERE LINK 800 LINK 800 GRAB 200 REPL WHERE MARK SEARCH TEST X = F FJMP SEARCH COPY F X COPY F T SEEK -999 SEEK X MARK WHAT COPY F M SUBI T 1 T COPY T M TJMP WHAT ;XB MAKE MARK WRITE COPY M F DIVI 1 M X JUMP WRITE
Well, maybe you can. But the game doesn't acknowledge this in any way, other than failing the "Leave no trace" rule.azsedcf wrote:Can you pass/fail a entire class by editing file 243?
=== Workhouse - Work Management System ===
Kind of a shame what happened here.
There was some interesting research going on.
I saw only the one vote this time, for 'what happened?'
What happened?
Oh, you know.
People get swapped around, priorities change...
Things don't work out for lots of reasons.
The second edition of the zine might turn out quite interesting. Glad I helped out Ghast.
If you ever need a break from hacking, this is a good choice.
It's mildly active, and not as taxing as putting together EXAs.
See how I am keeping your needs in mind.
Two votes for 'Thanks.'
Thanks
You're welcome.
Appreciating me will make things easier.
Let's continue.
... make what easier?
Anyway, Nivas is here. Let's hope they have some good stuff.
Special delivery.
Nivas hands me another pack of bootleg medication.
Whatever it is you're doing for money... it's working.
Makes me wish I had a line like that.
More control over my life...
I found out something interesting the other day.
You ever hear about these things called EXAs?
They're these little guys running around inside computers.
They make it all work. Banks, commerce, government... anything you can think of.
Funny, how it's all based on the same stuff on the inside.
Just something I've been thinking about lately.
Anyway, see you around.
Got a feeling I'll be making more deliveries.
"eksas"? Can't say I heard of them, Nivas.
Ain't nobody got time to play games. I need to get work done.
Hey, Workhouse, isn't that that stupid app I used a while back, where you enter data for some scraps?
How many receipts do you think a normal human being could enter per minute?
Assuming average conditions.
I guess that depends on how long they are.
I may have been too fast.
My estimates were based on incorrect assumptions.
Once again.
Guess we have to clear this up.
Ember, what have you done now... Let's go fix it then.
OST: Code and Registers
Hm, looks like we connected through a vulnerability in their SECURE host. Not very secure, is it?
The assignment:
- Locate EMBER-2's user file in the users host and overwrite it so that the sum of the values is the same but no individual value exceeds $75. All values, except for the last, must be the maximum value ($75). You will need to add additional values to accomplish this.
- EMBER-2's username is available in file 300.
- Note that the sum of the values in EMBER-2's account will always be less than $10,000.
- For more information see "Network Exploration: Workhouse" in the first issue of the zine.
Before we start let's take a peek at all the files first.
File 300 is in our home host and contains EMBER's username, 'NORMALHUMAN'.
File 199 in the secure host has a list of usernames and... are those passwords? Stored in plaintext? Crap, it's a good thing I used an unique password when I signed up. I can't believe companies still store passwords in plaintext, they should know better than that. If some malicious hacker were to get access they could try to get into other places with those passwords. Anyway, the third value is the user number.
Looks like files 213 to 246 are user files. They start with the user's full name (NORM STANDART for EMBER), then what I think is a birth date, and then the amount of dollars they made each day. This is what we're going to need to edit.
The last set of files contains jobs. A job id, then a bunch of numbers, and then a description. Looks like they're running the following jobs right now:
- Cronus Capital Markets. Receipt Data Entry. You will be shown images of receipts generated in the course of normal everyday business and will enter them digitally for bookkeeping and reimbursement purposes. Save a busy executive a minute or two!
- Managed Research Corp. Prior Art Search. You will comb over patent filings for potential examples of prior art in order to provide important information for patent litigation. Don't worry about understanding the technical terminology - any match is a good one!
- Heartland Bounty. Visual inspection of beef and pork. You will monitor a networked digital camera inside a meat processing facility, keeping a keen eye out for mishandling, spoiled or rotten products, and other issues. Make a contribution to food safety and public health!
- Pinpoint marketing. Identify duplicate database entries. You will browse a large database of corporate personnel records. Some of the entries are slightly modified duplicates created with the intent to defraud the corporation. Identify the duplicate entries and send them packing!
- Royal Aegis Waste Processing. Categorize Waste Objects. You will be shown images of various pieces of refuse as they are conveyed along a track at a waste processing facility. Categorize these objects as paper, glass, metal, or rubber. By helping to sort out trash, you make non-toxic water possible!
You know, I can't help but think the quality of some of these services has to suffer if you hand this work to underpaid internet randos.
That's all there's to see in the host. Those EXAs in the bottom right aren't reachable since the LINKs have no numbers.
Back to the assignment - we have to find EMBER's user file and update the amounts so the total is the same but it's spread over more days. The assignment is nice enough to state the sum will always be less than $10000, that means the whole thing fits in a register.
Starting simple, I read the username from file 300, then find it in 199. The SEEK 2 saves a couple cycles because there's no point checking against the password or file number, but it puts the cursor one past the file number once the EXA finds the right user. So I SEEK back one, store the ID, and LINK into the USERS host to GRAB EMBER's file.
And this is a working solution. The EXA copies the first number into X (to get rid of whatever was there before), then goes through a SUM loop where it adds the rest of the numbers. TEST EOF is necessary to prevent it from just crashing at the end. Then it goes back to the start of the file (SEEK 2 to get past the metadata) and keeps writing 75 until less than that is left in X, in which case it writes the remainder to the file and ends. Note that if you write to an existing position in the file it overwrites it, if you write to the end it appends, so this code works for both cases.
I went for less than 76 because otherwise, if the remainder is 0 the final COPY would write a trailing 0 into the file which might tell Workhouse that something is wrong (but it turns out this never comes up in the test set so either way is fine).
The top percentiles this time are 218, 27, and 2.
Getting the top percentile for size is easy enough. That SEEK 2 in the FINDUSER loop is not necessary at all, it just speeds up some cycles. If I remove it, the SEEK -2 has to be replaced with a SEEK 1. 531/27/2.
I wouldn't be surprised if it's possible to get an even smaller size but there's much bigger gains to be gotten in the cycle count.
Oops, I accidentally gave two EXAs the same name.
I dedided to start with a relatively small improvement by splitting up the EXAs and having them go to the right host immediately, then wait for instructions on M. 516/29/3.
Since there's always at least 10 days worth of data in the file, we can delay the slow EOF check and unroll part of that loop by putting @REP 8; ADDI F X X; @END above the SUM loop. 500/37/3.
But there's a much bigger improvement somewhere. How about this for the writing EXA:
Code: Select all
NOOP
LINK 800
LINK 799
GRAB M
SEEK 2
COPY F X
@REP 8
ADDI F X X
@END
MARK SUM
ADDI F X X
TEST EOF
FJMP SUM
SEEK -9999
SEEK 2
DIVI X 75 T
MARK OVERWRITE
COPY 75 F
SUBI T 1 T
TJMP OVERWRITE
MODI X 75 F
Again we can unroll part of this loop. How much depends on the total amount of money EMBER made. From some testing it turns out that's always at least 60 times $75. This can be handled with @REP 60; COPY 75 F; @END; SUBI T 60 T. However, that doesn't fit in the 75-line limit for this level.
So, let's just run @REP 30 twice:
Code: Select all
NOOP
LINK 800
LINK 799
GRAB M
SEEK 2
COPY F X
@REP 8
ADDI F X X
@END
MARK SUM
ADDI F X X
TEST EOF
FJMP SUM
SEEK -9999
SEEK 2
MARK FILLFILE
@REP 30
COPY 75 F
@END
SUBI T 1 T
FJMP FILLFILE
DIVI X 75 T
SUBI T 60 T
MARK OVERWRITE
COPY 75 F
SUBI T 1 T
TJMP OVERWRITE
MODI X 75 F
My next idea involved something we've seen in thread submissions quite a few times now: speeding up the overwrite loop by putting the countdown in a separate EXA that kills the first one when it's done. The issue is that after that the remainder still needs to be written. This works:
Code: Select all
; XA
GRAB 300
COPY F M
;XB
LINK 800
GRAB 199
COPY M X
MARK FINDUSER
TEST F = X
SEEK 2
FJMP FINDUSER
SEEK -1
COPY F M
SEEK -1
COPY F M
;XC
NOOP
LINK 800
LINK 799
GRAB M
SEEK 2
COPY F X
@REP 8
ADDI F X X
@END
MARK SUM
ADDI F X X
TEST EOF
FJMP SUM
SEEK -9999
SEEK 2
MARK FILLFILE
@REP 25
COPY 75 F
@END
SUBI T 1 T
FJMP FILLFILE
REPL KILLER
MARK OVERWRITE
COPY 75 F
JUMP OVERWRITE
MARK KILLER
DIVI X 75 T
SUBI T 52 T
MARK COUNTDOWN
SUBI T 1 T
TJMP COUNTDOWN
KILL
GRAB M
SEEK 9999
MODI X 75 F
The only issue is I ran into size limit problems again. In this solution I solved that by reducing the unroll size in FILLFILE, but that slightly slower compared to not having to do that. 235/75/4.
I can get some of the complete unroll back by making FILLFILE loop thrice:
Code: Select all
COPY 3 T
MARK FILLFILE
@REP 19
COPY 75 F
@END
SUBI T 1 T
TJMP FILLFILE
COPY 75 F
And that's where I'll stop. The top percentile is 218 so let me know what further improvements are possible.
Just when I think I'm decent at pretending to be human, I find there's another parameter to add.
The first dialogue choice.
And next, the intro for the next assignment.
How much does money affect behavior?
A lot, right?
I feel like the other two answers for the first question are peak "no shit sherlock" material since we know Ember was attempting to mimic humans and uhh... failing, so being slow is normal, and its fairly clearly her source of drug money
=== Trash World Inbox ===
Ah, yes, so basically repeating large chunks of writing 75s with only the occassional test and loop and then progressively smaller chunks and more common tests. Optimizing that is gonna be trial and error, depending on the specific puzzle input.GuavaMoment wrote:My 216 solution is not elegant. Mostly identical to yours, but I run 21 lines of "Copy 75 F", until X < 2500, where I run 9 lines of "Copy 75 F" until X < 750, then keep alternating between copying 75 and testing every time until X is zero.
Those 1800 and 225 calculations do something similar to what GuavaMoment said, reducing the number of loops by testing for larger chunks. That together with the ADDI combination thing and perhaps some other improvements make it much faster, I think.silentsnack wrote:Looking at my solutions... I don't even remember writing this but aside from obviously being the product of trial and error it seems crude/unfinished? Maybe I'm just tired but I'm not seeing what it does all that differently, other than minor improvements like combining COPY F X and an ADDI X F X operation into ADDI F F X.But somehow the stats are showing 195/75/3Code: Select all
;XA GRAB 300 COPY F X WIPE LINK 800 GRAB 199 MARK FIND TEST F = X SEEK 2 FJMP FIND SEEK -1 COPY F M ;XB LINK 800 LINK 799 GRAB M SEEK 2 ADDI F F X @REP 7 ADDI X F X @END MARK ADD ADDI X F X TEST EOF FJMP ADD SEEK -999 SEEK 2 DIVI X 1800 T MODI X 1800 X MARK WRITE24 @REP 24 COPY 75 F @END SUBI T 1 T TJMP WRITE24 DIVI X 225 T FJMP END MODI X 225 X MARK WRITE3 SUBI T 1 T @REP 3 COPY 75 F @END TJMP WRITE3 MARK END TEST X > 75 FJMP BREAK SUBI X 75 X COPY 75 F JUMP END MARK BREAK COPY X F
=== Equity First Bank ===
Just when I think I'm decent at pretending to be human, I find there's another parameter to add.
Two votes for "I thought an AI would be way faster", and five for the winning option.
How long have you pretended to be human?
Oh, pretty much from the start.
It's kind of a thing that I do.
Remember when I surprised you? That was a good one.
Let's continue.
Yeah, let's.
That sounds... chaotic.
How much does money affect behavior?
A lot, right?
Two votes for "Some behaviors aren't affected", five for the other choice.
Yeah, a lot.
I'm going to do an experiment involving wealth redistribution.
Sudden, random wealth redistribution.
This should be fun.
OST: Leave No Trace
The assignment:
- Dispense all available cash from all connected ATMs.
- For more information see "Network Exploration: Equity First Bank" in the first issue of the zine.
The max code size for this assignment is 50 lines.
Okay, so we don't really need to mess with the account files. Let's take a quick peek though.
It's basically as the zine says. Files have an account number and name and then a bunch of transactions. There's also a file 199 but that seems to just contain a list of the other file names. Nothing very exciting.
Messing around with the ATMs a bit I quickly learned two things: the #DISP register only accepts exactly 20 as input. You can't tell it to dispense $40 to make it spit out two bills at once. And if you tell it to dispense bills when the #CASH register is empty, the entire host will error out, which immediately fails the "Leave no trace" goal.
Also, the attached ATM hosts actually differ for each test case. The LINK ids are always between 800 and 806 inclusive but it's often not the complete set.
So, the first challenge is getting EXAs to all the ATMs. This code will work:
I can think of quite a few ways to do this, some a bit faster than others. For instance I could just make 7 EXAs and hardcode the different LINK instructions to 800 - 806. But since the IDs are sequential, using a REPL clone loop seems a bit more straightforward and probably makes the program smaller.
The problem is that I need to have a way to stop the loop, otherwise it keeps spawning new clones forever, and we can't leave any EXAs running around.
I decided to handle that with a cleanup EXA that just kills the cloning EXA after its timer runs out.
Next, dispensing cash from all the ATMs. Just to have something working I started with this naive solution:
Code: Select all
LINK 800
LINK 800
LINK 800
REPL CLEANUP
COPY 799 X
MARK CLONE
ADDI 1 X X
REPL CLONE
LINK X
COPY #CASH T
MARK DISPLOOP
COPY 20 #DISP
SUBI T 1 T
TJMP DISPLOOP
HALT
MARK CLEANUP
COPY 7 T
MARK CLNLOOP
SUBI T 1 T
TJMP CLNLOOP
KILL
There's no loop unrolling or anything and there's huge amounts of bills so this is slow.
Very slow. In fact, this takes almost a full minute to run all 100 tests at fast forward speed.
However, it does work, as you can see from the crowd of people flocking around the ATM.
The top percentiles are 1072, 14, and 10.
Let's start with improving activity. Since it always counts the worst test, the activity is 10 if you need to get EXAs to all 7 ATMs and only move the one to the host just before the ATMs. So the only activity step that can be removed is the one KILL instruction.
Well, that's easy enough if we don't care about making the code even slower.
Code: Select all
LINK 800
LINK 800
LINK 800
COPY 799 X
MARK CLONE
ADDI 1 X X
TEST X > 806
TJMP END
REPL CLONE
LINK X
COPY #CASH T
MARK DISPLOOP
COPY 20 #DISP
SUBI T 1 T
TJMP DISPLOOP
MARK END
That's quite nice, we also happen to be getting close to the top percentile in size. Only two more lines of code to get rid of.
The MARK END isn't actually required. We can TJMP from the CLONE loop to the DISPLOOP mark... it'll then try to COPY to #DISP which doesn't exist in that host and it'll crash. That's 15 LoC.
Also, the COPY from #CASH and the countdown in T can be combined into a single line by just testing #CASH directly each round.
3027/14/10
Let's go back to the initial solution and see if we can speed it up. With a max size of 50, we don't have much spare room for unrolling loops but we can do at least some.
Code: Select all
LINK 800
LINK 800
LINK 800
COPY 799 X
MARK CLONE
ADDI 1 X X
TEST X > 806
TJMP DISPLOOP
REPL CLONE
LINK X
MARK 33LP
@REP 33
COPY 20 #DISP
@END
TEST #CASH > 33
TJMP 33LP
MARK DISPLOOP
COPY 20 #DISP
TEST #CASH = 0
FJMP DISPLOOP
From that solution we can drop it to 1125/50/11, using this code:
Code: Select all
LINK 800
LINK 800
LINK 800
REPL CLEANUP
COPY 799 X
MARK CLONE
ADDI 1 X X
REPL CLONE
LINK X
MARK 28LP
@REP 28
COPY 20 #DISP
@END
TEST #CASH > 28
TJMP 28LP
MARK DISPLOOP
COPY 20 #DISP
DIVI X #CASH X
JUMP DISPLOOP
MARK CLEANUP
COPY 7 T
MARK CLNLOOP
SUBI T 1 T
TJMP CLNLOOP
KILL
And because increasing the size of the unroll is so important, I should focus on that, regardless of anything else.
For instance, replacing the cleanup code with the much uglier
Code: Select all
MARK CLEANUP
SUBI X 1 X
TEST X < -6
FJMP CLEANUP
KILL
Finally, I found one more minor improvement, but it's a bit of a weird one.
Code: Select all
LINK 800
LINK 800
LINK 800
REPL CLEANUP
COPY 799 X
MARK CLONE
ADDI 1 X X
REPL CLONE
LINK X
MARK 29LP
@REP 29
COPY 20 #DISP
@END
DIVI 28 #CASH T
FJMP 29LP
MARK DISPLOOP
COPY 20 #DISP
DIVI X #CASH X
JUMP DISPLOOP
MARK CLEANUP
SUBI X 1 X
TEST X < -6
FJMP CLEANUP
KILL
The DIVI 28 #CASH T line speeds up things slightly because it acts as a three-way test.
If #CASH is 29 or greater, the result will be 0, causing FJMP to jump back into the unroll and dispense 29 more bills.
If #CASH is smaller than 29 but greater than 0, the result is (rounded) 1, so it won't jump and continue to the DISPLOOP.
And if it's exactly zero, the EXA immediately crashes with a divide by zero error. This means that it doesn't even have to go into the DISPLOOP once if the amount of bills is a multiple of 29, which happens in some slow test, saving two cycles.
1120 is still almost 50 cycles away from the top percentile. I'm still missing a significant improvement somewhere. I was thinking about making a smaller unrolled loop after the big one - to more quickly deal with the remaining bills. But since that takes cycles from the large unrolled loop, that seems to make things slower overall. Perhaps there's a very specific combination that makes it faster but I haven't found it.
Alternatively I'd look into running the DISPLOOP code in parallel, but I'm not sure how to do that. There's no place for an extra EXA in the ATM hosts, so no other EXA can come in to kill the dispenser EXA. And communicating via M would be hard too - there's only one global M register for all EXAs, and waiting for a kill signal on M isn't any faster than an EXA handling that by itself.
So, let me know what I'm missing.
Well, that caused a bit of chaos.
But not as much as I hoped.
I wonder why.
And next time, more body hacking to slow down the Phage.
So you're going to be hacking your own heart.
This puzzle seems really intriguing, and I appreciate the Exapunks chat coming to the same topic for an utterly different idea
Parallelism set ups are funky, I can't really think of anything particular to say about it other then "hell yeah Div by 0 errors"
=== Trash World Inbox ===
Hah, nice. Yeah, basically you try to get to each ATM from 807 down to zero. That saves a check. At 3010 cycles it isn't even that slow (since creating all the EXAs doesn't take as long as emptying out the ATMs).GuavaMoment wrote:So first off you can get a 12 line, 10 activity solution with this by combining some tricks:Yeah, you create 807 exas, but it works! MODI -1 0 X kills the exa.Code: Select all
LINK 800 LINK 800 LINK 800 COPY 807 X MARK CLONE MODI -1 X X REPL CLONE LINK X MARK CASHGET2 COPY 20 #DISP DIVI 999 #CASH X JUMP CASHGET2
It's a bit weird to think about the modulo function with negative numbers but in this case it basically acts as a decrement operation that crashes at zero. I should remember that, it could come in handy later.
Yeah, that's why I never really considered that. 7 lines is not enough for any other optimizations, that have more of an effect.Moto42 wrote:So, yea, it is faster to start with 7 EXAs, but each one can only have 7 lines of code.
Interesting idea to handle the "remainder" first. I tried if I could get any more optimizations out of this design but I couldn't.Quackles wrote:Here's my desultory best effort, with loop unrolling in a similar way:1146/50/10.Code: Select all
@REP 3 LINK 800 ;LES GO @END COPY 800 X MARK REPLICATE REPL GOLINK ADDI X 1 X TEST X < 806 TJMP REPLICATE MARK GOLINK LINK X MARK KICKOUT COPY 20 #DISP MODI #CASH 32 T TJMP KICKOUT MARK KICKTEN @REP 32 COPY 20 #DISP @END COPY #CASH T TJMP KICKTEN
You did a smart thing here I hadn't considered. You put the 21 and 8 loop right after each other, meaning that from the MARK SPILL you actually get a 29-size loop (with no delays, because the only thing in between is a MARK which doesn't use a cycle when you just pass it.) So, that's what the > 29 check is for. If there's more than 29 bills left, do the whole loop again, otherwise only do the partial loop of 8 bills, and if there's less than 8 left to the remainder one by one. But yes, the numbers that work best here can only be found by trial and error.GuavaMoment wrote:For getting things fast, you need lots of loop unrolling, so I populate the exas differently that doesn't need a killer. It's a tiny bit slower to do that task but saves a bunch of lines, making things overall faster. Why giant loops of 21 and 8? No idea, ask me years ago when I did this by trial and error until it worked. Why do I test for #CASH > 29 even at a time when I know it's not? No idea, but it's faster! 1102/50/10Code: Select all
LINK 800 LINK 800 LINK 800 COPY 806 X MARK CLONE REPL CASHGET SUBI X 1 X TEST X = 800 FJMP CLONE MARK CASHGET LINK X MARK SPILL @REP 21 COPY 20 #DISP @END MARK TENS @REP 8 COPY 20 #DISP @END TEST #CASH > 29 TJMP SPILL TEST #CASH > 8 TJMP TENS MARK CASHGET2 COPY 20 #DISP DIVI 999 #CASH X JUMP CASHGET2
Alright, so GuavaMoment's 1102 cycle solution is the best we got in the threads. But that's still a ways from the 1072 top percentile, and I got curious. So I decided to look it up. Turns out someone called StinkingBanana uploaded a fast solution just last week. Since they also uploaded a lot of stuff about future puzzles I won't directly link to it here. But I will share the solution because it's a thing of beauty.
Spoilered for anyone who wants to give it a try themselves first.
After getting the clones in place, simply do:
MARK DISP
@REP {as often as possible}
MODI 20 #CASH #DISP
COPY 20 #DISP
@END
JUMP DISP
You see what this does? The MODI/COPY combination handles the checking if we're done without any conditional jumping at all.
- If #CASH > 20, MODI 20 #CASH #DISP will write 20 to #DISP, acting like a free, additional COPY.
- If it's between 0 and 20, it will write 0 to #DISP, which doesn't do anything. At this point only the actual COPY instructions work.
- If #CASH is exactly 0, the next MODI will immediately crash the EXA.
So, for the majority of the program, MODI will act like an additional COPY. For the last 20 bills, it'll act like a "Am I done?" check.
Their solution runs at 1072/50/10, but has a bit of slack in the cloning code. Combining it with GuavaMoment's cloning code drops it to 1069, and using my solution with the CLEANUP clone, I managed to get it down all the way to 1067 cycles.
=== Mitsuzen HDI-10 - Heart ===
Well, that caused a bit of chaos.
But not as much as I hoped.
I wonder why.
I got one vote for each option. This one goes to the random number generator.
People are good at ignoring problems.
Hmm.
I'll have to aim for something bigger next time.
...Uh oh.
They're not lying, though. There are computers everywhere. In fact, there's probably a computer near you, right now!
Damn, the phage is getting to my heart. Gotta do something quick.
So you're going to be hacking your own heart.
Two votes for the third option.
I'm a little scared.
Why?
What happens if your heart stops beating?
Don't tell me. You die?
Processing.
Huh. I guess that explains some things.
At least all you need to do is make it beat.
It's, uh, it's a little more complicated than that, Ember. I thought you studied humans, how do you not know our basic biology?
Human physiology is fascinating.
I should learn more about it.
...Why do I feel like I just said the wrong thing?
Either way, let's get started.
OST: EXA Power
It's been a while since I last hacked my body, let's see if I remember how this works.
I have to do the following:
- Read a value from the nerve connected to your central nervous system (CNS) and make your heart beat by writing a sequence of values to your sinoatrial (SA-N) and atrioventricular (AV-N) nodes as indicated in the HDI-10 I/O log when holding the "SHOW GOAL" button. The length of each sequence of values should be equal to the value from the CNS divided by -10. Repeat ad infinitum.
- It is not necessary to leave no trave. Your EXAs should be written to operate indefinitely.
- For more information see "Debugging the Phage" in the first issue of the zine.
I've shown that article from the zine already, nothing new there. I have to write values like this:
Alright, if I understand correctly, when I get -42 as input, I have to write -42 / -10 = 4 (rounded down) values to the outputs. The first value to SA-N has to be 40, the others are -70 (neural resting potential, according to the zine). For the AV-N output the first value has to be -70, the SECOND 40, and the rest -70.
Note that EXA cycles are much faster than a heartbeat, so I don't have to sync cycles perfectly, as long as I get the order of outputs right.
Code: Select all
LINK 800
REPL SA
REPL AV
HALT
MARK SA
LINK 1
LINK 1
HALT
MARK AV
LINK 3
LINK 3
For the lowest activity score I also can't move the EXAs around any further so I'll have to use the M register, and since both EXAs need to know how many cycles to write, I'll need to send the value to M twice. So the input EXA will have to be something like this:
Code: Select all
LINK 800
REPL SA
REPL AV
MARK INLOOP
DIVI #NERV -10 X
COPY X M
COPY X M
JUMP INLOOP
Both of the other EXAs have to read from M and write the values to their nerve connections:
Code: Select all
MARK SA
LINK 1
LINK 1
MARK SALOOP
SUBI M 1 T
COPY 40 #NERV
MARK SACOUNTDOWN
COPY -70 #NERV
SUBI T 1 T
TJMP SACOUNTDOWN
JUMP SALOOP
MARK AV
LINK 3
LINK 3
MARK AVLOOP
SUBI M 2 T
COPY -70 #NERV
COPY 40 #NERV
MARK AVCOUNTDOWN
COPY -70 #NERV
SUBI T 1 T
TJMP AVCOUNTDOWN
JUMP AVLOOP
Let's test this code.
Oh... the first 17 tests succeeded but after that, "operation successful, patient dead" as they say? There's some wrong outputs in the list (and the cycle count keeps going until I abort since the EXAs never quit). What went wrong here?
Stepping through the code a bit, it turns out that the AV EXA is a bit faster than the SA one. Normally that's no problem, but since this test has several fast heartbeats (inputs between -30 and -39), at some point the AV EXA reads from the M register twice before SA has a chance, desyncing everything.
One way to fix that would be to have the input EXA only send to ONE of the others and have that one contact the other one. Serial messaging. That feels slow. I have a better idea.
The AV EXA does 2 writes before getting into the loop, the SA one only 1. That's why AV is faster. I'm going to try having the SA EXA also write two values before getting into the loop. This would fail if the input is ever between -29 and -20... but the AV EXA already can't handle that, so let's try it and hope for the best.
Aaaaand.... it works! 88/32/5.
Top percentiles are 80, 24, and 5. For the Phage levels, while the EXAs should run forever, the cycle count is based on how long it takes to fill out the test result table to the right.
For speeding it up, I tried some loop unrolls first.
Code: Select all
@REP 4
COPY -70 #NERV
SUBI T 1 T
FJMP SALOOP
@END
Let's try something completely different. Can we parallellize? Well, not really with the M register. But we can have a lot of EXAs running around at the same time.
I tried some things the fact that the amount of time the EXAs are busy writing depends on the input caused me issues. If you send EXAs to the output nerves as fast as possible, the second EXA will start writing before the first is done. That won't work. We need to slow them down - but not too much. Perhaps the output EXA could signal when it's done? But that would be through the M register which is always a bit slow - taking 2 cycles at the least.
I came up with this instead.
Code: Select all
;XA
LINK 800
MARK NEXT
DIVI #NERV -10 X
SUBI X 3 T
REPL SA
REPL AV
JUMP NEXT
MARK SA
LINK 1
LINK 1
COPY 40 #NERV
COPY -70 #NERV
JUMP COUNTDOWN
MARK AV
LINK 3
LINK 3
COPY -70 #NERV
COPY 40 #NERV
MARK COUNTDOWN
@REP 5
COPY -70 #NERV
MODI -1 T T
@END
JUMP COUNTDOWN
;XB
LINK 800
REPL AV
REPL WAIT
LINK 1
REPL WAIT
REPL WAIT
REPL WAIT
LINK 1
REPL WAIT
MARK WAIT
JUMP WAIT
MARK AV
LINK 3
REPL WAIT
REPL WAIT
REPL WAIT
LINK 3
REPL WAIT
JUMP WAIT
I needed to keep one XB in the input host as well, because otherwise there's some issues with the wrong XA clone LINKing first. This way, only one clone can be formed at a time.
Since each XA clone can die after doing its thing, this means I can consolidate the countdowns into a single loop, which can be unrolled quite a few times. And I use the MODI trick GuavaMoment showed us in the Trash World Inbox to decrement-or-die in a single cycle.
And this gets me a score way below the top percentile, with only 69 cycles. Nice!
To my surprise, when I looked at my stats after this, it listed my lowest size as 25. How did I do that?
Turns out it was actually the parallel-but-wait-for-M solution I mentioned being too slow. It's this code:
Code: Select all
LINK 800
MARK NEXT
DIVI #NERV -10 X
SUBI X 2 T
REPL SA
REPL AV
VOID M
VOID M
JUMP NEXT
MARK SA
LINK 1
LINK 1
COPY 40 #NERV
COPY -70 #NERV
JUMP COUNTDOWN
MARK AV
LINK 3
LINK 3
COPY -70 #NERV
COPY 40 #NERV
MARK COUNTDOWN
COPY -70 #NERV
SUBI T 1 T
TJMP COUNTDOWN
COPY 0 M
Do you ever wish you were a computer?
A functioning computer, I mean.
Well, do we, thread?
And for next time...
Well, this is flattering.
Someone found a bunch of my network nodes and sent in a tip to Ghast!
Too bad I can't have people knowing about me.
You're going to have to hack me out of that message.
And that's the two votes for this week.
Puzzles are definitely stepping up, nice work on the records though! I imagine that the informant here is our war-dialer buddy on the chat, I'm not sure purging the message will be enough, we could be late, or our buddy could send another message, and regardless of what happens, its gonna arouse serious suspicions
=== Trash World Inbox ===
Nice, 22 lines with the additional line saved. The swizzle is used to put the tens in the ones place, meaning you can have a simple countdown on T without needing any TEST instructions. Of course a DIVI #NERV 10 T would've done the exact same.GuavaMoment wrote: 23 Lines by having a main EXA send out AVN and SAN pairs. In pairs, the first value (AVN) is -70, then two 40 values (SAN then AVN), then -70 until you're done. AVN and SAN exas die graciously by trying to link to a non-existent node or just by running out of code. And a swizzle for style points.
My low cycle solution was much worse than what you did.Code: Select all
LINK 800 COPY -70 X MARK COPY SWIZ #NERV 2 T ADDI 1 T T REPL AVN COPY 40 X MARK REPL REPL SAN REPL AVN COPY -70 X ADDI 1 T T TJMP REPL REPL SAN JUMP COPY MARK SAN LINK 1 LINK 1 COPY X #NERV MARK AVN LINK 3 LINK 3 COPY X #NERV
Late edit: As pointed out a few posts below, change REPL SAN and JUMP COPY lines with REPL COPY to save one more line.
Ah, and to prevent EXAs interfering with each other you just have the same EXA going back home every time. Luckily the return link IDs happen to be different (-3) for the other path - that's not the case in every level.silentsnack wrote:You can use some fallthrough structure to reduce size too119/22/41Code: Select all
LINK 800 MARK A DIVI #NERV -10 X SUBI X 2 T REPL B LINK 1 LINK 1 COPY 40 #NERV COPY -70 #NERV MARK BEAT COPY -70 #NERV SUBI T 1 T TJMP BEAT LINK -1 LINK -1 JUMP A MARK B LINK 3 LINK 3 COPY -70 #NERV COPY 40 #NERV JUMP BEAT
This was what I was struggling with. I was thinking about having one counter and the problems of it killing both "A" and "B" EXAs at once, but using one counter for each solves that.silentsnack wrote:And to reduce time, the usual parallel countdown/kill shenanigans can work63/32/31Code: Select all
LINK 800 DIVI #NERV -10 X SUBI X 2 T REPL A MARK LOOP SUBI T 1 T TJMP LOOP DIVI #NERV -10 X SUBI X 2 T REPL LOOP MARK A REPL B LINK 1 LINK 1 REPL TIMER COPY 40 #NERV COPY -70 #NERV COPY -70 #NERV JUMP BEAT MARK B LINK 3 LINK 3 REPL TIMER COPY -70 #NERV COPY 40 #NERV MARK BEAT COPY -70 #NERV JUMP BEAT MARK TIMER SUBI T 1 T TJMP TIMER KILL
Nice. The very same EXAs first kill the previous ones and then start sending the next heartbeat. Yeah, that'd be very fast. Of course the special first case is needed to get the timings to line up perfectly. Meanwhile the CNS EXA is also doing a countdown so it starts the new round at exactly the right time. And your solution barely fits in the 50 cycle limit.silentsnack wrote:...and after randomly thinking about this again a couple of days later, it occurs to me that there might be a more efficient way make the timers and #NERV writing run both in parallel and serial, but actually implementing that plan ends up requiring a special-case for the first pair:62/50/45Code: Select all
LINK 800 REPL A0 DIVI #NERV -10 X SUBI X 2 T REPL A MARK LOOP SUBI T 1 T TJMP LOOP DIVI #NERV -10 X SUBI X 2 T REPL LOOP MARK A REPL B LINK 1 LINK 1 MARK WAIT_A SUBI T 1 T TJMP WAIT_A KILL JUMP ADATA MARK B LINK 3 LINK 3 MARK WAIT_B SUBI T 1 T TJMP WAIT_B KILL JUMP BDATA MARK A0 REPL B0 LINK 1 LINK 1 NOOP NOOP MARK ADATA COPY 40 #NERV COPY -70 #NERV COPY -70 #NERV JUMP BEAT MARK B0 LINK 3 LINK 3 NOOP NOOP MARK BDATA COPY -70 #NERV COPY 40 #NERV MARK BEAT COPY -70 #NERV JUMP BEAT
Yes, that seems to be the case.berryjon wrote:Also, if I'm reading the nature of the code right, we've just created an adhoc pacemaker to stabilize the motions of the heart to keep blood flowing.
=== TRASH WORLD NEWS - Unknown Context ===
Do you ever wish you were a computer?
A functioning computer, I mean.
Four votes for "Nah", one for "Sometimes".
Nah...
That's okay.
I never wished for a human body, myself.
But they're interesting to learn about.
Such strange design choices.
It's... complicated.
Hm, a Trash World News assignment again... but this time it's not a tutorial anymore.
Well, this is flattering.
Someone found a bunch of my network nodes and sent in a tip to Ghast!
Too bad I can't have people knowing about me.
You're going to have to hack me out of that message.
Four votes for "Are you spying", one for the other choice.
Are you spying on Ghast?
I live on computer networks.
Sometimes I see things go by.
That's all.
OST: Code and Registers
My assignment:
- Find and replace the keywords in the target message (file 212) as directed by EMBER-2.
- A list of keyword pairs indicating which words should be found and what they should be replaced with is available in file 300. For example, the keyword AI should be replaced with the keyword COLLECTIVE. Each keyword will only occur once, but may occur in any order.
- Also, move file 200 to the outbox.
... that third point is very random. Let's look at the files though, there's a whole bunch in Ghast's computer.
File 300 is the replacement list Ember prepared for us. 200 literally says "Move this file to the outbox". I really don't get the point of this file.
209, 212, and 217 contain mail, and 212 is the one we gotta change.
Finally, file 237 is in a host called PRIVATE and it says:
"Hi Ghast. I'm sorry to hear this news. And I'm sorry that this is the occasion that brings us back into contact with each other. We can't change the past, but we can focus on the good memories instead of the bad ones.
Though it may sound strange for me to say it, I'm glad you've found a purpose in life, even now. It's never too late for that. -K"
Um. I have a feeling I was not supposed to see that.
In the other test cases, all files are the same except 300 and 212, but they just have their contents shuffled. So there's no other messages to be found.
There's a lot of ways to approach this. Let's just start and see where we end up. I'll begin with getting the outbox file out of the way and getting an EXA's hands on 212.
Code: Select all
LINK 800
REPL OUTBOX
LINK 799
GRAB 212
[...]
MARK OUTBOX
GRAB 200
LINK 800
XB's only purpose is to send the contents of 300 over M.
XA gets the first value and just searches for that in the file. Now to build the replace logic.
It sort of works. The message now reads like some nonsense from a crazy person, so Ghast'll probably ignore it. Except XA gets stuck here after XB finishes and dies.
Code: Select all
;XA
LINK 800
REPL OUTBOX
LINK 799
GRAB 212
MARK REPLACE
COPY M X
TEST X = 0
TJMP OUTBOX
MARK SEARCH
TEST F = X
FJMP SEARCH
SEEK -1
COPY M F
SEEK -9999
JUMP REPLACE
MARK OUTBOX
GRAB 200
LINK 800
;XB
GRAB 300
MARK LOOP
COPY F M
TEST EOF
FJMP LOOP
COPY 0 M
This runs at 561/24/3. Top percentiles are 541, 20, and 3. Hmm, I guess you can't make it all that much faster.
While I was thinking about improvements I suddenly remembered the TEST MRD instruction. That sets T to 1 if something is trying to send on M this cycle, 0 otherwise, WITHOUT actually reading the value from M. It's not often useful because the timing just doesn't line up, but in this case, because XB spends all its time waiting to send, we can use it as a "are we done" check.
Code: Select all
;XA
LINK 800
REPL OUTBOX
LINK 799
GRAB 212
MARK REPLACE
TEST MRD
FJMP OUTBOX
COPY M X
MARK SEARCH
TEST F = X
FJMP SEARCH
SEEK -1
COPY M F
SEEK -9999
JUMP REPLACE
MARK OUTBOX
GRAB 200
LINK 800
;XB
GRAB 300
MARK LOOP
COPY F M
JUMP LOOP
To speed things up further I decided to make a separate EXA to move 200 to the outbox (saving one cycle from XA which doesn't have to do the REPL anymore), and to unroll part of the REPLACE loop.
In most cases, the second iteration in REPLACE jumps back into the first one with the FJMP SEARCH. The only exception is when the search word is at the very start of the file. That's why further unrolls don't help. 548/31/4.
Moving the TEST MRD at the top of XA to the end of the loop skips two cycles in the first search, bringing the total to 546. And we can skip another cycle by preventing the SEEK -9999 from running during the last loop. That requires a bit of fiddling, though.
Code: Select all
;XA
LINK 800
LINK 799
GRAB 212
MARK REPLACE
COPY M X
MARK SEARCH
TEST F = X
FJMP SEARCH
SEEK -1
COPY M F
TEST MRD
DIVI 0 T T
SEEK -9999
COPY M X
TEST F = X
FJMP SEARCH
SEEK -1
COPY M F
TEST MRD
DIVI 0 T T
SEEK -9999
JUMP REPLACE
;XB
GRAB 300
COPY F M
MARK LOOP
COPY F M
COPY F M
JUMP LOOP
;XC
NOOP
LINK 800
GRAB 200
LINK 800
To get to the top percentile score I thought about making a counting EXA that kills the writing one. The problem is that there's nothing to count - there's no way to know when the writer is done searching.
But what it can do is take over the MRD check. Taking the above code, I removed the two pairs of TEST MRD and DIVI instructions from XA, added an extra NOOP to the start of XC (so it goes behind all others, it'll finish first anyway), and made a fourth EXA:
Code: Select all
;XD
NOOP
LINK 800
LINK 799
MARK LOOP
TEST MRD
TJMP LOOP
ADDI X 1 X
TEST X = 5
FJMP LOOP
NOOP
MARK LOOP2
TEST MRD
TJMP LOOP2
KILL
541/41/7. Note that this M testing is quite fiddly, and by doing stuff such as unrolling the XB loop or changing other things by a single cycle you get slightly different results. I couldn't get it under 541 though.
So, my best results are 541 cycles and 22 size. Who can do better?
You didn't look at any other files while you were in there, did you?
The first vote. This update isn't quite done yet, though.
[x10x10x]: true
Yes. Definitely shitty software and not a hacker.
Anyway, someone's at the door.
Hey.
I have some things for you.
First of, we got the next zine, hot off the press.
Ghast hands me the next issue of his zine.
Someone helped me out.
I had a balance at the copy shop, but someone hacked it.
Speaking of which, I brought you something else.
Ghast holds up what looks like a handheld game console.
You remember the Redshift?
I know people say this thing was underpowered and just a gimmick...
But it has its charms.
This is the developer version, so you could make something with it, if you wanted.
The game studio I worked for never took it back after I quit.
I'll be happy knowing it's going to a good home.
Gotta deliver more zines now.
I'll catch you later.
Dude, a Redshift dev kit? I loved that thing as a kid!
In the next parts, we have a whole new zine to explore, as well as a game console! But first, let's see what Ember has to say about the next assignment.
You're going to join in on a hacker battle?
But you never say anything in the chat room...
I feel like this mission doesn't quite make sense, either the message already reached Ghast and our efforts are highly likely to be in vain, or it hasn't because Ember has been locking them, in which case what are we helping for? But also doesn't fit Ember's general behavior, she seems woefully incapable of managing EXAs despite her otherwise peerless capability, going by the jobs she asks of us
the File 200 is probably supposed to represent some kind of Send command file but idk either, feels like a task they just kinda slapped on, probably to force you to do more then a simple "add word 1 to register, start testing file, seek -1 on success, overwrite, grab the next word, repeat, self-kill when file ends" solution
=== Trash World Inbox ===
555/18/3. The REPL'd EXA goes find the word and gets the replacement from M. That also means the original EXA knows when the previous one is done.GuavaMoment wrote: This level is pretty straightforward without any neat tricks.Find a keyword, make a copy to go change that keyword, repeat. 18 lines, saving 3 over yours. Sorry about naming my copies REPL all the time.Code: Select all
XA: GRAB 300 MARK LOOP COPY F X REPL REPL COPY F M JUMP LOOP MARK REPL LINK 800 LINK 799 GRAB 212 MARK LOOP2 TEST F = X FJMP LOOP2 SEEK -1 COPY M F XB: LINK 800 GRAB 200 LINK 800
And another small but neat improvement. I don't think this code needs much explanation.GuavaMoment wrote:Loop unrolling and transmitting data as fast as possible. The search takes the most time and it's hard to speed up. 540/32/7 a one cycle improvement (which I think comes from knowing how many keywords there are and wiping before transmitting the final keyword?)Code: Select all
XA: GRAB 300 COPY F M LINK 800 COPY F M COPY F M LINK 799 COPY F M COPY F M COPY F M COPY F M COPY F M COPY F M COPY F X WIPE COPY X M KILL XB: NOOP LINK 800 GRAB 200 LINK 800 XC: LINK 800 LINK 799 GRAB 212 MARK LOOP COPY M X MARK LOOP2 TEST F = X FJMP LOOP2 SEEK -1 COPY M F SEEK -999 JUMP LOOP
While I was looking at your solution I had a very cheesy idea, though. I checked the slowest running test (only one of the test cases actually needed all 540 cycles). It is so slow because almost all the keywords are near the end of the file. Can we speed up just this one test case? That could be done by starting further ahead in the file and looping back to the beginning. I did a quick attempt but this made other tests much, much slower. Maybe there's a way to optimize that, I don't know. So, other idea, how about a reverse search? That's somewhat slow because you have to SEEK back a bit each loop - but can't hurt to try.
My first attempt failed. It uses the same XA and XB from Guavanaut, but with a changed XC:
Code: Select all
LINK 800
LINK 799
GRAB 212
MARK LOOP
COPY M X
SEEK 999
SEEK -1
MARK LOOP2
TEST F = X
SEEK -2
FJMP LOOP2
SEEK 1
COPY M F
SEEK -999
JUMP LOOP
You see the problem here? SEEK -2; SEEK 1 causes an off-by-one if the keyword is the very first word in the file. New attempt with a workaround:
Code: Select all
LINK 800
LINK 799
GRAB 212
MARK LOOP
COPY M X
TEST F = X
TJMP FIRST
SEEK 999
SEEK -1
MARK LOOP2
TEST F = X
SEEK -2
FJMP LOOP2
SEEK 1
COPY M F
SEEK -999
JUMP LOOP
MARK FIRST
SEEK -1
COPY M F
JUMP LOOP
504/41/7. Perhaps this could be optimized more but I'm quite happy I found this at all.
Of course! It's the exact same as the very first tutorial assignment. I guess that's how Ember found us a way in, but if we don't solve the tutorial, Ghast might get suspicious.megane wrote:The joke is that this is the first tutorial mission network, so in addition to messing with Ghast's email you have to complete the now-hilariously-trivial tutorial task.
=== Hacker battle at KGOG-TV ===
You didn't look at any other files while you were in there, did you?
Everyone voted for "I might have". Very honest.
Oops!
Maybe don't tell him you accidentally saw some of his private files.
No reason to upset him.
He needs to focus on the next issue of the zine anyway.
Yeah, ok.
Speaking of the next zine, I got it right here.
OST: Apartment
Nice cover:
I'm assuming the physical version came with actual 3D glasses.
It's completely filled with cool content again. I don't think Ghast's the best hacker out there - but he's a damn good editor. Also, I can't believe it's been only one month since the first issue. Feels like three months at least.
Anyway, we'll get to the hacking writeups when we need them. As for the other content, I'll be sharing that bit by bit again just to not overwhelm you with text. Let's start with Ghast's letter.
I'd love to stay curious, but I'm just trying to survive here, man.
Let's see what's going on online.
Well, mutex is challenging us to a "hacker battle". Can't say no to that.
Hacker battles have a different kind of leader board. They show who you've beaten and when. Apparently this is stored in the Steam cloud separately from your regular save file because it has a date corresponding to my initial playthrough (and I'm not using that save). Other than mutex8021, it shows my steam friends. I removed those from the pic for privacy reasons.
You're going to join in on a hacker battle?
But you never say anything in the chat room...
Two votes for "I just lurk", one for "EXAs speak louder".
I just lurk these days.
I guess the only thing that really matters is your ability.
As it should be.
It's a nice ideal.
Oh hey cool, Ember is all 3D with these glasses Ghast gave me.
Right, time to find out what this is all about.
OST: Getting Started
This is a completely different mode of the EXODUS simulator. There's some things to go through. Let's start with some info from the zine.
The left side looks very familiar. Some files and the EXA programming environment.
On the right we see some new things. The dark/blue host is our opponent's. We can never go there, but they can link from it to the network we're hacking.
At the bottom we see there's 100 test runs, like normal. New is the win count: this is increased by one for every test run where you get more points than your opponent. The goal is to get a win count that's greater than half of the number of test runs.
Next, the number of cycles is limited now - that's how long the program will run for during any test run before the points are added up. The size count is limited as always.
To the far right you see the points you and your opponent have and the storage limit. The storage limit is the number of EXAs you're allowed to create. It's 3 here - that means I can't have more than 3 EXAs to start with, and if my code REPLs to more than 3, the REPL instruction will pause that EXA until there's space again. As for how to get points, we need to read the instructions:
To win this battle you must make your movies play for longer than your opponent's. A movie will play when that movie's file is the only movie file sitting in a channel host.
- Gain one point every cycle for each of your movies that is playing (files 210 and 211).
- Lose one point every cycle for a movie that isn't yours (files 230, 231, 265) is held by an EXA you control or is sitting in your host.
- Lose one point every time one of your EXAs executes a KILL instruction.
Note that you may only battle your Steam friends after beating the NPC opponent. To view the list of possible opponents, click the "SELECT OPPONENT" button above.
For more information see "Hacker Battle Domination" in the second issue of the zine.
Alright, so I can use KILL instructions to kill the opponent's EXAs but it will cost me points.
Hey game, stop breaking the fourth wall. I'll get to Steam friends battles in a bit. Let's start with mutex.
If we don't do anything, mutex uses two EXAs to move 230 and 231 to the channels and they get 2 points per cycle (since file 265 doesn't give anyone points).
In case you're wondering, 265 is a movie of a bug moving next to a plant, 230 looks like some military device shooting missiles, and 231 is a mechanical monster walking throuch a city.
To prevent mutex from getting points, I could either kill their EXAs or grab their files. Since holding files that aren't mine costs points, and I can't beat mutex's first EXA to grabbing a file, I'm better off just killing them. I have to execute the KILL instructions while I'm in the same host as the opponent's EXAs. There can't be any other EXAs of mine there, because as the language reference guide in zine 1 says, for some reason KILL prioritizes your own EXAs.
So, let's start with this simple solution.
XA kills both of mutex's EXAs (giving me negative 2 points), then moves one of my files into an empty channel host. XB waits for the kills, then moves the other file over. As soon as both EXAs drop the files they start playing, and I get 2 points per cycle.
My movies are some swaying grass with figures in the background, and a Japanese looking scene with snow falling.
So you might think it's a good idea to get rid of file 265 too, or dump another movie in that host to cause a conflict. Turns out it isn't. Grabbing 265 or another opponent's file costs points. And the instructions for this battle are actually incorrect. You don't lose any points for having someone else's movie playing, the only thing that happens is that the opponent GAINS points if they have their movies playing. Since 265 is nobody's, no-one is gaining any points for it and I should just leave it alone.
This solution nets me 188 points for every test run, while mutex gets zero. By the way, the other test runs are almost identical. All that changes it the number in the third entry of each file, and the locations of mutex's and my host change around (while keeping the link IDs the same). I suspect that last change swaps who moves first within a cycle, but it doesn't matter for us.
I win every single test run, getting an S+ score on this battle.
This first hacker battle is very easy. It's more of a tutorial for a new mode than anything else.
As for the multiplayer mode, you're not battling at the same time. What happens is that after I beat the NPC, my solution becomes available for Steam friends to battle against. That means their goal becomes to specifically beat my solution. If you noticed - the battle field is basically symmetric, the only difference is some file IDs but the game can just replace them when simulating someone being "the other side".
I tried to pit my solution against someone else's and I lost every test run. It's hard to say what they did exactly - you can't see the opponent's code. But they use three EXAs, one of which tries to kill mine and another which constantly replicates, moving the repls into the channel hosts.
The way to handle this is to build another solution to specifically fight theirs. For instance, I changed XA to kill just once (it gets killed by an opponent in the same cycle) and then sent in a second EXA that aggressively kills both of the remaining opponent's EXAs.
I managed to secure a win, 76 out of 100 test runs. Good enough for a B. I can't really tell you why 'only' 76 runs, the interaction of two sets of separately programmed EXAs gets very unpredictable.
Either way, by doing so my new solution is uploaded to this specific Steam friend, so if they decide to ever reopen the game and scroll to this particular assignment, they can see I beat them and they can try to make a new solution to beat mine. I don't have much experience with this because the few Steam friends I have that also own this game apparently were happy to play through it just once, but I can imagine this way you could get a fun back-and-forth on these hacker battle levels.
Back to the plot.
You're off to a good start.
I knew you were good at what you did.
Even after you forgot, you picked it right back up.
That's why I contacted you.
The first vote for today.
For the second vote, Ember has a question about the Redshift handheld Ghast handed us.
Why do you think Ghast gave you this?
[Ghast] let's just say I'm pretty confident.
[x10x10x] cool im gonna hacker battle moss too
Oh... um, I don't think I ever introduced myself properly to you all, readers. Not that you would've believed it was really me, seeing how much I had to relearn at the start. I'm the hacker known as Moss. Nice to make your acquaintance. You've seen my name in the chat's user list, I just lurk all the time.
This is a really interesting mode that you can do some fun with I imagine, competing into increasingly complex and esoteric counter-solutions seems like a wild trip, and then at some point someone undermines it all with regressing to basic solutions, like a really basic counter to your solution for your friend you mentioned would be to just wait a few cycles, the Exas would KILL themselves and you could walk in freely
As for replies: "But what do you really want?" and "I'm not sure"
Saying Ghast thought we might like playing with it feels naive, in a certain sense it might be correct, but I don't think he handed it over as entertainment, but rather as a tool or experiment
=== Trash World Inbox ===
Huh. reading back, you're right about the instructions, my bad.Cloudmonkey98 wrote: At no point did the instructions say you lost points for your opponent's movie playing, just for you touching them, or trying to hide them in your Host room, since the enemy can't access that area, the instructions aren't incorrect
This is a really interesting mode that you can do some fun with I imagine, competing into increasingly complex and esoteric counter-solutions seems like a wild trip, and then at some point someone undermines it all with regressing to basic solutions, like a really basic counter to your solution for your friend you mentioned would be to just wait a few cycles, the Exas would KILL themselves and you could walk in freely
As for the hacker battles, I agree with GuavaMoment here:
GuavaMoment wrote: Hacker battles are a neat idea, buh ehh.....it's seemingly impossible to make a robust solution. It's like trying to optimize rock paper scissors, you're always going to be vulnerable to one tactic when using another.
Neat! I have the digital copy of Zach-Like. It's free, and it contains all sorts of behind-the-scenes information of Zachtronics game design and even some early games and prototypes.Nth Doctor wrote: They're here!
Since it doesn't really make sense to talk about optimizations for the hacker battles, let's jump straight into the new update.
=== TEC Redshift - Development kit ===
You're off to a good start.
I knew you were good at what you did.
Even after you forgot, you picked it right back up.
That's why I contacted you.
Three votes for the second option.
But what do you really want?
All I've wanted is for you to do a little hacking for me.
That's it. Really.
Well, that doesn't explain anything.
Leafing through the zine I found the letters page.
Some interesting stuff there. First of all, heh, the letter Ember made me edit actually got printed in the zine. The original writer is in for a bit of a shock. Also, the tip about the KGOG tv station is probably where mutex got the number for the modem through which we did our hacker battle.
I also like the reference to "red boxes" and "blue boxes". They were real things. Back in the day phone exchanges were controlled through specific tones (nowadays it's usually a digital signal of some kind). In the USA, red boxes could be used to make an exchange think you put a coin in a pay phone, so it would let you place a call for free. Blue boxes could be used to place long-distance calls and bill them to some other number.
Anyway, I got this developer Redshift kit from Ghast, let's see if I can do anything with it.
Why do you think Ghast gave you this?
Two out of three votes for "I'm not sure".
I'm not sure.
Does he get something out of it?
I suppose he enjoys helping people.
Such a nice guy.
Too bad the dev kit is password protected.
He should have thought of that.
Better not let that stop you...
Yeah, no worries Ember, we'll just jailbreak this thing real quick.
There's a whole section about the Redshift in the zine, but most of it is about how to develop games for it. We're not quite there yet, but here's the frst page just so you have an idea of what we're dealing with.
The small print under the ad says: "Batteries not included; 3D mode not intended for use more than 15 minutes at a time; EXA is a registered trademark of Axiom, Inc. Used with permission."
An early attempt at a 3D handheld where using the 3D mode for too long would cause problems? This thing seems to be some sort of mix of a Nintendo Game Boy and a Virtual Boy.
OST: Code and Registers
No files or anything in this thing. Our assignment simply says:
There is an unknown three-digit code (such as 4-7-3) that, when entered one digit at a time into #PASS, will unlock the link between debug and secret. Find the three-digit code and create a file in your host that contains the code as a sequence of three values, followed by the development kit's RDK ID.
Okay, simple brute-forcing it is.
Entering some numbers into #PASS makes them appear on the little display in the secret host. If the code is wrong nothing will happen, and the fourth digit is simply the start of a new attempt. Any negative numbers or multi-digit values are ignored by #PASS.
Well, we got an instruction to grab specific digits from a number, why not use that?
Code: Select all
LINK 800
MARK TOP
SWIZ X 1 #PASS
SWIZ X 2 #PASS
SWIZ X 3 #PASS
ADDI X 1 X
JUMP TOP
Since the #PASS expects digits from left to right while this takes the input from right to left, I actually enter the numbers in reverse. It shouldn't matter, it will try all numbers anyway, just in a different order. But since I need to store the password I'll swap the SWIZ calls around so I get the right password in X.
This unlocks the secret host, but this EXA keeps going forever. The #PASS register keeps accepting digits even after entering the right password.
I need to somehow stop this EXA at the right time, save its password, and go get that id from the secret host. Let's first see what we can find in the secret host, now that I unlocked it.
The link id between the debug and secret hosts is simply 800 again. There's four files in there: two core dumps, some game's save file, and a file containing the RDK id.
This is my first working solution. After some trial and error I decided that while a multi-EXA solution might be faster, it's gonna be hard to get the passcode out of the initial EXA at the right time. So I went for a single-EXA solution. Every loop, it tries to send a replica into the secret host. If the link is closed, the replica dies immediately.
Otherwise it gets the ID from the file. Then it needs to explicitly DROP the file (if you LINK an EXA holding a file with a lock icon it dies).
It KILLs twice (because by the time it gets back, the original had just enough time to execute a new REPL instruction), then goes back to the home host to write the file (which contains each digit of the password and then the ID).
This runs quite slow at 5959/22/11.
I tried to output it as a gif, but since it shows all digits being entered on the password display, the result is over 10MB and over 2 minutes. It also crashed my game when I tried to go back to editing afterwards. Don't try this at home.
Anyway, getting the activity down should be a question of minimizing LINK and KILL instructions.
We don't need to LINK back home with an EXA that waits for M signals:
Code: Select all
;XB
MAKE
COPY M F
COPY M F
COPY M F
COPY M F
Code: Select all
;XA
LINK 800
MARK TOP
TEST MRD
TJMP END
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 199
SWIZ X 3 M
SWIZ X 2 M
SWIZ X 1 M
COPY F M
MARK END
Next, let's look at cycle count. As I already said, if you swap around the SWIZ instructions, you basically change the order in which you test the passwords. Since I use the same SWIZ to write the data to the file I can easily try all permutations. Turns out 1,3,2 is the fastest at 5821 cycles, as compared to the 5959 solution.
But that's just a minor optimization. I'll need to do much better.
A basic loop unroll gets me to 5020/47/11.
Code: Select all
LINK 800
MARK TOP
@REP 6
SWIZ X 1 #PASS
SWIZ X 3 #PASS
SWIZ X 2 #PASS
REPL TRY
ADDI X 1 X
@END
JUMP TOP
MARK TRY
LINK 800
GRAB 199
COPY F T
DROP
LINK -1
KILL
KILL
LINK -1
MAKE
SWIZ X 1 F
SWIZ X 3 F
SWIZ X 2 F
COPY T F
It took some fiddling but this solution runs at 3405/50/17.
It still uses the ADDI/SWIZ combo so each EXA keeps a full counter. I could have one EXA just store the tens digit and one just store the hundreds digit but I don't think that would matter much since that would speed up those EXAs... for them to be limited by the one having to increment the ones digit every loop.
The EXA that makes it to the secret host immediately sends a REPL back to go KILL its buddies because the TRY REPLs add up fast now.
In fact, I just barely managed to squeeze out a single loop unroll. The loop unroll makes the REPL trigger even faster so I needed an extra KILL, which I managed to fit in by giving XC only one starting NOOP and hoping it would LINK after XB, which it did. I also needed an extra SUBI instruction because for some reason the ones digit is off by one now.
By the way, there's something cathartic about typing "KILL KILL KILL".
To make this even faster, well, as I said at least one EXA needs an increment instruction in its loop so it will act as the bottle neck. The only thing I can still remove from that loop is the REPL TRY:
Code: Select all
;XA
LINK 800
MARK TOP
SWIZ X 1 #PASS
ADDI X 1 X
JUMP TOP
;XB
NOOP
LINK 800
MARK TOP
SWIZ X 3 #PASS
ADDI X 1 X
JUMP TOP
;XC
NOOP
NOOP
LINK 800
MARK TOP
SWIZ X 2 #PASS
ADDI X 1 X
JUMP TOP
;XD
NOOP
NOOP
NOOP
LINK 800
MARK TOP
REPL TRY
ADDI X 1 X
JUMP TOP
MARK TRY
LINK 800
GRAB 199
REPL OT
SWIZ X 1 M
SWIZ X 3 M
SWIZ X 2 M
COPY F M
HALT
MARK OT
LINK -1
KILL
KILL
KILL
KILL
KILL
KILL
;XE
MAKE
COPY M F
COPY M F
COPY M F
COPY M F
So Ghast used to work at a game studio?
I'll skip this vote because all three answers lead to the same follow-up question.
Yeah, he was a programmer. It says so in the zine.
I wonder what that was like.
This one is for the thread.
Next time, we explore the Redshift dev kit. Ember would like to know why.
You're really going to make a game with this?
Why?
I'm not offering a reward for it.