render: skater-family character fade from the entity opacity field

This commit is contained in:
mchughalex 2026-07-17 11:19:34 +01:00
parent e19657a45d
commit bfb3c07839
3 changed files with 56 additions and 0 deletions

View file

@ -39,6 +39,15 @@ REXCVAR_DEFINE_BOOL(
"unchanged. Off = refused captures stay pending (rescue/fixup paths).") "unchanged. Off = refused captures stay pending (rescue/fixup paths).")
.lifecycle(rex::cvar::Lifecycle::kHotReload); .lifecycle(rex::cvar::Lifecycle::kHotReload);
REXCVAR_DEFINE_BOOL(
skate3_native_render_scene_entity_fade_serve, true, "Skate 3",
"Serve skater-family character fade from the entity's own opacity "
"field (+496, the exact value the game binds as the shader's alpha "
"parameter) instead of the captured alpha row. Fixes spawn fade-ins "
"rendered opaque when the row capture lags or fails. LivingWorld "
"entities keep the LW store's fade. Off = captured-row fade.")
.lifecycle(rex::cvar::Lifecycle::kHotReload);
REXCVAR_DEFINE_BOOL( REXCVAR_DEFINE_BOOL(
skate3_native_render_scene_entity_ropa_world_primary, true, "Skate 3", skate3_native_render_scene_entity_ropa_world_primary, true, "Skate 3",
"Serve the ROPA garment world from the owner entity's m_MatLtoWTrans " "Serve the ROPA garment world from the owner entity's m_MatLtoWTrans "
@ -481,6 +490,29 @@ bool ReadEntityWorldRows(uint8_t* base, uint32_t ctx, float out_rows[12]) {
ReadWorldRowsChecked(base, info.entity, out_rows); ReadWorldRowsChecked(base, info.entity, out_rows);
} }
bool ReadSkaterFade(uint8_t* base, uint32_t ctx, float* out_alpha) {
if (!REXCVAR_GET(skate3_native_render_scene_entity_fade_serve)) {
return false;
}
CtxInfo info;
if (!LookupCtx(ctx, &info)) {
return false;
}
const bool skater_family =
info.cls == EntClass::kSkater || info.cls == EntClass::kColorized ||
info.cls == EntClass::kCac || info.cls == EntClass::kSkaterAux;
if (!skater_family) {
return false;
}
float alpha = 0.0f;
if (!LoadF32Block(base, info.entity + kEntOpacitySkater, &alpha, 1) ||
!(alpha >= -0.01f && alpha <= 1.01f)) {
return false;
}
*out_alpha = alpha;
return true;
}
uint32_t ServeInstancePalette(uint8_t* base, uint32_t ctx, float* out, uint32_t ServeInstancePalette(uint8_t* base, uint32_t ctx, float* out,
uint32_t max_rows) { uint32_t max_rows) {
CtxInfo info; CtxInfo info;

View file

@ -113,6 +113,13 @@ void ObserveCharItem(uint8_t* base, uint32_t ctx, uint32_t family,
bool ServeRopaWorld(uint8_t* base, uint32_t ctx, uint32_t vb_obj, bool ServeRopaWorld(uint8_t* base, uint32_t ctx, uint32_t vb_obj,
float out_rows[12]); float out_rows[12]);
// Fade serve (skater-family classes only; LivingWorld entities are
// served by the LW store): the entity's live opacity at +496, the exact
// value BindConstants points the shader's alpha parameter at. Returns
// false for unmapped ctxs, non-skater classes, or an out-of-range read
// (teardown), leaving the caller's captured-row fade untouched.
bool ReadSkaterFade(uint8_t* base, uint32_t ctx, float* out_alpha);
// True when the ctx maps to an entity whose garment table no longer // True when the ctx maps to an entity whose garment table no longer
// claims vb_obj as a live cloth target; the game DROPPED the garment // claims vb_obj as a live cloth target; the game DROPPED the garment
// (m_numClothModels back to 0, VB objects freed, memory recycled). The // (m_numClothModels back to 0, VB objects freed, memory recycled). The

View file

@ -9272,10 +9272,27 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
// Read-only: the ident[] stats line is the evidence for flipping any // Read-only: the ident[] stats line is the evidence for flipping any
// serve over to the direct fields. // serve over to the direct fields.
{ {
const bool serve_ent_fade =
REXCVAR_GET(skate3_native_render_scene_entity_fade);
for (DrawItem& item : scene.items) { for (DrawItem& item : scene.items) {
if (item.char_family == 0 || item.ctx == 0) { if (item.char_family == 0 || item.ctx == 0) {
continue; continue;
} }
// Skater-family fade from the entity's own opacity (+496): the
// value the game binds as the shader's alpha parameter. Same serving
// contract as the LW store (lw_alpha preferred by CharFadeAlpha;
// families whose shader composes the fade keep their captured rows
// bounded by it). LW-mapped items were already stamped above.
float ent_alpha = 1.0f;
if (serve_ent_fade && item.lw_alpha < 0.0f &&
skate3::native_entity::ReadSkaterFade(base, item.ctx, &ent_alpha)) {
item.lw_alpha = std::clamp(ent_alpha, 0.0f, 1.0f);
if (item.char_rows[14 * 4 + 1] > 0.0f &&
(item.char_family == 1 || item.char_family == 2 ||
item.char_family == 3 || item.char_family == 6)) {
item.char_rows[14 * 4 + 0] = item.lw_alpha;
}
}
skate3::native_entity::ObserveCharItem(base, item.ctx, skate3::native_entity::ObserveCharItem(base, item.ctx,
item.char_family, item.char_family,
item.lw_entity != 0, item.lw_entity != 0,