1use std::collections::HashMap;
20use crate::combat::{Element, CombatStats, ResistanceProfile};
21use crate::entity::AmorphousEntity;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub enum BossType {
30 Mirror,
31 Null,
32 Committee,
33 FibonacciHydra,
34 Eigenstate,
35 Ouroboros,
36 AlgorithmReborn,
37 ChaosWeaver,
38 VoidSerpent,
39 PrimeFactorial,
40}
41
42impl BossType {
43 pub fn all() -> &'static [BossType] {
45 &[
46 BossType::Mirror,
47 BossType::Null,
48 BossType::Committee,
49 BossType::FibonacciHydra,
50 BossType::Eigenstate,
51 BossType::Ouroboros,
52 BossType::AlgorithmReborn,
53 BossType::ChaosWeaver,
54 BossType::VoidSerpent,
55 BossType::PrimeFactorial,
56 ]
57 }
58
59 pub fn name(self) -> &'static str {
61 match self {
62 BossType::Mirror => "The Mirror",
63 BossType::Null => "The Null",
64 BossType::Committee => "The Committee",
65 BossType::FibonacciHydra => "Fibonacci Hydra",
66 BossType::Eigenstate => "The Eigenstate",
67 BossType::Ouroboros => "Ouroboros",
68 BossType::AlgorithmReborn => "Algorithm Reborn",
69 BossType::ChaosWeaver => "Chaos Weaver",
70 BossType::VoidSerpent => "Void Serpent",
71 BossType::PrimeFactorial => "Prime Factorial",
72 }
73 }
74
75 pub fn title(self) -> &'static str {
77 match self {
78 BossType::Mirror => "Reflection of Self",
79 BossType::Null => "The Eraser of Meaning",
80 BossType::Committee => "Democracy of Violence",
81 BossType::FibonacciHydra => "The Golden Recursion",
82 BossType::Eigenstate => "Collapsed Possibility",
83 BossType::Ouroboros => "The Serpent That Devours",
84 BossType::AlgorithmReborn => "Final Proof",
85 BossType::ChaosWeaver => "Unraveler of Rules",
86 BossType::VoidSerpent => "Consumer of Arenas",
87 BossType::PrimeFactorial => "The Indivisible Explosion",
88 }
89 }
90
91 pub fn tier(self) -> u32 {
93 match self {
94 BossType::Mirror => 1,
95 BossType::Null => 2,
96 BossType::Committee => 2,
97 BossType::FibonacciHydra => 3,
98 BossType::Eigenstate => 3,
99 BossType::Ouroboros => 3,
100 BossType::ChaosWeaver => 4,
101 BossType::VoidSerpent => 4,
102 BossType::PrimeFactorial => 4,
103 BossType::AlgorithmReborn => 5,
104 }
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum MusicType {
115 Ominous,
116 Frenetic,
117 Orchestral,
118 Glitch,
119 Silence,
120 Reversed,
121 Algorithmic,
122 Chaotic,
123 Crescendo,
124 MinimalDrone,
125}
126
127#[derive(Debug, Clone, PartialEq)]
129pub enum ArenaMod {
130 ShrinkEdges { rate_per_turn: u32 },
132 HazardTiles { element: Element, count: u32 },
134 DarkenVision { radius_reduction: f32 },
136 InvertControls,
138 SlipperyFloor { friction: f32 },
140 TeleportTraps { count: u32 },
142 None,
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum PhaseTransition {
153 GlyphReorganize,
155 Dissolve,
157 Split,
159 Merge,
161 Teleport,
163 PowerUp,
165}
166
167#[derive(Debug, Clone, PartialEq)]
169pub enum SpecialAbility {
170 MirrorCopy { depth: usize },
172 Erase(EraseTarget),
174 Summon { count: u32, hp_each: f32 },
176 AoeBlast { radius: f32, damage: f32, element: Element },
178 SelfHeal { amount: f32 },
180 LockAbility,
182 QuantumCollapse,
184 ReverseSemantic,
186 CounterPredict,
188 MarkovPredict,
190 RuleRandomize,
192 ConsumeArena { columns: u32 },
194 FactorialStrike { sequence_index: u32 },
196 ArithmeticPuzzle { target_factors: Vec<u32> },
198 FibonacciSplit,
200 CommitteeVote,
202 Entangle,
204 None,
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210pub enum EraseTarget {
211 PlayerBuffs,
212 HpBar,
213 MiniMap,
214 AbilitySlot,
215 InventorySlot,
216 DamageNumbers,
217 BossHpBar,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub enum BehaviorPattern {
223 Standard,
225 Aggressive,
227 Defensive,
229 Erratic,
231 Calculated,
233 Passive,
235 Berserk,
237}
238
239#[derive(Debug, Clone, PartialEq)]
241pub struct BossPhase {
242 pub phase_number: u32,
244 pub hp_threshold_pct: f32,
247 pub behavior_pattern: BehaviorPattern,
249 pub speed_mult: f32,
251 pub damage_mult: f32,
253 pub special_ability: SpecialAbility,
255 pub transition_animation: PhaseTransition,
257 pub dialogue_on_enter: String,
259}
260
261impl BossPhase {
262 pub fn new(phase_number: u32, hp_threshold_pct: f32) -> Self {
263 Self {
264 phase_number,
265 hp_threshold_pct,
266 behavior_pattern: BehaviorPattern::Standard,
267 speed_mult: 1.0,
268 damage_mult: 1.0,
269 special_ability: SpecialAbility::None,
270 transition_animation: PhaseTransition::PowerUp,
271 dialogue_on_enter: String::new(),
272 }
273 }
274
275 pub fn with_behavior(mut self, pattern: BehaviorPattern) -> Self {
276 self.behavior_pattern = pattern;
277 self
278 }
279
280 pub fn with_speed(mut self, mult: f32) -> Self {
281 self.speed_mult = mult;
282 self
283 }
284
285 pub fn with_damage(mut self, mult: f32) -> Self {
286 self.damage_mult = mult;
287 self
288 }
289
290 pub fn with_ability(mut self, ability: SpecialAbility) -> Self {
291 self.special_ability = ability;
292 self
293 }
294
295 pub fn with_transition(mut self, anim: PhaseTransition) -> Self {
296 self.transition_animation = anim;
297 self
298 }
299
300 pub fn with_dialogue(mut self, dialogue: impl Into<String>) -> Self {
301 self.dialogue_on_enter = dialogue.into();
302 self
303 }
304}
305
306#[derive(Debug, Clone)]
312pub struct BossLootEntry {
313 pub item_name: String,
314 pub drop_chance: f32,
315 pub min_quantity: u32,
316 pub max_quantity: u32,
317}
318
319#[derive(Debug, Clone)]
321pub struct BossProfile {
322 pub boss_type: BossType,
323 pub name: String,
324 pub title: String,
325 pub hp_base: f32,
326 pub damage_base: f32,
327 pub tier: u32,
328 pub phases: Vec<BossPhase>,
329 pub special_mechanics: Vec<String>,
330 pub loot_table: Vec<BossLootEntry>,
331 pub music_type: MusicType,
332 pub arena_mods: Vec<ArenaMod>,
333 pub resistance: ResistanceProfile,
334}
335
336impl BossProfile {
337 pub fn scaled_hp(&self, floor: u32) -> f32 {
339 self.hp_base * (1.0 + 0.15 * floor as f32)
340 }
341
342 pub fn scaled_damage(&self, floor: u32) -> f32 {
343 self.damage_base * (1.0 + 0.10 * floor as f32)
344 }
345}
346
347#[derive(Debug, Clone)]
353pub struct BossPhaseController {
354 phases: Vec<BossPhase>,
356 current_phase_idx: usize,
358 transitioning: bool,
360 transition_timer: f32,
362 transition_duration: f32,
364}
365
366impl BossPhaseController {
367 pub fn new(mut phases: Vec<BossPhase>) -> Self {
368 phases.sort_by(|a, b| b.hp_threshold_pct.partial_cmp(&a.hp_threshold_pct).unwrap());
370 Self {
371 phases,
372 current_phase_idx: 0,
373 transitioning: false,
374 transition_timer: 0.0,
375 transition_duration: 1.5,
376 }
377 }
378
379 pub fn current_phase(&self) -> Option<&BossPhase> {
381 self.phases.get(self.current_phase_idx)
382 }
383
384 pub fn current_phase_number(&self) -> u32 {
386 self.current_phase()
387 .map(|p| p.phase_number)
388 .unwrap_or(1)
389 }
390
391 pub fn check_transition(&mut self, hp_fraction: f32) -> Option<&BossPhase> {
394 if self.transitioning {
395 return None;
396 }
397
398 let mut target_idx = self.current_phase_idx;
400 for (i, phase) in self.phases.iter().enumerate() {
401 if i > self.current_phase_idx && hp_fraction <= phase.hp_threshold_pct {
402 target_idx = i;
403 }
404 }
405
406 if target_idx != self.current_phase_idx {
407 self.current_phase_idx = target_idx;
408 self.transitioning = true;
409 self.transition_timer = 0.0;
410 return self.phases.get(self.current_phase_idx);
411 }
412
413 None
414 }
415
416 pub fn update_transition(&mut self, dt: f32) -> bool {
418 if !self.transitioning {
419 return false;
420 }
421 self.transition_timer += dt;
422 if self.transition_timer >= self.transition_duration {
423 self.transitioning = false;
424 return true;
425 }
426 false
427 }
428
429 pub fn is_transitioning(&self) -> bool {
431 self.transitioning
432 }
433
434 pub fn transition_progress(&self) -> f32 {
436 if !self.transitioning {
437 return 0.0;
438 }
439 (self.transition_timer / self.transition_duration).clamp(0.0, 1.0)
440 }
441
442 pub fn phase_count(&self) -> usize {
444 self.phases.len()
445 }
446
447 pub fn speed_mult(&self) -> f32 {
449 self.current_phase().map(|p| p.speed_mult).unwrap_or(1.0)
450 }
451
452 pub fn damage_mult(&self) -> f32 {
454 self.current_phase().map(|p| p.damage_mult).unwrap_or(1.0)
455 }
456}
457
458#[derive(Debug, Clone, PartialEq, Eq, Hash)]
464pub enum PlayerActionType {
465 Attack,
466 Defend,
467 Heal,
468 UseAbility(u32),
469 UseItem,
470 Move,
471 Wait,
472}
473
474#[derive(Debug, Clone)]
476pub struct RecordedAction {
477 pub action_type: PlayerActionType,
478 pub turn: u32,
479 pub damage_dealt: f32,
480 pub element: Option<Element>,
481}
482
483#[derive(Debug, Clone)]
494pub struct MirrorBossState {
495 pub mirror_buffer: Vec<RecordedAction>,
497 pub buffer_depth: usize,
499 pub copy_delay: u32,
501 pub copying_stats: bool,
503 pub simultaneous: bool,
505 pub copied_attack: f32,
507 pub copied_defense: f32,
508}
509
510impl MirrorBossState {
511 pub fn new() -> Self {
512 Self {
513 mirror_buffer: Vec::new(),
514 buffer_depth: 3,
515 copy_delay: 1,
516 copying_stats: false,
517 simultaneous: false,
518 copied_attack: 0.0,
519 copied_defense: 0.0,
520 }
521 }
522
523 pub fn record_action(&mut self, action: RecordedAction) {
525 self.mirror_buffer.push(action);
526 if self.mirror_buffer.len() > self.buffer_depth {
527 self.mirror_buffer.remove(0);
528 }
529 }
530
531 pub fn get_mirrored_action(&self, current_turn: u32) -> Option<&RecordedAction> {
533 self.mirror_buffer
534 .iter()
535 .find(|a| a.turn + self.copy_delay == current_turn)
536 }
537
538 pub fn copy_stats(&mut self, player_stats: &CombatStats) {
540 self.copied_attack = player_stats.attack;
541 self.copied_defense = player_stats.armor;
542 self.copying_stats = true;
543 }
544
545 pub fn enter_phase2(&mut self) {
547 self.copying_stats = true;
548 }
549
550 pub fn enter_phase3(&mut self) {
552 self.simultaneous = true;
553 }
554}
555
556impl Default for MirrorBossState {
557 fn default() -> Self { Self::new() }
558}
559
560#[derive(Debug, Clone)]
568pub struct NullBossState {
569 pub erased_ui: Vec<EraseTarget>,
571 pub locked_abilities: Vec<u32>,
573 pub max_locked: usize,
575 pub buffs_erased: u32,
577 pub rng_state: u64,
579}
580
581impl NullBossState {
582 pub fn new() -> Self {
583 Self {
584 erased_ui: Vec::new(),
585 locked_abilities: Vec::new(),
586 max_locked: 3,
587 buffs_erased: 0,
588 rng_state: 0xDEAD_BEEF_CAFE_1234,
589 }
590 }
591
592 fn next_rng(&mut self) -> u64 {
594 self.rng_state ^= self.rng_state << 13;
595 self.rng_state ^= self.rng_state >> 7;
596 self.rng_state ^= self.rng_state << 17;
597 self.rng_state
598 }
599
600 pub fn erase_ui_element(&mut self) -> EraseTarget {
602 let targets = [
603 EraseTarget::HpBar,
604 EraseTarget::MiniMap,
605 EraseTarget::DamageNumbers,
606 ];
607 let idx = (self.next_rng() as usize) % targets.len();
608 let target = targets[idx];
609 if !self.erased_ui.contains(&target) {
610 self.erased_ui.push(target);
611 }
612 target
613 }
614
615 pub fn lock_random_ability(&mut self, max_slots: u32) -> Option<u32> {
617 if self.locked_abilities.len() >= self.max_locked || max_slots == 0 {
618 return None;
619 }
620 let slot = (self.next_rng() as u32) % max_slots;
621 if !self.locked_abilities.contains(&slot) {
622 self.locked_abilities.push(slot);
623 Some(slot)
624 } else {
625 None
626 }
627 }
628
629 pub fn erase_buffs(&mut self, active_buff_count: u32) -> u32 {
631 let to_erase = active_buff_count.min(2); self.buffs_erased += to_erase;
633 to_erase
634 }
635
636 pub fn restore_all(&mut self) -> Vec<EraseTarget> {
638 let restored = self.erased_ui.clone();
639 self.erased_ui.clear();
640 self.locked_abilities.clear();
641 restored
642 }
643}
644
645impl Default for NullBossState {
646 fn default() -> Self { Self::new() }
647}
648
649#[derive(Debug, Clone, Copy, PartialEq, Eq)]
653pub enum JudgePersonality {
654 Aggressive,
655 Defensive,
656 Random,
657 Strategic,
658 Chaotic,
659}
660
661#[derive(Debug, Clone)]
663pub struct Judge {
664 pub id: u32,
665 pub personality: JudgePersonality,
666 pub hp: f32,
667 pub max_hp: f32,
668 pub alive: bool,
669 pub ghost: bool,
671 pub voting: bool,
673 pub current_vote: Option<CommitteeAction>,
675}
676
677#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
679pub enum CommitteeAction {
680 Attack,
681 Defend,
682 HeavyStrike,
683 Heal,
684 Buff,
685 Summon,
686 SpecialAttack,
687}
688
689impl Judge {
690 pub fn new(id: u32, personality: JudgePersonality, hp: f32) -> Self {
691 Self {
692 id,
693 personality,
694 hp,
695 max_hp: hp,
696 alive: true,
697 ghost: false,
698 voting: false,
699 current_vote: None,
700 }
701 }
702
703 pub fn cast_vote(&mut self, boss_hp_frac: f32, rng_val: u64) -> CommitteeAction {
705 let vote = match self.personality {
706 JudgePersonality::Aggressive => {
707 if boss_hp_frac < 0.3 {
708 CommitteeAction::HeavyStrike
709 } else {
710 CommitteeAction::Attack
711 }
712 }
713 JudgePersonality::Defensive => {
714 if boss_hp_frac < 0.5 {
715 CommitteeAction::Heal
716 } else {
717 CommitteeAction::Defend
718 }
719 }
720 JudgePersonality::Random => {
721 let actions = [
722 CommitteeAction::Attack,
723 CommitteeAction::Defend,
724 CommitteeAction::HeavyStrike,
725 CommitteeAction::Heal,
726 CommitteeAction::Buff,
727 CommitteeAction::Summon,
728 CommitteeAction::SpecialAttack,
729 ];
730 actions[(rng_val as usize) % actions.len()]
731 }
732 JudgePersonality::Strategic => {
733 if boss_hp_frac < 0.3 {
734 CommitteeAction::Heal
735 } else if boss_hp_frac < 0.6 {
736 CommitteeAction::Buff
737 } else {
738 CommitteeAction::SpecialAttack
739 }
740 }
741 JudgePersonality::Chaotic => {
742 if rng_val % 2 == 0 {
744 CommitteeAction::HeavyStrike
745 } else {
746 CommitteeAction::Summon
747 }
748 }
749 };
750 self.current_vote = Some(vote);
751 self.voting = true;
752 vote
753 }
754
755 pub fn take_damage(&mut self, amount: f32) -> bool {
757 if !self.alive {
758 return false;
759 }
760 self.hp = (self.hp - amount).max(0.0);
761 if self.hp <= 0.0 {
762 self.alive = false;
763 true
764 } else {
765 false
766 }
767 }
768
769 pub fn become_ghost(&mut self) {
771 self.ghost = true;
772 }
773}
774
775#[derive(Debug, Clone)]
777pub struct CommitteeBossState {
778 pub judges: Vec<Judge>,
779 pub ghost_voting: bool,
781 pub merged: bool,
783 pub rng_state: u64,
785}
786
787impl CommitteeBossState {
788 pub fn new() -> Self {
789 let judges = vec![
790 Judge::new(0, JudgePersonality::Aggressive, 200.0),
791 Judge::new(1, JudgePersonality::Defensive, 200.0),
792 Judge::new(2, JudgePersonality::Random, 200.0),
793 Judge::new(3, JudgePersonality::Strategic, 200.0),
794 Judge::new(4, JudgePersonality::Chaotic, 200.0),
795 ];
796 Self {
797 judges,
798 ghost_voting: false,
799 merged: false,
800 rng_state: 0xC0FF_EE42,
801 }
802 }
803
804 fn next_rng(&mut self) -> u64 {
805 self.rng_state ^= self.rng_state << 13;
806 self.rng_state ^= self.rng_state >> 7;
807 self.rng_state ^= self.rng_state << 17;
808 self.rng_state
809 }
810
811 pub fn conduct_vote(&mut self, boss_hp_frac: f32) -> CommitteeAction {
814 let mut tallies: HashMap<CommitteeAction, u32> = HashMap::new();
815
816 for judge in &mut self.judges {
817 let can_vote = judge.alive || (judge.ghost && self.ghost_voting);
818 if can_vote {
819 let rng_val = self.rng_state;
820 self.rng_state ^= self.rng_state << 13;
821 self.rng_state ^= self.rng_state >> 7;
822 self.rng_state ^= self.rng_state << 17;
823 let vote = judge.cast_vote(boss_hp_frac, rng_val);
824 *tallies.entry(vote).or_insert(0) += 1;
825 }
826 }
827
828 tallies
830 .into_iter()
831 .max_by_key(|&(_, count)| count)
832 .map(|(action, _)| action)
833 .unwrap_or(CommitteeAction::Attack)
834 }
835
836 pub fn alive_count(&self) -> usize {
838 self.judges.iter().filter(|j| j.alive).count()
839 }
840
841 pub fn enable_ghost_voting(&mut self) {
843 self.ghost_voting = true;
844 for judge in &mut self.judges {
845 if !judge.alive {
846 judge.become_ghost();
847 }
848 }
849 }
850
851 pub fn merge_judges(&mut self) -> f32 {
853 let combined_hp: f32 = self.judges.iter().filter(|j| j.alive).map(|j| j.hp).sum();
854 self.merged = true;
855 combined_hp
856 }
857}
858
859impl Default for CommitteeBossState {
860 fn default() -> Self { Self::new() }
861}
862
863#[derive(Debug, Clone)]
867pub struct HydraHead {
868 pub id: u32,
869 pub hp: f32,
870 pub max_hp: f32,
871 pub depth: u32,
872 pub alive: bool,
873 pub parent_id: Option<u32>,
874}
875
876impl HydraHead {
877 pub fn new(id: u32, hp: f32, depth: u32, parent_id: Option<u32>) -> Self {
878 Self {
879 id,
880 hp,
881 max_hp: hp,
882 depth,
883 alive: true,
884 parent_id,
885 }
886 }
887
888 pub fn take_damage(&mut self, amount: f32) -> bool {
889 self.hp = (self.hp - amount).max(0.0);
890 if self.hp <= 0.0 {
891 self.alive = false;
892 true
893 } else {
894 false
895 }
896 }
897}
898
899#[derive(Debug, Clone)]
903pub struct FibonacciHydraState {
904 pub heads: Vec<HydraHead>,
905 pub max_depth: u32,
907 pub next_id: u32,
909 pub split_hp_ratio: f32,
911 pub total_damage_pool: f32,
913}
914
915impl FibonacciHydraState {
916 pub fn new(base_hp: f32) -> Self {
917 Self {
918 heads: vec![HydraHead::new(0, base_hp, 0, None)],
919 max_depth: 5,
920 next_id: 1,
921 split_hp_ratio: 0.618,
922 total_damage_pool: 0.0,
923 }
924 }
925
926 pub fn alive_count(&self) -> usize {
928 self.heads.iter().filter(|h| h.alive).count()
929 }
930
931 pub fn try_split(&mut self, dead_head_id: u32) -> Option<(u32, u32)> {
933 let (depth, parent_hp) = {
934 let head = self.heads.iter().find(|h| h.id == dead_head_id)?;
935 if head.alive || head.depth >= self.max_depth {
936 return None;
937 }
938 (head.depth, head.max_hp)
939 };
940
941 let child_hp = parent_hp * self.split_hp_ratio;
942 let id_a = self.next_id;
943 let id_b = self.next_id + 1;
944 self.next_id += 2;
945
946 self.heads.push(HydraHead::new(id_a, child_hp, depth + 1, Some(dead_head_id)));
947 self.heads.push(HydraHead::new(id_b, child_hp, depth + 1, Some(dead_head_id)));
948
949 if let Some(head) = self.heads.iter_mut().find(|h| h.id == dead_head_id) {
951 head.depth = self.max_depth;
952 }
953
954 Some((id_a, id_b))
955 }
956
957 pub fn damage_head(&mut self, head_id: u32, amount: f32) -> bool {
959 self.total_damage_pool += amount;
960 if let Some(head) = self.heads.iter_mut().find(|h| h.id == head_id) {
961 head.take_damage(amount)
962 } else {
963 false
964 }
965 }
966
967 pub fn is_defeated(&self) -> bool {
969 let alive = self.alive_count();
970 if alive > 0 {
971 return false;
972 }
973 !self.heads.iter().any(|h| !h.alive && h.depth < self.max_depth)
975 }
976
977 pub fn max_possible_heads(&self) -> u32 {
979 1 << self.max_depth }
981}
982
983impl Default for FibonacciHydraState {
984 fn default() -> Self { Self::new(1000.0) }
985}
986
987#[derive(Debug, Clone, Copy, PartialEq, Eq)]
991pub enum QuantumForm {
992 Attack,
993 Defense,
994 Evasion,
996}
997
998#[derive(Debug, Clone)]
1002pub struct EigenstateBossState {
1003 pub forms: Vec<QuantumForm>,
1005 pub collapsed_form: Option<QuantumForm>,
1007 pub observed: bool,
1009 pub entangled: bool,
1011 pub entangled_hp: f32,
1013 pub turns_unobserved: u32,
1015 pub rng_state: u64,
1017}
1018
1019impl EigenstateBossState {
1020 pub fn new() -> Self {
1021 Self {
1022 forms: vec![QuantumForm::Attack, QuantumForm::Defense],
1023 collapsed_form: None,
1024 observed: false,
1025 entangled: false,
1026 entangled_hp: 0.0,
1027 turns_unobserved: 0,
1028 rng_state: 0xABCD_0042,
1029 }
1030 }
1031
1032 pub fn observe(&mut self) -> QuantumForm {
1034 self.observed = true;
1035 self.turns_unobserved = 0;
1036 self.rng_state ^= self.rng_state << 13;
1037 self.rng_state ^= self.rng_state >> 7;
1038 self.rng_state ^= self.rng_state << 17;
1039 let idx = (self.rng_state as usize) % self.forms.len();
1040 let form = self.forms[idx];
1041 self.collapsed_form = Some(form);
1042 form
1043 }
1044
1045 pub fn unobserve(&mut self) {
1047 self.observed = false;
1048 self.collapsed_form = None;
1049 self.turns_unobserved += 1;
1050 }
1051
1052 pub fn add_evasion_form(&mut self) {
1054 if !self.forms.contains(&QuantumForm::Evasion) {
1055 self.forms.push(QuantumForm::Evasion);
1056 }
1057 }
1058
1059 pub fn entangle(&mut self, boss_hp: f32) -> f32 {
1061 self.entangled = true;
1062 self.entangled_hp = boss_hp;
1063 self.entangled_hp
1064 }
1065
1066 pub fn mirror_damage(&mut self, amount: f32) -> f32 {
1068 if self.entangled {
1069 self.entangled_hp = (self.entangled_hp - amount).max(0.0);
1070 amount } else {
1072 0.0
1073 }
1074 }
1075
1076 pub fn in_superposition(&self) -> bool {
1078 self.collapsed_form.is_none()
1079 }
1080
1081 pub fn damage_mult(&self) -> f32 {
1083 match self.collapsed_form {
1084 Some(QuantumForm::Attack) => 1.8,
1085 Some(QuantumForm::Defense) => 0.5,
1086 Some(QuantumForm::Evasion) => 1.0,
1087 None => 1.2, }
1089 }
1090
1091 pub fn defense_mult(&self) -> f32 {
1093 match self.collapsed_form {
1094 Some(QuantumForm::Attack) => 0.5,
1095 Some(QuantumForm::Defense) => 2.0,
1096 Some(QuantumForm::Evasion) => 0.8,
1097 None => 1.0,
1098 }
1099 }
1100}
1101
1102impl Default for EigenstateBossState {
1103 fn default() -> Self { Self::new() }
1104}
1105
1106#[derive(Debug, Clone)]
1113pub struct OuroborosBossState {
1114 pub reversed: bool,
1116 pub normal_turns_remaining: u32,
1118 pub swap_interval: u32,
1120 pub turns_since_swap: u32,
1122 pub total_self_heal: f32,
1124 pub total_player_heal: f32,
1126 pub phase2_active: bool,
1128 pub rng_state: u64,
1130}
1131
1132impl OuroborosBossState {
1133 pub fn new() -> Self {
1134 Self {
1135 reversed: true, normal_turns_remaining: 0,
1137 swap_interval: 5,
1138 turns_since_swap: 0,
1139 total_self_heal: 0.0,
1140 total_player_heal: 0.0,
1141 phase2_active: false,
1142 rng_state: 0xB0B0_0007,
1143 }
1144 }
1145
1146 pub fn process_damage(&mut self, raw_damage: f32, is_boss_attacking: bool) -> (f32, f32) {
1149 if self.reversed {
1150 let heal = raw_damage * 0.5;
1152 if is_boss_attacking {
1153 self.total_self_heal += heal;
1154 } else {
1155 self.total_player_heal += heal;
1156 }
1157 (raw_damage, heal)
1158 } else {
1159 (raw_damage, 0.0)
1161 }
1162 }
1163
1164 pub fn process_heal(&self, raw_heal: f32) -> f32 {
1167 if self.reversed {
1168 -raw_heal } else {
1170 raw_heal
1171 }
1172 }
1173
1174 pub fn advance_turn(&mut self) -> bool {
1176 if self.normal_turns_remaining > 0 {
1177 self.normal_turns_remaining -= 1;
1178 if self.normal_turns_remaining == 0 {
1179 self.reversed = true;
1180 return true; }
1182 }
1183
1184 if self.phase2_active {
1185 self.turns_since_swap += 1;
1186 self.rng_state ^= self.rng_state << 13;
1187 self.rng_state ^= self.rng_state >> 7;
1188 self.rng_state ^= self.rng_state << 17;
1189
1190 if self.turns_since_swap >= self.swap_interval
1192 && (self.rng_state % 3 == 0)
1193 {
1194 self.reversed = false;
1195 self.normal_turns_remaining = 2;
1196 self.turns_since_swap = 0;
1197 return true; }
1199 }
1200 false
1201 }
1202
1203 pub fn enter_phase2(&mut self) {
1205 self.phase2_active = true;
1206 }
1207}
1208
1209impl Default for OuroborosBossState {
1210 fn default() -> Self { Self::new() }
1211}
1212
1213#[derive(Debug, Clone)]
1221pub struct AlgorithmRebornState {
1222 pub action_frequency: HashMap<PlayerActionType, u32>,
1224 pub markov_transitions: HashMap<(PlayerActionType, PlayerActionType), u32>,
1227 pub last_action: Option<PlayerActionType>,
1229 pub predicted_action: Option<PlayerActionType>,
1231 pub counter_mode: bool,
1233 pub markov_mode: bool,
1235 pub degradation: f32,
1237 pub hidden_hp_frac: f32,
1239}
1240
1241impl AlgorithmRebornState {
1242 pub fn new() -> Self {
1243 Self {
1244 action_frequency: HashMap::new(),
1245 markov_transitions: HashMap::new(),
1246 last_action: None,
1247 predicted_action: None,
1248 counter_mode: false,
1249 markov_mode: false,
1250 degradation: 0.0,
1251 hidden_hp_frac: 1.0,
1252 }
1253 }
1254
1255 pub fn record_action(&mut self, action: PlayerActionType) {
1257 *self.action_frequency.entry(action.clone()).or_insert(0) += 1;
1258
1259 if let Some(ref last) = self.last_action {
1260 *self
1261 .markov_transitions
1262 .entry((last.clone(), action.clone()))
1263 .or_insert(0) += 1;
1264 }
1265 self.last_action = Some(action);
1266 }
1267
1268 pub fn most_used_action(&self) -> Option<PlayerActionType> {
1270 self.action_frequency
1271 .iter()
1272 .max_by_key(|&(_, count)| count)
1273 .map(|(action, _)| action.clone())
1274 }
1275
1276 pub fn counter_for(action: &PlayerActionType) -> PlayerActionType {
1278 match action {
1279 PlayerActionType::Attack => PlayerActionType::Defend,
1280 PlayerActionType::Defend => PlayerActionType::UseAbility(0), PlayerActionType::Heal => PlayerActionType::Attack, PlayerActionType::UseAbility(_) => PlayerActionType::Defend,
1283 PlayerActionType::UseItem => PlayerActionType::Attack,
1284 PlayerActionType::Move => PlayerActionType::UseAbility(1), PlayerActionType::Wait => PlayerActionType::Attack,
1286 }
1287 }
1288
1289 pub fn predict_next(&mut self) -> Option<PlayerActionType> {
1291 let last = self.last_action.as_ref()?;
1292 let mut best_action = None;
1293 let mut best_count = 0u32;
1294
1295 for ((from, to), &count) in &self.markov_transitions {
1296 if from == last && count > best_count {
1297 best_count = count;
1298 best_action = Some(to.clone());
1299 }
1300 }
1301
1302 self.predicted_action = best_action.clone();
1303 best_action
1304 }
1305
1306 pub fn update_degradation(&mut self, hp_frac: f32) {
1308 self.hidden_hp_frac = hp_frac;
1309 self.degradation = 1.0 - hp_frac;
1310 }
1311
1312 pub fn enter_phase2(&mut self) {
1314 self.counter_mode = true;
1315 }
1316
1317 pub fn enter_phase3(&mut self) {
1319 self.markov_mode = true;
1320 }
1321}
1322
1323impl Default for AlgorithmRebornState {
1324 fn default() -> Self { Self::new() }
1325}
1326
1327#[derive(Debug, Clone)]
1333pub struct ChaosWeaverState {
1334 pub weakness_overrides: HashMap<Element, Element>,
1336 pub slot_swaps: HashMap<u32, u32>,
1338 pub randomize_damage_display: bool,
1340 pub randomize_tiles: bool,
1342 pub chaos_count: u32,
1344 pub rng_state: u64,
1346}
1347
1348impl ChaosWeaverState {
1349 pub fn new() -> Self {
1350 Self {
1351 weakness_overrides: HashMap::new(),
1352 slot_swaps: HashMap::new(),
1353 randomize_damage_display: false,
1354 randomize_tiles: false,
1355 chaos_count: 0,
1356 rng_state: 0xC4A0_5555,
1357 }
1358 }
1359
1360 fn next_rng(&mut self) -> u64 {
1361 self.rng_state ^= self.rng_state << 13;
1362 self.rng_state ^= self.rng_state >> 7;
1363 self.rng_state ^= self.rng_state << 17;
1364 self.rng_state
1365 }
1366
1367 pub fn scramble_weaknesses(&mut self) {
1369 let elements = [
1370 Element::Physical, Element::Fire, Element::Ice, Element::Lightning,
1371 Element::Void, Element::Entropy, Element::Gravity, Element::Radiant,
1372 Element::Shadow, Element::Temporal,
1373 ];
1374
1375 self.weakness_overrides.clear();
1376 for &elem in &elements {
1377 let rng = self.next_rng();
1378 let target_idx = (rng as usize) % elements.len();
1379 self.weakness_overrides.insert(elem, elements[target_idx]);
1380 }
1381 self.chaos_count += 1;
1382 }
1383
1384 pub fn swap_ability_slots(&mut self, max_slots: u32) -> (u32, u32) {
1386 let a = (self.next_rng() as u32) % max_slots;
1387 let mut b = (self.next_rng() as u32) % max_slots;
1388 if b == a {
1389 b = (a + 1) % max_slots;
1390 }
1391 self.slot_swaps.insert(a, b);
1392 self.slot_swaps.insert(b, a);
1393 self.chaos_count += 1;
1394 (a, b)
1395 }
1396
1397 pub fn fake_damage_number(&mut self, _actual: f32) -> f32 {
1399 if self.randomize_damage_display {
1400 let rng = self.next_rng();
1401 (rng % 9999) as f32 + 1.0
1402 } else {
1403 _actual
1404 }
1405 }
1406
1407 pub fn enable_damage_randomization(&mut self) {
1409 self.randomize_damage_display = true;
1410 self.chaos_count += 1;
1411 }
1412
1413 pub fn enable_tile_randomization(&mut self) {
1415 self.randomize_tiles = true;
1416 self.chaos_count += 1;
1417 }
1418
1419 pub fn effective_weakness(&self, element: Element) -> Element {
1421 self.weakness_overrides
1422 .get(&element)
1423 .copied()
1424 .unwrap_or(element)
1425 }
1426}
1427
1428impl Default for ChaosWeaverState {
1429 fn default() -> Self { Self::new() }
1430}
1431
1432#[derive(Debug, Clone)]
1439pub struct VoidSerpentState {
1440 pub arena_width: u32,
1442 pub arena_height: u32,
1444 pub original_width: u32,
1446 pub original_height: u32,
1448 pub consumed_north: u32,
1450 pub consumed_south: u32,
1451 pub consumed_east: u32,
1452 pub consumed_west: u32,
1453 pub void_projectiles: bool,
1455 pub projectiles_per_turn: u32,
1457 pub serpent_emerged: bool,
1459 pub serpent_attack_damage: f32,
1461 pub turn_count: u32,
1463}
1464
1465impl VoidSerpentState {
1466 pub fn new(arena_w: u32, arena_h: u32) -> Self {
1467 Self {
1468 arena_width: arena_w,
1469 arena_height: arena_h,
1470 original_width: arena_w,
1471 original_height: arena_h,
1472 consumed_north: 0,
1473 consumed_south: 0,
1474 consumed_east: 0,
1475 consumed_west: 0,
1476 void_projectiles: false,
1477 projectiles_per_turn: 2,
1478 serpent_emerged: false,
1479 serpent_attack_damage: 50.0,
1480 turn_count: 0,
1481 }
1482 }
1483
1484 pub fn consume_edge(&mut self) -> Option<&'static str> {
1486 self.turn_count += 1;
1487 let direction = self.turn_count % 4;
1489 match direction {
1490 0 => {
1491 if self.consumed_north < self.original_height / 2 {
1492 self.consumed_north += 1;
1493 self.arena_height = self.arena_height.saturating_sub(1);
1494 Some("north")
1495 } else {
1496 None
1497 }
1498 }
1499 1 => {
1500 if self.consumed_east < self.original_width / 2 {
1501 self.consumed_east += 1;
1502 self.arena_width = self.arena_width.saturating_sub(1);
1503 Some("east")
1504 } else {
1505 None
1506 }
1507 }
1508 2 => {
1509 if self.consumed_south < self.original_height / 2 {
1510 self.consumed_south += 1;
1511 self.arena_height = self.arena_height.saturating_sub(1);
1512 Some("south")
1513 } else {
1514 None
1515 }
1516 }
1517 3 => {
1518 if self.consumed_west < self.original_width / 2 {
1519 self.consumed_west += 1;
1520 self.arena_width = self.arena_width.saturating_sub(1);
1521 Some("west")
1522 } else {
1523 None
1524 }
1525 }
1526 _ => None,
1527 }
1528 }
1529
1530 pub fn safe_area(&self) -> u32 {
1532 self.arena_width.saturating_mul(self.arena_height)
1533 }
1534
1535 pub fn arena_fraction(&self) -> f32 {
1537 let original = self.original_width * self.original_height;
1538 if original == 0 { return 0.0; }
1539 self.safe_area() as f32 / original as f32
1540 }
1541
1542 pub fn is_safe(&self, x: u32, y: u32) -> bool {
1544 x >= self.consumed_west
1545 && x < self.original_width - self.consumed_east
1546 && y >= self.consumed_north
1547 && y < self.original_height - self.consumed_south
1548 }
1549
1550 pub fn enable_projectiles(&mut self) {
1552 self.void_projectiles = true;
1553 }
1554
1555 pub fn emerge_serpent(&mut self, damage: f32) {
1557 self.serpent_emerged = true;
1558 self.serpent_attack_damage = damage;
1559 }
1560}
1561
1562impl Default for VoidSerpentState {
1563 fn default() -> Self { Self::new(20, 20) }
1564}
1565
1566#[derive(Debug, Clone)]
1573pub struct PrimeFactorialState {
1574 pub factorial_index: u32,
1576 pub factorial_cache: Vec<f32>,
1578 pub puzzle_active: bool,
1580 pub puzzle_target_factors: Vec<u32>,
1582 pub puzzles_solved: u32,
1584 pub rng_state: u64,
1586}
1587
1588impl PrimeFactorialState {
1589 pub fn new() -> Self {
1590 let factorials = vec![1.0, 2.0, 6.0, 24.0, 120.0, 720.0, 5040.0];
1592 Self {
1593 factorial_index: 0,
1594 factorial_cache: factorials,
1595 puzzle_active: false,
1596 puzzle_target_factors: Vec::new(),
1597 puzzles_solved: 0,
1598 rng_state: 0xA01E_0013,
1599 }
1600 }
1601
1602 fn next_rng(&mut self) -> u64 {
1603 self.rng_state ^= self.rng_state << 13;
1604 self.rng_state ^= self.rng_state >> 7;
1605 self.rng_state ^= self.rng_state << 17;
1606 self.rng_state
1607 }
1608
1609 pub fn next_factorial_damage(&mut self) -> f32 {
1611 let idx = self.factorial_index as usize;
1612 let damage = if idx < self.factorial_cache.len() {
1613 self.factorial_cache[idx]
1614 } else {
1615 *self.factorial_cache.last().unwrap_or(&1.0)
1617 };
1618 self.factorial_index += 1;
1619 if self.factorial_index as usize >= self.factorial_cache.len() {
1621 self.factorial_index = 0;
1622 }
1623 damage
1624 }
1625
1626 pub fn is_prime(n: u32) -> bool {
1628 if n < 2 {
1629 return false;
1630 }
1631 if n == 2 || n == 3 {
1632 return true;
1633 }
1634 if n % 2 == 0 || n % 3 == 0 {
1635 return false;
1636 }
1637 let mut i = 5u32;
1638 while i.saturating_mul(i) <= n {
1639 if n % i == 0 || n % (i + 2) == 0 {
1640 return false;
1641 }
1642 i += 6;
1643 }
1644 true
1645 }
1646
1647 pub fn filter_damage(&self, raw_damage: f32) -> f32 {
1649 let rounded = raw_damage.round() as u32;
1650 if Self::is_prime(rounded) {
1651 raw_damage
1652 } else {
1653 0.0 }
1655 }
1656
1657 pub fn generate_puzzle(&mut self) -> Vec<u32> {
1660 let small_primes = [2, 3, 5, 7, 11, 13];
1661 let count = 2 + (self.puzzles_solved.min(3) as usize); let mut factors = Vec::new();
1663 for _ in 0..count {
1664 let idx = (self.next_rng() as usize) % small_primes.len();
1665 factors.push(small_primes[idx]);
1666 }
1667 self.puzzle_target_factors = factors.clone();
1668 self.puzzle_active = true;
1669 factors
1670 }
1671
1672 pub fn check_puzzle_solution(&mut self, damage: u32) -> bool {
1674 if !self.puzzle_active || self.puzzle_target_factors.is_empty() {
1675 return false;
1676 }
1677
1678 let target: u32 = self.puzzle_target_factors.iter().product();
1680 if damage == target {
1681 self.puzzles_solved += 1;
1682 self.puzzle_active = false;
1683 self.puzzle_target_factors.clear();
1684 true
1685 } else {
1686 false
1687 }
1688 }
1689
1690 pub fn prime_hp(tier: u32) -> f32 {
1692 match tier {
1693 1 => 997.0,
1694 2 => 4999.0,
1695 3 => 10_007.0,
1696 4 => 49_999.0, _ => 99_991.0,
1698 }
1699 }
1700}
1701
1702impl Default for PrimeFactorialState {
1703 fn default() -> Self { Self::new() }
1704}
1705
1706#[derive(Debug, Clone)]
1712pub enum BossMechanicState {
1713 Mirror(MirrorBossState),
1714 Null(NullBossState),
1715 Committee(CommitteeBossState),
1716 FibonacciHydra(FibonacciHydraState),
1717 Eigenstate(EigenstateBossState),
1718 Ouroboros(OuroborosBossState),
1719 AlgorithmReborn(AlgorithmRebornState),
1720 ChaosWeaver(ChaosWeaverState),
1721 VoidSerpent(VoidSerpentState),
1722 PrimeFactorial(PrimeFactorialState),
1723}
1724
1725#[derive(Debug, Clone)]
1731pub enum BossEvent {
1732 PhaseChange {
1734 new_phase: u32,
1735 transition: PhaseTransition,
1736 dialogue: String,
1737 },
1738 SpecialAbility {
1740 ability: SpecialAbility,
1741 description: String,
1742 },
1743 Dialogue(String),
1745 MusicChange(MusicType),
1747 ArenaModification(ArenaMod),
1749 VictoryReward {
1751 boss_type: BossType,
1752 loot: Vec<BossLootEntry>,
1753 xp_reward: u64,
1754 },
1755 UiErased(EraseTarget),
1757 UiRestored(Vec<EraseTarget>),
1759 HydraSplit { parent_id: u32, child_ids: (u32, u32) },
1761 QuantumCollapse(QuantumForm),
1763 RulesChanged(String),
1765 AbilityLocked(u32),
1767 CommitteeVoteResult(CommitteeAction),
1769 ArenaShrunk { direction: String, remaining_fraction: f32 },
1771 PuzzleGenerated(Vec<u32>),
1773 PuzzleSolved,
1775 BossDefeated(BossType),
1777}
1778
1779#[derive(Clone)]
1785pub struct BossEncounter {
1786 pub entity: AmorphousEntity,
1788 pub profile: BossProfile,
1790 pub phase_controller: BossPhaseController,
1792 pub mechanic_state: BossMechanicState,
1794 pub turn_count: u32,
1796 pub damage_log: Vec<f32>,
1798 pub floor: u32,
1800 pub finished: bool,
1802 pub boss_stats: CombatStats,
1804}
1805
1806impl BossEncounter {
1807 pub fn update(&mut self, dt: f32, player_actions: &[RecordedAction]) -> Vec<BossEvent> {
1809 let mut events = Vec::new();
1810
1811 if self.finished {
1812 return events;
1813 }
1814
1815 for action in player_actions {
1817 self.record_player_action(action.clone());
1818 }
1819
1820 if self.phase_controller.is_transitioning() {
1822 self.phase_controller.update_transition(dt);
1823 return events; }
1825
1826 let hp_frac = self.entity.hp_frac();
1828 if let Some(phase) = self.phase_controller.check_transition(hp_frac) {
1829 let phase_num = phase.phase_number;
1830 let transition = phase.transition_animation;
1831 let dialogue = phase.dialogue_on_enter.clone();
1832
1833 events.push(BossEvent::PhaseChange {
1834 new_phase: phase_num,
1835 transition,
1836 dialogue: dialogue.clone(),
1837 });
1838
1839 if !dialogue.is_empty() {
1840 events.push(BossEvent::Dialogue(dialogue));
1841 }
1842
1843 self.on_phase_enter(phase_num, &mut events);
1845 }
1846
1847 self.tick_mechanic(dt, &mut events);
1849
1850 if self.entity.is_dead() {
1852 self.on_death(&mut events);
1853 }
1854
1855 self.entity.tick(dt, self.entity.age);
1857
1858 self.turn_count += 1;
1859 events
1860 }
1861
1862 fn record_player_action(&mut self, action: RecordedAction) {
1864 match &mut self.mechanic_state {
1865 BossMechanicState::Mirror(state) => {
1866 state.record_action(action);
1867 }
1868 BossMechanicState::AlgorithmReborn(state) => {
1869 state.record_action(action.action_type);
1870 }
1871 _ => {}
1872 }
1873 }
1874
1875 fn on_phase_enter(&mut self, phase_num: u32, events: &mut Vec<BossEvent>) {
1877 match &mut self.mechanic_state {
1878 BossMechanicState::Mirror(state) => {
1879 if phase_num == 2 {
1880 state.enter_phase2();
1881 } else if phase_num == 3 {
1882 state.enter_phase3();
1883 }
1884 }
1885 BossMechanicState::Null(_state) => {
1886 }
1888 BossMechanicState::Committee(state) => {
1889 if phase_num == 2 {
1890 state.enable_ghost_voting();
1891 } else if phase_num == 3 {
1892 let combined_hp = state.merge_judges();
1893 events.push(BossEvent::Dialogue(
1894 format!("The judges merge! Combined HP: {:.0}", combined_hp),
1895 ));
1896 }
1897 }
1898 BossMechanicState::Eigenstate(state) => {
1899 if phase_num == 2 {
1900 state.add_evasion_form();
1901 } else if phase_num == 3 {
1902 let copy_hp = state.entangle(self.entity.hp);
1903 events.push(BossEvent::SpecialAbility {
1904 ability: SpecialAbility::Entangle,
1905 description: format!("Entangled copy spawned with {:.0} HP", copy_hp),
1906 });
1907 }
1908 }
1909 BossMechanicState::Ouroboros(state) => {
1910 if phase_num == 2 {
1911 state.enter_phase2();
1912 events.push(BossEvent::RulesChanged(
1913 "The rules of damage and healing flicker...".into(),
1914 ));
1915 }
1916 }
1917 BossMechanicState::AlgorithmReborn(state) => {
1918 if phase_num == 2 {
1919 state.enter_phase2();
1920 events.push(BossEvent::Dialogue(
1921 "I have studied your every move.".into(),
1922 ));
1923 } else if phase_num == 3 {
1924 state.enter_phase3();
1925 events.push(BossEvent::Dialogue(
1926 "I know what you will do before you do it.".into(),
1927 ));
1928 }
1929 }
1930 BossMechanicState::ChaosWeaver(state) => {
1931 if phase_num == 2 {
1932 state.enable_tile_randomization();
1933 events.push(BossEvent::ArenaModification(
1934 ArenaMod::HazardTiles { element: Element::Entropy, count: 10 },
1935 ));
1936 }
1937 }
1938 BossMechanicState::VoidSerpent(state) => {
1939 if phase_num == 2 {
1940 state.enable_projectiles();
1941 } else if phase_num == 3 {
1942 state.emerge_serpent(80.0);
1943 events.push(BossEvent::Dialogue(
1944 "The Void Serpent emerges from the darkness!".into(),
1945 ));
1946 }
1947 }
1948 BossMechanicState::PrimeFactorial(state) => {
1949 if phase_num == 2 {
1950 let factors = state.generate_puzzle();
1951 events.push(BossEvent::PuzzleGenerated(factors));
1952 }
1953 }
1954 _ => {}
1955 }
1956 }
1957
1958 fn tick_mechanic(&mut self, _dt: f32, events: &mut Vec<BossEvent>) {
1960 let phase_num = self.phase_controller.current_phase_number();
1961
1962 match &mut self.mechanic_state {
1963 BossMechanicState::Mirror(state) => {
1964 if let Some(action) = state.get_mirrored_action(self.turn_count) {
1965 events.push(BossEvent::SpecialAbility {
1966 ability: SpecialAbility::MirrorCopy { depth: state.buffer_depth },
1967 description: format!("Mirror copies: {:?}", action.action_type),
1968 });
1969 }
1970 }
1971 BossMechanicState::Null(state) => {
1972 match phase_num {
1973 1 => {
1974 let erased = state.erase_buffs(3); if erased > 0 {
1976 events.push(BossEvent::SpecialAbility {
1977 ability: SpecialAbility::Erase(EraseTarget::PlayerBuffs),
1978 description: format!("Erased {} buff(s)", erased),
1979 });
1980 }
1981 }
1982 2 => {
1983 let target = state.erase_ui_element();
1984 events.push(BossEvent::UiErased(target));
1985 }
1986 3 => {
1987 if let Some(slot) = state.lock_random_ability(6) {
1988 events.push(BossEvent::AbilityLocked(slot));
1989 }
1990 }
1991 _ => {}
1992 }
1993 }
1994 BossMechanicState::Committee(state) => {
1995 if !state.merged {
1996 let hp_frac = self.entity.hp_frac();
1997 let action = state.conduct_vote(hp_frac);
1998 events.push(BossEvent::CommitteeVoteResult(action));
1999 }
2000 }
2001 BossMechanicState::FibonacciHydra(state) => {
2002 let dead_heads: Vec<u32> = state
2004 .heads
2005 .iter()
2006 .filter(|h| !h.alive && h.depth < state.max_depth)
2007 .map(|h| h.id)
2008 .collect();
2009
2010 for head_id in dead_heads {
2011 if let Some((a, b)) = state.try_split(head_id) {
2012 events.push(BossEvent::HydraSplit {
2013 parent_id: head_id,
2014 child_ids: (a, b),
2015 });
2016 }
2017 }
2018
2019 if state.is_defeated() {
2020 self.entity.hp = 0.0; }
2022 }
2023 BossMechanicState::Eigenstate(state) => {
2024 if state.observed {
2026 state.unobserve();
2027 }
2028 }
2029 BossMechanicState::Ouroboros(state) => {
2030 let swapped = state.advance_turn();
2031 if swapped {
2032 let msg = if state.reversed {
2033 "The rules twist back... damage heals, healing harms."
2034 } else {
2035 "For a brief moment, the rules return to normal..."
2036 };
2037 events.push(BossEvent::RulesChanged(msg.into()));
2038 }
2039 }
2040 BossMechanicState::AlgorithmReborn(state) => {
2041 let hp_frac = self.entity.hp_frac();
2042 state.update_degradation(hp_frac);
2043
2044 if state.markov_mode {
2045 if let Some(predicted) = state.predict_next() {
2046 let counter = AlgorithmRebornState::counter_for(&predicted);
2047 events.push(BossEvent::SpecialAbility {
2048 ability: SpecialAbility::MarkovPredict,
2049 description: format!(
2050 "Algorithm predicts {:?}, counters with {:?}",
2051 predicted, counter
2052 ),
2053 });
2054 }
2055 } else if state.counter_mode {
2056 if let Some(most_used) = state.most_used_action() {
2057 let counter = AlgorithmRebornState::counter_for(&most_used);
2058 events.push(BossEvent::SpecialAbility {
2059 ability: SpecialAbility::CounterPredict,
2060 description: format!(
2061 "Algorithm counters your favorite: {:?} with {:?}",
2062 most_used, counter
2063 ),
2064 });
2065 }
2066 }
2067 }
2068 BossMechanicState::ChaosWeaver(state) => {
2069 if self.turn_count % 3 == 0 {
2071 state.scramble_weaknesses();
2072 events.push(BossEvent::RulesChanged(
2073 "Element weaknesses have been scrambled!".into(),
2074 ));
2075 }
2076 if self.turn_count % 5 == 0 {
2078 let (a, b) = state.swap_ability_slots(6);
2079 events.push(BossEvent::RulesChanged(
2080 format!("Ability slots {} and {} swapped!", a, b),
2081 ));
2082 }
2083 }
2084 BossMechanicState::VoidSerpent(state) => {
2085 if let Some(direction) = state.consume_edge() {
2086 let frac = state.arena_fraction();
2087 events.push(BossEvent::ArenaShrunk {
2088 direction: direction.to_string(),
2089 remaining_fraction: frac,
2090 });
2091 }
2092 }
2093 BossMechanicState::PrimeFactorial(state) => {
2094 let damage = state.next_factorial_damage();
2095 events.push(BossEvent::SpecialAbility {
2096 ability: SpecialAbility::FactorialStrike {
2097 sequence_index: state.factorial_index,
2098 },
2099 description: format!("Factorial strike: {:.0} damage!", damage),
2100 });
2101 }
2102 }
2103 }
2104
2105 fn on_death(&mut self, events: &mut Vec<BossEvent>) {
2107 self.finished = true;
2108
2109 if let BossMechanicState::Null(state) = &mut self.mechanic_state {
2111 let restored = state.restore_all();
2112 events.push(BossEvent::UiRestored(restored));
2113 }
2114
2115 events.push(BossEvent::BossDefeated(self.profile.boss_type));
2116
2117 events.push(BossEvent::VictoryReward {
2119 boss_type: self.profile.boss_type,
2120 loot: self.profile.loot_table.clone(),
2121 xp_reward: (self.profile.tier as u64) * 500 + (self.floor as u64) * 100,
2122 });
2123 }
2124}
2125
2126pub struct BossEncounterManager;
2132
2133impl BossEncounterManager {
2134 pub fn start_encounter(
2136 boss_type: BossType,
2137 floor: u32,
2138 player_stats: &CombatStats,
2139 ) -> BossEncounter {
2140 let profile = Self::build_profile(boss_type);
2141 let scaled_hp = profile.scaled_hp(floor);
2142 let scaled_damage = profile.scaled_damage(floor);
2143
2144 let mut entity = AmorphousEntity::new(profile.name.clone(), glam::Vec3::ZERO);
2145 entity.hp = scaled_hp;
2146 entity.max_hp = scaled_hp;
2147
2148 let mut boss_stats = CombatStats {
2149 attack: scaled_damage,
2150 max_hp: scaled_hp,
2151 hp: scaled_hp,
2152 level: floor,
2153 crit_chance: 0.1,
2154 crit_mult: 2.5,
2155 ..CombatStats::default()
2156 };
2157
2158 boss_stats.armor = player_stats.attack * 0.3;
2160
2161 let phase_controller = BossPhaseController::new(profile.phases.clone());
2162 let mechanic_state = Self::create_mechanic_state(boss_type, scaled_hp);
2163
2164 BossEncounter {
2165 entity,
2166 profile,
2167 phase_controller,
2168 mechanic_state,
2169 turn_count: 0,
2170 damage_log: Vec::new(),
2171 floor,
2172 finished: false,
2173 boss_stats,
2174 }
2175 }
2176
2177 fn build_profile(boss_type: BossType) -> BossProfile {
2179 match boss_type {
2180 BossType::Mirror => Self::mirror_profile(),
2181 BossType::Null => Self::null_profile(),
2182 BossType::Committee => Self::committee_profile(),
2183 BossType::FibonacciHydra => Self::fibonacci_hydra_profile(),
2184 BossType::Eigenstate => Self::eigenstate_profile(),
2185 BossType::Ouroboros => Self::ouroboros_profile(),
2186 BossType::AlgorithmReborn => Self::algorithm_reborn_profile(),
2187 BossType::ChaosWeaver => Self::chaos_weaver_profile(),
2188 BossType::VoidSerpent => Self::void_serpent_profile(),
2189 BossType::PrimeFactorial => Self::prime_factorial_profile(),
2190 }
2191 }
2192
2193 fn create_mechanic_state(boss_type: BossType, base_hp: f32) -> BossMechanicState {
2195 match boss_type {
2196 BossType::Mirror => BossMechanicState::Mirror(MirrorBossState::new()),
2197 BossType::Null => BossMechanicState::Null(NullBossState::new()),
2198 BossType::Committee => BossMechanicState::Committee(CommitteeBossState::new()),
2199 BossType::FibonacciHydra => {
2200 BossMechanicState::FibonacciHydra(FibonacciHydraState::new(base_hp))
2201 }
2202 BossType::Eigenstate => BossMechanicState::Eigenstate(EigenstateBossState::new()),
2203 BossType::Ouroboros => BossMechanicState::Ouroboros(OuroborosBossState::new()),
2204 BossType::AlgorithmReborn => {
2205 BossMechanicState::AlgorithmReborn(AlgorithmRebornState::new())
2206 }
2207 BossType::ChaosWeaver => BossMechanicState::ChaosWeaver(ChaosWeaverState::new()),
2208 BossType::VoidSerpent => {
2209 BossMechanicState::VoidSerpent(VoidSerpentState::new(20, 20))
2210 }
2211 BossType::PrimeFactorial => {
2212 BossMechanicState::PrimeFactorial(PrimeFactorialState::new())
2213 }
2214 }
2215 }
2216
2217 fn mirror_profile() -> BossProfile {
2220 BossProfile {
2221 boss_type: BossType::Mirror,
2222 name: "The Mirror".into(),
2223 title: "Reflection of Self".into(),
2224 hp_base: 800.0,
2225 damage_base: 15.0,
2226 tier: 1,
2227 phases: vec![
2228 BossPhase::new(1, 1.0)
2229 .with_behavior(BehaviorPattern::Standard)
2230 .with_ability(SpecialAbility::MirrorCopy { depth: 3 })
2231 .with_dialogue("I am you, delayed."),
2232 BossPhase::new(2, 0.5)
2233 .with_behavior(BehaviorPattern::Calculated)
2234 .with_damage(1.3)
2235 .with_ability(SpecialAbility::MirrorCopy { depth: 3 })
2236 .with_transition(PhaseTransition::GlyphReorganize)
2237 .with_dialogue("Now I wear your strength as well."),
2238 BossPhase::new(3, 0.25)
2239 .with_behavior(BehaviorPattern::Aggressive)
2240 .with_speed(1.5)
2241 .with_damage(1.5)
2242 .with_ability(SpecialAbility::MirrorCopy { depth: 3 })
2243 .with_transition(PhaseTransition::PowerUp)
2244 .with_dialogue("We act as one. There is no delay."),
2245 ],
2246 special_mechanics: vec![
2247 "Copies player abilities with 1-turn delay".into(),
2248 "Phase 2: copies player stats".into(),
2249 "Phase 3: simultaneous mirrored actions".into(),
2250 ],
2251 loot_table: vec![BossLootEntry {
2252 item_name: "Shard of Reflection".into(),
2253 drop_chance: 1.0,
2254 min_quantity: 1,
2255 max_quantity: 1,
2256 }],
2257 music_type: MusicType::Ominous,
2258 arena_mods: vec![ArenaMod::None],
2259 resistance: ResistanceProfile::boss_resist(),
2260 }
2261 }
2262
2263 fn null_profile() -> BossProfile {
2264 BossProfile {
2265 boss_type: BossType::Null,
2266 name: "The Null".into(),
2267 title: "The Eraser of Meaning".into(),
2268 hp_base: 1200.0,
2269 damage_base: 12.0,
2270 tier: 2,
2271 phases: vec![
2272 BossPhase::new(1, 1.0)
2273 .with_behavior(BehaviorPattern::Passive)
2274 .with_ability(SpecialAbility::Erase(EraseTarget::PlayerBuffs))
2275 .with_dialogue("Let us subtract."),
2276 BossPhase::new(2, 0.6)
2277 .with_behavior(BehaviorPattern::Standard)
2278 .with_damage(1.2)
2279 .with_ability(SpecialAbility::Erase(EraseTarget::HpBar))
2280 .with_transition(PhaseTransition::Dissolve)
2281 .with_dialogue("Your interface is a luxury I revoke."),
2282 BossPhase::new(3, 0.3)
2283 .with_behavior(BehaviorPattern::Aggressive)
2284 .with_speed(1.3)
2285 .with_damage(1.4)
2286 .with_ability(SpecialAbility::LockAbility)
2287 .with_transition(PhaseTransition::Dissolve)
2288 .with_dialogue("Even your skills are expendable."),
2289 ],
2290 special_mechanics: vec![
2291 "Phase 1: erases player buffs".into(),
2292 "Phase 2: erases UI elements".into(),
2293 "Phase 3: locks random abilities each turn".into(),
2294 "On death: all erased elements restored".into(),
2295 ],
2296 loot_table: vec![BossLootEntry {
2297 item_name: "Void Fragment".into(),
2298 drop_chance: 1.0,
2299 min_quantity: 1,
2300 max_quantity: 2,
2301 }],
2302 music_type: MusicType::Silence,
2303 arena_mods: vec![ArenaMod::DarkenVision { radius_reduction: 3.0 }],
2304 resistance: ResistanceProfile::void_entity(),
2305 }
2306 }
2307
2308 fn committee_profile() -> BossProfile {
2309 BossProfile {
2310 boss_type: BossType::Committee,
2311 name: "The Committee".into(),
2312 title: "Democracy of Violence".into(),
2313 hp_base: 1500.0,
2314 damage_base: 18.0,
2315 tier: 2,
2316 phases: vec![
2317 BossPhase::new(1, 1.0)
2318 .with_behavior(BehaviorPattern::Standard)
2319 .with_ability(SpecialAbility::CommitteeVote)
2320 .with_dialogue("The vote is called. All in favor?"),
2321 BossPhase::new(2, 0.5)
2322 .with_behavior(BehaviorPattern::Calculated)
2323 .with_damage(1.2)
2324 .with_ability(SpecialAbility::CommitteeVote)
2325 .with_transition(PhaseTransition::GlyphReorganize)
2326 .with_dialogue("The dead still have a voice here."),
2327 BossPhase::new(3, 0.2)
2328 .with_behavior(BehaviorPattern::Berserk)
2329 .with_speed(1.4)
2330 .with_damage(1.8)
2331 .with_ability(SpecialAbility::CommitteeVote)
2332 .with_transition(PhaseTransition::Merge)
2333 .with_dialogue("We are ONE. The motion carries unanimously."),
2334 ],
2335 special_mechanics: vec![
2336 "5 judges vote on each action".into(),
2337 "Kill judges to change vote balance".into(),
2338 "Phase 2: dead judges vote as ghosts".into(),
2339 "Phase 3: remaining judges merge".into(),
2340 ],
2341 loot_table: vec![BossLootEntry {
2342 item_name: "Gavel of Authority".into(),
2343 drop_chance: 0.8,
2344 min_quantity: 1,
2345 max_quantity: 1,
2346 }],
2347 music_type: MusicType::Orchestral,
2348 arena_mods: vec![ArenaMod::None],
2349 resistance: ResistanceProfile::neutral(),
2350 }
2351 }
2352
2353 fn fibonacci_hydra_profile() -> BossProfile {
2354 BossProfile {
2355 boss_type: BossType::FibonacciHydra,
2356 name: "Fibonacci Hydra".into(),
2357 title: "The Golden Recursion".into(),
2358 hp_base: 1000.0,
2359 damage_base: 14.0,
2360 tier: 3,
2361 phases: vec![
2362 BossPhase::new(1, 1.0)
2363 .with_behavior(BehaviorPattern::Standard)
2364 .with_ability(SpecialAbility::FibonacciSplit)
2365 .with_dialogue("Cut one, and two shall grow."),
2366 BossPhase::new(2, 0.618)
2367 .with_behavior(BehaviorPattern::Aggressive)
2368 .with_speed(1.2)
2369 .with_damage(1.3)
2370 .with_ability(SpecialAbility::FibonacciSplit)
2371 .with_transition(PhaseTransition::Split)
2372 .with_dialogue("The golden ratio demands expansion!"),
2373 BossPhase::new(3, 0.3)
2374 .with_behavior(BehaviorPattern::Berserk)
2375 .with_speed(1.5)
2376 .with_damage(1.5)
2377 .with_ability(SpecialAbility::FibonacciSplit)
2378 .with_transition(PhaseTransition::Split)
2379 .with_dialogue("We are legion! 1, 1, 2, 3, 5, 8, 13..."),
2380 ],
2381 special_mechanics: vec![
2382 "Splits into 2 on death at 61.8% HP each".into(),
2383 "Max depth 5 (up to 32 heads)".into(),
2384 "All heads share a damage pool".into(),
2385 ],
2386 loot_table: vec![BossLootEntry {
2387 item_name: "Golden Spiral Shell".into(),
2388 drop_chance: 1.0,
2389 min_quantity: 1,
2390 max_quantity: 1,
2391 }],
2392 music_type: MusicType::Frenetic,
2393 arena_mods: vec![ArenaMod::None],
2394 resistance: ResistanceProfile::neutral(),
2395 }
2396 }
2397
2398 fn eigenstate_profile() -> BossProfile {
2399 BossProfile {
2400 boss_type: BossType::Eigenstate,
2401 name: "The Eigenstate".into(),
2402 title: "Collapsed Possibility".into(),
2403 hp_base: 1100.0,
2404 damage_base: 20.0,
2405 tier: 3,
2406 phases: vec![
2407 BossPhase::new(1, 1.0)
2408 .with_behavior(BehaviorPattern::Erratic)
2409 .with_ability(SpecialAbility::QuantumCollapse)
2410 .with_dialogue("Observe me and I become certain. Look away and I am everything."),
2411 BossPhase::new(2, 0.5)
2412 .with_behavior(BehaviorPattern::Calculated)
2413 .with_speed(1.3)
2414 .with_damage(1.4)
2415 .with_ability(SpecialAbility::QuantumCollapse)
2416 .with_transition(PhaseTransition::Dissolve)
2417 .with_dialogue("A third possibility emerges."),
2418 BossPhase::new(3, 0.25)
2419 .with_behavior(BehaviorPattern::Aggressive)
2420 .with_speed(1.5)
2421 .with_damage(1.6)
2422 .with_ability(SpecialAbility::Entangle)
2423 .with_transition(PhaseTransition::Split)
2424 .with_dialogue("We are entangled now. Harm one, harm both."),
2425 ],
2426 special_mechanics: vec![
2427 "Exists in superposition of Attack/Defense".into(),
2428 "Targeting collapses to one form".into(),
2429 "Phase 2: 3 forms (adds Evasion)".into(),
2430 "Phase 3: entangled copy mirrors all damage".into(),
2431 ],
2432 loot_table: vec![BossLootEntry {
2433 item_name: "Quantum Shard".into(),
2434 drop_chance: 1.0,
2435 min_quantity: 1,
2436 max_quantity: 1,
2437 }],
2438 music_type: MusicType::Glitch,
2439 arena_mods: vec![ArenaMod::None],
2440 resistance: ResistanceProfile::neutral(),
2441 }
2442 }
2443
2444 fn ouroboros_profile() -> BossProfile {
2445 BossProfile {
2446 boss_type: BossType::Ouroboros,
2447 name: "Ouroboros".into(),
2448 title: "The Serpent That Devours".into(),
2449 hp_base: 1300.0,
2450 damage_base: 16.0,
2451 tier: 3,
2452 phases: vec![
2453 BossPhase::new(1, 1.0)
2454 .with_behavior(BehaviorPattern::Standard)
2455 .with_ability(SpecialAbility::ReverseSemantic)
2456 .with_dialogue("What heals you, harms me. What harms you, heals me. Or is it the other way?"),
2457 BossPhase::new(2, 0.5)
2458 .with_behavior(BehaviorPattern::Erratic)
2459 .with_speed(1.2)
2460 .with_damage(1.3)
2461 .with_ability(SpecialAbility::ReverseSemantic)
2462 .with_transition(PhaseTransition::GlyphReorganize)
2463 .with_dialogue("The rules flicker between truth and lies."),
2464 ],
2465 special_mechanics: vec![
2466 "Damage/heal semantics reversed".into(),
2467 "Boss heals by dealing damage".into(),
2468 "Player heals by dealing damage to boss".into(),
2469 "Phase 2: rules randomly swap to normal for 2 turns".into(),
2470 ],
2471 loot_table: vec![BossLootEntry {
2472 item_name: "Ouroboros Ring".into(),
2473 drop_chance: 1.0,
2474 min_quantity: 1,
2475 max_quantity: 1,
2476 }],
2477 music_type: MusicType::Reversed,
2478 arena_mods: vec![ArenaMod::None],
2479 resistance: ResistanceProfile::boss_resist(),
2480 }
2481 }
2482
2483 fn algorithm_reborn_profile() -> BossProfile {
2484 BossProfile {
2485 boss_type: BossType::AlgorithmReborn,
2486 name: "Algorithm Reborn".into(),
2487 title: "Final Proof".into(),
2488 hp_base: 3000.0,
2489 damage_base: 25.0,
2490 tier: 5,
2491 phases: vec![
2492 BossPhase::new(1, 1.0)
2493 .with_behavior(BehaviorPattern::Standard)
2494 .with_ability(SpecialAbility::None)
2495 .with_dialogue("Begin the final computation."),
2496 BossPhase::new(2, 0.65)
2497 .with_behavior(BehaviorPattern::Calculated)
2498 .with_speed(1.2)
2499 .with_damage(1.4)
2500 .with_ability(SpecialAbility::CounterPredict)
2501 .with_transition(PhaseTransition::PowerUp)
2502 .with_dialogue("I have studied your every move."),
2503 BossPhase::new(3, 0.3)
2504 .with_behavior(BehaviorPattern::Calculated)
2505 .with_speed(1.5)
2506 .with_damage(1.8)
2507 .with_ability(SpecialAbility::MarkovPredict)
2508 .with_transition(PhaseTransition::GlyphReorganize)
2509 .with_dialogue("I know what you will do before you do it."),
2510 ],
2511 special_mechanics: vec![
2512 "Tracks player action frequency".into(),
2513 "Phase 2: counters most-used action".into(),
2514 "Phase 3: Markov-chain prediction of next action".into(),
2515 "No HP bar: judge by visual degradation".into(),
2516 ],
2517 loot_table: vec![
2518 BossLootEntry {
2519 item_name: "Core of the Algorithm".into(),
2520 drop_chance: 1.0,
2521 min_quantity: 1,
2522 max_quantity: 1,
2523 },
2524 BossLootEntry {
2525 item_name: "Proof of Completion".into(),
2526 drop_chance: 1.0,
2527 min_quantity: 1,
2528 max_quantity: 1,
2529 },
2530 ],
2531 music_type: MusicType::Algorithmic,
2532 arena_mods: vec![
2533 ArenaMod::HazardTiles { element: Element::Entropy, count: 5 },
2534 ],
2535 resistance: ResistanceProfile::boss_resist(),
2536 }
2537 }
2538
2539 fn chaos_weaver_profile() -> BossProfile {
2540 BossProfile {
2541 boss_type: BossType::ChaosWeaver,
2542 name: "Chaos Weaver".into(),
2543 title: "Unraveler of Rules".into(),
2544 hp_base: 1400.0,
2545 damage_base: 17.0,
2546 tier: 4,
2547 phases: vec![
2548 BossPhase::new(1, 1.0)
2549 .with_behavior(BehaviorPattern::Erratic)
2550 .with_ability(SpecialAbility::RuleRandomize)
2551 .with_dialogue("The rules are merely suggestions."),
2552 BossPhase::new(2, 0.45)
2553 .with_behavior(BehaviorPattern::Erratic)
2554 .with_speed(1.3)
2555 .with_damage(1.5)
2556 .with_ability(SpecialAbility::RuleRandomize)
2557 .with_transition(PhaseTransition::Teleport)
2558 .with_dialogue("Even the ground beneath you obeys me now."),
2559 ],
2560 special_mechanics: vec![
2561 "Scrambles element weakness chart".into(),
2562 "Swaps player ability slots".into(),
2563 "Randomizes damage number display".into(),
2564 "Phase 2: randomizes tile effects".into(),
2565 ],
2566 loot_table: vec![BossLootEntry {
2567 item_name: "Thread of Chaos".into(),
2568 drop_chance: 1.0,
2569 min_quantity: 1,
2570 max_quantity: 3,
2571 }],
2572 music_type: MusicType::Chaotic,
2573 arena_mods: vec![ArenaMod::None],
2574 resistance: ResistanceProfile::chaos_rift(),
2575 }
2576 }
2577
2578 fn void_serpent_profile() -> BossProfile {
2579 BossProfile {
2580 boss_type: BossType::VoidSerpent,
2581 name: "Void Serpent".into(),
2582 title: "Consumer of Arenas".into(),
2583 hp_base: 1600.0,
2584 damage_base: 20.0,
2585 tier: 4,
2586 phases: vec![
2587 BossPhase::new(1, 1.0)
2588 .with_behavior(BehaviorPattern::Passive)
2589 .with_ability(SpecialAbility::ConsumeArena { columns: 1 })
2590 .with_dialogue("The void hungers."),
2591 BossPhase::new(2, 0.55)
2592 .with_behavior(BehaviorPattern::Standard)
2593 .with_speed(1.2)
2594 .with_damage(1.3)
2595 .with_ability(SpecialAbility::ConsumeArena { columns: 2 })
2596 .with_transition(PhaseTransition::Dissolve)
2597 .with_dialogue("The void spits back what it cannot digest."),
2598 BossPhase::new(3, 0.25)
2599 .with_behavior(BehaviorPattern::Berserk)
2600 .with_speed(1.5)
2601 .with_damage(1.8)
2602 .with_ability(SpecialAbility::ConsumeArena { columns: 3 })
2603 .with_transition(PhaseTransition::Teleport)
2604 .with_dialogue("I emerge from the nothing!"),
2605 ],
2606 special_mechanics: vec![
2607 "Each turn, edge tiles become void".into(),
2608 "Phase 2: void tiles spit projectiles".into(),
2609 "Phase 3: serpent emerges for direct attacks".into(),
2610 "Arena shrinks continuously".into(),
2611 ],
2612 loot_table: vec![BossLootEntry {
2613 item_name: "Void Scale".into(),
2614 drop_chance: 1.0,
2615 min_quantity: 1,
2616 max_quantity: 2,
2617 }],
2618 music_type: MusicType::MinimalDrone,
2619 arena_mods: vec![
2620 ArenaMod::ShrinkEdges { rate_per_turn: 1 },
2621 ArenaMod::DarkenVision { radius_reduction: 2.0 },
2622 ],
2623 resistance: ResistanceProfile::void_entity(),
2624 }
2625 }
2626
2627 fn prime_factorial_profile() -> BossProfile {
2628 BossProfile {
2629 boss_type: BossType::PrimeFactorial,
2630 name: "Prime Factorial".into(),
2631 title: "The Indivisible Explosion".into(),
2632 hp_base: PrimeFactorialState::prime_hp(4),
2633 damage_base: 22.0,
2634 tier: 4,
2635 phases: vec![
2636 BossPhase::new(1, 1.0)
2637 .with_behavior(BehaviorPattern::Standard)
2638 .with_ability(SpecialAbility::FactorialStrike { sequence_index: 0 })
2639 .with_dialogue("Only primes can wound me."),
2640 BossPhase::new(2, 0.5)
2641 .with_behavior(BehaviorPattern::Calculated)
2642 .with_speed(1.2)
2643 .with_damage(1.5)
2644 .with_ability(SpecialAbility::ArithmeticPuzzle {
2645 target_factors: vec![2, 3, 5],
2646 })
2647 .with_transition(PhaseTransition::PowerUp)
2648 .with_dialogue("Solve the equation or perish in the factorial!"),
2649 ],
2650 special_mechanics: vec![
2651 "HP is a large prime number".into(),
2652 "Damage in factorial sequences: 1, 2, 6, 24, 120...".into(),
2653 "Only prime-numbered damage hurts this boss".into(),
2654 "Phase 2: arithmetic puzzle mechanic".into(),
2655 ],
2656 loot_table: vec![BossLootEntry {
2657 item_name: "Prime Gemstone".into(),
2658 drop_chance: 1.0,
2659 min_quantity: 1,
2660 max_quantity: 1,
2661 }],
2662 music_type: MusicType::Crescendo,
2663 arena_mods: vec![ArenaMod::None],
2664 resistance: ResistanceProfile::neutral(),
2665 }
2666 }
2667}
2668
2669#[cfg(test)]
2674mod tests {
2675 use super::*;
2676
2677 #[test]
2680 fn phase_controller_starts_at_first_phase() {
2681 let phases = vec![
2682 BossPhase::new(1, 1.0),
2683 BossPhase::new(2, 0.5),
2684 BossPhase::new(3, 0.25),
2685 ];
2686 let ctrl = BossPhaseController::new(phases);
2687 assert_eq!(ctrl.current_phase_number(), 1);
2688 }
2689
2690 #[test]
2691 fn phase_controller_transitions_on_hp_drop() {
2692 let phases = vec![
2693 BossPhase::new(1, 1.0),
2694 BossPhase::new(2, 0.5),
2695 BossPhase::new(3, 0.25),
2696 ];
2697 let mut ctrl = BossPhaseController::new(phases);
2698
2699 assert!(ctrl.check_transition(0.8).is_none());
2701
2702 let result = ctrl.check_transition(0.4);
2704 assert!(result.is_some());
2705 assert_eq!(ctrl.current_phase_number(), 2);
2706 }
2707
2708 #[test]
2709 fn phase_controller_skips_to_deepest_crossed_phase() {
2710 let phases = vec![
2711 BossPhase::new(1, 1.0),
2712 BossPhase::new(2, 0.5),
2713 BossPhase::new(3, 0.25),
2714 ];
2715 let mut ctrl = BossPhaseController::new(phases);
2716
2717 let result = ctrl.check_transition(0.1);
2719 assert!(result.is_some());
2720 assert_eq!(ctrl.current_phase_number(), 3);
2721 }
2722
2723 #[test]
2724 fn phase_controller_transition_animation() {
2725 let phases = vec![
2726 BossPhase::new(1, 1.0),
2727 BossPhase::new(2, 0.5),
2728 ];
2729 let mut ctrl = BossPhaseController::new(phases);
2730 ctrl.check_transition(0.4);
2731
2732 assert!(ctrl.is_transitioning());
2733 assert!(!ctrl.update_transition(0.5)); assert!(ctrl.is_transitioning());
2735 assert!(ctrl.update_transition(1.5)); assert!(!ctrl.is_transitioning());
2737 }
2738
2739 #[test]
2742 fn mirror_records_and_retrieves_actions() {
2743 let mut state = MirrorBossState::new();
2744 state.record_action(RecordedAction {
2745 action_type: PlayerActionType::Attack,
2746 turn: 5,
2747 damage_dealt: 20.0,
2748 element: Some(Element::Fire),
2749 });
2750
2751 let mirrored = state.get_mirrored_action(6);
2753 assert!(mirrored.is_some());
2754 assert_eq!(mirrored.unwrap().action_type, PlayerActionType::Attack);
2755 }
2756
2757 #[test]
2758 fn mirror_buffer_limited_to_depth() {
2759 let mut state = MirrorBossState::new();
2760 for i in 0..10 {
2761 state.record_action(RecordedAction {
2762 action_type: PlayerActionType::Attack,
2763 turn: i,
2764 damage_dealt: 10.0,
2765 element: None,
2766 });
2767 }
2768 assert_eq!(state.mirror_buffer.len(), 3); }
2770
2771 #[test]
2772 fn mirror_phase2_copies_stats() {
2773 let mut state = MirrorBossState::new();
2774 let stats = CombatStats { attack: 50.0, armor: 30.0, ..CombatStats::default() };
2775 state.enter_phase2();
2776 state.copy_stats(&stats);
2777 assert!(state.copying_stats);
2778 assert!((state.copied_attack - 50.0).abs() < f32::EPSILON);
2779 }
2780
2781 #[test]
2782 fn mirror_phase3_simultaneous() {
2783 let mut state = MirrorBossState::new();
2784 assert!(!state.simultaneous);
2785 state.enter_phase3();
2786 assert!(state.simultaneous);
2787 }
2788
2789 #[test]
2792 fn null_erase_buffs() {
2793 let mut state = NullBossState::new();
2794 let erased = state.erase_buffs(5);
2795 assert_eq!(erased, 2); assert_eq!(state.buffs_erased, 2);
2797 }
2798
2799 #[test]
2800 fn null_erase_ui_element() {
2801 let mut state = NullBossState::new();
2802 let target = state.erase_ui_element();
2803 assert!(!state.erased_ui.is_empty());
2804 assert!(state.erased_ui.contains(&target));
2805 }
2806
2807 #[test]
2808 fn null_lock_ability() {
2809 let mut state = NullBossState::new();
2810 let slot = state.lock_random_ability(6);
2811 assert!(slot.is_some());
2812 assert_eq!(state.locked_abilities.len(), 1);
2813 }
2814
2815 #[test]
2816 fn null_restore_all() {
2817 let mut state = NullBossState::new();
2818 state.erase_ui_element();
2819 state.lock_random_ability(6);
2820 let restored = state.restore_all();
2821 assert!(!restored.is_empty());
2822 assert!(state.erased_ui.is_empty());
2823 assert!(state.locked_abilities.is_empty());
2824 }
2825
2826 #[test]
2829 fn committee_has_five_judges() {
2830 let state = CommitteeBossState::new();
2831 assert_eq!(state.judges.len(), 5);
2832 assert_eq!(state.alive_count(), 5);
2833 }
2834
2835 #[test]
2836 fn committee_vote_returns_action() {
2837 let mut state = CommitteeBossState::new();
2838 let action = state.conduct_vote(0.8);
2839 let _ = action;
2841 }
2842
2843 #[test]
2844 fn committee_killing_judges() {
2845 let mut state = CommitteeBossState::new();
2846 let killed = state.judges[0].take_damage(500.0);
2847 assert!(killed);
2848 assert_eq!(state.alive_count(), 4);
2849 }
2850
2851 #[test]
2852 fn committee_ghost_voting() {
2853 let mut state = CommitteeBossState::new();
2854 state.judges[0].take_damage(500.0);
2855 state.enable_ghost_voting();
2856 assert!(state.judges[0].ghost);
2857 let _action = state.conduct_vote(0.5);
2859 }
2860
2861 #[test]
2862 fn committee_merge() {
2863 let mut state = CommitteeBossState::new();
2864 let combined = state.merge_judges();
2865 assert!(combined > 0.0);
2866 assert!(state.merged);
2867 }
2868
2869 #[test]
2872 fn fibonacci_hydra_starts_with_one_head() {
2873 let state = FibonacciHydraState::new(1000.0);
2874 assert_eq!(state.alive_count(), 1);
2875 }
2876
2877 #[test]
2878 fn fibonacci_hydra_split_on_death() {
2879 let mut state = FibonacciHydraState::new(1000.0);
2880 state.damage_head(0, 1000.0);
2882 assert_eq!(state.alive_count(), 0);
2883
2884 let result = state.try_split(0);
2886 assert!(result.is_some());
2887 let (a, b) = result.unwrap();
2888
2889 assert_eq!(state.alive_count(), 2);
2891 let head_a = state.heads.iter().find(|h| h.id == a).unwrap();
2892 assert!((head_a.max_hp - 618.0).abs() < 1.0);
2893 let head_b = state.heads.iter().find(|h| h.id == b).unwrap();
2894 assert!((head_b.max_hp - 618.0).abs() < 1.0);
2895 }
2896
2897 #[test]
2898 fn fibonacci_hydra_max_depth() {
2899 let mut state = FibonacciHydraState::new(1000.0);
2900 state.max_depth = 2; state.damage_head(0, 1000.0);
2904 let (a, b) = state.try_split(0).unwrap();
2905
2906 state.damage_head(a, 1000.0);
2908 let (c, d) = state.try_split(a).unwrap();
2909
2910 state.damage_head(b, 1000.0);
2911 let (e, f) = state.try_split(b).unwrap();
2912
2913 state.damage_head(c, 1000.0);
2915 assert!(state.try_split(c).is_none());
2916 state.damage_head(d, 1000.0);
2917 state.damage_head(e, 1000.0);
2918 state.damage_head(f, 1000.0);
2919
2920 assert!(state.is_defeated());
2921 }
2922
2923 #[test]
2924 fn fibonacci_hydra_max_possible_heads() {
2925 let state = FibonacciHydraState::new(1000.0);
2926 assert_eq!(state.max_possible_heads(), 32); }
2928
2929 #[test]
2932 fn eigenstate_superposition_by_default() {
2933 let state = EigenstateBossState::new();
2934 assert!(state.in_superposition());
2935 assert!(state.collapsed_form.is_none());
2936 }
2937
2938 #[test]
2939 fn eigenstate_observation_collapses() {
2940 let mut state = EigenstateBossState::new();
2941 let form = state.observe();
2942 assert!(!state.in_superposition());
2943 assert_eq!(state.collapsed_form, Some(form));
2944 }
2945
2946 #[test]
2947 fn eigenstate_unobserve_returns_to_superposition() {
2948 let mut state = EigenstateBossState::new();
2949 state.observe();
2950 state.unobserve();
2951 assert!(state.in_superposition());
2952 }
2953
2954 #[test]
2955 fn eigenstate_phase2_adds_evasion() {
2956 let mut state = EigenstateBossState::new();
2957 assert_eq!(state.forms.len(), 2);
2958 state.add_evasion_form();
2959 assert_eq!(state.forms.len(), 3);
2960 }
2961
2962 #[test]
2963 fn eigenstate_entangle() {
2964 let mut state = EigenstateBossState::new();
2965 let copy_hp = state.entangle(500.0);
2966 assert!(state.entangled);
2967 assert!((copy_hp - 500.0).abs() < f32::EPSILON);
2968 }
2969
2970 #[test]
2971 fn eigenstate_mirror_damage() {
2972 let mut state = EigenstateBossState::new();
2973 state.entangle(500.0);
2974 let mirrored = state.mirror_damage(100.0);
2975 assert!((mirrored - 100.0).abs() < f32::EPSILON);
2976 assert!((state.entangled_hp - 400.0).abs() < f32::EPSILON);
2977 }
2978
2979 #[test]
2982 fn ouroboros_starts_reversed() {
2983 let state = OuroborosBossState::new();
2984 assert!(state.reversed);
2985 }
2986
2987 #[test]
2988 fn ouroboros_reversed_damage_heals_attacker() {
2989 let mut state = OuroborosBossState::new();
2990 let (damage, heal) = state.process_damage(100.0, true);
2991 assert!((damage - 100.0).abs() < f32::EPSILON);
2992 assert!((heal - 50.0).abs() < f32::EPSILON);
2993 }
2994
2995 #[test]
2996 fn ouroboros_reversed_heal_damages() {
2997 let state = OuroborosBossState::new();
2998 let result = state.process_heal(50.0);
2999 assert!((result - (-50.0)).abs() < f32::EPSILON);
3000 }
3001
3002 #[test]
3003 fn ouroboros_normal_rules_no_heal() {
3004 let mut state = OuroborosBossState::new();
3005 state.reversed = false;
3006 let (damage, heal) = state.process_damage(100.0, true);
3007 assert!((damage - 100.0).abs() < f32::EPSILON);
3008 assert!((heal - 0.0).abs() < f32::EPSILON);
3009 }
3010
3011 #[test]
3014 fn algorithm_records_frequency() {
3015 let mut state = AlgorithmRebornState::new();
3016 state.record_action(PlayerActionType::Attack);
3017 state.record_action(PlayerActionType::Attack);
3018 state.record_action(PlayerActionType::Heal);
3019
3020 assert_eq!(state.action_frequency[&PlayerActionType::Attack], 2);
3021 assert_eq!(state.action_frequency[&PlayerActionType::Heal], 1);
3022 }
3023
3024 #[test]
3025 fn algorithm_most_used() {
3026 let mut state = AlgorithmRebornState::new();
3027 state.record_action(PlayerActionType::Attack);
3028 state.record_action(PlayerActionType::Attack);
3029 state.record_action(PlayerActionType::Heal);
3030
3031 assert_eq!(state.most_used_action(), Some(PlayerActionType::Attack));
3032 }
3033
3034 #[test]
3035 fn algorithm_markov_prediction() {
3036 let mut state = AlgorithmRebornState::new();
3037 state.record_action(PlayerActionType::Attack);
3039 state.record_action(PlayerActionType::Heal);
3040 state.record_action(PlayerActionType::Attack);
3041 state.record_action(PlayerActionType::Heal);
3042
3043 let prediction = state.predict_next();
3045 assert_eq!(prediction, Some(PlayerActionType::Attack));
3046 }
3047
3048 #[test]
3049 fn algorithm_counter_for() {
3050 assert_eq!(
3051 AlgorithmRebornState::counter_for(&PlayerActionType::Attack),
3052 PlayerActionType::Defend
3053 );
3054 assert_eq!(
3055 AlgorithmRebornState::counter_for(&PlayerActionType::Heal),
3056 PlayerActionType::Attack
3057 );
3058 }
3059
3060 #[test]
3061 fn algorithm_degradation_tracks_hp() {
3062 let mut state = AlgorithmRebornState::new();
3063 state.update_degradation(0.3);
3064 assert!((state.degradation - 0.7).abs() < f32::EPSILON);
3065 assert!((state.hidden_hp_frac - 0.3).abs() < f32::EPSILON);
3066 }
3067
3068 #[test]
3071 fn chaos_weaver_scramble_weaknesses() {
3072 let mut state = ChaosWeaverState::new();
3073 state.scramble_weaknesses();
3074 assert!(!state.weakness_overrides.is_empty());
3075 assert_eq!(state.chaos_count, 1);
3076 }
3077
3078 #[test]
3079 fn chaos_weaver_swap_slots() {
3080 let mut state = ChaosWeaverState::new();
3081 let (a, b) = state.swap_ability_slots(6);
3082 assert_ne!(a, b);
3083 assert_eq!(state.slot_swaps[&a], b);
3084 assert_eq!(state.slot_swaps[&b], a);
3085 }
3086
3087 #[test]
3088 fn chaos_weaver_fake_damage() {
3089 let mut state = ChaosWeaverState::new();
3090 state.enable_damage_randomization();
3091 let fake = state.fake_damage_number(100.0);
3092 assert!(fake > 0.0);
3095 }
3096
3097 #[test]
3100 fn void_serpent_starts_full() {
3101 let state = VoidSerpentState::new(20, 20);
3102 assert!((state.arena_fraction() - 1.0).abs() < f32::EPSILON);
3103 }
3104
3105 #[test]
3106 fn void_serpent_consume_edge_shrinks_arena() {
3107 let mut state = VoidSerpentState::new(20, 20);
3108 let dir = state.consume_edge();
3109 assert!(dir.is_some());
3110 assert!(state.arena_fraction() < 1.0);
3111 }
3112
3113 #[test]
3114 fn void_serpent_safe_zone() {
3115 let mut state = VoidSerpentState::new(20, 20);
3116 assert!(state.is_safe(10, 10));
3117 state.consumed_north = 5;
3119 state.arena_height = 15;
3120 assert!(!state.is_safe(10, 2)); assert!(state.is_safe(10, 10)); }
3123
3124 #[test]
3125 fn void_serpent_emerge() {
3126 let mut state = VoidSerpentState::new(20, 20);
3127 state.emerge_serpent(100.0);
3128 assert!(state.serpent_emerged);
3129 assert!((state.serpent_attack_damage - 100.0).abs() < f32::EPSILON);
3130 }
3131
3132 #[test]
3135 fn prime_check() {
3136 assert!(!PrimeFactorialState::is_prime(0));
3137 assert!(!PrimeFactorialState::is_prime(1));
3138 assert!(PrimeFactorialState::is_prime(2));
3139 assert!(PrimeFactorialState::is_prime(3));
3140 assert!(!PrimeFactorialState::is_prime(4));
3141 assert!(PrimeFactorialState::is_prime(5));
3142 assert!(PrimeFactorialState::is_prime(7));
3143 assert!(!PrimeFactorialState::is_prime(9));
3144 assert!(PrimeFactorialState::is_prime(97));
3145 assert!(PrimeFactorialState::is_prime(997));
3146 }
3147
3148 #[test]
3149 fn prime_filter_damage() {
3150 let state = PrimeFactorialState::new();
3151 assert!((state.filter_damage(7.0) - 7.0).abs() < f32::EPSILON); assert!((state.filter_damage(8.0) - 0.0).abs() < f32::EPSILON); }
3154
3155 #[test]
3156 fn factorial_sequence() {
3157 let mut state = PrimeFactorialState::new();
3158 assert!((state.next_factorial_damage() - 1.0).abs() < f32::EPSILON);
3159 assert!((state.next_factorial_damage() - 2.0).abs() < f32::EPSILON);
3160 assert!((state.next_factorial_damage() - 6.0).abs() < f32::EPSILON);
3161 assert!((state.next_factorial_damage() - 24.0).abs() < f32::EPSILON);
3162 assert!((state.next_factorial_damage() - 120.0).abs() < f32::EPSILON);
3163 assert!((state.next_factorial_damage() - 720.0).abs() < f32::EPSILON);
3164 assert!((state.next_factorial_damage() - 5040.0).abs() < f32::EPSILON);
3165 assert!((state.next_factorial_damage() - 1.0).abs() < f32::EPSILON);
3167 }
3168
3169 #[test]
3170 fn puzzle_generation() {
3171 let mut state = PrimeFactorialState::new();
3172 let factors = state.generate_puzzle();
3173 assert!(factors.len() >= 2);
3174 assert!(state.puzzle_active);
3175 }
3176
3177 #[test]
3178 fn puzzle_solution() {
3179 let mut state = PrimeFactorialState::new();
3180 state.puzzle_target_factors = vec![2, 3, 5];
3181 state.puzzle_active = true;
3182
3183 assert!(!state.check_puzzle_solution(29)); assert!(state.check_puzzle_solution(30)); assert_eq!(state.puzzles_solved, 1);
3186 assert!(!state.puzzle_active);
3187 }
3188
3189 #[test]
3192 fn encounter_creates_all_boss_types() {
3193 let player_stats = CombatStats::default();
3194 for &boss_type in BossType::all() {
3195 let encounter = BossEncounterManager::start_encounter(boss_type, 1, &player_stats);
3196 assert!(!encounter.finished);
3197 assert!(encounter.entity.hp > 0.0);
3198 assert!(!encounter.profile.name.is_empty());
3199 }
3200 }
3201
3202 #[test]
3203 fn encounter_scales_with_floor() {
3204 let player_stats = CombatStats::default();
3205 let e1 = BossEncounterManager::start_encounter(BossType::Mirror, 1, &player_stats);
3206 let e10 = BossEncounterManager::start_encounter(BossType::Mirror, 10, &player_stats);
3207 assert!(e10.entity.max_hp > e1.entity.max_hp);
3208 }
3209
3210 #[test]
3211 fn encounter_update_emits_events() {
3212 let player_stats = CombatStats::default();
3213 let mut encounter = BossEncounterManager::start_encounter(
3214 BossType::ChaosWeaver, 1, &player_stats,
3215 );
3216 let events = encounter.update(0.016, &[]);
3218 assert!(!events.is_empty());
3219 }
3220
3221 #[test]
3222 fn encounter_boss_death_emits_events() {
3223 let player_stats = CombatStats::default();
3224 let mut encounter = BossEncounterManager::start_encounter(
3225 BossType::Mirror, 1, &player_stats,
3226 );
3227 encounter.entity.hp = 0.0;
3228 let events = encounter.update(0.016, &[]);
3229
3230 let has_defeat = events.iter().any(|e| matches!(e, BossEvent::BossDefeated(_)));
3231 assert!(has_defeat);
3232 assert!(encounter.finished);
3233 }
3234
3235 #[test]
3236 fn encounter_null_death_restores_ui() {
3237 let player_stats = CombatStats::default();
3238 let mut encounter = BossEncounterManager::start_encounter(
3239 BossType::Null, 1, &player_stats,
3240 );
3241
3242 if let BossMechanicState::Null(ref mut state) = encounter.mechanic_state {
3244 state.erase_ui_element();
3245 }
3246
3247 encounter.entity.hp = 0.0;
3248 let events = encounter.update(0.016, &[]);
3249
3250 let has_restore = events.iter().any(|e| matches!(e, BossEvent::UiRestored(_)));
3251 assert!(has_restore);
3252 }
3253}