ADR: Stat- und Modifier-Pipeline #6
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of #1
Blocked by: #3
Entscheidung plus ADR unter
docs/adr/. Das ist das Herzstück der Meta-Progression — hier laufen Workshop, Cards und die Ultimate zusammen.Die Frage
EffectiveStats-Component?f32für den Slice, oder brauchen Idle-Kurven schon Big-Number-Semantik?Fertig, wenn die ADR die Regel festlegt und ein durchgerechnetes Beispiel enthält: Base-Damage über Workshop-Level und zwei Cards bis zum Endwert.
Entschieden. ADR in
docs/adr/0002-stat-und-modifier-pipeline.md, elf nummerierte Festlegungen.Die Kernentscheidung — schärfer als das, was reinging
Vorgegeben waren drei Akkumulatoren (
flat,points,mult) und vier Anwendungsregeln. Der eigentliche Fund ist, dass die vierte Regel gar kein Sonderfall ist: die Form gehört nicht zum Stat, sondern zum Modifikator. Der Stat trägt nur ein Bit —DirectoderInterval:Direct:(base + flat) × (1 + clamp(points)) × mult— der Wert wächstInterval:(base + flat) × (1 − clamp(points)) ÷ mult— der Wert schrumpftDamit sind es 3 Akkumulatoren × 1 Bit statt drei Regeln plus einem Sonderfall. Kein Pfad, an dem später eine fünfte Regel entsteht.
Der Clamp greift auf die Summe, genau einmal, nach der Summierung — nie pro Quelle. Folge, die ausdrücklich gewollt ist: keine abnehmenden Erträge, sondern eine harte Wand. Drei Quellen à 30 pp Cooldown-Reduktion bei
cap = 0,60ergeben 60 pp, die restlichen 30 pp verfallen ersatzlos. Die ungeclampte Summe bleibt für die Anzeige erhalten, damit der Spieler sieht, dass er gegen die Wand läuft.Drei Präzisierungen über die Vorgaben hinaus
1. Attack Speed war eine Falle. „Attack Speed ×1,25" ist genrenüblich eine Rate — also genau das, was die Intervall-Entscheidung verbietet. Statt einer fünften Regel löst der
Interval-Modus es:multteilt das Intervall, statt es zu multiplizieren. Der Stat heißt deshalbAttackInterval, die Anzeige bleibt „×1,25".2. „Nur Quellen persistieren" hatte ein Loch. Die Regel trägt nicht, wenn eine run-gebundene Quelle als
Resourceliegt — sie überlebt den Run und bringt genau die Bug-Klasse zurück, die der Component-Ansatz verhindern sollte. Die Ablageregel ist deshalb Teil der Entscheidung geworden: Lebensdauer bestimmtResourcevs.Component. Run-gebunden hängt an der Turm-Entity (auchRunUpgradesundEffectiveStats), permanent istResource. Das beantwortet zugleich die Herkunftsfrage — Herkunft steckt im Ablageort, nicht im Datenmodell, und Akkumulatoren werden immer vollständig neu gerechnet, nie subtrahiert.3. Zwei Stats passen in keine Form — als Befund festgehalten, nicht als Fehler.
SlowAuramodifiziert Gegner statt den Turm und ist als Stat ein Fremdkörper, als Fähigkeits-Component dagegen naheliegend. UndMaxHealthist der einzige Stat mit einem veränderlichen Ist-Wert daneben: steigt das Maximum mitten im Run, muss entschieden werden, was mit der aktuellen HP passiert. Beides steht unter „Offene Punkte", damit es nicht zwischen den Tickets verschwindet.Entfernungspfad — der Bug, der sonst in drei Wochen zuschlägt
Ein einziges System
sync_tower_abilitiesist der einzige Schreiber von Fähigkeits-Components und gleicht Soll gegen Ist ab. Keininsert/removean der Ereignisstelle. Dadurch:EquippedCardsändert sich, Abgleich läuft,remove::<T>(). Der einzige Fall, in dem überhaupt entfernt wird.Eine übriggebliebene Component ist damit strukturell unmöglich, nicht nur unwahrscheinlich. Dazu ein
debug_assertam Ende des Abgleichs. Bevy-Lifecycle-Hooks undRemovedComponentsbewusst nicht — ein Abgleich, der das Soll kennt, braucht keine Benachrichtigung.Rechenbeispiel (Zahlen als Platzhalter markiert)
flat+14,40 → 24,40 →mult2,40 × 1,30 = 3,12 → 76,13(0 + 4,00) × 1,60 = 6,40 HP/s. Ohne den flachen Akkumulator wäre es0 × 1,60 = 0— die Karte wirkungslos. Das ist der Grund, warum es drei Akkumulatoren sind und nicht zwei.type Stat = f32mit Revisions-Auslöser bei 10⁶ oder sichtbarer Rundung.Damit sind #7 und #12 frei.
Nachtrag: der Entfernungspfad wird einfacher.
Nach dem Schreiben dieser ADR ist entschieden worden, dass Cards im laufenden Run über einen Pause-Button nur einsehbar, nicht wechselbar sind — das Loadout steht vor dem Run fest.
Damit entfällt Fall 1 des Entfernungspfads (Card-Wechsel im Run) vollständig. Er war der einzige Fall, in dem
sync_tower_abilitiesüberhauptremove::<T>()aufruft. Übrig bleiben:In beiden Fällen entsteht der Turm neu, statt umgebaut zu werden. Im laufenden Run wird nie eine Fähigkeits-Component entfernt. Die Architektur der ADR bleibt richtig und wird strikt einfacher —
sync_tower_abilitiesist weiterhin der einzige Schreiber, aber sein Abgleich läuft praktisch nur noch beim Spawn.Die ADR selbst muss dafür nicht geändert werden: sie beschreibt einen Abgleich Soll-gegen-Ist, und ein Soll, das sich im Run nicht ändert, ist der triviale Sonderfall davon. Wenn Card-Wechsel im Run später doch kommen, ist der Pfad schon da — das ist genau die Eigenschaft, für die der Abgleich gewählt wurde.