feat(teleport C): TAS-driven fade transit + retire TeleportArrivalController
Wires the dormant TeleportAnimSequencer as the transit driver: on PlayerTeleport the player holds in PortalSpace behind a full-screen fade (FadeOverlay) until the destination terrain is resident (TeleportWorldReady, gated on the priority-applied landblock), then materializes (Place), and after the world fades back in regains control + acks the server (FireLoginComplete). No movement resolves against the empty world, so the outbound cell frame can't corrupt. Outdoor changes from place-immediately back to hold-until-resident (now fast, not a band-aid). - FadeOverlay: fullscreen NDC black quad, alpha = ShowTunnel ? 1 : FadeAlpha. - Retires TeleportArrivalController + its 2 tests (TAS subsumes the driver role). - Divergence register: AD-2 updated to the new mechanism; AD-31 (fade vs swirl). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9ac719424c
commit
3ce1fae332
7 changed files with 237 additions and 407 deletions
121
src/AcDream.App/Rendering/FadeOverlay.cs
Normal file
121
src/AcDream.App/Rendering/FadeOverlay.cs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Fullscreen black quad at a given alpha — the teleport fade cover (retail-teleport
|
||||
/// spec §C, 2026-06-22). Drawn LAST in the frame (over the world + UI) so it covers
|
||||
/// everything; the <see cref="AcDream.Core.World.TeleportAnimSequencer"/> drives the alpha
|
||||
/// (opaque-black through the transit, ramping the world back in on WorldFadeIn). The
|
||||
/// authentic 3D portal-swirl is deferred — this black cover replaces it for now.
|
||||
///
|
||||
/// <para>Draws in NDC, so it needs no view/projection. Self-contained GL state
|
||||
/// (feedback_render_self_contained_gl_state): sets blend + disables depth, restores the
|
||||
/// frame-global convention (depth test/write on) on exit.</para>
|
||||
/// </summary>
|
||||
public sealed class FadeOverlay : IDisposable
|
||||
{
|
||||
private const string VertSrc = @"#version 430 core
|
||||
layout(location = 0) in vec2 aPos;
|
||||
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }";
|
||||
|
||||
private const string FragSrc = @"#version 430 core
|
||||
uniform float uAlpha;
|
||||
out vec4 FragColor;
|
||||
void main() { FragColor = vec4(0.0, 0.0, 0.0, uAlpha); }";
|
||||
|
||||
private readonly GL _gl;
|
||||
private readonly uint _program;
|
||||
private readonly uint _vao;
|
||||
private readonly uint _vbo;
|
||||
private readonly int _locAlpha;
|
||||
|
||||
// Two triangles covering the whole NDC viewport.
|
||||
private static readonly float[] QuadVerts =
|
||||
{
|
||||
-1f, -1f, 1f, -1f, 1f, 1f,
|
||||
-1f, -1f, 1f, 1f, -1f, 1f,
|
||||
};
|
||||
|
||||
public FadeOverlay(GL gl)
|
||||
{
|
||||
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
||||
|
||||
uint vs = Compile(ShaderType.VertexShader, VertSrc);
|
||||
uint fs = Compile(ShaderType.FragmentShader, FragSrc);
|
||||
_program = _gl.CreateProgram();
|
||||
_gl.AttachShader(_program, vs);
|
||||
_gl.AttachShader(_program, fs);
|
||||
_gl.LinkProgram(_program);
|
||||
_gl.GetProgram(_program, ProgramPropertyARB.LinkStatus, out int linked);
|
||||
if (linked == 0)
|
||||
throw new InvalidOperationException($"FadeOverlay link failed: {_gl.GetProgramInfoLog(_program)}");
|
||||
_gl.DeleteShader(vs);
|
||||
_gl.DeleteShader(fs);
|
||||
|
||||
_locAlpha = _gl.GetUniformLocation(_program, "uAlpha");
|
||||
|
||||
_vao = _gl.GenVertexArray();
|
||||
_vbo = _gl.GenBuffer();
|
||||
_gl.BindVertexArray(_vao);
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo);
|
||||
unsafe
|
||||
{
|
||||
fixed (float* v = QuadVerts)
|
||||
_gl.BufferData(BufferTargetARB.ArrayBuffer,
|
||||
(nuint)(QuadVerts.Length * sizeof(float)), v, BufferUsageARB.StaticDraw);
|
||||
_gl.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 2 * sizeof(float), (void*)0);
|
||||
}
|
||||
_gl.EnableVertexAttribArray(0);
|
||||
_gl.BindVertexArray(0);
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0);
|
||||
}
|
||||
|
||||
private uint Compile(ShaderType type, string src)
|
||||
{
|
||||
uint s = _gl.CreateShader(type);
|
||||
_gl.ShaderSource(s, src);
|
||||
_gl.CompileShader(s);
|
||||
_gl.GetShader(s, ShaderParameterName.CompileStatus, out int ok);
|
||||
if (ok == 0)
|
||||
throw new InvalidOperationException($"FadeOverlay {type} compile failed: {_gl.GetShaderInfoLog(s)}");
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the fullscreen black cover at <paramref name="alpha"/> (0 = clear → no-op,
|
||||
/// 1 = opaque). Must be called LAST in the frame, after the world and UI, so it covers
|
||||
/// everything on screen.
|
||||
/// </summary>
|
||||
public void Draw(float alpha)
|
||||
{
|
||||
if (alpha <= 0f) return;
|
||||
alpha = Math.Clamp(alpha, 0f, 1f);
|
||||
|
||||
// ---- set state (everything this draw depends on) ----
|
||||
_gl.Disable(EnableCap.DepthTest);
|
||||
_gl.DepthMask(false);
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
_gl.Enable(EnableCap.Blend);
|
||||
_gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
|
||||
_gl.UseProgram(_program);
|
||||
_gl.Uniform1(_locAlpha, alpha);
|
||||
_gl.BindVertexArray(_vao);
|
||||
_gl.DrawArrays(PrimitiveType.Triangles, 0, 6);
|
||||
_gl.BindVertexArray(0);
|
||||
_gl.UseProgram(0);
|
||||
|
||||
// ---- restore the frame-global convention ----
|
||||
_gl.DepthMask(true);
|
||||
_gl.Enable(EnableCap.DepthTest);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gl.DeleteProgram(_program);
|
||||
_gl.DeleteVertexArray(_vao);
|
||||
_gl.DeleteBuffer(_vbo);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue