@motioneffector/spatial

Documentation

Gates

Gates control access on connections. They answer "Can the player go this way right now?" A gate might be a locked door, a hidden passage, a drawbridge controlled by game state, or a one-way drop. Gates attach to existing connections and evaluate against a traversal context you provide.

How It Works

When you call canTraverse(), the library checks if a gate exists on that connection. If it does, the gate's properties determine whether passage is allowed:

  • Locked gates check for a key in context.inventory
  • Conditional gates evaluate against a flag store (like @motioneffector/flags)
  • Hidden gates block until the gate ID appears in context.discovered

No gate means open passage. Gates don't block the connection from existing—they just control traversal at runtime.

[Hall] --EAST--> [Treasury]
          |
        Gate: { locked: true, keyId: 'gold-key' }
          |
        Without key: blocked
        With key: allowed

Basic Usage

import { createSpatialGraph, Direction } from '@motioneffector/spatial'

const graph = createSpatialGraph()
graph.createNode('hall')
graph.createNode('treasury')
graph.connect('hall', Direction.EAST, 'treasury')

// Add a locked gate
graph.setGate('hall', Direction.EAST, {
  id: 'treasury-door',
  locked: true,
  keyId: 'gold-key'
})

// Check traversal without key
const blocked = graph.canTraverse('hall', Direction.EAST, { inventory: [] })
console.log(blocked.allowed) // false
console.log(blocked.reason)  // 'locked'
console.log(blocked.gateId)  // 'treasury-door'

// Check traversal with key
const allowed = graph.canTraverse('hall', Direction.EAST, {
  inventory: ['gold-key']
})
console.log(allowed.allowed) // true

The gate blocks traversal until the player has the required key in their inventory.

Key Points

  • Gates attach to connections, not nodes — A gate goes on the path from A to B in direction D. The reverse path (B to A) can have a different gate or none at all.
  • Multiple properties combine — A gate can be both locked and hidden. The player must discover it AND have the key.
  • Gates are mutable — Use updateGate() to change properties (unlock a door, reveal a passage) without replacing the entire gate.
  • Removing a gate opens the pathremoveGate() removes access control; the connection remains.

Examples

Hidden Passages

Secret doors that must be discovered before they can be used.

graph.setGate('study', Direction.NORTH, {
  id: 'secret-bookcase',
  hidden: true,
  description: 'A bookcase that swings open'
})

// Player hasn't found it yet
const hidden = graph.canTraverse('study', Direction.NORTH, { discovered: [] })
console.log(hidden.allowed) // false
console.log(hidden.reason)  // 'hidden'

// Player discovered the passage
const found = graph.canTraverse('study', Direction.NORTH, {
  discovered: ['secret-bookcase']
})
console.log(found.allowed) // true

Conditional Gates

Gates that check game state via a flag store.

const flagStore = {
  check: (condition) => {
    // Your game's flag evaluation logic
    if (condition.check[0] === 'bridge_lowered') {
      return gameState.bridgeLowered === true
    }
    return false
  }
}

const graph = createSpatialGraph({ flagStore })

graph.createNode('cliff')
graph.createNode('fortress')
graph.connect('cliff', Direction.EAST, 'fortress')

graph.setGate('cliff', Direction.EAST, {
  id: 'drawbridge',
  condition: { check: ['bridge_lowered', '==', true] },
  blockedMessage: 'The drawbridge is raised.'
})

Unlocking Doors Dynamically

Change gate state during gameplay.

// Player uses key on door
graph.updateGate('hall', Direction.EAST, { locked: false })

// Now traversal is allowed without the key
const result = graph.canTraverse('hall', Direction.EAST, { inventory: [] })
console.log(result.allowed) // true

Custom Blocked Messages

Provide feedback when traversal fails.

graph.setGate('entrance', Direction.NORTH, {
  id: 'guard-post',
  locked: true,
  keyId: 'guard-badge',
  blockedMessage: 'The guard blocks your path. "Badge, please."'
})

const result = graph.canTraverse('entrance', Direction.NORTH, { inventory: [] })
// Use result.gateId to look up the gate and get blockedMessage
const gate = graph.getGate('entrance', Direction.NORTH)
console.log(gate?.blockedMessage) // 'The guard blocks your path. "Badge, please."'

Related