Skip to content

feat(viewer): add OpenWorlds atlas surface - #130

Merged
100yenadmin merged 2 commits into
mainfrom
viewer/openworlds-map-surface
May 26, 2026
Merged

feat(viewer): add OpenWorlds atlas surface#130
100yenadmin merged 2 commits into
mainfrom
viewer/openworlds-map-surface

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 26, 2026

Copy link
Copy Markdown
Member

Summary

Refs #117.

Adds the OpenWorlds atlas/map rollout slice as a viewer-owned read model bound to the exported Map screen.

  • Adds GET /atlas-surface.
  • Projects known locations, current location, route edges, travel options, quest markers, strategic clocks, downtime projects, region control, camp availability, and last strategic tick.
  • Binds viewer/openworlds/screen-map.jsx to /atlas-surface instead of prototype state.locations.
  • Keeps travel as a player intent through existing POST /move.
  • Keeps camp/rest display read-only until an engine-backed rest action is explicitly wired.
  • Filters hidden locations and private snapshot fields from the browser payload.

Architecture Notes

The atlas remains a read/adaptation layer:

  • build_atlas_surface(...) derives from the engine-owned campaign snapshot.
  • Browser clicks only select locations locally; they do not mutate travel state.
  • Travel buttons submit existing /move payloads such as { kind: "do", text: "Travel to Rain Market" }.
  • Hidden locations are omitted unless they are the current location.
  • Strategic clocks/projects/region control are read-only projections. The browser does not advance clocks, projects, world ticks, quest state, or location state.
  • Marking a location is local UI only and is labelled that way.

Files To Review

  • viewer/server.py
    • build_atlas_surface
    • _atlas_known_locations
    • _atlas_travel_options
    • _atlas_strategic
    • /atlas-surface route
  • viewer/openworlds/screen-map.jsx
    • atlasSurfaceFromCampaign
    • ScreenMap
    • AtlasSidebar
    • /move travel submission path
  • viewer/tests/test_atlas_surface.py
  • viewer/tests/test_openworlds_static.py

Validation

Local validation from /Volumes/LEXAR/repos/ClawDnD-openworlds-map-surface:

python3 -m py_compile viewer/server.py
python3 -m unittest discover -s viewer/tests -q
python3 scripts/license_check.py
git diff --check

Rendered smoke:

CLAWDND_STATE_DIR=/Volumes/LEXAR/Codex/clawdnd-atlas-surface-smoke python3 viewer/server.py camp_map 18767

Then Chrome/Playwright against http://127.0.0.1:18767/openworlds/, keyboard shortcut m:

  • Basilisk Gate and Rain Market rendered.
  • Hidden Hidden Crypt did not render.
  • Private strings did not render.
  • Selecting Rain Market rendered Find the Rain Seller and Sapper Cell Regroups.
  • No page errors.
  • Only known development warnings appeared: React DevTools info and Babel standalone production warning.

Screenshot artifacts:

  • /Volumes/LEXAR/Codex/clawdnd-atlas-surface-smoke/openworlds-atlas-1440x900.png
  • /Volumes/LEXAR/Codex/clawdnd-atlas-surface-smoke/openworlds-atlas-rain-market-1440x900.png

Rollback Notes

This PR is viewer-only. It does not change engine/rules/voice APIs or campaign persistence. Reverting it removes /atlas-surface and restores the previous prototype map behavior without touching saved campaign state.

Summary by CodeRabbit

  • New Features
    • World map now syncs live atlas data from the engine and auto-refreshes while visible; manual Refresh available
    • Added Make Camp / Camped toggle (disabled when unavailable) and reduced rest to a toast notification
    • Travel actions show immediate success/failure toasts and indicate busy state
    • Sidebar added: travel controls, quests/clocks/projects, and discovered locations
    • Simplified location pin hover previews showing current location and known routes

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d247b360-4fe4-4765-9c7f-64e73b1dd8d0

📥 Commits

Reviewing files that changed from the base of the PR and between db4b868 and 714bbd4.

📒 Files selected for processing (2)
  • viewer/server.py
  • viewer/tests/test_atlas_surface.py
📜 Recent review details
🧰 Additional context used
🧬 Code graph analysis (1)
viewer/tests/test_atlas_surface.py (1)
viewer/server.py (1)
  • build_atlas_surface (1614-1651)
🔇 Additional comments (3)
viewer/tests/test_atlas_surface.py (1)

184-213: LGTM!

viewer/server.py (2)

1435-1449: LGTM!


1598-1605: LGTM!


📝 Walkthrough

Walkthrough

This PR transitions the OpenWorlds map from client-modeled state to an engine-owned atlas read model. The backend projects snapshots via build_atlas_surface() and exposes /atlas-surface; the frontend polls that endpoint, derives map/time/travel state, renders AtlasMap/AtlasSidebar, and posts travel/camp actions back to the engine.

Changes

Atlas Surface Feature

Layer / File(s) Summary
Backend atlas projection helpers
viewer/server.py
Helpers compute visible location ids, hex/grid positions, tags, known locations, edges, travel disabled reasons/options, faction lookup, quest markers, and strategic slices used for the atlas.
Backend build_atlas_surface implementation
viewer/server.py
Assembles the final atlas payload: current location, known_locations, edges, travel_options, quest markers, strategic clocks/projects/region control, camp_available, last_world_tick_day, and control flags.
Backend route wiring and config
viewer/server.py
Adds "atlas_surface": "/atlas-surface" to _openworlds_config() and wires GET /atlas-surface to resolve campaign/catalog context and return build_atlas_surface() (or an empty- snapshot projection).
Frontend atlas fetch & state derivation
viewer/openworlds/screen-map.jsx
Adds atlasSurfaceFromCampaign(); ScreenMap fetches /atlas-surface (cancellable), polls every 7s while visible, and derives surface, time, map entities, travel/rest/camp availability, and ensures selected location validity.
Frontend UI refactor & travel/camp integration
viewer/openworlds/screen-map.jsx
Refactors into AtlasMap and AtlasSidebar (plus CampSidebar), adds camp toggle and refresh, renders SVG map (edges, time gradient, location pins), posts travel via POST /move with toast notifications, and makes rest a toast-only action.
LocationPin hover & window export
viewer/openworlds/screen-map.jsx
Simplifies hover preview (vignette + region/name + “Current location / Known route”) and exports atlasSurfaceFromCampaign to window along with ScreenMap and LocationPin.
HTTP route integration tests
viewer/tests/test_openworlds_static.py
Adds tests verifying the map template binds the atlas surface, selected-campaign projection safety, and catalog/read-only projection behavior (can_act/is_live_view).
Atlas surface unit tests
viewer/tests/test_atlas_surface.py
Unit tests cover full projection without private fields, closed/read-only behavior when not live, legacy save degradation, optional-list normalization, and a recursive private-key absence checker.

Sequence Diagram(s)

sequenceDiagram
  participant UI as ScreenMap
  participant Fetch as GET /atlas-surface
  participant Engine as Engine/Snapshot
  participant Sidebar as AtlasSidebar
  participant Move as POST /move

  UI->>Fetch: GET /atlas-surface (query from campaign)
  Fetch->>Engine: Read campaign snapshot
  Engine-->>Fetch: Snapshot data
  Fetch-->>UI: Atlas payload (locations, edges, travel_options, markers, strategic)
  UI->>UI: Update surface, time, selectedId

  loop Poll every 7s while visible
    UI->>Fetch: GET /atlas-surface
    Fetch-->>UI: Updated atlas payload
    UI->>UI: Refresh derived entities
  end

  Sidebar->>Move: POST selected travel option + campaign
  Move->>Engine: Execute move
  Engine-->>Move: Result
  Move-->>Sidebar: Success/Failure (toast)
  Sidebar->>UI: Request refresh
  UI->>Fetch: GET /atlas-surface
  Fetch-->>UI: Updated location/state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • 100yenadmin/ClawDnD#127 — Modifies viewer/server.py and _openworlds_config(); likely overlaps in config/route changes and should be checked for merge conflicts.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(viewer): add OpenWorlds atlas surface' accurately describes the main change: introducing a new atlas surface read model and endpoint for the OpenWorlds viewer.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • LINEAR integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@viewer/server.py`:
- Line 1450: The sort key lambda uses an ambiguous parameter name `l`; rename it
to a descriptive identifier (e.g., `entry` or `item`) in the call to out.sort so
the key becomes key=lambda entry: (not entry["current"], entry["name"]) to
satisfy E741 and improve readability — update the lambda in the out.sort(...)
invocation accordingly.
- Around line 1600-1605: The list comprehension that builds the "tags" field can
raise TypeError when row.get("tags") returns None because it iterates before the
isinstance check; fix by first assigning tags_list = row.get("tags") and then
using an explicit guard (e.g., tags_list = tags_list if isinstance(tags_list,
list) else []) and finally build tags via [_text(t) for t in tags_list if
_text(t)]; update the dict construction that sets "tags" to use this safe local
tags_list and keep using the _text helper for each element.
- Around line 1444-1448: The list comprehension for "connections" can raise
TypeError when row["connections"] exists but is None; update the code that
builds "connections" (around use of row, _text, visible_ids, and _atlas_tags) to
ensure iteration only happens over a sequence—e.g., first normalize connections
= row.get("connections") or [] (or otherwise guard with isinstance before
iterating) and then use [_text(c) for c in connections if _text(c) in
visible_ids]; this ensures None is treated as an empty list and avoids the
TypeError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9b20d01e-31ed-48fd-816a-2e142255fba8

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4330e and db4b868.

📒 Files selected for processing (4)
  • viewer/openworlds/screen-map.jsx
  • viewer/server.py
  • viewer/tests/test_atlas_surface.py
  • viewer/tests/test_openworlds_static.py
📜 Review details
🧰 Additional context used
🧬 Code graph analysis (1)
viewer/openworlds/screen-map.jsx (1)
viewer/openworlds/chrome.jsx (6)
  • Panel (246-261)
  • Divider (182-189)
  • Placeholder (205-214)
  • Pill (201-203)
  • BrassButton (237-244)
  • SectionTitle (191-199)
🪛 Ruff (0.15.14)
viewer/server.py

[warning] 1420-1420: Use a list comprehension to create a transformed list

(PERF401)


[error] 1450-1450: Ambiguous variable name: l

(E741)

🔇 Additional comments (26)
viewer/server.py (8)

1371-1396: LGTM!


1399-1410: LGTM!


1413-1421: LGTM!


1454-1510: LGTM!


1513-1543: LGTM!


1612-1649: LGTM!


2770-2775: LGTM!


2980-3005: LGTM!

viewer/tests/test_atlas_surface.py (4)

1-12: LGTM!


15-148: LGTM!


150-168: LGTM!


170-206: LGTM!

viewer/tests/test_openworlds_static.py (3)

108-117: LGTM!


430-480: LGTM!


482-513: LGTM!

viewer/openworlds/screen-map.jsx (11)

1-11: LGTM!


13-46: LGTM!


48-79: LGTM!


81-102: LGTM!


104-132: LGTM!


134-148: LGTM!


150-233: LGTM!


235-324: LGTM!


326-404: LGTM!


406-428: LGTM!


430-508: LGTM!

Comment thread viewer/server.py Outdated
Comment thread viewer/server.py Outdated
Comment thread viewer/server.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant