Files
Christopher Wiebe ce87eb11ad M6.4: Card-grid steps + hybrid origin + clade-restricted backgrounds
Per GODOT_PORTING_GUIDE.md §12, the four "easy" card-grid steps land
together (Species / Calling / Subclass / History), plus three real
features that emerged during testing: cross-step validation gating,
hybrid origin, and clade-restricted background availability.

New step files (Scenes/Steps/):
  StepSpecies.cs    — cards filtered by clade; for hybrids shows two
                      stacked grids (Sire / Dam).
  StepClass.cs      — all classes; class change clears chosen skills
                      and the previously-selected subclass.
  StepSubclass.cs   — subclasses filtered by ClassDef.SubclassIds.
  StepBackground.cs — backgrounds filtered by hybrid + clade rules
                      (see below).

UI/WizardValidation.cs (new):
  Static per-step validators against CharacterDraft. Replaces the
  per-instance Validate() route on the wizard side — Wizard now
  computes the lock state for every step in the flow, not just the
  current one. Mirrors app.jsx's firstIncomplete rule exactly.

  Bug it fixes: previously the wizard checked only the current step's
  validity, so picking a clade let you skip directly to Abilities
  without picking species/calling/etc.

UI/CharacterDraft.cs:
  Phase 6.5 hybrid fields — IsHybrid, SireCladeId, SireSpeciesId,
  DamCladeId, DamSpeciesId, DominantParent. EffectiveCladeId /
  EffectiveSpeciesId resolve to the dominant parent's lineage when
  hybrid; downstream steps don't need to care which path. Helpers
  HasClade(id) and HasAnyCladeOfKind(kind) feed the background
  availability rules.

StepClade.cs:
  Hybrid toggle splits the picker into Sire + Dam grids with a
  Dominant Lineage radio. Validation refuses same-clade Sire+Dam.
  Switched to build-once + mutate-in-place: cards are created once
  during Build(), Refresh just updates Modulate per selection state.
  Tearing down + rebuilding inside the click callback caused
  duplicates because Free() defers when the freed node is mid-signal.

StepBackground.cs:
  Availability rules table — predicates per restricted background id.
  Hybrid-only: passer, hybrid_underground, former_chattel.
  Clade-restricted: warren_runner (Leporidae), pack_raised (Canidae),
  herd_city_born (any prey clade).
  Hybrids match if either parent satisfies the rule.

Other steps (Species/Class/Subclass/Background):
  Refresh dispatched via Callable.From(Refresh).CallDeferred() so the
  rebuild runs after the click handler completes — same Free()-during-
  signal bug as StepClade hit, fixed via deferral instead of mutate-
  in-place because the card lists are dynamic (clade- / class- /
  hybrid-flag-dependent).

Wizard.cs:
  - RebuildStepperStates uses WizardValidation.FirstIncomplete to lock
    every step past the first unsatisfied one.
  - OnStepperClicked checks every step in [0..target-1].
  - UpdateChrome's banner uses WizardValidation for the active step.
  - Scroll preservation moved here (snapshot before step.Refresh
    fires, restore in _Process); StepStats's local copy removed.

Wizard.tscn:
  Scroll node marked unique_name_in_owner so Wizard can grab it.
  PopoverLayer's TraitChip is reused throughout the new step cards.

Aside.cs:
  Hybrid-aware summary — shows "Sire (dominant)" / "Dam" lineage rows
  when IsHybrid; otherwise the existing Clade / Species rows.

Verified end-to-end:
  - Walk Clade → Species → Calling → Subclass → History → Abilities
  - Stepper locks every step past first unsatisfied
  - Hybrid toggle works both directions, dominant changes lineage
  - Hybrid-only and clade-restricted backgrounds appear / disappear
    based on lineage
  - Scroll position preserved across selections
  - Drag-drop still works on Abilities

Closes M6.4. Per guide §12, next is M6.5 — StepSkills (class-driven
choice list with TraitChip per skill).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:24:33 -07:00

93 lines
3.0 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[gd_scene load_steps=5 format=3 uid="uid://wizard6m6v1"]
[ext_resource type="Script" path="res://Scenes/Wizard.cs" id="1_wizard"]
[ext_resource type="Script" path="res://UI/Widgets/CodexStepper.cs" id="2_stepper"]
[ext_resource type="PackedScene" path="res://Scenes/Aside.tscn" id="3_aside"]
[ext_resource type="Script" path="res://Scenes/Widgets/PopoverLayer.cs" id="4_popover"]
[node name="Wizard" type="Control"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("1_wizard")
[node name="Wrap" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
theme_override_constants/margin_left = 36
theme_override_constants/margin_right = 36
theme_override_constants/margin_top = 16
theme_override_constants/margin_bottom = 16
[node name="Layout" type="VBoxContainer" parent="Wrap"]
[node name="Header" type="HBoxContainer" parent="Wrap/Layout"]
theme_override_constants/separation = 24
[node name="TitleCol" type="VBoxContainer" parent="Wrap/Layout/Header"]
size_flags_horizontal = 3
[node name="Title" type="Label" parent="Wrap/Layout/Header/TitleCol"]
text = "THERIAPOLIS · CODEX OF BECOMING"
[node name="FolioLabel" type="Label" parent="Wrap/Layout/Header/TitleCol"]
unique_name_in_owner = true
text = "Folio I of VIII — Clade"
[node name="MetaLabel" type="Label" parent="Wrap/Layout/Header"]
text = "PORT/GODOT · M6"
[node name="Stepper" type="HBoxContainer" parent="Wrap/Layout"]
unique_name_in_owner = true
size_flags_horizontal = 3
script = ExtResource("2_stepper")
[node name="Page" type="HBoxContainer" parent="Wrap/Layout"]
size_flags_vertical = 3
[node name="PageMain" type="MarginContainer" parent="Wrap/Layout/Page"]
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_left = 12
theme_override_constants/margin_right = 28
theme_override_constants/margin_top = 16
theme_override_constants/margin_bottom = 16
[node name="Scroll" type="ScrollContainer" parent="Wrap/Layout/Page/PageMain"]
unique_name_in_owner = true
size_flags_horizontal = 3
size_flags_vertical = 3
horizontal_scroll_mode = 0
[node name="StepHost" type="VBoxContainer" parent="Wrap/Layout/Page/PageMain/Scroll"]
unique_name_in_owner = true
size_flags_horizontal = 3
[node name="Aside" parent="Wrap/Layout/Page" instance=ExtResource("3_aside")]
[node name="NavBar" type="HBoxContainer" parent="Wrap/Layout"]
theme_override_constants/separation = 24
[node name="BackButton" type="Button" parent="Wrap/Layout/NavBar"]
unique_name_in_owner = true
text = "← Back"
[node name="Spacer" type="Control" parent="Wrap/Layout/NavBar"]
size_flags_horizontal = 3
[node name="ValidationLabel" type="Label" parent="Wrap/Layout/NavBar"]
unique_name_in_owner = true
text = ""
[node name="NavProgress" type="Label" parent="Wrap/Layout/NavBar"]
unique_name_in_owner = true
text = "1 / 8"
[node name="NextButton" type="Button" parent="Wrap/Layout/NavBar"]
unique_name_in_owner = true
text = "Next "
[node name="PopoverLayer" type="CanvasLayer" parent="."]
script = ExtResource("4_popover")