Skip to content

[Feature] Public conditional raw-progress commit bridge #94

Description

@Shilvia-i

Area

Public API

Motivation / use case

I am developing Shilvia.ProgressBridge, a read-only progression comparison Mod targeting Slay the Spire 2 v0.107.1 / progress schema 21.

A future opt-in phase needs to conditionally commit a complete raw progress.save JSON document while preserving properties unknown to the consumer and using the save store already owned by the running game.

The currently available paths do not provide that boundary:

  • ProgressSaveManager.SaveProgress reaches the live store but reserializes the typed DTO, so unknown JSON properties cannot be guaranteed to survive.
  • direct local-file replacement can preserve the document but bypasses the live CloudSaveStore and save-batch coordination.
  • exposing or reflecting into a private save-store field would not be a supported consumer contract.
  • a generation check followed by an unlocked commit leaves a race with ordinary game saves.

Progress Bridge remains read-only unless every required safety capability is explicitly available.

Proposed solution

Would RitsuLib consider a narrow, versioned service such as IRawProgressCommitBridge?

The service would not return ISaveStore / CloudSaveStore and would not accept arbitrary paths. It would expose:

  1. A descriptor with protocol version, provider version, supported progress schemas, connection to the live game-owned store, and explicit feature flags.
  2. One active-profile-only conditional raw-progress commit operation.
  3. A request containing:
    • active profile identity and vanilla/modded environment;
    • schema number;
    • expected local fingerprint, length, and last-modified value;
    • expected Cloud-enabled/persisted state and observed remote generation;
    • proposed raw JSON, fingerprint, and byte length;
    • caller-generated transaction ID.
  4. Structured outcomes for generation conflict, profile change, validation failure, local replacement failure, Cloud failure with local data preserved, and recovery-required states.

For protocol 1, the provider would own this ordering:

  1. Validate request shape, schema, active profile, raw JSON, and proposed fingerprint.
  2. Acquire an exclusive save window shared with every save operation capable of changing active progress.
  3. Re-read local and Cloud generation through the live store.
  4. Return GenerationConflict without transaction artifacts on mismatch.
  5. Durably create recovery metadata that is local-only and outside Cloud-managed profile deletion.
  6. Begin the live Cloud save batch.
  7. Commit the caller's validated raw document through the live store, retaining official backup/temp/flush behavior.
  8. Verify the destination fingerprint and record the recoverable state.
  9. End the Cloud batch in finally.
  10. Complete or retain the journal according to the verified outcome, then release the exclusive window.

Unknown properties must be preserved by committing the validated raw payload rather than serializing a SerializableProgress DTO.

A key feasibility question is whether RitsuLib can provide an exclusive window that also covers ordinary game progress saves without Harmony patches. A Mod-only lock would not close the compare/commit race and should not advertise that capability. If this is not possible with the current game API, confirming that limitation would still be very helpful.

Alternatives considered

  • Typed ProgressSaveManager.SaveProgress: rejected because it cannot guarantee unknown-property preservation.
  • Direct profile-file replacement: rejected because it bypasses the live game-owned Cloud store and batch.
  • Reflecting into RitsuLib/game internals: rejected because it is not a stable public contract.
  • BaseLib: its current public API does not expose an equivalent raw progress persistence bridge.
  • Generic recursive JSON merge: rejected because progression fields and Epoch prerequisites require versioned, field-specific semantics.

Additional context

The consumer-side design currently has:

  • an exact protocol/schema/feature handshake that fails closed;
  • pure field-specific merge and official-deserializer validation;
  • destination/Cloud generation guards;
  • a pure recovery journal/state machine;
  • temporary-directory-only physical fault tests;
  • 74 passing synthetic tests;
  • no runtime writer and no profile write trial.

I can provide the proposed DTO/result shapes and consumer-side contract tests if this API is feasible. No real profile data is needed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/apiPublic API, contracts, or compatibility surface.good first issueGood for a first external contribution.status/needs-infoWaiting for more information from reporter or author.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions