fix: hold flame fixtures at a density that reads as fire

CHANDELIER01 authors one particle per second over a six second life, which
we reproduced faithfully and which sustains a single speck per candle — a
bare glow, no flame. Fixtures that look right sustain 20 to 60 particles;
authored rates vary wildly for the same intent. Since steady-state
population is rate times lifespan, floor the rate against the lifespan so
every flame fixture holds a comparable population. Removes the tracing that
established emission and draw were both working.
This commit is contained in:
Kelsi
2026-07-23 01:23:31 -07:00
parent 0ebc85be20
commit e16f0cf5dd

View File

@@ -101,6 +101,18 @@ void M2Renderer::emitParticles(M2Instance& inst, const M2ModelGPU& gpu, float dt
inst.currentSequenceIndex, gpu.globalSequenceDurations);
float life = interpFloat(em.lifespan, inst.animTime, inst.globalSequenceTime,
inst.currentSequenceIndex, gpu.globalSequenceDurations);
// A flame reads as a flame only when enough particles are alive at once.
// Authored rates vary wildly for the same visual intent — a candle asks
// for 40/s over half a second, CHANDELIER01 for 1/s over six seconds,
// which sustains a single speck per candle and looks like a bare glow.
// Steady-state population is rate x lifespan, so floor the rate against
// the lifespan to hold every fixture at a comparable density.
if (rate > 0.0f && life > 0.0f &&
(gpu.isLanternLike || gpu.isTorch || gpu.isBrazierOrFire || gpu.isKoboldFlame)) {
constexpr float kMinLiveParticles = 15.0f;
rate = std::max(rate, kMinLiveParticles / std::max(life, 0.1f));
}
if (rate <= 0.0f || life <= 0.0f) {
// Diagnostic: a lamp or flame whose emitter never fires produces a
// fixture that glows but shows no flame. Report each model once so a
@@ -196,20 +208,6 @@ void M2Renderer::emitParticles(M2Instance& inst, const M2ModelGPU& gpu, float dt
inst.particles.push_back(p);
// Diagnostic: flame fixtures report their first birth too, so a
// model that emits but never draws can be told apart from one that
// never emits at all.
if (!gpu.isSpellEffect && inst.particles.size() == 1 &&
(gpu.isLanternLike || gpu.isTorch || gpu.isBrazierOrFire)) {
static std::unordered_set<std::string> bornReported;
if (bornReported.insert(gpu.name).second) {
LOG_WARNING("Flame born: '", gpu.name, "' emitter=", ei,
" rate=", rate, " life=", life,
" tex=", (ei < gpu.particleTextures.size() &&
gpu.particleTextures[ei]) ? "ok" : "NULL",
" blend=", gpu.particleEmitters[ei].blendingType);
}
}
// Diagnostic: log first particle birth per spell effect instance
if (gpu.isSpellEffect && inst.particles.size() == 1) {
LOG_INFO("SpellEffect: first particle for '", gpu.name,
@@ -556,19 +554,6 @@ void M2Renderer::renderM2Particles(VkCommandBuffer cmd, VkDescriptorSet perFrame
const auto& gpu = *inst.cachedModel;
if (gpu.isInstancePortal) continue;
// Diagnostic counterpart to "Flame born": how many live particles a
// flame fixture presents to the draw pass, reported once per model.
const bool flameFixture = !gpu.isSpellEffect &&
(gpu.isLanternLike || gpu.isTorch || gpu.isBrazierOrFire);
bool reportThisModel = false;
if (flameFixture) {
static std::unordered_set<std::string> drawReported;
if (drawReported.insert(gpu.name).second) {
reportThisModel = true;
LOG_WARNING("Flame draw: '", gpu.name, "' liveParticles=",
inst.particles.size());
}
}
// Cache the last emitter's per-emitter state so adjacent particles
// sharing an emitter (the common case — particles from one source
@@ -635,17 +620,6 @@ void M2Renderer::renderM2Particles(VkCommandBuffer cmd, VkDescriptorSet perFrame
if (rawScale > 2.0f) alpha *= 0.02f;
if (cachedBlendType == 3 || cachedBlendType == 4) alpha *= 0.05f;
}
if (reportThisModel) {
reportThisModel = false;
LOG_WARNING("Flame particle: '", gpu.name,
"' lifeRatio=", lifeRatio,
" rawScale=", rawScale,
" scaleKeys=", em.particleScale.floatValues.size(),
" alpha=", alpha,
" alphaKeys=", em.particleAlpha.floatValues.size(),
" colorKeys=", em.particleColor.vec3Values.size(),
" blend=", static_cast<int>(cachedBlendType));
}
float scale = rawScale;
if (gpu.isSpellEffect) {
scale = std::max(rawScale * 1.5f, 0.15f);