docs: update serialization docs and skills for generator v4 (#2588)

## Summary

Brings every serialization-related doc, skill, and the CLAUDE.md rule in line with generator **v4** (adopted in #2586/#2587). No code changes.

**Updated surface, everywhere it was referenced:**
- `[SerializableFieldSaveFlag(order)]` / `[SerializableFieldDefault(order)]` → `[SaveFlag(nameof(Should), nameof(Default))]` on the field (second method optional).
- `[TimerDrift]` + `[DeserializeTimerField(order)]` → `[DeserializeTimer(nameof(Method), wallClock)]` on the field, with the anchored-time semantics spelled out: drifting by default (downtime preserves the remaining delay, idle saves byte-stable), `wallClock: true` for absolute deadlines, restart method invoked **only when a timer was running** (no sentinel), and the timer `MigrateFrom` pattern (`XxxNext`/`XxxDelay`) for wire-format changes.
- New `[SerializableField]` documentation: the real signature (the documented `saveIf` parameter never existed) plus the setter hooks — `allowFieldChange` (`bool Method(ref T value)`: coerce/veto before assignment) and `fieldChanged` (`void Method(T oldValue, T newValue)` after) — with the generated pipeline and the SG3015/SG3018 guardrails.
- `[SerializableProperty]` guidance narrowed to its remaining purpose: custom getters and setter semantics the hooks cannot express.
- `[AnchoredDateTime]` documented alongside `[DeltaDateTime]` (now marked legacy, with the version-bump warning for converting between them).

**Files:** `dev-docs/serialization.md`, `dev-docs/timers.md`, `dev-docs/claude-skills/modernuo-serialization.md`, `dev-docs/claude-skills/modernuo-timers.md`, `dev-docs/runuo-migration-docs/02-serialization.md`, `dev-docs/runuo-migration-docs/03-timers.md`, and a condensed v4 addition to CLAUDE.md rule 9.

**Example refresh:** the skill's `BagOfSending` "custom properties" example was itself converted in #2587 — it is now quoted in its real post-conversion form as the canonical hooks example; the real-examples list points at `BaseWeapon.cs` for custom getters and `BaseLight.cs` for the drifting-timer + `MigrateFrom` pattern.

Verified by grep: zero references to the removed v3 attribute names remain anywhere in `dev-docs/` or `CLAUDE.md`.
This commit is contained in:
Kamron Batman 2026-08-22 18:29:27 -07:00 committed by GitHub
parent b042edcf0b
commit b992c7b955
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 219 additions and 63 deletions

View file

@ -457,16 +457,24 @@ set
```
Without this, changes won't be saved.
Most RunUO custom setters only clamp the value or run side effects after assignment. Those
convert to a plain `[SerializableField]` with the `allowFieldChange`/`fieldChanged` hooks,
which handle the equality check and `MarkDirty()` for you -- reserve `[SerializableProperty]`
for custom getters (see `dev-docs/serialization.md`).
### 3. Field Ordering
The `[SerializableField(N)]` index determines serialization order. Choose a logical order and don't change it after the first save — or increment the version.
### 4. Conditional Serialization
Use `[SerializableFieldSaveFlag]` and `[SerializableFieldDefault]` to skip default values:
Use `[SaveFlag]` on the serializable field to skip default values (the second method is
optional -- omit it and the field keeps its default at load):
```csharp
[SerializableFieldSaveFlag(0)]
[SerializableField(0)]
[SaveFlag(nameof(ShouldSerializeMaxItems), nameof(MaxItemsDefaultValue))]
private int _maxItems;
private bool ShouldSerializeMaxItems() => _maxItems != -1;
[SerializableFieldDefault(0)]
private int MaxItemsDefaultValue() => -1;
```
@ -479,12 +487,15 @@ private List<Mobile> _followers;
```
### 6. DateTime Fields
Use `[DeltaDateTime]` to survive server restarts:
Use `[AnchoredDateTime]` to survive server restarts -- the value is shifted by downtime at
load, so the remaining time is preserved and idle saves stay byte-stable:
```csharp
[DeltaDateTime]
[AnchoredDateTime]
[SerializableField(0)]
private DateTime _expireTime;
```
(`[DeltaDateTime]` is the legacy equivalent; it rewrites bytes on every save. Converting an
existing field between the two changes the wire format and requires a version bump.)
### 7. Keeping Manual Serialization (Rare)
Some edge cases still need manual serialization. If a type has complex conditional logic that can't be expressed with attributes, you can implement `ISerializable` manually. But this is rare — try attributes first.