Community

SCI Programming => SCI Syntax Help => Topic started by: Doan Sephim on June 11, 2019, 11:35:48 AM

Title: Can someone explain Flags to me?
Post by: Doan Sephim on June 11, 2019, 11:35:48 AM
In the past, I've used Global variables for TRUE/FALSE situations like "the first time you enter a room." While it works, it does produce a hefty main.sc which limits what I can do in other rooms/regions. Since then, I've heard a bit about using Flags in the place of global variables for those kinds of uses. The direction given was to look through the QfG1 decompiles, but after having spent some time looking through the forums and scripts, I'm not sure I understand how to implement this.

I'm not a native programmer, so things often go over my head. Can someone point this out to me?
Title: Re: Can someone explain Flags to me?
Post by: EricOakford on June 11, 2019, 11:54:11 AM
Here's how event flags work, using code from my SCI01 Template:
Code: [Select]
(procedure (Btst flagEnum)
;Test a boolean game flag
(& [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
)

(procedure (Bset flagEnum  &tmp oldState)
;Set a boolean game flag
(= oldState (Btst flagEnum))
(|= [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
oldState
)

(procedure (Bclr flagEnum  &tmp oldState)
;Clear a boolean game flag
(= oldState (Btst flagEnum))
(&= [gameFlags (/ flagEnum 16)] (~ (>> $8000 (mod flagEnum 16))))
oldState
)

(procedure (SolvePuzzle flag points)
;Adds an amount to the player's current score. A flag (one used with
;Bset, Bclr, and Btst) is used so that a score is only added once.
(if (not (Btst flag))
(theGame changeScore: points)
(Bset flag)
)
)

They refer to a global variable array:
Code: [Select]
[gameFlags 10] ;each global can have 16 flags. 10 globals * 16 flags = 160 flags. If you need more flags, just increase the array!
The SolvePuzzle procedure checks if a flag is set, and if it's not, it awards the specified points to your score.

The flags are enumerated and defined in your GAME.SH file.

This is much more efficient than having one global per TRUE/FALSE situation, so that you only need separate global variables for situations with multiple states.
Title: Re: Can someone explain Flags to me?
Post by: NilG on June 11, 2019, 12:16:13 PM
Awesome!  I just counted and I've got 70 global binary variables in what's probably a little less than a quarter of the full game.  I can shift quite a few local variables over this way as well, which could help trim the necessary logic from some of the main rooms into the specific room/input scripts.  My heap's shifting further from nightmare to comfortable with each trick you all pass on.

Might not be bad to compile some article on the Wiki for heap management tricks like these for some of us more amateur programmer types.  Perhaps I'll take some time to at least start on something like that; it'd be nice to contribute for sure in content if not coding and ideas.
Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 11, 2019, 03:05:38 PM
This is quite helpful, thank you.
Follow up: I'm working in an older project, and it is not yet in Sierra script. It'll be translated over in time, but when I run the auto-convert I end up with about 140 or so errors (I'll get around to them eventually ;)
In the meantime, I'm working on the scripts with studio scripting. In the code you provided, there is the mathematical operation of "mod."
I've never seen that operation in studio script and was wondering if anyone knew the symbol for that operation in Studio.
Thanks again for your help with flags. I always suspected there was a better way to do these type of things, but never knew about it until now

Edit: Mod's symbol appears to be % in studio script.
When I'm defining in the game.sc script, does that take up heap space?
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 11, 2019, 05:59:46 PM
When I'm defining in the game.sc script, does that take up heap space?

Define's are a compile time thing, they don't take up any extra space.
Title: Re: Can someone explain Flags to me?
Post by: MusicallyInspired on June 11, 2019, 11:14:19 PM
Edit: Mod's symbol appears to be % in studio script.

Yeah, "MOD" is short for "modulus" and returns a remainder from a division operation...right?
Title: Re: Can someone explain Flags to me?
Post by: Kawa on June 12, 2019, 04:00:35 AM
Define's are a compile time thing, they don't take up any extra space.
I don't think you're talking about the same kind of defining that Doan Sephim is.
Yeah, "MOD" is short for "modulus" and returns a remainder from a division operation...right?
Correct.
Title: Re: Can someone explain Flags to me?
Post by: NilG on June 12, 2019, 05:07:44 AM
Is $8000 hex?  I know how to convert hex, but I'm a little fuzzy on how it applies here, if so?  I've got no real experience with bitwise operation; I feel like I get the most basic of concepts, but I'm definitely lacking in the applicability department, so I'm kind of slow at translating when it comes up.
Title: Re: Can someone explain Flags to me?
Post by: lskovlun on June 12, 2019, 06:03:24 AM
Is $8000 hex?  I know how to convert hex, but I'm a little fuzzy on how it applies here, if so?  I've got no real experience with bitwise operation; I feel like I get the most basic of concepts, but I'm definitely lacking in the applicability department, so I'm kind of slow at translating when it comes up.
Yup, 215, that is to say, the value you AND something with to isolate the 15th bit (OR to set it).
By shifting it right as here, you get decreasing powers of two which can be used similarly to isolate/set the 14th, 13th, etc. bit.
Title: Re: Can someone explain Flags to me?
Post by: Kawa on June 12, 2019, 06:20:44 AM
What's important is that those functions do the intricate math for you so you don't have to worry. I for one get confused easily when dealing with sub-byte data so I for one am thankful.
Title: Re: Can someone explain Flags to me?
Post by: Charles on June 12, 2019, 11:06:55 AM
It can be fun to learn about bit operations and binary/hex and all of that, but like Kawa said, that's all irrelevant knowledge for the Btst, Bset and Bclr procedures.  They do all the heavy lifting for you.

All you need to remember is that a single variable can hold 16 flags.  So the template game has 10 variables assigned to the gameFlags array. Ergo, 160 flags.  The first flag is flag #0, the last is flag #159. If you need more flags, extend the array; you get 16 more flags for each variable you extend the flag array.

You tell the Btst, Bset and Bclr procedures which flag number you want to test/set/clear.  And you should use an enum to track your flags, just to make things easier to read/follow. The enum's won't use any memory since the compiler will hardcode whatever number the flag is when compiling.

Here's how Btst, Bset, and Bclr compare to similar statements using individual global variables, assuming your global variable is named gVar1 and you want to replace it with a flag defined as FLAG1:
Code: [Select]
(Btst FLAG1)
(== gVar1 TRUE)

(Bset FLAG1)
(= gVar1 TRUE)

(Bclr FLAG1)
(= gVar1 FALSE)
Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 12, 2019, 11:27:38 AM
The flags are enumerated and defined in your GAME.SH file.
Do you define them in the Game.sh file or enumerate them (or does it even matter which?)
I apologize for the hand-holding needed with this, but what does it look like in the subheader?

This is what I see in the Template, but I'm confused on how it relates to the gameFlags:
Code: [Select]
;Event flags
;Example: fBabaFrog (original Sierra naming)
Title: Re: Can someone explain Flags to me?
Post by: EricOakford on June 12, 2019, 12:11:38 PM
The flags are enumerated and defined in your GAME.SH file.
Do you define them in the Game.sh file or enumerate them (or does it even matter which?)
I apologize for the hand-holding needed with this, but what does it look like in the subheader?

This is what I see in the Template, but I'm confused on how it relates to the gameFlags:
Code: [Select]
;Event flags
;Example: fBabaFrog (original Sierra naming)

It doesn't really matter; using "enum" is just a simpler way to list defines in numerical order. The compiler recognizes them in the exact same way.

As an example, here are the event flags from R44QSCI:
Code: [Select]
; Event flags
(enum
fFoyerLightOn
fMetJohan
fJohanHasTie
fJohanAtReception
fMetNiels
fNielsSleeping
fTookJar
fTookGarbage
fOpenedStoveHood
fTookFilter
fTookTie
fFellDownStairs
fTookShowerGel
fSavedJinwo
fMetRinze
fGaveRinzeBeer
fTookPen
fTookNote
fWroteNote
fJosineDumped
fJosineGotNote
fCanEnterErikRoom
fJinwoHasKey
fCanEnterJosineRoom
fErikWantsFood
fEnteredJosineRoom
fTookRyeBread
fWearingFilter
fSawErikDead
)

If you want to start an enum at a specific number, just type that number after "enum".

Oh yeah, I forgot to mention a bit of trivia earlier - KQ4 and LSL2, the first two SCI games, didn't use the "Btst/Bset/Bclr" trio at all - they just used global variables for every event. Since that's a waste of heap space, PQ2 implemented the much more efficient bit-based event flag procedures. These became a standard part of SCI games ever since.
Title: Re: Can someone explain Flags to me?
Post by: Charles on June 12, 2019, 12:14:07 PM
You can put them wherever. You can define then, or use enums. Enums are actually a bit easier to manage.  I mean, heck, if you wanted you could just use numbers for everything and keep it in your head.  Defines and Enums make no difference to the compiled code. They're only for making your code easier to read.  When they get compiled, the compiler will basically just do a find/replace with the defined or enumerated value.  They both have their place.  Defines are great for one-off values. Enums are great if you're defining a bunch of sequential values, like flags.

You could put enums in a seperate .sh file and include that in game.sh (although if you do that, the tooltip may not properly show up in SCICompanion when you hover over it).

I'll show you the more complicated example, and let you pare down as necessary. So if you have 160 flags to play with (as in the template game default), you can make those flags be whatever you want. Let's say your game has 40 rooms, and you want to know if ego has visited any room before. Then you have another 120 flags for game states or puzzle points.  You can break those up into multiple enums for easier readability.

in Game.sh:
Code: [Select]
;;; Sierra Script 1.0 - (do not remove this comment)
;put the Sierra Script comment at the top, so we can use sierra script in the header file
(include gameEnumVisited.sh)

create a new file called gameEnumVisited.sh:
Code: [Select]
;;; Sierra Script 1.0 - (do not remove this comment)
(enum    ;the default starting point is 0, although you can specify another)
   FLAG_VISITED_ROOM1    ;this is given a value of 0
   fVisitedRoom2                 ;this is given a value of 1
   VISITED_RM3                 ;this is given a value of 2
   ;... continue for as many as needed
   ;NOTE, flags can be called whatever you want, but being consistent with your naming will make your code easier to read.
)

(define FLAG_ROOM4  3)  ;we've explicitly given this a value of 3. We could have also made this the next line in the enum

;game states are called out here
(enum 40  ;we're starting this enum at 40, for the game state flags.
    fEgoIsJumping  ; has a value of 40. For when ego's jumping off a massive cliff.
    FLAG_BABAFROG ; has a value of 41. For when ego has turned Baba Yaga into a frog.
)

;puzzle points are defined here, so the user doesn't double-dip on points.
(enum 120
    POINTS_JUMPED ;120.  User jumped. Yay. Give yourself 5 points.
)

Then in each room's script, in the dispose code you would add the following
Code: [Select]
(method (dispose)
(Bset FLAG_VISITED_ROOM1) ; sets flag#0 to 1 (i.e. turns it on, or sets as TRUE)
                ;we'll also give the user some points, because the had to jump to leave the room
                (AddToScore POINTS_JUMPED 5) ;the AddToScore procedure will only add points if the POINTS_JUMPED flag is 0. 
                ;So no other checking is required. The user will only get 5 points once.
(super dispose:)
)

Keep in mind that you can still use Btst to check if points have been awarded. It's still just a flag.
Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 12, 2019, 01:34:00 PM
Thanks Eric and Charles! I only have one question remaining (I hope!) - the thing I don't understand is how the enums in the subheader relate to the variable and the procedure that references that variable. The enumerated list seems to be unconnected to the variable and procedures. I'm guessing they're connected by something in the procedure, but I don't get it, and it makes me feel like I'm missing something.
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 12, 2019, 02:07:52 PM
Code: [Select]
(procedure (Btst flagEnum)
;Test a boolean game flag
(& [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
)

If the flag you pass in is 35, then this essentially becomes:

(procedure (Btst flagEnum)
   ;Test a boolean game flag
   (& [gameFlags 2] (>> $8000 3)) ; since 35 /16 is 2, and 35 mod 16 is 3
)

So it's selecting the 3rd bit in the 2nd gameFlags array index. It's basically just doing integer division and modulo to turn the flag number into an array index (0 to however big the array is) and bit position (0-15).


Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 12, 2019, 02:16:34 PM
Thanks Troflip et al. I think I finally grasp what going on with this script and I'm very thankful for everyone for bearing with me. I kinda feel like the dumb kid in class asking all these questions, but everyone has been quite patient and accommodating, and for that I'm grateful.
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 12, 2019, 02:17:50 PM
Interestingly enough, if you're doing it this way in SCI0, this could end up eating up more memory, since the script code takes up heap space (I forget if that's true for SCI01 or not, but it's no longer the case for SCI1+ since script code isn't in the heap).

BTest, bSet, BClear functions themselves eat up 101 bytes. So that's a fixed cost right off the bat.
160 regular globals eat up 320 bytes, whereas [gameFlags 10] only eats up 20. So you're saving 300 bytes at a cost of 101 bytes. Still coming out ahead, except...

Except it takes 6 bytes of compiled code to call BTest or BSet.
But only 4 bytes (or possibly 5) to directly assign a global TRUE or FALSE.
And only 2 bytes (or possibly 3) to read from it.

So, read a flag 75 times in currently loaded script code and you've already lost your bytes advantage.

Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 12, 2019, 02:26:51 PM
So, read a flag 75 times in currently loaded script code and you've already lost your bytes advantage.
When you say 75 times in currently loaded script, i assume that that means (from a general gameplay perspective) in a single room, but after each room, it's all disposed, correct? So generally speaking, it's a heap saver, but if I were to read the flag(s) 75 times in a single room, it would be heap-negative compared to normal.

Or does it mean 75 times at all?
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 12, 2019, 02:33:10 PM
Yes correct, but you need to include all the places in Main.sc or Game.sc or whatever too (scripts that are always loaded).
Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 12, 2019, 02:39:06 PM
Yes correct, but you need to include all the places in Main.sc or Game.sc or whatever too (scripts that are always loaded).
I see, so if I have any of those procedure already called in the default scripts, those will always be adding towards that 75-time limit.

Even so, it seems likely to me, that 75 is a pretty high number of calls for average use, so I think it will ultimately save.
Title: Re: Can someone explain Flags to me?
Post by: Charles on June 12, 2019, 02:46:40 PM
Also considering the AddToScore procedure, which relies on the Btst/Bset/Bclr trio, let's round down to a hypothetical 50 flags being read in a single scene ( i.e. the current script, main.sc, game.sc, and whatever locales and regions are also included with the current script).  Is that a practical consideration?  50 flags read in a single game scene sounds more than sufficient, but I haven't done compared many games.

The only code I've really looked through is QFG1, and I'm struggling to recall if it ever did more than 5 flag read/writes in a single scene.
Title: Re: Can someone explain Flags to me?
Post by: NilG on June 12, 2019, 05:59:25 PM
One other question from me, too.  I implemented this for one of my globals and it seems to be working great after a couple of initial test runs.  However, when I compile, I get a bunch of "No effect on code" warnings for the new stuff, as in the attached.

The flag and procedures must be working, since the game's responding to events related to the flag.  I've edited the code and commented out the global for the time being while testing.  From the player standpoint, so far so good, but am I missing something?
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 12, 2019, 06:55:23 PM
Enclose the code in BTest in a return statement.

Title: Re: Can someone explain Flags to me?
Post by: NilG on June 12, 2019, 07:51:54 PM
That did it, except for the "oldState" lines in Bset and Bclr.

I found that enclosing "oldState" in a return statement in Bset made that one disappear; doing the same in Bclr did not.

I noticed that oldState isn't even in parentheses in either line, which seems unusual.  Are these what's getting returned?  Seems like it to me, they're the new values getting assigned(?), but making sure.  Any thoughts why Bclr's oldState would still give the "no effect on code" warning?

EDIT:  Must have done something wrong the first time, went back and added the return statement to Bclr again and now no warning.
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 12, 2019, 08:26:42 PM
I should have said: enclose the *last* statement of the procedure in a return statement.
Title: Re: Can someone explain Flags to me?
Post by: NilG on June 12, 2019, 10:01:32 PM
Regarding heap, it seems like memory usage is going up each time I replace (== z TRUE/FALSE) with (Btst z) or = with Bset/Bclr.

It's not a huge difference, but seems counterintuitive to replacing the variables with flags.  Is it plugging in the whole procedure rather than a pointer (or whatever the procedure equivalent would be) each time I reference the call, or is this unexpected?  Logically, it's working fine.  I'm just not sure if the more flag testing/setting I do (as opposed to variable work), the heavier the price should be.  Some variables can pop up a dozen times in a script, so just looking for the best overall path.
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 13, 2019, 12:09:08 AM
See my post a few posts up for an explanation of why this is. Basically, a procedure call takes up a few more bytes than assigning or testing a variable. You can disassemble the script to see exactly how much.



Code: [Select]
(== z TRUE/FALSE)

btw, why not use:
Code: [Select]
z  ; instead of (== z TRUE)

or

Code: [Select]
(not z)  ; instead of (== z FALSE)

Title: Re: Can someone explain Flags to me?
Post by: NilG on June 13, 2019, 05:06:14 AM
Thanks, troflip, not sure how I managed to miss that entire chunk of conversation, but makes sense.

Honestly, I just didn't realize the shorthand was accepted or think to try it out.  I'll def keep that in mind now, though.
Title: Re: Can someone explain Flags to me?
Post by: Kawa on June 13, 2019, 05:33:44 AM
Why would you just say z directly in a conditional expression instead of (== z TRUE)? For those playing along at home and don't know yet, this is because of a few things. First, TRUE and FALSE are just constants representing 1 and 0. Second, any non-zero value is "truthy". You'd think TRUE is the most truthy of all but it's just a number so it's equally truthy, this isn't Animal Farm. The expression in an if statement and such has to evaluate not to true but to truthy to execute, and that can be any expression whatsoever.
Code: [Select]
(if (AskConsent "Are you sure you want to shoot that?") ...) ; will execute if AskConsent returns non-zero
(if (Btst fShotTheThing) ...) ; will execute if the "shot the thing" flag is set
(if (== z TRUE) ...) ; will execute if z is exactly 1
(if z ...) ; will execute if z is 1 or higher

By that same token, (if mover (mover dispose:)) only executes if mover is non-zero and assumes it's a valid reference to some instance.

To elaborate on Phil's post about procedure calls and byte counts: bytecode-wise, (if z ...) turns into the following:
Code: [Select]
83 00      lal local0 //or 85 00 lat temp0, you get the point.
31 08      bnt justAfterThat
Load a local or temp into the accumulator, branch if the acc is not truthy. Four bytes, maybe one more if the code in the if block is large enough. The branch goes down eight bytes because that's how tiny the block itself is.
Compare that with a procedure call:
Code: [Select]
78         push1 // one parameter
39 2a      pushi 2a // flag #42
45 01 02   callb Btest 2 // call with two bytes worth of param data
31 08      bnt justAfterThat
That's six bytes to execute (if (Btest 42) ...). So if you test the same flag a bunch of times, consider testing it once and storing it to a temp.
And finally, checking your own properties as above is four bytes again:
Code: [Select]
63 12      pToa mover
31 08      bnt moverWasNull
Noting that mover is either null or something far greater than 1.
Now, by "returns non-zero" in the first half of this post above, we actually mean "sets the accumulator register to a non-zero value before popping back to the caller". You should be able to figure out what that means by now if you didn't already know.
Title: Re: Can someone explain Flags to me?
Post by: lskovlun on June 13, 2019, 03:49:22 PM
Code: [Select]
(if z ...) ; will execute if z is 1 or higher
You mean non-zero. Whether an integer is treated as signed or unsigned depends on the opcode. That is why we have > versus >u (or was that u>).
Title: Re: Can someone explain Flags to me?
Post by: NilG on July 09, 2019, 06:23:09 PM
Now I'm going crazy.

A month or so ago, I set up flags all around and they all seemed to work without a problem.  I foolishly reverted to globals, but recently began to switch back to flags after figuring out a way to work with scripts to keep them positive heapwise.

But one's giving me trouble now that I can't figure out, and that's only after editing one room script.

Code: [Select]
(procedure (Btst flagEnum)
;Test a boolean game flag
(return (& [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16))))
)


(procedure (Bset flagEnum  &tmp oldState)
;Set a boolean game flag
(= oldState (Btst flagEnum))
(|= [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
(return oldState)
)

(procedure (Bclr flagEnum  &tmp oldState)
;Clear a boolean game flag
(= oldState (Btst flagEnum))
(&= [gameFlags (/ flagEnum 16)] (~ (>> $8000 (mod flagEnum 16))))
(return oldState)
)

They are as copied directly from this thread, other than I added the return statements to bypass warnings.

But for example, if I run this:

Code: [Select]
(if (Said 'nudge/bag,trash')
(if (not (Btst fTrashMoved)) (Print 6 23) (theTrashPile ignoreControl: ctlWHITE setMotion: DPath 138 148 loop: 0) (Bset fTrashMoved)
else (Print 6 28))
)

I get the correct Print statement and theTrashPile moves, BUT it will just let me do this repeatedly and subsequent Btst fTrashMoved indicates it's false.

I have another flag, fAlleyLightBusted, that seems to stick after I throw a rock at the light... until I nudge the trash, at which point it seems to go false. Enter and leave the room multiple times, no problem, until the nudge.  Neither flag even has a related Bclr; once they're set once, they should be set forever.  They're also completely unrelated actions; the only Bset for fTrashMoved is the one above and, again, everything in the "if" statement happens except the flag set.  fAlleyLightBusted doesn't even get set in the RoomScript, it's in a different Script (although same script doc) altogether.

Basically, it all seems fubar, at least when it comes to this damn bag.  I tried setting up a test:

Code: [Select]
(if (Said 'what')
(FormatPrint "Trash Moved: %d" (Btst fTrashMoved))
(FormatPrint "Lights Out: %d" (Btst fAlleyLightBusted))
)

and find that fAlleyLightBusted does go from false to true (or, more accurately, a very large negative number, is that good?) when the rock is throw.  fTrashMoved remains 0 when the nudge script kicks in.  And fAlleyLightBusted goes back to 0 when the nudge script is run. 

Should the test above accurately reflect flag settings, as I feel like it should?

I've double and triple checked that all previously global variables have been correctly renamed to flags (and these flags/variables only apply to this single room).  The flag names match the enums given in game.sh.

Is there a way to flush print the contents of gameFlags all over so I can check if the wrong bit is getting set for the bag?  I wouldn't even know how to go about fixing that, but I really just can't see anything that would cause this behavior and it's the only thought I'm having.
Title: Re: Can someone explain Flags to me?
Post by: troflip on July 09, 2019, 10:45:16 PM
You're sure you're recompiling all files? And you're not relying on a saved game or anything?
Title: Re: Can someone explain Flags to me?
Post by: lskovlun on July 09, 2019, 10:47:47 PM
Also make sure that your enum declarations don't have duplicate numbers. It's easy to do this by accident.
Title: Re: Can someone explain Flags to me?
Post by: NilG on July 10, 2019, 02:23:45 AM
I do Compile All each time before a test run and almost always rebuild just to kill the redundancy, but I just went through and compiled both Main and rm006 specifically to be sure.  Still no luck.  No saved games; I've largely relied on the debugging shortcuts, but even when I do save, I crush any save files to be safe.  There aren't any currently.

In game.sh, I just have the enum in a list format:

Code: [Select]
(enum
fAlleyLightBusted
fArtBoxFirstOpened
fArtifakeSwapped
        …
        fStinkySandwichKickout
fTrashMoved
fWearingCoveralls
)

No numbers specified, so I don't *think* that's the case, but is it possible?
Title: Re: Can someone explain Flags to me?
Post by: Charles on July 10, 2019, 09:30:38 AM
Is there a way to flush print the contents of gameFlags all over so I can check if the wrong bit is getting set for the bag?  I wouldn't even know how to go about fixing that, but I really just can't see anything that would cause this behavior and it's the only thought I'm having.

Try using %x instead of %d for the formatting. That'll display the hex value, instead of the signed number.  That'll make it easier to paste into your favorite hex calculator (I love Win10's calculator with it's Programming mode, for seeing hex and binary values).

How many bytes is your flag array?  It sounds like your Bset procedure isn't limiting itself to the specified bit. The code looks fine to me though, so to debug and see exactly what's in the flag arrays try: (so if your flag array is 5 bytes long)
Code: [Select]
(FormatPrint
    "gameFlags: %04x %04x %04x %04x %04x"
    [gameFlags 0]
    [gameFlags 1]
    [gameFlags 2]
    [gameFlags 3]
    [gameFlags 4]
)

Then convert that number into binary, and see the state of each flag.
Title: Re: Can someone explain Flags to me?
Post by: troflip on July 10, 2019, 01:02:37 PM
Title: Re: Can someone explain Flags to me?
Post by: NilG on July 10, 2019, 06:35:49 PM
I've currently got 71 flags enumerated and a 10 item gameFlags array.  gameFlags is itself the final global variable.  I've got 73 other globals before that I've added, though the ones related just to this screen are commented out, bringing that to 71.  None of the other three global variables which were previously associated with this screen, which are also used in other rooms, should be in play here.

(FormatPrint
    "gameFlags: %04x %04x %04x %04x %04x"

dump for gameFlags 0 - 9:

Starts out:
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
which is fine.  A couple will get set to true initially, but not implemented, so all false is good.

Nudge bag:
0400 0000 0000 0000 0000 0000 0000 0000 0000 0000

Throw rock:
8400 0000 0000 0000 0000 0000 0000 0000 0000 0000

Nudge bag after throw rock:
0400 0000 0000 0000 0000 0000 0000 0000 0000 0000

fAlleyLightBusted is the very first flag enumerated, so my understanding's fuzzy, but the addition there is expected?  fTrashMoved is the second to last of 71, so that one looks to be in the wrong place as far as I understand.  It's resetting the whole first byte or maybe array element altogether maybe when it flags what seems to be the wrong bit anyway?
Title: Re: Can someone explain Flags to me?
Post by: troflip on July 10, 2019, 08:05:34 PM
So fAlleyLightBusted corresponds to throw rock? That looks correct.

But your "Nudge bag" is definitely not setting fTrashMoved, if trashed moved is 69. It appears to be setting the 5th one (i.e. 4).

What happens if you do:
Code: [Select]
(FormatPrint "Trash Moved flags: %d" fTrashMoved)

69?  Or 4?
Title: Re: Can someone explain Flags to me?
Post by: NilG on July 10, 2019, 08:46:47 PM
Yeah, throwing the rock corresponds to the alley light bustage.

Huh.  It does say it's 69, so that seems a good thing.  I guess it's Btsting the right one, since it keeps treating it as false, instead of switching to the else Print statement.

The fifth flag is for an event taking place on another room altogether, and one that isn't even in use yet outside of the enumeration (all events still utilize the original global variable).  Search confirms that it doesn't pop up in any of the other files...
Title: Re: Can someone explain Flags to me?
Post by: troflip on July 10, 2019, 09:39:58 PM
Show us the code that sets fAlleyLightBusted
Title: Re: Can someone explain Flags to me?
Post by: NilG on July 10, 2019, 10:05:09 PM
RoomScript event handler:

Code: [Select]
(if (Said 'throw>')
(if (Said '/rock/light')
(if (not (gEgo has: INV_LIEVENROCK)) (Print 6 57)
else (self setScript: lightsOut)))
(if (Said '/rock/window')
(if (not (gEgo has: INV_LIEVENROCK)) (Print 6 57)
else (Print 6 93)))
)

and then it's actually set in the lightsOut script:

Code: [Select]
(instance lightsOut of Script

(method (changeState newState)
(= state newState)
(switchto state
(
(ProgramControl)
(Print 6 58)
(theLight loop: 2 cel: 0)
(= seconds 1)
)
(
(= cycles 1)
(gEgo view: 002 loop: 1 cel: 0)
)
(
(= cycles 1)
)
(
(if (!= (gEgo cel:) 5) (gEgo cel: (+ (gEgo cel:) 1)) (self changeState: 2)
else (= seconds 1))
)
(
(= cycles 1)
)
(
(if (!= (theLight cel:) 2) (theLight cel: (+ (theLight cel:) 1)) (self changeState: 4)
else (= cycles 1))
)
(
(= gDefaultPalette 1)
(Rm drawPic: 6)
(theDoor cel: 1)
(theFirstDumpster cel: 1)
(theLight loop: 3)
(thePuddle cel: 1)
(if (not (Btst fGreaseJarInTrash)) (theSecondDumpster cel: 1)
else (theSecondDumpster cel: 3))
(theTrashPile loop: 1)
(theTrash1 cel: 1)
(theTrash2 cel: 1)
(theTrash3 cel: 1)
(theTrash4 cel: 1)
(theWallLine show:)
(Bset fAlleyLightBusted)
(gEgo put: INV_LIEVENROCK)
(theGlint dispose:)
(= cycles 1)
)
(
(gEgo view: 0 loop: 1 cel: 7)
(Print 6 59)
(= seconds 2)
)
(
(PlayerControl)
(Print 6 60)
(= cycles 1)
)
)
)
)
Title: Re: Can someone explain Flags to me?
Post by: Charles on July 10, 2019, 11:10:18 PM
I’m leaning towards there being an error in the Bset procedure.

Try doing some methodical testing:
Code: [Select]
(Bset 0)
(FormatPrint ... the game flag print code)
(Bset 1)
(FormatPrint  blahblah)
(Bset 69)
(FormatPrint ...)

Incidentally, the 8400 from the earlier output translates to binary 1000 0100 0000 0000, i.e. flag 0 and flag 5 are true. An easy way to convert is just ask google (or DuckDuckGo) for “0x8400 in binary”. With 0x#### being a common shorthand for “hex number”.

Title: Re: Can someone explain Flags to me?
Post by: troflip on July 11, 2019, 01:08:57 AM
Bset looks fine to me, but just doing some simple testing like Charles suggested should narrow down if that is the case, or you have something else going on.

Of note, setting flag 69 should result in a 0400 in the 4th gameFlags array spot instead in the 0th. Kind of suspicious that it's setting the same bit just in the wrong array index.
Title: Re: Can someone explain Flags to me?
Post by: NilG on July 12, 2019, 08:00:42 PM
Most telling, I guess, is that this:

Code: [Select]
(if (Said 'what')
(Bset 0)
(FormatPrint "Bset 0: %d" (Btst fAlleyLightBusted))
(Bset 5)
(FormatPrint "Bset 5: %d" (Btst fBrokenSandwichThrown))
(Bset 69)
(FormatPrint "Bset 69: %d" (Btst 69)))

shows that 0 and 5 get set, but the last printout still says "Bset 69: 0"  Moreover,

Code: [Select]
(Bset 0)
(FormatPrint "Bset 0: %d" (Btst fAlleyLightBusted))
(Bset 69)
(FormatPrint "Bset 5: %d" (Btst fBrokenSandwichThrown))
(FormatPrint "Bset 69: %d" (Btst 69)))

shows that Btst fBrokenSandwichThrown (which is the element 5) is getting set in this latter case as well, even with the number specified.  So 5 seems to be getting set in either case, which I guess may point to the issue being in selecting the array element, as troflip noted?  I checked setting 70, as well, and find that 6 gets set instead.

I'm not sure why, yet, though; I've gone back and recopied the Bset from this script again, and have experienced the same even when I leave it as is (allowing for the no return warning to pop-up).  I've run a search and verified that there isn't some other Bset procedure I've added somewhere else that's getting called in lieu of the one in Main.sc (and there's only the one there).  It does seem strange.
Title: Re: Can someone explain Flags to me?
Post by: lskovlun on July 12, 2019, 09:38:27 PM
Phil, there seems to be a problem with compiling the |= operator.
If I replace this:
Code: [Select]
(|= [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
with this
Code: [Select]
(= [gameFlags (/ flagEnum 16)] (| [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16))))
then it works. For good measure, let's change Bclr as well:
Code: [Select]
(&= [gameFlags (/ flagEnum 16)] (~ (>> $8000 (mod flagEnum 16))))
becomes
Code: [Select]
(= [gameFlags (/ flagEnum 16)] (& [gameFlags (/ flagEnum 16)] (~ (>> $8000 (mod flagEnum 16)))))
Title: Re: Can someone explain Flags to me?
Post by: NilG on July 12, 2019, 10:08:30 PM
That does seem to make the difference here, from a few brief tests.
Title: Re: Can someone explain Flags to me?
Post by: Charles on July 12, 2019, 10:50:10 PM
I betcha what’s happening is the compiler is interpreting the |= as:
Code: [Select]
(= [gameFlags (/ (/ flagEnum 16) 16)] (| [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16))))

So, dividing the flag# by 16, then by 16 again.

If that’s the case, I’d expect (Bset 16) to actually set flag 0, (Bset 17) to set flag 1, etc. and (Bset 256) to set flag 16.
Title: Re: Can someone explain Flags to me?
Post by: lskovlun on July 13, 2019, 08:33:16 AM
No, that's not quite it. SCI Companion generates this weird code for Bset:
Code: [Select]
procedure proc_003a
; (= oldState (Btst flagEnum))
  003a:3f 01             link 1 // (var $1)
  003c:78               push1
  003d:8f 01              lsp param1
  003f:41 e6 02          call proc_0028 2
  0042:a5 00              sat temp0

; Take flagEnum and divide it by 16
  0044:8f 01              lsp param1
  0046:35 10              ldi 10
  0048:08                 div
  0049:36                push
  004a:9b 06             lsli local6

; Similarly, get the remainder
  004c:38 8000          pushi 8000 // $8000 sel_32768
  004f:8f 01              lsp param1
  0051:35 10              ldi 10
  0053:0a                 mod
; Compute $8000 >> that remainder
  0054:0c                 shr
; OR the bit in
  0055:14                  or
; but wait, wtf is this?
  0056:1a                 eq?
  0057:3a                toss
  0058:60               pprev
; Store the corrupted result back
  0059:bb 06             ssli local6
; (return oldState)
  005b:85 00              lat temp0
  005d:48                 ret
Yay stack corruption: Companion seems confused about when (/ flagEnum 16) is on the stack, and when it's not, and starts throwing away random stack values (at code offset 0057). So you are right that the code ends up storing into the wrong variable, but it is due to improper stack handling. The eq?/pprev idiom could be quite clever under the right circumstances, but I don't see how it helps here.
Title: Re: Can someone explain Flags to me?
Post by: lskovlun on July 13, 2019, 10:41:04 AM
Just for fun, here's what Companion decompiles its own broken Bset to:
Code: [Select]
(procedure (localproc_003a param1 &tmp temp0)
(= [gameFlags (==
(/ param1 16)
(|
[gameFlags (/ param1 16)]
(>> $8000 (mod param1 16))
)
)]
(= temp0 (localproc_0028 param1))
)
(return temp0)
)
I'm surprised it doesn't resort to assembly to explain this mess...
Title: Re: Can someone explain Flags to me?
Post by: troflip on July 13, 2019, 01:08:59 PM
This was fixed years ago, but the build up there is still old :-(

If someone has the repro cloned (kawa?), can they make a build?
Title: Re: Can someone explain Flags to me?
Post by: Kawa on July 13, 2019, 05:25:43 PM
Spun up a fresh SCI0 game (since we're using Said and all that) in my March '19 copy of SCI Companion, and added the flag procedures from page 3, the ones with the return statements.

For Bset, the original code is
Code: [Select]
(procedure (Bset flagEnum  &tmp oldState)
(= oldState (Btst flagEnum))
(|= [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
(return oldState)
)

This disassembles to
Code: [Select]
// EXPORTED procedure #18 (Bset)
(procedure proc_0044
  0044:3f 01             link 1 // (var $1)
  0046:78               push1
  0047:8f 01              lsp param1
  0049:41 e6 02          call proc_0032 2

  004c:a5 00              sat temp0
  004e:8f 01              lsp param1
  0050:35 10              ldi 10
  0052:08                 div
  0053:36                push
  0054:9b 35             lsli local53
  0056:38 8000          pushi 8000 // $8000 sel_32768
  0059:8f 01              lsp param1
  005b:35 10              ldi 10
  005d:0a                 mod
  005e:0c                 shr
  005f:14                  or
  0060:76               push0
  0061:1a                 eq?
  0062:35 00              ldi 0
  0064:14                  or
  0065:60               pprev
  0066:bb 35             ssli local53
  0068:85 00              lat temp0
  006a:48                 ret
)

and decompiles to
Code: [Select]
(procedure (Bset param1 &tmp temp0)
(= temp0 (Btst param1))
(= [gameFlags (|
(/ param1 16)
(== 0 (| [gameFlags temp0] (>> $8000 (mod param1 16))))
)]
(| [gameFlags temp0] (>> $8000 (mod param1 16)))
)
(return temp0)
)

However, if I copy in the SCI11 versions...
Code: [Select]
(procedure (Bset flag &tmp theWord)
(= theWord (Btest flag))
(= [gFlags  (/ flag 16)]
(| [gFlags (/ flag 16)] (>> $8000 (mod flag 16)))
)
(return theWord)
)
Code: [Select]
// EXPORTED procedure #18 (Bset)
(procedure proc_0044
  0044:3f 01             link 1 // (var $1)
  0046:78               push1
  0047:8f 01              lsp param1
  0049:41 e6 02          call proc_0032 2

  004c:a5 00              sat temp0
  004e:8f 01              lsp param1
  0050:35 10              ldi 10
  0052:08                 div
  0053:9b 35             lsli local53
  0055:38 8000          pushi 8000 // $8000 sel_32768
  0058:8f 01              lsp param1
  005a:35 10              ldi 10
  005c:0a                 mod
  005d:0c                 shr
  005e:14                  or
  005f:36                push
  0060:8f 01              lsp param1
  0062:35 10              ldi 10
  0064:08                 div
  0065:b3 35             sali local53
  0067:85 00              lat temp0
  0069:48                 ret
)
Code: [Select]
(procedure (Bset param1 &tmp temp0)
(= temp0 (Btest param1))
(= [gameFlags (/ param1 16)]
(| [gameFlags (/ param1 16)] (>> $8000 (mod param1 16)))
)
(return temp0)
)

Interesting how the decompile looks just like the original 🤔