Skip to content

test(engine): close the t105 ruling riders — soulbond pin, dangling-id pin, save-compat round trip#5794

Merged
matthewevans merged 1 commit into
mainfrom
ship/t105-rider-pins
Jul 14, 2026
Merged

test(engine): close the t105 ruling riders — soulbond pin, dangling-id pin, save-compat round trip#5794
matthewevans merged 1 commit into
mainfrom
ship/t105-rider-pins

Conversation

@matthewevans

Copy link
Copy Markdown
Member

Three pins demanded by the ruling on the t105 checkpoint. Test-only; no production
change.

  1. purged_token_soulbond_source_must_not_pair_cr_702_95b — soulbond is the largest
    slice of the class (50 of 91 defs, 25 of 65 faces) and MUST stay false for a source
    that has ceased to exist (CR 702.95b: pairing exists only between creatures on the
    battlefield). It stays false structurally today, but structural immunity is one
    refactor away from gone, so pin the behavior rather than the mechanism.

    Soulbond's triggers are synthesized from the keyword during the card-data build, not
    by parse_oracle_text, so they cannot be pulled off an oracle-synthesized object the
    way Dreampod Druid's are (the inline-keyword harness foot-gun). The filter is therefore
    constructed — and then asserted BYTE-IDENTICAL to the filter all 50 soulbond defs
    actually carry in card-data. That premise guard is what keeps a constructed filter from
    becoming a fabricated one: if the real shape drifts, the premise fails loudly instead of
    the pin passing against a filter no card has. Carries a non-vacuity leg (a live unpaired
    creature MUST match the same filter).

  2. lki_attachment_ids_are_identity_only_and_survive_a_purged_aura — the ruling flagged the
    ObjectId inside LKISnapshot::attachments as a possible dangling-reference trap. It is
    not: att.object_id is used in exactly two places in the filter layer, and both are pure
    identity comparisons (== source_id / != source.id, for exclude_source); it is never
    fed to state.objects.get(). The kind and controller the gate reads are stored by
    value. Pinned by destroying the Aura object outright after the host is purged, so the
    stored id dangles — and HasAttachment must still answer TRUE from the snapshot without
    a panic. (AttachmentSnapshot is also already persisted today via
    ZoneChangeRecord::attachments, so this is a reused shape, not a new surface.)

  3. lki_snapshot_attachments_are_save_compatible_both_directionsGameState is
    Serialize/Deserialize and owns lki_cache, so LKISnapshot IS persisted. OLD → NEW: a
    pre-change save with no attachments key must still deserialize and default to the empty
    set (the pre-change fail-closed answer, so a legacy save cannot start fabricating
    attachment matches). NEW → NEW: a snapshot carrying attachments must survive a full
    GameState round trip intact.

…d pin, save-compat round trip

Three pins demanded by the ruling on the t105 checkpoint. Test-only; no production
change.

1. `purged_token_soulbond_source_must_not_pair_cr_702_95b` — soulbond is the largest
   slice of the class (50 of 91 defs, 25 of 65 faces) and MUST stay false for a source
   that has ceased to exist (CR 702.95b: pairing exists only between creatures on the
   battlefield). It stays false structurally today, but structural immunity is one
   refactor away from gone, so pin the behavior rather than the mechanism.

   Soulbond's triggers are synthesized from the keyword during the card-data build, not
   by `parse_oracle_text`, so they cannot be pulled off an oracle-synthesized object the
   way Dreampod Druid's are (the inline-keyword harness foot-gun). The filter is therefore
   constructed — and then asserted BYTE-IDENTICAL to the filter all 50 soulbond defs
   actually carry in card-data. That premise guard is what keeps a constructed filter from
   becoming a fabricated one: if the real shape drifts, the premise fails loudly instead of
   the pin passing against a filter no card has. Carries a non-vacuity leg (a live unpaired
   creature MUST match the same filter).

2. `lki_attachment_ids_are_identity_only_and_survive_a_purged_aura` — the ruling flagged the
   `ObjectId` inside `LKISnapshot::attachments` as a possible dangling-reference trap. It is
   not: `att.object_id` is used in exactly two places in the filter layer, and both are pure
   identity comparisons (`== source_id` / `!= source.id`, for `exclude_source`); it is never
   fed to `state.objects.get()`. The `kind` and `controller` the gate reads are stored by
   value. Pinned by destroying the Aura object outright after the host is purged, so the
   stored id dangles — and `HasAttachment` must still answer TRUE from the snapshot without
   a panic. (`AttachmentSnapshot` is also already persisted today via
   `ZoneChangeRecord::attachments`, so this is a reused shape, not a new surface.)

3. `lki_snapshot_attachments_are_save_compatible_both_directions` — `GameState` is
   Serialize/Deserialize and owns `lki_cache`, so `LKISnapshot` IS persisted. OLD → NEW: a
   pre-change save with no `attachments` key must still deserialize and default to the empty
   set (the pre-change fail-closed answer, so a legacy save cannot start fabricating
   attachment matches). NEW → NEW: a snapshot carrying attachments must survive a full
   `GameState` round trip intact.
@matthewevans
matthewevans enabled auto-merge July 14, 2026 07:39
@matthewevans
matthewevans added this pull request to the merge queue Jul 14, 2026
Merged via the queue into main with commit df872cf Jul 14, 2026
13 checks passed
@matthewevans
matthewevans deleted the ship/t105-rider-pins branch July 14, 2026 08:03
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