1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
//! Boss generator — a composite, multi-part, multi-phase encounter creature
//! built by escalating the monster rig/body/skin/anim pipeline. See
//! docs/superpowers/specs/2026-07-06-phase7-bosses-design.md.
use crate::generators::monster::body;
use crate::gltf::{Asset, Material, Part};
use crate::palette::Palette;
use crate::recipe::BossParams;
mod anim;
pub mod meta;
mod preset;
mod rig;
use meta::BossMeta;
/// Approximate collider shape per archetype: the colossus is a stout biped,
/// so it fits `BipedBrute` (a tighter capsule); the rest still fall back to
/// `Serpent` (a low sprawling capsule) until Tasks 8-10 pick their own plans.
fn collider_plan(a: crate::recipe::BossArchetype) -> crate::recipe::BodyPlan {
match a {
crate::recipe::BossArchetype::Colossus | crate::recipe::BossArchetype::Lich => {
crate::recipe::BodyPlan::BipedBrute
}
crate::recipe::BossArchetype::SwarmQueen => crate::recipe::BodyPlan::Insectoid,
crate::recipe::BossArchetype::DragonLord | crate::recipe::BossArchetype::Hydra => {
crate::recipe::BodyPlan::Serpent
}
}
}
/// Real boss pipeline: archetype preset -> rig plan -> shared organic
/// body/skin pass -> collider fit -> (clips in Task 6) -> `BossMeta`
/// assembly (weak points, destructible parts, arena sizing).
pub fn generate(p: &BossParams, pal: &Palette) -> Asset {
let mut owned = p.clone();
preset::apply_archetype_preset(&mut owned);
let p = &owned;
let br = rig::build_boss_rig(p);
// Eyes must glow regardless of the emissive knob: floor the emission
// whenever the rig carries eye primitives (same rule as monster generate),
// so the default sentinel emissive still lights up an infernal hydra.
// A LOW floor (0.12) is deliberate: this value both (a) sets the fraction
// of body vertices painted full accent (`build_body`) and (b) scales the
// flat material emissive the renderer adds everywhere. A high floor washes
// the whole body bright accent and flattens its shading (candy/coral); a
// low floor keeps the body dark with sparse glowing crack accents and lets
// the rounded form read — dark scales + glowing lava eyes, high value
// contrast (matching how fire_wyrm reads dark-with-glow).
let eye_glow = br
.rig
.prims
.iter()
.any(|d| d.tint == crate::generators::monster::rig::PrimTint::Eye);
let e = p.emissive.clamp(0.0, 1.0);
// `emissive` here drives BOTH (a) the fraction of BODY vertices painted
// full accent albedo in `build_body` (`body_accent`) and (b) the UNIFORM
// material emissive the renderer adds to every surface. For most bosses
// these are one value. The colossus DECOUPLES them: a dark stone golem
// must read dark with the glow CONCENTRATED at the molten core (an
// Eye-tinted prim that always paints full accent regardless of these
// knobs) and in sparse crevice cracks — NOT measled bright accent all
// over (the green-on-green / washed-out failure). So its body gets only a
// few percent accent speckle and a LOW uniform emissive floor that lets
// the core+eye albedo pop without washing the rock.
// The lich is the OTHER gallery hero and is necrotic-only (never
// element-switched), so it hits the exact same green-on-green
// full-bright failure the colossus did: it gets the SAME dark-base
// treatment — a near-black robed body with the glow concentrated at the
// phylactery core, the eyes, the crown, and the floating implements
// (all pushed as Eye/Horn-tinted prims that paint full accent regardless
// of these knobs), NOT measled bright-green speckle over the whole robe.
let colossus = matches!(p.archetype, crate::recipe::BossArchetype::Colossus);
let lich = matches!(p.archetype, crate::recipe::BossArchetype::Lich);
// The swarm-queen is fungal-element (a brood-mother's palette) and hits
// the same over-saturation failure necrotic did on the lich: fungal's
// teal/green accent reads loud even at a moderate value, so a uniform
// whole-body emissive wash would flatten the huge carapace into a green
// glow blob instead of a dark insectoid with bright brood sacs. Same
// dark-base treatment: near-black carapace, glow concentrated at the
// brood sacs + eyes (`PrimTint::Eye`, already unconditional full accent).
let swarm_queen = matches!(p.archetype, crate::recipe::BossArchetype::SwarmQueen);
// The dragon-lord's arctic palette hits the same same-hue problem as the
// lich's necrotic green: the body (pale icy blues) and the accent glow
// (a bright cyan) share a hue family, so a uniform emissive wash reads as
// "the whole dragon is glowing pale blue" and washes out flat, instead of
// "a dark-scaled dragon with a glowing frost heart/eyes". Same dark-base
// treatment as the lich/swarm-queen: glow concentrated at the heart +
// eyes (`PrimTint::Eye`, already unconditional full accent), body base
// darkened (see `body_pal`) so those glow points POP against contrast.
let dragon_lord = matches!(p.archetype, crate::recipe::BossArchetype::DragonLord);
// The hydra is the gallery's marquee boss and is infernal-only (never
// element-switched), which hits the exact same same-hue problem as the
// dragon-lord's icy blues: the infernal base palette's warm oranges/reds
// and the orange accent glow share a hue family, so the ORIGINAL
// undecoupled `(e, e.max(...))` fallback below painted roughly half the
// body vertices full bright accent — a uniform candy-orange hydra instead
// of a dark charcoal-basalt beast with glowing lava eyes/cracks. Same
// dark-base treatment as the other three gallery bosses: glow
// concentrated at the eyes (`PrimTint::Eye`, already unconditional full
// accent) and sparse crack speckle, body base darkened (see `body_pal`)
// so those glow points POP against contrast.
let hydra = matches!(p.archetype, crate::recipe::BossArchetype::Hydra);
let (body_accent, emissive) = if colossus {
(
(e * 0.12).clamp(0.03, 0.07),
if eye_glow { 0.11 } else { 0.0 },
)
} else if lich || dragon_lord {
// Unlike the colossus's brown body / orange glow (different hue
// families, so a uniform low emissive floor still reads "brown"),
// the lich's body and glow are BOTH green — any uniform
// whole-surface emissive additive reads as "the whole robe is
// glowing", not "a dark robe with a few bright accents". So the
// uniform floor here is kept almost off; the phylactery/eyes/crown
// circlet/implements already paint FULL accent unconditionally via
// `PrimTint::Eye`/`Horn` (see `plan_lich`), so the "glow" pop comes
// from ALBEDO CONTRAST against the (separately darkened, see
// `body_pal` below) near-black robe, not from an emissive wash.
(
(e * 0.04).clamp(0.008, 0.02),
if eye_glow { 0.02 } else { 0.0 },
)
} else if swarm_queen || hydra {
(
(e * 0.04).clamp(0.008, 0.02),
if eye_glow { 0.02 } else { 0.0 },
)
} else {
// Currently unreachable: all 5 archetypes take a dark-base branch
// above. Kept as a deliberate default/guard for future archetypes
// that don't yet have a dark-base branch of their own.
(e, e.max(if eye_glow { 0.12 } else { 0.0 }))
};
// Necrotic green reads visually "loud"/glowing even at a moderate raw
// value (it carries far more perceived luminance than an equally-dark
// stone brown, which is why the colossus's brown-on-orange decoupling
// above wasn't enough here on its own — measured renders still washed
// bright green). So the lich additionally darkens the BASE body
// palette fed to `build_body` (terrain/foliage/trunk/rock, i.e.
// everything but `accent`) before meshing, so the dark robe reads
// genuinely near-black instead of "toxic green wash" — the glow stays
// exactly as bright since `accent` (the phylactery/eyes/crown/implement
// color) is untouched.
let body_pal = if lich || swarm_queen || dragon_lord || hydra {
let mut dp = *pal;
// The dragon-lord gets a MILDER dim than the lich/swarm-queen/hydra's
// near-black 0.2: the brief calls for a darker SLATE-BLUE body (icy
// scales, not a void), just dark enough that the glowing frost heart
// and eyes clearly pop. The hydra keeps the near-black 0.2 dim: it
// needs a dark charcoal-basalt body (not a mere dim orange) so the
// glowing lava eyes/cracks read as clearly separate from the base.
let dim = if dragon_lord { 0.3 } else { 0.2 };
for t in &mut dp.terrain {
*t *= dim;
}
for f in &mut dp.foliage {
*f *= dim;
}
dp.trunk *= dim;
for r in &mut dp.rock {
*r *= dim;
}
dp
} else {
*pal
};
let mut mesh = body::build_body(&br.rig, p.size, p.detail, p.seed, body_accent, &body_pal);
// The lich's throne is a literal CSG-carved mesh, not an SDF primitive
// (see `rig::build_throne_mesh`); merge it into the body mesh BEFORE
// skinning so `skin_body`'s nearest-primitive classifier binds its
// vertices to the throne's own rank-7 anchor family (rigid, static).
if let Some(extra) = &br.extra_mesh {
mesh.merge(extra);
}
crate::generators::monster::skin_body(&mut mesh, &br.rig);
mesh.validate().expect("boss mesh invalid");
// Whole-body collider reuses the monster fit; boss body plan approximated
// by the closest monster BodyPlan for the collider shape. Serpent (a
// capsule along the long axis) is the right approximation for the hydra's
// low sprawling torso + reared necks; the colossus is a biped, so it maps
// to BipedBrute. Tasks 8-10 pick plans for the remaining archetypes.
let plan = collider_plan(p.archetype);
let mut phys = body::fit_collider(&br.rig, p.size, plan);
// `fit_collider` clamps size to 4.0 (the monster range), so bosses sized
// above 4.0 (boss geometry goes up to 8.0) would saturate at the size-4
// mass. Override with a mass scaled to the boss's true size, using the
// same 55.0 * s^3 relationship, clamped to the boss size range instead.
let boss_s = p.size.clamp(0.2, 8.0);
phys.mass = 55.0 * boss_s * boss_s * boss_s;
let animations = if p.animate {
anim::build_boss_clips(&br.rig, p)
} else {
Vec::new()
};
// Serialize the archetype/element via serde so the metadata strings match
// the recipe's snake_case keys exactly (e.g. "swarm_queen", not the
// Debug-derived "SwarmQueen"/"swarmqueen") for clean round-trips.
let mut bm = BossMeta::new(
serde_json::to_value(p.archetype)
.ok()
.and_then(|v| v.as_str().map(str::to_owned))
.unwrap_or_default(),
serde_json::to_value(p.element)
.ok()
.and_then(|v| v.as_str().map(str::to_owned))
.unwrap_or_default(),
);
bm.weak_points = br.weak_points;
bm.parts = br.parts;
bm.arena.recommended_radius = (p.size * 2.7).max(4.0);
bm.phases = anim::build_phase_meta(p, &bm.weak_points);
Asset {
name: "boss".into(),
parts: vec![Part {
mesh,
material: Material {
roughness: 0.7,
emissive: pal.accent * emissive * 0.6,
..Default::default()
},
}],
skeleton: Some(br.rig.skeleton),
animations,
physics: Some(phys),
boss: Some(bm),
lods: Vec::new(),
instanced: Vec::new(),
}
}