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

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

Hello and welcome to this Screenshot Let's Play of 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 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

Carbon dioxide fucked around with this message at 20:09 on Dec 16, 2022

Adbot
ADBOT LOVES YOU

Carbon dioxide
Oct 9, 2012

EXA language reference guide




(click to enlarge)

Carbon dioxide fucked around with this message at 17:26 on Dec 18, 2021

Carbon dioxide
Oct 9, 2012

Part 1 - Old friends and new issues



As we start the game and the loading screen pops up we immediately get blasted by this jam.
OST: Exapunks :siren: LISTEN to this. Seriously. :siren:



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.


(click to enlarge)


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 gently caress 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 gently caress

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.



(click to enlarge)

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.

Carbon dioxide fucked around with this message at 17:08 on Apr 9, 2022

Carbon dioxide
Oct 9, 2012

Part 2 - TRASH WORLD NEWS - Tutorial 1

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? drat, 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.

Carbon dioxide fucked around with this message at 17:11 on Apr 9, 2022

Carbon dioxide
Oct 9, 2012

Quackles posted:

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.

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

Carbon dioxide
Oct 9, 2012

Part 3 - TRASH WORLD NEWS - Tutorial 2

Let's start with some comments from the threads.

Quackles posted:

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

GuavaMoment posted:

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.

No, I only have the Steam version. I have beaten story mode before, but not the postgame stuff.

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.

Carbon dioxide fucked around with this message at 17:49 on Dec 4, 2021

Carbon dioxide
Oct 9, 2012

Part 4 - TRASH WORLD NEWS - Tutorial 3

megane posted:

A way to save a cycle (without using any new operations) is SUBI X F F.
Of course. :doh:



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.

Carbon dioxide
Oct 9, 2012

Part 5 - TRASH WORLD NEWS - Tutorial 4

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.

Carbon dioxide fucked around with this message at 17:43 on Dec 18, 2021

Carbon dioxide
Oct 9, 2012

EXA Language Reference Guide




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

Carbon dioxide fucked around with this message at 17:48 on Dec 18, 2021

Carbon dioxide
Oct 9, 2012

idhrendur posted:

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.

Good point!

I specifically didn't mention yet 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".

In this solution


Changing XB from what it is to:

code:
LINK 800
LINK 800
MAKE

COPY M T

MARK LOOPSTART
COPY T F
SUBI T 1 T
TJMP LOOPSTART
COPY T F
This drops the cycle count to 304 cycles.

Note that that final copy is necessary to get the final zero in the file.

Doing the same with the single EXA solution gets us to 305 cycles btw.

The size is still 11, and according to the histograms, 10 is possible, and so is a sub-200 cycle account. If anyone has any thoughts on how to get there, please share them.

Carbon dioxide
Oct 9, 2012

Very nice stuff!

What I'm gonna do is discuss your solutions in my next LP update. I'll clearly section it off from the new puzzle so that people who are mostly here for the plot can just skip over that while we can nerd out over optimizations.

Carbon dioxide
Oct 9, 2012

Part 6 - Euclid's Pizza

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.

idhrendur posted:

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

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.

silentsnack posted:

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 exa
code:
LINK 800
GRAB 200
LINK 800
ADDI F 1 T
WIPE
MAKE
MARK WATT
SUBI T 1 T
COPY T F
TJMP WATT
Size 10
Cycles 307
Activity 2
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.

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.

silentsnack posted:

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)
code:
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
Cycles: 212
Size: 24
Activity 2
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.

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 posted:

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:

silentsnack posted:

:doh: 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 batchsize
code:
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
Cycles 143
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.
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.
In other words, this unrolls into
code:
SUBI T 1 F
SUBI T 2 F
SUBI T 3 F
And so on.

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:

Quackles posted:

OK, so I was inspired by silentsnack's trick with the @{1,1} and made my version which uses a similar trick.

code:
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 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.

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

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 :siren: 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.

Carbon dioxide fucked around with this message at 16:30 on Dec 24, 2021

Carbon dioxide
Oct 9, 2012

Part 7 - Mitsuzen HDI-10


=== Trash World Inbox ===

GuavaMoment posted:

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

Cloudmonkey98 posted:

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

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 :siren: 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.

Carbon dioxide
Oct 9, 2012

Part 8 - Peanut-free Peanut bars

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:

GuavaMoment posted:

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

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

Quackles posted:

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:
;TRANSMITTER SETUP
LINK 800

MARK LOOP
ADDI #NERV 35 X
MULI X 117 M
JUMP LOOP
code:
;RECEIVER SETUP
LINK 800
LINK 1
LINK 1
LINK 1
LINK 1

MARK LOOP
DIVI M 117 X
SUBI X 35 #NERV
JUMP LOOP
124 / 14 / 6.

(you could probably get better efficiency at expense of size by unrolling the main loop some, but I'm happy with this)
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.

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.

silentsnack posted:

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:

code:
;====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
38 cycles / 29 size / 137 activity.

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:
Wow. ;WTF HAX is right.

I'm going to do some line by line commentary, both for my own understanding and for any readers.

code:
;====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
Ignoring the REPL for a moment, what this does it simple: it stores the two first values from #NERV into X and T, moves itself to the outgoing #NERV, and sends those two values out again. This could only work if the first two values never pass the -120 / 50 limits - but apparently that's the case in this test set.

code:
MARK OUT
@REP 2
SUBI M 120 #NERV
@END
JUMP OUT
This, together with the REPL OUT, makes sure there are two EXAs in the host containing the outgoing node. They're doing nothing but waiting for messages on M, subtracting 120 from them, and sending them to #NERV. With two EXAs doing this at the same time, you don't watch any cycles on waiting for M (the same issue as in GuavaMoment's pizza solution).
Now, the question is, where do those Ms come from and why do they need 120 subtracted?

code:
;====XB====
NOOP
MARK LOOP
REPL LOOP
Wait an initial cycle (probably to give XA time to set itself up), then get into a loop type we haven't seen before: the newly created EXA immediately creates a clone of itself that creates a clone of itself and so on. After each EXA created its clone, it goes further down the code.

code:
LINK 800
ADDI #NERV 120 X; ???
LINK 1
DIVI X 170 T;WTF HAX
LINK 1
FJMP BANDPASS
The first thing I'm noticing are the LINK instructions intermingled with the code. The XBs are slowly moving across the nodes. Even though they never make it to the arm #NERV, this is necessary because with this much cloning, the hosts are going to fill up fast. By intermingling them like this, there's (usually) not two clones trying to traverse the same LINKs at once. That would be horrible - because then it's unpredictable which one is waiting and the results would become unpredictable.

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:
TEST X > 0
MULI T 170 M
HALT
Alright, so now you test if X is positive. If so, we send 1 * 170 to M, otherwise -1 * 170. We already know that XA subtracts 120, so in case of 170, it writes 50, otherwise it writes -120. Clamping done.

code:
MARK BANDPASS
LINK 1
COPY X M
Otherwise it goes to the next host for some peace and quiet and copies the value of X to M. The 120 that was added at the start is removed by XA.

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.

silentsnack posted:

code:
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
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.

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.

silentsnack posted:

code:
;====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
Now down to 36 cycles / 15 size / 73 activity.
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.

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 :siren: 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.

Carbon dioxide
Oct 9, 2012

I discovered that not only does one of the original RL Zines still exist, it occasionally (every 5 years or so) releases a new issue.

Phrack issue 70, released October 2021.

Carbon dioxide
Oct 9, 2012

Part 9 - Point of Sale System


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

GuavaMoment posted:

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
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:
code:
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
Repeat the test exactly 10 times. If it falls through all of that you know you're in the worst-case test and you can just immediately delete the value at the file pointer and halt the EXA. 27 cycles.

For the second half:

code:
; 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
As you say, 26 cycles. The tricky thing here is making sensible choices to stay under the size limit. It is important to unroll the loop in XA. That way the slowest EXA in the communication will be XB, which has to run an extra TJMP for each cycle. Having a loop in XA would slow both of them down. This means you do not have enough lines left to do any loop unrolling in XC - but that's perfectly fine because XC's only purpose is waiting a set number of cycles. In my solution, each loop in XC takes 3 cycles, so counting up to 7 is perfect. If I had needed some number of cycles not divisible by 7 I would just have slapped some extra NOOPs at the end.

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:
;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
Now XA sends Ghast's ID twice. Once XC gets its copy, it temporarily stores it in X, then waits for the date, copies that and the ID to the log file, and then only has to wait for the amount from XB, meaning it can handle more steps before XB is done. 58 cycles.

We already know we can improve Activity with some REPLs:

code:
;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
Exactly the same as before, except the LOGAPPENDER is now created by XB, removing the need for it to LINK. Result: 58/32/2
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.

Carbon dioxide
Oct 9, 2012

berryjon posted:

I can't make heads or tails of programming, so I'm just along for the story.

That's great! I hope my decision to keep the inbox section separate from the main story updates is helpful to you. And if there's anything else I can do to make it more accessible to as much people as possible I'd like to hear it.

Carbon dioxide
Oct 9, 2012

Part 10 - SFCTA Highway Sign


=== Trash World Inbox ===

silentsnack posted:

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

code:
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
Brings it own to 99 cycles / 23 size / 3 activity
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 posted:

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 faster
code:
;====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
57/48/4 which is a slight improvement over 58 cycles.
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 posted:

And now for the same solution but a couple of lines changed to use a slightly different routine to parse the file:

code:
;====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...]
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.
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.

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.

GuavaMoment posted:

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:
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
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.
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 :siren: 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:
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
Just check if X is 26, because at that time the sign is full and we're done. Then jump out of the loop and WIPE the file. It works - but it does add two cycles to every loop.





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:
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
166/10/1

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:
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
118/42/1

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:
MARK LOOP
NOOP
COPY F #DATA
JUMP LOOP
Except... not quite. Since the other EXA uses two additional cycles every row (to TEST X = 27 and jump back or not), this new EXA needs to do so as well or it'll get out of sync. I tried also unrolling this loop 9 times, but it won't fit.

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:
MARK LOOP
COPY F #DATA
SUBI T 1 T
TJMP LOOP
Still three cycles (MARK doesn't take a cycle), but the NOOP became a SUBI on the T register. Replace the unconditional JUMP with a TJMP and it'll leave the loop as soon as T hits 0.

And the two additional cycles at the end of each row are just barely enough to make this complete solution work:
code:
; 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
This is quite optimized:
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.

Carbon dioxide
Oct 9, 2012

GuavaMoment posted:

Also here you can just test for EOF instead of X, then change TJMP DONE to FJMP LOOP. Remove the ADDI and JUMP lines to get a 10 line solution.

Your suggestion is basically equivalent to the 10-line solution I show in my update, right?

Carbon dioxide
Oct 9, 2012

Part 11 - Лрикладной Семиотики


=== Trash World Inbox ===

Talisriel posted:

I did have another method to show for the achievement on Zebros:
code:
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
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.
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 posted:

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

As you said, that'll cost a bit of time, but I can point out a couple other optimizations to your code:
code:
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
Since you need the result of the SWIZ operations only once, you can send it directly to #DATA instead of using T as an intermediate. I also removed the COPY 0 T since that value would be overwritten a couple lines later. 276/17/1

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.

GuavaMoment posted:

You can have one exa feeding in the row data:
code:
LINK 800
MARK LOOP
DIVI X 9 #DATA
ADDI 1 X X
JUMP LOOP
One feeding in the column data:
code:
NOOP
LINK 800
MARK LOOP
MODI X 9 #DATA
ADDI 1 X X
JUMP LOOP
One feeding in data from the file
code:
GRAB 300
NOOP
LINK 800
MARK LOOP
COPY F #DATA
TEST EOF
FJMP LOOP
WIPE
and one to kill everything at the end
code:
NOOP
NOOP
NOOP
LINK 800
COPY 25 X
MARK LOOP
TEST X = 0
SUBI X 1 X
FJMP LOOP
KILL
KILL
86/30/6, still one cycle off top percentage though...
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.

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. :negative: .

Let's just pretend that never happened.

silentsnack posted:

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)
code:
;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
84/26/4

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.
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 posted:

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

code:
;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
83/41/29

...which turns out to be an even cruder approach, forgoing loops in favor of recursion and bruteforce @REP spam
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.

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 :siren: 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:
LINK 800
LINK 801
LINK 801
LINK 801
KILL
GRAB 276
LINK -1
LINK -1
LINK -1
LINK -1
This is too simple, of course. It works for the initial test run, but the 'enemy EXA' is going to be in a different one of those 8 top-right hosts every test run.
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:
MARK RETURN
LINK -1
JUMP RETURN
34/15/27.
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:
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
This simply repeats the 800/801 choice three times. Each EXA that doesn't "choose" the REPL then has to jump to the next choice, ignoring the REPL's LINK instruction. It's quite ugly and it feels like we should be able to do (much) better, yet the new score is 18/25/27, which corresponds to that big peak to the left of the cycle histogram.

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.

Carbon dioxide fucked around with this message at 14:58 on Jan 30, 2022

Carbon dioxide
Oct 9, 2012

Part 12 - UC Berkeley EECS


=== Trash World Inbox ===

silentsnack has some improvements for last week's puzzle.

silentsnack posted:

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.
code:
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
40/14/27
Reduces it to 14 from my 15. The trickiest part is getting the right way to rearrange the code, I guess.

silentsnack posted:

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 spaghetti
code:
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
[...]
15/46/27
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.


=== 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:
SEEK 124

MARK FINDENTRY
SEEK 2
TEST F = X
FJMP FINDENTRY
SEEK 2 is necessary for every cycle (to go to the next entry's name), and it's best to have it at the start to prevent some awkwardness when the EXA finds the right entry. Having the initial SEEK only go to 124 allows me to do this without too much overhead.



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:
MARK SENDLOOP
COPY F M
SUBI X 1 X
TEST X = 0
FJMP SENDLOOP
It automatically stops when it's done.

And I made XB that simply does this:
code:
MAKE

MARK LOOP
COPY M F
JUMP LOOP
Getting close to a solution. The only problem is that XB never halts.

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:
;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
124/33/7. As it happens 0 isn't used as a valid number either so I can use that as the end-of-message packet. And I also used T for the for loop in XA now.

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:
;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
96/61/7, cycle count already better than the top percentile somehow. But I still have some more ideas.

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:
LINK 800
COPY M X

MARK FINDTAPE
LINK 800
LINK 800

REPL FINDTAPE

HOST T
TEST X = T
FJMP FINDENTRY

; TAPE HANDLING
This way, the new EXA is already LINKing to the next tape node while this one is still checking. I reuse the FINDENTRY mark here - the first command after it is a SEEK that requires the EXA to hold a file; so it quickly makes the EXA crash.

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

.

.

.

.

.

.

Part 12.5 - ПАСЬЯНС

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 :siren: 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.

Carbon dioxide fucked around with this message at 20:07 on Dec 16, 2022

Carbon dioxide
Oct 9, 2012

Edit: merged with previous post for archival purposes

Carbon dioxide fucked around with this message at 20:07 on Dec 16, 2022

Carbon dioxide
Oct 9, 2012

Part 13 - Workhouse Work Management System


=== Trash World Inbox ===

Cloudmonkey98 posted:

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...
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 posted:

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

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.

GuavaMoment posted:

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/7
code:
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
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.

silentsnack posted:

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.
code:
;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
74/73/11

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.
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 posted:

For reducing size further, dividing by zero is an efficient way to crash an EXA. As is grabbing a nonexistent file.
code:
;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
420/27/9
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.

azsedcf posted:

Can you pass/fail a entire class by editing file 243?
Well, maybe you can. But the game doesn't acknowledge this in any way, other than failing the "Leave no trace" rule.


=== 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:
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
By dividing X by 75, we get how often 75 fits in X, and we can just put that in T and use a countdown. Saves a TEST instruction every cycle. For the end, just use the MODI function to get the remainder quickly.

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:
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
Before entering FILLFILE, the EXA has T set to 1 (from the last EOF test). The first loop's SUBI makes it 0, false, and the second makes it -1, true. This is a quick way of going through the loop just twice without requiring extra setup of the T register. 275/71/3.

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:
; 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
After the FILLFILE unroll is done, XC replicates itself into a KILLER clone. Since that one's job, at first, is just to count down, it has plenty of time to calculate the starting value of T, as long as the number in SUBI T 52 T is adjusted so it starts at exactly the right cycle count. After the killer kills the EXA that writes 75, it can grab the file (XB has been waiting all that time to send the ID of the file just once more), and adds the remainder.

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:
COPY 3 T

MARK FILLFILE
@REP 19
COPY 75 F
@END
SUBI T 1 T
TJMP FILLFILE

COPY 75 F
and change the SUBI of the killer to 60. I had to go for @REP 19 instead of 20, because the countdown always runs at least once and for the test where there's exactly 61 * 75, it would start at zero and go into the negatives, getting stuck into an infinite loop. The limit where this still barely works is at 3 * 19 + 1 hardcoded 75s, hence the lone COPY 75 T to save one more cycle. 230/71/4.

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?


Carbon dioxide fucked around with this message at 16:56 on Feb 12, 2022

Carbon dioxide
Oct 9, 2012

Part 14 - Equity First Bank


=== Trash World Inbox ===

GuavaMoment posted:

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

silentsnack posted:

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.
code:
;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
But somehow the stats are showing 195/75/3
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.


=== 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:
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
A very simple loop with a countdown so it knows when the ATM runs out of cash.

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:
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
I put the countdown in the CLONE loop so I can remove the cleanup EXA. Result: 3028/16/10.

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:
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
I started from the low-size solution since it fits some more unrolling. Simple enough: if there's still more than 33 bills left, spit out 33, otherwise count them one by one. 1145/50/10. Much faster already. Also, turns out that combining this with the cleanup EXA solution drops the cycle count to 1144, even though there's only space for 27 unrolls.

From that solution we can drop it to 1125/50/11, using this code:
code:
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
The main change is that I got rid of the HALT instruction by stopping the loop using a divide by zero crash. This lets me bump the unroll to 28 which is enough to save a bunch more cycles.

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:
MARK CLEANUP
SUBI X 1 X
TEST X < -6
FJMP CLEANUP
KILL
saves a line of code, allowing one more COPY in the unroll, dropping the cycles from 1125 to 1122.

Finally, I found one more minor improvement, but it's a bit of a weird one.
code:
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
1120/50/11

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.

Carbon dioxide
Oct 9, 2012

Part 15 - Mitsuzen HDI-10 - Heart


=== Trash World Inbox ===

GuavaMoment posted:

So first off you can get a 12 line, 10 activity solution with this by combining some tricks:
code:
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
Yeah, you create 807 exas, but it works! MODI -1 0 X kills the exa.
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).

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.

Moto42 posted:

So, yea, it is faster to start with 7 EXAs, but each one can only have 7 lines of code.
Yeah, that's why I never really considered that. 7 lines is not enough for any other optimizations, that have more of an effect.

Quackles posted:

Here's my desultory best effort, with loop unrolling in a similar way:
code:
@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
1146/50/10.
Interesting idea to handle the "remainder" first. I tried if I could get any more optimizations out of this design but I couldn't.

GuavaMoment posted:

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/10
code:
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
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.

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!



drat, 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:
LINK 800
REPL SA
REPL AV

HALT

MARK SA
LINK 1
LINK 1

HALT

MARK AV
LINK 3
LINK 3
I'll just start with a single EXA that clones itself, to have the lowest activity score out of the way.

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:
LINK 800
REPL SA
REPL AV

MARK INLOOP
DIVI #NERV -10 X
COPY X M
COPY X M
JUMP INLOOP
Since I can read from the #NERV only once (after that it will output the next value), I use X as an intermediate and divide by -10 while I'm at it.

Both of the other EXAs have to read from M and write the values to their nerve connections:
code:
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
I assume that every input corresponds to at least two writes to the outputs (otherwise you can't get a proper heartbeat). That's why I can put the special stuff (writing the 40) outside the loop, as long as I make sure to subtract the appropriate value from my counter too.

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:
@REP 4
COPY -70 #NERV
SUBI T 1 T
FJMP SALOOP
@END
and also for AV. Or unroll the big SALOOP/AVLOOP. Can't use @REP there because the MARK names need to be different for each duplicate, but a manual unroll still works. Anyway, best I got with either attempt was 86 cycles. This ain't it.

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:
;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
XB simply fills up all the hosts with clones until there's only one free position and then gets into an infinite waiting loop. XA reads a value from the input, parses it, then clones itself and sends the clones off to the output. Since there's only one free space per host they'll just queue up in order, and LINK to the next host the first available cycle.
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:
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
131/25/29. The original EXA won't jump to NEXT until it reads from M twice, which means both clones finished. I can't replace the two VOID M's with something like SUBI M M X, the game specifically disallows reading from M twice in the same instruction. The top percentile value is 24 so further improvement is possible but I'm not sure what.

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.

Carbon dioxide
Oct 9, 2012

Part 16 - TRASH WORLD NEWS - Unknown Context


=== Trash World Inbox ===

GuavaMoment posted:

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.

code:
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
My low cycle solution was much worse than what you did.

Late edit: As pointed out a few posts below, change REPL SAN and JUMP COPY lines with REPL COPY to save one more line.
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.

silentsnack posted:

You can use some fallthrough structure to reduce size too
code:
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
119/22/41
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 posted:

And to reduce time, the usual parallel countdown/kill shenanigans can work
code:
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
63/32/31
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 posted:

...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:
code:
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
62/50/45
:psyduck: 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.

berryjon posted:

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.
Yes, that seems to be the case.


=== 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:
LINK 800
REPL OUTBOX
LINK 799
GRAB 212
[...]

MARK OUTBOX
GRAB 200
LINK 800
The easiest way I can think of to handle this is to search through the file once for every word I need to replace. Sure, it's slow, but it should work.



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:
;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
A simple solution is to have XB send a final control message. That means XA has to test for it. If it finds the control message it jumps to the outbox MARK - and will then die because it cannot grab another file while it's still holding one. Success!



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:
;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
This improves the cycles and size: 555/22/3

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:
;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
Since the TEST MRD now happens a single cycle after COPY M F, there can't be a JUMP between them in XB. A bit of reordering in XB solved that. I also changed the jump to END by a divide by zero. It doesn't save any cycles but it looks nicer. 545/32/4.

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:
;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
Basically, the inner LOOP tests if there's something on M every other cycle. Whenever XA reads from M, there's a cycle when there's nothing to read and TEST MRD is false. These cycles line up once for every word to replace, so I keep count in X. Once it hits five, the EXA goes to another TEST MRD loop, because now, when it's false, XA is done and needs to be killed as quickly as possible. We could do that from the original loop but the ADDI and TEST would delay the KILL. The single NOOP saves a cycle by making sure M goes empty just when this EXA is about to test (instead of when it's about to jump back to LOOP2.

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


Carbon dioxide fucked around with this message at 11:04 on Apr 9, 2022

Carbon dioxide
Oct 9, 2012

Part 17 - Baby's first hacker battle


=== Trash World Inbox ===

GuavaMoment posted:

This level is pretty straightforward without any neat tricks.
code:
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
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.
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 posted:

code:
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
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?)
And another small but neat improvement. I don't think this code needs much explanation.

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:
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
For every new search value, it SEEKs to EOF, and then one back to the last word and starts testing. Every LOOP2 iteration it has to SEEK -2 to move back one word from where it last searched. That means it has to step forward one position when it finds the word but that's fine.

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:
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
Now it checks the first position first. If that's the one, it replaces it in the FIRST 'subroutine' and jumps right back to search for the next. In that case it doesn't need to jump back to the beginning of the file because we never have to replace the same position twice. If the first word doesn't need replacing it SEEKs to the end and searches backwards.

504/41/7. Perhaps this could be optimized more but I'm quite happy I found this at all.

megane posted:

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.
:doh: 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.


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

Carbon dioxide fucked around with this message at 11:57 on Mar 12, 2022

Carbon dioxide
Oct 9, 2012

berryjon posted:

Given the actual speeds that the EXAs work at, I think anyone watching TV might have barely seen a blip or two, thinking it was a transmission error to a glitch for a second before their movie played.

Yeah, that's never made completely clear. If you pause the simulation at any point the movie footage just keeps playing (well, repeating). Same thing with the CCTV cam footage for the ATM hack, for instance.

Carbon dioxide
Oct 9, 2012

Part 18 - TEC Redshift - Dev kit

=== Trash World Inbox ===

Cloudmonkey98 posted:

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
Huh. reading back, you're right about the instructions, my bad.

As for the hacker battles, I agree with GuavaMoment here:

GuavaMoment posted:

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.

Nth Doctor posted:

They're here!
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.

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:
LINK 800

MARK TOP
SWIZ X 1 #PASS
SWIZ X 2 #PASS
SWIZ X 3 #PASS
ADDI X 1 X
JUMP TOP
The SWIZ function takes a mask as second operand. If the mask is 0002 (or just 2) it says "take 0 for the thousands, hundreds, and tens place, and take the value from the input's tens place (second digit from the right) for the output's ones place." The Language Reference Guide in the second post has some examples.

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:
;XB

MAKE
COPY M F
COPY M F
COPY M F
COPY M F
And to get rid of the KILL, the original XA needs to know when to stop. Since I'm using M now, the MRD test works.
code:
;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
It's important to have the TEST MRD when the replica is already sending on M, but before XA can do another REPL (since that'll cause a LINK). This solution times it right and runs at 7938/23/2.

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:
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
Much better, but still a far cry from the top percentile. I believe the only way to get there is with parallelism.



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:
;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
2918/48/15. Not bad. According to the top percentiles it should be possible to get the cycle count further down to at least 2884, and it should be possible to have a solution with only 20 lines of code, too. I'll leave that to the threads.

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.


Carbon dioxide
Oct 9, 2012

Part 19 - A SECRET TO EVERYBODY

=== Trash World Inbox ===

Let's see what improvements we got this time. My high scores were 2918 cycles and 22 size for the Redshift unlock.

First, the size improvements. silentsnack and GuavaMoment have basically identical 19 line solutions.

GuavaMoment posted:

code:
LINK 800
MARK REPL
ADDI X 1 X
SWIZ X 1 #PASS
SWIZ X 2 #PASS
SWIZ X 3 #PASS
REPL REPL
LINK 800
GRAB 199
COPY F T
DROP
MAKE
SWIZ X 1 F
SWIZ X 2 F
SWIZ X 3 F
COPY T F
LINK -1
KILL
LINK -1
...aside from leaving incorrect files in your home node, but as long as one of them is correct it works.
4964/19/13, saving 5 cycles over silentsnack's solution in exchange for extra activity and an additional garbage file. This differs from my solution by combining the REPL and the jump to the start of the loop. I couldn't do that because I put the ADDI at the end (which meant it tries 000 too, but that's not a passcode that actually occurs).

Anyway, I thought I tried a solution where it leaves some garbage and it didn't work. Must've done something else wrong then.

Next, cycle count.

GuavaMoment mentioned a 2880 cycle solution is possible by counting down instead of up. It's gaming the tests a little bit - but not nearly as much as the following.

silentsnack posted:

code:
;====XA====
MAKE
COPY 2 F
COPY 16 F
COPY 30 F
COPY 54 F
COPY 73 F
COPY 104 F
COPY 108 F
COPY 109 F
COPY 158 F
COPY 160 F
COPY 161 F
COPY 169 F
COPY 173 F
COPY 177 F
COPY 181 F
COPY 202 F
COPY 207 F
COPY 953 F
COPY 990 F
COPY 971 F
COPY 0 F

SEEK -99
MARK LMAO
COPY F T
REPL TEST
TJMP LMAO

COPY 215 T
MARK LOOP
ADDI T 1 T
REPL TEST
JUMP LOOP

MARK TEST
LINK 800
SWIZ T 3 #PASS
SWIZ T 2 #PASS
SWIZ T 1 #PASS

LINK 800
GRAB 199
COPY T M
COPY F M
KILL

;====XB====
MAKE
COPY M X
KILL
KILL
SWIZ X 3 F
SWIZ X 2 F
SWIZ X 1 F
COPY M F
2253/50/748 - bullshitting our way to crazy statistics :v:
Hahaha, this code is quite the sight. So, XA starts with writing a whole bunch of values into a file (in the home host so that file won't get in the way later), then sends REPLs to test each value in the file. Looking at them, I think they're the 17 lowest passcodes and the three highest from the test set. This means you never have to brute force all the way into the 950s, and it also means you can skip the first 200 or so passwords because we already know which passwords in that range are used. If none of those 20 passwords are correct, it brute forces the range that wasn't tested yet, but that's now much smaller.

Thinking about it, what silentsnack actually did was change a brute force attack into a partial dictionary attack. Sounds much less like bullshit if I phrase it this way, wouldn't you say? So this solution makes for a good real life lesson: hacking into an account is much easier if the hacker has foreknowledge about likely passwords. That's why you should never use dictionary words as passwords (use random characters instead), and you should never reuse passwords across sites. If it gets leaked in one place, hackers will first try that same password for all your other accounts. Instead, invest in a proper password manager.

Anyway, silentsnack came up with a further improvement.

silentsnack posted:

code:
;==== XA M=LOCAL ====
MARK LMAO
COPY M T
REPL TEST
TJMP LMAO

COPY 232 T
MARK LOOP
ADDI T 1 T
REPL TEST
JUMP LOOP

MARK TEST
LINK 800
SWIZ T 3 #PASS
SWIZ T 2 #PASS
SWIZ T 1 #PASS

LINK 800
GRAB 199
MODE
COPY T M
COPY F M

;==== XB M=LOCAL ====
COPY 2 M
COPY 16 M
COPY 30 M
COPY 54 M
COPY 73 M
COPY 104 M
COPY 108 M
COPY 109 M
COPY 158 M
COPY 160 M
COPY 161 M
COPY 169 M
COPY 173 M
COPY 177 M
COPY 181 M
COPY 202 M
COPY 207 M
COPY 216 M
COPY 221 M
COPY 953 M
COPY 971 M
COPY 990 M
COPY 0 M

;==== XC M=GLOBAL ====
MAKE
COPY M T
KILL
KILL
SWIZ T 3 F
SWIZ T 2 F
SWIZ T 1 F
COPY M F
2186/50/732 and as noted XA and XB need to start in local communication mode, which dunno if you've covered that yet.
Yeah, so the idea is the same, but the file (which took time to write) has been replaced by an EXA which sends the dictionary over the M register. This is faster and also saves a couple cycles for a larger dictionary.

I've talked about communication modes but I've not needed them so far so I'll explain it again:
EXAs, by default, start in GLOBAL communication mode. That means they can receive and send messages over M from any friendly EXA in any host.
You can swap them to LOCAL mode using the MODE command, or at the start by clicking the toggle on the M register. In LOCAL mode, they can only communicate with EXAs in the same host - and very importantly, only with other EXAs that are also in LOCAL mode.

So, by putting XC in GLOBAL and the other two in LOCAL, XC's COPY M T will just wait until XA switches modes and sends its first GLOBAL mode package. Meanwhile, as long as XA and XB stay in the same host, they can communicate in LOCAL mode all they want.


=== Redshift Homebrew - A SECRET TO EVERYBODY ===

Before I continue, GuavaMoment reminded me that there's a Steam Achievement tied to last week's assignment.
It is called RITE_OF_PASSAGE and the description states: You have the bog witch’s approval. Now complete the rite of passage.
Of course it's up to the player to figure out it's linked to this assignment.



Remember that random save file? We need to set RITE OF PASSAGE COMPLETE to TRUE. Not 1 for TESTs, no, the actual word TRUE.

No speed goals or anything so let's just grab my initial brute force solution to open the locked host and write some code after it.
code:
LINK 800

MARK TOP
SWIZ X 3 #PASS
SWIZ X 2 #PASS
SWIZ X 1 #PASS
REPL TRY
ADDI X 1 X
JUMP TOP

MARK TRY
LINK 800

GRAB 220
SEEK 9999
SEEK -3
COPY F X
SEEK 1
COPY X F
Since I can't write the word TRUE from scratch, I seek to a position where the word is already in the file and just copy that over.



And that's another achievement in the pocket. Let's get back to Ember.

So Ghast used to work at a game studio?

Yeah, he was a programmer.

I wonder what that was like.



One vote for "Probably terrible" and two for...

I bet it was fun, while it lasted.

Figuring out how to push a console to its limits...
Making something that brings joy to people...
Yeah, I can see that.


You can? Hm.





Now that I unlocked the Redshift dev kit, it's time for some homebrew, don't you think?

You're really going to make a game with this?
Why?
I'm not offering a reward for it.




One vote for "Not everything I do is for you", but two for "Making something".

Making something is its own reward.

Oh, it's one of those intangible things.
A sense of accomplishment? Self-actualization?
I'm not good at abstract concepts.
I'll have to observe you closely.


You surprise me, Ember. You do understand why Ghast would make games but not why I would do so?


OST: Code and Registers

This is the Redshift dev kit. As you can see, it looks quite different from the other things we've done so far. Luckily the zine has a comprehensive guide on this.

Welcome to this game's sandbox mode. Other than the EXAs being a bit different than we're used to, this stage has no size limit, no EXA limit other than what fits in the host and it lets you go wild.

This is a special episode, I've been looking forward to showing this off since I started this LP, but I'll start with a description of all the features in the sandbox. If this is a bit too dry for you, feel free to skip to the next occurrence of the text: "Let's build something!"

Let's use the zine to go through all the features.



There's already a lot to cover on the first page. You can skip reading it if you like since I'll show an example of each feature.

First of all, the DATA instruction.



DATA is a special pseudo-instruction that indicates that an EXA should already hold a file when it starts, containing the values after DATA. All DATA instructions are parsed in order they appear in, before the game even starts. During execution, these instructions are ignored. A very convenient way to store your game data.



You can place them wherever you want in your code but I prefer to have them at the start.

By the way - sadly you cannot use DATA outside of the Redshift. That would make some earlier assignments much easier to solve.

---

The only type of graphics in Redshift are sprites.
Each EXA can hold a 10x10 sprite in the G register. An EXA can be initialized with a sprite, you can turn pixels on and off by drawing on them with your mouse.





Every sprite starts out on the top left. To move it, simply write a new X position directly to GX, or Y position to GY. If I run this program for two steps, you see my drawing jump right and then down, so it appears in roughly the middle of the 120x100 pixels screen.



If you really want to use the 3D mode you better get yourself some of those special glasses.

Switching the 3D toggle on the Exapunks device dims the lights of the dev kit so you can truly focus on the headache inducing game.





By writing a value between -9 and 9 to the GZ register, you can make the sprite pop "in" or "out" of the screen in 3D mode. In 2D mode, there's no difference.



Finally, you can change the contents of the sprite itself by writing three digit values to GP. The first digit tells the EXA what to do (0 = turn pixel off, 1 = turn it on, 2 = toggle), and the second and third digit have the X and Y positions, counting from the top left.
This picture is after running COPY 060 GP, turning the 6th pixel of the top row off, and COPY 199 GP, turning the pixel in the bottom right on.

---

You can follow drawing code easily if you go step by step, but if you run the Redshift at normal speed (which is done with the dev kit's fast forward button), you won't see anything. That's because these draw instructions go by real fast.

What you're supposed to do is use the WAIT instruction for this. Unique to the Redshift, WAIT makes the EXA wait until the next frame. The Redshift runs at 30 fps.

So, if I put a WAIT instruction after all this drawing code, the user will only see the final result. That drawing hopping from the top left to the intended position? Individual pixels going on or off? That's way too fast for the user to see.

A big annoyance, though, is that WAIT instructions don't cause EXAs to wait for each other. So if EXAs have different amounts of instructions and you don't sync them up with NOOPs or M communication, they will quickly desync. It's a lot to keep in mind while coding for the Redshift.

As far as I can tell a WAIT instruction only takes a single cycle. So in practice, it doesn't mean "pause the EXA", it means "advance the frame of the Redshift". That means you can't even line up other EXAs by counting the number of instructions that can be run between frames (which was how real life old consoles dealt with this issue). It's weird. You're better off making sure all the EXAs are synced before doing a WAIT.



Next page of the zine.



If you need a score display or something you can use the built in font. Sending a value in the 300 range to GP replaces whatever was in the sprite register with that character. 300 itself is a space which blanks out the entire sprite. That could be useful.



The Redshift has a built-in collision detection system, a common way for games to prevent characters going through walls, or for detecting when a bullet hits the target. You use it by first having an EXA write something to the CO (collision output) register.



Then, if a collision occurs (which happens if both sprites have at least one lit-up pixel in same position on the Redshift's screen), the CI register (the bottom-most to the right) will contain the CO value of the other EXA. If there's multiple collisions, the highest CO value wins. And if there's no collisions, CI will always contain -9999. Collision ignores the GZ (3D depth) value.

---

While graphics and collision are handled within the EXAs, for input and sound we need to deal with hardware registers.



For input, read the #PADX and #PADY registers for the state of the D-pad. Each digit in the output of #PADB stands for one of the other buttons (X, Y, Z and Start). #EN3D will tell you if the device is in 3D mode.

While a game is running, you can control the Redshift with the mouse (by clicking the buttons) or keyboard, by default WASD for the d-pad, JKL for the three action buttons, and Enter for the Start button. This can be remapped from the EXAPUNKS options menu.

Finally, there are four sound registers. #SQR0 and #SQR1 are linked to square wave sound channels, #TRI0 to a triangle wave, and #NSE0 is a simple noise channel. Writing a number to any sound register will cause that sound to play continuously until a new number is sent. 0 turns it off, and for the square and triangle channels, 60 is the middle C, with lower numbers corresponding to lower notes and higher numbers to higher ones, following the piano keys as seen in the zine. Similarly, a value sent to the noise channel controls the pitch.

That's basically all there's to say about Redshift development.

Let's build something!

As a few readers might already know, during my initial playthrough back in 2018, I got kinda nerdsniped by this sandbox and spent way too much time on it. I will show here what I built back then because I still like it. But since this is an in-depth playthrough I'll take you through the design as well.

When I read the documentation, the first thing that piqued my interest was the sound channel setup. Two square waves, one triangle wave and a noise channel? That's almost exactly how the sound was set up in 8-bit Nintendo consoles such as the NES and the Game Boy. So, it should be possible to do something with that, right?

And programming an actual game in Redshift is hard. Game music is good enough for me.

Since I'm not a music writer, I first went to search for some sort of reference of how music is actually written in those old consoles.
To my surprise I actually found sheet music for a famous 8-bit song, with the 8-bit sound wave channels as the "instruments". A bit weird, but exactly what I needed.

One problem, though, I barely knew anything about sheet music. I knew about time signatures and measures and half notes and quarter notes and that's about where it ends.

I had to learn about how to recognize what key the music was played in (the clef with the flat symbols), and whatever the heck this was:


I mean I can figure out those are eighth notes with a rest in between. The flat symbol is easy to understand but hard to implement because when you need to convert your tones to numbers, a flat becomes an extra offset that I found easy to forget. But that little 3. That damned 3. It's apparently called a "tuplet" and it means something like "I want you to play notes that don't actually fit in the meter, so shorten all of them a bit so it kinda fits."

This song plays at 144 bpm, or 2.4 per second. Redshift has 30 frames per second. That means every frame is 2.4 / 30 = 0.08 of a beat. That means one beat is 12.5 frames exactly. With a 4/4 signature, a beat is a quarter note.

Since this doesn't quite fit in the frame count I decided to bump the speed to 150 BPM, because that makes every possible note I want to play a round number of frames.
Half note: 24 frames.
Quarter note: 12
Eighth: 6
Sixteenth: 3
And those funky tuplet notes are 4 frames each.

It plays slightly fast but at least you don't get weird variation in timing due to rounding errors.

With that in mind I started coding.
code:
== SQR0
DATA 70 24 0 8 70 4 70 4
DATA 70 4 70 4

; A TON MORE DATA HERE

LINK 801
MARK MAINLOOP
TEST EOF
TJMP RESET
COPY F #SQR0
COPY F X
MARK WAITLOOP
SUBI X 1 X
WAIT
TEST X > 1
TJMP WAITLOOP
COPY 0 #SQR0
WAIT
JUMP MAINLOOP
MARK RESET
SEEK -9999
JUMP MAINLOOP
The code isn't all that complex. After loading in the data, it copies the first value from the file into the square wave register, and then it uses the second value for a frame countdown. Rinse and repeat. Once it hits the end of the file it simply resets to the beginning.

I quickly noticed that my first version sounded terrible. It sounded like one long continuous note changing pitch. I realized I needed to build in a 'breath' between each note. That's what the COPY 0 #SQR0; WAIT does. I changed the TEST to already jump out if the value is 1 (1 frame to go). It then adds a single frame of silence. So the data snippet I included plays 23 frames of sound, 1 of silence (a half note), 8 frames of silence, (a rest, part of a tuplet), 3 frames of sound, 1 of silence (an 'eighth note' in the tuplet) and so on.

There's one more thing - this song has a repeat for the second part. That was no trouble to code since DATA instructions respect @REP macros.

The second square wave and the triangle wave have the exact same code as the first, just a different data file. The triangle wave took a bit more effort because it has a bass clef and as the music noob I am, I had to figure out where my middle C went.

Something else I noticed around this time is that it's incredibly easy to make timing mistakes while copying the sheet music into the data files - but also easy to figure out because if one instrument goes out of sync it sounds awful immediately.

Finally, there's the noise channel. The sheet music's fourth instrument is not called "noise", it's in fact a snare drum. This one was harder to convert than I thought. First of all, I had to find a pitch. The noise channel is actually a quite horrible crackling noise and there was no sound I really loved, but a pitch value of 50 sounded closest to the original. Secondly, I started making a data file like for the other channels - and utterly failed. If the sheet music calls for a quarter note - well, it's not a piano or a flute - on a snare drum that's still a very short sound. It's the speed of the beats that goes down for longer notes, not the length of the sound.

It turns out 2 frames of noise works the best here.

The snare drum has a section where it repeats the same measure 40 times. I could've put 40 repeats in the data file but I decided to clean that up a bit, so the noise channel is handled by two EXAs. This is the first:

code:
NOTE NSE0

NOTE TURNS OUT YOU NEED
NOTE TO DO QUITE A BIT
NOTE OF MANUAL TWEAKING
NOTE TO MAKE A NOISE CH.
NOTE SOUND LIKE DRUMS...

DATA 50 4 0 32 50 4 50 4
DATA 50 4

DATA 50 4 0 32 50 4 50 4
DATA 50 4

DATA 50 4 0 32 50 4 50 4
DATA 50 4

DATA 50 4 0 8 50 4 0 8
DATA 50 4 0 8
DATA 50 4 0 2 50 4 0 2

NOTE PUT REPEATING PART
NOTE IN OWN EXA FOR
NOTE CLEANER CODE

LINK 801
MARK MAINLOOP
TEST EOF
TJMP REPEAT
COPY F #NSE0
COPY F X
TEST X = 2
TJMP SKIP
MARK WAITLOOP
SUBI X 1 X
WAIT
TEST X > 2
TJMP WAITLOOP
MARK SKIP
COPY 0 #NSE0
WAIT
COPY 0 #NSE0
WAIT
JUMP MAINLOOP
MARK REPEAT
COPY 40 X
MARK REPLOOP
COPY 1 M
VOID M
SUBI X 1 X
TEST X > 0
TJMP REPLOOP

SEEK -9999
JUMP MAINLOOP
You can see in the DATA section that every non-rest sound has a time of 4. That's actually two frames of sound and 2 of rest, because in this case the TEST jumps out two frames early. Longer notes I handled by just adding additional "rests" to the data file. I needed a special case to handle 50 4 0 2 which is just a basic eighth note, of which the first 2 frames are the drum and the remaining 4 are silent. Because that meant having 2 rest frames on top of the two built-in ones I need the SKIP jump. I'm sure I could've done this in a much cleaner way but by this time I had the whole thing finally working and synced up and didn't want to break it again.

Once this EXA reaches the EOF, this indicates we've hit the repeating measure of the drums. It sends a message to M which indicates the second noise channel EXA needs to play one measure, then the other EXA sends a message back over M when it's done, this EXA counts down from 40, sending the other one repeating instructions to keep doing that measure, and once it's done the song starts over.

Using this sort of back-and-forth with M was the best way I could figure out to keep these two EXAs in sync. Here is the other noise channel EXA:
code:
NOTE NSE0

NOTE REPEATING PART OF
NOTE DRUMS

DATA 50 4 0 8 50 4 50 4
DATA 50 4 50 4 0 8 50 4
DATA 0 8

LINK 801
VOID M
MARK MAINLOOP
TEST EOF
TJMP RESET
COPY F #NSE0
COPY F X
MARK WAITLOOP
SUBI X 1 X
WAIT
TEST X > 1
TJMP WAITLOOP
COPY 0 #NSE0
WAIT
JUMP MAINLOOP
MARK RESET
SEEK -9999
COPY 0 M
VOID M
JUMP MAINLOOP
It's simple. It waits for M, then runs the same "two frames of sound, 2 frames of rest" logic as the other one, for its file which contains a single measure. Once it runs out, it sends a message on M and waits for instructions.

As a final touch I created some pixel art, consisting of four sprites. It doesn't do anything, it isn't animated, but at least the screen isn't completely empty during the demo.



This code doesn't do much, it just moves the sprite into the right position and then gets into an infinite loop so the EXA stays alive.

And that's all of the code.

I wonder, have you figured out yet which song I decided to recreate in the Redshift?

Well, there's no delaying it any longer. Here it is, the recording I made in 2018.

https://www.youtube.com/watch?v=sP3sPc0oHu0&t=37s

My rendition of the song starts at 37 seconds. The bit before is me scrolling through all the code so you can get a sense of how much work it was putting together those data files.

Here is a link to the sheet music I used.

And finally, here's the game disc so you can play it for yourself.



Wait what?

Yes, the Redshift has an export system. It exports games as PNGs of a disc that contain the code. If you drag it into the Redshift view in EXAPUNKS, it will load the game for you. This has got to be the coolest save export system I've ever seen.



You can either drag that image of the disc into this screen, or if you don't own the game, download the EXAPUNKS TEC Redshift Player for free on Steam to play the demo.

The Steam Discussion Boards for the Redshift Player are also a decent place to find other solutions, including actual games you can run in the Player.
They might seem like simple games but the amount of coding that went into them is impressive. EXAPUNKS really doesn't have a language that's friendly for complex programs.



When you leave the Redshift dev kit, the game will ask you if you actually made a game (the best Zachtronics can do without building some sort of Star Trek AI that can magically tell if a program is a game or not). The level will count as completed if you hold this button.

Congratulations! You did it.
What does it feel like to be a game developer?




We immediately go to the next thread vote.

Time for some research.
Human physiology, human morality...




And the intro for the next assignment.

Carbon dioxide fucked around with this message at 13:11 on Jul 2, 2022

Carbon dioxide
Oct 9, 2012


By the way I didn't want to bring it up in the update but I'm wondering how you managed to count down from here while silentsnack's solutions hardcode a check up till 990. Or do those do the digits in different orders? :thunk:

Carbon dioxide
Oct 9, 2012


Hah, that version was made by Quackles. Here's the original upload, which shows off all of his code.

https://www.youtube.com/watch?v=dZVrbpQoqg0

So, funny story, I finished A Secret to Everybody while Quackles was in the middle of his "secret project" and we hadn't seen anything except maybe a couple screenshots.
At the time I had no idea his Shenzhen I/O secret project was ALSO a song.

So mine came first - but Ra Ra Rasputin's code and design is probably a factor of magnitude more complex than Secret To Everybody.

Carbon dioxide
Oct 9, 2012

Part 20 - Digital Library Project

=== Trash World Inbox ===

As I expected, Quackles' rendition of Ra Ra Rasputin in Shenzhen I/O came up in the thread.
Because it's very impressive, here's a link to the Youtube video and a detailed write-up. Note that the write-up continues in the next part of that LP.


=== Digital Library Project ===

Congratulations! You did it.
What does it feel like to be a game developer?




One vote for "Pain" which I feel, yeah. It's though work. I prefer doing other kinds of programming. Anyway, two votes went to "great". The reward can make it worth the effort.

It's great.

Yeah?
At least there's one thing you can enjoy.
I'm not sure I fully understand it, but there you go.




Before we continue, a voiced cutscene with Ember.

Okay. I have a real question to ask you.
Say there's a trolley on a track heading towards five people.
Three of those five people are vegetarians.




Once again, for the voice-acted parts, dialog choices really make no difference.

Not this again...

There's a switch you can throw that will shut down a meat packing facility that is located down the road.
Unfortunately, the switch will also prevent the train from stopping, because...
Because...

Wait.
I may have reversed something.
Recalibrating.
There are too many variables here.
Okay.
Let's return to this later.
I need more data first.
And I need more processing power.


Well, that was a complete waste of time.



I have two assignments available now. Let's start with the first, hacking a library. Ook!



Time for some research.
Human physiology, human morality...




An unanimous vote.

Your usual topics.

Yeah, I guess so.
Wait. Am I really that predictable?
I'll have to change things up soon.
Let's get those books.



OST: Behind the Scenes

We're in the Palo Alto digital library project. Looks like a quite complicated network.

The assignment:
- Books are stored in the host corresponding to the first digit of their call number, while a book's file ID is 200 plus the last two digits of the call number. For example, book 512 would be stored in the host 500-599 as file 212.
- Duplicate each of the books requested by EMBER-2 and bring them back to your host.
- The call numbers for the books EMBER-2 wants are available in the file 300.
- Note that EMBER-2 will never request more than one book from the same host.


Alright, let's nose around a bit first. File 300 contains Ember's shopping list. To my knowledge there's no way to reach that EXA holding a file on the far left.



As for the books, here's all of them. Did you know you can use that little button with the right arrow in a file or EXA's title bar to pop it out of the left column so you can drag and drop it anywhere?

I feel some of these books might not be very trustworthy, but if Ember wants them, we'll get them for her I guess. Since you can't steal from a library (that would be evil!) I'll need to copy the books.

First of all, getting to them.



This icon seems to indicate that to get deeper into the network, I always need to use LINK 800, and to get back home it's -1.

My first attempt to get to the files looks like this:
code:
GRAB 300
MARK START
COPY F X
REPL GO
JUMP START

MARK GO
LINK 800
DIVI X 100 T

MARK GETTHERE
LINK 800
SUBI T 1 T
TJMP GETTHERE
The EXA starts by reading from the file. By writing values to X, the REPLs will still have the value in memory. The hundreds digit tells us how many LINKs we need to traverse, which is handled in a simple loop. The original EXA will die once it tries to read past the end of the file.

code:
REPL WRITER

MODI X 100 X
ADDI X 200 X
GRAB X
; DO STUFF

MARK WRITER
; DO OTHER STUFF
To grab the file we need to do a bit of arithmetic. I also REPL a WRITER which can hold the new file to write the copy to. Testing this code I see some hosts get a bit crowded. That might slow down EXAs a bit, if they have to wait for space. But that's a problem for later.

Since we're going to need communications between EXAs and since a lot of them will be writing in parallel, I think it's a good idea to have my single initial EXA (from which the others are replicated) start in local communication mode and just keep each WRITER in the same host as its corresponding reader.



With the writer and reader code in place, the replicated EXAs start copying their files. The READER will send a 0 once it reaches EOF. After that, it conveniently self-destructs when it tries to run the MAKE instruction while already holding a file. Using 0 as EOF indicator means the WRITER can simply use the T register as an intermediate, since ALL non-0 values are considered true for the purpose of a TJMP, including keywords.

All that's left is getting our EXAs back home. That can be done in a very simple way:

code:
GRAB 300
MARK START
COPY F X
REPL GO
JUMP START

MARK GO
LINK 800
DIVI X 100 T

MARK GETTHERE
LINK 800
SUBI T 1 T
TJMP GETTHERE

REPL WRITER

MODI X 100 X
ADDI X 200 X
GRAB X

MARK READING
COPY F M
TEST EOF
FJMP READING
COPY 0 M

MARK WRITER
MAKE
COPY M T
MARK WRITING
COPY T F
COPY M T
TJMP WRITING

@REP 10
LINK -1
@END
In most cases it will reach the home node before going through all the repetitions, which is fine because in that case the next LINK will just cause the EXA to die, dropping the file in the process.

By the way, now that I got the second zine, here's a full explanation of the @REP macro.



If you didn't know about the performance increase of unrolls, the zine also explains it here.



And this is my first working solution.

I had to compress this gif to not make it too huge. It's playing at double speed now.



290/38/74. Top percentile scores are 218, 26 and 10 respectively. Honestly, my solution isn't bad - I'm already below tenth percentile for cycles. But let's see how this can be improved.

A slow part of this solution is the check if the file is done every cycle. The files have a minimum size: 34. That means if we step into the check cycle after 33 steps we're fine.

I unrolled both loops to 33 direct COPYs - but it didn't quite fit. So I added some code so each does a 16 unroll twice. I set up a countdown in T for that, and had to remove some of the final LINK unroll because it didn't fit otherwise.
code:
GRAB 300
MARK START
COPY F X
REPL GO
JUMP START

MARK GO
LINK 800
DIVI X 100 T

MARK GETTHERE
LINK 800
SUBI T 1 T
TJMP GETTHERE

COPY 2 T

REPL WRITER

MODI X 100 X
ADDI X 200 X
GRAB X

MARK A
@REP 16
COPY F M
@END
SUBI T 1 T
TJMP A

COPY F M

MARK READING
COPY F M
TEST EOF
FJMP READING
COPY 0 M

MARK WRITER
MAKE

MARK B
@REP 16
COPY M F
@END
SUBI T 1 T
TJMP B
COPY M F

COPY M T
MARK WRITING
COPY T F
COPY M T
TJMP WRITING

MARK END
@REP 4
LINK -1
@END
JUMP END
232/75/74. Much better.

The next thing I thought of was removing the test from one of the loops entirely. The result was not an improvement but I'll share it anyway in case it inspires someone.
code:
GRAB 300
MARK START
COPY F X
REPL GO
JUMP START

MARK GO
LINK 800
DIVI X 100 T

MARK GETTHERE
LINK 800
SUBI T 1 T
TJMP GETTHERE

REPL WRITER

MODI X 100 X
ADDI X 200 X
GRAB X

@REP 33
COPY F M
@END

MARK READING
COPY F M
TEST EOF
FJMP READING
DROP
KILL


COPY 399 X
MARK GRAB
ADDI 1 X X
TEST X = 410
TJMP READING
REPL GRAB

GRAB X
JUMP END


MARK WRITER
MAKE

MARK B
COPY M F
JUMP B


MARK END
@REP 5
LINK -1
@END
JUMP END
The reader simply KILLs the writer when it's done. This allows for the full unroll. The real problem comes from the fact that the file IDs of the generated files are somewhere between 400 and 409. So I need a REPL structure to try grabbing all those file numbers until it finds one. I also needed to fiddle with the timing a bit so that it doesn't KILL some other EXA that happens to pass by. Anyway, this runs at 258 cycles, so slower than the previous solution.

I didn't get a better score than 232 cycles, so I'll leave the further improvements to the thread.

As for reducing the size, the trick is that file IDs are always globally unique. I don't need to keep track of where to go if I just simply try grabbing the correct file in every host. Don't forget merging the COPY from 300 with the first MODI, which is now possible since we don't need the hundreds digit for anything anymore.
code:
GRAB 300
MARK START
MODI F 100 X
ADDI X 200 X
REPL GETTHERE
JUMP START

MARK GETTHERE
LINK 800
REPL GETTHERE

GRAB X

REPL WRITER

MARK READING
COPY F M
TEST EOF
FJMP READING
COPY 0 M

MARK WRITER
MAKE
COPY M T
MARK WRITING
COPY T F
COPY M T
TJMP WRITING

MARK END
LINK -1
JUMP END
295/26/87. That's the top percentile score.

Finally, activity. It's possible to get that all the way down to 10. Can you see how?

To get to 10 activity I can't LINK back even once. I need to send one EXA out that communicates the data back via global M. That'll be a single file at a time so it will be slow. Also, I need to grab the files in order starting from the lowest, otherwise I'd need to track back. In other words - I'm going to need to apply some kind of sorting to file 300.

It is very annoying to sort a file with EXAs. With only two registers per EXA, one which is overwritten whenever you test which value is greater, and with the fact that there's only an overwrite instruction, not an insert one, I'm not even sure if it can be done with a single EXA.

Luckily we don't need to actually store the sorted file. All we need is an EXA that outputs the values of file 300 in order from low to high. And, after that, since we can't use KILL instructions, some smart handling of EXAs so that they don't get in each others way.

I came up with a solution that's not that easy to understand, so I'll go through it bit by bit. It starts with three EXAs. XA sorts file 300. XB writes the new files. And XC goes visit the library. Here's the top part of XA:
code:
;XA LOCAL
GRAB 300

MARK NEXTFILE

COPY F X

MARK FINDLOWEST
TEST EOF
TJMP FOUND
TEST F < X
FJMP FINDLOWEST
SEEK -1
COPY F X
JUMP FINDLOWEST

MARK FOUND
This code puts the first value of F in X. Then it checks every subsequent value of F. If it's lower than X, it puts that in X instead. Once it hits EOF we know it found the lowest value currently in the file.

After this, XA has some logic to send the file to the XC, my reader EXA. I'll skip that for the moment. To complete the sorting algorithm, after doing so (and while XC is busy copying a file), XA executes this:
code:
SEEK -9999
MARK FINDCURRENT
TEST F = X
FJMP FINDCURRENT
SEEK -1
VOID F

SEEK -9999
TEST EOF
FJMP NEXTFILE
In short, it finds the value it just sent to XC, deletes it from the file, then jumps back to the start. If the file is completely empty, it falls through to the code below.

Let's look at XC next.
code:
;XC GLOBAL

LINK 800

MARK LOOP
MODI M 100 X ;GB
DIVI 0 X T ; DIE ON 0
ADDI X 200 X
MODE
MARK NEXTHOST
LINK 800
REPL FIND
NOOP
TEST MRD ;LD
FJMP NEXTHOST
VOID M ;LD
VOID M ;LF
MODE
JUMP LOOP


MARK FIND
GRAB X
COPY 0 M ;LD
MODE

MARK READLOOP
COPY F M ;GE
TEST EOF
FJMP READLOOP
COPY 0 M ;GE EOF
MODE
COPY 0 M ;LF
The comments (such as GB) indicate what communication is being done. The first letter stand for Global or Local mode, and the B corresponds to B in another EXA to see where the specific M calls line up.
It starts by getting a file ID from M, and doing the same logic as before to get the actual filename. There's a DIVI inserted so the EXA can quickly die when the program is done.

Otherwise it LINKs to the next host (this is always safe, since it starts in the gateway, and Ember never needs two files in the same host), and makes a replicated EXA (FIND to see if the file is there. If it is, the replica immediately communicates over local M (message LD), which causes the MRD in the original XC EXA to be true. In that case it has to VOID LD to allow the replicated FIND EXA to continue, otherwise it jumps to NEXTHOST and tries again. If the file is found, the original has another VOID which won't be triggered for a while.

The bottom half switches back to GLOBAL mode, and starts sending the file's content, ending with a 0 (GF). Then it changes mode back to LOCAL, sends message LF, which tells the original XC it should continue. At that point it switches back to GLOBAL mode and waits for the next file ID.

This whole switcharoo is necessary because, for example, if you leave XC in GLOBAL mode, it will keep stealing file data that's supposed to go to XB.

Speaking of which, it's time to look at the whole thing and see how XB fits into the picture.



XB starts by sending a message over LOCAL M (LA). This is basically it waiting for someone to read it. XA is the one to do so, directly after the MARK FOUND.
At that point, XA quickly switches to GLOBAL mode, sends the file ID to XC (GB, which we already saw from XC's point of view), then switches back and tells XB to get ready to receive data.

This order is important - if XA informed XB before XC, XB might already be in global mode, stealing the file ID that XC needs. Anyway, XB switches to GLOBAL mode, and starts writing the data to a new file. Once it gets a zero (tested by using the T registry as intermediate and with FJMP ENDFILE, it drops the file, switches to LOCAL mode, and tells XA it's ready to start receiving the next file. By that time XA has already found the next lowest value, so the complicated game of telephone repeats.

Once XA runs out of file IDs to send, it executes its bottom two lines: placing the special value 0 in X and then jumping to FOUND, which holds the communication protocol. It sends the 0 to both XB and XC, which will both die because of this. XA then dies itself with the DIVI 1 X T. And with that, all files are written.

I probably could have done without XB, but that would mean XA repeatedly dropping its file to make another one and then pick it up again.

The result is 1200/70/10. One file after another makes it slow but that's the only way to get the activity all the way down.

Processing.
Yes, this is what I wanted.




And with that, we're at the first thread vote for today.

I've been analyzing television and radio broadcasts.
And I have a few questions.
First of all, why do some things become popular while others don't?




And the second vote.

Carbon dioxide
Oct 9, 2012


I'm gonna guess this is a vote for the Ember scenes?

Carbon dioxide
Oct 9, 2012

Part 21 - EXA-Blaster Modem - Radio Station hack

=== Trash World Inbox ===

I ended the last update with top scores of 232, 26, and 10. Let's see what you came up with.

silentsnack posted:

code:
;M=LOCAL
GRAB 300
MARK CLONE
ADDI F 2000 X
REPL TRAVERSE
JUMP CLONE

MARK TRAVERSE
LINK 800
REPL TRAVERSE
SWIZ X 421 X

GRAB X
REPL WRITER

MARK READ
COPY F M
JUMP READ

MARK WRITER
MAKE
MARK W_LOOP
COPY M F
NOOP
TEST MRD
TJMP W_LOOP

MARK HOME
LINK -1
JUMP HOME
289/24/87
Nice and simple size improvement. All it does different is skip the EOF check, and line up the cycles so the writer can do an MRD check instead. The ADDI F 2000 X; SWIZ X 421 X trick doesn't save any space but is a neat use of SWIZ. Since some people commented they didn't understand SWIZ very well, I'll try to explain what this does.

So, imagine you start with a file ID 512. According to the assignment, the GRAB instruction needs 212 (200 plus the last two digits). The 5 tells us what host it's in but because we try every host, we don't strictly need it.
First we add 2000 to 512 with the ADDI. That's 2512.
Next, we do SWIZ 2512 421. It looks more complex than it is. The second operand, 421, just says "take the fourth digit from the right, then the second, and then the first", so [2]512, 25[1]2, 251[2], forming 212, which is the actual number we need.

silentsnack posted:

code:
;M=LOCAL
GRAB 300
@REP 4
ADDI F 2000 T
REPL SKIP
@END
ADDI F 2000 T
WIPE

MARK SKIP
LINK 800
MARK TRAVERSE
LINK 800
REPL TRAVERSE

SWIZ T 421 T
GRAB T
REPL WRITER

MARK READ
@REP 18
COPY F M
@END
JUMP READ

MARK TIMER
SUBI T 1 T
TJMP TIMER
COPY 0 M
HALT

MARK WRITER
MAKE
COPY M F;  [[COPY--
COPY 15 T

MARK WRITE_BATCH
COPY M F;  --TO--
SUBI T 1 T
COPY M F;  --FILE--
TJMP WRITE_BATCH

COPY M F;  --EVERY--
COPY 34 T
COPY M F;  --OTHER--
REPL TIMER
COPY M T;  --CYCLE]]

MARK WRITE_END
COPY T F
COPY M T
TJMP WRITE_END

@REP 10
LINK -1
@END
188/72/87
Nice. It starts with a @REP for reading the file list. Sometimes there's less files to grab in which case the original one just dies once it reaches the end of the list. Otherwise it wipes that file and goes handle the last entry itself.

Then it uses a REPL setup to just send a copy of itself to every node to go look for the file - that's faster than checking if you're in the right host. There doesn't seem to be a big problem with the hosts getting too crowded because the EXAs die immediately if they can't find the file and don't all start moving in the same cycle. If it finds the file it makes a WRITER EXA and starts copying. The writer starts with a countdown from 15 which runs every 2 writes. This is all done in the spare cycles between the writes. After the internal timer runs out, it then starts doing a somewhat slower write where it checks if a separate TIMER EXA is done each write. This is necessary because without it it would block endlessly once the reader is done. The TIMER runs for a set time which corresponds to the longest file in the test set. That seems slow, but remember, only the slowest test counts so this works out very well.

By the way, you can drop it to 185 cycles by moving over the SWIZ to directly below MARK SKIP.

Quackles posted:

For the activity-10 version, I had the reader EXA send back the shelf ID it was in, plus 100. Then the control EXA in my host finds the first number in its list that is less than that number, and sends it to mean 'grab that book'. So it's a granular sort that sorts in buckets, even if the process of scanning the list is a bit inefficient each time the next book needs to be queued.

The control EXA also tells the reader EXA to go to the next shelf if it doesn't find anything.

Reader EXA:
code:
COPY 100 X	;ID of shelf AFTER this one
LINK 800
COPY 1 M 	;sync
MARK NEWSHELF
ADDI X 100 X
LINK 800
COPY X M	;send shelf ID
COPY M T	
FJMP NEWSHELF 	;0 = next shelf, other number = book to grab
GRAB T 
MARK READOUT
COPY F M
TEST EOF
FJMP READOUT
DROP
COPY -9999 M
JUMP NEWSHELF 	;exploiting assumption: only one book per shelf is ever needed
Controller EXA:
code:
VOID M			;sync
MARK NEXTBOOK
GRAB 300
MARK NEXTBOOK2
NOOP
TEST MRD		;wait for shelf ID - no response = reader vanished, give up
FJMP GAMEOVER
COPY M X		;X has shelf ID after this one
MARK READBOOKLIST
TEST EOF
TJMP NOTFOUND
TEST F < X		;find any number that's on this shelf
TJMP COPYBOOK
JUMP READBOOKLIST
MARK NOTFOUND
COPY 0 M 		;next shelf
SEEK -9999
JUMP NEXTBOOK2
MARK COPYBOOK
SEEK -1
SWIZ F 0021 X		;generate book ID, send it
ADDI 200 X X
COPY X M
SEEK -1 
VOID F
DROP
MAKE
MARK COPYLOOP		;copy book then go back to controlling the reader
COPY M X
TEST X = -9999
COPY X F
FJMP COPYLOOP
SEEK -1
VOID F
DROP
JUMP NEXTBOOK
MARK GAMEOVER
1304/54/10.
Ah, an alternative low activity solution. I think Quackles' explanation speaks for itself. The code is quite readable, which is in part because the low activity solution forces you to read the books one by one.

Finally, silentsnack has an update to the Mitzuzen HDI-10 Heart level (update 15 of this LP).

silentsnack posted:

and as usual some completely unrelated random thing reminded me go back and take another look at the Phage/Heart solution, because it kinda bugged me to have 3 separate control structures redundantly running independent countdowns of the same number. And it turns out the suspicion that there should be a better way was correct, it just required approaching the timer from a different angle. Previous best time was 62/50/45.
code:
;XA
LINK 800

MARK LOOP
DIVI #NERV -10 X
SUBI X 3 T

FJMP SKIP
MARK WAIT
SUBI T 1 T
TJMP WAIT
MARK SKIP

REPL LOOP
REPL B
LINK 1
LINK 1
KILL
COPY 40 #NERV
COPY -70 #NERV
COPY -70 #NERV
JUMP BEAT

MARK B
LINK 3
LINK 3
KILL
COPY -70 #NERV
COPY 40 #NERV
MARK BEAT
COPY -70 #NERV
JUMP BEAT

;XB
NOOP
NOOP
LINK 800
REPL B
LINK 1
LINK 1
COPY 40 #NERV
COPY -70 #NERV
COPY -70 #NERV
JUMP BEAT
MARK B
LINK 3
LINK 3
COPY -70 #NERV
COPY 40 #NERV
MARK BEAT
COPY -70 #NERV
JUMP BEAT
Combined with splitting off the special-case'd first generation into a separate EXA to save a REPL operation brings it down to 56/45/42.
I don't even know what to say about this other than I never thought the count could get this low.


=== TEC EXA-Blaster™ Modem ===

Processing.
Yes, this is what I wanted.




Two votes for the first option, one for the second.

Glad it's helping.

I love ingesting a good corpus...
Not that that's all I'm doing.
I read the same way any normal human reads.
By analyzing patterns of word recurrence and deriving correlations between concepts implicit in statistically significant word clusters.


Excuse me?



lmao

Anyway, speaking of hungry, the zine has some new recipes for y'all to try.



Mmm, sounds like another 5 star experience. :discourse:



For the new assignment, we have to influence the media.

I've been analyzing television and radio broadcasts.
And I have a few questions.
First of all, why do some things become popular while others don't?




One vote for "Good work finds its fans", but two for the one about big media.

Big media conglomerates push some artists and abandon others.

So it comes down to corporate agendas?
We should test this.
Think we could pick a random pop song and make it a hit?


Okay, let's.


OST: Code and Registers

The assignment:
- Connect to each radio station and replace every song in the playlist (file 200) with CAN'T (NOT) GET OVER YOU by ME2U (file 300). Each song in a playlist consists of two keywords: the song name and the artist name.
- A list of phone numbers for the radio stations is available in file 301.
- Note that the EXAs in global mode can only communicate if there is a path of links connecting them.
- For more information see "Hacker Skills: Modem Control at the Direct Level" in the second issue of the zine.




Manually operating a modem? It's been a while.





If I send the 11-digit number to the #DIAL register, the modem will make a connection. Sending -1 will make the modem hang up and reset, and you can dial again.

That one note in the assignment is important though. You can't communicate between EXAs if they're in completely separate networks. If an EXA needs to send a message from a radio host to elsewhere you better keep the link open.

There's one more thing: Even though the modem is ours, leaving a file in the modem host (the one with the #DIAL register) will fail the assignment because for some reason that counts as leaving a trace.

With a max of 100 lines we have plenty of space to play around. Let's start with the dialer.

code:
GRAB 301
LINK 800
MARK DIAL
@REP 11
COPY F #DIAL
@END
COPY -1 #DIAL
TEST EOF
FJMP DIAL
WIPE
Simply grab the file, dial 11 digits, then hang up immediately. That gives us only one cycle to actually get into each radio host but if I time it well that should work out. Since I take file 301 along, I need to WIPE it at the end.

The song is exactly two values (name and artist), so theoretically that should fit in the registers. Let's create a second EXA and give that a try.
code:
;XB
GRAB 300
COPY F T
COPY F X
WIPE
LINK 800
MARK TRY
REPL TRY
LINK 800

GRAB 200
Put the title in T and the artist in X. Destroy the file, then just keep trying to get into an open modem link with REPLs. This works, it's fast enough to try it each cycle.

Now I have to overwrite each song in the playlist with Ember's song. But I shouldn't add anything to the playlists, so I need to keep track of the length. That is going to require a register. We need a trick.

code:
;XB continued
MARK WRITE
COPY T F
COPY X F
TEST EOF
FJMP WRITE
On the first iteration of this loop, it writes the title and artist to the playlist. After that, it'll start writing 0s (the content of the T register after TEST EOF and the artist. That's good, it means we have the title safely stored at the beginning of the file.



Finally, the EXA SEEKs to the start of the file, grabs the song title, and writes it over the 0s in another loop.

The only thing that's left are copies of XB still replicating in the modem host. That's easily solved by just adding a couple of KILL instructions at the end of XA.



Not the cleanest solution, but it works.



Top percentile scores are 129, 32 and 9. Let's get closer to them.



The first thing I can do is unroll the loop of the file writer, making use of the fact that a playlist never has less than 6 songs. This immediately drops the cycle count from 185 to 159, with 55 cycles and 15 activity. For some reason, changing the speed of the file writer means XA needs more KILL instructions at the end.

I can use another trick to get it down a bit more to 154 cycles.
code:
GRAB 300
COPY F X
COPY F T
WIPE
LINK 800
MARK TRY
REPL TRY
LINK 800

GRAB 200

@REP 5
COPY X F
COPY T F
@END

MARK WRITE
COPY X F
COPY T F
TEST EOF
FJMP WRITE

SEEK -9999
SEEK 1
COPY F X
SEEK 10

MARK WRITE2
TEST F > 0
COPY X F
JUMP WRITE2
The main change was combining the SEEK 1 and the TEST EOF in the final loop. I do a file read (I used a TEST but a COPY would've worked just as well), which moves the cursor forward one if it succeeds. But if the EXA is already at the end of the file, it'll die. To make this work I had to write the artist name instead of the song title in the WRITE2 loop.

By the way, just to clean up the code a bit, putting a NOOP after the MARK TRY is just as fast, but XA only needs a single KILL at the end now.

Of course, since the WRITE2 loop has no conditional logic anymore, it is trivial to unroll.
code:
@REP 3
TEST F > 0
COPY X F
@END
Turns out three repeats is enough.

And since I have plenty of spare lines, the dialing sequence can be unrolled as well. It's manual, though, since you can't nest @REPs.



138/90/11

Conveniently, I can fit 4 dial actions. That way, the EOF test lines up perfectly after the second iteration of the outer loop.

And that's the best I can come up with... or so I thought at first. I then realized I can make use of the fact the slowest case needs three repeats at the end of XB.
code:
;XB second half
REPL SEND

MARK WRITE
COPY X F
COPY T F
TEST EOF
FJMP WRITE

SEEK -5
COPY M F
SEEK 1
COPY M F
SEEK 1
COPY M F
HALT

MARK SEND
@REP 3
COPY T M
@END
135/92/11

No need to do any tests in that last loop, all I need to do is seek back 3 songs from the end and run it exactly three times. Worst case, I overwrite some artist names twice with the same value. To prevent the need to SEEK back to the start to get the artist name, I just have a REPL hold it and send it to me.

But there's more. If I know a playlist is at least 6 songs and at most 9, and I can overwrite data, I don't need an EOF test at all.



Write six values from the start, 3 from the end. Worst case there's a bit of overlap. 122/85/11, several cycles under par.


The low activity score is trickier than it seems.
It's easy to get it down to 10: just merge XA and XB so you only LINK from home to the modem once. To get it down to 9 you need to remove the KILL for the ever-replicating original XB, though. The problem is you don't have much space for testing anything in XB, because you need both registers to hold the song info.

I came up with a solution where XB starts with only the song title. XA sends the artist name over M twice, every time it dials a number. It sends a zero when it's done. XB waits for M, and as soon as it sees something it first tests if it's zero (in which case it stops). If it's not zero, it reads M again to store the result in T - which can now safely be done because the test is complete. Other than that it works the same as before.
code:
GRAB 300
COPY F X
COPY F T
DROP

; ORIGINAL XA
GRAB 301
LINK 800
REPL TRY
COPY T X

MARK DIAL

@REP 11
COPY F #DIAL
@END
COPY X M
COPY X M
COPY -1 #DIAL

TEST EOF
FJMP DIAL
WIPE

COPY 0 M
HALT

; ORIGINAL XB
MARK TRY
NOOP
TEST M = 0
TJMP END

REPL TRY

COPY M T
LINK 800

GRAB 200

@REP 6
COPY X F
COPY T F
@END

SEEK 9999
SEEK -6

@REP 3
COPY X F
COPY T F
@END

MARK END
This code is all one EXA, the "ORIGINAL" notes are just a clarification. 180/57/9.

Since I spent a lot of time getting the cycles down to 122, I'll leave the low size optimization to the threads.

That was surprisingly easy.
People are requesting the single, humming it to themselves, buying the album...
Just because they've heard it before.
Is that really all it takes?




The first thread vote. And for next time...

Theory: The inconsistency of ratings depends on context.
We made a hit by picking a generic pop song and artificially boosting its exposure.
But there are other domains where this isn't possible.
For example, you couldn't change people's behavior by giving a Last Stop store a five-star rating in a restaurant guide.
That would be ridiculous.


Carbon dioxide
Oct 9, 2012

GuavaMoment posted:

I'm really looking forward to the next level as I liked it a lot, and spent a lot of time on it. I'm still chasing down one cycle I know is possible, so maybe you all can help?

Feel free to all help each other out, just don't post about levels yet before I covered them in my LP.

Speaking of, I'm not entirely sure how my schedule will work out next week with Easter and such. The update might be delayed a bit.

Carbon dioxide
Oct 9, 2012

Part 22 - StreetSmarts GIS Database

=== Trash World Inbox ===

silentsnack posted:

code:
GRAB 301
LINK 800

MARK LOOP
REPL PAYLOAD
COPY 11 T

MARK DIALER
COPY F #DIAL
SUBI T 1 T
TJMP DIALER

COPY 800 M

TEST EOF
COPY -1 #DIAL
FJMP LOOP

MARK PAYLOAD
LINK -1
GRAB 300
COPY F X
COPY F T
DROP

LINK 800
LINK M

GRAB 200
MARK DATA
SEEK -2
COPY X F
COPY T F
COPY F F
JUMP DATA
367/28/26
A low size solution. Quite straightforward - while the 'main' EXA is dialing, a payload EXA goes back to read the payload file to X and T. It then waits for a message from the dialing EXA, and LINKs to the radio host when it gets it. There it overwrites the playlist. COPY F F is a way to check if you're at the end of the file yet. If not, it takes the value at the F cursor and writes it to the next location. Doesn't matter since it gets overwritten anyway. I didn't know COPY F F was legal, since you can't do something like ADDI F F X. But it is, and it's necessary here because X and T are already occupied.


=== StreetSmarts GIS Database ===


That was surprisingly easy.
People are requesting the single, humming it to themselves, buying the album...
Just because they've heard it before.
Is that really all it takes?




Two votes for "more complex", but 3 for this one.

Mass media is a sham.

It's seeming that way.
Hmm.
Girl you need to get a cluuuue...
I just can't not get over yoooou!
Oops. I guess it is pretty catchy.


I didn't know AIs could get an earworm.





Next up, hacking a GIS database.

Theory: The inconsistency of ratings depends on context.
We made a hit by picking a generic pop song and artificially boosting its exposure.
But there are other domains where this isn't possible.
For example, you couldn't change people's behavior by giving a Last Stop store a five-star rating in a restaurant guide.
That would be ridiculous.




All votes but one for "You never know".

You never know...

This is why we need to experiment.



OST: Network Exploration

The assignment:
- Each host contains a list of restaurants and their ratings, from one to five stars (file 200). Locate the entry that corresponds to the Last Stop on Eddy Street and change its rating from one to five stars.
- The name of the target restaurant and its location within the GIS grid is available in file 300. The first coordinate is the number of times to move east, while the second coordinate is the number of times to move north (positive) or south (negative).
- For more information see "Network Exploration: Geographic Information Systems" in the second issue of the zine.


Let's see what the zine has to teach us.



We're dealing with a StreetSmarts system here. I'm just wondering what that would look like for any city that isn't grid based.

Also, since GIS is short for Geographic Information System, it's pronounced GIS, not GIS.



As you can see there's one or two restaurants in each block. There's also a compass needle to conveniently show us which way is which. East (the first value in file 300) is to the top right, while north is to the top left.

Let's start building.

I think it'd be convenient to have one EXA stay home to just transmit the coordinates:
code:
;XA
GRAB 300
COPY F X
COPY F M
COPY F M
COPY X M
East, then north/south, and finally the name of the restaurant to look up in the file.

The 'walking' EXA should first go the right "east" position (can I call it longitude?):

code:
;XB
LINK 800
COPY M T
FJMP NS

MARK EAST
LINK 801
SUBI T 1 T
TJMP EAST

MARK NS
If the east value starts at 0, XB shouldn't move at all from the starting grid space, so it skips the loop. Otherwise it LINKs until the number of steps becomes zero.

The north-south (latitude) code has to deal with negative values, so it's more complicated:

code:
MARK NS
COPY M X
TEST X < 0
TJMP GOSOUTH

COPY X T
FJMP FOUND

MARK NORTH
LINK 800
SUBI T 1 T
TJMP NORTH
JUMP FOUND

MARK GOSOUTH

COPY X T
MARK SOUTH
LINK 802
ADDI T 1 T
TJMP SOUTH

MARK FOUND
First I check if the value is negative, if so it jumps to GOSOUTH. If not, I first check if it's zero, in which case we're done. From there on I use the T register again for the countdown so I can use the TJMP without TEST when the EXA is in the right place.

Finally, grab the file, and find the right restaurant. Since any restaurant has at least 1 star, I can copy the first star and write that to the file four times.





This initial solution runs at 48/40/6. The top percentiles are 26, 25 and 6 respectively.

Activity is already optimized at 6. That makes sense, because 6 is how many steps it takes to get to the far corners of the grid, so that'll correspond to the highest-activity test case. Time to improve the other metrics.

First of all, we can speed up the search within the file. Since there's never more than 2 restaurants in a file, the following solution works:

code:
MARK FINDINFILE
TEST F = X
TJMP COPY
SEEK 6
MARK COPY
COPY F X
@REP 4
COPY X F
@END
Check if it's the first value. If not, it's the second value, so SEEK to the star just beyond it. This drops the cycle count to 37.

For the next improvement, I considered that specifically going for the correct grid space is a bit slow with all the tests if the EXA is there yet. Since the restaurant we're looking for exists in only one place, I just can have EXA REPLs check everywhere.

It's a bit fiddly to have the EXAs only go where they need to be and not visit nodes several times.

code:
;XA 

GRAB 300
COPY F M

;XB

LINK 800
COPY M X

REPL SOUTH
REPL NORTH
REPL EAST
JUMP GRABFILE

MARK SOUTH
LINK 802
REPL EAST
REPL GRABFILE
LINK 802
REPL EAST
JUMP GRABFILE

MARK NORTH
LINK 800
REPL EAST
REPL GRABFILE
LINK 800
REPL EAST
JUMP GRABFILE

MARK EAST
@REP 2
LINK 801
REPL GRABFILE
@END
LINK 801


MARK GRABFILE
GRAB 200
TEST F = X
TJMP COPY
SEEK 5
TEST F = X
DIVI T T X ;DIE
MARK COPY
COPY F X
@REP 4
COPY X F
@END
From the start, XB REPLs itself into south-, north- and eastbound copies. It also jumps to GRABFILE in case the restaurant is in the initial grid space.

The south- and north-bound EXAs each link in their directions twice, leaving eastbound and GRABFILE EXAs behind. This is all written out instead of using @REP so I have manual control over when to use REPL and when a JUMP suffices, to prevent creating extra EXAs.

The eastbound EXAs link in their directions three times, leaving GRABFILE instances behind so the entire grid is covered.

Finally, in the GRABFILE code, after SEEKing for the second restaurant entry, it has to test if that's the correct one. If there's only one entry, the TEST makes this EXA die, and if it's the wrong one, the DIVI by zero will do so.

27/41/20.

Notice that I always do the REPL to the moving EXAs first, and that I start with SOUTH and NORTH before EAST. Moving to the back of the grid takes the longest so it's important to optimize it as much as possible. I also tried making the eastbound EXA spawn columns of south/north-bound ones but that was slightly slower.

There's one more cycle to be saved here, though.

code:
MARK SOUTH
LINK 802
REPL EAST
LINK 802
REPL EAST
REPL GRABFILE
LINK 800
JUMP GRABFILE

MARK NORTH
LINK 800
REPL EAST
LINK 800
REPL EAST
REPL GRABFILE
LINK 802
JUMP GRABFILE
It's actually slightly faster to first spawn the eastbound EXA in the farthest south and north columns, and then go back to see if the skipped file isn't the right one. 26 cycles.

I tried more tweaking with the movement order but I couldn't get it better than this.


Finally, for size, the most straightforward trick is to just use loops instead of unrolls. Remove all speed optimizations in favour of simpler code:

code:
;XA
GRAB 300
COPY F M

;XB
LINK 800
COPY M X

REPL SOUTH
REPL NORTH
REPL EAST
JUMP GRABFILE

MARK SOUTH
LINK 802
REPL EAST
REPL GRABFILE
JUMP SOUTH

MARK NORTH
LINK 800
REPL EAST
REPL GRABFILE
JUMP NORTH

MARK EAST
LINK 801
REPL GRABFILE
JUMP EAST

MARK GRABFILE
GRAB 200
MARK FIND
TEST F = X
FJMP FIND

COPY F X
@REP 4
COPY X F
@END
Down to 49/32/20. The directional loops would go on forever, except those EXAs just die as soon as they can't link any further.

For the next improvement, we need to reuse lines. This 29-line solution does so:
code:
;XA
GRAB 300
COPY F M

;XB
LINK 800
COPY M X
JUMP START

MARK EAST
LINK 801
MARK START
REPL EAST
REPL NORTH
REPL GRABFILE

MARK SOUTH
LINK 802
REPL GRABFILE
JUMP SOUTH

MARK NORTH
LINK 800
REPL GRABFILE
JUMP NORTH

MARK GRABFILE
GRAB 200
MARK FIND
TEST F = X
FJMP FIND

COPY F X
@REP 4
COPY X F
@END
Basically, I rewrote the LINKing logic to the slower solution where the eastbound EXA splits off in north- and southbound ones. Then by rearranging the REPLs, I can use a fallthrough to turn the eastbound ones south. Also, the START mark means I don't need the initial set of REPLs anymore.

Finally, I can combine the southbound and northbound loops by putting the LINK ids in a register:

code:
COPY 800 T
REPL NORTHSOUTH
REPL GRABFILE

COPY 802 T

MARK NORTHSOUTH
LINK T
REPL GRABFILE
JUMP NORTHSOUTH
This clocks in at 47/27/20. I'll leave the further improvement to the top percentile size of 25 lines to the threads.

Oh.
Apparently, that Last Stop location is getting mobbed.
Processing.
People are choosing to believe the guide even when it's obviously wrong...
Why would they think Last Stop has actual good food?
And why did the guide change their behavior, but not the highway sign?




Here is the first vote.

Before I finish for today, do you remember when I posted that story from the first zine, way back in episode 7? It was a couple of pages long.

The second zine contains the last half of the story, here it is.

Click the images for higher resolution.





Next time, we'll have another hacker battle.

Time for the next opponent.
Are you ready?


Carbon dioxide fucked around with this message at 14:36 on Apr 18, 2022

Carbon dioxide
Oct 9, 2012

Yes. The zine gives you enough to at least solve all the puzzles, but doesn't go out of its way to help you find high scores.

Carbon dioxide
Oct 9, 2012

Part 23 - Valhalla Hacker Battle

=== Trash World Inbox ===

GuavaMoment posted:

25 lines? How about 18.

code:
GRAB 300
COPY F X
WIPE
COPY 800 T
MARK MAIN
LINK T
REPL MAIN
GRAB 200
ADDI T 1 T
REPL MAIN
MARK TEST
TEST F = X
FJMP TEST
COPY F X
COPY X F
COPY X F
COPY X F
COPY X F
Ah, I was wondering if something like this was possible but didn't quite figure it out. The trick is to start LINKing from 800 and then use a combination of REPL and increments of the LINK id to go in every direction. But if you get that wrong the EXAs will keep looping forever.

GuavaMoment also shared this 24 cycle solution:

GuavaMoment posted:

code:
GRAB 300
COPY F X
LINK 800
MARK MAIN
REPL TOP3
REPL TOP2
REPL TOP1
REPL RIGHT2
REPL RIGHT1
REPL LEFT2
REPL LEFT1
WIPE
JUMP EDDY
MARK TOP3
LINK 801
MARK TOP2
LINK 801
MARK TOP1
LINK 801
REPL RIGHT2
REPL LEFT2
REPL RIGHT1
REPL LEFT1
JUMP EDDY
MARK RIGHT2
LINK 802
MARK RIGHT1
LINK 802
JUMP EDDY
MARK LEFT2
LINK 800
MARK LEFT1
LINK 800
MARK EDDY
GRAB 200
TEST F = X
TJMP STARS
SEEK 5
TEST F = X
FJMP EDDY ; this kills the exa when it's in the wrong node
MARK STARS
COPY F X
COPY X F
COPY X F
COPY X F
COPY X F
The farthest nodes need directed EXAs as soon as possible, so I create the farthest east EXA first, then furthest south, then north. All the outer nodes get checked first (cycles 11 and 12), and 10 central nodes all get checked on cycle 13. I know faster times are possible but I just cannot figure it out. I don't know if I can fill the grid faster, and I don't know actually using the coordinates of the correct Eddy's could help.
Those RIGHT2 and RIGHT1 jumps where you end up further down the code if you need to do fewer LINKs are a quite neat solution.

Next, both silentsnack and GuavaMoment posted 23-cycle solutions. They are similar in the sense that they each use two EXAs, one to handle the back rows and one to handle the front rows. That's apparently very slightly faster than using a single EXA.

For the sake of brevity I'll only post silentsnack's solution here but I want to mention that GuavaMoment's solution has an EXA priority quirk, where depending on what EXA's code you paste in first, it runs at either 23 or 26 cycles.

silentsnack posted:

code:
;XA
GRAB 300
COPY F M
SEEK -1
COPY F X
LINK 800
REPL NORTH;B20-B30
LINK 801
MARK NORTH;B21-B31
REPL SOUTH
LINK 800
REPL HERE
WIPE;CRASH

MARK SOUTH;B10,B11
REPL HERE
LINK 802
MARK HERE
GRAB 200
TEST F = X
TJMP YES
SEEK 5
TEST F = X
DIVI T T T
MARK YES
COPY F X
@REP 4
COPY X F
@END

;XB
LINK 800
LINK 801
COPY M X
REPL NORTH
LINK 801
REPL N1;B22-B32
LINK 801
MARK N1;B23-B33
REPL S1
REPL HERE
LINK 800
JUMP HERE

MARK S1;B12,B13
LINK 802
JUMP HERE

MARK NORTH;B41-B43
REPL SOUTH
LINK 800
LINK 800
REPL EAST
MARK WEST;B00,B40
REPL HERE
LINK 803
JUMP HERE

MARK SOUTH;B01-B03
LINK 802
LINK 802
REPL WEST
MARK EAST
LINK 801
REPL HERE
LINK 801

MARK HERE
GRAB 200
TEST F = X
TJMP YES
SEEK 5
TEST F = X
DIVI T T T
MARK YES
COPY F X
@REP 4
COPY X F
@END
23/73/25
Very optimized, but also quite hard to read with the many REPLs.

Finally, silentsnack has a 22-cycle solution which sadly doesn't fit into the size limit.

silentsnack posted:

code:
;XA
GRAB 300
COPY F X
REPL WEST_X
COPY X M
MARK SEND
COPY X M
COPY X M
HALT

MARK NORTHWEST;B30-B40
LINK 800
REPL HERE
LINK 800
JUMP HERE

MARK WEST_X;B20-B21
REPL SEND
LINK 800
REPL NORTHWEST
REPL SOUTHWEST

REPL HERE
LINK 801
JUMP HERE

MARK SOUTHWEST;B10-B00

LINK 802
REPL HERE
LINK 802

MARK HERE
GRAB 200
TEST F = X
TJMP YES
SEEK 5
TEST F = X
DIVI T T T
MARK YES
COPY F X
@REP 4
COPY X F
@END

;XB
REPL SOUTH
LINK 800
LINK 801
REPL MID_3
LINK 800
COPY M X
;B31-B41
REPL HERE
LINK 800
MARK HERE
GRAB 200
TEST F = X
TJMP YES
SEEK 5
TEST F = X
DIVI T T T
MARK YES
COPY F X
@REP 4
COPY X F
@END
HALT

MARK MID_3;B22
NOOP
NOOP
LINK 801
COPY M X
JUMP HERE

MARK SOUTH;B11-B01
LINK 800
LINK 801
LINK 802
COPY M X
REPL HERE
LINK 802
JUMP HERE

;XC
LINK 800
LINK 801
LINK 801
REPL NORTHEAST
LINK 801
COPY M X
REPL SOUTHEAST
;B33-B43
LINK 800
REPL HERE
LINK 800
GRAB 200
TEST F = X
TJMP YES
SEEK 5
TEST F = X
TJMP YES
HALT

MARK SOUTHEAST;B1X-B0X
LINK 802
REPL HERE
LINK 802
GRAB 200
TEST F = X
TJMP YES
SEEK 5
TEST F = X
TJMP YES
HALT

MARK EAST_X;B23
LINK 801
JUMP HERE

MARK NORTHEAST;B32-B42
COPY M X
REPL EAST_X
REPL SOUTHEAST
LINK 800
REPL HERE
LINK 800
MARK HERE
GRAB 200
TEST F = X
TJMP YES
SEEK 5
TEST F = X
DIVI T T T
MARK YES
COPY F X
@REP 4
COPY X F
@END
22 cycles, 125 lines (so it doesn't count for statistics)
Without going into the nitty gritty, the easiest way to understand this is by running it and seeing which EXA grabs which file.



As you can see labour is now divided among 3 EXAs.

I didn't really try but I can imagine getting this under the 75 size limit is impossible. It's easy to make the code (a bit) smaller, but not without having it be slower as well.


=== Valhalla ===

Oh.
Apparently, that Last Stop location is getting mobbed.
Processing.
People are choosing to believe the guide even when it's obviously wrong...
Why would they think Last Stop has actual good food?
And why did the guide change their behavior, but not the highway sign?




Everyone seems to be agreeing that the signs were vague. Yeah, that's always the problem with astrology, isn't it?

The signs were vague.

Hmm...
I guess it's easier to care about food than an abstract problem.


Nothing more to say about that, Ember? Well, ok.





Next up, a hacker battle against =plastered.

Time for the next opponent.
Are you ready?




These unanimous votes make it easy for me to keep count.

What, you're my coach now?

Would that be a bad thing?
That extreme baseball book you found me was full of interesting information.
Go on, champ, get out there and win.


I think I just threw up into my mouth a little bit.


OST: Getting Started

It's much the same as before. I first have to beat =plastered before I can play against other people.

The assignment reads:
To win this battle you must control a majority of the hosts for as long as possible.
To take control of a host, write any value to its #CTRL register. Reading from a #CTRL register will tell if you (1) or your opponent (-1) controls the host.
- Gain one point every cycle you control more hosts than your opponent.
- Lose one point every time one of your EXAs executes a KILL instruction.
For more information see "Hacker Battle Domination" in the second issue of the zine.


There's a 10 EXA limit, a 100 size limit, and each battle runs for 100 cycles.



Take note of the layout of the grid. It's basically linear, but both me and the opponent connect into the one-but-last position on our sides.

If I just let it run without doing anything, =plastered sends a single EXA in that first goes to the right (the end of the line), activates that node, and then goes down the path, activating each node in turn. Once it reaches my end it turns back and repeats its pattern.

As usual, hacker battles are kinda trivial to win.

I can actually just replicate =plastered's code one to one, like so:

code:
LINK 800
LINK -1

MARK START
@REP 8
COPY 1 #CTRL
LINK 800
@END

@REP 8
COPY 1 #CTRL
LINK -1
@END
JUMP START
The odds seem to be on my side because doing this, I win 52 out of 100 battles, which counts as an overall win. It's a C, though. I'm sure I can do at least a bit better.

Since =plastered doesn't bother REPLicating anything, I can just take the single-point loss of executing a well-timed KILL and win every battle with about 90 points.

code:
;XA

NOOP
LINK 800
LINK -1

MARK START
@REP 8
COPY 1 #CTRL
LINK 800
@END

@REP 8
COPY 1 #CTRL
LINK -1
@END
JUMP START

;XB

@REP 6
LINK 800
@END
KILL




Since the enemy EXA is gone after XB has done its thing, I don't even need the loop anymore. XA could set every #CTRL register once and die, and the rest of the cycles I just rack up points for free.

If you don't want to use a KILL, another solution is to just REPL an EXA into every host and have them set #CTRL in a small loop. Because the enemy can only set a single host at once and I overwrite it the next cycle, it'll never hold a majority of the hosts.

code:
LINK 800

REPL SOUTH
MARK NEXT
REPL SET

LINK 800
JUMP NEXT

MARK SOUTH
LINK -1

MARK SET
COPY 1 #CTRL
JUMP SET
Either way, this gives me an easy S+.

Do you like it? Winning?



And that brings us to our first vote already.




To be fair, any chump could've beaten you, =plastered.




Anyway, there's someone at the door. Sounds like my neighbor, Isadora.

To anyone keeping track, the game's opening cut scene took place on Saturday, October 4th, 1997. It's now Tuesday, November 4th, so exactly one month has passed in-game. The previous cut scene with Isadora was on October 8th.



Hey...
Sorry I haven't been in touch.
Things are a little crazy at work right now.
They promoted me to a new position, and... well there's just a lot going on.


I didn't notice this before but Isadora is actually wearing somewhat more formal clothes than in her previous cut scene. A nice touch.

Isadora offers me a plastic bag with something inside of it.
It's moderately heavy.
Maybe a couple books or something?

You like puzzle games, don't you?
I remember you being a fan.
I used to play games with my sister sometimes, but she moved to Japan a few years ago.
She sent me this game, but I guess I can't play it because of the region lock.
Looks fun, though.
I thought maybe you could get it working.
You always were good at that kind of thing.


Isadora sighs.

I have to go.
So much work to do... really hope this job doesn't eat me up.
Okay, bye for now.


Speaking of puzzles, the last page of the second zine has some nonograms, also known as Picross or paint-by-numbers puzzles. Here's the instructions and the first puzzle.



Since there's not much for the thread to do with these hacker battles, why not do one of these? If you want to share your result please use spoiler tags so other people get a chance to solve it for themselves too.

We'll check out Isadora's video game later. First things first - I can barely hold a controller right now since the Phage seems to be acting up in my left hand.

First it was your arm... now it's your hand.



And that brings us to our second vote.

Adbot
ADBOT LOVES YOU

Carbon dioxide
Oct 9, 2012

Part 24 - Mitsuzen HDI-10 - Left hand

=== Trash World Inbox ===

Thanks cardinale and biosterous for the nonogram submissions.

biosterous posted:


I'm not entirely sure what it depicts. It kinda looks like a disk in a cartridge, but it doesn't have the same shape as the Redshift disks. It might be for something we have yet to see.


=== Mitsuzen HDI-10 - Left hand ===


Do you like it? Winning?



All votes went to "Sure".

Sure.

Of course.
That's how human brains are designed, aren't they?
To like winning. So that you will always try to win.
It's a very simple design.
Any time I start to discredit myself for being too simple, I think about human brains and feel a little better.


Oof.



I mean, I don't know about clones, but the main problem with most conspiracy theories is that they assume a level of government competence we've never seen in real life, so I'll have to give hydro credit for that, at least.



Anyway, I need to take a break for a moment and first do something about my Phage infection.

First it was your arm... now it's your hand.



Three votes for "It's getting bad".

Yeah, it's getting bad.

I can see that.
Fortunately, it looks like you can easily access the nodes connected to your median nerve.
You'll just need to make a small incision in your ventral forearm.
Better watch out for that artery, though.




This is one of those choices where all options lead to the same dialogue.

Ugh.

Sounds like you're getting used to this.
That's good. It's nothing to be squeamish about.



OST: EXA Power

All right, here's what I need to do.

- There are three nerve signals that need to be relayed: muscle control (M), which runs from your central nervous system (CNS) to your hand (HND) and heat (H) and pressure (P), which run the other direction.
- For each signal, read a value from the input nerve and relay it to the output nerve. Repeat
ad infinitum.
- It is not necessary to leave no trace. Your EXAs should be written to operate indefinitely.
- For more information see "Debugging the Phage" in the first issue of the zine.


Hmm. Looks like the main challenge is one of 'bandwidth'. Using the M register is usually fast, but you can only have one global signal per cycle. Having EXAs running back and forth is slower, but you can have multiple, with the limit being that you can only have one EXA traverse any specific LINK at one time.

I think I'll start with the M solution and try to start optimizing for low activity since that seems easy, if not very fast.

Now, sending messages from all three nerves in parallel is hard. Getting that synced up so the right EXA gets the right message.
So, what if we make it simpler and just... don't?

For the low activity we need to start with one EXA and REPL from there to have the minimum amount of LINKs.

code:
LINK 800

REPL RIGHT
; LEFT
COPY -3 X
LINK -3
LINK -3

MARK RIGHT
COPY 3 X
LINK X
LINK X
This gets the original EXA (LEFT) to M-CNS, and the RIGHT one to M-HND. You'll see in a bit why I am using X for this.

Next, the LEFT EXA starts sending and the RIGHT one starts receiving, exactly 14 signals.

code:
LINK 800

REPL RIGHT
; LEFT
COPY -3 X
LINK -3
LINK -3

MARK SEND
COPY 14 T

MARK SENDLP
COPY #NERV M
SUBI T 1 T
TJMP SENDLP

MARK RIGHT
COPY 3 X
LINK X
LINK X

MARK RECEIVE
COPY 14 T

MARK RECEIVELP
COPY M #NERV
SUBI T 1 T
TJMP RECEIVELP
Just a couple countdown loops like we've seen before. Why 14? Well, that's the ugly hack I'm using here. The assignment's tests mark a program as OK when for each nerve 14 signals have been relayed. It doesn't care about the fact that my hand will fall off or something, and losing my hand is certainly worth it when aiming for some meaningless high score.

After "completing" the M nerve, it's time to LINK to the next nerves. Importantly, the RIGHT EXA needs to switch to sending and the LEFT one to receiving. When 14 signals have been sent in that round, they need to move on once more, but then RIGHT needs to keep sending. So I need a contextual swap of some sort.

Here is the complete working program.



This is where the X register comes in use. Once either the SENDLP or RECEIVELP loops are done, the EXA LINK to the next host in its direction (as stored in X), and then uses the value of X to determine whether it should be sending or receiving. So, in summary:
- In the first iteration, LEFT starts out sending and RIGHT starts out receiving.
- In the second iteration, the above test is first run, which sets RIGHT to sending and LEFT to receiving.
- In the third iteration, the test is run again, keeping RIGHT to sending and LEFT to receiving.
- After that, if the test didn't cut off the program after getting all expected signals, the EXA would try to link to a non-existent host and die.



This leads to a strange solution where the nerve signals are handled nerve by nerve.



Results: 181/29/9. The top percentile scores stand at 37, 24, and 9 respectively.

I can make a small size improvement by moving some LINK instructions to inside the SEND and RECEIVE marks.

code:
LINK 800

REPL RIGHT
; LEFT
COPY -3 X
LINK X

MARK SEND
LINK X
COPY 14 T

MARK SENDLP
COPY #NERV M
SUBI T 1 T
TJMP SENDLP

TEST X = 3
TJMP SEND

MARK RECEIVE
LINK X
COPY 14 T

MARK RECEIVELP
COPY M #NERV
SUBI T 1 T
TJMP RECEIVELP

TEST X = -3
TJMP RECEIVE

JUMP SEND


MARK RIGHT
COPY 3 X
LINK X
JUMP RECEIVE
181/27/9. I feel like this might be the right approach to get to the smallest size. There's actually some duplicate code in there. But I can't quite figure out how to get rid of it.

Let's look at cycle count instead.

My first idea involved files. I'm still using the "we only need 14 signals" hack, but this time, three EXAs read 14 signals each into a file, take that to the recipient nerve and write from the files to there. I unrolled as many loops as I could, although the fact that the different EXAs need to move in different ways makes it hard to keep track of their state.

code:
LINK 800
REPL LEFT

LINK 3
LINK 3
LINK 3
REPL H

LINK 3

MARK READ
MAKE
@REP 14
COPY #NERV F
@END

TJMP SKIP
LINK -3
LINK -3

MARK SKIP
@REP 6
LINK -3
@END

MARK WRITE
SEEK -9999
@REP 14
COPY F #NERV
@END

MARK H
COPY 1 T
JUMP READ


MARK LEFT
LINK -3
LINK -3
MAKE

COPY 2 T

MARK READ2
@REP 7
COPY #NERV F
@END
SUBI T 1 T
TJMP READ2

@REP 4
LINK 3
@END

JUMP WRITE
The top half of this code handles the right-going EXAs (for H and P). H is REPL'd and sets a flag in T, while P LINKs to its node. They then read the 14 entries to a file, and, making use of the flag that was set in T, they make their way to their receiving nodes, after which they just write the data from the file. LEFT uses a copy of the file code so that afterwards it can LINK the right way without any additional logic.

After the EXAs are done they fall through into some other code that tries to do things, but it doesn't matter, by then the tests have finished.

Runs at 47/72/24, which is still 10 whole cycles away from the top percentile. Most of it wasted on walking back and forth. I think I can't get any better without involving the M register.

At this point I realized that while syncing the M register is hard with three pairs of EXAs, it's surprisingly easy with two. Since an M communication step always takes two cycles (for the first EXA to publish it and then for the second to read it), using two pairs you can send one message each cycle, as long as you make sure to alternate every cycle.

So, what if we change two of the nerves to make use of M, while keeping the 'file transfer protocol' EXA for the third one? Because walking takes so much time, it's probably best to use the file EXA for the M nerve, since that one's closest.

I'll show you the end result because my intermediates were very confusing, dirty code.



XA is the file EXA. It's now much simpler after I removed all the logic. LINK to M-CNS, read 14 values, LINK to M-HND and write them.

XB handles the two other nerves. The rightbound (top half) part simply goes to the nerves and starts reading from them. Notice that the furthest EXA is exactly one cycle off from the other one (through the LINK 3 just after the REPL, so the H nerve starts sending first, causing the P one to wait a cycle, and then send, and since there's no loops or anything, just 14 repetitions of the COPY to M, they will keep alternating.

For the left side, the two EXAs also are one cycle out of sync, but there it's required to use an actual two-cycle loop. If they read from M every single cycle, which EXA reads what becomes completely unpredictable.

This solution started off as a one-EXA one too, but I noticed that after I'd cleaned up the code, the file transfer was the bottleneck by just a few cycles. Making it into a dedicated EXA solved this by removing the need for it to REPL several times. I still had to make sure to send it ahead of XB.

Anyway, this runs at a nice score of 37/70/19.



By the way, from this solution it isn't too hard to make something that actually keeps running forever, although it'll be slightly slower.

code:
;XA

LINK 800

MARK START

LINK -3
LINK -3

MAKE
MARK READ
@REP 7
COPY #NERV F
@END

@REP 4
LINK 3
@END

SEEK -9999
@REP 7
COPY F #NERV
@END

LINK -3
LINK -3
WIPE
JUMP START

;XB

NOOP
LINK 800
REPL LEFT

LINK 3
LINK 3
LINK 3

REPL MREADLOOP
LINK 3
MARK MREADLOOP
@REP 9
COPY #NERV M
@END
JUMP MREADLOOP


MARK LEFT
LINK -3
LINK -3
LINK -3
REPL MWRITELOOP
LINK -3

MARK MWRITELOOP
@REP 9
COPY M #NERV
NOOP
@END
JUMP MWRITELOOP
All I did was put a big loop around XA that resets it, and also make XB's MREADLOOP an actual loop. This means every so often MREADLOOP takes an additional cycle, so I had to unroll the MWRITELOOP to the same amount, and add a NOOP so the 2-cycle read keeps working. The REP amounts don't really matter as long as the read/write REPs within each EXA are the same. The above code runs at 49/74/24 in case you're interested.


I have the top percentile score in cycles and activity, but didn't quite get it in size. As always it'll be interesting to see what you come up with.

Do you ever feel like it's a losing battle?
This constant effort to maintain your physical body.




The first vote.

Next time, we'll finally get to take a look at this video game Isadora got us from Japan, a game for the Sawayama WonderDisc console. Let's see what Ember has to say.

WonderDisc games are restricted by region locks.
Why?




And that's the second vote.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply