Compiler's relation constructor confused about adjacency?

I came across an old thread with an interesting relation, and I tried to construct a similar one:

Place is a room.

Viewability relates a room (called A) to a room (called B) when a door (called D) is adjacent to A and D is adjacent to B. The verb to be viewable from means the viewability relation.

A door called a glass door is east of Place.
Other Place is east of glass door.

A door called a grille is east of Other Place.
Yet Another Place is east of grille.

Every turn:
	showme whether or not Place is viewable from Other Place;
	showme whether or not glass door is adjacent to Place;
	showme whether or not glass door is adjacent to Other Place;
	showme whether or not glass door is adjacent to Yet Another Place;
	showme the list of rooms viewable from the location.

Test views with "z / e / e".

The relation does not work in either 6M62 or 10.1.2. The transcript looks like:

Place
You can see a glass door here.

>z
Time passes.

"whether or not Place is viewable from Other Place" = truth state: false
"whether or not glass door is adjacent to Place" = truth state: true
"whether or not glass door is adjacent to Other Place" = truth state: true
"whether or not glass door is adjacent to Yet Another Place" = truth state: false
"list of rooms viewable from the location" = list of rooms: {}
>e
(first opening the glass door)

Other Place
You can see a glass door and a grille here.

"whether or not Place is viewable from Other Place" = truth state: false
"whether or not glass door is adjacent to Place" = truth state: true
"whether or not glass door is adjacent to Other Place" = truth state: true
"whether or not glass door is adjacent to Yet Another Place" = truth state: false
"list of rooms viewable from the location" = list of rooms: {}
>e
(first opening the grille)

Yet Another Place
You can see a grille here.

"whether or not Place is viewable from Other Place" = truth state: false
"whether or not glass door is adjacent to Place" = truth state: true
"whether or not glass door is adjacent to Other Place" = truth state: true
"whether or not glass door is adjacent to Yet Another Place" = truth state: false
"list of rooms viewable from the location" = list of rooms: {}

Some digging into the generated I6 (6M62 version) shows three routines are important here:

  • TestAdjacency()
  • Relation_71()
  • Prop_35()

TestAdjacency() is part of the world model template/kit:

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! WorldModel.i6t: Adjacency Relation
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ TestAdjacency R1 R2 i row;
	if (R1 ofclass K9_region) RunTimeProblem(RTP_REGIONSNOTADJACENT, R1);
	else if (R2 ofclass K9_region) RunTimeProblem(RTP_REGIONSNOTADJACENT, R2);
	row = (R1.IK1_Count)*No_Directions;
	for (i=0: i<No_Directions: i++, row++)
		if (Map_Storage-->row == R2) rtrue;
	rfalse;
];

When invoked directly by the code for the every turn rule, it behaves as expected:

! [3: showme whether or not glass door is adjacent to place]
#ifdef DEBUG;
print "~whether or not glass door is adjacent to Place~ = truth state: ",
	(DA_TruthState) ((((TestAdjacency(I125_place,I126_glass_door))))), "^";	! <-- call to TestAdjacency() correctly evaluates true
#endif;

Note that the order of arguments in the preceding block is flipped from the order in the I7; glass door is adjacent to Place becomes TestAdjacency(I125_place,I126_glass_door). This is because the verb to be adjacent to is defined as the reversed adjacency relation by the Standard Rules.

The other two routines work together to decide whether the viewability relation holds:

! Routine to decide if viewability(t_0, t_1)
[ Relation_71 
	t_0 ! Call parameter 'A': object		! <-- room A mapped to t_0
	t_1 ! Call parameter 'B': object		! <-- room B mapped to t_1
	tmp_0 ! Let/loop value, e.g., 'D': door	! <-- door D mapped to tmp_0
	;
	  if (~~(t_0 ofclass K1_room)) rfalse;
	  if (~~(t_1 ofclass K1_room)) rfalse;
	return ((((
				(Prop_35(t_0) && (tmp_0=deferred_calling_list-->0, true)) ||	! <-- handles test for room A, always fails!
				(tmp_0 = 0,false)
			))) && (((TestAdjacency(t_1,tmp_0)))));	! <-- as I7 would be "door D is adjacent to room B"
];

! True or false?
! [ Exists x : door(x) & room(x) & called='d'(door)(x) & adjacent-to(const_0, x) ]	! <-- door(x) but also room(x)???
[ Prop_35 
	const_0 ! Implied call parameter	! <-- assigned value of t_0 parameter in Relation_71(), i.e. room A
	x ! internal use only
	x_ix ! internal use only
	;
	;
	for (x=IK1_First: x: x=x.IK1_Link){		! <-- iterates through ROOMS, not DOORS
	    if ((x ofclass K4_door) &&			! <-- can never be true!
			(TestAdjacency(const_0,x))){	! <-- as I7 would be "iterated room is adjacent to room A"
	        deferred_calling_list-->0 = x;
	        rtrue;
	    }
	}
	rfalse;
];

So the reason that this relation fails is that Prop_35() always fails, and the reason that Prop_35() always fails is that the loop is iterated through the wrong kind (i.e. rooms, not doors).

Based on the shorthand representation in the leading comment and the ofclass test inside the loop, the compiler clearly understands (on some level) that the routine should be dealing with a door. The logic of the generated code works correctly if the for loop parameters are adjusted manually to iterate through doors. So why is the code being generated this way?

2 Likes

Yeah, the compiler has always been a little confused as to the exact meaning of adjacency- I didn’t dig into the I6, but I noted this in my treatise on spatial relations:

Adjacency A room is adjacent to another room if it is possible to move directly from one to the other by a mapped direction- perhaps surprisingly, 2 rooms connected only by a door (regardless of whether the door is currently open or unlocked) are not adjacent to each other. Although the documentation states that adjacency applies only to two rooms, possibly as a bug it also sometimes applies to a room and a door, but that fact probably shouldn’t be relied upon. As a convenience, the adjective ‘adjacent’ applies to any room adjacent to the player’s current location.

This is what the documentation says about adjacency:

Another useful adjective built into Inform is “adjacent”. Two rooms are said to be adjacent if there is a map connection between them which does not pass through some barrier such as a door.

So it should by that definition be a logical impossibility that a door could be adjacent to anything, and the construction Viewability relates a room (called A) to a room (called B) when a door (called D) is adjacent to A and D is adjacent to B. The verb to be viewable from means the viewability relation. should be disallowed.

EDIT: to make this work reliably, you need to make a new relation called door-adjacency or suchlike and build your viewability relation from that.

1 Like

To your point, I also see that under Index/Relations the adjacency relation is listed as relating a room to a room, so it is fair play to say that this is not the intended use. Then the question becomes: Why aren’t the guards against improper use of kinds intervening at some point?

… oh. It seems that I somehow never really noticed that there don’t seem to be any such guards for conditions. For example, the compiler (in both 6M62 and 10.1.2) is fine with the condition glass door carries Place, which should be an illegal comparison given that Index/Relations says the carrying relation is between a person and a thing.

All right, then. So the answer to my question is basically: “Because the compiler doesn’t test conditions to see whether they use kinds improperly.”

This serves the purpose:

Portality relates a door (called D) to a room (called R) when R is the front side of D or R is the back side of D. The verb to provide passage from means the portality relation. The verb to provide passage to means the portality relation.

Viewability relates a room (called A) to a room (called B) when a door (called D) provides passage to A and D provides passage from B and D is open. The verb to be viewable from means the viewability relation.
1 Like

It’s worse than you think.

Try the following:

"Adjacency" by PB

The verb to be beside means the adjacency relation.

Origin is a room. Lab is south.  blue door is east. blue door is west of Store. blue door is an open door. 

When play begins:
	say "The blue door is [unless the blue door is adjacent to the Lab]not [end if]adjacent to the Lab.";
	say "The blue door is [unless the blue door is adjacent to the Store]not [end if]adjacent to the Store.";
	say "The Lab is [unless the Lab is adjacent to the blue door]not [end if]adjacent to the blue door.";
	say "The Store is [unless the Store is adjacent to the blue door]not [end if]adjacent to the blue door.";
	say "Adjacent to the blue door: [list of objects which are adjacent to blue door].";
	say "Adjacent to the Lab: [list of objects which are adjacent to Lab].";
	say "Adjacent to the Store: [list of objects which are adjacent to Store].";

[Pantry is a room.] Kitchen is a room. Pantry is south.  green door is east. green door is west of Cellar. green door is an open door. 

When play begins:
	say "The green door is [unless the green door is adjacent to the Pantry]not [end if]adjacent to the Pantry.";
	say "The green door is [unless the green door is adjacent to the Cellar]not [end if]adjacent to the Cellar.";
	say "The Pantry is [unless the Pantry is adjacent to the green door]not [end if]adjacent to the green door.";
	say "The Cellar is [unless the Cellar is adjacent to the green door]not [end if]adjacent to the green door.";
	say "Adjacent to the green door: [list of objects which are adjacent to green door].";
	say "Adjacent to the Pantry: [list of objects which are adjacent to Pantry].";
	say "Adjacent to the Cellar: [list of objects which are adjacent to Cellar].";

Attic is a room. red door is east. red door is an open door.

When play begins:
	say "The red door is [unless the red door is adjacent to the Attic]not [end if]adjacent to the Attic.";
	say "The red door is [unless the red door is adjacent to the Lab]not [end if]adjacent to the Lab.";
	say "The Attic is [unless the Attic is adjacent to the red door]not [end if]adjacent to the red door.";
	say "The Lab is [unless the Lab is adjacent to the red door]not [end if]adjacent to the red door.";
	say "Adjacent to the red door: [list of objects which are adjacent to red door].";
	say "Adjacent to the Attic: [list of objects which are adjacent to Attic].";


When play begins:
	say  "----Testing beside (adjacency) vs adjacent to (reversed adjacency)----[line break]";
	say "The red door is [unless the red door is beside the Attic]not [end if]beside the Attic.";
	say "The red door is [unless the red door is beside the Lab]not [end if]beside the Lab.";
	say "The Attic is [unless the Attic is beside the red door]not [end if]beside the red door.";
	say "The Lab is [unless the Lab is beside the red door]not [end if]beside the red door.";
	say "Beside the red door: [list of objects which are beside red door].";
	say "Beside the Attic: [list of objects which are beside Attic].";
	say "Beside the Lab: [list of objects which are beside Lab].";

I have filed a bug report.

Part of the issue seems to be that the property IK1_Count (enumerator for rooms) is declared as a common property at the I6 level. This means that it will read as value zero for objects that are not rooms, so any object not of the room kind will impersonate the first declared room in an adjacency check due to the line in TestAdjacency() that chooses which row of the map storage array to search:

row = (R1.IK1_Count)*No_Directions;	! <-- if R1 is not room, same result as for room having IK1_Count 0

For your scenario, the first declared room is Origin, and the generated Map_Storage array is:

Array Map_Storage -->
	0 0 0 I126_lab 0 0 0 0 0 0 0 0						! Exits from: I125_origin
	I125_origin 0 0 0 0 0 I127_blue_door 0 0 0 0 0		! Exits from: I126_lab
	0 0 0 0 0 0 0 I127_blue_door 0 0 0 0				! Exits from: I128_store
	0 0 0 I130_pantry 0 0 0 0 0 0 0 0					! Exits from: I129_kitchen
	I129_kitchen 0 0 0 0 0 I131_green_door 0 0 0 0 0	! Exits from: I130_pantry
	0 0 0 0 0 0 0 I131_green_door 0 0 0 0				! Exits from: I132_cellar
	0 0 0 0 0 0 I134_red_door 0 0 0 0 0					! Exits from: I133_attic
;

The only room truly adjacent to Origin is Lab. I think that explains all of the odd results you’ve shown.

It’s not perfect, but it would at least signal a problem usage to add some more kind checking to TestAdjacency():

[ TestAdjacency R1 R2 i row;
	if (R1 ofclass K9_region) RunTimeProblem(RTP_REGIONSNOTADJACENT, R1);
	else if (R2 ofclass K9_region) RunTimeProblem(RTP_REGIONSNOTADJACENT, R2);
	if (~~R1 ofclass K1_room) RunTimeProblem(RTP_BADVALUEPROPERTY, IK1_Count);		! ADDED
	else if (~~R2 ofclass K1_room) RunTimeProblem(RTP_BADVALUEPROPERTY, IK1_Count);	! ADDED
	...

Given the way that Map_Storage is generated and TestAdjacency() is written, from an operational perspective a door can be adjacent to a room (but not a door), and a room can be adjacent to either doors or other rooms (but only in cases where no door intervenes).

OK, so here’s a version of TestAdjacency() that behaves as advertised by WWI and the Index, except that rooms connected by a door count as adjacent (which I think is the natural naive interpretation):

6M62
Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! WorldModel.i6t: Adjacency Relation
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

! MODIFIED
! now returns object ID/true or nothing/false
[ TestAdjacency R1 R2 no_recurse      i row link rv;
	!print "<B = ", (name) R1, ", A = ", (name) R2, ">";
	if (R1 ofclass K9_region) RunTimeProblem(RTP_REGIONSNOTADJACENT, R1);
	else if (R2 ofclass K9_region) RunTimeProblem(RTP_REGIONSNOTADJACENT, R2);
	if ((~~no_recurse) && ((~~R1 ofclass K1_room) || (~~R2 ofclass K1_room))) return nothing;
	if (R1 == R2) return nothing;
	row = (R1.IK1_Count)*No_Directions;
	for (i=0: i<No_Directions: i++) {
		link = Map_Storage-->(row+i);
		if (link == R2) return R2;
		if (link ofclass K4_door) {
			rv =  DoorLink(link, R2);
			if (rv == R1) return R1;
		}
	}
	return nothing;
];

! ADDED
[ DoorLink D R1    R2;
	if (~~ D ofclass K4_door) return nothing;
	if ((D.&found_in)-->0 == R1)
		R2 = (D.&found_in)-->1;
	if ((D.&found_in)-->1 == R1)
		R2 = (D.&found_in)-->0;
	return R2;
];

-) instead of "Adjacency Relation" in "WorldModel.i6t".
10.1.2
Include (-

! MODIFIED
! now returns object ID/true or nothing/false
[ TestAdjacency R1 R2 no_recurse      i row link rv;
	!print "<B = ", (name) R1, ", A = ", (name) R2, ">";
	if (R1 ofclass K9_region) RunTimeProblem(RTP_REGIONSNOTADJACENT, R1);
	else if (R2 ofclass K9_region) RunTimeProblem(RTP_REGIONSNOTADJACENT, R2);
	if ((~~no_recurse) && ((~~R1 ofclass K1_room) || (~~R2 ofclass K1_room))) return nothing;
	if (R1 == R2) return nothing;
	row = (R1.IK1_Count)*No_Directions;
	for (i=0: i<No_Directions: i++) {
		link = Map_Storage-->(row+i);
		if (link == R2) return R2;
		if (link ofclass K4_door) {
			rv =  DoorLink(link, R2);
			if (rv == R1) return R1;
		}
	}
	return nothing;
];

-) replacing "TestAdjacency".

Include (-

! ADDED
[ DoorLink D R1    R2;
	if (~~ D ofclass K4_door) return nothing;
	if ((D.&found_in)-->0 == R1)
		R2 = (D.&found_in)-->1;
	if ((D.&found_in)-->1 == R1)
		R2 = (D.&found_in)-->0;
	return R2;
];

-).

Making an extension out of it would be quite simple.

EDIT: Updated code above to handle one-way map connections and avoid false negatives as pointed out by drpeterbatesuk below. Note that the handling for one-way doors is the same as in the original, which may be counterintuitive: if a one-way connection R1 → R2 is set up, then R2 is adjacent to R1 will be true, while R1 is adjacent to R2 will be false. In other words, for such a case A is adjacent to B if it is possible to move from B to A, but not vice versa.

Also note that use of the “long form” phrases for accessing relations, e.g. list of rooms to which R1 relates via the adjacency relation, will work the opposite way from what’s described above. This is due to the verb “to be adjacent to” being defined as meaning the reversed adjacency relation in the Standard Rules.

EDIT 2: Clearly, I was not paying enough attention when I wrote that code. There was also a bug in the DoorLink() routine due to a copy-paste error. That has been corrected, and one-way door situations are now handled.

It seems to me that this code, if it finds a link to any door from R1 before finding a link to R2, will return a result based on the adjacency of that door to R2 regardless of whether there may be a link to R2 later in the row of Map_Storage being iterated over?

A further thought- how should 1-way-doors be handled? If R1->D->R2 but D is 1-way such that there’s no return passage, is R2 adjacent to R1 and vice versa?

I suppose the same consideration applies to 1-way mapping dirrections- at present R1->R2 but R2-x-R1 means that R2 is adjacent to R1 (one can move directly from R1 to R2) but R1 is not adjacent to R2. This seems correct, since the relation is defined in terms of free movement rather than geography. However, a naive interpretation might asssume that if R1 is adjacent to R2 the reverse must also be true- indeed the definition given in WI might well be taken in this way as it doesn’t go into reciprocity or directionality of travel, but merely states that two rooms are adjacent if there is ‘a [direct] map connection between them’ when it would be more correct to say that ‘a room is adjacent to a second room if there is a [direct] map connection from the second to the first’.

If I were making an extension of this, I would tend to creating a new relation of door-adjacency in addition to the (corrected) built-in adjacency relation, rather than replacing it.