ScottKit producing misplaced/out-of-order strings

Something’s been bugging me about how tidy this is and it didn’t sit right and I think it’s finally hit me.

Your test isn’t simulating a real-world situation because your second print statements are reusing strings, which means ScottKit can reuse the messages. If you replace the first few lines with unique messages in the second print statement:

occur
	 print "1"
	 print "a"

occur
	 print "2"
	 print "b"

occur
	 print "3"
	 print "c"

occur
	 print "4"
	 print "d"

occur
	 print "5"
	 print "e"

then run, you’ll find you get various broken messages.

I grant that there might still be potential to break the barrier, but in ScottKit-land it’s very clear right now that using more than one print message per line will get you in trouble past 99 unique messages, and when coding non-trivial conditions there are places where the compiler gets confused and seemingly demands additional print statements.

But the fact that ScottKit and Scottfree can both at least handle displaying those 380+ top-slot messages is a good sign.

I’m trying to wrap my head around this but is it correct that:

Print "A"
print "B"
Print "C"

Use 3 messages, but

print "A\nB\nC"

Only uses one message. If so, what is the drawback with using the second method?
(In rotten_v3, jcompton uses the first example on line 372-, but the second example on other places)

Your interpretation of those scenarios is correct: top example counts as three messages, bottom example is just one. Also, messages can be reused:

action talk ghost when here ghost and at platform
	print "If thou didst ever thy dear father love--
Revenge his foul and most unnatural murder
Send Claudius to HELL
And bring me the *treasures* of my ENEMIES!" # all of this is message 1
	pause
	print "A ghostly voice cries SWEAAAAAARRRRR!" # message 2
	set_flag 2
	put ghost purgatory
	put ophelia manor

occur 2% when flag 2
	print "A ghostly voice cries SWEAAAAAARRRRR!" # also message 2

Now, that’s a situation where I am deliberately splitting up the messages, both so I can do the little pause bit and because although I never re-use that message a third time, it saves a few bytes by not having that text appear twice in two different messages.

Unfortunately there’s a limitation in ScottKit where it struggles to automatically split up certain complex condition and action lists using continue , so in several cases I am forced to do this:

action give script when here players and flag 5 and present script
	print "We can play the MURDER OF GONZAGO!"
	print "They exit to the throne room"
	print "The play is ABOUT to begin!" # ugh, 3 messages, I would prefer just 1!
	put players throneroom
	put script throneroom

This is the most awful case, what should be just one print statement (and hence one message) had to be split into three because with fewer, things like this happen:

/Library/Ruby/Gems/2.3.0/gems/scottkit-1.6.0/lib/scottkit/compile.rb:463:in block in generate_code': condition has 7 conditions (RuntimeError)

For whatever reason, ScottKit can work its way around these problems if you give it more print statements.

I’m losing 8-9 messages in Ghost King to the problem and am hopeful that it’s an easy fix for Mike if he has a minute to replicate it with my filed issue.

I peekeed at the Ruby code to see if I could find what caused the error and why your workaround “fixes” it.

DISCLAIMER: I have never programmed in Ruby. In fact this is the first time I see Ruby source-code ever.

On row 462-463 in compile.rb the error is triggered if number of conditions and the number of action.arguments exceeds 5. I’m not sure what counts as action.arguments but obviously the below action triggers it:

action give script when here players and flag 5 and present script
	print "We can play the MURDER OF GONZAGO!\nThey exit to the throne room\nThe play is ABOUT to begin!"
	put players throneroom
	put script throneroom

But WHY does the following work?

action give script when here players and flag 5 and present script
	print "We can play the MURDER OF GONZAGO!"
	print "They exit to the throne room"
	print "The play is ABOUT to begin!"
	put players throneroom
	put script throneroom

A bit higher up in compile.rb (line 333-) there is a comment “Instructions must not exceed four per batch”. Hmm, interesting…

If I decompile your “fixed” version I get the following:

action GIV SCR when here Players and flag 5 and present Script
	print "We can play the MURDER OF GONZAGO!"
	print "They exit to the throne room"
	print "The play is ABOUT to begin!"
	continue
	
occur 0%
	put Players Room1
	put Script Room1
	comment "cont"

There is a document “docs/notes/continue-action” in the source-code that leads me to believe that the continue instruction doesn’t need to be the fourth instruction and therefore it should be possible to write the following to save two messages in this instanse.

action give script when here players and flag 5 and present script
	print "We can play the MURDER OF GONZAGO!\nThey exit to the throne room\nThe play is ABOUT to begin!"
	continue

occur 0%
	put players throneroom
	put script throneroom
	comment "cont"

I don’t have the compiler at hand so I can’t try it out (I’m at work…) until tonight so this is just a theoretical take, but maybe this is an opening to work around the compiler to save messages.

2 Likes

Your solution does work–I’d reached a similar conclusion by looking at the disassembly of this and other SA games. Just tested it out and it plays fine and does indeed reduce the message count by two precious slots.

And if there was zero chance of the compiler getting fixed I would probably embrace it and put a bunch of comments warning me to be extra-super-special careful when altering or moving that code because it’s inherently more fragile. But since the project is still supported, I will defer to the docs which strictly forbid this kind of maneuver and patiently (saw your GitHub note, Mike) encourage compiler enhancement instead. :slight_smile:

continueNever use this action . It is used internally to allow a sequence of actions that is too long to fit into a single action slot, but there is no reason at all why you would ever explicitly use it: in fact, this kind of low-level detail is precisely what ScottKit is supposed to protect you from. I don’t know why I’m even mentioning it.

2 Likes

Cool!

I guess if you’re careful about you could force the compiler to split the action by inserting dummy instructions (for example setting an unused flag) to push the instruction-count over 4.

I absolutely agree that is a type of optimization that it would be great if the compiler did it.

1 Like

I personally disagree with the "continueNever use this action" statement. This is how the engine works, and disregarding the use of “continue” opens up can of worms in terms of possible complexity and hard-to-diagnose bugs. Just my opinion, as a person obsessed with knowing all the the low-level details :slight_smile:

Using the “continue” command anywhere among the first 4 commands in an action or occurrence will set a flag, that simply interprets any number of consecutive occurrences with a 0% chance of execution as the thing to execute next. Perhaps “occur 0%” could have been better named “subroutine” or something, and “continue” could have been named something like “execute_subroutines”.

I don’t want to belittle Mike’s awesome work on ScottKit with any of these comments, btw. Having made my own editor and interpreters, i know the amount of time, effort and thinking can go into something like this, and I love how ScottKit works and what it allows you to do.

1 Like

These viewpoints are coming at the same topic from different angles…

I don’t think that Mike-as-voice-of-ScottKit is saying “continue is bad”, he/it is obviously aware that it’s a fundamental part of the SA system. He’s/it’s saying “if you’re using ScottKit, you shouldn’t have to explicitly use continue.” (which I agree with, and obviously the compiler is trying to address, it’s just failing to do so in certain corner cases which I guess haven’t been documented until I got this terrible-wonderful idea.)

That’s the thing… it does, some of the time. That’s how I was able to blithely code away for quite a while without noticing it, and presumably it never came up in the handful of proof-of-concept games he worked on over the long history of this toolset.

2 Likes

“Action arguments” are the parameters passed to commands. They’re defined in the same place as conditions: when you have put players throneroom, that actually compiles as a single opcode “put”, which is interpreted as “put the first thing referenced in a condition, into the second thing referenced in a condition”.

Since your condition is here players and flag 5 and present script, the first thing referenced is the players, the second thing referenced is the number five, and the third thing referenced is the script. So it gets compiled into here players and NOP throneroom and…, where NOP is a condition that does nothing and always succeeds.

This is why adding the “continue” fixes things! Your original code:

action give script when here players and flag 5 and present script
    print "message here"
    put players throneroom
    put script throneroom

needs to compile into something like this:

…when
    here players and NOP throneroom
    and present script and NOP throneroom
    and flag 5

because it needs the first four parameters to be “players, throneroom, script, throneroom” in that order. From the error messages you mention, I’m guessing ScottKit is going about it an even simpler way, which gives seven conditions:

…when
    NOP players and NOP throneroom
    and NOP script and NOP throneroom
    and here players and present script and flag 5

And this is just too many for the format to support on a single action.

When you break it up with a continue, though, things get better!

…when here players and present script and flag 5
    print message [whatever number]
    continue

continuation when NOP players and NOP throneroom and NOP script and NOP throneroom
    put
    put

In this case, there are only four parameters involved in a single action, which is fine.

Now, this does suggest another enhancement for the compiler—it should be able to reorder conditions in order to repurpose their parameters. This would let this compile without a continue at all, since the format supports five conditions, and here players and NOP throneroom and present script and NOP throneroom and flag 5 is five. But the details might be unpleasant; I might poke at the source and see how difficult it would be later.

3 Likes

…never mind, I spoke too soon. I’d thought that the nouns used in all conditions could be used as action arguments, but it seems it can only use ones with the condition NOP for that.

So ignore that part. A continue is definitely needed here.

Right, right. The question is: whose job is it to insert that continue?

It’s clear enough that the problem crops up most often with three conditions on the action: most, but not all, of the instances of this problem in my GHOST KING code show up where there are three conditions. And sometimes the fix is to change from one print to two, but occasionally it requires three prints.

And (from my not-entirely-casual but not exhaustive) investigations, it really does seem like there’s something very particular about print coaxing the compiler to fix the problem.

For example, Henrik suggested “setting an unused flag” but that won’t cut it:

# GITHUB ISSUE 38 again

action monologue when flag 6 and counter_gt 25 and counter_le 30
	print "*Am I then reveng'd when he is FIT\nand SEASONED for his passage? No!"
#	print "UP, sword! Prolong thy sickly days*\n\nClaudius mumbles amen and SKULKS off" comment this bad boy out and try set_flag 20
	put claudius tourney
	clear_flag 6
	set_counter 0
	put ophelia hallway

The second print is there to solve the compiler bork.

Try the “dummy flag” fix - comment out the print and add set_flag 20.

No matter where I put set_flag 20 in that list of actions, the result is the same: compiler bork complaining of 6 conditions. But the second print is some kinda magic, it seems.

In classic “the doctor tells you ‘if it hurts, don’t do it’” fashion, yes, looking for places I can avoid using three conditions is good hygiene and I did go through and spot an easy one on my own. But, would be nice if the compiler either did more to fix the problem, or was more emphatic about “we cannot fix this, go solve your code problem another way.”

I’m certainly no expert, but if I understood this right then you need to minimize the total number of parameters, and set_flag takes a parameter. Would a pair of swap_loc_default do the trick?

2 Likes

…sort of…

It looks like swap_loc_default is deprecated and has been replaced by swap_room and it took some massaging but this does compile and seems to play correctly (in the SK built-in interpreter, and nothing looked wrong in scottfree either)

action monologue when flag 6 and counter_gt 25 and counter_le 30
	print "*Am I then reveng'd when he is FIT\nand SEASONED for his passage? No!"
#	print "UP, sword! Prolong thy sickly days*\n\nClaudius mumbles amen and SKULKS off"
	put claudius tourney
	swap_room
	swap_room
	clear_flag 6
	set_counter 0
	put ophelia hallway

So, a workable solution in lieu of compiler repair that will confuse scholars. :slight_smile:

set_flag uses up a condition to store the “20”. Printing is unusual because there’s a separate opcode for every single message—that’s where the limitation on the number of messages comes from.

Agreed. I’d honestly say it’s a bug that the compiler can’t handle this on its own.

1 Like

I got this working:

 action give script when here players and flag 5 and present script
	print "We can play the MURDER OF GONZAGO!\nThey exit to the throne room\nThe play is ABOUT to begin!"
	#print "We can play the MURDER OF GONZAGO!"
	#print "They exit to the throne room"
	#print "The play is ABOUT to begin!"
	set_flag 20
	set_flag 20
	put players throneroom
	put script throneroom

If my math is correct - The original code has 3 conditions and the two put has two arguments each, alas 7 (print doesn’t count as argument, see above). The set_flag has only one argument so example 2=three conditions + print + 2 x set_flag + continue = 3+0+1+1+0=5. The rest is pushed to the “subroutine”.

Unfortunately not very elegant…

1 Like

Opcode 0 means “do nothing” and takes no parameters, so in theory you could use that to pad out your actions, but I don’t know if ScottKit has a name for that one.

Doesn’t appear so. It’s not listed in the Instructions in compile.rb, and the decompiler seems to try very hard to avoid it:

    class Instruction
      def quote(*args); @game.quote(*args); end

      def render(args)
        if (@op == 0)
          return "NOP" # shouldn't happen
        elsif (@op <= 51)
          return "print #{quote @game.messages[@op]}"
        elsif (@op >= 102)
          return "print #{quote @game.messages[@op-50]}"
        end

(and just trying to use “nop” or “0” is rejected by the compiler)

Anyway, I appreciate the joint efforts on the workaround and identifying places which hopefully will make it easier for the compiler to be improved. I now know that at the cost of a couple of do-nothing instructions (two swap_rooms or a pair of flag set/unsets seems to be a reliable combo) I can probably power through these types of blocks and reclaim some wasted messages, while leaving a note in the source code to go in and reclaim those wasted bytes if indeed the compiler is improved.

Hm, interesting. Well, if it would be useful to you, I can put in a pull request to add a nop result; it wouldn’t be a difficult change.

Making it use continue properly would be more difficult, and probably beyond the limit of my very limited Ruby skills.

Not on my account, but thank you. It sounds like Mike has the info he needs to examine the problem and find a solution when he has time, and in the meanwhile I have a couple of weird-but-better-than-burning-message-slots workarounds for the issue.

And so that all of this collaborative debugging was not in vain, I have gone through and taken advantage of the workaround to reclaim a handful of strings and added a new puzzle to GHOST KING.

Doing so involved an action list so apparently unprecedentedly epic that it required two applications of the double-swap_room maneuver…

(code is also spoiler)

action monologue when carried skull and !flag 13 and at cemetery
	print "Alas poor Yorick! I knew him
To what base uses we may return!\n
funeral procession comes. BURYS Ophelia
Her brother Laertes YELLS as he leaves
'THE DEVIL TAKE THY SOUL!'"
	pause
	set_flag 13
	swap_room # sure okay fine
	swap_room
	drop grave
	put seal manor
	swap_room # wait again really
	swap_room # yes really
	put horatio grandroom # okay in fairness this is pretty ambitious
	put ship docks        # for a scott adams game
1 Like