[I7] Behavior of dark rooms

I’ve been considering how Inform 7 handles darkness. The intent was to come up with a better, more granular solution for handling darkness and wrap it as an extension.

My first thought was to simply study how it’s been done in the examples in the Recipe Book (Down Below, Hohmann Transfer, Unblinking, Zorn of Zorna, Four Stars 1 and 2, Peeled, and so on), or various games (Hunter in Darkness or Bronze come to mind). I wanted to ask what people would like from their perfect lighting model.

The more I considered that question though, the more it seemed backward. We already know the basic I7 model of darkness is limited, and we’ve discussed what it should do better. I think it’s more interesting to ask what the existing I7 model does well.

For instance, should the idea of a separate implementation of “dark room” be kept? Is it still useful, or just a legacy from the Infocom days? Do we even want a more complex general lighting system?

Hmmm… thinking about it, I would say that the separate dark room probably isn’t useful. It would be better to allow rooms to have the same lit property as things, and then a lit room would provide light when the player could see it (that is, when they were enclosed in it and not in an opaque container or something). I just checked that a lit opaque enterable closed container provides light when you’re inside it and it’s closed, so it’d be consistent to have lit rooms provide light when you’re inside them.

Admittedly this isn’t a thought that comes from any attempts to use the lighting system myself; it comes from seeing so many other people bang their heads against the difference between “lighted” (for rooms) and “lit” (for things), and the confusing thing about how “a dark room” doesn’t mean a room that’s actually in darkness but a room that doesn’t provide light.

It’s essentially the same as the I6 model.

It does two things well:

  • It tracks darkness/light with a not-too-expensive calculation which only needs to run once per turn.

  • It keeps other objects (in the dark room) out of scope. In Inform, the best way to keep an object un-referrable-to is to keep it outside the player’s room. For darkness, the objects stay in the original room but the player is moved to an empty room (“Darkness”). That solves that problem.

I chose a slightly different approach in Dialog.

There’s a routine to determine whether the player can see. This is a two-stage affair: First, traverse the object tree via parent-links from the player, until a room or closed opaque container is encountered (this is the “visibility ceiling”). If it is a room, check whether it’s inherently dark (this will often be compiled to a simple object flag). Otherwise, and this is the common case, we’re done; the player can see.

Second stage: If the room is inherently dark, or the visibility ceiling isn’t a room, consider each potentially light-providing object in the game. Does it currently provide light? If so, find the visibility ceiling of said object. If it is the same as the visibility ceiling of the player, the player can see. If there is no such light-providing object, the player is in darkness.

In code:

(player can see)
        (current player $Player)
        (visibility ceiling of $Player is $Ceil)
        (light reaches ceiling $Ceil)

(visibility ceiling of (room $R) is $R)

(visibility ceiling of $Obj is $Parent)
        ($Obj is #in $Parent)
        ($Parent is opaque)
        ($Parent is closed)

(visibility ceiling of $Obj is $Ceil)
        ($Obj has parent $Parent)
        (visibility ceiling of $Parent is $Ceil)

(light reaches ceiling (room $Ceil))
        ~(inherently dark $Ceil)

(light reaches ceiling $Ceil)
        *($Obj provides light)
        (visibility ceiling of $Obj is $Ceil)

In terms of performance, I think this computation is comparable to what Inform 6 needs to do. The difference being that Inform 6 will cache the result of the first stage, representing it as a special room. Zarf, am I mistaken?

Now the question is: How often does the game need to perform the computation? It needs to happen at least once per turn, when determining what objects are in scope. That is, the routine for determining the current scope needs to invoke ‘(player can see)’, and handle the result as two separate cases. In addition, the computation has to be carried out in certain action handlers (e.g. look, exits, read and a few more) to prevent actions that would be impossible in darkness, or to just phrase the standard response differently. Finally, the routine is invoked when printing the status line. So, roughly speaking, three times per move instead of one.

But this isn’t such a big deal. For games with a lot of potential light sources (usually there’s just a single lamp), the computation gets heavier. But it’s still negligible compared to parsing, which also happens on every move and involves word-matching against every object in scope.

Anyway, that’s my two cents. Hope they are helpful!