Bubbling Beaker Awards (Award #23, Apr 19 2024)

Thanks! I found it a while ago and had forgotten that it took some tinkering to update.

2 Likes

This week’s prize, Bubbling Beaker Award® #9 is presented to @Dannii, who is not a mad scientist but does very admirable work plumbing the depths of Inform’s internal machinery. This award is not for the post itself but for the extension that it announces, called Data Structures by Dannii Willis. This extension creates some entirely new data types for use in Inform 6M62 – no mean feat in a time before Inform 7 was made open source. As I understand it (which is not very well), the net effect is to loosen the typing restrictions of Inform somewhat, allowing values that work like weakly-typed variables, plus other kinds of value that offer affordances found in other programming languages (e.g. dictionaries, nullable values, two-value structs, and others). (NOTE: My understanding is that, unfortunately, the extension does not work in Inform 10 due to certain incompatibilities. It does work for 6M62.)

Congratulations, Dannii! The floor is yours if you would like to talk a little bit about your creation. Perhaps you can share a little about what motivated its creation, what makes it possible, the kinds of hurdles that you had to overcome in order to write it, and the prospects of a version compatible with Inform v10.

11 Likes

Thank you for this honour! Data Structures will surely be my Inform 7 magnum opus, I doubt I’ll ever make anything else for I7 that’s so ambitious.

So Data Structures was the third iteration, the first being the JSON extension, and that explains the initial motivation: I wanted to make an extension to handle parsing and generating JSON. And I wanted to do that because I thought it would be a better data format for a lot of situations than what Inform currently has, which is to use tables, even if the data doesn’t really fit into a two dimension array. The big problem with the JSON extension is that users would have to manually clean up the memory, and no one wants to have to manually call mfree all the time!

The second iteration was the Collections extension. The big change here was that the kinds you could put into them were not limited to the JSON types, but any (sayable) I7 kind. Map keys no longer had to be texts as they did in the JSON extension. But you still had to manually clean up.

The restriction of sayable kinds is because of how I implemented the any kind (called a collection reference): in order to store heterogenous data in these data structures, we need a way for values to carry their own kind IDs along with them. So Collections uses malloc to create a two value block, putting the kind ID in the first word, and the value itself in the second. But how do you get a kind ID in I7? The first kind ID that I found was to use the {-printing-routine:K} invocation, essentially the function for printing each kind. This worked, but did mean the system had to be limited to sayable kinds. As I was working on Data Structures I realised I could instead use the {-strong-kind:K} invocation, meaning that non-sayable kinds could also be used. And for composite kinds (like a list of K, or one of these new data structures), the strong kind actually refers to a I6 array that stores all the sub kinds. So if you make a “couple of (list of anys) and (map of objects to (map of numbers to list of texts))” then all of those kinds are stored and can be evaluated, no matter how deeply nested. The printing routine invocation would only give you the ID for “list” or “map” or “couple”, which is obviously much less useful.

So finally we come to the Data Structures extension. If there’s anything really impressive that I did, it’s that I slowly worked out how to define new block values without there being any documentation for how to do so. This took a lot of trial and error messing around with kind definitions in the Load-*.i6t templates (now called the Neptune language.) By using block values we removed the need to clean up manually, instead I7’s reference counting would take care of it all for us. It also meant that the new kinds would be much better integrated into the existing kind system; JSON and Collections both needed to have an array kind, but Data Structures doesn’t, instead the standard list kind is used, and integrates smoothly with the new kinds. Need an array of heterogenous kinds? Just use a list of anys!

While I was working on Data Structures I’d also been using Rust and TypeScript more, and had come to see how useful algebraic data types were, specifically sum types (also known as tagged unions). While I7 doesn’t yet support generic sum types, I did add some specific examples of sum types, namely the option and result kinds. Options let you store either a value or a null value, and are a type safe way of storing optional values, allowing you to distinguish 0 from NULL. Results let you store a success value or an error text message, also called checked exceptions. By returning a result you can require that any users of a phrase must check if it succeeded, so that errors can’t be ignored.

I also added a few other data structures I thought could be useful. Couples let you return two values from a phrase. (I wanted to make variable length tuples, but it wasn’t possible in 6M62. It might be possible in the future.) Promises represent a value yet to be determined, and will be familiar to many other languages. But the most ambitions and hacky was the closure kind, allowing you to freeze the state of a function and resume it later on. This required implementing a partial Quetzal parser in order to identify how many locals there are, amongst other nasty hacks. It’s truly gnarly.

There were huge changes to Inform across the board between 6M62 and version 10, so progress for an Inform 10 version of Data Structures is slowly ongoing. I’ve been prioritising getting the Glk system ready, but periodically check if Inform 10 can now handle everything needed for DS. We’re not quite there yet, but maybe soon. And it will possibly also be included as one of the built in kits. I’m also keen to change its implementation of maps from the naive linear search it currently has to a much more efficient binary search, though how to handle block values is an open question.

I’m not sure what else to say. Anyone have any questions about it?

14 Likes

PS, I want to claim mad scientist title for this abomination :stuck_out_tongue:

6 Likes

That’s horrible. I’m in awe.

4 Likes

This week’s prize, Bubbling Beaker Award® #10 is presented to @matt_weiner, who was at one point a very frequent contributor to the forum but has not been by for several years. (Honorable mention is given to @Skinny_Mike, who provided significant technical assistance at how and when does I7 guess which indirect article to use? - #19 by Skinny_Mike) Matt’s award-winning post concerns a method of improving Inform’s handling of irregular indefinite article selection in the context of conditional printed name output. The running example is a unicorn, which may or may not be honest – out-of-the box, I7 will prefer “an unicorn” and “a honest unicorn” when printing the name of this animal.

The code wasn’t quite perfected in the original post, but I’ve taken the liberty of updating and streamlining it a bit. Details will be given in a follow-up post.

3 Likes

Why is this interesting? Because I7’s default method of choosing whether to use “a” or “an” as an indefinite article is quite prone to error when faced with words that have irregular pronunciation. In short, it always picks “a” when the word starts with a consonant and “an” when the word starts with a vowel. (Note that the only vowels recognized by I7 are a, e, i, o and u, so y always counts as a consonant.)

Although the template code is good about ensuring that the entire printed name is considered (including anything prefixed via a Before printing the name... rule), as well as the number word for a plural group of identical objects such as “six hourglasses,” it has no provision for deeper phonetic analysis. English is just about the worst language for attempting to predict pronunciation from spelling, so the reason for this gap is easy to appreciate.

The solution presented is not particularly elegant; all it does is create exception tables that are checked when deciding which indefinite article to use. Any word that the author wants to act differently than the default is simply added to a table. Performance is not great, but it’s not a major problem for the limited number of exceptions that any one story is likely to require.

The following example code is for 6M62, but conversion to 10.1 is straightforward. Note that it is written for Glulx only:

An Honest Unicorn
"An Honest Unicorn"

The Lab is a room.

An animal can be honest or dishonest. Understand the honest property as describing an animal.

Before printing the name of an animal (called beast):
	say "[if beast is dishonest]dis[end if]honest ".

The unicorn is an honest animal.
The yak is a dishonest animal.

A block is a kind of thing. It has a number called weight. The description of a block is “It looks to be about [weight of the item described in words] pound[s].”

After examining a block (called B):
	now the indefinite article of B is "";
	now the printed name of B is “[weight of B in words]-pound block of [B]”.

The yttrium is a block with weight one. The indefinite article of the yttrium is "some".

The uniform is wearable. The description is “It’s a United States Army uniform.”

After examining the uniform for the first time:
	now the printed name of the uniform is “United States Army uniform”.

A unicorn, a uniform, an hourglass, a yak and yttrium are in the lab. An Æsop anthology is in the lab.
A rock, a pebble, and an umbrella are in the lab.

Table of Words That Start With Vowel Sounds
word
“Æsop”
“hour”
“hourglass”
“honest”
“yttrium”

Table of Words That Don’t Start With Vowel Sounds
word
“one”
“uniform”
“unicorn”
“united”
“United”

To decide whether (string - a text) starts with a vowel sound (this is vowel sound checking):
	let the first word be punctuated word number 1 in string;
	if the first word is a word listed in the Table of Words That Start With Vowel Sounds, yes;
	if the first word is a word listed in the Table of Words That Don’t Start With Vowel Sounds, no;
	if character number 1 in the first word is a vowel, yes;
	no.

To decide whether (letter - a text) is a vowel:
	let lower be letter in lower case;
	if lower is:
		-- "a": yes;
		-- "e": yes;
		-- "i": yes;
		-- "o": yes;
		-- "u": yes;
		-- otherwise: no.

The initial sound rules are a rulebook. The initial sound rules have outcomes vowel and consonant.

An initial sound rule (this is the basic initial sound test rule):
	let temp be the substituted form of “[I6 buffer]”;
	if temp starts with a vowel sound:
		vowel;
	otherwise:
		consonant.

To say the/-- I6 buffer:
	(- PrintI6Buffer(); -).

Include (-

[ PrintI6Buffer len i;
	len = StorageForShortName-->0;
	for ( i = WORDSIZE : i < len + WORDSIZE : i++ ) {
		glk_put_char(StorageForShortName->i);
	}
];

-).

[The only change of interest in this inclusion is to routine PrefaceByArticle]
Include (-

Global short_name_case;

[ PrefaceByArticle obj acode pluralise capitalise i artform findout artval;
	if (obj provides articles) {
		artval=(obj.&articles)–>(acode+short_name_case*LanguageCases);
		if (capitalise)
			print (Cap) artval, " ";
		else
			print (string) artval, " ";
		if (pluralise) return;
		print (PSN__) obj; return;
	}

	i = GetGNAOfObject(obj);
	if (pluralise) {
		if (i < 3 || (i >= 6 && i < 9)) i = i + 3;
	}
	i = LanguageGNAsToArticles-->i;

	artform = LanguageArticles + 3*WORDSIZE*LanguageContractionForms*(short_name_case + i*LanguageCases);

	#Iftrue (LanguageContractionForms == 2);
	if (artform-->acode ~= artform-->(acode+3)) findout = true;
	#Endif; ! LanguageContractionForms

	#Iftrue (LanguageContractionForms == 3);
	if (artform-->acode ~= artform-->(acode+3)) findout = true;
	if (artform-->(acode+3) ~= artform-->(acode+6)) findout = true;
	#Endif; ! LanguageContractionForms

	#Iftrue (LanguageContractionForms == 4);
	if (artform-->acode ~= artform-->(acode+3)) findout = true;
	if (artform-->(acode+3) ~= artform-->(acode+6)) findout = true;
	if (artform-->(acode+6) ~= artform-->(acode+9)) findout = true;
	#Endif; ! LanguageContractionForms

	#Iftrue (LanguageContractionForms > 4);
	findout = true;
	#Endif; ! LanguageContractionForms

	#Ifdef TARGET_ZCODE;
	if (standard_interpreter ~= 0 && findout) {
		StorageForShortName-->0 = 160;
		@output_stream 3 StorageForShortName;
	if (pluralise) print (number) pluralise; else print (PSN__) obj;
	@output_stream -3;
	}
	#Ifnot; ! TARGET_GLULX
	if (findout) {
		if (pluralise)
			VM_PrintToBuffer(StorageForShortName, 160, EnglishNumber, pluralise);	! MODIFIED
		else
			VM_PrintToBuffer(StorageForShortName, 160, PSN__, obj);	! MODIFIED
	}
	#Endif; ! TARGET_

	acode = acode + 3*LanguageContraction(StorageForShortName + WORDSIZE);	! MOVED
	Cap (artform-->acode, ~~capitalise); ! print article
	if (pluralise) return;
	print (PSN__) obj;
];

-) instead of “Object Names II” in “Printing.i6t”.

[The only change of interest in this inclusion is to routine LanguageContraction]
Include (-

Constant LanguageAnimateGender = male;
Constant LanguageInanimateGender = neuter;
Constant LanguageContractionForms = 2; ! English has two:
! 0 = starting with a consonant
! 1 = starting with a vowel
[ LanguageContraction text    result rv i;
	!!! This is the old routine:
	!if (text->0 == ’a’ or ’e’ or ’i’ or ’o’ or ’u’
	!or ’A’ or ’E’ or ’I’ or ’O’ or ’U’) return 1;
	!return 0;
	!!! Out w/ old – in w/ new:
	rv = FollowRulebook( (+ initial sound rules +), nothing, true );	! suppress line breaks
	if ((rv) && RulebookSucceeded()) {
		result = ResultOfRule();
		if (result == (+ vowel outcome +)) return 1;
		return 0;
	}
	return 0;
];

Array LanguageArticles -->
! Contraction form 0: Contraction form 1:
! Cdef Def Indef Cdef Def Indef
"The " "the " "a " "The " "the " "an " ! Articles 0
"The " "the " "some " "The " "the " "some "; ! Articles 1

! a i
! s p s p
! mf n m f n m f n m f n
Array LanguageGNAsToArticles --> 0 0 0 1 1 1 0 0 0 1 1 1;

-) instead of “Articles” in “Language.i6t”.

The code is oriented around special cases, so to make use of it you’ll want to update the two tables that handle these. The Table of Words That Start With Vowel Sounds is for words like “honest” that begin with a written consonant but spoken vowel. The Table of Words That Don’t Start With Vowel Sounds is for words like “unicorn” that begin with a written vowel but a spoken consonant.

As seen in the example code, special characters depicting ligated vowels can also be handled.

3 Likes

What happens when an American and an Englishman converse about “herbs” in general in the game?

4 Likes

This week’s prize, Bubbling Beaker Award® #11 is presented to @Draconis, which marks his second win. His award-winning post lays out a method for allowing the player to issue commands to multiple NPCs in a group, a la Infocom’s Suspended. I haven’t delved into the technical details, but from what I understand about the parser, this is not a simple modification. This work was developed into a public extension.

Congratulations, Draconis! Perhaps you would be willing to say a few words about what it took to get this to work?

[An important note: Award frequency will be decreasing to every other week between now and the end of the year, so the next award will be for Fri Nov 17.]

5 Likes

Oh, man, this one was a while ago! I need to refresh my memory for how this works!

Okay. Let’s imagine we’re giving the command ALICE AND BOB, GO NORTH.

The way the parser works by default, if it sees a comma, it jumps back to the beginning of the command and calls NounDomain. NounDomain is the routine that tries to parse the name of a single object, so most of the other parsing routines delegate to it.

That’s the bit that I modified: instead of calling NounDomain, I make it instead call ParseToken(ELEMENTARY_TT, MULTI_TOKEN)—that is, try to parse a “[things]” token.

This “[things]” token will fail, because we already know the list of actors ends with a comma—that’s how we ended up here in the first place. (If “[things]” sees something after a comma that it can’t understand, it thinks there’s a bad entry in the list.) But as it works, it puts the list of things it parses into the multiple object list, and it doesn’t clear that out when it fails! So now the multiple object list is {Alice, Bob}.

Once it fails, we go back into Inform 7 by calling the “multiple actor rulebook”, which takes the multiple object list, makes sure it’s a valid list of people, and stores it in a global variable of its own. It then takes the rest of the command—everything that comes after—and sticks this in another global variable. If this rulebook succeeds (the list was valid), then the parser continues after the comma, attempting to parse the rest of the line as a normal command. If it fails, then the parser reacts the same way it originally would if NounDomain failed—deciding the part before the comma isn’t an actor name after all, and trying to parse it as a verb instead (the .NotConversation label).

So now we have the list of intended actors {Alice, Bob} stored in a global variable, and the intended command GO NORTH in another global variable. Once we have this, a “rule for reading a command” takes over. As long as the list of intended actors isn’t empty, we set the player to the first element of that list, and parse the intended command. The player is now Alice, and the parser is given the text GO NORTH.

But wait, we don’t want this action to be performed as the player! We want it to be performed by an NPC! So a “first before doing anything” rule sees what’s happening, resets who the player is, and turns the action into a request: “going north” becomes “asking Alice to try going north”. It executes that action, then removes the first entry from the intended actor list.

This continues until the multiple actor list is empty. Since the command is actually parsed separately for each actor, it can deal with the actors having different surroundings: ALICE AND BOB, TAKE ROCK when each one has a different rock in their room, for example. The “rule for reading a command” also pauses when a disambiguation question is asked, letting the player answer before going back to multicommand processing.

As a side bonus, the extension also creates the action COMMAND [things] TO [text], which I used to test the multicommand machinery. In other words, this extension gives you a way to create new actions that convert to giving orders, which normally is nigh impossible! It just puts the list of actors and the intended command into the appropriate global variables, without any of the parser trickery.

9 Likes

Side note: the way Suspended does it is actually hilariously simple by comparison! It goes like this:

  • When parsing an actor, look for the exact phrase BOTH [person] AND [person]. If so, set the actor to a special “both” value.
  • If the “both” actor is commanded to perform any action except moving Fred, give an error.
  • Make sure all the actors involved are in the location of Fred.

Notably, it never even bothers to check that there are two different people involved! You never need multiple robots to accomplish something, even in the one specific place in the game where this syntax works—you can always say BOTH SENSA AND SENSA, MOVE FRED!

6 Likes

This week’s prize, Bubbling Beaker Award® #12 is presented to @capmikee, who used to be a frequent contributor to the forum. His award-winning post is really just a side note regarding a conversation extension that he was working on, but it demonstrates a method for handling a situation in which the author would like the grammar line for a two-object action to handle only the second noun.

Congratulations to capmikee! Because he has been inactive for so long, I will provide a short write-up in a follow-up post.

[An important note: Award frequency will be continued at every other week between now and the end of the year, so the next award will be for Fri Dec 01.]

6 Likes

This week’s award is interesting because the compiler will disallow code such as:

Quizzing it about is an action applying to one thing and one visible thing.

Understand "ask about [any known thing]" as quizzing it about (with nouns reversed).

The produced Problem message states: “...you can't use a 'reversed' action when you supply fewer than two values for it to apply to, since reversal is the process of exchanging them.” Despite use of the keyword “supplying,” the compiler does not recognize attempts to provide a rule for supplying a missing noun... as a valid way to address its concerns.

In capmikee’s post, he shows that a for supplying a missing second noun... rule can be set up to manipulate the noun and second noun manually:

For supplying a missing second noun when an actor quizzing something about:
	now the second noun is the noun;
	now the noun is the interlocutor of the person asked.

(Note that the interlocutor is a property of people in the context of his sample code, and the property is updated by rules for speech actions.)

The logic for assignment of the noun must be provided by the author in this case, as there is logic in the parser that will demand a non-nothing noun at run-time. (This is because the action definition in this case specifies that it applies to one thing and one visible thing.) However, the redefinition of the nouns persists throughout the remainder of action processing, so rules in other rulebooks can be constructed with action patterns that conform to the way that the author thinks about the action. For example:

After quizzing Bob about the soccer ball:
	say "Bob says, 'There's only one kind of football!'"

A question about does the player mean... (DTPM) rules comes up in capmikee’s post, but it is not addressed in detail. As he notes, the DTPM rules are handled prior to rules for supplying missing nouns, so there is a slight hitch in writing naturally-structured DTPM rules. For example, the rule

Does the player mean quizzing Bob about the soccer ball:
	it is likely.

will not work as expected because, even though the command entered might be >ASK ABOUT BALL, at the time that the DTPM rule is processed the noun is soccer ball and the second noun is nothing.

Fortunately, this is not hard to work around by also performing the switch within the DTPM rules. There’s a bit of a technicality here regarding the noun and second noun while processing DTPM rules, in that the assignments are temporary and will be undone at the end of processing the rulebook. Consequently, the manual swapping must be done twice: once during DTPM and once during supplying missing nouns.

Here’s a quick sample block showing the essentials in action:

DTPM with Noun Changes
"DTPM with Noun Changes"

Place is a room.

Bob is a man in Place.

A thing can be known.

A tennis ball is a known thing. A soccer ball is a known thing.

Quizzing it about is an action applying to one thing and one visible thing.

A thing has a thing called the interlocutor. [things not people, to leave room for "talkable" objects]

The interlocutor of the player is Bob. [set manually for brevity of example]

Understand "ask about [any known thing]" as quizzing it about.

To decide which object is the current actor: [needed because swaperoo rule is not in an action-processing rulebook]
	(- actor -).

This is the swaperoo rule:
	now the second noun is the noun;
	now the noun is the interlocutor of the current actor.

First does the player mean quizzing about: [placed first so that other DTPM rules see post-swap nouns]
	follow the swaperoo rule.

Does the player mean quizzing about the soccer ball: [soccer ball is *second* noun]
	it is likely.

For supplying a missing second noun when an actor quizzing about:
	follow the swaperoo rule.

Carry out quizzing about: [to show that the noun and second noun assignments persist from supply missing nouns stage]
	say "quizzing [noun] re: [second noun]..."

After quizzing Bob about the soccer ball: [sample specialized response]
	say "Bob says, 'There's only one kind of football!'"

In practice, a general report ... quizzing ... about ... rule to handle non-special cases would also be desirable, but that’s left as an exercise to the reader.

7 Likes

This week’s prize, Bubbling Beaker Award® #13 is presented to inimitable @Zed. It demonstrates a minor modification to the parser’s built-in ScoreMatchL() routine to ensure that it understands possessive pronouns when they apply to parts of a person instead of just things being carried by that person. (The OP is using the example of a command like >X HIS EYES.)

While this award-winning post may lack the scale of some of the more ambitious recipients, there is no doubt that it provides a general improvement in parsing. In addition to its utility, this post was selected because it’s a good example of the kind of small tinkering that starts one on the road to mad scientisthood, and so may encourage others to begin the journey.

Congratulations, Zed, on this your third BBA! I hope that you’ll drop by to say a few words about it; if not, then I’ll post a brief overview before the next award.

[An important note: Award frequency will continue at every other week between now and the end of the year, so the next award will be for Fri Dec 15.]

11 Likes

@Zed makes the following observation on the parser deficiency leading to this fix:

This is an oversight of the parser’s. When it’s evaluating the applicability of something with a possessive pronoun, it discounts parts and only looks at what the person has (i.e., carries or wears).

It would be more accurate to say that this is one of a number of places where the Inform parser has not been updated to take account of developments elsewhere in the language (in this case the introduction of ‘parts/incorporation’). At the time it was written, the existing code was fine.

5 Likes

aw, shucks! I didn’t even slightly remember this. :grinning: There’s not a lot to say about it: it was a fairly trivial tweak, with the only trick being finding where to make the trivial tweak.

Graham accepted this patch (PR #47), so it’ll be in the next version of Inform.

5 Likes

A quick overview of the details of Zed’s award for the beginner mad scientist…

The ScoreMatchL() routine (stands for “score match list”) is a routine inherited with some modifications from the I6 Standard Library. Its job is to generate a score that is used for automatic disambiguation of nouns, and it checks a number of conditions to decide on a composite score. If a player command is ambiguous about the noun (say >TAKE COIN when both a silver coin and golden coin are available) but ScoreMatchL() is able to determine a single highest scoring item, then it uses this “best guess” as the automatic choice, resulting in output like:

>TAKE COIN
(the silver coin)

Taken.

The I7 features that interact with this functionality directly are the does the player mean rules and the clarifying the parser's choice... activity, but the parser also has some I6-level functionality about possessive pronouns with special logic in this routine. This logic insists that certain I6-level conditions (which can’t be modified at the I7 level) are true in order to consider an object as a valid choice for the noun at all – if the conditions aren’t met, then the object is disqualified.

As drpeterbatesuk points out, the condition checked for a command like >X HER EYES is that the object matching the word “eyes” is an object tree child of the associated pronoun (in this example, “her”). In I6, if one wanted a person to have separate eyes as part of their body, the eyes object would be a child of the woman object in the object tree. In I7, the incorporation relation is handled with a separate, distributed object tree structure, which is missed by the older I6 logic, so even though the eyes object would match the word “eyes” in the player command, ScoreMatchL() would disqualify the object because it doesn’t understand the incorporation relation and can’t relate it to the woman object.

Zed’s change updates the logic to account for incorporation, so problem solved.

5 Likes

This week’s prize, Bubbling Beaker Award® #14 is presented to that paragon of productive mad science, @drpeterbatesuk. His award-winning post is ironically on this very thread! It shows a method for fetching the address of compiler-generated parsing routines for properties by crafting a special-purpose grammar line. This is useful when one wants to be able to have an action that uses two kinds of value (in this case, a colour and a colour), which is normally disallowed by the I7 compiler. (It’s also useful as an example of delving into the dictionary and grammar line structures generated at the I6 level, an obscure bit of knowledge.)

EDIT: I just realized that the way that I described the good doctor’s post above, it looks like a repeat of Award #6, given to BadParser. It’s true that the code takes the form of a solution to the same problem, but what’s significant and award-worthy is the way that it shows how to extract the relevant routine address from the compiled grammar lines, so that the code works even if the location of the routine changes. If one is trying to use a grammar line with two kinds of value, then the particular improvement here is that it can be set up once and then forgotten about, instead of having to hand-tweak the address with every new compilation.

Congratulations, drpeterbatesuk, on your own third Bubbling Beaker®! (Perhaps I should start a leaderboard.) Since your explanations are always excellent examples of completeness and clarity, there may not be much to add over what’s already been posted, but please feel free to take the floor.

[An important note: Award frequency will be continuing at every other week between now and the end of the year, so the next award will be for Fri Dec 30.]

8 Likes

I don’t think this is quite correct. That arrangement would imply that the eyes are either carried or worn by their owner, which often will not be what’s really wanted. The trick used in I6 that functionally most closely approximates the uses of I7’s incorporation relation is the property add_to_scope through which an author can add a list of objects that will always be in scope when the providing object is in scope. For this example, that means that whenever the player can interact with a given person through typed commands, they can also interact with that person’s eyes- even though the eyes are not children in the object tree of that person or even necessarily nearby (usually they will be kept offstage).

EDIT: this subterfuge works quite well for some purposes, but not others- e.g. a test for whether the person’s eyes are indirectly in the same room as their owner will fail.

3 Likes

It’s been a while since I’ve done any pure I6, but the approach that I recall using in the past is to mark the person object as transparent (so that children are placed in scope) and have any component parts be direct children.

Under that scheme, I guess body parts are indistinguishable from objects carried by the person object in terms of object tree and attribute state, but use of a BodyPart class with an appropriate before property (to handle ##Take and the like) always seemed like a good enough way to handle the functional distinction, if needed. (The default handling is Those seem to belong to the woman. Accurate, if understated.) I never really liked to use add_to_scope because of the “not really there” aspect to which you’re referring.

You made me curious, so I wrote a test scenario. Under StdLib 6/11, built-in possessive pronoun handling doesn’t work with objects handled via add_to_scope, for much the same reason that they don’t work in I7. In the scenario producing the following exchange, the eyes objects are handled via placement as direct child, and the nose objects are handled via add_to_scope:

Starting Point
An uninteresting room.

You can see a woman and a rock here.

>X WOMAN
Like you, she has a nose and eyes.

>X EYES
(your eyes)
You see nothing special about your eyes.

>X HER EYES
You see nothing special about the woman's eyes.

>X NOSE
You see nothing special about your nose.

>X HER NOSE
I only understood you as far as wanting to examine the woman.

>X HER ROCK
You see nothing special about the rock.

(The rock is interesting as a comparison. Because only one object matches via name, the restriction imposed by the possessive pronoun is ignored.)

3 Likes