Skip to main content

Grid Navigation System

Grid Navigation System

Status: DESIGN Created: 2026-04-01 Supersedes: grid-layout.md (expanded with navigation, expandable grids, economy integration, multiplayer)

Last updated: October 21, 2026

Zoo Simulator - Grid Navigation System Design

Status: DESIGN Created: 2026-04-01 Supersedes: grid-layout.md (expanded with navigation, expandable grids, economy integration, multiplayer)

Overview

Transform Zoo Simulator from a container-based manager into a spatial strategy game with navigable grid zones. Players walk their zoo using N/S/E/W commands, place habitats and attractions on specific grid squares, and grow their zoo from a small 3x3 starter to a sprawling multi-zone complex. Other players can visit and walk through your zoo.

Design Pillars

  1. Start Small, Grow Big - 3x3 starter grid expands as players earn money and progress
  2. Placement Matters - Adjacency bonuses reward thoughtful layout design
  3. Walk Your Zoo - N/S/E/W navigation reuses proven Mystery/DF patterns
  4. Visit Friends - Party members and friends can tour your zoo layout
  5. Backward Compatible - Existing zoos auto-migrate with default layouts

Grid Model

Expandable Grid Sizes

Grids start small and grow as the player's zoo develops:

Grid Size Unlock Condition Plots Cost to Expand
3x3 Default (new zone) 9 Free
5x5 Zoo Score >= 200 25 5,000 Zoo Bucks
7x7 Zoo Score >= 500 49 15,000 Zoo Bucks
9x9 Zoo Score >= 800 81 30,000 Zoo Bucks
11x11 DEFERRED — not implemented (2026-05-16) 121 50 Golden Tickets

Each zone has its own grid. Different zones can be different sizes.

Build-Order Numbering & Persistence (player-facing "Attraction N" / "Habitat N")

Zones, habitats, and attractions live in Map<String, ?> keyed by random UUIDs. The player-facing 1-based numbering (view attractions / view habitats, and the buy animal habitat N / move animal habitat N / upgrade attraction N selectors) MUST reflect build order (oldest = 1), and the display and the selector MUST agree (fb 5370/5374).

A LinkedHashMap preserves build order only while the account stays Redis-hot. On the MySQL account_data JSON hydration path (any cache miss / TTL / corruption), MySQL normalizes JSON object keys to length-then-lexical order — and equal-length UUID keys sort purely lexically, scrambling build order (fb 8800/8801/8803: "attraction 3 is attraction 1"). The durable fix: each Attraction / Habitat (and pre-existing ZooZone) carries a builtDate; ZooLayoutOrdering sorts by it at every numbering call site, with legacy null-builtDate rows falling back to the trailing build number baked into the default name ("ride 3" -> 3). Numbering is NEVER derived from raw map iteration order.

zoo_account cache codec

builds the zoo_account Redis cache with NameBasedEnumKryo5Codec (not the default ordinal codec). A ZooAccount's serialized graph embeds drift-prone enums (Species, AttractionType, ZoneTheme, HabitatType, animal sex). The stock ordinal codec rebinds a cached enum to a different constant when constants are inserted mid-list (e.g. Species gained SQUIRREL/RACCOON/OPOSSUM/SKUNK between KANGAROO and the bird block, shifting every later ordinal), so a pre-deploy cached account decodes to the wrong species and crashes advance-day / breeding / feeding (fb 8800/8803/8824/8586/8587). Old entries self-heal on first read post-deploy (decode fails -> MySQL fallback -> re-cache under the name-based codec).

ZooGridSquare Model

@Data
public class ZooGridSquare implements Serializable {
    private Integer x;
    private Integer y;

    // What's built here
    private String plotType;       // EMPTY, HABITAT, ATTRACTION, PATH, ENTRANCE, DECORATION, CONSTRUCTION
    private String linkedId;       // UUID of habitat/attraction placed here (null for path/decoration/empty)
    private String linkedName;     // Display name ("Savanna Habitat 1", "Restaurant")

    // Descriptions (shown when player walks here)
    private String description;           // Full description
    private String shortDescription;      // Revisit text

    // Navigation
    private Boolean isBlocked;            // Can't walk here (under construction, wall)
    private Boolean isWalkable;           // Players and visitors can traverse (paths, entrances)

    // Visitor appeal
    private Integer appealBonus;          // From decorations and adjacency
    private Boolean isVisitorAccessible;  // Connected to entrance via path network

    // Construction
    private String constructionType;      // What's being built (null if complete)
    private Integer constructionDaysLeft; // 0 = complete
}

Plot Types

Type Symbol Walkable Visitors See Player Builds
EMPTY . Yes Nothing Available for building
HABITAT H No Animals (from adjacent path) Build habitat command
ATTRACTION A Yes Enter and spend money Build attraction command
PATH = Yes Walk along Build path (200 Bucks)
ENTRANCE E Yes Entry/exit point Fixed (auto-placed at edges)
DECORATION D No Aesthetic boost Place decoration (500-2000)
CONSTRUCTION C No "Under construction" Auto during build

Walkable vs. visitor-accessible: the Walkable column governs player movement and where structures may be placed — EMPTY land is walkable so a player can route through it while building. It does NOT define the visitor path network. Visitor accessibility is a BFS from each entrance that follows ONLY PATH, ATTRACTION, and ENTRANCE squares; a habitat earns visitor revenue only when it is adjacent to a square on that path network. Empty land never connects a habitat to the entrance for revenue purposes ().

Default 3x3 Grid (New Zone)

Status (2026-05-30): SEEDED ON DEMAND via . createZone still does not seed the grid (ZooZone.gridSquares is @Builder.Default new LinkedHashMap<>(), empty), but reconcileGrid is now the canonical grid-population path and is idempotent: on the first build or navigation it initializes the E-path-E grid (entrances at opposite corners + L-shaped path), then back-fills every habitat/attraction in the zone container that is not yet linked to a grid square, expanding the grid when it runs out of plots. It is called at build time (, , and the zone-builder fallback) AND on every navigation/view ( enter/navigate/look/view-map). This fixed the bug where structures built after the first navigation never appeared on the grid (the old migrateExistingZone was a one-time op that no-opped once gridSquares was non-empty), reported as "all my zone plots are staying empty". migrateExistingZone is retained as a thin wrapper over reconcileGrid.

E = .
. . .
. . E
  • Entrance at (0,0) and (2,2) — opposite corners
  • One path connecting them
  • 6 empty plots for building

Zone Data Model Changes

Add to existing ZooZone:

// Grid-based layout (new fields)
private Map<String, ZooGridSquare> gridSquares;  // Key: "x,y"
private Integer gridWidth;    // Current grid size (3, 5, 7, 9, or 11)
private Integer gridHeight;   // Same as width (square grids)

Reuses Proven Patterns From

Pattern Source Game What We Borrow
N/S/E/W movement Mystery Delta-based coordinate movement, move() method
Grid squares model Mystery Map<String, Square> keyed by "x,y"
Zone transitions Mystery Enter zone, go back to parent
Direction normalization DF 8-direction parsing, prefix stripping
Auto-detection on arrival Mystery Show what's at new location
interface Common Spring bean delegation pattern

Movement Commands

Command Action
"go north" / "north" / "n" Move to (x, y+1)
"go south" / "south" / "s" Move to (x, y-1)
"go east" / "east" / "e" Move to (x+1, y)
"go west" / "west" / "w" Move to (x-1, y)
"look" / "look around" Describe current square and adjacent
"view map" / "map" Speak a voice-friendly layout summary of the current zone, no ASCII grid ()
"enter zone [name]" Switch to different zone (accepts 1 enter zone, enter zone 1, enter zone one, theme name, or zone name)
"go back" / "back" Return to zone selection

Movement Flow (Per Step)

1. Calculate target: newX = currentX + dx, newY = currentY + dy
2. Bounds check: 0 <= newX < gridWidth, 0 <= newY < gridHeight
3. Lookup: targetSquare = zone.gridSquares.get(newX + "," + newY)
4. If null or blocked: "There is no path [direction] from here." then the open exits, "You can go
   south, east, or west.", offered as options. The direction is the first compass word anywhere in
   what the player said ("north into my zoo"), as a whole word.
5. Update position: progress.currentX = newX, progress.currentY = newY
6. Play SFX: FOOTSTEPS_HALLWAY (paths), CREAKY_DOOR (entering habitat area)
7. Show arrival text:
   - Square description
   - Adjacent habitat info ("To the north you can see the Lion habitat")
   - Adjacent attraction info ("There's a Restaurant to the east")
   - Available exits
8. Auto-detect: habitat viewing from adjacent path squares

What Players See When Walking

On a PATH square:

You walk along the zoo path. To the north, you can see the Savanna Habitat
with 3 lions and 2 zebras. To the east, there's a Restaurant (8 visitors
inside). From here you can go north, south, east, west.

On an ATTRACTION square:

You're at the Restaurant in Zone 1 (Africa). 12 visitors are dining.
Today's revenue: 240 Zoo Bucks. To the west you can see the Savanna
Habitat. From here you can go north, south, east, west.

On an EMPTY square:

An empty plot ready for development. You can build a habitat, attraction,
path, or decoration here. From here you can go south, west.

View Map Command (Voice Layout)

view map is read aloud by Alexa, so it does NOT render an ASCII grid. Equals signs, pipes, single-letter legends, and bracketed coordinates are voice-hostile (; .renderAsciiMap rewritten in commit 6564686465). It speaks the zone size, a natural-language count of each feature type, and where the player is standing:

Here is the layout of Africa. It is five squares wide and five squares deep. It has two entrances, two habitats, one attraction, six walkways, and one decoration. You are in the south west area. Walk north, south, east, or west to explore, or say look around for details.

Per-square detail for the current and adjacent squares comes from the look-around and movement describePosition path, not from view map.


Placement System

Build/Place Commands

Command Result
"place habitat savanna" Build savanna habitat on current empty square
"place attraction restaurant" Build restaurant on current empty square
"build path" Convert current empty square to path
"place decoration fountain" Add decoration to current empty square
"move habitat north" Relocate habitat to adjacent empty square (50% cost)
"demolish" Remove structure from current square (25% refund)
"expand zone" Upgrade grid from 3x3 to 5x5, etc.

Placement Rules

  1. Can only place on EMPTY squares
  2. Must be standing on or adjacent to the target square
  3. Habitats require matching zone theme (lion needs savanna-compatible zone)
  4. Construction takes 1 day (square shows CONSTRUCTION, usable next advance-day)
  5. Paths are instant (no construction delay)
  6. Decorations are instant

Construction Flow

Player: "place habitat savanna"
→ Validates: standing on empty square, has funds, habitat type matches zone
→ Deducts cost (2,500 Zoo Bucks for savanna)
→ Square becomes CONSTRUCTION (plotType="CONSTRUCTION", constructionType="HABITAT")
→ constructionDaysLeft = 1
→ "Construction started! Your Savanna Habitat will be ready tomorrow."
→ Next advance-day: constructionDaysLeft-- → 0 → convert to HABITAT
→ "Your Savanna Habitat is now open! You can buy animals for it."

Adjacency Bonus System

When visitors view exhibits from adjacent path squares:

Adjacent Pair Bonus Reason
Attraction + Habitat +30% attraction revenue Hungry/curious after watching animals
Attraction + Entrance +20% attraction revenue Impulse traffic entering/leaving
Habitat + Habitat +10% satisfaction Thematic coherence
Decoration + Any +5% appeal to adjacent Aesthetic enhancement

Note (2026-05-16): Original design called for finer-grained pairs (Restaurant+Habitat 30%, Shop+Entrance 20%, Playground+Habitat-babies 25%, Ride+Restaurant 10%, Habitat+Habitat 10%, Decoration 5%). collapses every AttractionType into a single ATTRACTION plot type — so the table above matches code reality. Reintroducing per-AttractionType bonuses would require inspecting the linked attraction in getAdjacencyBonusPair and is filed as a future enhancement (LOW severity).

Adjacency Calculation

public double calculateAdjacencyBonus(ZooZone zone, int x, int y) {
    double bonus = 0.0;
    ZooGridSquare center = zone.getGridSquares().get(x + "," + y);

    // Check 4 cardinal neighbors
    for each neighbor in [(x-1,y), (x+1,y), (x,y-1), (x,y+1)]:
        ZooGridSquare adj = zone.getGridSquares().get(neighbor);
        if (adj == null) continue;
        bonus += getAdjacencyBonusPair(center, adj);

    return bonus;
}

Visitor Pathing

BFS from Entrances

Visitors enter at ENTRANCE squares and walk along connected PATH/ATTRACTION squares:

1. Find all ENTRANCE squares in zone
2. BFS from each entrance along walkable squares (PATH, ATTRACTION, ENTRANCE)
3. Mark all reachable squares as isVisitorAccessible = true
4. For each reachable PATH square, check adjacent HABITAT squares
5. Habitats adjacent to reachable paths earn visitor revenue
6. Habitats NOT adjacent to any reachable path earn ZERO visitor revenue

Critical Design Point: Building habitats without connecting paths means no visitors see them. This creates the core strategic tension.

Visitor Journey (Modified from Current)

Current system generates visitors globally. New system:

Per visitor per zone:
1. Enter at random entrance
2. BFS walk along paths (visit 3-8 squares based on morale)
3. At each PATH square: view adjacent habitats (morale += appealBonus * quality)
4. At each ATTRACTION square: spend money (revenue += revenuePerVisitor * morale)
5. Apply adjacency bonuses to all revenue/appeal calculations
6. Track total distance walked (shorter paths = more efficient zoo)

Economy Integration

Grid Expansion Costs

Upgrade Cost Unlock
3x3 -> 5x5 5,000 Zoo Bucks Score >= 200
5x5 -> 7x7 15,000 Zoo Bucks Score >= 500
7x7 -> 9x9 30,000 Zoo Bucks Score >= 800
9x9 -> 11x11 50 Golden Tickets DEFERRED — .expandZone caps at 9x9 today; no Golden Ticket spend path exists (2026-05-16)

Building Costs on Grid

Same as existing costs but placement is now on a specific square:

Structure Cost Time
Path 200 Zoo Bucks Instant
Decoration (fountain) 500 Zoo Bucks Instant
Decoration (garden) 1,000 Zoo Bucks Instant
Decoration (statue) 2,000 Zoo Bucks Instant
Habitat (varies) 2,000-4,000 1 day
Attraction (varies) 1,500-5,000 1 day
Relocation 50% of build cost Instant
Demolish 25% refund Instant

Premium Grid Content

Golden Ticket purchases available on the grid:

Item GT Cost Effect
Premium Decoration (dragon statue) 10 GT +15% appeal to all adjacent
Premium Path (golden walkway) 5 GT +10% visitor spending on this path
Instant Construction 3 GT per building Skip 1-day wait
Grid Expansion (11x11) 50 GT DEFERRED — premium-only max size, not yet implemented

Multiplayer: Visit Other Zoos

Status: SHIPPED 2026-05-26 (Phase 2 of expansion-model.md). Handler + service + read-only walk + rate-zoo + destination-economy visit rewards now LIVE on develop. , , , zoo_simulator.zoo_visits, and zoo_simulator.zoo_visit_ratings shipped. ZOO_RATE_ZOO enum added; ZOO_VISIT_ZOO / ZOO_LEAVE_VISIT / ZOO_RATE_ZOO registered in process_action_registry + process_action_handlers + process_alternate_synonyms + process_one_shot_values. Canonical economic spec moved to expansion-model.md § Social/Visit Layer (Q4 destination-economy formulas) — the original "100 ZB × 3/day" spec below is SUPERSEDED. Scenario tests: 1539-1544. Phase 7 covers party-coop / comments / friend-list / featured-zoo-of-day.

Visit Flow

Player: "visit [friend name]'s zoo" or "visit zoo"
→ Load target player's zoo account (read-only)
→ Place visiting player at target's first zone entrance
→ Visiting player navigates freely using N/S/E/W
→ Each habitat viewed: show animals, training, enrichment
→ Each attraction visited: show revenue stats
→ Visiting player can leave comments ("rate zoo")
→ Visit counts toward target's daily visitor count (+5 per real visit)

Party Visit (Cooperative)

Party members can visit the leader's zoo together:

//  (extends Mystery's party pattern)
public void visitLeaderZoo(GameBasicProperties props) {
    // Teleport party member to leader's current zone + position
    // Shared viewing: all party members see same zoo
    // Party chat works during visit
}

Visit Rewards

Who Reward
Visitor 100 Zoo Bucks per zoo visited (3/day cap)
Zoo owner +5 visitors per real player visit
Both Friendship XP (if friends system active)

Visit Limitations

  • Can't modify another player's zoo
  • Can't buy/sell animals in other zoos
  • Read-only navigation with viewing descriptions
  • Visit sessions time out after 10 minutes of inactivity

Migration Plan (Existing Zoos)

Auto-Layout Algorithm

When zone.gridSquares == null (existing zoo, first load with grid system):

1. Set gridWidth = gridHeight = 3 (default starter)
2. Place entrance at (0,0) and (2,2)
3. Build path: (0,0)→(1,0)→(1,1)→(1,2)→(2,2)
4. Place first habitat at (0,1) (adjacent to path)
5. Place remaining habitats at (2,0), (0,2) (adjacent to path where possible)
6. Place first attraction at (2,1) (on path)
7. Remaining structures placed on remaining empty squares
8. If more structures than squares: auto-expand to 5x5, re-run

This ensures:

  • All existing habitats are on the grid and visible to visitors
  • All habitats are adjacent to at least one path
  • No progress is lost
  • Layout is functional (not optimal, player can rearrange)

NLU Registration

Relocation (ZOO_MOVE_STRUCTURE → ) is designed here but not built: has no moveStructure, so the enum carries no ZOO_MOVE_STRUCTURE constant. Building it adds the constant, its process_action_handlers row and its process_action_registry row together; fails while the registry row is missing.

FreeTextDeclarationAction Additions

// Grid navigation
ZOO_NAVIGATE,           // matchedKey = NORTH, SOUTH, EAST, WEST
ZOO_LOOK,               // View current square and surroundings
ZOO_VIEW_MAP,           // Speak voice-friendly zone layout, no ASCII ()

// Grid placement
ZOO_PLACE_STRUCTURE,    // matchedKey = habitat type, attraction type, path, decoration
ZOO_MOVE_STRUCTURE,     // matchedKey = direction
ZOO_DEMOLISH,           // Remove structure from current square
ZOO_EXPAND_ZONE,        // Upgrade grid size

// Visiting
ZOO_VISIT_ZOO,          // Visit another player's zoo
ZOO_RATE_ZOO,           // Rate a visited zoo
ZOO_LEAVE_VISIT,        // End visit

process_action_handlers

INSERT INTO game_process.process_action_handlers
  (story_action, game_type, handler_bean, handler_method, enabled) VALUES
('ZOO_NAVIGATE', 'ZOO_SIMULATOR', 'zooGridNavigationHandler', 'navigate', 1),
('ZOO_LOOK', 'ZOO_SIMULATOR', 'zooGridNavigationHandler', 'lookAround', 1),
('ZOO_VIEW_MAP', 'ZOO_SIMULATOR', 'zooGridNavigationHandler', 'viewMap', 1),
('ZOO_PLACE_STRUCTURE', 'ZOO_SIMULATOR', 'zooGridPlacementHandler', 'placeStructure', 1),
('ZOO_MOVE_STRUCTURE', 'ZOO_SIMULATOR', 'zooGridPlacementHandler', 'moveStructure', 1),
('ZOO_DEMOLISH', 'ZOO_SIMULATOR', 'zooGridPlacementHandler', 'demolish', 1),
('ZOO_EXPAND_ZONE', 'ZOO_SIMULATOR', 'zooGridPlacementHandler', 'expandZone', 1),
('ZOO_VISIT_ZOO', 'ZOO_SIMULATOR', 'zooVisitHandler', 'visitZoo', 1),
('ZOO_RATE_ZOO', 'ZOO_SIMULATOR', 'zooVisitHandler', 'rateZoo', 1),
('ZOO_LEAVE_VISIT', 'ZOO_SIMULATOR', 'zooVisitHandler', 'leaveVisit', 1);

process_alternate_synonyms

INSERT INTO game_process.process_alternate_synonyms
  (original_key, synonym_phrases, game_context) VALUES
('ZOO_NAVIGATE', 'go north', 'ZOO_SIMULATOR'),
('ZOO_NAVIGATE', 'go south', 'ZOO_SIMULATOR'),
('ZOO_NAVIGATE', 'go east', 'ZOO_SIMULATOR'),
('ZOO_NAVIGATE', 'go west', 'ZOO_SIMULATOR'),
('ZOO_NAVIGATE', 'north', 'ZOO_SIMULATOR'),
('ZOO_NAVIGATE', 'south', 'ZOO_SIMULATOR'),
('ZOO_NAVIGATE', 'east', 'ZOO_SIMULATOR'),
('ZOO_NAVIGATE', 'west', 'ZOO_SIMULATOR'),
('ZOO_NAVIGATE', 'walk', 'ZOO_SIMULATOR'),
('ZOO_NAVIGATE', 'move', 'ZOO_SIMULATOR'),
('ZOO_LOOK', 'look around', 'ZOO_SIMULATOR'),
('ZOO_LOOK', 'look', 'ZOO_SIMULATOR'),
('ZOO_LOOK', 'where am i', 'ZOO_SIMULATOR'),
('ZOO_VIEW_MAP', 'view map', 'ZOO_SIMULATOR'),
('ZOO_VIEW_MAP', 'show map', 'ZOO_SIMULATOR'),
('ZOO_VIEW_MAP', 'map', 'ZOO_SIMULATOR'),
('ZOO_VIEW_MAP', 'zoo layout', 'ZOO_SIMULATOR'),
('ZOO_PLACE_STRUCTURE', 'place habitat', 'ZOO_SIMULATOR'),
('ZOO_PLACE_STRUCTURE', 'place attraction', 'ZOO_SIMULATOR'),
('ZOO_PLACE_STRUCTURE', 'build path', 'ZOO_SIMULATOR'),
('ZOO_PLACE_STRUCTURE', 'place decoration', 'ZOO_SIMULATOR'),
('ZOO_PLACE_STRUCTURE', 'put habitat here', 'ZOO_SIMULATOR'),
('ZOO_PLACE_STRUCTURE', 'put restaurant here', 'ZOO_SIMULATOR'),
('ZOO_MOVE_STRUCTURE', 'move habitat', 'ZOO_SIMULATOR'),
('ZOO_MOVE_STRUCTURE', 'relocate', 'ZOO_SIMULATOR'),
('ZOO_DEMOLISH', 'demolish', 'ZOO_SIMULATOR'),
('ZOO_DEMOLISH', 'remove building', 'ZOO_SIMULATOR'),
('ZOO_EXPAND_ZONE', 'expand zone', 'ZOO_SIMULATOR'),
('ZOO_EXPAND_ZONE', 'upgrade zone', 'ZOO_SIMULATOR'),
('ZOO_EXPAND_ZONE', 'make zone bigger', 'ZOO_SIMULATOR'),
('ZOO_VISIT_ZOO', 'visit zoo', 'ZOO_SIMULATOR'),
('ZOO_VISIT_ZOO', 'visit friend', 'ZOO_SIMULATOR'),
('ZOO_RATE_ZOO', 'rate zoo', 'ZOO_SIMULATOR'),
('ZOO_LEAVE_VISIT', 'leave visit', 'ZOO_SIMULATOR'),
('ZOO_LEAVE_VISIT', 'go home', 'ZOO_SIMULATOR');

Scoring Impact

New Score Components

Component Max Points Formula
Path Network Efficiency 50 connected_habitats / total_habitats * 50
Adjacency Optimization 50 total_adjacency_bonus / max_possible * 50
Grid Utilization 25 used_squares / total_squares * 25
Visitor Path Coverage 25 visitor_accessible_habitats / total_habitats * 25

Added to existing 1000-point scale as bonus (max 1150 with perfect grid).


Example Playthrough

> build zone africa
Created Zone 1 with AFRICA theme! Your starting layout is a 3x3 grid.
  E = .
  . . .
  . . E
You're at the entrance (0,0). Go east to explore.

> go east
You walk along the path. This is an empty plot. You can build here.
From here you can go east, west.

> place habitat savanna
Construction started! Your Savanna Habitat will be ready tomorrow.
Cost: 2,500 Zoo Bucks. Balance: 12,000 Zoo Bucks.

> go east
This square is empty. To the west, construction is underway.
From here you can go south, west.

> build path
Path built! Cost: 200 Zoo Bucks. Balance: 11,800 Zoo Bucks.

> go south
You walk along the path. This is an empty plot.
From here you can go north, south, west.

> place attraction restaurant
Construction started! Your Restaurant will be ready tomorrow.
Cost: 3,000 Zoo Bucks. Balance: 8,800 Zoo Bucks.

> advance day
=== Day Summary ===
Construction complete: Savanna Habitat, Restaurant
Visitors: 25 (12 visited the Restaurant near the Savanna Habitat!)
Revenue: 580 Zoo Bucks (Restaurant earned +30% from habitat adjacency)

> view map
Here is the layout of Africa. It is three squares wide and three squares deep. It has two entrances, one habitat, and two walkways. You are in the east area. Walk north, south, east, or west to explore, or say look around for details.