Legend World Vertical Slice 002 — Facility Commitment
Status: v1.1 — reconciled and finalized. All items are confirmed architecture decisions: the §8.2 cache-key correction, deterministic duplicate-field_path rejection, Step 11 all-or-nothing atomic commitment, failed_commitment visibility until explicit owner action, Step 11 re-validation, and the renovation/expansion/facility-upgrade/replacement category distinctions. One internal contradiction (the proposed_at field comment vs. §3.3) was found and fixed during finalization — see §3.2. No unresolved ownership conflicts remain.
Role boundary: Logic, contracts, invariants, and tests only — no code, no database migrations, no UI, no Supabase/Lovable access attempted, per explicit instruction.
Authoritative inputs: DL-MASTER (v1.1, Step 11), DL-SPEC-014 (v1.4, §8–§10), LS-SPEC-005 (v1.2), LS-SPEC-006 (v1.4, including the confirmed §8.2 cache-key correction), LEGEND-WORLD-VERTICAL-SLICE-001 (v2.0).
Scope discipline: Strictly stadium/world architecture — existing stadium → proposed change → pending queue → validation → Step 11 atomic commitment → World Manifest update if required → persistent history. No league, franchise-marketplace, valuation, advertising, or unrelated system content appears anywhere below.
0. Cache-Key Correction — Confirmed
LS-SPEC-006 §8.2's World Manifest cache key is (settlement_id, visual_year, site_id, orientation_bucket), confirmed as an authoritative architecture decision. orientation_bucket is derived only from venue orientation — no other component of venue_footprint is included in the cache key unless a future engineering pass demonstrates it actually affects generation. §6 and §7 below rely on this directly.
1. Facility Commitment Flow, Mapped to Owning Spec
1. Existing Stadium (live state) → DL-SPEC-014 §11 (existing)
2. Proposed Change (renovation/expansion/
replacement/upgrade) → DL-SPEC-014 §7–§9 (existing, made concrete here)
3. Pending Facility Queue → DL-SPEC-014 §8.2 (existing, made concrete here)
4. Validation (structural + cost) → DL-SPEC-014 §8.3 + DL-SPEC-008 (existing boundary)
5. Step 11 Atomic Commitment → DL-MASTER Step 11 + DL-SPEC-014 §8.5/INV-7/INV-8 (existing)
6. Changed venue_footprint → new VenueWorldRequest → LS-SPEC-006 §1.1 (existing)
7. Updated World Manifest → LS-SPEC-006 §6 (existing, §8.2 fix per §0)
8. Persistent Stadium/World History → DL-SPEC-014 §9.3/§10 + LS-SPEC-005 §10 (existing, composed)
2. Stage 1 — Existing Stadium (Baseline State)
2.1 Contract
StadiumState -- the live record, DL-SPEC-014 §11, read at proposal time
stadium_id: string
franchise_id: string
status: 'active' | 'renovated' | 'replaced' | 'demolished' | 'preserved'
lifecycle_stage: 'original' | 'renovated'
field_geometry: FieldGeometryInput & FieldGeometryFixed -- per VERTICAL-SLICE-001 §5.1
venue_footprint: VenueFootprint -- per VERTICAL-SLICE-001 §5.3
seating_configuration: SeatingDeck[]
suite_configuration: SuiteConfig[]
facility_levels: { bullpen_quality, clubhouse_quality, training_complex_level: 0-4 }
commercial_facilities: { concessions_quality, team_store_quality, parking_capacity }
scoreboard_type: string -- era-gated, this stadium's own technology (distinct from
-- LS-SPEC-006's manifest "lighting" field — see §2.2 note)
lighting_type: string
capacity: number -- confirmed DL-SPEC-008 contract
revenue_modifier: number -- confirmed DL-SPEC-008 contract
assessed_value: number -- confirmed DL-SPEC-008 contract
annual_debt_service: number | null -- confirmed DL-SPEC-008 contract
suite_revenue: number -- confirmed DL-SPEC-008 contract
training_complex_level: 0-4 -- confirmed DL-SPEC-010 contract
2.2 Naming Note, Not a Conflict — Flagged for Clarity Only
lighting_type here (a stadium's own lighting infrastructure) and the World Manifest's lighting field (LS-SPEC-006 §6.1, ambient/environmental lighting for the surrounding world) are different concepts sharing a similar name. This is not a functional conflict — nothing in either spec confuses the two operationally — but it's a documentation clarity risk worth naming so implementers don't conflate them. No action taken here beyond this note; a future documentation pass could rename one of the two fields for precision.
3. Stage 2 — Proposed Facility Change
3.1 Change Categories, Defined Precisely (Resolving an Imprecise Boundary in DL-SPEC-014)
DL-MASTER Step 11 names four categories — renovations, expansions, replacement-stadium openings, facility upgrades — but DL-SPEC-014 never precisely distinguished "renovation" from "expansion." Resolved here, within DL-SPEC-014's own existing ownership (not a new-owner situation, just a needed precision):
| Category | Definition |
|---|---|
| Renovation | A change to existing infrastructure that does not increase capacity or alter the field footprint's overall scale — e.g., new scoreboard, new lighting technology, wall material change, facility-level upgrade |
| Expansion | A change that increases capacity and/or physically alters the footprint's scale — e.g., an added deck, an enlarged seating bowl, changed fence distances |
| Replacement | An entirely new stadium record, per DL-SPEC-014 §9 — never a modification of the existing one |
| Facility Upgrade | A change to facility_levels/commercial_facilities only — no geometry or capacity impact (training complex, concessions, team store, parking) |
A single proposal may combine changes from multiple categories (e.g., a renovation-and-facility-upgrade bundle) — categorization is per-field-change, not per-proposal, per §4's structure.
3.2 Contract
FacilityChangeProposal
stadium_id: string
franchise_id: string
proposed_at: timestamp -- any point within the offseason sequence (DL-MASTER Steps 1-10);
-- NOT gated by DL-SPEC-004's season_start_day/season_end_day —
-- that boundary applies only to Step 11 commitment, see §3.3
field_changes: FieldChange[]
FieldChange
field_path: string -- e.g. "seating_configuration[deck_id=3].section_count"
category: 'renovation' | 'expansion' | 'facility_upgrade' -- 'replacement' is never a FieldChange, see §9
new_value: any
affects_venue_footprint: boolean -- see §6.1 for the definitive field-to-footprint mapping
3.3 When Proposals Are Accepted
Corrected timing, more precise than DL-SPEC-014 v1.4's original statement. DL-SPEC-014 §8.2/§8.5 previously implied proposals are accepted only outside the season window. That's imprecise: proposals may be submitted at any point in the offseason sequence (DL-MASTER Steps 1–10), not merely "outside the season." What's actually gated by season_start_day/season_end_day is commitment (Step 11), not proposal. This document corrects that imprecision — a proposal submitted, say, during Coaching Evaluation (Step 6) is valid; it simply queues (§4) until Step 11.
4. Stage 3 — Pending Facility Queue
4.1 Contract
PendingFacilityChange
id: string
stadium_id: string
proposal: FacilityChangeProposal
status: 'pending_validation' | 'pending_commitment' | 'committed' | 'failed_commitment' | 'withdrawn'
cost_estimate: { amount: number, financing_impact: object } -- from DL-SPEC-008, read-only here
queued_at: timestamp
4.2 What Remains Unchanged Until Step 11
Nothing in StadiumState (§2.1) is modified by a proposal entering the queue. Every confirmed field-level contract — capacity, revenue_modifier, assessed_value, annual_debt_service, suite_revenue, training_complex_level, venue_footprint, status, lifecycle_stage — remains at its pre-proposal value for the entire offseason, until Step 11 either commits or fails the batch. A PendingFacilityChange is purely additive queue state; it never shadows or partially overrides the live StadiumState.
4.3 Multiple Pending Changes to One Stadium — Combination Rule
Deterministic merge order, conflict rejection at proposal time. Per LS-SPEC-005/006's project-wide determinism requirement, extended here: pending changes for a stadium combine in queued_at order (earliest first). Two simultaneously-pending changes may never target the same field_path — a second proposal referencing a field already claimed by an existing pending_validation/pending_commitment change is rejected at proposal time (§8, failure state), not silently merged or allowed to create ambiguity about which value wins. This is a hard rule, not a preference: it eliminates any need for a "last write wins" tiebreak that could otherwise vary by implementation detail.
5. Stage 4 — Validation
5.1 Structural Validation (At Proposal Time)
field_pathmust reference a real, existing field onStadiumState.- If
category = 'expansion'and the resulting geometry would exceed the stadium's site's available area (per LS-SPEC-005 site data, read only through LS-SPEC-006 — this document does not query LS-SPEC-005 directly, preserving the one-door rule end to end), the proposal is rejected — an expansion that needs more land than the site has must go through the Replacement path (§9), never a silent relocation. - No
field_pathmay target a fixed-by-rule geometry field (base_path_distance,pitching_distance) — inherited unchanged from VERTICAL-SLICE-001 §5.2/INV-3.
5.2 Cost Validation (DL-SPEC-008's Boundary, Unchanged)
Every PendingFacilityChange requires a cost_estimate from DL-SPEC-008 before advancing to pending_commitment — this document never computes a dollar figure, per DL-SPEC-014 §8.3.
5.3 Re-Validation at Step 11 (New Precision, Within DL-SPEC-014's Existing Scope)
Cost and structural validity are re-checked at Step 11 commit time, not assumed valid from proposal time. An offseason spans multiple steps (draft, free agency, coaching decisions) during which a franchise's financial position can change — a change affordable when proposed in Step 3 is not guaranteed affordable by Step 11. This is a workflow-timing decision within DL-SPEC-014's own ownership (it already owns when validation happens; DL-SPEC-008 still owns how cost is computed), not a new responsibility for either spec.
6. Stage 5 — Step 11 Atomic Commitment
6.1 Field-to-Footprint Mapping (Definitive)
| Field | Affects venue_footprint? |
|---|---|
foul_line_distance_left/right, center_field_distance, wall_height_by_section |
Yes — geometry |
home_plate_orientation |
Yes — orientation, and therefore the manifest cache's orientation_bucket (§0) |
seating_configuration, suite_configuration |
No |
scoreboard_type, lighting_type |
No |
facility_levels, commercial_facilities |
No |
| Replacement (§9) | Always — a full new footprint, potentially a full new site/settlement |
6.2 Commitment Contract
FacilityCommitmentBatch -- one per stadium, assembled at Step 11
stadium_id: string
changes: PendingFacilityChange[] -- all with status = 'pending_commitment', in queued_at order
FacilityCommitmentResult
stadium_id: string
outcome: 'committed' | 'failed'
updated_stadium_state: StadiumState | null -- null if failed
world_manifest_regenerated: boolean
failure_reason: string | null
6.3 Atomicity — All-or-Nothing, Precisely Defined
If re-validation (§5.3) fails for any single change in the batch, the entire batch fails — no partial commitment. This is the literal meaning of DL-SPEC-014 INV-8 ("no partial commitment, no per-change independent application"), made operational: StadiumState is not modified at all unless every change in the batch passes re-validation. Individual changes are not silently dropped to let the rest through.
6.4 Determinism
Given an identical FacilityCommitmentBatch (same changes, same queued_at order) applied to an identical pre-commit StadiumState, the resulting updated_stadium_state must be identical every time — extending LS-SPEC-005 §14 INV-6 and LS-SPEC-006 §2.2a/§6.4 through this commitment process. No randomness in merge order (already excluded by §4.3's conflict-rejection rule), no randomness in re-validation outcome.
7. Stage 6 — World Manifest Update If Required
7.1 Regeneration vs. Reuse
Per §6.1's mapping: if any committed change has affects_venue_footprint = true, a new VenueWorldRequest is sent to LS-SPEC-006 §1.1 as part of the same Step 11 pass, and the response's manifest is cached under the corrected key from LS-SPEC-006 §8.2 (settlement_id, visual_year, site_id, orientation_bucket). If no committed change affects venue_footprint, the existing cached manifest is reused unmodified — no regeneration, no new request.
7.2 Replacement Always Regenerates
A committed Replacement (§9) always sends a full new VenueWorldRequest, including — where relocation is involved — a full Stage 2/3 candidate-site-generation and selection pass (§9.2) rather than any shortcut.
8. Failure States
| Failure | Trigger | Behavior |
|---|---|---|
| Duplicate field-path proposal | §4.3, two pending changes target the same field | Second proposal rejected at proposal time with a specific error naming the conflicting change |
| Structural validation failure | §5.1 | Proposal rejected before it ever reaches pending_validation |
| Expansion exceeds site area | §5.1 | Rejected — a recommendation to use the Replacement path is returned, not a silent workaround |
| Cost re-validation failure at Step 11 | §5.3 | Entire batch fails per §6.3 — FacilityCommitmentResult.outcome = 'failed', all changes in the batch revert to status = 'failed_commitment', none silently discarded — the owner must revise or withdraw before the next offseason's Step 11 |
| World Manifest regeneration failure | §7.1, LS-SPEC-006 unable to produce a manifest | Per VERTICAL-SLICE-001 §8, this is treated as a defect against LS-SPEC-005/006's guaranteed-fallback design, not a normal failure this document handles gracefully — the whole batch fails (§6.3) rather than committing a stadium state with no corresponding valid world |
| Fixed-geometry override attempt | §5.1 | Rejected explicitly, same as VERTICAL-SLICE-001 §8 |
No auto-cancellation. A failed batch's changes are never silently discarded — they remain failed_commitment, visible to the owner, requiring explicit revision or withdrawal, consistent with the project-wide principle that an owner's decision is never overridden without their knowledge.
9. Replacement Stadiums
9.1 Never a FieldChange
Per §3.2's note: a Replacement is never expressed as a FieldChange on the existing stadium. It's a distinct proposal type:
ReplacementProposal
predecessor_stadium_id: string
franchise_id: string
new_location: settlement_id | 'same_as_predecessor'
new_site: SiteSelectionResponse | 'candidate_pending' -- see §9.2
new_field_geometry: FieldGeometryInput
preserve_predecessor_as: 'demolished' | 'preserved' -- DL-SPEC-014 §10, owner's narrative choice
9.2 Relocation Reuses the Existing Candidate-Site Pipeline
If new_location differs from the predecessor's settlement, or a new site within the same settlement is desired, this proposal triggers the identical VERTICAL-SLICE-001 Stage 2/3 flow (CandidateSiteRequest → LS-SPEC-006 §1.1a → LS-SPEC-005 §1.3a → owner selection) — not a shortcut, not a simplified version. A ReplacementProposal with new_site: 'candidate_pending' is not eligible for Step 11 commitment until a SiteSelectionResponse has been captured, the same as initial franchise creation.
9.3 Commitment
At Step 11, a committed ReplacementProposal creates a new stadiums record (predecessor_stadium_id set), transitions the predecessor's status to preserve_predecessor_as's value, and always triggers a full new VenueWorldRequest (§7.2) — never a partial update to the predecessor's record.
10. Persistent Stadium/World History
10.1 What's Written at Commitment
Every committed FacilityCommitmentBatch writes an immutable stadium_renovation_history entry per DL-SPEC-014 §8.1, containing the full before/after StadiumState snapshot, the batch's changes, and — for multiplayer franchises — a corresponding DL-SPEC-013 League Event Log entry per DL-SPEC-014 §8.6. A failed batch also writes a history entry (status failed_commitment, with failure_reason) — failures are part of the permanent record too, not silently omitted.
10.2 Old Stadiums Never Deleted
Unchanged from DL-SPEC-014 §9.3 — a replaced/demolished/preserved stadium's record persists permanently.
11. Invariants
- INV-1: No
StadiumStatefield changes outside of a Step 11 commitment — proposal and queuing never mutate live state (§4.2). - INV-2: Two simultaneously-pending changes on one stadium never target the same
field_path(§4.3). - INV-3: Facility Commitment is all-or-nothing per stadium per Step 11 pass — no partial application (§6.3, restating DL-SPEC-014 INV-8 operationally).
- INV-4: A
FacilityCommitmentBatchapplied to identical pre-commit state always produces an identical result (§6.4). - INV-5: Any committed change with
affects_venue_footprint = truealways triggers a newVenueWorldRequest; no such change ever commits while reusing a stale cached manifest (§7.1, per the confirmed §0 cache-key correction). - INV-6: A Replacement is never expressed as a
FieldChange; it is always a distinctReplacementProposal(§9.1). - INV-7: An expansion that cannot fit its site is rejected, never silently relocated (§5.1, §8).
- INV-8: A failed commitment batch is never silently discarded — it persists as
failed_commitmentwith a reason, requiring explicit owner action (§8, §10.1). - INV-9: Fixed-by-rule geometry fields are never accepted as a
FieldChangetarget, under any category (§5.1, inherited from VERTICAL-SLICE-001 INV-3).
12. Acceptance Tests
| Test | Validates |
|---|---|
Propose a renovation (scoreboard change) mid-offseason, confirm it queues without altering live StadiumState |
§4.2, INV-1 |
Propose two changes targeting the same field_path |
Second is rejected — INV-2 |
Propose a facility upgrade (training complex) and confirm no VenueWorldRequest is sent at commitment |
§7.1 — reuse path |
Propose a fence-distance expansion and confirm a VenueWorldRequest is sent, manifest regenerated, cache key includes the new orientation_bucket if orientation changed |
§6.1, §7.1, §0 (confirmed cache-key correction) |
| Propose an expansion exceeding site area | Rejected, Replacement recommended — §5.1, INV-7 |
| Queue three valid changes, then fail cost re-validation on one at Step 11 | Entire batch fails, all three revert to failed_commitment — §6.3, INV-3, INV-8 |
| Commit a Replacement with relocation to a new settlement | Full candidate-site pipeline (VERTICAL-SLICE-001 Stage 2/3) is exercised, not bypassed — §9.2 |
| Commit a Replacement preserving the predecessor | Predecessor status = 'preserved', never deleted, new stadium's predecessor_stadium_id set — §9.3, §10.2 |
Run identical FacilityCommitmentBatch twice against identical starting state |
Byte-identical result both times — INV-4 |
Attempt a FieldChange on base_path_distance |
Rejected — INV-9 |
13. Explicit Non-Goals
This document does not address: league-level effects of stadium changes, franchise marketplace or valuation logic beyond the already-confirmed assessed_value/annual_debt_service field contracts, advertising/cosmetic stadium skins (DL-SPEC-017 §3's territory, untouched here), or any Living World era-transition mechanics beyond what's needed for a single Step 11 pass — all explicitly out of scope per your instruction.
14. Recommended Next Steps
This document and LEGEND-WORLD-VERTICAL-SLICE-001 together fully specify stadium creation and stadium lifecycle change, with no outstanding ownership items. The next stadium/world task is Living World mode's era-transition contracts (LS-SPEC-006 §4.2), which both vertical slices have consistently scoped out as a separate concern.