Closes the tabled "Easter's own octave" mechanism gap. resolveDay only ever checked yesterday/tomorrow for an inbound transfer - a single hop - so anything impeded in Holy Week just vanished rather than reappearing after the Octave. Live-verified the real behavior first (2033 proof year): a candidate skips the whole privileged span and keeps walking forward, FIFO by native date, until it lands; a transferred-in candidate never gets the "still gets a nod" commemoration a native occurrence of the same rank would along the way (confirmed with the Annunciation, zero commemoration anywhere in the span). Also fixed Low Sunday itself, which needed the same privileged-sunday treatment as Easter Day. Built resolveForwardTransferLanding: a bounded queue simulation that reuses applyIncomingTransfer unchanged per hop - its existing privileged-feria-major no-op was already the right single-hop refusal, it just needed a real cascade around it instead of a dead end. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
This commit is contained in:
@@ -19,7 +19,12 @@ across every hour and content type in this app.
|
||||
|
||||
1. **Mechanism gaps** — a real design/code piece not built yet, blocking any content pass on
|
||||
it from starting:
|
||||
- Easter's own octave — tabled, "a whole different discussion."
|
||||
- ~~Easter's own octave~~ — DONE (2026-09-01): the transfer-cascade piece (a candidate
|
||||
impeded within Holy Week/Easter Day/the Octave/Low Sunday now correctly skips the whole
|
||||
span and keeps walking forward, FIFO, until it lands) is built and live-verified — see
|
||||
the dated log's "Easter octave transfer cascade" entry. Content-wise, Easter Day/the
|
||||
Octave's own full Matins proper (distinct psalms, not just antiphons) is still not
|
||||
modeled — a separate, narrower remaining gap, not this one.
|
||||
- Date picker: `day-nav.ts` currently only steps a day at a time (prev/next); there's no
|
||||
way to jump to an arbitrary date without walking there one day at a time or editing the
|
||||
URL. Needs a calendar-grid picker UI — open, not started.
|
||||
@@ -4705,3 +4710,60 @@ exercised correctly across hundreds of ordinary weekdays in the existing test su
|
||||
|
||||
`npm test` (820 passed — one net fewer than before since two tests merged into one clearer
|
||||
case), `tsc --noEmit`, and `npm run build` all pass.
|
||||
|
||||
### Easter octave transfer cascade — mechanism built (2026-09-01)
|
||||
|
||||
Closes the "Easter's own octave" item from this file's own Priority list. User asked for it
|
||||
directly, then answered two design questions with real evidence rather than guesswork:
|
||||
"what happens to the saint's own day" and "what are the gating rules" for something impeded
|
||||
within Holy Week/the Easter Octave.
|
||||
|
||||
**Root cause, found by reading the existing code rather than assuming**: `resolveDay` only ever
|
||||
checked yesterday/tomorrow for an inbound transfer — a single hop. There was already an honest,
|
||||
existing comment marking this exact gap (`applyIncomingTransfer`'s `privileged-feria-major`
|
||||
branch: "a transfer chain — not modeled... simply not delivered here rather than guessed at").
|
||||
Anything impeded in Holy Week just vanished rather than reappearing.
|
||||
|
||||
**Live-verified real behavior (2033: Palm Sunday Apr 10 → Low Sunday Apr 24, five real saints
|
||||
falling inside that window) before building anything**:
|
||||
- A candidate impeded anywhere in the whole span (Holy Week, Easter Day, the Octave, Low
|
||||
Sunday) skips the *entire* span — not just the day it was impeded on — and keeps walking
|
||||
forward day by day past it, same as any ordinary transfer, until it finds a day that isn't
|
||||
already spoken for by a real native occurrence or an earlier-queued candidate ahead of it in
|
||||
line. St. Leo I (native Apr 11, Duplex) landed Apr 27; Ss. Soter & Caius (native Apr 22)
|
||||
landed Apr 28 (right after Leo); St. George (native Apr 23) had to wait until May 4, well
|
||||
past several already-occupied days.
|
||||
- **Multiple candidates can be pending at once** — a real FIFO queue ordered by native date,
|
||||
not just "the one most recent candidate." All three above overlapped in their pending window.
|
||||
- **A transferred-in candidate never gets the "duplex-majus+ still gets a nod" commemoration**
|
||||
a *native* occurrence of that rank would on one of these days — confirmed with the
|
||||
Annunciation (Duplex I. classis, native Easter Sunday itself in 2035): zero commemoration
|
||||
anywhere across the whole span, landing outright on Apr 4 (past Low Sunday, Apr 1 that year).
|
||||
This is genuinely different from a native duplex-majus+ candidate (e.g. St. Mark, native
|
||||
within some other year's Octave, which *does* get commemorated in place) — the "never
|
||||
displaced, but gets a nod" treatment is reserved for a day's own native candidate, not
|
||||
anything merely passing through.
|
||||
- **Low Sunday itself needed the same fix as Easter Day** — found while building this: it
|
||||
carries the same formal "Duplex I. classis" rank as Easter Day in the source, but was
|
||||
falling through to eastertide's plain `ordinary-sunday` default (today's earlier fix only
|
||||
covered offset 0). Not independently collision-verified (no Monastic-track saint — a real
|
||||
`SanctiM/MM-DD.txt` file — ever lands there across the real range of Easter dates), but
|
||||
corroborated by its own rank label and by the complete absence of even a `Transfer:` note for
|
||||
a same-dated non-Monastic-track saint (St. Isidore of Seville 2027, St. Vincent Ferrer 2043)
|
||||
that a real Monastic day would show. Fixed in `calendar/temporal.ts`'s `resolveTemporalCategory`
|
||||
alongside offset 0.
|
||||
|
||||
**What got built**: a real `MAX_FORWARD_TRANSFER_LOOKBACK_DAYS`-bounded (60 days, generous
|
||||
headroom over the worst observed real case) queue simulation, `resolveForwardTransferLanding`
|
||||
in `calendar/index.ts` — walks chronologically from the lookback window up through the target
|
||||
date, replaying each day's own native occurrence and, if a candidate is already queued, one
|
||||
landing attempt per day (reusing `applyIncomingTransfer` completely unchanged — its existing
|
||||
`privileged-feria-major` no-op turned out to already be exactly the right single-hop refusal;
|
||||
the fix was purely wrapping it in an actual cascade instead of a dead end). Only forward-
|
||||
direction transfers chain this way; a Vigil's backward transfer stays single-hop as before —
|
||||
structurally different (displaces backward in time) and not observed to need it.
|
||||
|
||||
New tests in `tests/calendar/transfer.test.ts` cover the full chain (Leo/Hermenegild/Tiburtius
|
||||
landing in strict native-date order past the whole 2033 span) and the Annunciation's own
|
||||
zero-commemoration case (2035). `npm test` (823 passed), `tsc --noEmit`, and `npm run build`
|
||||
all pass.
|
||||
|
||||
+99
-19
@@ -13,10 +13,13 @@ import { applyEmberDay } from './ember-days';
|
||||
|
||||
/**
|
||||
* A day's occurrence considered on its own — no awareness of what an
|
||||
* adjacent day might be trying to transfer in. `resolveDay` calls this for
|
||||
* the date itself *and* for yesterday/tomorrow (to check for an inbound
|
||||
* transfer) without ever recursing into their own adjacent-day checks —
|
||||
* that's what keeps this from being mutually recursive.
|
||||
* adjacent day might be trying to transfer in. `resolveDay` calls this
|
||||
* for the date itself, for tomorrow (a backward-direction Vigil transfer,
|
||||
* single-hop), and — via `resolveForwardTransferLanding` — for up to
|
||||
* `MAX_FORWARD_TRANSFER_LOOKBACK_DAYS` days *before* the date (a forward
|
||||
* transfer can chain through more than one impeded day) — without ever
|
||||
* recursing into any of those days' own adjacent-day checks in turn —
|
||||
* that's what keeps this from being mutually/infinitely recursive.
|
||||
*/
|
||||
function resolveNativeOccurrence(isoDate: string): { temporalCategory: TemporalCategory; result: OccurrenceResult } {
|
||||
const weekday = weekdayOf(isoDate);
|
||||
@@ -59,13 +62,22 @@ function applyIncomingTransfer(
|
||||
fromDate: string,
|
||||
): DayWinner {
|
||||
if (temporalCategory === 'privileged-feria-major') {
|
||||
// Deferred: a transfer landing on one of these (the concrete case is
|
||||
// Holy Week, right after Palm Sunday) needs its own Easter-date-keyed
|
||||
// lookup table, the same way the reference engine handles it — not
|
||||
// modeled yet. The transfer is simply not delivered here rather than
|
||||
// guessed at. The lesser privileged-feria tier doesn't need this —
|
||||
// ordinary collision/transfer logic is fine there, same as any
|
||||
// ordinary-sunday landing.
|
||||
// Never a landing spot, no matter the candidate's rank or the day's
|
||||
// own winner — unlike every other category, a transferred-in
|
||||
// candidate doesn't even get the bare "duplex-majus+ still gets a
|
||||
// nod" commemoration a *native* occurrence of that rank would (live-
|
||||
// verified 2026-09-01: the Annunciation, Duplex I. classis, passes
|
||||
// through the entire Easter Octave with zero commemoration anywhere
|
||||
// in it, unlike a same-ranked native occurrence there — e.g. St.
|
||||
// Mark, Duplex II. classis, native within the Octave in some years,
|
||||
// which *does* get commemorated). This single-hop refusal is the
|
||||
// building block `resolveForwardTransferLanding` below chains
|
||||
// through the whole privileged span (Holy Week + Easter Day + the
|
||||
// Octave + Low Sunday) to find where a candidate actually lands —
|
||||
// this function itself stays single-hop on purpose, same as the
|
||||
// ordinary categories below. The lesser privileged-feria tier
|
||||
// doesn't need this — ordinary collision/transfer logic is fine
|
||||
// there, same as any ordinary-sunday landing.
|
||||
return winner;
|
||||
}
|
||||
// Tags the returned winner with where it transferred from, but only when
|
||||
@@ -88,9 +100,9 @@ function applyIncomingTransfer(
|
||||
// not just take over because nothing else was assigned here.
|
||||
const decided = decideOccurrence(temporalCategory, winner.id, candidate);
|
||||
commemorations.push(...tagCommemorations(decided.commemorations));
|
||||
// If the candidate fails here too (decided.transfer set), that's a
|
||||
// transfer chain — not modeled, same "not delivered rather than
|
||||
// guessed at" stance as the privileged-feria-major case above.
|
||||
// If the candidate fails here too (decided.transfer set), the caller
|
||||
// (resolveForwardTransferLanding, for a forward-direction candidate)
|
||||
// keeps it queued and tries the next day — that's the real chain.
|
||||
return tagIfLanded(decided.winner);
|
||||
}
|
||||
const collision = resolveCollision(candidate, winner);
|
||||
@@ -98,6 +110,77 @@ function applyIncomingTransfer(
|
||||
return tagIfLanded(collision.winner);
|
||||
}
|
||||
|
||||
// How far forward a still-unlanded candidate is allowed to keep looking
|
||||
// for a day — generous headroom over the worst real case found so far
|
||||
// (St. George, 2033: native Apr 23, impeded through the rest of Holy
|
||||
// Week + Easter Day + the Octave + Low Sunday, then further blocked by
|
||||
// 5 more already-occupied days in a row, landing May 4 — an 11-day
|
||||
// chain). Not expected to ever bind; exists so a pathological/malformed
|
||||
// sanctoral-calendar entry fails a candidate quietly (never lands, same
|
||||
// "not delivered rather than guessed at" stance as everywhere else in
|
||||
// this file) instead of the lookup silently walking forever.
|
||||
const MAX_FORWARD_TRANSFER_LOOKBACK_DAYS = 60;
|
||||
|
||||
/**
|
||||
* A forward-direction transfer (the vigil/backward case stays single-hop,
|
||||
* see `resolveDay` below) can chain through more than one impeded day in
|
||||
* a row — the concrete, and so far only observed, case is anything
|
||||
* impeded within Holy Week/Easter Day/the Easter Octave/Low Sunday (all
|
||||
* `privileged-feria-major` or `privileged-sunday`, none of which ever
|
||||
* accept a landing, per `applyIncomingTransfer`'s own single-hop refusal
|
||||
* above): live-verified 2033, three different candidates (St. Leo I,
|
||||
* Ss. Soter & Caius, St. George) all transfer, skip the entire span, and
|
||||
* queue up on the days immediately after it — in strict native-date
|
||||
* order, each waiting for the nearest day not already claimed by a real
|
||||
* native occurrence or an earlier-queued candidate ahead of it. That's a
|
||||
* real FIFO queue, not just "the one most recent candidate," since
|
||||
* multiple can be pending at once (all three above were, simultaneously,
|
||||
* that same fortnight).
|
||||
*
|
||||
* Simulates that queue's state chronologically from
|
||||
* `MAX_FORWARD_TRANSFER_LOOKBACK_DAYS` before `isoDate` up through
|
||||
* `isoDate` itself (exclusive of any day on/after `isoDate` — those
|
||||
* aren't resolved yet), then makes one landing attempt for `isoDate`
|
||||
* against whichever candidate is oldest in the resulting queue, if any.
|
||||
* Each day in the walk is judged only by its own `resolveNativeOccurrence`
|
||||
* (never a fully-`resolveDay`-resolved day, including for the historical
|
||||
* days being walked over) — consistent with `applyIncomingTransfer`
|
||||
* itself, which was already only ever given a freshly native-resolved
|
||||
* winner/commemorations, not a fully resolved one, even before this.
|
||||
*/
|
||||
function resolveForwardTransferLanding(
|
||||
isoDate: string,
|
||||
temporalCategory: TemporalCategory,
|
||||
winner: DayWinner,
|
||||
commemorations: Commemoration[],
|
||||
): DayWinner {
|
||||
const queue: { candidate: SanctoralIdentity; fromDate: string }[] = [];
|
||||
let cursor = addDays(isoDate, -MAX_FORWARD_TRANSFER_LOOKBACK_DAYS);
|
||||
while (cursor < isoDate) {
|
||||
const native = resolveNativeOccurrence(cursor);
|
||||
const oldest = queue[0];
|
||||
if (oldest) {
|
||||
const scratchCommemorations: Commemoration[] = [];
|
||||
const attempt = applyIncomingTransfer(oldest.candidate, native.temporalCategory, native.result.winner, scratchCommemorations, oldest.fromDate);
|
||||
const landed =
|
||||
(attempt.kind === 'sanctoral' && attempt.id === oldest.candidate.id) ||
|
||||
scratchCommemorations.some((c) => c.kind === 'sanctoral' && c.id === oldest.candidate.id);
|
||||
if (landed) {
|
||||
queue.shift();
|
||||
}
|
||||
}
|
||||
if (native.result.transfer?.direction === 'forward') {
|
||||
queue.push({ candidate: native.result.transfer.candidate, fromDate: cursor });
|
||||
}
|
||||
cursor = addDays(cursor, 1);
|
||||
}
|
||||
const oldest = queue[0];
|
||||
if (!oldest) {
|
||||
return winner;
|
||||
}
|
||||
return applyIncomingTransfer(oldest.candidate, temporalCategory, winner, commemorations, oldest.fromDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves everything about a given day *except* hour content — weekday,
|
||||
* season, temporal precedence category, and the day's real winner plus
|
||||
@@ -112,11 +195,8 @@ export function resolveDay(isoDate: string): LiturgicalDay {
|
||||
let winner = result.winner;
|
||||
const commemorations = [...result.commemorations];
|
||||
|
||||
const yesterdayIso = addDays(isoDate, -1);
|
||||
const yesterday = resolveNativeOccurrence(yesterdayIso);
|
||||
if (yesterday.result.transfer?.direction === 'forward') {
|
||||
winner = applyIncomingTransfer(yesterday.result.transfer.candidate, temporalCategory, winner, commemorations, yesterdayIso);
|
||||
}
|
||||
winner = resolveForwardTransferLanding(isoDate, temporalCategory, winner, commemorations);
|
||||
|
||||
const tomorrowIso = addDays(isoDate, 1);
|
||||
const tomorrow = resolveNativeOccurrence(tomorrowIso);
|
||||
if (tomorrow.result.transfer?.direction === 'backward') {
|
||||
|
||||
+20
-10
@@ -202,21 +202,31 @@ export function adventEmberDayOffset(isoDate: string): number | undefined {
|
||||
* the Christmas vigil (Dec 24 can land on a Sunday) — and a Sunday's own
|
||||
* privileged/ordinary status should win in that case regardless, so
|
||||
* Sundays are resolved straight from `bySeason` without consulting the
|
||||
* overrides at all. The one deliberate exception is Easter Sunday itself
|
||||
* (offset 0): `eastertide`'s own `bySeason` default is `ordinary-sunday`
|
||||
* (live-verified for the *ordinary* Sundays that follow it — see that
|
||||
* entry's own comment), but Easter Day needs the stronger
|
||||
* `privileged-sunday` tier instead (live-verified 2026-09-01: the
|
||||
* Annunciation, Duplex I. classis, transfers off Easter Day itself in
|
||||
* years they coincide, the same "transfers, no exception" behavior as
|
||||
* every other privileged-sunday case) — checked directly here rather
|
||||
* than via `offsets`, since offsets are never consulted for a Sunday.
|
||||
* overrides at all. The deliberate exceptions are Easter Sunday itself
|
||||
* (offset 0) and Low Sunday (offset 7, the Octave's own closing day):
|
||||
* `eastertide`'s own `bySeason` default is `ordinary-sunday` (live-
|
||||
* verified for the *ordinary* Sundays from the 2nd Sunday after Easter
|
||||
* on — see that entry's own comment), but both bookend Sundays of the
|
||||
* Easter Octave need the stronger `privileged-sunday` tier instead.
|
||||
* Easter Day: live-verified 2026-09-01, the Annunciation (Duplex I.
|
||||
* classis) transfers off it in years they coincide, the same "transfers,
|
||||
* no exception" behavior as every other privileged-sunday case. Low
|
||||
* Sunday: not independently collision-verified (no Monastic-track saint
|
||||
* — `SanctiM/MM-DD.txt` — ever falls there across the real range of
|
||||
* Easter dates, so no live test case exists), but its own formal rank in
|
||||
* the reference source ("Dominica in Albis in Octava Paschæ ~ Duplex I.
|
||||
* classis") matches Easter Day's own exactly, and no `Transfer:` note
|
||||
* ever shows there even for a same-named non-Monastic-track saint
|
||||
* (St. Isidore of Seville, 2027; St. Vincent Ferrer, 2043) that a real
|
||||
* Monastic-track day would display — treated the same as Easter Day on
|
||||
* that basis. Both checked directly here rather than via `offsets`,
|
||||
* since offsets are never consulted for a Sunday.
|
||||
*/
|
||||
export function resolveTemporalCategory(isoDate: string, season: Season, weekday: Weekday): TemporalCategory {
|
||||
const bySeasonEntry = temporalCategories.bySeason[season];
|
||||
const base = bySeasonEntry ? (weekday === 'sunday' ? bySeasonEntry.sunday : bySeasonEntry.feria) : 'ordinary-feria';
|
||||
if (weekday === 'sunday') {
|
||||
if (season === 'eastertide' && easterOffsetOf(isoDate) === 0) {
|
||||
if (season === 'eastertide' && [0, 7].includes(easterOffsetOf(isoDate))) {
|
||||
return 'privileged-sunday';
|
||||
}
|
||||
return base;
|
||||
|
||||
@@ -216,13 +216,15 @@ export interface LiturgicalDay {
|
||||
/** Set when this date's own native sanctoral candidate couldn't be kept
|
||||
* here at all (calendar/commemorations.ts's `decideOccurrence` `transfer`
|
||||
* signal) and moved to a later/earlier date instead. No landing date is
|
||||
* recorded here — `resolveDay` only ever checks the immediate adjacent
|
||||
* date, but a real landing can chain further than that (an unmodeled
|
||||
* case noted in `applyIncomingTransfer`, e.g. a transfer running into
|
||||
* Holy Week), so naming a specific "to" date risked asserting a wrong
|
||||
* one; this exists purely so the display can say "not an omission, this
|
||||
* office moved elsewhere" without claiming to know where. Distinct from
|
||||
* — and not implied by — `Commemoration`, since a transferred-away
|
||||
* recorded here — this exists purely so the display can say "not an
|
||||
* omission, this office moved elsewhere" without needing the caller to
|
||||
* separately go find where; the actual landing date is discoverable
|
||||
* from the landing day's own winner/commemoration, tagged with
|
||||
* `transferredFrom` (see calendar/index.ts's `resolveForwardTransferLanding`,
|
||||
* which does chain forward through more than one impeded day when
|
||||
* needed — e.g. anything impeded within Holy Week/the Easter Octave —
|
||||
* up to its own `MAX_FORWARD_TRANSFER_LOOKBACK_DAYS` bound). Distinct
|
||||
* from — and not implied by — `Commemoration`, since a transferred-away
|
||||
* candidate gets no commemoration on its own native date at all. */
|
||||
transferredAway?: { candidate: SanctoralIdentity };
|
||||
}
|
||||
|
||||
@@ -107,3 +107,96 @@ describe('transfer mechanism (resolveDay integration)', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// A forward transfer can chain through more than one impeded day in a
|
||||
// row — the whole Holy Week/Easter Day/Easter Octave/Low Sunday span
|
||||
// (2026-09-01: found real, live-verified precedence bugs in exactly this
|
||||
// area, then built the actual multi-hop mechanism per direct
|
||||
// instruction). `resolveForwardTransferLanding` in calendar/index.ts
|
||||
// walks up to `MAX_FORWARD_TRANSFER_LOOKBACK_DAYS` days back to find
|
||||
// where a still-unlanded candidate actually lands, in strict native-date
|
||||
// (FIFO) order against every other pending candidate.
|
||||
describe('multi-hop forward transfer through Holy Week/the Easter Octave', () => {
|
||||
it('2033: St. Leo I (native Easter Monday, Duplex) chains all the way past the whole privileged span to land on Easter+8 — verified against the real reference engine', () => {
|
||||
// Easter 2033 is Apr 17. Live-verified against the reference engine
|
||||
// (Monastic Tridentinum 1617, 2026-09-01): Leo transfers off Apr 11
|
||||
// (Monday of Holy Week) with no commemoration anywhere in Holy Week,
|
||||
// Easter Day, the Octave, or Low Sunday (Apr 24), landing outright on
|
||||
// Apr 27 there (Apr 25/26 already spoken for by St. Mark and Ss.
|
||||
// Cletus & Marcellinus in the reference's own calendar). vu's own
|
||||
// calendar differs on Apr 25-27 (different saints assigned), so this
|
||||
// asserts the *mechanism* — Leo reaching a day past the whole span in
|
||||
// the right relative order — not the exact reference-engine landing
|
||||
// day, which depends on which calendar is in play.
|
||||
const holyMonday = resolveDay('2033-04-11');
|
||||
expect(holyMonday.winner).toEqual({ kind: 'temporal', id: 'palm-sunday' });
|
||||
expect(holyMonday.commemorations).toEqual([]);
|
||||
expect(holyMonday.transferredAway?.candidate.id).toBe('st-leo-i');
|
||||
|
||||
for (const impededDate of ['2033-04-13', '2033-04-17', '2033-04-21', '2033-04-24']) {
|
||||
const day = resolveDay(impededDate);
|
||||
expect(day.commemorations.some((c) => c.kind === 'sanctoral' && c.id === 'st-leo-i')).toBe(false);
|
||||
}
|
||||
|
||||
// Lands the first day past the whole span not already spoken for by
|
||||
// vu's own calendar (Apr 25 = St. Mark, native and unaffected).
|
||||
const landing = resolveDay('2033-04-25');
|
||||
expect(landing.winner).toEqual({
|
||||
kind: 'sanctoral',
|
||||
id: 'st-mark',
|
||||
name: 'St. Mark, Evangelist',
|
||||
nameLa: 'Sanctus Marcus, Evangelista',
|
||||
rank: 'duplex-2-classis',
|
||||
});
|
||||
expect(landing.commemorations).toEqual([
|
||||
{
|
||||
kind: 'sanctoral',
|
||||
id: 'st-leo-i',
|
||||
name: 'St. Leo I, Pope, Confessor and Doctor of the Church',
|
||||
nameLa: 'Sanctus Leo I, Papa, Confessor et Ecclesiæ Doctor',
|
||||
rank: 'duplex',
|
||||
transferredFrom: '2033-04-11',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('2033: three simultaneously-pending candidates (Leo, Hermenegild, Ss. Tiburtius/Valerian/Maximus) land in strict native-date order, none skipping ahead', () => {
|
||||
const leo = resolveDay('2033-04-25').commemorations.find((c) => c.kind === 'sanctoral' && c.id === 'st-leo-i');
|
||||
const hermenegild = resolveDay('2033-04-26').commemorations.find((c) => c.kind === 'sanctoral' && c.id === 'st-hermenegild');
|
||||
const tiburtius = resolveDay('2033-04-27').commemorations.find(
|
||||
(c) => c.kind === 'sanctoral' && c.id === 'ss-tiburtius-valerian-and-maximus',
|
||||
);
|
||||
expect(leo?.kind === 'sanctoral' ? leo.transferredFrom : undefined).toBe('2033-04-11');
|
||||
expect(hermenegild?.kind === 'sanctoral' ? hermenegild.transferredFrom : undefined).toBe('2033-04-13');
|
||||
expect(tiburtius?.kind === 'sanctoral' ? tiburtius.transferredFrom : undefined).toBe('2033-04-14');
|
||||
});
|
||||
|
||||
it("2035: the Annunciation (native Easter Sunday itself, Duplex I. classis) transfers past the whole span with zero commemoration anywhere in it — matching the real reference engine's own behavior, not just a native-date-order queue effect", () => {
|
||||
// Easter 2035 is Mar 25 -- the Annunciation's own fixed date. Live-
|
||||
// verified 2026-09-01: the real Monastic 1617 engine shows her
|
||||
// transferring off Easter Day with no commemoration anywhere in Holy
|
||||
// Week/the Octave/Low Sunday (Apr 1 that year), unlike a same-ranked
|
||||
// *native* occurrence there (e.g. St. Mark, Duplex II. classis,
|
||||
// commemorated when native within some other year's Octave) --
|
||||
// confirming a transferred-in candidate never gets the "duplex-majus+
|
||||
// still gets a nod" treatment on these days, only a native one does.
|
||||
const easterSunday = resolveDay('2035-03-25');
|
||||
expect(easterSunday.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' });
|
||||
expect(easterSunday.transferredAway?.candidate.id).toBe('annunciation');
|
||||
|
||||
for (const impededDate of ['2035-03-26', '2035-03-29', '2035-04-01']) {
|
||||
const day = resolveDay(impededDate);
|
||||
expect(day.commemorations.some((c) => c.kind === 'sanctoral' && c.id === 'annunciation')).toBe(false);
|
||||
}
|
||||
|
||||
const landing = resolveDay('2035-04-04');
|
||||
expect(landing.winner).toEqual({
|
||||
kind: 'sanctoral',
|
||||
id: 'annunciation',
|
||||
name: 'The Annunciation of the Blessed Virgin Mary',
|
||||
nameLa: 'Annuntiatio Beatæ Mariæ Virginis',
|
||||
rank: 'duplex-1-classis',
|
||||
transferredFrom: '2035-03-25',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user