Skip to content

08 — Save

Goal

Learn what a save file actually holds about health, what it deliberately leaves out, and why a restore is silent.


What This Scene Demonstrates

The participant flow:

Scene health systems → StableId lookup → HealthSnapshot per owner → one labelled section → coordinator → save payload

Three enemies carry a HealthSystem and a StableId. HealthSaveParticipant finds all three on its own — nothing in this scene points it at any of them.

A second participant defined inside the panel's own file writes an encounter counter into the same payload, so health is visibly not privileged: the coordinator cannot tell the two apart.


Why This Scene Was Built On The Death FX Scene

Because the most important thing here is something that does not happen, and you cannot see an absence in a scene that had no effect to begin with.

Kill an enemy with the panel and the real death path runs: the handler fires, the FX plays, the Died counter ticks. Now restore a save taken while it was alive. The enemy comes back at full health with nothing playing at all, and Revived still reads zero.

That silence is correct. RestoreSnapshot writes the fields directly rather than going through the kill or revive paths, and firing death events during a load would spawn VFX and drop loot every time a save was read. But it means an in-place restore leaves presentation to you.


What To Look For

  • Who Gets Saved lists the three enemies with their ids and live health. A HealthSystem without a StableId is skipped — there would be no way to find it again on load
  • What The Snapshot Holds is the short list: Max, Current, Dead. Read the payload and confirm there is nothing else in it
  • NOT saved and not restored: shields, regen timers and cooldowns, i-frame state, combat state, damage locks, and any state your own rules hold. An enemy saved mid-fight behind an OverhealShield comes back with the right health and no shield
  • The Events That Do Not Fire counts Died and Revived on the watched enemy. Only the Kill button moves them. Restoring — in either direction — moves neither
  • A Save That Cannot Be True captures a real save and forces Max to 0, a state no capture can produce. The participant refuses that entry, names the owner, leaves the object alone, and the encounter section still loads

The three Debug Panel — Enemy … Health overlays are kept from the original scene on purpose: they show current/max live for all three enemies, so a restore is visible across the whole scene rather than only on the one the save panel is watching.


Scene Setup

  • Three enemies, each with HealthSystem and StableId
  • The death FX handlers from 07, left intact — they are the point, see above
  • The per-enemy health debug panels, left intact
  • HealthSavePanel in place of DeathFXHandlersPanel, with Event Watch Target assigned explicitly rather than left to auto-resolve, so the same enemy is watched every run

The participant needs no configuration. Unlike Inventory's it takes no constructor arguments — health snapshots resolve nothing through a database, so there is nothing to hand it.


Sample Scope

This scene focuses only on:

  • Capturing every scene HealthSystem through one participant
  • The exact contents of HealthSnapshot, and the far longer list of what is absent
  • Event silence on restore, in both directions
  • Refusing a snapshot that describes no state the system could have been in

This scene does NOT cover:

  • Writing the payload to disk — nothing here does any file I/O, on purpose
  • Restoring shields, regen, i-frames, combat state or rule state, none of which are in the snapshot
  • Reconstructing presentation after an in-place restore
  • Save slots, versioning beyond the participant's own guard, or networking

If shields matter to your game, restore them through whatever granted them — a status effect via the Status Effects participant, or a participant of your own. The snapshot type belongs to Health's contract assembly, and a second definition of "health state" is not something this layer should invent.


Authority Note

This scene may include a permissive sample authority setup. If no authority is resolved, mutations are allowed for demonstration purposes.


Networking Reminder

No networking is included. Multiplayer authority and synchronization are the developer's responsibility.


How To Use

  1. Press Capture while all three enemies are healthy, and read the payload
  2. Press Damage a few times, then Restore. Health returns, on the watched enemy and on the others
  3. Press Reset Counters, then Kill. The FX plays and Died reads 1
  4. Press Restore. The enemy is alive again, Revived still reads 0, and nothing played
  5. Press Clear A Wave, capture again, and watch both sections move together
  6. Press Load A Save With Max 0. The health entry is refused by name; the encounter section still loads
  7. Read Last Result after each action

Step 4 is the one worth watching twice. Everything the death handler did in step 3 is still done — the enemy is upright and undamaged with its death FX having played and no revive having happened.


Failure Behaviour

Failures are reported through RevSaveReport rather than thrown. A load that throws is how a game ends up stuck on its main menu.

Common patterns:

  • No eligible health systems → nothing in the scene has both HealthSystem and StableId → Add a StableId; the capture would otherwise write no health section at all

  • The section fails naming every owner → the ids in the save are not the ids in the scene → Usually the loaded scene is not the one the save was taken in. An object spawned at runtime gets a fresh id every launch unless StableId.AssignId is called with one of your own

  • An entry is refused for an impossible snapshotMax at or below zero, or Current outside 0..Max → No capture produces these, so the file was edited or truncated. An entry carrying no snapshot at all deserialises to exactly this, which is why the two cases behave the same

  • A restore reports a partial → some owners restored and others were refused → That section is no longer a description of anything. Do not carry it into a later save

  • Damage is refused → a rule, an i-frame, a damage lock or authority said no → Not a save concern; the panel reports it so you can tell it apart from a save problem

Note what is not checked: Dead is never validated against Current. Whether an object at zero health is dead belongs to its death rules, which RestoreSnapshot restores nothing of — rejecting that pairing would refuse real saves in order to catch tampered ones.


Behind The Scenes

This panel uses public save and health APIs only:

  • HealthSaveParticipant
  • RevSaveCoordinator.Capture(...) / RevSaveCoordinator.Restore(...)
  • RevSaveReport
  • RevSaveEnvelope / RevSaveSection
  • IRevSaveParticipant
  • StableId / StableIdOwners.Find<T>(...)
  • HealthSystem.TryTakeDamage(...) / TryHeal(...) / Kill()
  • HealthSystem.Died / Revived
  • HealthSnapshot

The participant is a translator, not a store. It adapts Health's existing CaptureSnapshot / RestoreSnapshot pair and widens nothing, which is why it lives in a define-gated integration assembly rather than in the Health package.


Key Takeaway

A save holds three numbers about a character, and a great deal of what makes that character feel alive is not among them.

The restore is deliberately silent, so nothing downstream of death or revival happens when a file is read. That is the right default and it is not a complete answer: after an in-place restore, read IsDead and put the object into the matching state yourself. Reloading the scene sidesteps the whole problem, which is why most games do exactly that.