The Let's Play Archive

EXAPUNKS

by Carbon dioxide

Part 19: A SECRET TO EVERYBODY

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.