Skip to content

feat(time): add campaign calendar display projection - #187

Merged
100yenadmin merged 1 commit into
mainfrom
openworlds/calendar-display
May 26, 2026
Merged

feat(time): add campaign calendar display projection#187
100yenadmin merged 1 commit into
mainfrom
openworlds/calendar-display

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 26, 2026

Copy link
Copy Markdown
Member

Summary

Adds a clean-room campaign calendar display layer for OpenWorlds Table and Atlas surfaces.

This is intentionally display-only:

  • Campaign.day remains the canonical in-world day counter.
  • Campaign.time_of_day remains the tactical phase.
  • Optional Campaign.calendar metadata can describe months, weekdays, seasons, and moon cycles.
  • Viewer/OpenWorlds projects a browser-safe calendar read model and keeps /move as the only player-intent write lane.

Refs #176.

What Changed

  • Added strict but optional engine calendar metadata models:
    • CampaignCalendar
    • CalendarMonth
    • CalendarMoon
  • Added servers/engine/campaign_calendar.py, a pure projection helper that maps Campaign.day plus time_of_day into a human date label, season, and moon phase.
  • seed_world() now accepts optional world.calendar metadata, but ignores malformed or empty calendar blocks so a decorative calendar can never block world startup.
  • /session-surface and /atlas-surface now include:
    • dayLabel
    • calendar.available
    • calendar.canonical_day
    • date, season, and first moon phase metadata
  • OpenWorlds Table and Atlas render calendar details when available and retain legacy Day N behavior otherwise.

Architecture Notes

This PR deliberately does not add calendar controls, time mutation APIs, event scheduling, real-time ticking, or browser-side state writes.

The date label is a projection, not authority. Consequences, travel, rests, strategic ticks, and campaign persistence continue to key off the engine-owned day counter. The viewer duplicates a small JSON-dict projection because it is a stdlib downstream reader and must not import the engine package at runtime.

External Research

Fantasy Calendar was evaluated as reference-only. Its useful concepts are static calendar metadata, date rendering, and deterministic moon phases. This PR does not vendor or port Fantasy Calendar code and does not add a runtime dependency.

Review Findings Squashed

  • Fixed optional world.calendar validation so malformed calendar metadata degrades instead of aborting seed_world().
  • Fixed engine projection fallback for schema-valid but unusable empty calendars so it agrees with viewer legacy Day N behavior.
  • Guarded the OpenWorlds Table calendar detail line with width and ellipsis so long season/moon names do not collide with action buttons.

Validation

Run from /Volumes/LEXAR/repos/ClawDnD-calendar-display:

uv run --directory servers/engine --group dev pytest -q tests/test_campaign_calendar.py tests/test_content.py::test_seed_world_seeds_strategic_state_additively tests/test_store.py
python3 -m unittest viewer.tests.test_session_surface viewer.tests.test_atlas_surface viewer.tests.test_openworlds_static -q
python3 -m py_compile viewer/server.py scripts/license_check.py
uv run --directory servers/engine python -m py_compile campaign_calendar.py content.py models.py
python3 scripts/license_check.py
git diff --check

Additional JSX smoke:

node - <<'NODE'
const fs = require('fs');
const vm = require('vm');
const babelPath = 'viewer/openworlds/vendor/babel-standalone-7.29.0.min.js';
const ctx = { window: {}, self: {}, console };
ctx.window = ctx;
ctx.self = ctx;
vm.createContext(ctx);
vm.runInContext(fs.readFileSync(babelPath, 'utf8'), ctx, { filename: babelPath });
const Babel = ctx.Babel;
for (const file of ['viewer/openworlds/screen-table.jsx', 'viewer/openworlds/screen-map.jsx']) {
  Babel.transform(fs.readFileSync(file, 'utf8'), { presets: ['react'] });
  console.log(`transformed ${file}`);
}
NODE

Rollback Plan

Revert this PR to remove the optional calendar metadata and viewer projection. Existing saves without calendar are unaffected because all fields are additive and the old Day N fallback remains.

Summary by CodeRabbit

  • New Features

    • Added campaign calendar system supporting custom date epochs, months with seasons, and moon phase tracking.
    • Calendar projections now display on both strategic and tactical views with computed weekdays, seasons, and moon phases.
    • Moon cycles calculate deterministic phase information based on elapsed days.
  • Tests

    • Added comprehensive calendar projection, seeding, and UI rendering tests.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 04284709-2538-43bd-b3c2-d3fe3b3a88b5

📥 Commits

Reviewing files that changed from the base of the PR and between 06a7ea6 and b032432.

📒 Files selected for processing (10)
  • servers/engine/campaign_calendar.py
  • servers/engine/content.py
  • servers/engine/models.py
  • servers/engine/tests/test_campaign_calendar.py
  • viewer/openworlds/screen-map.jsx
  • viewer/openworlds/screen-table.jsx
  • viewer/server.py
  • viewer/tests/test_atlas_surface.py
  • viewer/tests/test_openworlds_static.py
  • viewer/tests/test_session_surface.py

Cache: Disabled due to Reviews > Disable Cache setting


📝 Walkthrough

Walkthrough

Adds a display-only campaign calendar: Pydantic calendar models, engine projection (date and moon phases), optional seeding of calendar metadata, viewer-side projection into session/atlas surfaces, and UI rendering of a compact calendar detail (season + moon phase).

Changes

Campaign Calendar Projection and Display

Layer / File(s) Summary
Calendar Data Models and Campaign Integration
servers/engine/models.py
Three new Pydantic models (CalendarMonth, CalendarMoon, CampaignCalendar) and an optional calendar field on Campaign (None by default).
Engine Calendar Projection Logic
servers/engine/campaign_calendar.py, servers/engine/tests/test_campaign_calendar.py
Implements month cursor, moon phase selection, and project_calendar_date() producing year/month/day-of-month/weekday/season/era, formatted labels, and per-moon age/phase; tests cover valid and fallback behaviors.
World Seeding Calendar Integration
servers/engine/content.py, servers/engine/tests/test_campaign_calendar.py
seed_world() accepts optional world["calendar"], validates via CampaignCalendar.model_validate(), assigns only if months present, and degrades to None on errors; tests verify preservation and safe ignore on malformed input.
Viewer Server Calendar Projection
viewer/server.py, viewer/tests/test_atlas_surface.py, viewer/tests/test_session_surface.py
Adds _positive_int, _openworlds_calendar_projection, legacy label helper, and _openworlds_calendar wrapper; session and atlas surfaces include a top-level calendar projection. Tests check projected fields and that state authority/write capabilities remain engine-only.
Frontend Calendar Display Rendering
viewer/openworlds/screen-map.jsx, viewer/openworlds/screen-table.jsx, viewer/tests/test_openworlds_static.py
Derives calendarDetail (season + first moon name:phase) from surface.calendar when calendar.available is true and renders it in header and scene caption; static-route tests assert presence of calendar bindings/identifiers.

Sequence Diagram(s)

sequenceDiagram
  participant Engine as Engine (campaign_calendar.py)
  participant Viewer as Viewer Server (viewer/server.py)
  participant Frontend as Frontend (screen-map/table.jsx)

  Engine->>Viewer: Snapshot includes Campaign.calendar (seeded)
  Viewer->>Viewer: _openworlds_calendar_projection(snapshot) → compute date, moons
  Viewer->>Frontend: session/atlas surface with calendar field
  Frontend->>Frontend: derive calendarDetail (season + moon: phase)
  Frontend->>Frontend: render calendar label in header/caption
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • 100yenadmin/ClawDnD#130: Related viewer atlas/surface plumbing; this PR extends that surface payload with calendar projection and UI hooks.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@100yenadmin 100yenadmin added enhancement New feature or request tier-1 Tier 1: Claude Code plugin labels May 26, 2026
@100yenadmin
100yenadmin marked this pull request as draft May 26, 2026 16:04
@100yenadmin
100yenadmin marked this pull request as ready for review May 26, 2026 19:01
@100yenadmin
100yenadmin force-pushed the openworlds/calendar-display branch from 0200599 to b032432 Compare May 26, 2026 19:01
@100yenadmin
100yenadmin merged commit 8b6219f into main May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request tier-1 Tier 1: Claude Code plugin

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant