Adds calendar/octaves.ts: a lookback over the past week collecting every
octave (sanctoral or temporal) still active on a date, stacking multiple
at once (Christmas + St. Stephen + St. John + Holy Innocents all
commemorated together within the Christmas Octave). Declared via an
optional `octave` field on a saint's own record or a new small
TemporalFeastRecord (calendar/temporal-feasts.ts) for temporal ids like
Christmas/Pentecost that didn't have a metadata record before -- data-
driven per user design discussion, with `{ enabled: true }` alone using
sensible defaults (8 days, semiduplex threshold) so a minimal declaration
works without authored content.
Wired into resolveDay as a post-processing layer: doesn't change how a
single day's own precedence contest is decided, just adds commemorations
for active octaves and occasionally overrides the winner when the
occurring saint doesn't clear the strictest active octave's threshold.
Populated so far: St. Lawrence's own octave (the one that repeatedly cost
real saints their spot in August), the three Comites Christi octaves
(Stephen/John/Innocents -- Thomas of Canterbury deliberately excluded,
per discussion), and Pentecost's (duplex threshold, user-specified).
Pentecost's octave offsets are also removed from temporal-categories.yml's
privileged-feria-major classification, letting a real candidate reach the
new octave layer instead of being transferred away first -- with the
side effect that Pentecost's own Ember Saturday no longer forces a
transfer (a sub-threshold saint is now commemorated in place instead, see
tests/calendar/transfer.test.ts's updated case). Assumption, Nativity
BVM, Immaculate Conception, and All Saints' own octaves are not yet
populated with octave data -- deliberately deferred to a follow-up pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9 collects, 3 antiphons -- including backfilling the pre-existing All
Saints entry. Also updates two pre-existing tests whose shared date
(2025-11-30, Advent I Sunday) now correctly shows St. Andrew commemorated
alongside it, confirmed against the live reference engine.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Found via real data (the May sanctoral pull): a transfer landing on a day
whose own native winner was temporal blindly took over as winner,
regardless of that day's own precedence category — so a transferred-in
Simplex saint (St. Felix I, impeded by Pentecost's Ember Saturday) was
overwriting Trinity Sunday itself instead of being merely commemorated,
the way a native Simplex candidate on an ordinary Sunday already
correctly is.
Fixes applyIncomingTransfer to run the arriving candidate through
decideOccurrence using the receiving day's own temporalCategory, the same
rules a native occurrence there would use, instead of assuming an empty
temporal slot means automatic victory.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrects and completes the occurrence rules, based on a design discussion
plus one concrete data point: St. Anthony Abbot (plain Duplex) was found
outright winning against an ordinary Sunday in the real Monastic 1617
engine, which the old duplex-1-classis-only threshold got wrong.
- FeastClass gains `vigil`, inserted between `simplex` and `semiduplex` —
one ordering that correctly serves both "does this win against a Sunday"
(vigil behaves like simplex there) and "which of two saints wins a
landing-day collision" (vigil beats simplex, loses to semiduplex).
- LiturgicalDay.occurring (a flat OccurringFeast[] that could only ever
express a losing *sanctoral* candidate) is replaced by `winner:
DayWinner` + `commemorations: Commemoration[]` — a discriminated list
that can hold the temporal day itself, one or more sanctoral entries, or
(not built yet, but the shape already accommodates it) a future octave
kind.
- commemorations.ts: ordinary Sundays let Duplex+ win outright (Sunday
commemorated in return), Semiduplex/Vigil transfer elsewhere (too
substantial a feast to cheapen with a bare commemoration), Simplex stays
and is commemorated. Privileged Sundays never displace; Duplex-majus+
commemorated, everything else transfers.
- collision.ts (new): resolves two sanctoral candidates wanting the same
day (a transfer landing on an already-occupied day, or two native
saints sharing a date) — duplex > semiduplex > vigil > simplex, loser
always commemorated, ties favor the native occupant.
- temporal-id.ts (new): maps any date to one of the 52 real Sunday-collect
ids from the previous commit, so a temporal winner/commemoration can
actually be looked up, not just labeled "temporal" in the abstract.
- index.ts's resolveDay orchestrates all of it, including the actual
Monday/Saturday transfer mechanism. Landing on a privileged feria (the
concrete case: Holy Week, right after Palm Sunday) is explicitly
deferred rather than guessed at — it needs its own Easter-keyed lookup
table, the same way the reference engine handles it.
Added the Vigil of St. Lawrence (Aug 9) as real content specifically to
exercise the backward-transfer rule end-to-end: Aug 9, 2026 is a Sunday,
so the vigil transfers cleanly back to Saturday, verified by a new
integration test alongside the unit-level rule and collision tests.
Adds a real label under each hour's heading — "Monday in the 10th week
after Trinity", "The 1st Sunday of Advent" — computed from whichever
LiturgicalDay the hour actually resolved against, which can differ from
the nav date for Compline's evening anticipation.
calendar/day-label.ts combines two things: an ordinal week-within-season
label (pure date arithmetic on the season anchors from calendar/temporal.ts
and calendar/easter.ts — Trinity-counted, not Divinum Officium's own
Pentecost-counted convention; meant to become configurable later via the
same day->id indirection already used for the sanctoral calendar, not
hardcoded forever), and a feast name from calendar/commemorations.ts's
occurrence decision — shown alone if the feast displaces the day outright,
prefixed onto the temporal label if merely commemorated, or omitted
entirely if nothing's occurring.