Skip to content

Polish v1 release flow and prepare v2 rebuild#1

Open
yamcy1225 wants to merge 20 commits into
masterfrom
codex/runimal-v1-polish
Open

Polish v1 release flow and prepare v2 rebuild#1
yamcy1225 wants to merge 20 commits into
masterfrom
codex/runimal-v1-polish

Conversation

@yamcy1225

@yamcy1225 yamcy1225 commented Apr 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • tighten the v1 starter loop with launch-gate logic, tests, docs, telemetry, and store copy updates
  • fix the release workflow and add deterministic native capture plus Playwright-backed UI review assets
  • polish phone, watch, and mac surfaces, including the watch first-screen interaction and readability pass
  • add the Runimal v2 selective-rebuild foundation: AGENTS guidance, v2 architecture/rebuild/phase docs, asset inventory, and isolated Domain/Sync/Reward/Export/UI skeletons
  • make the v2 Domain/Sync/Reward/Export boundaries real SwiftPM products with focused tests for watch-local archive creation, sync envelope retries, manual reward spending, export receipts, GPS path display, and route quality
  • add RunimalWatchAdapterV2, a pure SwiftPM bridge from existing watch-produced WorkoutSessionArchive values into v2 CompletedRunArchive and SyncEnvelope without touching app targets
  • add RunimalPhoneAdapterV2, a SwiftPM-only phone ingest seam that turns v2 CompletedRunArchive payloads into existing WorkoutSessionArchive persistence candidates plus unspent RunResource candidates
  • add a Codable RunResourceLedger plus pure phone ingest application use case so duplicate sync cannot duplicate resources or resurrect already-spent resources
  • define the phone resource-ledger persistence preflight with a versioned JSON snapshot, reserved run-resource-ledger-v2.json sidecar, and app-target wiring order
  • wire the first RunimalPhone app-target path to v2 ingest: canonical watch archives now apply the v2 ingest plan and persist an unspent run-resource-ledger-v2.json sidecar while preserving existing archive/run identity
  • prune unspent v2 ledger resources when their source workout archive is deleted or cleared as an imported external run, while preserving spent resources for future audit/history work
  • spend v2 run resources only after user growth actions succeed: companion feed, egg forge, and egg incubation now mark existing sidecar resources spent while legacy sidecar-less records remain usable
  • add a simulator app-target runtime check for v2 ledger sidecar creation, manual spend marking, unspent prune, and spent-resource preservation, then document the first resource allocation UI/imagegen/OpenGame design brief

Commit Guide

  • 686d9fe Add release workflow and UI review tooling
  • 842255e Lock v1 starter loop and launch docs
  • d20f05a Polish phone and watch companion UI
  • cec7032 Refine watch runtime pager and viewport polish
  • b8ecab2 Prepare Runimal v2 rebuild without disturbing v1
  • e6f0657 Make Runimal v2 domains testable
  • f0700fa Bridge watch archives into Runimal v2
  • 136129d Bridge v2 archives into phone ingest candidates
  • e6b8c93 Protect v2 run resources from duplicate ingest
  • 019de5a Define phone resource ledger persistence preflight
  • cdfe1b5 Wire phone ingest to the v2 resource sidecar
  • 1976d5f Prune unspent v2 resources when runs are deleted
  • 8c9465d Spend v2 run resources at user growth actions
  • feec19b Prove v2 resource ledger behavior in the simulator

Validation

  • swift test --filter RunimalV2Tests — 6 Swift Testing tests passed for the first v2 domain batch
  • swift test --filter RunimalWatchAdapterV2Tests — 4 Swift Testing tests passed
  • swift test --filter RunimalPhoneAdapterV2Tests — 4 Swift Testing tests passed
  • swift test --filter RunimalResourceLedgerV2Tests — 4 Swift Testing tests passed
  • swift test --filter RunimalPhoneIngestApplicationV2Tests — 3 Swift Testing tests passed
  • swift test --filter RunimalResourceLedgerPersistenceV2Tests — 1 Swift Testing test passed
  • swift test --filter RunimalPhoneIngestPersistenceDraftV2Tests — 1 Swift Testing test passed
  • swift test --filter RunimalPhoneCoreArchiveIngestPlanV2Tests — 1 Swift Testing test passed
  • swift test --filter RunimalPhoneSpendApplicationV2Tests — 3 Swift Testing tests passed
  • swift test — 37 XCTest + 100 Swift Testing tests passed
  • xcodegen generate
  • xcodebuild -project RunimalApple.xcodeproj -scheme RunimalPhone -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -derivedDataPath /tmp/runimal-phone-v2-runtime-check-build CODE_SIGNING_ALLOWED=NO build — succeeded
  • SIMCTL_CHILD_RUNIMAL_RUNTIME_CHECK=resource-ledger-v2 xcrun simctl launch --terminate-running-process booted com.jaw.runimal.phone — report failures=[] with sidecarCreated, companionFeedSpent, eggForgeSpent, eggIncubationSpent, unspentResourcePrunedAfterDelete, and spentResourcePreservedAfterDelete all true
  • xcodebuild -project RunimalApple.xcodeproj -scheme RunimalPhone -destination 'generic/platform=iOS' -derivedDataPath /tmp/runimal-phone-v2-runtime-device-build CODE_SIGNING_ALLOWED=NO build — succeeded
  • git diff --check
  • previous v1 validation retained: xcodebuild for RunimalPhone / RunimalWatch / RunimalMac, simulator capture refresh, real-device install and launch on iPhone + Apple Watch

Notes

  • Package.swift exposes v2 SwiftPM products for pure-domain and adapter validation; this PR now also wires the minimal RunimalPhone dependency edge through project.yml and regenerated RunimalApple.xcodeproj.
  • RunimalPhoneAdapterV2 explicitly does not create CompletedRunRecord or spend growth resources during ingest; manual spend now happens only after existing user-facing growth actions succeed.
  • RunimalRewardV2.RunResourceLedger is now persisted by the phone app as run-resource-ledger-v2.json; unspent orphan cleanup and manual spend marking now run on phone paths.
  • The runtime check is environment-gated by RUNIMAL_RUNTIME_CHECK=resource-ledger-v2 and writes only to isolated RunimalPhoneRuntimeChecks app-support directories.
  • Legacy v1 records without a v2 sidecar remain usable; if a v2 sidecar exists and is already spent, the phone growth action is blocked from consuming that run again.
  • Spent resources are intentionally preserved on delete/clear until v2 reward audit/history UX is designed.
  • Resource allocation UI work is now framed in docs/v2/resource-allocation-ui-v2.md; imagegen requires OPENAI_API_KEY and OpenGame remains an external prototype tool only, not a Runimal dependency.
  • Remaining follow-up risk: real Apple Watch → iPhone hardware sync has not yet confirmed sidecar creation, manual spend marking, and pruning through visible UI flows.
  • .omx/ local runtime state is ignored and not committed.
  • OpenGame remains an external prototype tool only; it is not a Runimal dependency.

@yamcy1225 yamcy1225 force-pushed the codex/runimal-v1-polish branch from 5aba511 to d20f05a Compare April 4, 2026 18:26
Codex added 2 commits April 6, 2026 17:39
Runimal already has a working phone/watch/mac app and core tooling, so the safest rebuild path is to preserve proven assets while creating separate v2 boundaries for domain, sync, reward, export, UI, and app-shell exploration. This commit adds the documentation and skeleton structure for that path without wiring new targets into XcodeGen or SwiftPM yet.

Constraint: project.yml is the XcodeGen source of truth and must not be changed during first-pass skeleton work
Constraint: Existing RunimalPhone, RunimalWatch, RunimalMac, SharedUI, RunimalCore, and RunimalCoreTests behavior must remain untouched
Rejected: Replacing RunimalCore immediately | too risky before the v2 recording/sync boundaries are proven
Rejected: Committing .omx runtime state | local workflow state should not be shared through GitHub
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not connect the V2 skeletons to Package.swift or project.yml until the next phase adds tests and an explicit integration plan
Tested: swiftc -parse Sources/RunimalDomainV2/RunimalDomainV2.swift Sources/RunimalSyncV2/SyncBoundaryDraft.swift Sources/RunimalRewardV2/RewardBoundaryDraft.swift Sources/RunimalExportV2/ExportBoundaryDraft.swift
Tested: swift test (37 XCTest + 73 Swift Testing tests passed)
Not-tested: XcodeGen regeneration or device install, because no project.yml/app-target files changed
@yamcy1225 yamcy1225 changed the title Polish v1 release flow and watch UI Polish v1 release flow and prepare v2 rebuild Apr 23, 2026
Codex added 15 commits April 23, 2026 18:05
The rebuild plan needed a first executable success beyond documentation, so the v2 domain, sync, reward, and export skeletons are now SwiftPM products with focused tests. The models still sit beside the existing app targets and do not alter the v1 Phone/Watch/Mac surfaces.

Constraint: Keep project.yml and app target wiring unchanged during this phase
Constraint: Preserve RunimalCore as the calculation and legacy archive backend while v2 boundaries are proven
Rejected: Integrating v2 modules into XcodeGen app targets now | premature before adapter tests exist
Rejected: Generating bitmap assets in this pass | design needs briefs first and should not block recording-loop verification
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Add Watch/Phone adapter tests before importing these v2 modules into app targets
Tested: swift test --filter RunimalV2Tests (6 Swift Testing tests passed)
Tested: swift test (37 XCTest + 79 Swift Testing tests passed)
Tested: git diff --check
Not-tested: XcodeGen regeneration or real-device install because project.yml and app target source lists were not changed
RunimalWatch already creates canonical WorkoutSessionArchive values, so the next safe v2 step is a pure SwiftPM adapter that wraps those archives into CompletedRunArchive and SyncEnvelope without importing watch frameworks or touching app targets.

Constraint: Do not modify project.yml or app target source wiring during adapter validation
Constraint: Preserve WatchRunSessionManager and WorkoutArchiveCanonicalizer as the proven recording/canonicalization path
Rejected: Rebuilding the watch session manager now | too risky before adapter and phone-ingest boundaries are tested
Rejected: Importing HealthKit/CoreLocation into v2 adapter target | would make the boundary harder to test outside the app
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Add the phone-side v2 ingest adapter before wiring RunimalWatchAdapterV2 into WatchConnectivity or app targets
Tested: swift test --filter RunimalWatchAdapterV2Tests (4 Swift Testing tests passed)
Tested: swift test (37 XCTest + 83 Swift Testing tests passed)
Tested: git diff --check
Not-tested: XcodeGen regeneration or real-device install because project.yml and app target files were not changed
The phone side now has a SwiftPM-only v2 adapter that turns CompletedRunArchive payloads into existing WorkoutSessionArchive persistence candidates while keeping growth as an unspent RunResource. This keeps RunimalPhone wiring out of scope until app-target tests define the exact store integration.

Constraint: project.yml and existing app targets must remain untouched during this phase
Rejected: Wire directly into PhoneDashboardStore now | would mix app-target migration with the testable SwiftPM boundary
Rejected: Auto-create CompletedRunRecord during ingest | violates v2's resource-first growth boundary
Confidence: high
Scope-risk: narrow
Directive: Do not spend RunResource or create growth records in ingest; keep that in a later explicit reward allocation step
Tested: swift test --filter RunimalPhoneAdapterV2Tests
Tested: swift test
Tested: git diff --check
Tested: git diff --name-only -- project.yml RunimalPhone RunimalWatch RunimalMac SharedUI Sources/RunimalCore Tests/RunimalCoreTests
Not-tested: iPhone app target runtime wiring is intentionally not connected yet
The v2 reward boundary now includes a Codable RunResourceLedger that deduplicates archive resources, records spend intents, and prevents duplicate sync from resurrecting already-spent resources. The phone adapter gains a pure application use case that applies an ingest plan to archive candidates and the resource ledger without importing RunimalPhone.

Constraint: keep app targets and project.yml untouched until the app wiring step has its own XcodeGen/xcodebuild validation
Rejected: Persist resources directly in RunimalPhone now | would couple storage migration to the pure SwiftPM boundary
Rejected: Treat duplicate sync as a new resource | would let repeated watch transfers create duplicate growth opportunities
Confidence: high
Scope-risk: narrow
Directive: Ingest may upsert resources, but spending must remain behind explicit SpendIntent validation
Tested: swift test --filter RunimalResourceLedgerV2Tests
Tested: swift test --filter RunimalPhoneIngestApplicationV2Tests
Tested: swift test
Tested: git diff --check
Tested: git diff --name-only -- project.yml RunimalPhone RunimalWatch RunimalMac SharedUI Sources/RunimalCore Tests/RunimalCoreTests
Not-tested: RunimalPhone runtime persistence wiring is intentionally not connected yet
The v2 resource ledger now has a versioned JSON snapshot codec and a reserved phone-side sidecar filename. The phone ingest application can produce a persistence draft that carries the updated workout archive list plus the ledger snapshot, letting the app wiring step stay small and ordered.

Constraint: RunimalPhone and project.yml remain untouched until the app integration pass can run XcodeGen and xcodebuild together
Rejected: Write files directly from SwiftPM adapter | would couple the pure boundary to app filesystem policy too early
Rejected: Store resources inside completed-runs.json now | would conflate workout receipt with growth record migration
Confidence: high
Scope-risk: narrow
Directive: Persist archive changes before ledger sidecar writes, and keep CompletedRunRecord creation outside ingest
Tested: swift test --filter RunimalResourceLedgerPersistenceV2Tests
Tested: swift test --filter RunimalPhoneIngestPersistenceDraftV2Tests
Tested: swift test
Tested: git diff --check
Tested: git diff --name-only -- project.yml RunimalPhone RunimalWatch RunimalMac SharedUI Sources/RunimalCore Tests/RunimalCoreTests
Not-tested: app-target file IO wrapper is intentionally documented but not wired yet
The phone app now applies the v2 ingest boundary when receiving a canonical workout archive, while preserving the existing WorkoutSessionArchive identity and payload for the current v1 store. A separate run-resource-ledger-v2.json sidecar keeps post-run resources unspent until a later explicit growth action.

Constraint: project.yml remains the XcodeGen source of truth for app target package dependencies
Constraint: CompletedRunRecord creation and resource spending must remain outside archive receipt
Rejected: Convert core archives through the v2 archive round trip in the app path | legacy non-UUID run IDs could be rewritten before existing UI and persistence read them
Confidence: high
Scope-risk: moderate
Directive: Do not spend RunResource during sync receipt; wire spending only through an explicit post-run growth use case
Tested: swift test --filter RunimalPhoneCoreArchiveIngestPlanV2Tests
Tested: swift test
Tested: xcodegen generate
Tested: xcodebuild -project RunimalApple.xcodeproj -scheme RunimalPhone -destination 'generic/platform=iOS' -derivedDataPath /tmp/runimal-phone-v2-ledger-build CODE_SIGNING_ALLOWED=NO build
Tested: git diff --check
Not-tested: Simulator or physical-device runtime sidecar file creation
Run deletion and imported-run clearing now map removed core archive IDs through the same v2 stable archive-ID helper used by ingest, then prune only unspent ledger resources tied to those source archives.

Constraint: Spent resources are preserved to avoid erasing future spend and audit evidence.
Rejected: Clear all resources for a deleted archive | would erase spent resource history before v2 reward audit is designed.
Confidence: high
Scope-risk: narrow
Directive: Keep ingest and delete paths using the same resourceArchiveID(forExistingCoreArchiveID:) mapping for non-UUID legacy archive IDs.
Tested: swift test --filter RunimalResourceLedgerV2Tests
Tested: swift test --filter RunimalPhoneCoreArchiveIngestPlanV2Tests
Tested: swift test
Tested: xcodebuild -project RunimalApple.xcodeproj -scheme RunimalPhone -destination 'generic/platform=iOS' -derivedDataPath /tmp/runimal-phone-v2-ledger-build CODE_SIGNING_ALLOWED=NO build
Tested: git diff --check
The first phone-side spend wiring keeps workout receipt separate from growth allocation, then marks a v2 resource spent only after an existing user action succeeds: companion feed, egg forge, or egg incubation. Legacy records without sidecars remain usable while already-spent v2 resources block double consumption.

Constraint: Existing v1 records may not have a v2 ledger sidecar, so missing sidecars must not block legacy growth actions.
Rejected: Spend resources during workout receipt | violates the v2 separation between recording trust and growth intent.
Confidence: high
Scope-risk: moderate
Directive: Keep receipt and spend paths separate; do not mark a RunResource spent until a user-facing growth action succeeds.
Tested: swift test --filter RunimalPhoneSpendApplicationV2Tests
Tested: swift test
Tested: xcodebuild -project RunimalApple.xcodeproj -scheme RunimalPhone -destination 'generic/platform=iOS' -derivedDataPath /tmp/runimal-phone-v2-spend-build CODE_SIGNING_ALLOWED=NO build
Tested: git diff --check
The phone-side resource ledger now has an app-target runtime check that launches under an explicit environment flag, runs isolated receipt/spend/prune scenarios, and writes a JSON report from inside the simulator app sandbox. The same pass records the first post-run resource allocation UI brief so visual work starts from the verified manual-spend model instead of guessing.

Constraint: The check must not mutate normal app data, so it uses isolated Application Support directories under RunimalPhoneRuntimeChecks.
Rejected: Treat pure SwiftPM tests as enough runtime evidence | they do not prove app-target persistence paths or simulator launch wiring.
Rejected: Add OpenGame or imagegen outputs to app targets | design tools remain external until accepted assets are translated deliberately.
Confidence: high
Scope-risk: moderate
Directive: Keep RUNIMAL_RUNTIME_CHECK entry points isolated and environment-gated; never show them in normal launches.
Tested: xcodegen generate
Tested: swift test --filter RunimalPhoneSpendApplicationV2Tests
Tested: swift test
Tested: xcodebuild -project RunimalApple.xcodeproj -scheme RunimalPhone -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -derivedDataPath /tmp/runimal-phone-v2-runtime-check-build CODE_SIGNING_ALLOWED=NO build
Tested: SIMCTL_CHILD_RUNIMAL_RUNTIME_CHECK=resource-ledger-v2 xcrun simctl launch --terminate-running-process booted com.jaw.runimal.phone; report failures=[] and all ledger checks true
Tested: xcodebuild -project RunimalApple.xcodeproj -scheme RunimalPhone -destination 'generic/platform=iOS' -derivedDataPath /tmp/runimal-phone-v2-runtime-device-build CODE_SIGNING_ALLOWED=NO build
Tested: git diff --check
Not-tested: Real Apple Watch to iPhone hardware sync sidecar creation/spend/prune path.
The v2 rebuild now has an explicit design-quality track that turns image generation into reviewed candidate references instead of production assets. The docs define the Watch-first metric hierarchy, phone route-detail priorities, growth-resource UX constraints, and batch-ready prompts for the first Runimal v2 UI concepts.

Constraint: Existing app targets, project.yml, and Package.swift must remain untouched for this design-only pass

Rejected: Generate ad hoc production assets immediately | current session exposes no live image tool namespace and OPENAI_API_KEY is missing

Confidence: high

Scope-risk: narrow

Directive: Treat output/imagegen artifacts as candidate references until reviewed for readability, accessibility, copyright safety, and SwiftUI feasibility

Tested: Parsed output/imagegen/runimal-v2-ui-briefs.jsonl with Python json; git diff --check

Not-tested: Live image generation API call
Codex CLI was updated to 0.123.0 so image_generation is now stable and exposed in a fresh tmux session. The first Runimal v2 imagegen brief generated a Watch running HUD candidate and copied the native output into the repo's candidate artifact directory.

Constraint: Existing app code, project.yml, and Package.swift remain untouched

Rejected: Use the imagegen skill CLI | user asked to fix Codex native image generation and OPENAI_API_KEY is not needed for the native tool

Confidence: high

Scope-risk: narrow

Directive: Keep this PNG as a candidate reference only; do not wire it into app targets without design/accessibility review

Tested: codex-cli 0.123.0 reports image_generation stable true; native image tool generated PNG; file reports PNG image data 1254 x 1254

Not-tested: SwiftUI implementation of the visual direction
The image-generation track now has candidate references for every prepared Runimal v2 brief: phone route detail, resource allocation, companion growth, and app icon, alongside the existing Watch HUD candidate. The output README records first-read design judgments and keeps the artifacts explicitly non-production until accessibility and SwiftUI feasibility review.

Constraint: Existing app code, project.yml, and Package.swift remain untouched

Rejected: Wire generated PNGs into asset catalogs now | candidates need design/accessibility review and vector/SwiftUI translation first

Confidence: high

Scope-risk: narrow

Directive: Use these images only as references; extract hierarchy, palette, spacing, and silhouettes before implementation

Tested: Parsed runimal-v2-ui-briefs.jsonl; verified all five expected PNG files exist; file identified each PNG; git diff --check

Not-tested: In-app rendering or small-size icon review
The user preferred a Tamagotchi-adjacent direction, so this adds a separate candidate batch that translates that taste into Runimal's existing PixelPetView, GameSurface, GameBoyPalette, and RunimalVisualTile language without wiring generated bitmaps into the app.

Constraint: Existing app targets, project.yml, Package.swift, and asset catalogs remain untouched

Rejected: Copy Tamagotchi branding, device shapes, or characters | IP risk and inconsistent with Runimal's existing original pixel-pet system

Confidence: high

Scope-risk: narrow

Directive: Treat generated PNGs as design references only; redraw accepted ideas as SwiftUI/vector/pixel assets before production use

Tested: jsonl parsed with python3; file verified each pixel PNG; git diff --check

Not-tested: Watch-size SwiftUI implementation and accessibility contrast in production views
The prior pixel batch captured the handheld virtual-pet UI mood but used generic companion silhouettes. This adds a species-locked candidate batch based on the documented five base species, fixed colors, and five rare-variant overlay rules, plus design-direction guardrails so future UI work does not regress.

Constraint: Existing app targets, project.yml, Package.swift, and asset catalogs remain untouched

Rejected: Generic green pixel pet direction | fails the documented Windrunner/Stoneback/Sparkfang/Mosshop/Seedle identity rules

Rejected: Treat rare variants as replacement species | docs require variant overlays on top of base species silhouette and color

Confidence: high

Scope-risk: narrow

Directive: Production companion UI must preserve species silhouette, base color, and variant-as-overlay separation before LCD chrome polish

Tested: Read Runimal world/species docs and external research note; jsonl parsed with python3; file verified generated PNGs; git diff --check

Not-tested: SwiftUI implementation, watch-size accessibility, and exact text fidelity inside generated mockups
The character art direction now needs the full five-stage growth flow, not only variant overlays. This adds a 5 base species x 5 growth stages matrix plus one per-species evolution sheet based on the documented level/growth blueprints.

Constraint: Existing app targets, project.yml, Package.swift, and asset catalogs remain untouched

Rejected: Mix rare variants into base growth sheets | this batch must validate clean species growth before variant overlays

Confidence: high

Scope-risk: narrow

Directive: Use these images as references only; production silhouettes should be encoded in SpeciesVisualRenderProfile and growthStageBlueprints

Tested: file verified all evolution PNGs; jsonl parsed with python3; git diff --check

Not-tested: SwiftUI pixel implementation and exact generated in-image text fidelity
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant