Lands the fading-secret-door feature and fixes the door "flip-back" that surfaced while testing it. #188 — fading-wall doors (e.g. "Pedestal Weak Spot") fade their wall part out via TransparentPartHook instead of swinging: - TranslucencyHookSink consumes TransparentPartHook -> TranslucencyFadeManager (per-(entity,part) linear translucency ramp; holds at End frame). - WbDrawDispatcher: new per-instance alpha SSBO (binding 7); ClassifyBatches takes opacityMultiplier (1 - translucency, per CMaterial::SetTranslucencySimple 0x005396f0) forcing AlphaBlend; fully-invisible parts skipped. - mesh_modern.vert/.frag: binding-7 InstanceAlphaBuf -> vOpacityMultiplier -> FragColor.a *= vOpacityMultiplier. - Register AP-89: the fade multiplies sampled texture alpha, not a separate D3D9 material alpha channel (observably identical for texture-alpha==1 surfaces). Door flip-back fix (affected BOTH #188 fading walls AND #187 sliding doors): a door/wall that finished opening holds a single unchanging frame, so the uncommitted IsEntityCurrentlyMoving cache-bypass narrowing dropped it onto the Tier-1 static cache -- which only remembers the REST pose + opacity 1.0 -- snapping it visually shut/opaque while physics stayed open. Reverted that narrowing: every Sequencer entity stays on the per-frame path (live pose + live fade opacity), the known-good pre-optimization behavior. The per-frame CPU cost that narrowing chased was a Debug-build artifact -- Release is GPU-bound (~200 fps in Sawato, measured), so the unconditional add is free where it matters. Left a code comment barring re-introduction. Tests: full Core suite green (2649 passed, 2 skipped). Live visual gate PASSED -- both fading-wall and sliding doors hold open. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
96 lines
3.9 KiB
C#
96 lines
3.9 KiB
C#
using System.Numerics;
|
|
using DatReaderWriter.Types;
|
|
|
|
namespace AcDream.Core.Physics;
|
|
|
|
/// <summary>
|
|
/// Phase E.1 — the consumption point for hooks that
|
|
/// <see cref="AnimationSequencer"/> emits during each Advance tick.
|
|
///
|
|
/// <para>
|
|
/// The sequencer mirrors ACE's <c>Sequence.execute_hooks</c> /
|
|
/// <c>PhysicsObj.add_anim_hook</c> by collecting all hooks that fire at
|
|
/// each crossed frame boundary; a per-entity tick loop drains
|
|
/// <see cref="AnimationSequencer.ConsumePendingHooks"/> and forwards each
|
|
/// hook to an <c>IAnimationHookSink</c> implementation.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// In production the sink fans hooks out to downstream subsystems:
|
|
/// <list type="bullet">
|
|
/// <item><description>
|
|
/// <see cref="SoundHook"/> / <see cref="SoundTableHook"/> /
|
|
/// <see cref="SoundTweakedHook"/> → Phase E.2 audio engine
|
|
/// (OpenAL voice allocation + 3D positional playback).
|
|
/// </description></item>
|
|
/// <item><description>
|
|
/// <see cref="CreateParticleHook"/> /
|
|
/// <see cref="DestroyParticleHook"/> / <see cref="StopParticleHook"/> /
|
|
/// <see cref="CallPESHook"/> / <see cref="DefaultScriptHook"/> /
|
|
/// <see cref="DefaultScriptPartHook"/> → Phase E.3 particle system.
|
|
/// </description></item>
|
|
/// <item><description>
|
|
/// <see cref="AttackHook"/> → Phase E.4 combat dispatcher
|
|
/// (animation-hook frame = damage frame for melee / thrown).
|
|
/// </description></item>
|
|
/// <item><description>
|
|
/// <see cref="TransparentPartHook"/> → #188
|
|
/// <c>AcDream.Core.Rendering.TranslucencyHookSink</c> (per-Setup-part
|
|
/// translucency ramp; see <c>TranslucencyFadeManager</c>).
|
|
/// </description></item>
|
|
/// <item><description>
|
|
/// <see cref="ReplaceObjectHook"/>,
|
|
/// <see cref="TransparentHook"/> (whole-object variant — NOT yet
|
|
/// ported, #188 scope was the per-part case only),
|
|
/// <see cref="LuminousHook"/>,
|
|
/// <see cref="DiffuseHook"/>,
|
|
/// <see cref="ScaleHook"/>,
|
|
/// <see cref="NoDrawHook"/>,
|
|
/// <see cref="SetOmegaHook"/>,
|
|
/// <see cref="TextureVelocityHook"/>,
|
|
/// <see cref="SetLightHook"/> →
|
|
/// GfxObjMesh / renderer state mutations on the target entity
|
|
/// (<see cref="SetLightHook"/> is the one exception already wired,
|
|
/// via <c>AcDream.Core.Lighting.LightingHookSink</c>).
|
|
/// </description></item>
|
|
/// <item><description>
|
|
/// <see cref="AnimationDoneHook"/> → UI / controller notifications
|
|
/// ("emote finished", "attack animation complete").
|
|
/// </description></item>
|
|
/// </list>
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// A <see cref="NullAnimationHookSink"/> is provided for headless tests and
|
|
/// for offline mode where audio/particles aren't desired.
|
|
/// </para>
|
|
/// </summary>
|
|
public interface IAnimationHookSink
|
|
{
|
|
/// <summary>
|
|
/// Called for each hook produced by <see cref="AnimationSequencer.Advance"/>.
|
|
/// </summary>
|
|
/// <param name="entityId">
|
|
/// Local WorldEntity id that produced this hook. The sink can use this
|
|
/// to attach side-effects to the right entity (e.g. 3D-positional
|
|
/// audio at that entity's world position).
|
|
/// </param>
|
|
/// <param name="entityWorldPosition">
|
|
/// Current world-space position of the entity, captured at tick time.
|
|
/// Pre-computed for the sink so each implementation doesn't have to
|
|
/// resolve it independently.
|
|
/// </param>
|
|
/// <param name="hook">The hook (a typed subclass of AnimationHook).</param>
|
|
void OnHook(uint entityId, Vector3 entityWorldPosition, AnimationHook hook);
|
|
}
|
|
|
|
/// <summary>
|
|
/// No-op <see cref="IAnimationHookSink"/> — discards every hook.
|
|
/// Used for tests + headless renders.
|
|
/// </summary>
|
|
public sealed class NullAnimationHookSink : IAnimationHookSink
|
|
{
|
|
public static readonly NullAnimationHookSink Instance = new();
|
|
private NullAnimationHookSink() { }
|
|
public void OnHook(uint entityId, Vector3 entityWorldPosition, AnimationHook hook) { }
|
|
}
|