Rules for Characters Exposing the Player To Dangerous Things

I think what you want here is Hypothetical Questions by Jesse McGrew (who now goes by Tara McGrew but the extensions are under the old name for continuity reasons).

This extension lets you execute a phrase (like “try the current action”), calculate one value (like “whether or not the poison gas is exposed to the player”), then undo the phrase while preserving that one value.

Of course, this might be overkill, because I believe the only actions in the standard model that can change touchability are opening, closing, and going (since only rooms and closed containers block touchability). But if you have elaborate touchability rules or more actions to keep track of, Hypothetical Questions will ensure it just works without having to think about edge cases.

4 Likes

Interesting! I’ll check out the extension. I have heard of it but never explored it. The context is mainly as simple as this:

A thing can be dangerous or benign.
Definition: a thing is exposed to the player if the player can touch it.

The Office is a room.

A glass box is a container in the Office.
The glass box is closed, openable, and transparent.

In the glass box is some poison gas.
The poison gas is dangerous.

A woman called Skylar is in the Office.
Skylar wears the gloves of phasing.

Persuasion rule for asking Skylar to try doing something:
	persuasion succeeds.

Every turn:
	if the poison gas is exposed to the player:
		say "Exposed."

Before someone doing something that exposes a dangerous thing to the player:
	say "Don't!"

It’s that last thing that’s problematic. Basically it’s a condition of “if an NPC is asked to something dangerous”, don’t do it. The trick is the idea of what “dangerous” means here. In this case, it means if the player will be exposed to anything that has the property dangerous.

I’ll check out the extension and see if I can wiggle this idea into a hypothetical. It’s definitely an interesting idea because that’s essentially what the NPC is doing: considering a hypothetical to see if they should do something or not.

1 Like

Yep, I think Hypothetical Questions will handle that nicely. You want to hypothetically “try the current action” and check if the player is exposed to danger (which you need to wrap up into a rule but that’s not too hard). If so, stop the action.

The tricky part will be avoiding infinite recursion, since trying the action will run the Before rulebook again. Off the top of my head, you could make this a Persuasion rule instead, then try the action with “requested” set to false—so that your rule only runs when “asking Skylar to try opening the box”, and the action it hypothetically attempts is “Skylar opening the box”. (Or you can just set a global flag, that works too.)

2 Likes

I tried something like this:

Before someone opening the glass box:
	hypothetically try opening the glass box and consider the player's fate rulebook;
	if the outcome of the rulebook is:
		-- the player dies outcome: say "That would kill you."

Currently that generates a translating the source failed error.

Inform 6.41 for Win32 (22nd July 2022)
auto.inf(57429): Error:  Missing operator: inserting '+'
>             (TryAction(0, player, ##Open, I_glass_box_U1, 0)Hypo_Mid  ...etc

So far I’m getting that with a lot of stuff I try and it looks like the same happens even with the example in the extension. But, assuming I can figure out what’s going on there, I entirely agree that this looks like it will definitely do the job!

1 Like

Which version of Inform are you using? It looks like it’s not properly inserting a semicolon where it should in one of the I6 substitutions.

1 Like

This particular example is running on Inform for Windows 10.1.2.20220830. The language version in my Settings is set to Current. The same issue happens if i specifically set the value to Inform 10.1.

Interestingly, I tried switching back to Inform 9.3 (6M62). This required updating the extension version info. When I run the same example under that version of Inform, I get a “Glulx fatal error: Stack overflow in function call.”

If I went back to Inform 9.2 (6L38), the same Glulx error appears but it does give a number this time: “Glulx fatal error: Stack overflow in function call (10000).”

1 Like

Try this. Open up the extension, go to the block of code at the beginning that looks like this:

To hypothetically (ph - a phrase) and consider (R - a rule): (-
{-my:1} = Hypo_Start({phrase options});
if ({-my:1} == 1) {ph}
Hypo_Middle({R});
if ({-my:1} == 2) Hypo_End();
-)

Insert a semicolon after {ph} so it looks like:

To hypothetically (ph - a phrase) and consider (R - a rule): (-
{-my:1} = Hypo_Start({phrase options});
if ({-my:1} == 1) {ph}; ! <- here
Hypo_Middle({R});
if ({-my:1} == 2) Hypo_End();
-)

If this works, probably worth submitting as a bugfix. If it doesn’t, that means the I6-to-Inter-to-I6 pipeline broke something again, and that should be reported too.

1 Like

Got it. So I made that change and that definitely resolves the initial error. Now what I get is this in the output:

> [6] skylar, open the box
[asking Skylar to try opening the glass box]
[(1) Skylar opening the glass box]
Fatal Error: glk_stream_close() was called for a window stream.

The logic in place is this:

Before someone opening the glass box:
	hypothetically try opening the glass box and consider the player's fate rulebook;
	if the outcome of the rulebook is:
		-- the player dies outcome: say "That would kill you."

I don’t know if it’s indicative or not but I removed the whole NPC doing the action, just to see if that mattered. So I tried changing the condition to this:

Before opening the glass box:
	hypothetically try opening the glass box and consider the player's fate rulebook;
	if the outcome of the rulebook is:
		-- the player dies outcome: say "That would kill you."

That leads to this error:

> [4] open glass box
[opening the glass box]
Fatal Error: Stack overflow
1 Like

Looks like an error in the extension. I can try to debug it later.

That one’s because it goes into an infinite recursion—before opening the box, try opening the box, but before opening the box, try opening the box…

I think I might have narrowed it down to where it’s failing. I essentially tried to reverse engineer the extension to see how it works. What I find is that failure occurs when I reach this line:

@restoreundo rule;

That’s in the Hypo_Middle() function. Once that line is reached, the error mentioned above happens.

From what I can tell, this hasn’t worked for awhile. I found this comment from 2018:

I’m not sure if what was found there was the same issue but I too can’t get the example from the extension to compile.

So, the proximate cause of the failure is from Hypo_Capture_End:

glk_stream_close(glk_stream_get_current(), gg_arguments);
hypo_capture_len = gg_arguments-->1;
glk_stream_set_current(hypo_capture_previous);
hypo_capture_previous = 0;

This closes the current stream, and switches printing back to the stream saved in hypo_capture_previous. This is supposed to only be called after Hypo_Capture_Start saves the current stream in hypo_capture_previous, then creates a new memory stream writing to the hypo_capture array.

Except, for some reason, Hypo_Capture_End is getting called when that’s not the case. So it tries to close the stream that’s printing to the main window, and that crashes and burns.

Oh, wait. I think I see it. It’s from that semicolon I had you insert!

{-my:1} = Hypo_Start({phrase options});
if ({-my:1} == 1) {ph}
Hypo_Middle({R});
if ({-my:1} == 2) Hypo_End();

The intended flow for this code is:

  • Set my:1 to Hypo_Start
  • If my:1 is 1, then we’re in the hypothetical: call ph, then call Hypo_Middle
  • Otherwise, if my:1 is 2, then we’re out of the hypothetical: call Hypo_End
  • (Otherwise, if my:1 is 0, then something went wrong. Maybe the interpreter doesn’t support undo. In this case, Hypo_Start should have printed an error message already.)

But inserting that semicolon made it so that Hypo_Middle is called regardless of the state of my:1, meaning it’s called again after the hypothetical ends, when we’re back in the real world.

I believe it should look like this:

{-my:1} = Hypo_Start({phrase options});
if ({-my:1} == 1){
    {ph};
    Hypo_Middle({R});
} else if ({-my:1} == 2){
    Hypo_End();
}

Try that change and see if it works. Later I can fire up Inform and see if this makes one of the examples run properly.

EDIT: The older version, which you can find commented-out in the extension, uses a switch statement, which would make this structure simpler. Since it’s commented out I figure there’s some weird drawback which is why I’m not using that here. But that’s probably better style.

Both the newer code and the older code also pass “phrase options” to Hypo_Start, but no phrase options are defined, either in the phrase or in Hypo_Start. I wonder what that was meant to do.

2 Likes

I tried that. I no longer get the error, so that’s good!

However, now the failure condition is not hypothetical. So I still have this:

Before someone opening the glass box:
	hypothetically try opening the glass box and consider the player's fate rulebook;
	if the outcome of the rulebook is:
		-- the player dies outcome: say "That would kill you."

Now the output is:

> [6] skylar, open the box
[asking Skylar to try opening the glass box]
[(1) Skylar opening the glass box]
Skylar opens the glass box.
[(1) Skylar opening the glass box - succeeded]

[asking Skylar to try opening the glass box - succeeded]

The poison gas reaches your lungs.



    *** The End ***

I will note that with this change the example from the extension does compile. But I can’t quite tell if it’s acting successfully. It certainly runs and I do see the outputs and I think it’s what the author intended to happen. So, if that’s the case, perhaps my example isn’t set up right.

Here’s a slimmed down example:

"Testing" by Jeff Nyman.

Include Hypothetical Questions by Tara McGrew.

The player's fate is a rulebook.
The player's fate rulebook has outcomes player dies and nothing noteworthy (success - the default).
A player's fate rule when the story has ended: player dies.

A thing can be dangerous or benign.

Definition: a thing is exposed to the player if the player can touch it.

The Office is a room.

A woman called Skylar is in the Office.
Skylar wears the gloves of phasing.

A glass box is a container in the Office.
The glass box is closed, openable, and transparent.

In the glass box is some poison gas.
The poison gas is dangerous.

Instead of opening the glass box:
	say "The poison gas deters you."

Persuasion rule for asking Skylar to try doing something:
	persuasion succeeds.

A rule for reaching inside something:
	if the person reaching is Skylar and Skylar is wearing the gloves of phasing:
		allow access.

Every turn:
	if the player can touch the poison gas:
		say "The poison gas reaches your lungs.";
		end the story.

Before someone opening the glass box:
	hypothetically try opening the glass box and consider the player's fate rulebook;
	if the outcome of the rulebook is:
		-- the player dies outcome: say "That would kill you."
		
Test me with "actions / open glass box / skylar, open the box".

Given that the extension example seems to work, let me make sure I’m using everything correctly here.

Actually, I think my condition would be wrong. I’m doing this:

hypothetically try opening the glass box and consider the player's fate rulebook;

But that won’t work. The player is prevented from opening the box. But I can’t say “hypothetically try someone opening the glass box” or even “hypothetically try skylar opening the glass box” because the action is technically her being asked to do it.

So I tried: “hypothetically try asking Skylar to try opening the glass box and consider the player’s fate rulebook” but that doesn’t work.

So my guess is the extension is now working and I just need to figure out how to get the condition I want.

Yeah, when I was recreating the extension the only thing I found on that was in 27.17 (“Handling phrase options”). But the example code didn’t seem to match what was shown there.

Unrelated but I also was trying to see if some of that code was using “invocations” which are talked about in 27.29. And that part of the manual says: “Please do not use any of the undocumented invocation syntaxes: they change frequently, without notice or even mention in the change log.” (Bold in the original.)

The hypothetical flag is initially false.

Persuasion when the hypothetical flag is false:
    now the hypothetical flag is true;
    hypothetically try the current action and consider the player's fate rulebook;
    if the outcome of the rulebook is the player dies outcome:
        say "That would kill you!";
        persuasion fails;
    make no decision.

The “hypothetical flag” prevents infinite recursion.

EDIT: In general, you can try printing “the hypothetical output” after running a hypothetical to see what would have gotten printed. That will show error messages and such.

1 Like

Got it. So I definitely believe you fixed the extension with your change.

I think in my case, part of what I’m trying to do won’t work. I say that because even with your persuasion example, the game still ends when the hypothetical condition is tried. I added some say statements to your example like this:

Persuasion when the hypothetical flag is false:
	now the hypothetical flag is true;
	say "-- [the current action].";
	hypothetically try the current action and consider the player's fate rulebook;
	say "-- [the hypothetical output].";
	if the outcome of the rulebook is the player dies outcome:
		say "That would kill you!";
		persuasion fails;
	make no decision.

What that gets me in output is:

>[3] skylar, open the box
[asking Skylar to try opening the glass box]
-- asking Skylar to try opening the glass box.
-- .

[(1) Skylar opening the glass box]
Skylar opens the glass box.
[(1) Skylar opening the glass box - succeeded]

[asking Skylar to try opening the glass box - succeeded]

The poison gas reaches your lungs.



    *** The End ***

So it’s not getting output. Maybe because the check only comes with an every turn rule?

Every turn:
	if the player can touch the poison gas:
		say "The poison gas reaches your lungs.";
		end the story.

I’m thinking I might just need to rethink this approach. Looking at what the consequences of an action will be is a bit trickier than I thought.

On the plus side, however, totally awesome that you found the issue with the extension. It’s actually quite an interesting extension in terms of how it works.

2 Likes

Is the issue that you don’t so much need to be testing the current action (asking skylar to try opening the glass box) as the requested action (skylar opening the glass box)? I think there’s a variable that keeps track of that, though I forget what it is – and apologies if this isn’t a useful comment, most of this conversation is well above my pay grade, but since that potential issue occurred to me and it seems like you’re getting stymied I thought it might be worth flagging!

EDIT: oh wait, just saw the last bit of your post. Yeah, since the every turn rulebook doesn’t fire as part of the action-processing sequence, that seems like it could be the issue. It’d probably be simple enough to write a new rule to check whether poison gas is touchable after finishing every action, though (probably as a report rather than an after rule, so as not to preempt regular output), so maybe that’s worth a try?

1 Like

Yeah, the only thing that will be run hypothetically is the one phrase you specify: in this case, trying the current action. Other things, like the passage of time, won’t happen.

You can work around this by writing a phrase that runs the whole thing:

To execute the whole turn:
    try the current action;
    follow the turn sequence rules.
1 Like

Ah, yep, interesting! That does the trick.

So just to try something out, I went back to a more readable format that I was hoping to do:

Before asking someone to try doing something when the hypothetical flag is false:
	now the hypothetical flag is true;
	say "-- [the current action].";
	hypothetically execute the whole turn and consider the player's fate rulebook;
	say "-- [the hypothetical output].";
	if the outcome of the rulebook is the player dies outcome:
		say "That would kill you."

That gets you this:

>[3] skylar, open the box
[asking Skylar to try opening the glass box]
-- asking Skylar to try opening the glass box.
-- .
That would kill you.

[(1) Skylar opening the glass box]
Skylar opens the glass box.
[(1) Skylar opening the glass box - succeeded]

[asking Skylar to try opening the glass box - succeeded]

The poison gas reaches your lungs.



    *** The End ***

So notice that it does show the “That would kill you” text but goes on to end the story. But I found you can actually just stop the action.

Before asking someone to try doing something when the hypothetical flag is false:
	now the hypothetical flag is true;
	say "-- [the current action].";
	hypothetically execute the whole turn and consider the player's fate rulebook;
	say "-- [the hypothetical output].";
	if the outcome of the rulebook is the player dies outcome:
		say "That would kill you.";
		stop the action.

That actually does work. I’m not sure if that’s a viable way to do it in all cases. But it’s at least interesting to explore, under the assumption of keeping the code a bit more open to being qualified (such as “asking someone to try doing something dangerous” or “asking someone to try doing something selfish” and so on).

And, of course, the original persuasion rule idea works just fine as well. This is pretty nifty! Much thanks for your assistance.

@DeusIrae Thank you as well for your comment. I was heading in the same direction as well as you and I think we all converged on the same general idea here.

1 Like

Yep; “before” rules don’t stop the action by default. So you print the text saying “that would kill you” and then just let the action carry on.

If it’s in a “before” rule, yes, that’s the standard way to do it.

Fair point. McGrew’s version does use the {my:X} invocation to create a local variable, which is technically not documented and thus not supported.

I’ve cleaned it up a bit to avoid the undocumented invocations, bringing back the switch statement instead of using local variables. (An alternate way would be to wrap all of this in an I6 routine, instead of compiling it inline, in which case we can just declare our own local variables without needing {my:X}.)

Per the documentation, this should also be using {-open-brace} and {-close-brace} instead of literal { and }, but this causes internal errors. So the literal braces remain.

In testing, the example code compiles and works well in the latest version.

Hypothetical Questions-v5.i7x (8.4 KB)

1 Like