diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index b69a4ae..96f5a35 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -12,6 +12,12 @@ Build a drag-and-drop itinerary editor for SportsTime using UITableView bridged
**Dependencies:** None (foundation phase)
+**Plans:** 2 plans
+
+Plans:
+- [ ] 01-01-PLAN.md - Create SortOrderProvider utility and Trip day derivation methods
+- [ ] 01-02-PLAN.md - Create tests verifying semantic position persistence
+
**Requirements:**
- DATA-01: All movable items have semantic position `(day: Int, sortOrder: Double)`
- DATA-02: Travel segments are positioned items with their own sortOrder
@@ -97,7 +103,7 @@ Build a drag-and-drop itinerary editor for SportsTime using UITableView bridged
| Phase | Status | Requirements | Completed |
|-------|--------|--------------|-----------|
-| 1 - Semantic Position Model | Not Started | 8 | 0 |
+| 1 - Semantic Position Model | Planned | 8 | 0 |
| 2 - Constraint Validation | Not Started | 4 | 0 |
| 3 - Visual Flattening | Not Started | 3 | 0 |
| 4 - Drag Interaction | Not Started | 8 | 0 |
diff --git a/.planning/phases/01-semantic-position-model/01-01-PLAN.md b/.planning/phases/01-semantic-position-model/01-01-PLAN.md
new file mode 100644
index 0000000..863af5b
--- /dev/null
+++ b/.planning/phases/01-semantic-position-model/01-01-PLAN.md
@@ -0,0 +1,155 @@
+---
+phase: 01-semantic-position-model
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - SportsTime/Core/Models/Domain/SortOrderProvider.swift
+ - SportsTime/Core/Models/Domain/Trip.swift
+autonomous: true
+
+must_haves:
+ truths:
+ - "Games get sortOrder derived from game time (minutes since midnight + offset)"
+ - "Inserting between two items produces midpoint sortOrder"
+ - "Day number can be calculated from any date given trip start date"
+ artifacts:
+ - path: "SportsTime/Core/Models/Domain/SortOrderProvider.swift"
+ provides: "sortOrder calculation utilities"
+ exports: ["initialSortOrder(forGameTime:)", "sortOrderBetween(_:_:)", "sortOrderBefore(_:)", "sortOrderAfter(_:)", "needsNormalization(_:)", "normalize(_:)"]
+ - path: "SportsTime/Core/Models/Domain/Trip.swift"
+ provides: "Day derivation methods"
+ contains: "func dayNumber(for date: Date) -> Int"
+ key_links:
+ - from: "SortOrderProvider"
+ to: "ItineraryItem.sortOrder"
+ via: "Utilities compute values assigned to sortOrder property"
+ pattern: "sortOrder.*=.*SortOrderProvider"
+---
+
+
+Create the sortOrder calculation utilities and day derivation methods that Phase 1 depends on.
+
+Purpose: Establish the foundational utilities for semantic position assignment. Games need sortOrder derived from time, travel/custom items need midpoint insertion, and items need day derivation from trip dates.
+
+Output: `SortOrderProvider.swift` with all sortOrder utilities, `Trip.swift` extended with day derivation methods.
+
+
+
+@~/.claude/get-shit-done/workflows/execute-plan.md
+@~/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/01-semantic-position-model/01-RESEARCH.md
+
+# Existing source files
+@SportsTime/Core/Models/Domain/ItineraryItem.swift
+@SportsTime/Core/Models/Domain/Trip.swift
+
+
+
+
+
+ Task 1: Create SortOrderProvider utility
+ SportsTime/Core/Models/Domain/SortOrderProvider.swift
+
+Create a new file `SortOrderProvider.swift` with an enum containing static methods for sortOrder calculation.
+
+Include these methods (as specified in 01-RESEARCH.md):
+
+1. `initialSortOrder(forGameTime: Date) -> Double`
+ - Extract hour and minute from game time
+ - Calculate minutes since midnight
+ - Return 100.0 + minutesSinceMidnight (range: 100-1540)
+ - This ensures games sort by time and leaves room for negative sortOrder items
+
+2. `sortOrderBetween(_ above: Double, _ below: Double) -> Double`
+ - Return (above + below) / 2.0
+ - Simple midpoint calculation
+
+3. `sortOrderBefore(_ first: Double) -> Double`
+ - Return first - 1.0
+ - Creates space before the first item
+
+4. `sortOrderAfter(_ last: Double) -> Double`
+ - Return last + 1.0
+ - Creates space after the last item
+
+5. `needsNormalization(_ items: [ItineraryItem]) -> Bool`
+ - Sort items by sortOrder
+ - Check if any adjacent gap is less than 1e-10
+ - Return true if normalization needed
+
+6. `normalize(_ items: inout [ItineraryItem])`
+ - Sort by current sortOrder
+ - Reassign sortOrder as 1.0, 2.0, 3.0... (integer spacing)
+ - Updates items in place
+
+Use `Calendar.current` for date component extraction. Import Foundation only.
+
+
+File exists at `SportsTime/Core/Models/Domain/SortOrderProvider.swift` with all 6 methods. Build succeeds:
+```bash
+xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -20
+```
+
+ SortOrderProvider.swift exists with all 6 static methods, project builds without errors
+
+
+
+ Task 2: Add day derivation methods to Trip
+ SportsTime/Core/Models/Domain/Trip.swift
+
+Extend the existing Trip struct with day derivation methods in a new extension at the bottom of the file.
+
+Add these methods:
+
+1. `func dayNumber(for date: Date) -> Int`
+ - Use Calendar.current to get startOfDay for both startDate and target date
+ - Calculate days between using dateComponents([.day], from:to:)
+ - Return days + 1 (1-indexed)
+
+2. `func date(forDay dayNumber: Int) -> Date?`
+ - Use Calendar.current to add (dayNumber - 1) days to startDate
+ - Return the resulting date
+
+Add a comment block explaining:
+- Day 1 = trip.startDate
+- Day 2 = startDate + 1 calendar day
+- Games belong to their start date (even if running past midnight)
+
+These methods complement the existing `itineraryDays()` method but work with raw Date values rather than the Trip's stops structure.
+
+
+Build succeeds and new methods are callable:
+```bash
+xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -20
+```
+
+ Trip.swift has dayNumber(for:) and date(forDay:) methods, project builds without errors
+
+
+
+
+
+1. Both files exist and contain expected methods
+2. `xcodebuild build` succeeds with no errors
+3. SortOrderProvider methods are static and accessible as `SortOrderProvider.methodName()`
+4. Trip extension methods are instance methods callable on any Trip value
+
+
+
+- SortOrderProvider.swift exists with 6 static methods for sortOrder calculation
+- Trip.swift extended with dayNumber(for:) and date(forDay:) methods
+- Project builds successfully
+- No changes to existing ItineraryItem.swift (model already has correct fields)
+
+
+
diff --git a/.planning/phases/01-semantic-position-model/01-02-PLAN.md b/.planning/phases/01-semantic-position-model/01-02-PLAN.md
new file mode 100644
index 0000000..b972b48
--- /dev/null
+++ b/.planning/phases/01-semantic-position-model/01-02-PLAN.md
@@ -0,0 +1,199 @@
+---
+phase: 01-semantic-position-model
+plan: 02
+type: execute
+wave: 2
+depends_on: ["01-01"]
+files_modified:
+ - SportsTimeTests/SortOrderProviderTests.swift
+ - SportsTimeTests/SemanticPositionPersistenceTests.swift
+autonomous: true
+
+must_haves:
+ truths:
+ - "User can persist an item's position, reload, and find it in the same location"
+ - "Moving travel segment to different day updates its day property"
+ - "Inserting between two items gets sortOrder between their values (e.g., 1.0 and 2.0 -> 1.5)"
+ - "Games remain fixed at their schedule-determined positions"
+ artifacts:
+ - path: "SportsTimeTests/SortOrderProviderTests.swift"
+ provides: "Unit tests for SortOrderProvider"
+ min_lines: 80
+ - path: "SportsTimeTests/SemanticPositionPersistenceTests.swift"
+ provides: "Integration tests for position persistence"
+ min_lines: 100
+ key_links:
+ - from: "SortOrderProviderTests"
+ to: "SortOrderProvider"
+ via: "Test imports and calls provider methods"
+ pattern: "SortOrderProvider\\."
+ - from: "SemanticPositionPersistenceTests"
+ to: "LocalItineraryItem"
+ via: "Creates and persists items via SwiftData"
+ pattern: "LocalItineraryItem"
+---
+
+
+Create comprehensive tests verifying the semantic position model works correctly.
+
+Purpose: Prove that requirements DATA-01 through DATA-05 and PERS-01 through PERS-03 are satisfied. Tests must verify: sortOrder calculation correctness, midpoint insertion math, day derivation accuracy, and persistence survival across SwiftData reload.
+
+Output: Two test files covering unit tests for SortOrderProvider and integration tests for persistence behavior.
+
+
+
+@~/.claude/get-shit-done/workflows/execute-plan.md
+@~/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/01-semantic-position-model/01-RESEARCH.md
+@.planning/phases/01-semantic-position-model/01-01-SUMMARY.md
+
+# Source files
+@SportsTime/Core/Models/Domain/SortOrderProvider.swift
+@SportsTime/Core/Models/Domain/ItineraryItem.swift
+@SportsTime/Core/Models/Local/SavedTrip.swift
+
+
+
+
+
+ Task 1: Create SortOrderProvider unit tests
+ SportsTimeTests/SortOrderProviderTests.swift
+
+Create a new test file `SortOrderProviderTests.swift` with tests for all SortOrderProvider methods.
+
+Test cases to include:
+
+**initialSortOrder tests:**
+- `test_initialSortOrder_midnight_returns100`: 00:00 -> 100.0
+- `test_initialSortOrder_noon_returns820`: 12:00 -> 100 + 720 = 820.0
+- `test_initialSortOrder_7pm_returns1240`: 19:00 -> 100 + 1140 = 1240.0
+- `test_initialSortOrder_1159pm_returns1539`: 23:59 -> 100 + 1439 = 1539.0
+
+**sortOrderBetween tests:**
+- `test_sortOrderBetween_integers_returnsMidpoint`: (1.0, 2.0) -> 1.5
+- `test_sortOrderBetween_negativeAndPositive_returnsMidpoint`: (-1.0, 1.0) -> 0.0
+- `test_sortOrderBetween_fractionals_returnsMidpoint`: (1.5, 1.75) -> 1.625
+
+**sortOrderBefore tests:**
+- `test_sortOrderBefore_positive_returnsLower`: 1.0 -> 0.0
+- `test_sortOrderBefore_negative_returnsLower`: -1.0 -> -2.0
+
+**sortOrderAfter tests:**
+- `test_sortOrderAfter_positive_returnsHigher`: 1.0 -> 2.0
+- `test_sortOrderAfter_zero_returnsOne`: 0.0 -> 1.0
+
+**needsNormalization tests:**
+- `test_needsNormalization_wellSpaced_returnsFalse`: items with gaps > 1e-10
+- `test_needsNormalization_tinyGap_returnsTrue`: items with gap < 1e-10
+- `test_needsNormalization_empty_returnsFalse`: empty array
+- `test_needsNormalization_singleItem_returnsFalse`: one item
+
+**normalize tests:**
+- `test_normalize_reassignsIntegerSpacing`: after normalize, sortOrders are 1.0, 2.0, 3.0...
+- `test_normalize_preservesOrder`: relative order unchanged after normalize
+
+Use `@testable import SportsTime` at top.
+
+
+Tests compile and pass:
+```bash
+xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/SortOrderProviderTests test 2>&1 | grep -E "(Test Case|passed|failed)"
+```
+
+ SortOrderProviderTests.swift exists with 16+ test cases, all tests pass
+
+
+
+ Task 2: Create persistence integration tests
+ SportsTimeTests/SemanticPositionPersistenceTests.swift
+
+Create a new test file `SemanticPositionPersistenceTests.swift` with integration tests for semantic position persistence.
+
+These tests verify PERS-01, PERS-02, PERS-03 requirements.
+
+Test cases to include:
+
+**Position persistence (PERS-01):**
+- `test_itineraryItem_positionSurvivesEncodeDecode`: Create ItineraryItem with specific day/sortOrder, encode to JSON, decode, verify day and sortOrder match exactly
+- `test_localItineraryItem_positionSurvivesSwiftData`: Create LocalItineraryItem, save to SwiftData ModelContext, fetch back, verify day and sortOrder match
+
+**Semantic-only state (PERS-02):**
+- `test_itineraryItem_allPositionPropertiesAreCodable`: Verify ItineraryItem.day and .sortOrder are included in Codable output (not transient)
+
+**Midpoint insertion (PERS-03):**
+- `test_midpointInsertion_50Times_maintainsPrecision`: Insert 50 times between adjacent items, verify all sortOrders are distinct
+- `test_midpointInsertion_producesCorrectValue`: Insert between sortOrder 1.0 and 2.0, verify result is 1.5
+
+**Day property updates (DATA-02, DATA-05):**
+- `test_travelItem_dayCanBeUpdated`: Create travel item with day=1, update to day=3, verify day property changed
+- `test_item_belongsToExactlyOneDay`: Verify item.day is a single Int, not optional or array
+
+**Game immutability (DATA-03):**
+- `test_gameItem_sortOrderDerivedFromTime`: Create game item for 7pm game, verify sortOrder is ~1240.0 (100 + 19*60)
+
+Use in-memory SwiftData ModelContainer for tests:
+```swift
+let config = ModelConfiguration(isStoredInMemoryOnly: true)
+let container = try ModelContainer(for: LocalItineraryItem.self, configurations: config)
+```
+
+Import XCTest, SwiftData, and `@testable import SportsTime`.
+
+
+Tests compile and pass:
+```bash
+xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/SemanticPositionPersistenceTests test 2>&1 | grep -E "(Test Case|passed|failed)"
+```
+
+ SemanticPositionPersistenceTests.swift exists with 8+ test cases, all tests pass
+
+
+
+ Task 3: Run full test suite to verify no regressions
+
+
+Run the complete test suite to verify:
+1. All new tests pass
+2. No existing tests broken by new code
+3. Build and test cycle completes successfully
+
+If any tests fail, investigate and fix before completing the plan.
+
+
+```bash
+xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test 2>&1 | tail -30
+```
+Look for "** TEST SUCCEEDED **" at the end.
+
+ Full test suite passes with no failures, including all new and existing tests
+
+
+
+
+
+1. SortOrderProviderTests.swift exists with 16+ test methods covering all SortOrderProvider functions
+2. SemanticPositionPersistenceTests.swift exists with 8+ test methods covering persistence requirements
+3. All tests pass when run individually and as part of full suite
+4. Tests verify the success criteria from ROADMAP.md Phase 1:
+ - Position survives reload (tested via encode/decode and SwiftData)
+ - Travel day update works (tested via day property mutation)
+ - Midpoint insertion works (tested via 50-iteration precision test)
+ - Games use time-based sortOrder (tested via initialSortOrder)
+
+
+
+- 24+ new test cases across 2 test files
+- All tests pass
+- Tests directly verify Phase 1 requirements DATA-01 through DATA-05 and PERS-01 through PERS-03
+- No regression in existing tests
+
+
+