fuzzyworld

when-am-i / commit

docs: document new calendars, anchor events, colors, and authoring skill

483f1c579ca00b75266d773df4bed40afb7c2014 · Pavel Safronov · 2026-06-20 18:31:56 -0700 · parents 4a2e1dbe4ac6

Claude Code usage: this commit ≈0 out / ≈0 cached · cost unknown (no per-model trailer) · session 3 tally 29.0K in / 269.0K out / 22.9M cached · usage by commit and session

docs: document new calendars, anchor events, colors, and authoring skill

- README: supported-calendars list (convertible vs descriptive-only), the new
  calendars, anchorEvents/"winters since", and the new-calendar skill; badge
  count now 10; SCHEMA.json reference corrected to SCHEMA.md + validator.
- plan.md: mark the three stretch goals shipped (anchor events, scalable colors,
  authoring skill) and record the five new regions + the broken-Temporal-calendar
  notes (chinese/ethiopic/coptic).

Usage: 29.0K in / 269.0K out / 22.9M cached (session tally)
rtk:   110.8K saved, 80.9% over 243 cmds (global/lifetime)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
 README.md | 40 ++++++++++++++++++++++++++++++----
 plan.md   | 73 +++++++++++++++++++++++++++++++++++++++++++++++----------------
 2 files changed, 91 insertions(+), 22 deletions(-)

diff --git a/README.md b/README.md
index 4149632..79e28de 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 [![pipeline status](https://gitlab.com/PavelSafronov/when-am-i/badges/main/pipeline.svg)](https://gitlab.com/PavelSafronov/when-am-i/-/pipelines)
 [![live demo](https://img.shields.io/badge/demo-live-success)](https://pavelsafronov.gitlab.io/when-am-i)
 ![version](https://img.shields.io/badge/version-v0.1.0-blue)
-![calendar data](https://img.shields.io/badge/calendar%20data-5-orange)
+![calendar data](https://img.shields.io/badge/calendar%20data-10-orange)
 ![license](https://img.shields.io/badge/license-MIT-green)
 
 ![React](https://img.shields.io/badge/React-20232A?logo=react&logoColor=61DAFB)
@@ -23,14 +23,33 @@ Imagine you're a time traveler who ends up in a random place and time in history
 - Interactive world map for selecting a location
 - Date picker for choosing any year in history
 - **Shuffle** button that drops you at a random place and date on a non-default
-  (non-Gregorian) calendar — Islamic, Persian, Julian, or a lunar reckoning
+  (non-Gregorian) calendar — e.g. Chinese, Japanese, Saka, Buddhist, Ethiopian,
+  Islamic, Persian, Julian, or a lunar reckoning
 - Displays the local calendar year and system for the chosen place and time
 - Shows **all** valid answers at once — different people at the same place and time
   reckoned dates differently (e.g. colonial Julian alongside an Indigenous lunar calendar)
+- For calendars with no numbered year, gives the kind of answer a local would —
+  the moon and night ("a full moon, about night 15"), and, where the data
+  records a documented event, **"about N winters since &lt;event&gt;"**
 - A global **modern Gregorian default** for dates from 1927 on, so most recent dates
   resolve anywhere — shown alongside any local calendar (e.g. modern Iran → Persian *and* Gregorian)
 - Educational and fun way to explore world calendars and history
 
+### Supported calendars
+
+The engine returns every valid answer for a place and date. A calendar is
+**convertible** when a deterministic rule yields a numbered local year, or
+**descriptive-only** when it has no continuous numbered year (we still place you
+in time — the moon/season, the named year, and "winters since" a recorded event —
+and show the Gregorian year for reference).
+
+- **Convertible:** Gregorian, Julian, Islamic (Umm al-Qura), Hebrew,
+  Persian (Solar Hijri), Indian (Saka), Buddhist, Japanese (nengō, Meiji onward),
+  Ethiopian.
+- **Descriptive-only:** the Chinese sexagenary cycle (the named 60-year
+  stem-branch / zodiac-animal year), the Japanese pre-1873 lunisolar calendar,
+  and observational lunar calendars such as the Algonquian (Massachusett) reckoning.
+
 ## Tech Stack
 
 - React (SPA)
@@ -170,15 +189,28 @@ overlapping regions are expected, not an error.
 The colonial Julian and Gregorian eras live in `colonial-america.json`, whose
 larger polygon overlaps this one — so a Boston click resolves against both files.
 
+An era for a calendar with no numbered year may also carry **`anchorEvents`** — a
+list of documented, dated, **sourced** events a local reckons time from. The app
+reports the most recent one at or before the date as "about N winters since
+&lt;event&gt;" (e.g. the Massachusett reckoning counts winters since the
+1616–1619 epidemic). Anchor events must be real and cited — the engine only
+counts the winters, it never invents the event.
+
 ### Schema
 
-See `src/data/calendars/SCHEMA.json` for the schema and field documentation.
+See `src/data/calendars/SCHEMA.md` (human-readable field docs) and
+`scripts/validate-data.mts` (the authoritative, machine-checkable schema run by
+`validate:data`).
 
 ### How to contribute
 
 - Add or edit files in `src/data/calendars/` via pull request.
 - Use [geojson.io](https://geojson.io/) or similar tools to draw and export region polygons.
-- Include sources/references for each era.
+- Include sources/references for each era (and for every anchor event).
+- Working in Claude Code? The repo ships a **`new-calendar`** skill
+  (`.claude/skills/new-calendar/`) that walks the whole flow — researching a
+  place/people's calendar with citations, drafting the polygon and eras, picking
+  the right converter (or descriptive-only), then validating and screenshotting.
 - Run `npm run screenshot:calendar -- src/data/calendars/<your-file>.json` and
   attach the images to your PR, so the region, eras, and conversions can be
   reviewed visually.
diff --git a/plan.md b/plan.md
index 56107a9..d753f39 100644
--- a/plan.md
+++ b/plan.md
@@ -214,14 +214,34 @@ A `screenshot:calendar -- <file.json>` script that renders **one calendar's** an
    `@js-temporal/polyfill@0.5.1` (throws on every date — "Unexpected leap month
    suffix"; no fixed version available), so **Persian/Iran** was used instead.
 
+   **The five most-populous calendars added 2026-06-20** (covering the most
+   humans × time, per Pavel's request): [china.json](src/data/calendars/china.json)
+   (Chinese sexagenary cycle — descriptive-only, hand-rolled stem-branch/zodiac
+   naming, since the Temporal lunisolar `chinese` is broken),
+   [india.json](src/data/calendars/india.json) (Saka, existing Temporal converter),
+   [japan.json](src/data/calendars/japan.json) (a pre-1873 lunisolar
+   descriptive era + the modern Japanese **nengō** era, Temporal `japanese` using
+   `era`/`eraYear`, scoped to Meiji+),
+   [thailand.json](src/data/calendars/thailand.json) (Buddhist Era, existing
+   converter), and [ethiopia.json](src/data/calendars/ethiopia.json) (Ethiopian
+   calendar, **hand-rolled via JDN** because the Temporal `ethiopic` *and*
+   `coptic` calendars also throw in this polyfill — same class of bug as
+   `chinese`). New converters live in
+   [src/utils/calendars.ts](src/utils/calendars.ts) with tests.
+
 ---
 
 ## Stretch goals (post-MVP)
 
 ### AI-assisted dataset authoring (requested 2026-06-20)
-Add Claude Code **skill(s)** under `.claude/skills/` that help an author create a new
-calendar region file end-to-end, lowering the bar to contribute data. Sketch of what a
-"new calendar dataset" skill would do:
+**✅ Shipped 2026-06-20** as the `new-calendar` skill
+([.claude/skills/new-calendar/SKILL.md](.claude/skills/new-calendar/SKILL.md)):
+it walks an author through researching a place/people's calendar(s) with sourced
+citations, drafting the GeoJSON polygon and eras, picking the right converter (or
+descriptive-only), and running `validate:data` / `gen:badges` /
+`screenshot:calendar` so the result is PR-ready — with the project's "never
+invent, always source" rule front and centre. A review/QA skill remains a
+possible follow-up. Original sketch of what the skill does:
 - Take a place + people/polity as input (e.g. "the Mughal Empire", "Coast Salish around
   Puget Sound") and **research** the calendar system(s), era boundaries, and *whose*
   reckoning it is (`usedBy`), gathering **citable sources** (the project's acceptance gate
@@ -254,13 +274,16 @@ authoring/build-time tooling being considered:
   ("draw your own region"). Keep authoring output reproducible either way.
 
 ### Scalable calendar colors (requested 2026-06-20)
-Today [src/utils/calendarColor.ts](src/utils/calendarColor.ts) already computes colors
-**on the fly** — colors are *not* stored in the data files. A small curated `PALETTE`
-covers the common calendars for recognizability; anything else falls back to a
-deterministic `hsl(hashHue(name), 60%, 75%)`, so it already scales to many calendars.
-The weakness at scale is **distinctness**, not hard-coding: `hashHue` can map two names
-to near-identical hues, and the fixed saturation/lightness can clash or wash out.
-Future work — generate visually separable colors:
+**✅ Shipped 2026-06-20** in [src/utils/calendarColor.ts](src/utils/calendarColor.ts).
+The new `calendarColorScale(names)` assigns colors to the *set* of calendars on
+screen at once: curated calendars keep their `PALETTE` color, the rest are walked
+in a stable (sorted) order and handed **golden-angle-spaced** hues so they fan out
+around the wheel instead of colliding on a raw name hash. [App.tsx](src/App.tsx)
+builds one scale from every calendar in the dataset, so a calendar keeps the same
+color across the map, the timeline, and as you scrub dates (legend ↔ map ↔ timeline
+agree). `calendarColor(name)` is retained for a lone name / fallback (now also
+golden-angle-based). Possible later polish: contrast/accessibility tuning so no
+generated color vanishes on the map tiles. Original framing of the problem:
 - Space hues apart (e.g. golden-angle increments) instead of a raw name hash, and/or
   assign colors **per render** so the calendars actually on screen are maximally distinct.
 - Keep determinism where it matters (legend ↔ map agreement within a view) and keep the
@@ -283,15 +306,29 @@ This is authentic *in structure*. Its pieces split sharply by cost:
     `src/utils/calendars.ts` computes the night-of-the-moon + phase from the Julian Day
     Number; the "Algonquian lunar calendar" answer now leads with it (still
     `convertible: false`, no year) instead of the bare "no year count" note.
-  - **Eclipse-anchored "winters since"** — if the reference event is an eclipse, the date
-    is computable.
-- **Needs curated, sourced per-region data (the expensive part — hence stretch):**
+  - **Eclipse-anchored "winters since"** — superseded by the per-region
+    `anchorEvents` approach below (eclipses turned out to need either a heavy
+    ephemeris or the same curated data, so they're just one *kind* of anchor).
+- **"Winters since" via per-region anchor events — ✅ Shipped 2026-06-20.** Pavel's
+  call (the most reliable option): each anchor must be a **documented, sourced**
+  event supplied in the data; the engine only counts the winters, never invents
+  one. Implemented as an optional `Era.anchorEvents` (`{ name, date, sources }[]`),
+  with `wintersSince()` in [src/utils/calendars.ts](src/utils/calendars.ts)
+  picking the most recent anchor at/before the date and reporting "about N winters
+  since &lt;event&gt;" (counted as completed calendar years so anniversaries aren't
+  off-by-one). Wired through [resolve.ts](src/utils/resolve.ts) →
+  `CalendarConversion.reckoning` → a sourced line in
+  [ResultPanel.tsx](src/components/ResultPanel.tsx); validated in
+  `validate-data.mts` and the data loader; documented in SCHEMA.md/README.
+  [massachusett.json](src/data/calendars/massachusett.json) carries the first
+  anchor (the 1616–1619 epidemic → "about 84 winters since" in 1700).
+- **Still needs curated, sourced per-region data (the expensive part — hence stretch):**
   - **Moon names** — vary by nation, inconsistently attested; must be cited, not invented.
-  - **Historical-event anchors for "winters since"** — must be real, dateable, locally
-    remembered events (e.g. the **1616–1619 epidemic**), **not** invented ones. The
-    "asteroid crash" in the brainstorm is illustrative only — no such event occurred.
-- **Schema (when we do this):** add optional `reckoning` text and an optional `moons`
-  list / `anchorEvents` to descriptive-only calendars. Optional + back-compatible.
+  - More **historical-event anchors** for other descriptive calendars — must be real,
+    dateable, locally remembered events, **not** invented ones (the "asteroid crash" in
+    the brainstorm was illustrative only — no such event occurred).
+- **Schema:** the `anchorEvents` part shipped (above); optional `reckoning` text and a
+  `moons` list remain optional + back-compatible future additions.
 
 ### Data accuracy
 - Refine Boston's generic `"Algonquian lunar calendar"` to the specific **Massachusett**