The Let's Play Archive

EXAPUNKS

by Carbon dioxide

Part 30: King's Ransom Online

Part 30 - King's Ransom Online

=== Trash World Inbox ===

Last time, we had to write an algorithm to calculate the supposedly winning player in Extreme League Baseball.

It was quite a hard one to optimize, but as always you people came up with some ideas I'd never thought of.

GuavaMoment posted:

code:
XA:
MAKE
VOID M
COPY M X
COPY M F
JUMP START
MARK STUFF
VOID M
VOID M
MARK START
TEST M > X
FJMP STUFF
COPY M X
SEEK -1
COPY M F
JUMP START

XB:
LINK 800
GRAB 199
MARK START
COPY F T
REPL CALC
NOOP 	*delays the next calculating EXA from coming out too soon*
NOOP
NOOP
TEST EOF
FJMP START
DROP
LINK -1
COPY 4 T 	*a delay loop to slow down the upcoming kill command until everything is done*
MARK LOOP
SUBI T 1 T
TJMP LOOP
KILL

MARK CALC
GRAB T
SEEK 1
ADDI F F X
ADDI F X X
DIVI X 3 X
MULI F F T
DIVI T F T
ADDI X T X
SUBI F F T
MULI T 20 T
ADDI X T M
SEEK -999
ADDI X T M
COPY F M
86/47/3.
As GuavaMoment explained, the main trick is to have XA store the value in X. That way, checking if a new value is higher only takes one cycle, as long as you send the value again so it can actually be stored if needed. Doing this also saves time at the end because you don't need to clean up the value from the file.

silentsnack posted:

Similar to the Wonderdisc puzzle, we can reduce cycles for logic-jumps by @REPing some of the slowest and most repetitive processing

code:
;XA
LINK 800
GRAB 199

@REP 10
COPY F T
REPL DO_STUFF
@END

MARK DO_STUFF
GRAB T
SEEK 1
ADDI F F X
ADDI X F X
DIVI X 3 X
MULI F F T
DIVI T F T
ADDI X T X
SUBI F F T
MULI T 20 T
ADDI X T X
TEST X > 341
DIVI X T M
MODE
SEEK -9
COPY F M


;XB
LINK 800
MAKE
COPY M X
MODE
JUMP COPY

MARK MAX
SEEK -2
MARK COPY
COPY M F
COPY X F
MARK NEXT
@REP 3
MODE
SEEK -1
COPY M X
TEST X > F
MODE
TJMP MAX
VOID M
@END
JUMP NEXT


;XC
COPY 34 T
MARK WAIT
SUBI T 1 T
TJMP WAIT
LINK 800
NOOP
KILL
KILL
KILL
GRAB 400
LINK -1
SEEK 1
VOID F
79/85/7
The @REPs sound like a simple solution but they really aren't. This solution is so fast because there's the minimum amount of waiting between EXAs. And that's possible because both the algorithm EXA (XA) and the comparator (XB) are in the same host. They start in global mode, but as soon as an XA REPL sends the first value they both immediately switch to local mode. That means all other XA REPLs just have to wait their turn while this particular XA can safely send the name of the player to XB without interruptions. As soon as it's done, XB switches back to global and gets the next value.

As a side effect, this makes the timing independent of the number of EXAs, meaning that TEST X > 341 trick can be used so EXAs with a score that never wins die immediately. I actually thought of that but couldn't get it to work because it kept throwing off the timing.

silentsnack posted:

As for size optimization, what if we compared scores directly instead of sending a bunch of busywork transmissions and VOIDing them?
code:
;XA -- LOCAL
LINK 800
GRAB 199

MARK ARTICHOKE
COPY F T
REPL BROCCOLI
JUMP ARTICHOKE

MARK DISJUNCTION
MODE
COPY F M

MARK BROCCOLI
GRAB T
SEEK 1
ADDI F F X
ADDI X F X
DIVI X 3 X
MULI F F T
DIVI T F T
ADDI X T X
SUBI F F T
MULI T 20 T
ADDI X T X

MARK CHIPOTLE
COPY X M
SEEK -9
TEST MRD
FJMP DISJUNCTION
TEST X > M
TJMP CHIPOTLE


;XB -- GLOBAL
MAKE
COPY M F

;XC -- LOCAL
LINK 800
VOID M
110/32/2
I'm pretty sure this one gets its high score because of the veggie-themed labels.

Joking aside, this solution hurts my head. After running the algorithm, each EXA sends the result on local M. The very first one gets voided by XC to prevent the solution from getting stuck. It then checks if another one is sending on M. The timing works out quite precisely - if there's nothing on M this is the last remaining EXA, and the DISJUNCTION jump makes sure the name is written to the file by XB.

If there a value on M, this EXA tests if it has a greater algorithm result than the incoming value. If not, it dies. Otherwise, it jumps back to CHIPOTLE and sends its high score again. At that point the other EXA will be waiting to TEST and since it will have the smaller value, it will die. Another EXA will be ready to send its M and the pattern repeats.

So the EXAs are literally playing an elimination tournament against each other, the last one standing will have the high score. Very clever.

I'm happy to see that nobody changed the core math code. That means I didn't miss any obvious optimizations in there.


=== King's Ransom Online ===

Okay, uh. Hmm.
Processing.
It turns out sports betting is not, in fact, a good way to make money.
You should have said something.




Four votes for each between the threads! It comes down to a coin toss this time.

I thought you'd be smarter than that.

What does that mean?
Don't put this on me.
This is all happening because I'm trying to help you.
Let's continue.


Don't gaslight me, lady.





Next up is King's Ransom Online, the massive multiplayer game that's all the rage right now.

Did you know people pay real money for items in online games?



One vote for "it's pretty common", three for "Real money..." and four for "Losers". Feeling snarky, I see.

Losers...

It's apparently a booming business.
Some people even make their living from games this way.
Normally, it takes a lot of skill and perseverance.
But if someone were able to change the ownership of these digital objects...
Theoretically, that person could become very wealthy overnight.
I'm not suggesting that you do that, of course.
But I am suggesting you try.


I mean, I do need the money. And it's not like I'm stealing real objects, right?

Let's jack in.


OST: EXA Power

The assignment:
- Reset the ownership of all castles and sub-buildings to P00000 (file 300), the player ID for unowned buildings.
- To ensure that there are no witnesses you must first disconnect all connected players. Terminate every EXA in every host before changing any castle or sub-building files anywhere in the network. If you leave an EXA alive in one host while changing a file in another you will fail the task.
- For more information see "Network Exploration: King's Ransom Online" in the second issue of the zine.


More information in the zine.



Alright, ignoring all the old-timey text and that ad for a wristwatch, looks like players are represented by EXAs, and there's two main types of files: ones representing buildings and ones representing weapons.

Each host has a building 200 which is a castle, and a bunch of sub-buildings. Generally, the file IDs of sub buildings are in the castle file:



However, there are cases where sub-buildings have their own sub-buildings so I will need to check for those as well.

I can't see the details of the weapons yet because they're all being held by foreign EXAs. What I do see is two counters in the Goals: there are 14 EXAs to terminate and 26 buildings to clear. This differs a bit between test runs. What seems consistent is there's always between 2 and 6 buildings per host, including the castle, and between 1 and 3 EXAs.

Well, let's start by terminating the EXAs.

code:
COPY 800 X
LINK X

@REP 5
REPL LINK
ADDI X 1 X
@END

MARK LINK
LINK X

KILL
KILL
KILL
Some basic code to get an EXA in each host from 800-805, and then three KILLs to clear them out.



You can see the EXAs do represent players - killing them makes their characters disappear from the screen in the top right. The weapons are as described in the zine. Unfortunately, both the weapons and buildings have IDs in the 200-300 range. I won't be able to just loop over all file IDs to only change the buildings.

I'll just start with the obvious solution then, grabbing the castle, updating its ownership, and grabbing any sub-buildings from its list.

I already got an EXA in every host, and the assignment says I can't start messing with the buildings until the foreign EXAs are gone, so I'll just use the same ones.
code:
;XA
COPY 800 X
LINK X
COPY M T

@REP 5
REPL LINK
ADDI X 1 X
@END

MARK LINK
LINK X

KILL
KILL
KILL

COPY 200 X

MARK GRABSUBFILE
GRAB X
SEEK 2
COPY T F
MARK SUBFILELP
COPY F X
REPL GRABSUBFILE
JUMP SUBFILELP

;XB

GRAB 300
COPY F M
I use XB and a single use of the M register to load the null player ID into XA before it starts splitting, but without delaying it. XA uses the T register for this player ID because conveniently, it doesn't need it for anything else.

After the KILLs, the castle file ID (200) is copied into X, and then my EXA grabs the file, seeks to the player ID position, and overwrites it with the null id. For each following sub-building id, it loads the value into X and makes a new EXA that grabs it. This recursive code should get all buildings, no matter how deep it has to go.

However, this solution doesn't quite work. The first XA already starts messing with the buildings before the last one has killed all the foreign EXAs.

I can just start with an ugly workaround, just have everything wait until all foreign EXAs are gone.



Six NOOPs or a short loop do the trick.



48/35/25 as an initial score. The top percentiles are 32, 26 and 25.

What's also interesting is the huge distribution in size. I can imagine that this puzzle is much more devious if you can't figure out recursion. Luckily, I'm quite familiar with it.

Let's look at optimizations. First of all, to lower the cycle count, instead of having that whole list of NOOPs I can make it conditional - earlier EXAs have to wait longer.
code:
; XA

COPY 800 T
LINK T
COPY M X

@REP 5
REPL LINK
ADDI T 1 T
@END

MARK LINK
LINK T

KILL
KILL
KILL

SUBI 807 T T
DIVI T 2 T

MARK WAIT
SUBI T 1 T
TJMP WAIT

COPY 200 T

MARK GRABSUBFILE
GRAB T
SEEK 2
COPY X F
MARK SUBFILELP
COPY F T
REPL GRABSUBFILE
JUMP SUBFILELP

;XB

GRAB 300
COPY F M
46/34/25

I can shorten the REPL loop by reading hardcoded LINK ids from M instead.

code:
;XA

LINK 800
COPY M X

@REP 5
REPL LINK
@END

MARK LINK
LINK M

KILL
KILL
KILL

NOOP
NOOP
COPY 200 T

MARK GRABSUBFILE
GRAB T
SEEK 2
COPY X F
MARK SUBFILELP
COPY F T
REPL GRABSUBFILE
JUMP SUBFILELP

;XB
GRAB 300
COPY F M

;XC
REPL Y
REPL X
COPY 800 M
HALT
MARK X
COPY 801 M
HALT
MARK Y
NOOP
COPY 802 M

;XD
REPL Y
REPL X
COPY 803 M
HALT
MARK X
COPY 804 M
HALT
MARK Y
NOOP
COPY 805 M
39/45/25. XC and XD need two idle cycles anyway so that XB can send the player id first. I use the idle cycles for set up with REPL instructions.

Sending the player ID over M costs two cycles at the top, while we're wasting cycles with NOOPs further down. If I send that message later, I need to send it 6 times but with some smart timing I can save two cycles.

code:
;XA

LINK 800

@REP 5
REPL LINK
@END

MARK LINK
LINK M

KILL
KILL
KILL

NOOP
COPY 200 T
COPY M X

MARK GRABSUBFILE
GRAB T
SEEK 2
COPY X F
MARK SUBFILELP
COPY F T
REPL GRABSUBFILE
JUMP SUBFILELP

;XB

GRAB 300
COPY F X
NOOP
NOOP
NOOP

@REP 5
REPL SEND
@END

MARK SEND
NOOP ; For some reason this timing is fastest
NOOP
COPY X M

;XC and XD same as before.
37/56/25. I tried changing XC and XD to start a bit earlier but didn't manage to save any cycles that way. I think this is a good place to leave this and start looking at size.

Starting from the lowest size code above (the 34 LoC one), the first thing I noticed is that the two lines SUBI 807 T T, DIVI T 2 T aren't strictly necessary. As long as the file fiddling doesn't happen too early it's fine. Leaving those out just makes the WAIT loop run 800-odd times and that makes it slow, but it still works.

And since I need that wait loop anyway, why not put the KILL in there? That way it gets executed plenty of times but it saves two additional KILL lines.

code:
;XA

COPY 800 T
LINK T
COPY M X

@REP 5
REPL LINK
ADDI T 1 T
@END

MARK LINK
LINK T

MARK WAIT
KILL
DIVI T 5 T
TJMP WAIT

COPY 200 T

MARK GRABSUBFILE
GRAB T
SEEK 2
COPY X F
MARK SUBFILELP
COPY F T
REPL GRABSUBFILE
JUMP SUBFILELP

;XB

GRAB 300
COPY F M
54/30/37. I replaced that SUBI in the WAIT with a DIVI because it's a much faster way to count down from very large numbers. Since DIVI rounds down, it'll hit exactly zero no matter what. Any number between 2 and 5 works for this solution.

I tried some more things, such as putting the REPL LINK in a loop. That didn't work out because T and X are already occupied and adding a counter in some other way costs a lot of lines.

It's also possible to get rid of getting the subfile ids from each file. Use a simple loop counting down from file id 300 and trying every id in turn. However, you do need an extra check to not write to the weapon files. That's doable - just check if the second value is a number or not. But that check made it longer than the current solution. So this is the best I got.

---

Finally, there's activity. I already have top percentile activity, but could I go beyond? Well, no matter what I need 7 LINKs to get the EXAs in position. Add to that 3 KILLs in each of the 6 hosts for the 25 score.
However, the test with the highest number of foreign EXAs has 17 of them. That means that theoretically, you could save one more activity point.

To do so, I'd need to figure out how many foreign EXAs there are per host. How would you do that? You can't communicate with them. The only thing I can think of is fill the hosts with your own EXAs. Once a host is full, both LINK and REPL instructions block until a space is freed.

I spent some time (actually, way too much time) attempting to build a solution. It couldn't quite get it working, but I feel I'm quite close. I'll share what I have, maybe someone in the thread has the missing link.
code:
;XA LOCAL

COPY 800 X
LINK X

@REP 5
REPL LINK
ADDI X 1 X
@END

MARK LINK
LINK X

; There's a single EXA in each host now.

REPL COUNTER ; This one starts by waiting
REPL FILLER ; also starts by waiting

COPY 200 X

MARK GRABSUBFILE

; Grab all the files recursively. Once you have them jump to END. Do not modify files yet.
GRAB X
SEEK 3

MARK SUBFILELP
TEST EOF
TJMP END
COPY F X
REPL GRABSUBFILE
JUMP SUBFILELP

MARK FILLER

; Wait until all files are grabbed
COPY 20 T
MARK WAIT
SUBI T 1 T
TJMP WAIT


; Replicate until all squares are filled, but not more than 8 times. After replicating, each EXA goes to END.
COPY 8 T 

MARK FILLRUP
SUBI T 1 T
NOOP
FJMP END
REPL FILLRUP ; This will block once the squares are filled.
JUMP END

MARK COUNTER
; Wait until GRABSUBFILE and FILLER are done.
COPY 36 T
MARK WAIT2
SUBI T 1 T
TJMP WAIT2

COPY 0 X ; Add all local M values together. There are sent from END, below.
MARK NEXTM
TEST MRD
FJMP NEXTVOID
ADDI M X X
JUMP NEXTM

; After reading from M, the EXA who sent it halts, freeing up a space. That causes FILLRUP to continue until it's at 8 repl steps.
; This voids their M messages so they stop immediately.
MARK NEXTVOID
NOOP
TEST MRD
FJMP ENDVOID
VOID M
JUMP NEXTVOID

MARK ENDVOID
; At this point all EXAs except this one are gone from the host. This one has the total of the M messages in X.
; X = 12 squares minus this EXA, minus the EXA where REPL was blocking, minus the number of foreign EXAs
; so 10 - X = number of foreign EXAs.
SUBI 10 X T

; Kill em.
MARK KILLOOP
KILL
SUBI T 1 T
TJMP KILLOOP

; Switch to global mode, grab the files again, request the player ID and update the files.
COPY 200 X
MODE

MARK GRABSUBFILE2
GRAB X
SEEK 2
COPY M F

MARK SUBFILELP2
COPY F X
REPL GRABSUBFILE2
JUMP SUBFILELP2

MARK END
COPY 1 M

; ---- XB ---- (global)

; Wait until all foreign EXAs are killed (has to be timing based, limited options here)
; Then start sending the player ID on global M. 36 times = the maximum number of files in any test.

GRAB 300
COPY F X

COPY 88 T
MARK WAIT
SUBI T 1 T
TJMP WAIT

COPY 36 T

MARK LOOP
COPY X M
SUBI T 1 T
TJMP LOOP

; ---- XC ---- (global)

; Wait until all files are updated, then VOID M any remaining sends from XB.
; KILL is not an option.

COPY 155 T
MARK LP
SUBI T 1 T
TJMP LP

MARK ENDLOOP
VOID M
NOOP
NOOP
TEST MRD
TJMP ENDLOOP
94 lines out of 100 maximum. As I said, it doesn't work.
If you replace SUBI 10 X T with COPY 3 T to hardcode the number of kills, it does run with an activity of 25, proving that the logic is sound.

It's necessary to create file grabbing EXAs twice, because KILLs prefer own EXAs.

The one problem I couldn't resolve is the fact that as soon as the XA counter starts reading from M, those file grab and filler EXAs start dying, so the blocked REPL one starts running again, and sends to M before the counter is done counting, causing a wrong total. I tried a lot of different ways to slow them down, but nothing quite worked.


That's it for optimizations. But there's one more thing. This level has a Steam achievement tied to it, but it's quite tricky to get. It is called KLEPTOMANCER, and you might figure out it's for this level by its description, Verily hath every item been unduly purloined. What you need to do is move all weapons into the home host while leaving buildings alone. Since sub-buildings aren't locked to a host this takes a bit of work.

At least for an achievement none of the regular rules apply - the EXAs don't need to finish, for instance.
code:
;XA

COPY 800 X
LINK X

@REP 5
REPL LINK
ADDI X 1 X
@END

MARK LINK
LINK X

KILL
KILL
KILL

COPY 199 X

MARK GRABFILES
ADDI X 1 X
REPL GRABAFILE
JUMP GRABFILES


MARK GRABAFILE
GRAB X
SEEK 1
TEST F < 9999
DIVI 1 T T
LINK -1
LINK -1

;XB

GRAB 300
WIPE
XB wipes 300 to make space for the 9 weapons in the map. XA simply loops through all possible file IDs (starting from 200), grabs them, checks if the second value is a number (TEST F < 9999 is true for any number but false for any text; and only weapons have a number in the second position), and if so takes it home.



I think this is right. Steam achievements don't trigger a second time after unlocking them in a previous playthrough, so I hope I understood the unlock requirements correctly.

Moving on...

Oh. They just shut the whole game down.
Something about "hackers" or something?
I guess that means you.
People are taking this so seriously.




The first vote.

You know what really moves people?
Art.




And the vote for the intro for next time.

A fun fact to end with: there's an annoying little bug in the game, where if you beat a level, and then directly quit out of the game without going to the 'desktop' (level select with Ember etc.), the next part of the CHATSUBO chat doesn't trigger. I have no idea if it catches up eventually or not.

While working on this update I shut down the game without properly exiting, and the bug triggered. Since I like to keep the chat in sync with the rest of the story, I had no choice but to resort to hacking the save files to "unsolve" the level so I could re-trigger the chat. It feels extremely meta, but there you have it.