From bfb3c078398bafddeed15a565f4eaa0a686de794 Mon Sep 17 00:00:00 2001 From: mchughalex <25671999+mchughalex@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:19:34 +0100 Subject: [PATCH] render: skater-family character fade from the entity opacity field --- src/native/skate3_native_entity.cpp | 32 +++++++++++++++++++++++++++++ src/native/skate3_native_entity.h | 7 +++++++ src/skate3_native_scene.cpp | 17 +++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/src/native/skate3_native_entity.cpp b/src/native/skate3_native_entity.cpp index 77819bd..84fd2e0 100644 --- a/src/native/skate3_native_entity.cpp +++ b/src/native/skate3_native_entity.cpp @@ -39,6 +39,15 @@ REXCVAR_DEFINE_BOOL( "unchanged. Off = refused captures stay pending (rescue/fixup paths).") .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( skate3_native_render_scene_entity_ropa_world_primary, true, "Skate 3", "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); } +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 max_rows) { CtxInfo info; diff --git a/src/native/skate3_native_entity.h b/src/native/skate3_native_entity.h index 4aa470b..caf4b75 100644 --- a/src/native/skate3_native_entity.h +++ b/src/native/skate3_native_entity.h @@ -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, 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 // claims vb_obj as a live cloth target; the game DROPPED the garment // (m_numClothModels back to 0, VB objects freed, memory recycled). The diff --git a/src/skate3_native_scene.cpp b/src/skate3_native_scene.cpp index 482c6df..800dadb 100644 --- a/src/skate3_native_scene.cpp +++ b/src/skate3_native_scene.cpp @@ -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 // serve over to the direct fields. { + const bool serve_ent_fade = + REXCVAR_GET(skate3_native_render_scene_entity_fade); for (DrawItem& item : scene.items) { if (item.char_family == 0 || item.ctx == 0) { 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, item.char_family, item.lw_entity != 0,