Best way to enumerate actions and rules applying to objects

For the past few weeks, I’ve been working on a project to extract information from a running Inform 7 to be used in an external game engine. I’ve had success with extracting the rooms within a game, things within a game, and their locations in rooms.

Next, my goal is to enumerate actions that the player can take. What’s the best way to do this?

Some context:

  • I’m not against using I6 code
  • I’m serializing the information into JSON and passing it thru Vorple to a JS frontend, so ideally I’d print information about these actions in that format. The exact method of serializing actions is out of scope for this question, but I will need to be able to “say” or print information about actions, so that’s my main focus.

If I understand correctly, even though I might think of each object in game having a list of actions associated with it, that’s not how Inform models actions, right? Actions are instead implemented as applying to a kind of object? (Let me know if I’m off track here.)

In that case, my first goal (1) would be to get a list of actions and the kinds of objects they apply to.

My next challenge would be to identify instances where instead rules have been implemented, since that’s typically how behavior is added to actions that normally “do nothing”, right? Is there a way (2) to enumerate these rules?

The compiled game file does not contain this information.

Or rather, it does, but only in the form of executable code. You can only find out what actions have been implemented by trying them.

In that case, my first goal (1) would be to get a list of actions and the kinds of objects they apply to.

Nearly all actions are defined as applying to “any object in scope” or “any object in reach”.

The action table is called ActionData (an I6 array), but it’s not going to get you very far.

2 Likes

Is there an I7 way to iterate thru the actions that exist during the compilation / code generation step?

i.e. something like the below? (which does not work)

repeat with action running through actions:
   Say "[action]";

Edit: or something similar for listing rulebooks? Either would be useful!

You can list the names of rulebooks.

Include (-
[ PrintRulebookNames i;
  for (i = 1 : i < NUMBER_RULEBOOKS_CREATED : i++) {
    print (string) RulebookNames-->i;
    print "^";
  }
];
-)

To say rulebook names:
    (- PrintRulebookNames(); -)

You should look at auto.inf and see what ni (the I7 compiler) actually generates. If you know C, much of it will be straightforward to read; the Inform 6 Documentation will fill in the less straightforward parts.

You’ll see that the I7 compiler does a huge amount of what would be considered ugly hard-coding if it were done by a human. And that’s why there are all these things where it seems like there must be a way to get at information, but we keep saying there isn’t.

That’s helpful! I have been searching thru both auto.inf as well as the template files used by I7, but I hadn’t found RulebookNames yet.

I notice it’s wrapped in an #IFNDEF MEMORY_ECONOMY, is that the macro used for “Release” vs. “Release for testing”?

A while back I wrote some code to identify the available non-trivial actions at runtime by inspecting ActionData and hypothetically executing each action to detect if any Instead rules apply (I needed this as part of an explicit-state model checker for I7: basically an algorithm to explore all reachable states of the story and check whether specified properties are violated; as you might expect, the state space is too large for this to work very well). I’ve collected the code here in case it’s helpful:

Code
Include (-
Global testingActions;

[ FindActions n i ad ac bits ;
	testingActions = VM_AllocateMemory(WORDSIZE * ActionData-->0 / AD_RECORD_SIZE);
	for (n = 0, i = 1 : i <= ActionData-->0 : i = i + AD_RECORD_SIZE) {
		ad = ActionData + (WORDSIZE * i);
		ac = ad-->AD_ACTION;
		bits = ad-->AD_REQUIREMENTS;
		if (bits & OUT_OF_WORLD_ABIT)
			continue;
		if (RulebookEmpty(CarryOutRB(ac)) && NoRelevantInsteadRules(ac))
			continue;
		testingActions-->(n++) = ad;
		!print ac, ": ";
		!SayActionName(ac);
		!print "; ^";
		!if (bits & NEED_NOUN_ABIT)
		!	print (PrintNounKind) ad-->AD_NOUN_KOV;
		!if (bits & NEED_SECOND_ABIT)
		!	print " to ", (PrintNounKind) ad-->AD_SECOND_KOV;
		!print "^";
	}
	testingActions-->n = 0;
];

[ NoRelevantInsteadRules ac r ;
	@protect latest_rule_result 4;
	@saveundo r;
	if (r == -1) {
		@protect 0 0;
		return latest_rule_result-->0;
	}
	latest_rule_result-->0 = 1;
	action = ac;
	if (B20_instead() ~= 0)
		latest_rule_result-->0 = 0;
	@restoreundo r;
];
-).

Include (- Replace GenericVerbSub OldGenericVerbSub; -) after "Definitions.i6t".
Include (-
Global interceptRulebooks = false;
[ CarryOutRB ac co ;
	interceptRulebooks = true;
	co = indirect(#actions_table-->(ac+1));
	interceptRulebooks = false;
	return co;
];
[ GenericVerbSub ch co re ;
	if (interceptRulebooks) return co;
	else OldGenericVerbSub(ch, co, re);
];
-).
2 Likes

This is very helpful, thanks! It’s actually helpful to my project for different reasons- I’ve been trying to figure out how to best determine the rulebook numbers for the check, carry out, and report rules for each action.

Right now I’m relying on string comparison with the rulebook names, but adding a routine that intercepts the call to GenericVerbSub is brilliant!