The Let's Play Archive

EXAPUNKS

by Carbon dioxide

Part 15: Mitsuzen HDI-10 - Heart

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!



Damn, the phage is getting to my heart. Gotta do something quick.

So you're going to be hacking your own heart.



Two votes for the third option.

I'm a little scared.

Why?
What happens if your heart stops beating?
Don't tell me. You die?
Processing.
Huh. I guess that explains some things.
At least all you need to do is make it beat.




It's, uh, it's a little more complicated than that, Ember. I thought you studied humans, how do you not know our basic biology?

Human physiology is fascinating.
I should learn more about it.


...Why do I feel like I just said the wrong thing?

Either way, let's get started.


OST: EXA Power

It's been a while since I last hacked my body, let's see if I remember how this works.

I have to do the following:
- Read a value from the nerve connected to your central nervous system (CNS) and make your heart beat by writing a sequence of values to your sinoatrial (SA-N) and atrioventricular (AV-N) nodes as indicated in the HDI-10 I/O log when holding the "SHOW GOAL" button. The length of each sequence of values should be equal to the value from the CNS divided by -10. Repeat ad infinitum.
- It is not necessary to leave no trave. Your EXAs should be written to operate indefinitely.
- For more information see "Debugging the Phage" in the first issue of the zine.


I've shown that article from the zine already, nothing new there. I have to write values like this:



Alright, if I understand correctly, when I get -42 as input, I have to write -42 / -10 = 4 (rounded down) values to the outputs. The first value to SA-N has to be 40, the others are -70 (neural resting potential, according to the zine). For the AV-N output the first value has to be -70, the SECOND 40, and the rest -70.

Note that EXA cycles are much faster than a heartbeat, so I don't have to sync cycles perfectly, as long as I get the order of outputs right.
code:
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.