summaryrefslogtreecommitdiff
path: root/test/test_validate.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-11 21:03:00 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-11 21:03:00 +0200
commitf15e44dd4c1b871c2daeb952b1c8c848274ea1f1 (patch)
tree2e2f73260780f43a9d9a3e38ed9c181f94c9d50b /test/test_validate.ml
parent953427d8d1e3a34994be53b60e18662ec26fef4e (diff)
downloadcolitur-f15e44dd4c1b871c2daeb952b1c8c848274ea1f1.tar.gz
colitur-f15e44dd4c1b871c2daeb952b1c8c848274ea1f1.zip
kernel(calendar): place transferred celebrations (RG 96-98)
Calendar.year now runs a placement pass after resolving every day: each deferred candidate (RG 95's I-class-only right of translation, via Precedence's Transfer disposition) is placed on the next day the rite's new Rite.t.transfer_target names as admissible, transferred_in/out are set on the two ends of the move, and the whole year is re-resolved to a fixed point, bounded by a hard max_transfer_rounds = 64 guard. transfer_target is rite-supplied rather than a generic search Calendar drives itself: RG 96's 'not I or II class' is not derivable from band or disposition alone (RG 91's own table lets a universal I-class feast outrank an ordinary Sunday in a raw contest, yet RG 96 forbids landing a translation there regardless), and the search's starting point is rite-specific too (the Annunciation exception). It takes an occupant callback exposing what Calendar currently resolves as observed on any date, so the rite never has to re-implement occurrence resolution. Two correctness properties drove most of the design: - A candidate's permanent natural loss at its own origin (the layer entry never moves) is rediscovered every round; left unfiltered this oscillates a placed candidate between two dates forever, since its own rank makes it look 'occupied' to a fresh search from its origin. Both the round loop's gather and the final per-day omitted accounting filter this out, keeping only sightings that are either brand new or losing at a candidate's *current* target (a fresh RG 97-98 bump). - RG 97-98's sort has to actually decide something, not just happen to agree with Precedence.resolve's own tie-break next round: a claimed-this-round overlay lets earlier-processed candidates in one round block later ones in the same pass, so two coinciding I-class feasts land on consecutive admissible days in the one round they collide, in band order. Also folds in Task 5's review finding: year_bounds clamps y to [1582, 9999] once, up front, rather than guarding start and stop independently (each guard only ever covered one of the two rite.year_start calls, leaving year 999 and year 100000 each able to call it out of domain through the other branch).
Diffstat (limited to 'test/test_validate.ml')
-rw-r--r--test/test_validate.ml13
1 files changed, 8 insertions, 5 deletions
diff --git a/test/test_validate.ml b/test/test_validate.ml
index 31d7a3d..df8c99c 100644
--- a/test/test_validate.ml
+++ b/test/test_validate.ml
@@ -5,9 +5,9 @@ module V = Rite_ef.Vocab_ef
module T = Rite_ef.Temporal_ef
(* Plan 3's real EF precedence rules (Precedence_ef, Tasks 7-11) don't exist
- yet -- Validate.run doesn't read [rules] at all (nothing does before
- Task 5's Calendar), so a placeholder is enough to assemble a well-typed
- Rite.t here. *)
+ yet -- Validate.run doesn't read [rules] or [transfer_target] at all
+ (nothing does before Task 5's Calendar and Task 6's placement pass), so a
+ placeholder is enough to assemble a well-typed Rite.t here. *)
let ef_rules : (V.season, V.rank) P.rules =
{ P.band = (fun _ _ -> 0);
disposition = (fun ~winner:_ ~loser:_ -> P.Omit);
@@ -15,7 +15,8 @@ let ef_rules : (V.season, V.rank) P.rules =
let ef_rite : (V.season, V.rank) Rite.t =
{ Rite.id = T.id; vocab = V.vocab; year_start = T.year_start; temporal = T.temporal;
- anchors = T.anchors; rules = ef_rules; season_runs = V.seasons }
+ anchors = T.anchors; rules = ef_rules; season_runs = V.seasons;
+ transfer_target = (fun _ origin _ -> origin) }
let run year = Val.run ef_rite ~year
@@ -174,7 +175,9 @@ module Synthetic = struct
let rite ?(vocab = vocab) ?(anchors = fun _ -> []) ?(season_runs = [ A; B ]) temporal
: (season, rank) Rite.t =
- { Rite.id = "synthetic"; vocab; year_start; temporal; anchors; rules; season_runs }
+ { Rite.id = "synthetic"; vocab; year_start; temporal; anchors; rules; season_runs;
+ (* Validate.run doesn't read this either (see [ef_rules] above). *)
+ transfer_target = (fun _ origin _ -> origin) }
let run ?vocab ?anchors ?season_runs temporal =
Val.run (rite ?vocab ?anchors ?season_runs temporal) ~year:2026