I6 Template Layer

Inform 7 6M62ContentsIntroductionFunction IndexRules Index

WorldModel.i6t

WorldModel contents

The Core Tree.

Whereas I6 traditionally has a simple object tree hierarchy for containment, support, carrying and so on, the I7 template also uses the component_* properties to provide a second tree which defines the "part of" relation. These two trees interact in a subtle way, and it took a very long time to work out the simplest way to express this.

The core of an object is the root of its subtree in the component relation tree. So for a television set with a control panel which has a button on, the core for the button (and also for the panel and the set) will be the set. When X is a part of Y, it must be spatially in the same position as Y, and so the spatial location of X is determined by the position in the object tree of its core. (In effect, the spatial situation can be found by contracting together all nodes corresponding to objects which are parts of each other, but it would waste memory to construct such a tree: CoreOfParentOfCoreOf simulates what the parent operation would be in such a tree if it existed, wasting a little time instead.)

The holder of an object is its component parent, if it is part of something, or its object-tree parent if it has one. It is illegal for both of these to be non-nothing, so this is unambiguous.

It is just possible for HolderOf to be called before the player has been placed into the model world, in cases where Inform is checking a past tense condition in the opening pre-turn. We don't want to return nothing then because that would make it true that

HolderOf(player) == ContainerOf(player)

and similar conditions – thus, it would appear that in the immediate past the player had been on the holder of the player, which is not in fact the case. So we return thedark as a typesafe but impossible value here.

41[ HolderOf o; 42    if (InitialSituation-->DONE_INIS == false) return thedark; 43    if (o && (o.component_parent)) return o.component_parent; 44    if (o && (parent(o))) return parent(o); 45    return nothing; 46]; 47 48[ ParentOf o; 49    if (o) o = parent(o); 50    return o; 51]; 52 53[ CoreOf o; 54    while (o && (o provides component_parent) && (o.component_parent)) o = o.component_parent; 55    return o; 56]; 57 58[ CoreOfParentOfCoreOf o; 59    while (o && (o provides component_parent) && (o.component_parent)) o = o.component_parent; 60    if (o) o = parent(o); 61    while (o && (o provides component_parent) && (o.component_parent)) o = o.component_parent; 62    return o; 63];

Climbing the Core Tree.

LocationOf returns the room in which an object can be found, or nothing if it is out of play. Directions and regions are always out of play. Doors and backdrops can be in multiple places: if they are in the current location, then that's the answer; otherwise a two-sided door is in its "front side", and a backdrop in the earliest declared room which it's currently in. For a room, LocationOf is necessarily itself.

CommonAncestor finds the nearest object indirectly containing o1 and o2, or returns nothing if there is no common ancestor. (This is a port of CommonAncestor from the I6 library, adapted to work on the core tree.)

78[ LocationOf o; 79    if (~~(o ofclass K1_room or K2_thing)) return nothing; 80    if (o ofclass K4_door) { 81        if (parent(o) == real_location) return real_location; 82        return FrontSideOfDoor(o); 83    } 84    if (o ofclass K7_backdrop) { 85        ! print "(deciding ", (the) O, " is at ", (the) BackdropLocation(o), ") "; 86        return BackdropLocation(o); 87    } 88    while (o) { 89        if (o ofclass K1_room) return o; 90        o = CoreOfParentOfCoreOf(o); 91    } 92    return nothing; 93]; 94 95[ CommonAncestor o1 o2 i j; 96    o1 = CoreOf(o1); 97    o2 = CoreOf(o2); 98     99    for (i=o1: i: i = CoreOfParentOfCoreOf(i)) 100        for (j=o2: j: j = CoreOfParentOfCoreOf(j)) 101            if (j == i) return j; 102 103    return nothing; 104]; 105 106[ IndirectlyContains o1 o2; 107    if ((o1 == nothing) || (o2 == nothing)) rfalse; 108    if ((o1 ofclass K1_room) && (o2 ofclass K4_door)) { 109        if (o1 == FrontSideOfDoor(o2)) rtrue; 110        if (o1 == BackSideOfDoor(o2)) rtrue; 111        rfalse; 112    } 113    if (o2 ofclass K7_backdrop) rfalse; 114    for (o2 = HolderOf(o2) : o2 && o2 ~= thedark : o2 = HolderOf(o2)) if (o2 == o1) rtrue; 115    rfalse; 116];

To Decide Whether In.

A curiosity, this: the I6 definition of "To decide whether in (obj - object)". As can be seen, there are three possible interpretations of "in", depending on the kind of object, so this could all be done with three different definitions in the Standard Rules, so that run-time type checking would decide which to apply: but that would produce a little overhead and make the code for this rather common operation a bit complicated. Besides, this way we can produce a respectable run-time problem message when the phrase is misapplied.

Note that "in X" is not equivalent to "the player is in X": the latter uses direct containment, whereas we use indirect containment. Thus "in the Hall" and "in the laundry-basket" are both true if the player is in a laundry-basket in the Hall.

133[ WhetherIn obj; 134    if (obj has enterable) { 135        if (IndirectlyContains(obj, player)) rtrue; 136        rfalse; 137    } 138    if (obj ofclass K9_region) return TestRegionalContainment(real_location, obj); 139    if (obj ofclass K1_room) { 140        if (obj == real_location) rtrue; 141        rfalse; 142    } 143    RunTimeProblem(RTP_NOTINAROOM, obj); 144    rfalse; 145];

Containment Relation.

This is the single most important relation in I7: direct containment. It is complicated by the fact that "A is in B" is represented differently at run-time when A is a room and B is a region, and when it isn't.

For each A there is at most one B such that "A is in B" is true. Because of this we test the relation with a function of one variable which turns A into B, rather than a function of two variables returning true or false. ContainerOf is that function.

I7 frequently needs to compile loops over all A such that "A is in B". In simpler relations (such as the support relation below) it can do this efficiently by iterating through the object-tree children of B, but for containment we have to provide an iterator function TestContainmentRange, as otherwise we would get the wrong result when B is a region.

164[ ContainerOf A p; 165    if (A ofclass K1_room) return A.map_region; 166    p = parent(A); 167    if (p == nothing) return nothing; 168    if (p ofclass K5_container) return p; 169    if (p ofclass K1_room) return p; 170    if (p ofclass K9_region) return p; 171    return nothing; 172]; 173 174[ TestContainmentRange obj e f; 175    if (obj ofclass K9_region) { 176        objectloop (f ofclass K1_room && f.map_region == obj) 177            if (f > e) return f; 178        return nothing; 179    } 180    if (obj ofclass K5_container or K1_room) { 181        if (e == nothing) return child(obj); 182        return sibling(e); 183    } 184    return nothing; 185];

Support Relation.

This then is simpler, with no need for an iterator governing searches.

191[ SupporterOf obj p; 192    p = parent(obj); 193    if (p == nothing) return nothing; 194    if (p ofclass K6_supporter) return p; 195    return nothing; 196];

Carrying Relation.

Only people may carry, and something worn is not in this sense carried.

202[ CarrierOf obj p; 203    p = parent(obj); 204    if (p && (p ofclass K8_person) && (obj hasnt worn)) return p; 205    return nothing; 206];

Wearing Relation.

Only people may wear.

212[ WearerOf obj p; 213    p = parent(obj); 214    if (p && (p ofclass K8_person) && (obj has worn)) return p; 215    return nothing; 216];

Having Relation.

A person has something if and only if he either wears or carries it.

222[ OwnerOf obj p; 223    p = parent(obj); 224    if (p && (p ofclass K8_person)) return p; 225    return nothing; 226];

Making Parts.

Note that MakePart removes the part-to-be from the object tree before attaching it: it cannot, of course, be the core of the resulting object.

233[ MakePart P Of First; 234    if (P == player) return RunTimeProblem(RTP_CANTMAKEPART, Of); 235    if (parent(P)) remove P; give P ~worn; 236    if (Of == nothing) { DetachPart(P); return; } 237    if (P.component_parent) DetachPart(P); 238    P.component_parent = Of; 239    First = Of.component_child; 240    Of.component_child = P; P.component_sibling = First; 241]; 242 243[ DetachPart P From Daddy O; 244    Daddy = P.component_parent; P.component_parent = nothing; 245    if (Daddy == nothing) { P.component_sibling = nothing; return; } 246    if (Daddy.component_child == P) { 247        Daddy.component_child = P.component_sibling; 248        P.component_sibling = nothing; return; 249    } 250    for (O = Daddy.component_child: O: O = O.component_sibling) 251        if (O.component_sibling == P) { 252            O.component_sibling = P.component_sibling; 253            P.component_sibling = nothing; return; 254        } 255];

Movements.

Note that an object is detached from its component parent, if it has one, when moved.

262[ MoveObject F T opt going_mode was L; 263    if (F == nothing) return RunTimeProblem(RTP_CANTMOVENOTHING); 264    if (F ofclass K7_backdrop) { 265        if (T ofclass K9_region) { 266            give F ~absent; F.found_in = T.regional_found_in; 267            if (TestRegionalContainment(LocationOf(player), T)) move F to LocationOf(player); 268            else remove F; 269            return; } 270        if (T == FoundEverywhere) { 271            give F ~absent; F.found_in = FoundEverywhere; 272            return; 273        } 274        return RunTimeProblem(RTP_BACKDROP, F, T); 275    } 276    if (T ofclass K9_region) return RunTimeProblem(RTP_NOTBACKDROP, F, T); 277    if (T == FoundEverywhere) return RunTimeProblem(RTP_BACKDROPONLY, F); 278    if (~~(F ofclass K2_thing)) return RunTimeProblem(RTP_NOTTHING, F, T); 279    if (F has worn) { 280        give F ~worn; 281        if (F in T) return; 282    } 283    DetachPart(F); 284    if (going_mode == false) { 285        if (F == player) { PlayerTo(T, opt); return; } 286        if (IndirectlyContains(F, player)) { 287            L = LocationOf(T); 288            if (L == nothing) return RunTimeProblem(RTP_CANTBEOFFSTAGE); 289            if (LocationOf(player) ~= L) { 290                was = parent(player); 291                move player to real_location; 292                move F to T; 293                PlayerTo(was, true); 294                return; 295            } 296        } 297    } 298    move F to T; 299]; 300 301[ RemoveFromPlay F; 302    if (F == nothing) return RunTimeProblem(RTP_CANTREMOVENOTHING); 303    if (F == player) return RunTimeProblem(RTP_CANTREMOVEPLAYER); 304    if (F ofclass K4_door) return RunTimeProblem(RTP_CANTREMOVEDOORS); 305    if (IndirectlyContains(F, player)) return RunTimeProblem(RTP_CANTBEOFFSTAGE); 306    give F ~worn; DetachPart(F); 307    if (F ofclass K7_backdrop) give F absent; 308    remove F; 309];

On Stage.

The following implements the "on-stage" and "off-stage" adjectives provided by the Standard Rules. Here, as above, note that the I6 attribute absent marks a floating object (see below) which has been removed from play; in I7 only doors and backdrops are allowed to float, and only backdrops are allowed to be removed from play.

319[ OnStage O set x; 320    if (O ofclass K1_room) rfalse; 321    if (set < 0) { 322        while (metaclass(O) == Object) { 323            if (O ofclass K1_room) rtrue; 324            if (O ofclass K9_region) rfalse; 325            if (O ofclass K4_door) rtrue; 326            if (O ofclass K7_backdrop) { if (O has absent) rfalse; rtrue; } 327            x = O.component_parent; if (x) { O = x; continue; } 328            x = parent(O); if (x) { O = x; continue; } 329            rfalse; 330        } 331    } 332    x = OnStage(O, -1); 333    if ((x) && (set == false)) RemoveFromPlay(O); 334    if ((x == false) && (set)) MoveObject(O, real_location); 335    rfalse; 336];

Moving the Player.

Note that the player object can only be moved by this routine: this allows us to maintain the invariant for real_location and location (for which, see Light.i6t) and to ensure that multiply-present objects can be witnessed where they need to be.

345[ PlayerTo newplace flag L; 346    L = LocationOf(newplace); 347       if (L == nothing) return RunTimeProblem(RTP_CANTBEOFFSTAGE); 348    @push actor; actor = player; 349    move player to newplace; 350    location = L; 351    real_location = location; 352    MoveFloatingObjects(); 353    SilentlyConsiderLight(); 354    DivideParagraphPoint(); 355    if (flag == 0) <Look>; 356    if (flag == 1) give location visited; 357    if (flag == 2) AbbreviatedRoomDescription(); 358    @pull actor; 359];

Move During Going.

The following routine preserves the invariant for real_location, but gets location wrong since it doesn't adjust for light. Nor are floating objects moved. It should be used only in the course of other operations which get the rest of this right. (I7 uses it only for the "going" action, where these various operations are each handled by different named rules to increase the flexibility of the system.)

370[ MoveDuringGoing F T; 371    MoveObject(F, T, 0, true); 372    if (actor == player) { 373        location = LocationOf(player); 374        real_location = location; 375    } 376];

Being Everywhere.

The following is used as the found_in property for any backdrop which is "everywhere", that is, which is found in every room.

383[ FoundEverywhere; rtrue; ];

Testing Everywhere.

388[ BackdropEverywhere O; 389    if (O ofclass K7_backdrop) { 390        if (O has absent) rfalse; 391        if (O.found_in == FoundEverywhere) rtrue; 392    } 393    rfalse; 394];

Changing the Player.

It is very important that nobody simply change the value of the player variable, because so much else must be updated when the identity of the player changes: the light situation, floating objects, the real_location and so on. Because of this, the NI compiler contains code which compiles an assertion of the proposition "is(player, X)" into a function call ChangePlayer(X) rather than a variable assignment player = X. So we cannot catch out the system by writing "now the player is Mr Henderson".

We must ensure that if player is initially X, is changed to Y, and is then changed back to X, that both X and Y end exactly as they began – hence the flummery below with using remove_proper to ensure that proper is left with the correct value. Note that: (1) at any given time exactly one person has the I6 concealed attribute: the current player; (2) the selfobj is the default initial value of player, and because it has as its actual printed name "yourself", we need to override this when something else takes over as player: we change to "your former self", in fact. No such device is needed for other people being changed from because they are explicitly given printed names – say "Mr Darcy", "the sous-chef", etc. – in the source text.

419[ ChangePlayer obj flag; 420    if (~~(obj ofclass K8_person)) return RunTimeProblem(RTP_CANTCHANGE, obj); 421    if (~~(OnStage(obj, -1))) return RunTimeProblem(RTP_CANTCHANGEOFFSTAGE, obj); 422    if (obj.component_parent) return RunTimeProblem(RTP_CANTMAKEPART, obj); 423    if (obj == player) return; 424 425    give player ~concealed; 426    if (player has remove_proper) give player ~proper; 427    if (player == selfobj) { 428        player.saved_short_name = player.short_name; 429        player.short_name = PRINT_PROTAGONIST_INTERNAL_RM('c'); 430    } 431    player = obj; 432    if (player == selfobj) { 433        player.short_name = player.saved_short_name; 434    } 435    if (player hasnt proper) give player remove_proper; ! when changing out again 436    give player concealed proper; 437 438    location = LocationOf(player); real_location = location; 439    MoveFloatingObjects(); 440    SilentlyConsiderLight(); 441];

Floating Objects.

A single object can only be in one position in the object tree, yet backdrops and doors must be present in multiple rooms. This is accomplished by making them, in I6 jargon, "floating objects": objects which move in the tree whenever the player does, so that – from the player's perspective – they are always present when they should be. In I6, almost anything can be made a floating object, but in I7 this is strictly and only used for backdrops and two-sided doors.

There are several conceptual problems with this scheme: chiefly that it assumes that the only witness to the spatial arrangement of objects is the player. In I6 that was usually true, but in I7, where every person can undertake actions, it really isn't true any longer: if the objects float to follow the player, it means that they are not present with other people who might need to interact with them. This is why the accessibility rules are somewhat hacked for backdrops and doors (see Light.i6t). In fact we generally achieve the illusion we want, but this is largely because it is difficult to catch out all the exceptions for backdrops and doors, and because in practice authors tend not to set things up so that the presence or absence of backdrops much affects what non-player characters do.

All the same, the scheme is not logically defensible, and this is why we do not allow the user to create new categories of floating objects in I7.

The I6 implementation of MoveFloatingObjects acted on the location rather than the real_location, which (a) meant that multiply-present objects could include thedark – the generic Darkness place – as one possible location, but (b) assumed that the only sense by which the player could witness an object was sight. In I7, thedark is not a valid room, and we are a bit more careful about the senses.

475[ MoveFloatingObjects toroom i k l m address flag; 476    if (toroom == nothing) toroom = real_location; 477    if (toroom == nothing) return; 478    objectloop (i) { 479        address = i.&found_in; 480        if (address ~= 0 && i hasnt absent) { 481            if (ZRegion(address-->0) == 2) { 482                m = address-->0; 483                .TestPropositionally; 484                if (m.call(toroom) ~= 0) move i to toroom; 485                else { if (i in toroom) remove i; } 486            } else { 487                k = i.#found_in; 488                for (l=0 : l<k/WORDSIZE : l++) { 489                    m = address-->l; 490                    if (ZRegion(m) == 2) jump TestPropositionally; 491                    if (m == toroom || m in toroom) { 492                        if (i notin toroom) move i to toroom; 493                        flag = true; 494                    } 495                } 496                if (flag == false) { if (i in toroom) remove i; } 497            } 498            if ((i ofclass K4_door) && (parent(i) == nothing)) { 499                move i to ((i.door_to).call()); 500            } 501        } 502    } 503]; 504 505[ MoveBackdrop bd D x address; 506    if (~~(bd ofclass K7_backdrop)) return RunTimeProblem(RTP_BACKDROPONLY, bd); 507    if (bd.#found_in > WORDSIZE) { 508        address = bd.&found_in; 509        address-->0 = D; 510    } else bd.found_in = D; 511    give bd ~absent; 512    MoveFloatingObjects(); 513];

Backdrop Location.

This determines what the "location" of a backdrop is, or, if "target" is other than nothing, determines whether it can ever be the location. Note that this routine also works for two-sided doors, despite its name.

521[ BackdropLocation O target address m x i k l r sl; 522    if (O has absent) return nothing; 523    if ((target == nothing or real_location) && (parent(O) == real_location)) 524        return real_location; 525    address = O.&found_in; 526    if (address ~= 0) { 527        k = O.#found_in; 528        for (l=0 : l<k/WORDSIZE : l++) { 529            m = address-->l; 530            if (ZRegion(m) == 2) { 531                sl = location; 532                if (target) { 533                    location = target; 534                    r = m.call(); 535                    if (r ~= 0) { location = sl; return target; } 536                } else { 537                    objectloop (x ofclass K1_room) { 538                        location = x; 539                        r = m.call(); 540                        if (r ~= 0) { location = sl; return x; } 541                    } 542                } 543                location = sl; 544            } else { 545                if (m ofclass K9_region) { 546                    objectloop (x ofclass K1_room) 547                        if (TestRegionalContainment(x, m)) 548                            if (target == nothing or x) 549                                return x; 550                } else { 551                    if (target == nothing or m) return m; 552                } 553            } 554        } 555    } 556    return nothing; 557];

Wearing Clothes.

An object X is worn by a person P if and only if (i) the object tree parent of X is P, and (ii) X has the worn attribute. In fact for I7 purposes we are careful to ensure that (ii) happens only when (i) does in any case: if X is moved in the object tree, or made a part of something, or removed from play, then worn is removed.

567[ WearObject X P opt; 568    if (X == false) rfalse; 569    if (X notin P) MoveObject(X, P, opt); 570    give X worn; 571];

Map Connections.

MapConnection returns the room which is in the given direction (as an object) from the given room: it is used, among other things, to test the "mapped east of" and similar relations. (The same relations are asserted true with AssertMapConnection and false with AssertMapUnconnection.)

RoomOrDoorFrom returns either the room or door which is in the given direction, and is thus simpler (since it doesn't have to investigate what is through such a door).

Both routines return values which are type-safe in I7 provided the kind of value they are assigned to is "object": neither returns any specific kind of object without fail. (MapConnection is always either a door or nothing, but nothing is not a typesafe value for "door".)

Note that map connections via doors are immutable.

591[ MapConnection from_room dir 592    in_direction through_door; 593    if ((from_room ofclass K1_room) && (dir ofclass K3_direction)) { 594        in_direction = Map_Storage--> 595            ((from_room.IK1_Count)*No_Directions + dir.IK3_Count); 596        if (in_direction ofclass K1_room) return in_direction; 597        if (in_direction ofclass K4_door) { 598            @push location; 599            location = from_room; 600            through_door = in_direction.door_to(); 601            @pull location; 602            if (through_door ofclass K1_room) return through_door; 603        } 604    } 605    return nothing; 606]; 607 608[ DoorFrom obj dir rv; 609    rv = RoomOrDoorFrom(obj, dir); 610    if (rv ofclass K4_door) return rv; 611    return nothing; 612]; 613 614[ RoomOrDoorFrom obj dir use_doors in_direction sl through_door; 615    if ((obj ofclass K1_room) && (dir ofclass K3_direction)) { 616        in_direction = Map_Storage--> 617            ((obj.IK1_Count)*No_Directions + dir.IK3_Count); 618        if (in_direction ofclass K1_room or K4_door) return in_direction; 619    } 620    return nothing; 621]; 622 623[ AssertMapConnection r1 dir r2 in_direction; 624    SignalMapChange(); 625    in_direction = Map_Storage--> 626        ((r1.IK1_Count)*No_Directions + dir.IK3_Count); 627    if ((in_direction == 0) || (in_direction ofclass K1_room)) { 628        Map_Storage-->((r1.IK1_Count)*No_Directions + dir.IK3_Count) = r2; 629        return; 630    } 631    if (in_direction ofclass K4_door) { 632        RunTimeProblem(RTP_EXITDOOR, r1, dir); 633        return; 634    } 635    RunTimeProblem(RTP_NOEXIT, r1, dir); 636]; 637 638[ AssertMapUnconnection r1 dir r2 in_direction; 639    SignalMapChange(); 640    in_direction = Map_Storage--> 641        ((r1.IK1_Count)*No_Directions + dir.IK3_Count); 642    if (r1 ofclass K4_door) { 643        RunTimeProblem(RTP_EXITDOOR, r1, dir); 644        return; 645    } 646    if (in_direction == r2) 647        Map_Storage-->((r1.IK1_Count)*No_Directions + dir.IK3_Count) = 0; 648    return; 649];

Adjacency Relation.

A relation between two rooms which, note, does not see connections through doors.

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

Regional Containment Relation.

This tests whether an object with a definite physical location is in a given region. (For a two-sided door which straddles regions, the front side is what counts; for a backdrop, a direction or another region, the answer is always no.) We rely on the fact that every room object has a map_region property which is the smallest region containing it, if any does, and nothing otherwise; region objects are then in the object tree such that parenthood corresponds to spatial containment. (Note that in I7, a region must either completely contain another region, or else have no overlap with it.)

676[ TestRegionalContainment obj region o; 677    if ((obj == nothing) || (region == nothing)) rfalse; 678    if (obj ofclass K7_backdrop or K4_door) { 679        if (obj has absent) rfalse; 680        objectloop (o ofclass K1_room) 681            if (TestRegionalContainment(o, region)) 682                if (BackdropLocation(obj, o)) 683                    rtrue; 684        rfalse; 685    } 686    if (~~(obj ofclass K1_room)) obj = LocationOf(obj); 687    if (obj == nothing) rfalse; 688    o = obj.map_region; 689    while (o) { 690        if (o == region) rtrue; 691        o = parent(o); 692    } 693    rfalse; 694];

Doors.

There are two sorts of door: one-sided and two-sided.

A two-sided door is in two rooms at once: one is called the front side, the other the back side. The front side is the one declared first in the source. Note that a one-sided door is also an I6 object of class K4_door, and then the front side is the room holding it, while the back side is nothing.

The door_to property calculates the room which the door leads to; that of course depends on which side of it the player is standing. Similarly, door_dir calculates the direction it leads in. Unlike the I6 setup, where door_dir returned the direction property (n_to, s_to, etc.), here in I7's template it returns the direction object (n_obj, s_obj, etc.)

711[ FrontSideOfDoor D; if (~~(D ofclass K4_door)) rfalse; 712    if (D provides found_in) return (D.&found_in)-->0; ! Two-sided 713    return parent(D); ! One-sided 714]; 715 716[ BackSideOfDoor D; if (~~(D ofclass K4_door)) rfalse; 717    if (D provides found_in) return (D.&found_in)-->1; ! Two-sided 718    return nothing; ! One-sided 719]; 720 721[ OtherSideOfDoor D from_room rv; 722    if (D ofclass K4_door) { 723        @push location; 724        location = LocationOf(from_room); 725        rv = D.door_to(); 726        @pull location; 727    } 728    return rv; 729]; 730 731[ DirectionDoorLeadsIn D from_room rv dir; 732    if (D ofclass K4_door) { 733        @push location; 734        location = LocationOf(from_room); 735        rv = D.door_dir(); 736        @pull location; 737    } 738    return rv; 739];

Visibility Relation.

We use TestScope to decide whether there is a line of sight from A to B; it's a relation which cannot be asserted true or false.

746[ TestVisibility A B; 747    if (~~OffersLight(parent(CoreOf(A)))) rfalse; 748    if (suppress_scope_loops) rtrue; 749    return TestScope(B, A); 750];

Touchability Relation.

We use ObjectIsUntouchable to decide whether there is physical access from A to B; it's a relation which cannot be asserted true or false.

757[ TestTouchability A B rv; 758    if (A ofclass K4_door or K7_backdrop) MoveFloatingObjects(LocationOf(B)); 759    if (B ofclass K4_door or K7_backdrop) MoveFloatingObjects(LocationOf(A)); 760    if (TestScope(B,A) == false) rv = true; 761    else rv = ObjectIsUntouchable(B, true, A); 762    if (A ofclass K4_door or K7_backdrop) MoveFloatingObjects(); 763    if (rv) rfalse; 764    rtrue; 765];

Concealment Relation.

An activity determines whether one object conceals another; it's a relation which cannot be asserted true or false.

772[ TestConcealment A B; 773    if (A ofclass K2_thing && B ofclass K2_thing) { 774        particular_possession = B; 775        if (CarryOutActivity(DECIDING_CONCEALED_POSSESS_ACT, A)) rtrue; 776    } 777    rfalse; 778];