More weirdness with enterable supporters that are part of things

In the discussion of the object hierarchy, Gavin brought up this case:

Test results:

[1] sit on couch
You get onto the couch.

[2] stand on couch
But you’re already on the couch.

[3] sit on tailgate
(getting off the couch)
You get onto the tailgate.

[4] stand on tailgate
(getting off the tailgate)
(getting out of the room)
But you aren’t in anything at the moment.

[5] g
You get onto the tailgate.

I’m baffled by what’s going on here. The key parts of the standard rules appear to be:

Check an actor entering (this is the implicitly pass through other barriers rule):
	if the holder of the actor is the holder of the noun, continue the action;
	let the local ceiling be the common ancestor of the actor with the noun;
	while the holder of the actor is not the local ceiling:
		let the current home be the holder of the actor;
		if the player is the actor:
			if the current home is a supporter or the current home is an animal:
				say "(getting off [the current home])[command clarification break]" (A);
			otherwise:
				say "(getting out of [the current home])[command clarification break]" (B);
		silently try the actor trying exiting;
		if the holder of the actor is the current home, stop the action;
[rule continues]

and

Carry out an actor getting off (this is the standard getting off rule):
	let the former exterior be the not-counting-parts holder of the noun;
	surreptitiously move the actor to the former exterior.

The first rule makes the player get out of things until they’re in the local ceiling, which is the common ancestor of the player with the original holder. The second rule carries out the exit by moving the player to the former exterior, which is the not-counting-parts holder of the player. Not-counting-parts holder translates into I6 as CoreOfParentOfCoreOf, and common ancestor translates into I6 as CommonAncestor, which takes CoreOf both arguments and finds if there’s anything that is indirectly CoreOfParentOfCoreOf both things.

What seems to be happening is that the local ceiling is getting set to the truck, and when the player exits from the tailgate it converts to getting off the tailgate which sends them to the room. Since the room is not the local ceiling, it tries exiting again, which fails.

Now, I don’t expect to understand what’s going wrong here, because I don’t understand I6 and especially not the object tree. But I found something that’s confusing even at the level I think I understand.

What I don’t understand is how you get moved from the tailgate to the room in the first place. I put in some debug code to allow you to retrieve the common ancestor and not-counting-parts holder:

Zooming is an action applying to one visible thing. Understand "zoom [any object]" as zooming.

Report zooming: say "The common ancestor of you with [the noun] is [common ancestor of the player with the noun]."

Booming is an action applying to nothing. Understand "boom" as booming. 

Report booming: say "The not-counting-parts holder of you is [not-counting-parts holder of the player]."

and got this:

room
You can see a truck (empty) and a couch here.

stand on tailgate
You get onto the tailgate.

zoom tailgate
The common ancestor of you with the tailgate is truck.

boom
The not-counting-parts holder of you is truck.

get off tailgate
You get off the tailgate.

room
You can see a truck (empty) and a couch here.

If the not-counting-parts holder of the player is the truck when they’re on the tailgate, and the standard getting off rule moves the player surreptitiously to their not-counting-parts holder [EDIT: The issues is that the rules moves the player to the tailgate’s not-counting-parts holder], why does it move them to the room rather than the truck? “Surreptitiously move” is just a call to an I6 “move.”

2 Likes

I think this is related to some unexpected behavior we observed a few weeks back where the lid of a closed dumpster was considered to be darkness. I think there is a fundamental conceptual confusion within the I7 standard rules regarding components: the stated intention is that they are on the exterior of the parent (and thus visible from outside), but the code tends to treat them as being identical to the parent. That is, if I have a thing T with a component C, the standard rules tend to treat things contained by C as if they were contained by T. This causes obvious problems if you have something like a desk (supporter) that incorporates a drawer (container), so the rules have special cases that make things work most of the time, but these break down once you move beyond these simple cases.

In your case, the player is contained by the tailgate, which is part of the truck, which is contained by the room. When you leave the tailgate, you enter (the core of) the parent of the core of the tailgate. The core of the tailgate is the truck, the parent of the truck is the room, and the room is its own core, so you end up in the room.

But what if you put something inside the tailgate, such as a rug or a box, and stand on/inside that. Now, when you exit, you would go to the core of the parent of (the core of) the rug. The rug is its own core, its parent is the tailgate, and the tailgate’s the core is the truck, so exiting the rug teleports you inside the truck!

4 Likes

I was actually thinking about the dumpster example when I wrote that – I was considering the idea of a truck that you could enter both the front and back of, and the back had a tailgate lift and a roller door that could be closed. Inside the back of the truck (but not the front) would be dark if the door was closed, and there’d also be a storage shelf inside the back of the truck.

This way around, the tailgate stays lit even if the back of the truck is dark. Since the front of the truck never gets dark it avoids the problem with the dumpster lid.

The extra bits of code for that were:

The back of the truck is part of the truck.  It is an unlit openable enterable container.
A storage shelf is in the back of the truck.  It is a supporter.
Test dark with "enter back of the truck / close back / open back / exit / enter tailgate / close back / l".

This wasn’t for any project I’m working on, it was just a thought experiment. But I concluded that the storage shelf shouldn’t be part of the truck, but the tailgate and back of the truck should be (the front of the truck wasn’t separately modeled, that was just “the truck”). But that’s when I noticed the odd behaviour of trying to stand on the tailgate you’re already sitting on, and how that differs from a regular supporter that isn’t part of something else.


Yes, that’s a bit peculiar too.

A rug is an enterable supporter on the tailgate.
Test rug with "stand on tailgate / stand on rug / stand on tailgate / stand on rug / exit / exit".

Explicitly standing on the tailgate from the rug works as expected, but the generic exit takes you to the truck’s front interior instead.

Probably to resolve this, the truck would have to have a truck cab that’s the actual vehicle instead or something, as part of the truck. It’s all very complicated.

2 Likes

Which is to say that my “zoom” test wasn’t checking the right thing. I had missed that the standard getting off rule moves you to the core of the parent of the core of the noun, and had set up “zoom” to see what the core of the parent of the core of the player was. So it makes sense that the getting off action moves you to the room… and that explains why the implicit exit when we try to enter the tailgate while on the tailgate moves you to the room, causing the problem we saw.

Here’s what I tenatively think, again to be taken with several grains of salt because of my unfamiliarity with I6 and the object tree. CoreOfParentOfCoreOf is constructed backwards for our purposes here. It moves up the component tree, moves up the contain/support tree, and then moves up the component tree again. But if a container/supporter that is part of a bigger thing is considered to be on the outside of the bigger thing, then exiting shouldn’t take you to the bigger thing but to its holder! In fact, the current behavior can lead to a mess; the rug test works the same even if the rug is defined as a thing rather than a vehicle, meaning that “stand on tailgate/stand on rug/exit” will put you in the truck even though the truck isn’t a container, let alone an enterable one.

I feel like the function we want is really ParentOfCoreOf? If your holder is part of something, figure out what holds that… and then stop. No need to worry about what that is part of, at least in this case.

I very incautiously made that alteration to CoreOfParentOfCoreOf:

There is a room.  In it is a truck and a couch.

The truck is a vehicle.
The tailgate is part of the truck.  It is an enterable supporter.

The couch is an enterable supporter.

Zooming is an action applying to one visible thing. Understand "zoom [any object]" as zooming.

Report zooming: say "The common ancestor of you with [the noun] is [common ancestor of the player with the noun]."

Booming is an action applying to one visible thing. Understand "boom [any object]" as booming. 

Report booming: say "The not-counting-parts holder of [the noun] is [not-counting-parts holder of the noun]."


Test me with "sit on couch / stand on couch / sit on tailgate / stand on tailgate / g"

A rug is an enterable supporter on the tailgate.
Test rug with "stand on tailgate / stand on rug / stand on tailgate / stand on rug / exit / exit".

Include 
(- 
[ HolderOf o;
	if (InitialSituation-->DONE_INIS == false) return thedark;
	if (o && (o.component_parent)) return o.component_parent;
	if (o && (parent(o))) return parent(o);
	return nothing;
];

[ ParentOf o;
	if (o) o = parent(o);
	return o;
];

[ CoreOf o;
	while (o && (o provides component_parent) && (o.component_parent)) o = o.component_parent;
	return o;
];

[ CoreOfParentOfCoreOf o;
while (o && (o provides component_parent) && (o.component_parent)) o = o.component_parent;
if (o) o = parent(o);
return o;
]; 
-) instead of "The Core Tree" in "WorldModel.i6t".

and getting off in the rug now takes you to the tailgate, as it should.

Trying to enter the tailgate when you’re already in it does have you perform the exit and then reenter it instead of refusing, but at least it works instead of having you enter the room. The issue is that the common ancestor of you and the tailgate is the room rather than the tailgate, so you go into the exit cycle, but it does stop once you’re in the room.

…oh, but getting on the rug from the room throws an infinite loop where you constantly enter and exit the truck. That’s bad. I think the issue is that the implicitly pass through other barriers rule has a lot of checks on the holder of the noun, and probably the holder of that, and that’s going to take you up the component tree. Though honestly if components are on the outside, shouldn’t at least some of these be not-counting-parts holders? As it is, you can get onto the tailgate from outside without getting into the truck, but you can’t get onto the rug from outside without getting into the truck, and that doesn’t really work in any world model.

Honestly I’m not sure why the can’t enter what’s already entered rule checks whether the noun is the common ancestor of the noun with the actor, instead of just checking whether the actor is directly in/on the noun. What are the cases where that makes a difference?..OK, I thought of one, and it’s bad! Posted below.

EDIT: Somehow I left a bunch of fragments here, so I tried to edit to complete my thoughts… one developed into a new example that I’ll post below.

1 Like

That is my thinking as well, at least in many cases. In terms of visibility, touch access, entering, etc., components should be treated as if they were separate things in the same location as their cores. (Actual interior components, like a shelf inside a safe, are probably best handled as fixed-in-place things inside the container, rather than components.)

I’m disappointed but not surprised to see that replacing CoreOfParentOfCoreOf with ParentOfCoreOf causes other problems. Assuming this sort of change is a good idea, actually doing it will probably require a lot of case-by-case decisions. (I get a similar issue where I can’t stand on the rug if I am in the room and the rug is on the dumpster lid, but I can get onto the lid and then onto the rug.)

In the long run, I suspect that Inform will want to distinguish between the current concept of enclosure and what we might call “direct enclosure”. If we have desk that incorporates a drawer, then everything in the drawer is enclosed by the desk, but only things on top of the desk are directly enclosed.

2 Likes

It does already have that, though – that’s the difference between containment and enclosure.

The desk contains only its own contents/supports (technically containment and support are separate I7 relations, but I think they end up stored in the same I6 place so it makes little difference).

Enclosure is a little trickier to reason about in general (since parts always should only be on the outside of a thing), but it seems reasonable to say that a desk encloses anything that is in the desk drawer as well. (The drawer still counts as “outside” the desk because it’s not on the supporting surface and is directly reachable from the location of the desk. And it counts as a part because were you to push the desk between rooms, it follows.)

1 Like

I’m experimenting with replacing “holder” in the implicitly pass through other barriers rule with “not-counting-parts holder” and I noticed this:

let the target be the holder of the noun;
	if the noun is part of the target, let the target be the holder of the target;

It looks like in this one case, the rule basically decided to go with ParentOfCoreOf instead of CoreOfParentOfCoreOf. This is after the actor has exited everything they need to exit, and the rule has checked whether the holder of the actor is the noun (in which case the exits have taken them where they need to go) and the holder of the actor is the holder of the noun (in which case there are no barriers left and the entering action can be tried).

So the remaining case is, the actor isn’t held by anything relevant but the noun is. And in this case, the rule checks to see whether the noun is part of something and then goes straight to the holder of that thing, instead of using CoreOfParentOfCoreOf/not-counting-parts holder, which would go on to whatever incorporated the new target.

…I bet this will lead to weird stuff when there’s an enterable thing that’s part of something that’s part of something. And indeed:

Museum is a room.

A statue is in the museum. The description of the statue is "The statue stands on a plinth. Its arm is stretched low to the ground."

A plinth is an enterable supporter. It is part of the statue. The description of the plinth is "A wide platform around the statue, which looks like you could sit on it."

An arm is part of the statue. The description of the arm is "The arm ends in an unusually wide and flat palm."

A palm is an enterable supporter. It is part of the arm. The description of the palm is "The palm is so wide and flat it looks like you could sit on it."

test me with "x statue/x arm/x palm/sit on palm/x plinth/sit on plinth".

Museum
You can see a statue here.

test me
(Testing.)

[1] x statue
The statue stands on a plinth. Its arm is stretched low to the ground.

[2] x arm
The arm ends in an unusually wide and flat palm.

[3] x palm
The palm is so wide and flat it looks like you could sit on it.

[4] sit on palm
(entering the statue)
That’s not something you can sit down on.

[5] x plinth
A wide platform around the statue, which looks like you could sit on it.

[6] sit on plinth
You get onto the plinth.

sit on palm
(getting off the plinth)
(getting out of Museum)
But you aren’t in anything at the moment.

2 Likes

I looked at the can’t enter what’s already entered rule and I really don’t see why it’s the way it is.

Check an actor entering (this is the can't enter what's already entered rule):
	if the actor is the noun, make no decision;
	let the local ceiling be the common ancestor of the actor with the noun;
	if the local ceiling is the noun:
		if the player is the actor:
			if the noun is a supporter:
				say "But [we]['re] already on [the noun]." (A);
			otherwise:
				say "But [we]['re] already in [the noun]." (B);
		stop the action.

So if we try this with the tailgate/truck example we get:

room
You can see a truck (empty) and a couch here.

get on tailgate
You get onto the tailgate.

get into truck
But you’re already in the truck.

Which is bad, because you aren’t already in the truck. You’re on the tailgate.

Similarly:

Hall is room. 

A dais is an enterable supporter in Hall. A throne is an enterable container. It is part of dais.

Hall
You can see a dais here.

sit on throne
You get into the throne.

stand on dais
But you’re already on the dais.

AFAICT the only cases where it makes a difference to use the local ceiling rather than checking to see if the player is directly on/in the noun are these, where the noun is part of/in/on something else and the actor tries to enter the other thing. But in those cases, the actor should be allowed to try entering the other thing!

Well, I guess there’s the case where the actor is on a supporter that’s part of something non-enterable, and then standing on the other thing might be blocked because it really amounts to standing on the supporter (which the player is already on). But honestly it seems like in those cases it should be the author’s responsibility to redirect entering the big object to the supporter (which then gets blocked by the can’t enter what’s already entered rule), since that’s what has to happen if the actor isn’t on the supporter.

BTW I reported the case at the top of the thread (due to Gavin IIRC) and expressed my opinion that enterable supporters that are parts of things are FUBAR.

1 Like

I left out too many words. My idea of direct enclosure is still recursive: the desk directly encloses everything it supports, and everything enclosed by the things it supports, but not its components or their contents. This is distinct from containment/support/etc, which are not recursive. I guess “direct” is ambiguous, but I don’t have a better alternative at the moment.

My point is, we will probably want to be able to distinguish, say, “everything that is ultimately inside a container” from “everything that will move with the container”. So, a paper on a tray in a box on top of a desk is “directly” enclosed by the desk, but an eraser in a box in a drawer incorporated by a desk is “indirectly” enclosed by the desk.

But let’s fix components first.

1 Like

On the downside, I expect this to be added to the “won’t fix/as intended” list.

On the upside, it’s in a part of the library code that’s relatively safe and easy to mess with (compared to e.g. the parser).

1 Like