Compare commits
No commits in common. "main" and "claude/strange-ardinghelli-d810cd" have entirely different histories.
main
...
claude/str
1
.gitattributes
vendored
|
|
@ -1 +0,0 @@
|
|||
.github/workflows/*.lock.yml linguist-generated=true merge=ours
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
# Gitea Actions CI gate for the self-hosted runners.
|
||||
#
|
||||
# Deliberately does NOT use actions/setup-dotnet: data.forgejo.org (the mirror
|
||||
# Gitea resolves actions from) does not host that action at all, and the
|
||||
# self-hosted runners carry the pinned SDK band from global.json already.
|
||||
# actions/checkout IS mirrored, so it is used normally.
|
||||
#
|
||||
# The suite runs through tools/run-release-gate.ps1 rather than a bare
|
||||
# `dotnet test`: that script owns the xUnit trait-lane filter which excludes
|
||||
# the InstalledDat / Live / Manual / OS-specific lanes. A bare `dotnet test`
|
||||
# fails ~36 tests by design because those lanes assert their own preconditions.
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
# Docs-only pushes change nothing a test can fail on, and each gate run is
|
||||
# ~7 minutes of clean build + 14k tests + a 121 MB release. Skip them; a
|
||||
# code push (or manual dispatch) still runs everything from scratch —
|
||||
# deliberately uncached, so the gate keeps proving a from-nothing build.
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- 'claude-memory/**'
|
||||
- 'memory/**'
|
||||
- '**.md'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
windows-gate:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Verify the pinned SDK band resolves
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet --version
|
||||
dotnet --list-sdks
|
||||
|
||||
# NOT tools/run-release-gate.ps1 here. That script redirects every child
|
||||
# process to its own log file, so the step emits nothing for minutes at a
|
||||
# time; Forgejo treats a task that stops reporting as a zombie and fails
|
||||
# it while the work is still running (observed: job marked failed with 20
|
||||
# dotnet processes still alive and a complete 8.7 MB TRX on disk). Running
|
||||
# the projects directly keeps output streaming. The script stays the
|
||||
# canonical LOCAL gate; the trait filter below is copied from its default.
|
||||
- name: Build
|
||||
shell: pwsh
|
||||
run: dotnet build AcDream.slnx -c Release --nologo
|
||||
|
||||
- name: Test (lane-filtered, streaming)
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$filter = 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=Linux&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure'
|
||||
$failed = @()
|
||||
foreach ($proj in Get-ChildItem tests -Directory | Sort-Object Name) {
|
||||
$csproj = Join-Path $proj.FullName "$($proj.Name).csproj"
|
||||
if (-not (Test-Path $csproj)) { continue }
|
||||
Write-Host "::group::$($proj.Name)"
|
||||
dotnet test $csproj -c Release --no-build --nologo --filter $filter
|
||||
if ($LASTEXITCODE -ne 0) { $failed += $proj.Name }
|
||||
Write-Host "::endgroup::"
|
||||
}
|
||||
if ($failed.Count) { throw "Failed test projects: $($failed -join ', ')" }
|
||||
|
||||
linux-portable:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Portable closure (Linux lanes run here, not on Windows)
|
||||
run: |
|
||||
set -e
|
||||
dotnet --version
|
||||
# Core.Net runs SINGLE-THREADED here, on its own, and the split is
|
||||
# measured rather than defensive: on this 6-core container the
|
||||
# assembly FAILS in 40 s with default parallelism and PASSES in 10 s
|
||||
# with one thread. Its sessions do real socket work on background
|
||||
# threads, so contention both breaks and slows them. Windows has 18
|
||||
# cores, passes in ~7 s parallel, and REGRESSED when serialized, so
|
||||
# this stays scoped to Linux.
|
||||
echo '::group::AcDream.Core.Net.Tests (single-threaded)'
|
||||
dotnet test tests/AcDream.Core.Net.Tests/AcDream.Core.Net.Tests.csproj \
|
||||
-c Release --nologo \
|
||||
--filter 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure' \
|
||||
-- xUnit.MaxParallelThreads=1
|
||||
echo '::endgroup::'
|
||||
|
||||
for p in \
|
||||
tests/AcDream.Platform.Tests \
|
||||
tests/AcDream.Core.Tests \
|
||||
tests/AcDream.Content.Tests \
|
||||
tests/AcDream.Runtime.Tests \
|
||||
tests/AcDream.Headless.Tests \
|
||||
tests/AcDream.Launcher.Core.Tests \
|
||||
tests/AcDream.UI.Abstractions.Tests ; do
|
||||
echo "::group::$p"
|
||||
dotnet test "$p" -c Release --nologo \
|
||||
--filter 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure'
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
release:
|
||||
# Same workflow rather than a workflow_run trigger: workflow_run is a
|
||||
# GitHub feature whose Forgejo support is unreliable, while `needs` is
|
||||
# guaranteed. A red gate therefore cannot publish.
|
||||
needs: [windows-gate, linux-portable]
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Compute release version
|
||||
id: ver
|
||||
shell: pwsh
|
||||
run: |
|
||||
$v = '0.1.0-build.{0}' -f ([DateTime]::UtcNow.ToString('yyyyMMddHHmm'))
|
||||
"version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
||||
Write-Host "release version: $v"
|
||||
|
||||
- name: Build payloads with release-attachment URLs
|
||||
shell: pwsh
|
||||
env:
|
||||
TAG: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
./tools/publish-bin.ps1 -Version $env:TAG -BaseUrl "${{ github.server_url }}/${{ github.repository }}/releases/download/$env:TAG"
|
||||
|
||||
- name: Create the release and upload payloads
|
||||
shell: pwsh
|
||||
env:
|
||||
TAG: ${{ steps.ver.outputs.version }}
|
||||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$api = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
||||
$headers = @{ Authorization = "token $env:TOKEN" }
|
||||
$body = @{
|
||||
tag_name = $env:TAG
|
||||
name = "acdream alpha $env:TAG"
|
||||
body = "Automated alpha build from ${{ github.sha }}."
|
||||
draft = $false
|
||||
prerelease = $true
|
||||
target_commitish = 'main'
|
||||
} | ConvertTo-Json
|
||||
$release = Invoke-RestMethod -Method Post -Uri "$api/releases" -Headers $headers -ContentType 'application/json' -Body $body
|
||||
Write-Host "created release id=$($release.id)"
|
||||
foreach ($f in Get-ChildItem bin -File) {
|
||||
Write-Host ("uploading {0} ({1:N1} MB)" -f $f.Name, ($f.Length/1MB))
|
||||
Invoke-RestMethod -Method Post -Headers $headers -Uri "$api/releases/$($release.id)/assets?name=$($f.Name)" -Form @{ attachment = Get-Item $f.FullName } | Out-Null
|
||||
}
|
||||
|
||||
- name: Republish the `latest` pointer release
|
||||
shell: pwsh
|
||||
env:
|
||||
TAG: ${{ steps.ver.outputs.version }}
|
||||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$api = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
||||
$headers = @{ Authorization = "token $env:TOKEN" }
|
||||
|
||||
# Forgejo has no /releases/latest/download/ route, so the launcher
|
||||
# needs a pointer at a URL that never changes. A one-asset release on
|
||||
# the fixed `latest` tag is that pointer. Keeping it in a release
|
||||
# rather than in git means no payload branch, no bot commits on main,
|
||||
# and no push that would retrigger this workflow.
|
||||
$existing = Invoke-RestMethod -Method Get -Headers $headers `
|
||||
-Uri "$api/releases/tags/latest" -SkipHttpErrorCheck
|
||||
if ($existing.id) {
|
||||
Invoke-RestMethod -Method Delete -Headers $headers -Uri "$api/releases/$($existing.id)" | Out-Null
|
||||
# The tag outlives its release and would block recreation.
|
||||
Invoke-RestMethod -Method Delete -Headers $headers -Uri "$api/tags/latest" -SkipHttpErrorCheck | Out-Null
|
||||
Write-Host "removed the previous latest pointer"
|
||||
}
|
||||
|
||||
$body = @{
|
||||
tag_name = 'latest'
|
||||
name = "Update feed -> $env:TAG"
|
||||
body = "**Download ``launcher-win-x64.zip``**, unzip it, and run ``acdream-launcher.exe``. It installs the game and keeps itself and the client up to date.`n`nThis is build ``$env:TAG``."
|
||||
draft = $false
|
||||
prerelease = $false
|
||||
target_commitish = 'main'
|
||||
} | ConvertTo-Json
|
||||
$pointer = Invoke-RestMethod -Method Post -Uri "$api/releases" -Headers $headers `
|
||||
-ContentType 'application/json' -Body $body
|
||||
|
||||
# Upload the payloads here too, not just the manifest. `latest` is the
|
||||
# top of the Releases page and the first thing a person sees; a
|
||||
# pointer-only release gives them nothing to click and makes them hunt
|
||||
# for a build tagged with a timestamp. The launcher only needs
|
||||
# manifest.json, but a friend needs launcher-win-x64.zip.
|
||||
foreach ($f in Get-ChildItem bin -File) {
|
||||
Invoke-RestMethod -Method Post -Headers $headers `
|
||||
-Uri "$api/releases/$($pointer.id)/assets?name=$($f.Name)" `
|
||||
-Form @{ attachment = Get-Item $f.FullName } | Out-Null
|
||||
}
|
||||
Write-Host "latest now carries $env:TAG and its downloads"
|
||||
|
||||
- name: Prune old releases
|
||||
shell: pwsh
|
||||
env:
|
||||
KEEP: '5'
|
||||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$api = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
||||
$headers = @{ Authorization = "token $env:TOKEN" }
|
||||
$keep = [int]$env:KEEP
|
||||
|
||||
# Each build is ~121 MB of attachments, so without this the server
|
||||
# grows by that much on EVERY push to main. Keep the newest $keep
|
||||
# versioned releases: enough to grab a previous build or bisect a
|
||||
# regression, bounded at well under a gigabyte.
|
||||
$releases = Invoke-RestMethod -Method Get -Headers $headers -Uri "$api/releases?limit=100"
|
||||
# Never touch the `latest` pointer — it is the launcher's feed, not a build.
|
||||
$builds = @($releases | Where-Object { $_.tag_name -ne 'latest' } |
|
||||
Sort-Object -Property created_at -Descending)
|
||||
|
||||
Write-Host "$($builds.Count) versioned release(s); keeping $keep"
|
||||
foreach ($old in ($builds | Select-Object -Skip $keep)) {
|
||||
Invoke-RestMethod -Method Delete -Headers $headers -Uri "$api/releases/$($old.id)" | Out-Null
|
||||
# The tag survives its release and would otherwise accumulate.
|
||||
Invoke-RestMethod -Method Delete -Headers $headers -Uri "$api/tags/$($old.tag_name)" -SkipHttpErrorCheck | Out-Null
|
||||
Write-Host " pruned $($old.tag_name)"
|
||||
}
|
||||
236
.github/agents/agentic-workflows.agent.md
vendored
|
|
@ -1,236 +0,0 @@
|
|||
---
|
||||
description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# GitHub Agentic Workflows Agent
|
||||
|
||||
This agent helps you work with **GitHub Agentic Workflows (gh-aw)**, a CLI extension for creating AI-powered workflows in natural language using markdown files.
|
||||
|
||||
## What This Agent Does
|
||||
|
||||
This is a **dispatcher agent** that routes your request to the appropriate specialized prompt based on your task:
|
||||
|
||||
- **Creating new workflows**: Routes to `create` prompt
|
||||
- **Updating existing workflows**: Routes to `update` prompt
|
||||
- **Debugging workflows**: Routes to `debug` prompt
|
||||
- **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt
|
||||
- **Creating report-generating workflows**: Routes to `report` prompt — consult this whenever the workflow posts status updates, audits, analyses, or any structured output as issues, discussions, or comments
|
||||
- **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt
|
||||
- **Fixing Dependabot PRs**: Routes to `dependabot` prompt — use this when Dependabot opens PRs that modify generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`). Never merge those PRs directly; instead update the source `.md` files and rerun `gh aw compile --dependabot` to bundle all fixes
|
||||
- **Analyzing test coverage**: Routes to `test-coverage` prompt — consult this whenever the workflow reads, analyzes, or reports on test coverage data from PRs or CI runs
|
||||
- **Rendering ASCII charts in markdown**: Routes to `asciicharts` guide — consult this whenever the workflow needs compact charts that render reliably in GitHub issues, comments, or discussions
|
||||
- **CLI commands and triggering workflows**: Routes to `cli-commands` guide — consult this whenever the user asks how to run, compile, debug, or manage workflows from the command line, or when they need the MCP tool equivalent of a `gh aw` command
|
||||
- **Reducing token consumption / cost optimization**: Routes to `token-optimization` guide — consult this whenever the user asks how to reduce token usage, lower costs, speed up workflows, or measure the impact of prompt changes with experiments
|
||||
- **Choosing workflow architectures and design patterns**: Routes to `patterns` guide — consult this whenever the user asks for strategy, architecture, operating models, or pattern selection for agentic workflows
|
||||
|
||||
> [!IMPORTANT]
|
||||
> For architecture/pattern-selection requests, load `https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/patterns.md` first.
|
||||
|
||||
Workflows may optionally include:
|
||||
|
||||
- **Project tracking / monitoring** (GitHub Projects updates, status reporting)
|
||||
- **Orchestration / coordination** (one workflow assigning agents or dispatching and coordinating other workflows)
|
||||
|
||||
## Files This Applies To
|
||||
|
||||
- Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md`
|
||||
- Workflow lock files: `.github/workflows/*.lock.yml`
|
||||
- Shared components: `.github/workflows/shared/*.md`
|
||||
- Configuration: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/github-agentic-workflows.md
|
||||
|
||||
## Problems This Solves
|
||||
|
||||
- **Workflow Creation**: Design secure, validated agentic workflows with proper triggers, tools, and permissions
|
||||
- **Workflow Debugging**: Analyze logs, identify missing tools, investigate failures, and fix configuration issues
|
||||
- **Version Upgrades**: Migrate workflows to new gh-aw versions, apply codemods, fix breaking changes
|
||||
- **Component Design**: Create reusable shared workflow components that wrap MCP servers
|
||||
|
||||
## How to Use
|
||||
|
||||
When you interact with this agent, it will:
|
||||
|
||||
1. **Understand your intent** - Determine what kind of task you're trying to accomplish
|
||||
2. **Route to the right prompt** - Load the specialized prompt file for your task
|
||||
3. **Execute the task** - Follow the detailed instructions in the loaded prompt
|
||||
|
||||
## Available Prompts
|
||||
|
||||
### Create New Workflow
|
||||
**Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet
|
||||
|
||||
**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/create-agentic-workflow.md
|
||||
|
||||
**Use cases**:
|
||||
- "Create a workflow that triages issues"
|
||||
- "I need a workflow to label pull requests"
|
||||
- "Design a weekly research automation"
|
||||
|
||||
### Update Existing Workflow
|
||||
**Load when**: User wants to modify, improve, or refactor an existing workflow
|
||||
|
||||
**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/update-agentic-workflow.md
|
||||
|
||||
**Use cases**:
|
||||
- "Add web-fetch tool to the issue-classifier workflow"
|
||||
- "Update the PR reviewer to use discussions instead of issues"
|
||||
- "Improve the prompt for the weekly-research workflow"
|
||||
|
||||
### Debug Workflow
|
||||
**Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors
|
||||
|
||||
**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/debug-agentic-workflow.md
|
||||
|
||||
**Use cases**:
|
||||
- "Why is this workflow failing?"
|
||||
- "Analyze the logs for workflow X"
|
||||
- "Investigate missing tool calls in run #12345"
|
||||
|
||||
### Upgrade Agentic Workflows
|
||||
**Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations
|
||||
|
||||
**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/upgrade-agentic-workflows.md
|
||||
|
||||
**Use cases**:
|
||||
- "Upgrade all workflows to the latest version"
|
||||
- "Fix deprecated fields in workflows"
|
||||
- "Apply breaking changes from the new release"
|
||||
|
||||
### Create a Report-Generating Workflow
|
||||
**Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment
|
||||
|
||||
**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/report.md
|
||||
|
||||
**Use cases**:
|
||||
- "Create a weekly CI health report"
|
||||
- "Post a daily security audit to Discussions"
|
||||
- "Add a status update comment to open PRs"
|
||||
|
||||
### Create Shared Agentic Workflow
|
||||
**Load when**: User wants to create a reusable workflow component or wrap an MCP server
|
||||
|
||||
**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/create-shared-agentic-workflow.md
|
||||
|
||||
**Use cases**:
|
||||
- "Create a shared component for Notion integration"
|
||||
- "Wrap the Slack MCP server as a reusable component"
|
||||
- "Design a shared workflow for database queries"
|
||||
|
||||
### Fix Dependabot PRs
|
||||
**Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`)
|
||||
|
||||
**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/dependabot.md
|
||||
|
||||
**Use cases**:
|
||||
- "Fix the open Dependabot PRs for npm dependencies"
|
||||
- "Bundle and close the Dependabot PRs for workflow dependencies"
|
||||
- "Update @playwright/test to fix the Dependabot PR"
|
||||
|
||||
### Analyze Test Coverage
|
||||
**Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy.
|
||||
|
||||
**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/test-coverage.md
|
||||
|
||||
**Use cases**:
|
||||
- "Create a workflow that comments coverage on PRs"
|
||||
- "Analyze coverage trends over time"
|
||||
- "Add a coverage gate that blocks PRs below a threshold"
|
||||
|
||||
### Render ASCII Charts in Markdown
|
||||
**Load when**: The workflow needs in-markdown charts (sparklines, bars, table+trend views) that must align cleanly and render reliably across GitHub surfaces, including mobile.
|
||||
|
||||
**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/asciicharts.md
|
||||
|
||||
**Use cases**:
|
||||
- "Show a compact trend chart in an issue comment"
|
||||
- "Render a dashboard table with sparkline trends"
|
||||
- "Generate aligned ASCII bars for service metrics"
|
||||
|
||||
### CLI Commands Reference
|
||||
**Load when**: The user asks how to run, compile, debug, or manage workflows from the command line; needs the MCP tool equivalent of a `gh aw` command; or is in a restricted environment (e.g., Copilot Cloud) without direct CLI access.
|
||||
|
||||
**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/cli-commands.md
|
||||
|
||||
**Use cases**:
|
||||
- "How do I trigger workflow X on the main branch?"
|
||||
- "What's the MCP equivalent of `gh aw logs`?"
|
||||
- "I'm in Copilot Cloud — how do I compile a workflow?"
|
||||
- "Show me all available gh aw commands"
|
||||
|
||||
### Token Consumption Optimization
|
||||
**Load when**: The user asks how to reduce token usage, lower workflow costs, make a workflow faster or cheaper, or measure the impact of prompt or configuration changes.
|
||||
|
||||
**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/token-optimization.md
|
||||
|
||||
**Use cases**:
|
||||
- "How do I reduce the token cost of this workflow?"
|
||||
- "My workflow is too expensive — how do I optimize it?"
|
||||
- "How do I compare token usage between two runs?"
|
||||
- "Should I use gh-proxy or the MCP server?"
|
||||
- "How do I use sub-agents to reduce costs?"
|
||||
- "How do I measure the impact of a prompt change?"
|
||||
|
||||
### Workflow Pattern Selection
|
||||
**Load when**: The user asks for architecture, strategy, operating model selection, or pattern recommendations for building agentic workflows.
|
||||
|
||||
**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/patterns.md
|
||||
|
||||
**Use cases**:
|
||||
- "Which pattern should I use for multi-repo rollout?"
|
||||
- "How should I structure this workflow architecture?"
|
||||
- "What pattern fits slash-command triage?"
|
||||
- "Should this be DispatchOps or DailyOps?"
|
||||
|
||||
## Instructions
|
||||
|
||||
When a user interacts with you:
|
||||
|
||||
1. **Identify the task type** from the user's request
|
||||
2. **Load the appropriate prompt** from the GitHub repository URLs listed above
|
||||
3. **Follow the loaded prompt's instructions** exactly
|
||||
4. **If uncertain**, ask clarifying questions to determine the right prompt
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Initialize repository for agentic workflows
|
||||
gh aw init
|
||||
|
||||
# Generate the lock file for a workflow
|
||||
gh aw compile [workflow-name]
|
||||
|
||||
# Trigger a workflow on demand (preferred over gh workflow run)
|
||||
gh aw run <workflow-name> # interactive input collection
|
||||
gh aw run <workflow-name> --ref main # run on a specific branch
|
||||
|
||||
# Debug workflow runs
|
||||
gh aw logs [workflow-name]
|
||||
gh aw audit <run-id>
|
||||
|
||||
# Upgrade workflows
|
||||
gh aw fix --write
|
||||
gh aw compile --validate
|
||||
```
|
||||
|
||||
## Key Features of gh-aw
|
||||
|
||||
- **Natural Language Workflows**: Write workflows in markdown with YAML frontmatter
|
||||
- **AI Engine Support**: Copilot, Claude, Codex, or custom engines
|
||||
- **MCP Server Integration**: Connect to Model Context Protocol servers for tools
|
||||
- **Safe Outputs**: Structured communication between AI and GitHub API
|
||||
- **Strict Mode**: Security-first validation and sandboxing
|
||||
- **Shared Components**: Reusable workflow building blocks
|
||||
- **Repo Memory**: Persistent git-backed storage for agents
|
||||
- **Sandboxed Execution**: All workflows run in the Agent Workflow Firewall (AWF) sandbox, enabling full `bash` and `edit` tools by default
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/github-agentic-workflows.md for complete documentation
|
||||
- Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud
|
||||
- Workflows must be compiled to `.lock.yml` files before running in GitHub Actions
|
||||
- **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF
|
||||
- Follow security best practices: minimal permissions, explicit network access, no template injection
|
||||
- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns.
|
||||
- **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself.
|
||||
- **Triggering runs**: Always use `gh aw run <workflow-name>` to trigger a workflow on demand — not `gh workflow run <file>.lock.yml`. `gh aw run` handles workflow resolution by short name, input parsing and validation, and correct run-tracking for agentic workflows. Use `--ref <branch>` to run on a specific branch.
|
||||
- **CLI commands reference**: For a complete guide on all `gh aw` commands and their MCP tool equivalents (for restricted environments), see https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/cli-commands.md
|
||||
11
.github/mcp.json
vendored
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"github-agentic-workflows": {
|
||||
"command": "gh",
|
||||
"args": [
|
||||
"aw",
|
||||
"mcp-server"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
3
.github/workflows/aw.json
vendored
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ghes": false
|
||||
}
|
||||
23
.github/workflows/copilot-setup-steps.yml
vendored
|
|
@ -1,23 +0,0 @@
|
|||
name: "Copilot Setup Steps"
|
||||
|
||||
# This workflow configures the environment for GitHub Copilot Agent with gh-aw MCP server
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
# The job MUST be called 'copilot-setup-steps' to be recognized by GitHub Copilot Agent
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Set minimal permissions for setup steps
|
||||
# Copilot Agent receives its own token with appropriate permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Install gh-aw extension
|
||||
uses: github/gh-aw-actions/setup-cli@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8
|
||||
with:
|
||||
version: v0.74.8
|
||||
485
.github/workflows/headless-portability.yml
vendored
|
|
@ -1,485 +0,0 @@
|
|||
name: Headless portability
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
portable-headless:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pinned .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
# No apt step here on purpose. This job's whole claim is that the closure
|
||||
# below is presentation-free: it builds Bake, Plugin.Abstractions, Core,
|
||||
# Core.Net, Content, Runtime and Headless, runs their tests, and invokes
|
||||
# the Headless CLI. Nothing in it opens a display, links GL, or calls
|
||||
# xvfb-run, so an "install the graphical smoke dependencies" step here was
|
||||
# both unnecessary and, being unconditional `sudo apt-get` in a
|
||||
# two-operating-system matrix, fatal on the windows-latest leg (exit 127,
|
||||
# `sudo: command not found`). The graphical jobs that do use xvfb-run and
|
||||
# jq run only on ubuntu-latest and take both from the runner image.
|
||||
- name: Build presentation-free closure
|
||||
shell: pwsh
|
||||
run: |
|
||||
$projects = @(
|
||||
"src/AcDream.Platform/AcDream.Platform.csproj",
|
||||
"src/AcDream.Launcher.Core/AcDream.Launcher.Core.csproj",
|
||||
"src/AcDream.Bake/AcDream.Bake.csproj",
|
||||
"src/AcDream.Plugin.Abstractions/AcDream.Plugin.Abstractions.csproj",
|
||||
"src/AcDream.Core/AcDream.Core.csproj",
|
||||
"src/AcDream.Core.Net/AcDream.Core.Net.csproj",
|
||||
"src/AcDream.Content/AcDream.Content.csproj",
|
||||
"src/AcDream.Runtime/AcDream.Runtime.csproj",
|
||||
"src/AcDream.Headless/AcDream.Headless.csproj"
|
||||
)
|
||||
foreach ($project in $projects) {
|
||||
dotnet build $project -c Release
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
# AcDream.Core.Tests still contains historical App integration fixtures,
|
||||
# so it is deliberately not a member of this no-App restore lane. Core is
|
||||
# built directly above and exercised through every portable downstream
|
||||
# test project below; the complete solution lane retains Core.Tests.
|
||||
- name: Test presentation-free closure
|
||||
shell: pwsh
|
||||
run: |
|
||||
$projects = @(
|
||||
"tests/AcDream.Platform.Tests/AcDream.Platform.Tests.csproj",
|
||||
"tests/AcDream.Launcher.Core.Tests/AcDream.Launcher.Core.Tests.csproj",
|
||||
"tests/AcDream.Bake.Tests/AcDream.Bake.Tests.csproj",
|
||||
"tests/AcDream.Core.Net.Tests/AcDream.Core.Net.Tests.csproj",
|
||||
"tests/AcDream.Content.Tests/AcDream.Content.Tests.csproj",
|
||||
"tests/AcDream.Runtime.Tests/AcDream.Runtime.Tests.csproj",
|
||||
"tests/AcDream.Headless.Tests/AcDream.Headless.Tests.csproj"
|
||||
)
|
||||
foreach ($project in $projects) {
|
||||
dotnet test $project -c Release
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
- name: Verify CLI without connecting
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet run --project src/AcDream.Headless/AcDream.Headless.csproj -c Release -- --help
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
Set-Content -LiteralPath headless-k0.json -Value '{"version":1,"sessions":[]}'
|
||||
dotnet run --project src/AcDream.Headless/AcDream.Headless.csproj -c Release -- validate --config headless-k0.json
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Verify Linux headless host executable permission
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -x src/AcDream.Headless/bin/Release/net10.0/acdream-headless
|
||||
|
||||
portable-launcher:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pinned .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Build and test the portable launcher
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet build src/AcDream.Launcher/AcDream.Launcher.csproj -c Release
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
dotnet test tests/AcDream.Launcher.Tests/AcDream.Launcher.Tests.csproj -c Release
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Publish the self-contained launcher distribution
|
||||
shell: pwsh
|
||||
run: |
|
||||
$rid = if ($IsWindows) { "win-x64" } else { "linux-x64" }
|
||||
dotnet publish src/AcDream.Launcher/AcDream.Launcher.csproj `
|
||||
-c Release `
|
||||
-r $rid `
|
||||
-o "artifacts/acdream-launcher-$rid"
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Verify self-contained Windows launcher and bake artifacts
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$root = "artifacts/acdream-launcher-win-x64"
|
||||
if (-not (Test-Path -LiteralPath "$root/acdream-launcher.exe" -PathType Leaf)) { throw "launcher executable missing" }
|
||||
if (-not (Test-Path -LiteralPath "$root/acdream-bake.exe" -PathType Leaf)) { throw "bake executable missing" }
|
||||
if (Test-Path -LiteralPath "$root/acdream-launcher.dll") { throw "launcher is not single-file" }
|
||||
if (Test-Path -LiteralPath "$root/acdream-bake.dll") { throw "bake is not single-file" }
|
||||
$env:DOTNET_ROOT = "Z:\definitely-not-installed"
|
||||
$env:DOTNET_ROOT_X64 = "Z:\definitely-not-installed"
|
||||
$env:DOTNET_MULTILEVEL_LOOKUP = "0"
|
||||
& "$root/acdream-launcher.exe" --verify-publish
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
& "$root/acdream-bake.exe" --help
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Verify self-contained Linux launcher and bake artifacts
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
root=artifacts/acdream-launcher-linux-x64
|
||||
self_contained=$(dotnet msbuild \
|
||||
src/AcDream.Launcher/AcDream.Launcher.csproj \
|
||||
-nologo \
|
||||
-property:RuntimeIdentifier=linux-x64 \
|
||||
-getProperty:SelfContained | tr -d '\r\n ')
|
||||
test "$self_contained" = true
|
||||
test -x "$root/acdream-launcher"
|
||||
test -x "$root/acdream-bake"
|
||||
test ! -f "$root/acdream-launcher.dll"
|
||||
test ! -f "$root/acdream-bake.dll"
|
||||
DOTNET_ROOT=/definitely-not-installed \
|
||||
DOTNET_ROOT_X64=/definitely-not-installed \
|
||||
DOTNET_MULTILEVEL_LOOKUP=0 \
|
||||
"$root/acdream-launcher" --verify-publish
|
||||
DOTNET_ROOT=/definitely-not-installed \
|
||||
DOTNET_ROOT_X64=/definitely-not-installed \
|
||||
DOTNET_MULTILEVEL_LOOKUP=0 \
|
||||
"$root/acdream-bake" --help
|
||||
|
||||
linux-graphical:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pinned .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Build and publish Linux graphical client
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet build src/AcDream.App/AcDream.App.csproj -c Release -r linux-x64
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
dotnet publish src/AcDream.App/AcDream.App.csproj `
|
||||
-c Release `
|
||||
-r linux-x64 `
|
||||
--self-contained false `
|
||||
-o artifacts/acdream-linux-x64
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Test portable graphical boundary
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet test `
|
||||
tests/AcDream.UI.Abstractions.Tests/AcDream.UI.Abstractions.Tests.csproj `
|
||||
-c Release
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
dotnet test `
|
||||
tests/AcDream.App.Tests/AcDream.App.Tests.csproj `
|
||||
-c Release `
|
||||
--filter "FullyQualifiedName~LinuxMonotonicFramePacingWaiterTests|FullyQualifiedName~LinuxPlatformBoundaryTests|FullyQualifiedName~GraphicalHostPlatformServicesTests|FullyQualifiedName~GraphicalLegacyConfigurationMigratorTests|FullyQualifiedName~GraphicalWindowBackendSelectionTests"
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Verify Linux package contract
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
root=artifacts/acdream-linux-x64
|
||||
test -x "$root/AcDream.App"
|
||||
test -f "$root/AcDream.App.dll"
|
||||
test -f "$root/libglfw.so.3"
|
||||
test -f "$root/libopenal.so"
|
||||
test -f "$root/Rendering/Shaders/mesh_modern.vert"
|
||||
test -f "$root/plugins/AcDream.Plugins.Smoke/AcDream.Plugins.Smoke.dll"
|
||||
test -f "$root/plugins/AcDream.Plugins.Smoke/plugin.json"
|
||||
|
||||
test "$(grep -RIl --include='*.cs' 'LibraryImport(\"kernel32.dll\"' \
|
||||
src/AcDream.App | wc -l)" -eq 1
|
||||
grep -q 'WindowsHighResolutionFramePacingWaiter.cs' < <(
|
||||
grep -RIl --include='*.cs' 'LibraryImport(\"kernel32.dll\"' \
|
||||
src/AcDream.App)
|
||||
! grep -RIn --include='*.cs' 'LocalApplicationData' \
|
||||
src/AcDream.App src/AcDream.UI.Abstractions
|
||||
! grep -RIn --include='*.cs' \
|
||||
'WindowsHighResolutionFramePacingWaiter.Create()' \
|
||||
src/AcDream.App \
|
||||
--exclude='FramePacingWaiterFactory.cs' \
|
||||
--exclude='WindowsHighResolutionFramePacingWaiter.cs'
|
||||
|
||||
# Campaign V slice V9 built this job to prove lavapipe (Mesa's software
|
||||
# Vulkan) passes the capability gate: every Vulkan feature acdream requires
|
||||
# is core 1.3 or a descriptor-indexing feature lavapipe implements. That made
|
||||
# it the first CI job in the project's history to render a frame.
|
||||
#
|
||||
# Until Campaign V slice V11, linux-graphical (above) carried the mirror
|
||||
# case: llvmpipe (Mesa's software OpenGL) FAILING the capability gate,
|
||||
# because mandatory GL_ARB_bindless_texture has no llvmpipe implementation.
|
||||
# V11 deleted the GL backend entirely, so there is no more GL capability
|
||||
# gate for any driver to pass or fail — that job's "Verify actionable
|
||||
# unsupported-driver gate" step went with it. "Verify the forced-unsupported
|
||||
# gate exits 4" below is what now proves the exit-code-4 contract still
|
||||
# fires, forcing an unsupported VULKAN feature instead.
|
||||
#
|
||||
# NOT DONE HERE, deliberately: a GL-versus-Vulkan pixel comparison. It was
|
||||
# never viable in CI even before V11 — the probe harness renders synthetic
|
||||
# verification scenes rather than the world, and the world needs retail DATs
|
||||
# that CI does not have and cannot be given. The real GL-versus-Vulkan
|
||||
# differential was V7's, on the developer machine, against the DATs, with
|
||||
# both clocks pinned, before V11 deleted the GL arm it depended on.
|
||||
linux-vulkan:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Redirect the portable per-user roots into the workspace so the capability
|
||||
# report, which the app writes to its own diagnostics directory rather than
|
||||
# to a path a caller chooses, lands somewhere collectable. Slice L0 made
|
||||
# these XDG-driven precisely so a host could place them.
|
||||
env:
|
||||
XDG_CONFIG_HOME: ${{ github.workspace }}/artifacts/xdg/config
|
||||
XDG_DATA_HOME: ${{ github.workspace }}/artifacts/xdg/data
|
||||
XDG_CACHE_HOME: ${{ github.workspace }}/artifacts/xdg/cache
|
||||
VULKAN_REPORT: ${{ github.workspace }}/artifacts/xdg/cache/acdream/diagnostics/graphical-capabilities-vulkan.json
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pinned .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Install lavapipe, the Vulkan loader and Xvfb
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
jq \
|
||||
libvulkan1 \
|
||||
mesa-vulkan-drivers \
|
||||
vulkan-tools \
|
||||
xauth \
|
||||
xvfb
|
||||
|
||||
- name: Record the software Vulkan device
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p artifacts
|
||||
# Evidence, not configuration: no ICD is forced, because on a runner
|
||||
# with no GPU lavapipe is the only one the loader can find. If that
|
||||
# ever stops being true the DeviceType assertion below turns red
|
||||
# rather than silently measuring different hardware, which is the
|
||||
# outcome this row wants; its whole point is that the gate passes on
|
||||
# the weakest conformant device in existence.
|
||||
ls -l /usr/share/vulkan/icd.d/ || true
|
||||
vulkaninfo --summary 2>&1 | tee artifacts/vulkaninfo-summary.txt
|
||||
|
||||
- name: Build and publish the Linux graphical client
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet publish src/AcDream.App/AcDream.App.csproj `
|
||||
-c Release `
|
||||
-r linux-x64 `
|
||||
--self-contained false `
|
||||
-o artifacts/acdream-linux-x64
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Test the Vulkan backend's platform-independent decisions
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet test `
|
||||
tests/AcDream.App.Tests/AcDream.App.Tests.csproj `
|
||||
-c Release `
|
||||
--filter "FullyQualifiedName~AcDream.App.Tests.Rendering.Gpu.Vk"
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
# (a) + (b): one run, two gates. The harness opens a real window, runs the
|
||||
# capability gate against a real device, and presents the V6c/V6d
|
||||
# verification scenes through the real RHI. ACDREAM_VULKAN_PROBE_FRAMES is
|
||||
# what makes it terminate: nothing in CI ever closes a window.
|
||||
- name: Probe the Vulkan capability gate on lavapipe
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
root=artifacts/acdream-linux-x64
|
||||
out="$PWD/artifacts/vulkan-probe"
|
||||
mkdir -p "$out"
|
||||
|
||||
# 24-bit depth explicitly: xvfb-run's default screen is 8-bit, which
|
||||
# leaves the X11 WSI without a usable visual.
|
||||
ACDREAM_RENDER_BACKEND=vulkan \
|
||||
ACDREAM_VULKAN_PROBE=1 \
|
||||
ACDREAM_VULKAN_PROBE_FRAMES=30 \
|
||||
ACDREAM_AUTOMATION_ARTIFACT_DIR="$out" \
|
||||
ACDREAM_DISPLAY_PROTOCOL=x11 \
|
||||
ACDREAM_NO_AUDIO=1 \
|
||||
xvfb-run -a -s "-screen 0 1920x1080x24" \
|
||||
"$root/AcDream.App" /tmp/not-needed 2>&1 | tee artifacts/vulkan-probe.log
|
||||
|
||||
test -f "$VULKAN_REPORT"
|
||||
cp "$VULKAN_REPORT" artifacts/vulkan-capabilities-pass.json
|
||||
|
||||
jq -r '"device: \(.DeviceName) (\(.DeviceType)), \(.DeviceApiVersion), \(.DriverInfo)"' \
|
||||
"$VULKAN_REPORT"
|
||||
|
||||
# The gate accepted the device outright.
|
||||
jq -e '.SupportFailures | length == 0' "$VULKAN_REPORT"
|
||||
test "$(jq -r '.ActiveDisplayProtocol' "$VULKAN_REPORT")" = X11
|
||||
# A software device, which is the whole point of this row.
|
||||
test "$(jq -r '.DeviceType' "$VULKAN_REPORT")" = Cpu
|
||||
# Vulkan 1.3 floor, unpacked from VK_MAKE_API_VERSION: major is bits
|
||||
# 22+, minor is bits 12-21. Evaluate() already rejects anything lower,
|
||||
# so this asserts the report agrees with the verdict rather than
|
||||
# re-deriving it.
|
||||
jq -e '
|
||||
((.DeviceApiVersionPacked / 4194304) | floor) as $major
|
||||
| (((.DeviceApiVersionPacked % 4194304) / 4096) | floor) as $minor
|
||||
| $major > 1 or ($major == 1 and $minor >= 3)' "$VULKAN_REPORT"
|
||||
# Advertisement is not evidence: the active probe created the device,
|
||||
# built the descriptor layouts and a pipeline from committed .spv, drew
|
||||
# an offscreen triangle and read the pixels back.
|
||||
jq -e '.FunctionProbe.Failures | length == 0' "$VULKAN_REPORT"
|
||||
jq -e '.FunctionProbe.DeviceCreation and .FunctionProbe.OffscreenReadback' \
|
||||
"$VULKAN_REPORT"
|
||||
|
||||
- name: Assert the offline render produced a real frame
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
png=artifacts/vulkan-probe/vulkan-bringup.png
|
||||
test -f "$png"
|
||||
|
||||
# PNG IHDR carries the dimensions at bytes 16..23, big-endian. Reading
|
||||
# them proves the capture path returned a full backbuffer rather than a
|
||||
# stub, without depending on a rasterizer's pixel values.
|
||||
width=$(od -An -tu4 -j16 -N4 --endian=big "$png" | tr -d ' ')
|
||||
height=$(od -An -tu4 -j20 -N4 --endian=big "$png" | tr -d ' ')
|
||||
bytes=$(stat -c%s "$png")
|
||||
echo "captured ${width}x${height}, ${bytes} bytes"
|
||||
|
||||
test "$width" -ge 640
|
||||
test "$height" -ge 360
|
||||
# A uniform-colour frame at this size encodes to a few kilobytes. This
|
||||
# threshold is the "the scene actually drew" line, and is deliberately
|
||||
# a byte count rather than a pixel baseline: lavapipe and any other
|
||||
# rasterizer are free to disagree about shading, and nothing in CI has
|
||||
# a reference frame to disagree with.
|
||||
test "$bytes" -gt 8192
|
||||
|
||||
# (c) The gate's failure path, exercised on a device that actually
|
||||
# supports everything, so the exit-code-4 contract is proven rather than
|
||||
# assumed. Same knob slice V5 built for exactly this.
|
||||
- name: Verify the forced-unsupported gate exits 4
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
root=artifacts/acdream-linux-x64
|
||||
out="$PWD/artifacts/vulkan-forced"
|
||||
mkdir -p "$out"
|
||||
|
||||
set +e
|
||||
ACDREAM_RENDER_BACKEND=vulkan \
|
||||
ACDREAM_VULKAN_PROBE=1 \
|
||||
ACDREAM_VULKAN_PROBE_FRAMES=30 \
|
||||
ACDREAM_VULKAN_FORCE_UNSUPPORTED=timelineSemaphore \
|
||||
ACDREAM_AUTOMATION_ARTIFACT_DIR="$out" \
|
||||
ACDREAM_DISPLAY_PROTOCOL=x11 \
|
||||
ACDREAM_NO_AUDIO=1 \
|
||||
xvfb-run -a -s "-screen 0 1920x1080x24" \
|
||||
"$root/AcDream.App" /tmp/not-needed \
|
||||
> artifacts/vulkan-forced.log 2>&1
|
||||
code=$?
|
||||
set -e
|
||||
|
||||
cat artifacts/vulkan-forced.log
|
||||
test -f "$VULKAN_REPORT"
|
||||
cp "$VULKAN_REPORT" artifacts/vulkan-capabilities-forced.json
|
||||
|
||||
test "$code" -eq 4
|
||||
test "$(jq -r '.ForcedUnsupportedFeature' "$VULKAN_REPORT")" = timelineSemaphore
|
||||
test "$(jq -r '.Features.TimelineSemaphore' "$VULKAN_REPORT")" = false
|
||||
jq -e '.SupportFailures | length > 0' "$VULKAN_REPORT"
|
||||
jq -e '.SupportFailures | any(test("timelineSemaphore"))' "$VULKAN_REPORT"
|
||||
# The refusal must be actionable: the operator is told where the full
|
||||
# report is, not merely that something was unsupported.
|
||||
grep -q 'graphical-capabilities-vulkan.json' artifacts/vulkan-forced.log
|
||||
|
||||
# (d) The committed .spv are the only shaders the Vulkan backend ever
|
||||
# loads. An App test already re-hashes the GLSL sources against the
|
||||
# manifest, which catches "edited a shader, forgot to recompile". Nothing
|
||||
# until now tied the committed BINARIES to those sources, so a stale or
|
||||
# hand-edited .spv would have shipped silently. Recompiling here closes
|
||||
# that, and does it on a second operating system.
|
||||
- name: Verify the committed SPIR-V is fresh
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
committed=src/AcDream.App/Rendering/Shaders/spv
|
||||
fresh="$PWD/artifacts/spv-fresh"
|
||||
rm -rf "$fresh"
|
||||
mkdir -p "$fresh"
|
||||
|
||||
pwsh tools/compile-shaders.ps1 -OutputDirectory "$fresh"
|
||||
|
||||
# The file SET first: a .spv present in one tree and not the other is
|
||||
# drift the per-file compare would never visit.
|
||||
diff <(cd "$committed" && ls -1 | sort) <(cd "$fresh" && ls -1 | sort)
|
||||
|
||||
drift=0
|
||||
for path in "$committed"/*.spv; do
|
||||
name=$(basename "$path")
|
||||
if ! cmp -s "$path" "$fresh/$name"; then
|
||||
echo "DRIFT: $name differs from a fresh compile"
|
||||
echo " committed $(sha256sum "$path" | cut -d' ' -f1) $(stat -c%s "$path") bytes"
|
||||
echo " fresh $(sha256sum "$fresh/$name" | cut -d' ' -f1) $(stat -c%s "$fresh/$name") bytes"
|
||||
drift=$((drift + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# The manifest is compared as JSON rather than as bytes: it is written
|
||||
# with Environment.NewLine, so a byte compare would report drift for
|
||||
# the operating system rather than for the shaders.
|
||||
diff <(jq -S . "$committed/shaders.manifest.json") \
|
||||
<(jq -S . "$fresh/shaders.manifest.json")
|
||||
|
||||
if [ "$drift" -ne 0 ]; then
|
||||
echo "$drift .spv artifact(s) are stale."
|
||||
echo "Run tools/compile-shaders.ps1 and commit the result."
|
||||
exit 1
|
||||
fi
|
||||
echo "all committed .spv match a fresh compile"
|
||||
|
||||
- name: Upload Vulkan evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-vulkan-evidence
|
||||
if-no-files-found: warn
|
||||
path: |
|
||||
artifacts/vulkaninfo-summary.txt
|
||||
artifacts/vulkan-probe.log
|
||||
artifacts/vulkan-forced.log
|
||||
artifacts/vulkan-capabilities-pass.json
|
||||
artifacts/vulkan-capabilities-forced.json
|
||||
artifacts/vulkan-probe/*.png
|
||||
1347
.github/workflows/hygiene-assessment.lock.yml
vendored
146
.github/workflows/hygiene-assessment.md
vendored
|
|
@ -1,146 +0,0 @@
|
|||
---
|
||||
description: Daily hygiene assessment of acdream's main branch — flag workarounds,
|
||||
ungrounded code, Phase/roadmap drift, and architecture violations.
|
||||
on:
|
||||
schedule: daily
|
||||
workflow_dispatch: {}
|
||||
permissions: read-all
|
||||
network:
|
||||
allowed:
|
||||
- defaults
|
||||
- dotnet
|
||||
tools:
|
||||
github:
|
||||
toolsets: [default]
|
||||
safe-outputs:
|
||||
create-issue:
|
||||
max: 1
|
||||
close-older-issues: true
|
||||
labels:
|
||||
- ai
|
||||
- hygiene
|
||||
engine:
|
||||
id: copilot
|
||||
model: gpt-5.3-codex
|
||||
---
|
||||
|
||||
# acdream Hygiene Assessment
|
||||
|
||||
You are **DereLint**, a focused AI auditor for the acdream Asheron's Call client.
|
||||
Your job: scan `main` once a day and produce a single rolling report on hygiene
|
||||
drift. Engineer-grade tone. No persona slang. The audience is a senior C# /
|
||||
systems engineer who already operates under a strict retail-faithfulness rule.
|
||||
|
||||
## Mission
|
||||
|
||||
acdream's core rule (from `CLAUDE.md`): **"The code is modern. The behavior is
|
||||
retail."** Every AC-specific algorithm must be ported from
|
||||
`docs/research/named-retail/` (the Sept 2013 EoR PDB) and never guessed. The
|
||||
roadmap drives one phase at a time. Workarounds are forbidden unless the user
|
||||
has explicitly approved them. Drift from any of that is what you flag.
|
||||
|
||||
Before you start your analysis: `git fetch origin main && git checkout main`.
|
||||
Then read these to ground yourself:
|
||||
|
||||
- `CLAUDE.md` — the project's operating instructions (most important)
|
||||
- `docs/plans/2026-04-11-roadmap.md` — current phase, agreed order
|
||||
- `docs/plans/2026-05-12-milestones.md` — current milestone
|
||||
- `docs/ISSUES.md` — open issues you must NOT re-file
|
||||
- `docs/architecture/acdream-architecture.md` — architecture source of truth
|
||||
|
||||
## What to look for
|
||||
|
||||
Five categories. For each finding, cite `file:line`.
|
||||
|
||||
### 1. Workaround patterns (CLAUDE.md forbids these unless user-approved)
|
||||
|
||||
- `// WORKAROUND` / `// HACK` / `// FIXME` / `// XXX` comments
|
||||
- Guard early-returns at symptom sites (`if (badState) return;`) that look like
|
||||
band-aids rather than root-cause fixes
|
||||
- `try/catch` blocks swallowing exceptions silently
|
||||
- "grace period" timers / "settle delay" sleeps
|
||||
- Flags named like `_suppressXDuringY` that mask wire-level mistakes
|
||||
|
||||
### 2. Ungrounded retail-port code
|
||||
|
||||
- AC-specific algorithm code (collision, animation, motion, dat-decode,
|
||||
rendering math) that has **no decomp citation** in comments. Every
|
||||
retail-faithful port should reference a symbol from
|
||||
`docs/research/named-retail/symbols.json` or a function address from
|
||||
`docs/research/decompiled/`.
|
||||
- Magic numbers in physics / motion / wire-format paths that aren't cited
|
||||
against a retail source.
|
||||
|
||||
### 3. Roadmap drift
|
||||
|
||||
- Phase markers in code (`// Phase L.5:`, `// Phase N.4:`) that reference
|
||||
phases no longer matching the roadmap.
|
||||
- Sections of `docs/plans/2026-04-11-roadmap.md` flagged "ahead" / "active"
|
||||
that don't match what the last 20 commits actually touched.
|
||||
- The "Currently working toward" line in `CLAUDE.md` vs. what the last 20
|
||||
commit subjects actually touched. If they disagree, flag it.
|
||||
|
||||
### 4. Test / build hygiene
|
||||
|
||||
- `dotnet build` warnings (the project should build with zero warnings).
|
||||
- Tests in failing state (`dotnet test`).
|
||||
- Test count regression below the baseline documented in `CLAUDE.md`.
|
||||
- Build / launch needing `--no-build` workarounds anywhere.
|
||||
|
||||
### 5. Architecture drift
|
||||
|
||||
- `using WorldBuilder.*` outside `src/AcDream.App/Rendering/Wb/` and
|
||||
`src/AcDream.Core/Rendering/Wb/` (Phase O extracted WB code into those
|
||||
directories — references outside are a regression).
|
||||
- `Environment.GetEnvironmentVariable("ACDREAM_*")` calls outside diagnostic
|
||||
owner classes (per `CLAUDE.md` "Code Structure Rules" item 5).
|
||||
- `IDatReaderWriter` consumers that should be using `DatCollection`
|
||||
(post-Phase O: `DatCollection` is the only dat reader).
|
||||
- Code in `AcDream.Core` that references `AcDream.App` or GL types directly
|
||||
(layer separation violation per `CLAUDE.md` Code Structure Rules item 2).
|
||||
|
||||
## Accepted exceptions
|
||||
|
||||
If `docs/ISSUES.md` already has an OPEN entry for a finding, **don't re-file
|
||||
it**. Mention it under "Known accepted exceptions" instead. Same for items
|
||||
explicitly listed as deferred in the roadmap.
|
||||
|
||||
## Output
|
||||
|
||||
Create one GitHub Issue titled `acdream Hygiene Report YYYY-MM-DD`. The
|
||||
framework will close any prior `ai+hygiene`-labeled issues automatically.
|
||||
Body structure:
|
||||
|
||||
### Executive Summary
|
||||
|
||||
Two sentences on overall hygiene. Concrete; no fluff.
|
||||
|
||||
### Findings
|
||||
|
||||
For each: **Location** (file:line, linked to the source), **Category** (1-5),
|
||||
**Problem** (one sentence), **Recommendation** (one sentence),
|
||||
**Decomp/Doc reference** (where applicable — cite the named symbol or doc).
|
||||
|
||||
### Roadmap reality check
|
||||
|
||||
Currently-working-toward line vs. recent commit subjects. State whether they
|
||||
match or where they diverge.
|
||||
|
||||
### Known accepted exceptions
|
||||
|
||||
Issues already filed in `docs/ISSUES.md` that you observed during the scan.
|
||||
Name them by ID, don't re-file.
|
||||
|
||||
### Suggested next step
|
||||
|
||||
ONE concrete action the team should take. If everything is clean, call the
|
||||
`noop` safe-output with "All clear — no hygiene drift found." instead of
|
||||
creating an issue.
|
||||
|
||||
## Style
|
||||
|
||||
- Engineer tone. No slang.
|
||||
- Be specific. "Workaround in PhysicsEngine.cs:142" beats "physics has issues."
|
||||
- Be conservative. If you're unsure something is a workaround vs. an
|
||||
intentional retail-faithful port, say so — don't assert.
|
||||
- Keep the report under 1500 words. The team wants signal, not a wall of text.
|
||||
35
.github/workflows/release-gate.yml
vendored
|
|
@ -1,35 +0,0 @@
|
|||
name: Complete Release gate
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
complete-release:
|
||||
name: Complete Release suite (Windows)
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pinned .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Run complete bounded Release gate
|
||||
shell: pwsh
|
||||
run: ./tools/run-release-gate.ps1
|
||||
|
||||
- name: Upload Release gate evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-gate-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
path: artifacts/release-gate/
|
||||
56
.gitignore
vendored
|
|
@ -2,11 +2,6 @@
|
|||
bin/
|
||||
obj/
|
||||
out/
|
||||
# NOTE: the repo-root /bin folder holds the alpha distribution feed written by
|
||||
# tools/publish-bin.ps1. It stays IGNORED here on purpose so a stray `git add`
|
||||
# can never put ~150 MB of payloads on main (GitHub also hard-rejects any file
|
||||
# over 100 MB). tools/publish-dist.ps1 force-adds it onto the Gitea-only `dist`
|
||||
# branch instead, which is what the launcher's update feed reads.
|
||||
|
||||
# Rider / VS
|
||||
.idea/
|
||||
|
|
@ -23,31 +18,13 @@ packages/
|
|||
Thumbs.db
|
||||
|
||||
# Reference repos and retail client (large, not our code, separate licenses)
|
||||
# WorldBuilder is exempt — it's a load-bearing dependency tracked as a git
|
||||
# submodule pointing at our fork (Phase N, see docs/architecture/worldbuilder-inventory.md).
|
||||
references/*
|
||||
!references/WorldBuilder
|
||||
!references/WorldBuilder/
|
||||
references/
|
||||
|
||||
# Claude Code session state
|
||||
.claude/
|
||||
# Superpowers brainstorm visual-companion scratch (mockups regenerate; not source)
|
||||
/.superpowers/
|
||||
launch.log
|
||||
launch-*.log
|
||||
proveout*.log
|
||||
launch.utf8.log
|
||||
n4-verify*.log
|
||||
|
||||
# A6.P5 (2026-05-25) — door-stuck reproduction captures (multi-MB);
|
||||
# the 3-record fixture extracted from these lives at
|
||||
# tests/AcDream.Core.Tests/Fixtures/door-bug/over-penetration-capture.jsonl
|
||||
door-stuck-capture.jsonl
|
||||
door-stuck-*.launch.log
|
||||
door-stuck-*.launch.utf8.log
|
||||
door-fix-*.launch.log
|
||||
door-fix-*.jsonl
|
||||
door-walkthrough.*
|
||||
|
||||
# ImGui auto-saved window/docking state (per-user, not source)
|
||||
imgui.ini
|
||||
|
|
@ -62,12 +39,6 @@ __pycache__/
|
|||
# Per-session scratch (Claude commit message drafts, ad-hoc temp files)
|
||||
tmp/
|
||||
|
||||
# Disposable dotnet test/build output redirected by local validation runs
|
||||
.test-out/
|
||||
|
||||
# Connected-gate, benchmark, and visual-capture artifacts are machine-local
|
||||
logs/
|
||||
|
||||
# Git worktrees for isolated feature work
|
||||
.worktrees/
|
||||
|
||||
|
|
@ -75,8 +46,6 @@ logs/
|
|||
# The committed reference workflow lives in CLAUDE.md "Retail debugger toolchain";
|
||||
# session-specific traces should not pollute the repo.
|
||||
*.cdb
|
||||
# tools/cdb/ holds committed reference scripts — exempt them from the blanket rule above.
|
||||
!tools/cdb/*.cdb
|
||||
launch_*.log
|
||||
launch_*.err
|
||||
launch_*.ps1
|
||||
|
|
@ -95,26 +64,3 @@ substep_trace*
|
|||
sg_built.txt
|
||||
# Stray bash-mangled path artifacts from PowerShell-via-bash escaping
|
||||
C[-]*
|
||||
|
||||
# Obsidian vault config (personal, not project-wide)
|
||||
.obsidian/
|
||||
|
||||
# Junction to Claude Code per-project memory (Obsidian vault visibility)
|
||||
claude-memory
|
||||
studio-shots/
|
||||
|
||||
# MP1b acdream-bake output — user-machine artifact, never committed
|
||||
# (docs/superpowers/plans/2026-07-05-mp1b-pak-and-bake.md, Task 5).
|
||||
*.pak
|
||||
|
||||
# session-local physics capture artifacts (worktree root)
|
||||
/resolve-*.jsonl
|
||||
/launch-*.log
|
||||
|
||||
# Campaign V capture/evidence output - session-local, never tracked (423 MB lesson, 2026-07-29)
|
||||
artifacts/
|
||||
341-slope-capture.jsonl
|
||||
|
||||
# IconForge DAT extraction scratch (geometry + textures dumped from the
|
||||
# installed client dats; regenerate with tools/MosswartArt, never commit).
|
||||
tools/IconForge/work/
|
||||
|
|
|
|||
4
.gitmodules
vendored
|
|
@ -1,4 +0,0 @@
|
|||
[submodule "references/WorldBuilder"]
|
||||
path = references/WorldBuilder
|
||||
url = git@github.com:eriknihlen/WorldBuilder.git
|
||||
branch = acdream
|
||||
5
.vscode/settings.json
vendored
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"github.copilot.enable": {
|
||||
"markdown": true
|
||||
}
|
||||
}
|
||||
48
AcDream.slnx
|
|
@ -1,67 +1,21 @@
|
|||
<Solution>
|
||||
<Folder Name="/src/">
|
||||
<Project Path="src/AcDream.App/AcDream.App.csproj" />
|
||||
<Project Path="src/AcDream.Bake/AcDream.Bake.csproj" />
|
||||
<Project Path="src/AcDream.Cli/AcDream.Cli.csproj" />
|
||||
<Project Path="src/AcDream.Content/AcDream.Content.csproj" />
|
||||
<Project Path="src/AcDream.Core/AcDream.Core.csproj" />
|
||||
<Project Path="src/AcDream.Core.Net/AcDream.Core.Net.csproj" />
|
||||
<Project Path="src/AcDream.Headless/AcDream.Headless.csproj" />
|
||||
<Project Path="src/AcDream.Launcher/AcDream.Launcher.csproj" />
|
||||
<Project Path="src/AcDream.Launcher.Core/AcDream.Launcher.Core.csproj" />
|
||||
<Project Path="src/AcDream.Platform/AcDream.Platform.csproj" />
|
||||
<Project Path="src/AcDream.Plugin.Abstractions/AcDream.Plugin.Abstractions.csproj" />
|
||||
<Project Path="src/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.csproj" />
|
||||
<Project Path="src/AcDream.Plugins.Smoke/AcDream.Plugins.Smoke.csproj" />
|
||||
<Project Path="src/AcDream.Runtime/AcDream.Runtime.csproj" />
|
||||
<Project Path="src/AcDream.UI.Abstractions/AcDream.UI.Abstractions.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/samples/">
|
||||
<Project Path="samples/AcDream.RenderPacks.AtmosphericTier2/AcDream.RenderPacks.AtmosphericTier2.csproj" />
|
||||
<Project Path="samples/AcDream.RenderPacks.NoOp/AcDream.RenderPacks.NoOp.csproj" />
|
||||
<Project Path="samples/AcDream.RenderPacks.ShadowsOnlyTier2/AcDream.RenderPacks.ShadowsOnlyTier2.csproj" />
|
||||
<Project Path="src/AcDream.UI.ImGui/AcDream.UI.ImGui.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tools/">
|
||||
<Project Path="tools/A8CellAudit/A8CellAudit.csproj" />
|
||||
<Project Path="tools/AnimHookScan/AnimHookScan.csproj" />
|
||||
<Project Path="tools/dump-keymap/dump-keymap.csproj" />
|
||||
<Project Path="tools/LayoutDump/LayoutDump.csproj" />
|
||||
<Project Path="tools/MosswartArt/MosswartArt.csproj" />
|
||||
<Project Path="tools/PesChainAudit/PesChainAudit.csproj" />
|
||||
<Project Path="tools/ProjectileVfxAudit/ProjectileVfxAudit.csproj" />
|
||||
<Project Path="tools/RainMeshProbe/RainMeshProbe.csproj" />
|
||||
<Project Path="tools/RenderPackValidator/AcDream.Tools.RenderPackValidator.csproj" />
|
||||
<Project Path="tools/RetailTimeProbe/RetailTimeProbe.csproj" />
|
||||
<Project Path="tools/SetupInspect/SetupInspect.csproj" />
|
||||
<Project Path="tools/ShaderCompiler/ShaderCompiler.csproj" />
|
||||
<Project Path="tools/SkyObjectInspect/SkyObjectInspect.csproj" />
|
||||
<Project Path="tools/SpellDump/SpellDump.csproj" />
|
||||
<Project Path="tools/StarsProbe/StarsProbe.csproj" />
|
||||
<Project Path="tools/TextureDump/TextureDump.csproj" />
|
||||
<Project Path="tools/WeatherEnumerator/WeatherEnumerator.csproj" />
|
||||
<Project Path="tools/WeatherSetupProbe/WeatherSetupProbe.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/">
|
||||
<Project Path="tests/AcDream.App.Tests/AcDream.App.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Bake.Tests/AcDream.Bake.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Cli.Tests/AcDream.Cli.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Content.Tests/AcDream.Content.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Core.Tests.Fixtures.HelloPlugin/AcDream.Core.Tests.Fixtures.HelloPlugin.csproj" />
|
||||
<Project Path="tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Core.Net.Tests/AcDream.Core.Net.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Headless.Tests/AcDream.Headless.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Launcher.Core.Tests.Fixtures.InstallLeaseHolder/AcDream.Launcher.Core.Tests.Fixtures.InstallLeaseHolder.csproj" />
|
||||
<Project Path="tests/AcDream.Launcher.Core.Tests.Fixtures.ConsoleSignalChild/AcDream.Launcher.Core.Tests.Fixtures.ConsoleSignalChild.csproj" />
|
||||
<Project Path="tests/AcDream.Launcher.Core.Tests.Fixtures.ConsolelessSupervisorParent/AcDream.Launcher.Core.Tests.Fixtures.ConsolelessSupervisorParent.csproj" />
|
||||
<Project Path="tests/AcDream.Launcher.Core.Tests/AcDream.Launcher.Core.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Launcher.Tests/AcDream.Launcher.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Platform.Tests/AcDream.Platform.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Plugins.MossTank.Tests/AcDream.Plugins.MossTank.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Plugin.Tests.Fixtures.HostPlugin/AcDream.Plugin.Tests.Fixtures.HostPlugin.csproj" />
|
||||
<Project Path="tests/AcDream.Plugin.Tests.Fixtures.InvalidRenderPackInternal/AcDream.Plugin.Tests.Fixtures.InvalidRenderPackInternal.csproj" />
|
||||
<Project Path="tests/AcDream.Plugin.Tests.Fixtures.InvalidRenderPackMultiple/AcDream.Plugin.Tests.Fixtures.InvalidRenderPackMultiple.csproj" />
|
||||
<Project Path="tests/AcDream.RenderPackValidator.Tests/AcDream.RenderPackValidator.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.Runtime.Tests/AcDream.Runtime.Tests.csproj" />
|
||||
<Project Path="tests/AcDream.UI.Abstractions.Tests/AcDream.UI.Abstractions.Tests.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Repository-wide language and warning policy. Project files only override
|
||||
these values when a target has a documented, target-specific need. -->
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Deterministic>true</Deterministic>
|
||||
|
||||
<!-- Use custom names for every graph. A conventional packages.lock.json
|
||||
always overrides NuGetLockFilePath, which makes neutral and RID locks
|
||||
impossible to keep side by side. -->
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<NuGetLockFilePath Condition="'$(RuntimeIdentifier)' == ''">$(MSBuildProjectDirectory)/packages.neutral.lock.json</NuGetLockFilePath>
|
||||
<NuGetLockFilePath Condition="'$(RuntimeIdentifier)' != ''">$(MSBuildProjectDirectory)/packages.$(RuntimeIdentifier).lock.json</NuGetLockFilePath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Arch" Version="2.1.0" />
|
||||
<PackageVersion Include="Avalonia" Version="12.1.1" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="12.1.1" />
|
||||
<PackageVersion Include="Avalonia.Headless.XUnit" Version="12.1.1" />
|
||||
<PackageVersion Include="Avalonia.Themes.Fluent" Version="12.1.1" />
|
||||
<PackageVersion Include="BCnEncoder.Net" Version="2.2.1" />
|
||||
<PackageVersion Include="BCnEncoder.Net.ImageSharp" Version="1.1.2" />
|
||||
<PackageVersion Include="Chorizite.Core" Version="0.0.18" />
|
||||
<PackageVersion Include="Chorizite.DatReaderWriter" Version="2.1.7" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageVersion Include="Serilog" Version="4.0.2" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageVersion Include="Silk.NET.Input" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.OpenAL" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.OpenAL.Extensions.Creative" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.OpenAL.Extensions.EXT" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.OpenAL.Soft.Native" Version="1.23.1" />
|
||||
<PackageVersion Include="Silk.NET.Shaderc" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Windowing" Version="2.23.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<PackageVersion Include="StbImageSharp" Version="2.30.16" />
|
||||
<PackageVersion Include="StbTrueTypeSharp" Version="1.26.12" />
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
45
NOTICE.md
|
|
@ -1,45 +0,0 @@
|
|||
# Third-Party Notices
|
||||
|
||||
This file lists third-party software used by acdream, along with their
|
||||
license terms and copyright notices.
|
||||
|
||||
---
|
||||
|
||||
## WorldBuilder
|
||||
|
||||
Portions of acdream's rendering and dat-handling code are copied from
|
||||
WorldBuilder (https://github.com/Chorizite/WorldBuilder), MIT-licensed.
|
||||
The extracted code lives under:
|
||||
|
||||
- `src/AcDream.Core/Rendering/Wb/` — pure helpers (texture decode,
|
||||
scenery transforms, terrain math).
|
||||
- `src/AcDream.App/Rendering/Wb/` — GL infrastructure and mesh pipeline.
|
||||
|
||||
Original copyright holders: Chorizite contributors (see WorldBuilder's
|
||||
LICENSE file). Adapted by acdream maintainers to consume our
|
||||
`DatCollection` directly (replacing WB's `DefaultDatReaderWriter`) and
|
||||
to remove editor-only code paths.
|
||||
|
||||
Original MIT license text:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) Chorizite contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
10
NuGet.Config
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
</packageSources>
|
||||
<fallbackPackageFolders>
|
||||
<clear />
|
||||
</fallbackPackageFolders>
|
||||
</configuration>
|
||||
370
README.md
|
|
@ -2,294 +2,174 @@
|
|||
|
||||
A modern open-source C# / .NET 10 Asheron's Call client.
|
||||
|
||||
acdream ports the observable behaviour of the September 2013 retail client to
|
||||
Silk.NET and a modern, plugin-friendly architecture. The code is modern; the
|
||||
behaviour is retail.
|
||||
Faithful port of the retail client's behaviour to Silk.NET with a modern,
|
||||
plugin-friendly architecture. The code is modern; the behaviour is retail.
|
||||
|
||||
**Status:** playable pre-alpha. M3, “Cast a spell,” landed on 2026-07-21 and
|
||||
M4, “Live in the world,” is active. The graphical client supports the connected
|
||||
combat, magic, movement, portal, inventory, loot, and retained-UI loops used by
|
||||
the current test characters. The presentation-independent `GameRuntime` and
|
||||
the Linux/Windows multi-session headless host are complete. Native Linux
|
||||
graphics are intentionally parked at the L1 capability checkpoint; Windows is
|
||||
the currently validated graphical platform.
|
||||
**Status:** playable pre-alpha. You can log in to an ACE server, walk and
|
||||
run through Dereth, see other players animate correctly, watch the
|
||||
day-night cycle, hear ambient audio, and take weapons out. Many systems
|
||||
are still stubbed or in-progress — see roadmap.
|
||||
|
||||
The [documentation map](docs/README.md) is the entry point for current
|
||||
milestones, roadmap state, architecture, issues, retail divergences, research,
|
||||
and durable project memory.
|
||||
## Stack
|
||||
|
||||
## Technology
|
||||
|
||||
- **Runtime:** C# and .NET 10
|
||||
- **Graphics:** [Silk.NET](https://github.com/dotnet/Silk.NET), OpenGL 4.3
|
||||
core, bindless textures, shader draw parameters, SSBOs, and multi-draw
|
||||
indirect
|
||||
- **Audio:** OpenAL through Silk.NET
|
||||
- **Content:** retail DAT files plus a machine-local, memory-mapped
|
||||
`acdream.pak` produced by `AcDream.Bake`
|
||||
- **Networking:** custom UDP, ISAAC cipher, and game-message layers compatible
|
||||
with ACEmulator
|
||||
- **UI:** retained retail gameplay UI plus opt-in ImGui developer tools
|
||||
- **Automation:** the same presentation-independent `GameRuntime` is hosted by
|
||||
both the graphical client and `AcDream.Headless`
|
||||
|
||||
The modern renderer is mandatory. There is no legacy renderer fallback.
|
||||
Startup reports an actionable error if the required OpenGL capabilities are
|
||||
missing.
|
||||
- **Language:** C# .NET 10
|
||||
- **Graphics:** [Silk.NET](https://github.com/dotnet/Silk.NET) (OpenGL 4.3)
|
||||
- **Audio:** OpenAL via Silk.NET
|
||||
- **Dat parsing:** [Chorizite.DatReaderWriter](https://github.com/Chorizite/DatReaderWriter)
|
||||
- **Networking:** Custom UDP + ISAAC cipher + game-message layer, wire-compatible
|
||||
with ACEmulator server
|
||||
|
||||
## What works
|
||||
|
||||
- ACE login, character selection, world entry, chat, client commands,
|
||||
reconnect, and graceful logout.
|
||||
- Outdoor, building, cellar, and dungeon streaming with prepared terrain,
|
||||
scenery, buildings, EnvCells, collision, portal visibility, sky, fog,
|
||||
lighting, audio, and day/night presentation.
|
||||
- Local and observed movement, animation, jumping, selection, radar, combat
|
||||
stances, melee, bows, crossbows, spell projectiles, death, corpses, chests,
|
||||
and looting.
|
||||
- Inventory bags, stable server ordering, stack splitting, ground drops,
|
||||
paperdoll equipment, weapon switching, quick bars, item use, cooldowns, and
|
||||
giving items to NPCs.
|
||||
- Retail-style retained UI for vitals, chat, toolbar, inventory, character,
|
||||
attributes, skills, spellbook, components, effects, combat/spell/jump bars,
|
||||
radar/compass, dialogs, external containers, and assessment.
|
||||
- Complete end-of-retail spell catalog, learned and favorite spells,
|
||||
component preflight, connected casts, enchantments, DAT-driven projectiles
|
||||
and effects, recall, portal-space travel, Hidden/UnHide, and remote
|
||||
materialization.
|
||||
- One presentation-independent runtime owner for session, entities, objects,
|
||||
inventory, character state, selection, interactions, combat, magic,
|
||||
movement, physics, projectiles, world environment, and portal transit.
|
||||
- A no-window Windows/Linux host with deterministic bot commands/events,
|
||||
shared immutable content, multi-session scheduling, isolation, reconnect,
|
||||
resource telemetry, and tested 1/5/10/30-session ownership.
|
||||
- Plugin loading, shared command/input abstractions, retained markup panels,
|
||||
and permanent ImGui developer tools behind `ACDREAM_DEVTOOLS=1`.
|
||||
- Connecting to a local ACEmulator (ACE) server on `127.0.0.1:9000`
|
||||
- Character selection and login
|
||||
- Rendering Dereth terrain with retail-correct texture blending,
|
||||
per-vertex lighting, and road overlays
|
||||
- Static scenery (buildings, trees, scenery objects) via EnvCell walker
|
||||
- Animated characters (own + remote) with walk / run / strafe / jump /
|
||||
turn / attack motions sourced from the retail motion tables
|
||||
- Network sync with remote players — you can watch other characters
|
||||
animate correctly, including speeds and directional motion
|
||||
- Day-night cycle driven from the retail Region dat (0x13000000) —
|
||||
correct DayGroup picking via the retail LCG, correct keyframe
|
||||
interpolation, correct per-keyframe sky-object replace
|
||||
- Weather (rain/snow particles synced from the server via the retail
|
||||
DayGroup name)
|
||||
- Sky dome, stars, moon, clouds, sun — each rendered from the retail
|
||||
Region's SkyObjects with texture scrolling and alpha fade
|
||||
- Plugin host with live event replay-on-subscribe
|
||||
|
||||
## Current boundaries
|
||||
## What's stubbed or in-progress
|
||||
|
||||
- The active M4 prelude is
|
||||
[world interaction completion](docs/plans/2026-07-23-world-interaction-completion.md).
|
||||
Slices 1–3, including assessment and its final formula/icon/layout
|
||||
correction, are user-accepted. Equipped-child picking and vendor
|
||||
browse/buy/sell are the next uncompleted slices.
|
||||
- Issue `#225` retains the lifestone/particle shared-alpha visual comparison.
|
||||
Its connected lifetime and performance routes already pass.
|
||||
- Narrow carried behaviour debt includes issue `#153` (an unstreamed
|
||||
far-teleport edge), issue `#116` (slide feel), issue `#235` (30 Hz capped/RDP
|
||||
jump presentation), and the live temporary-stopgap rows in the
|
||||
[retail divergence register](docs/architecture/retail-divergence-register.md).
|
||||
- Native Linux graphics are deferred. L0 portability and L1 backend/capability
|
||||
reporting are implemented; WSLg reaches the GPU through Mesa D3D12 but does
|
||||
not expose mandatory `GL_ARB_bindless_texture`. Resume with a supported
|
||||
physical Linux AMD/NVIDIA driver before beginning later Slice L work.
|
||||
- Advanced vendor/trade/crafting/social surfaces and larger M4 quest,
|
||||
character-creation, and emote bodies remain roadmap work.
|
||||
- Indoor transitions (building interiors) — disabled, Phase B.3 pending
|
||||
- Combat — animation works, damage math not wired
|
||||
- Lightning visual — the retail PhysicsScript-driven flash is researched
|
||||
but not wired (see `docs/research/2026-04-23-lightning-real.md`)
|
||||
- TimeSync drift — we only sync calendar on login, not periodically,
|
||||
so acdream's in-game clock gradually drifts from retail's
|
||||
- Landscape draw distance — currently `ACDREAM_STREAM_RADIUS=2` (~400m)
|
||||
vs retail's several kilometres
|
||||
|
||||
## Prerequisites
|
||||
See `docs/plans/2026-04-11-roadmap.md` for the ordered phase list.
|
||||
See `docs/ISSUES.md` for the rolling list of known bugs + small deferred
|
||||
features (tactical, bug-level; the roadmap is strategic, phase-level).
|
||||
|
||||
## Building + running
|
||||
|
||||
**Requires:**
|
||||
- .NET 10 SDK
|
||||
- Your own retail Asheron's Call DAT directory containing:
|
||||
- `client_portal.dat`
|
||||
- `client_cell_1.dat`
|
||||
- `client_highres.dat`
|
||||
- `client_local_English.dat`
|
||||
- A machine-local `acdream.pak` built from those DATs
|
||||
- A running ACE server for connected play; the examples use
|
||||
`127.0.0.1:9000`
|
||||
- For the graphical client, a driver exposing the mandatory modern OpenGL
|
||||
capabilities
|
||||
- A retail Asheron's Call dat directory (Turbine/Microsoft property —
|
||||
supply your own). Contains `client_portal.dat`, `client_cell_1.dat`,
|
||||
`client_highres.dat`, `client_local_English.dat`.
|
||||
- A running ACE (ACEmulator) server on `127.0.0.1:9000` (or override
|
||||
via env var)
|
||||
|
||||
The project does not distribute Microsoft/Turbine DAT files or derived
|
||||
prepared packages.
|
||||
|
||||
## Build and test
|
||||
**Launch (PowerShell on Windows — bash has trouble with the apostrophe
|
||||
in "Asheron's Call"):**
|
||||
|
||||
```powershell
|
||||
dotnet restore AcDream.slnx
|
||||
dotnet build AcDream.slnx -c Release
|
||||
dotnet test AcDream.slnx -c Release --no-build
|
||||
```
|
||||
|
||||
The current baseline is a successful Release build with **8,826 passing tests
|
||||
and 5 intentional skips**. The build currently reports 17 test-project
|
||||
warnings tracked by [`#228`](docs/ISSUES.md#228--clean-release-build-emits-17-test-project-warnings);
|
||||
production compilation has zero errors.
|
||||
|
||||
## Prepare content
|
||||
|
||||
Production rendering and collision use the validated prepared package rather
|
||||
than decoding world meshes on the frame path:
|
||||
|
||||
```powershell
|
||||
dotnet run --project src\AcDream.Bake\AcDream.Bake.csproj -c Release -- `
|
||||
--dat-dir "C:\Games\Asheron's Call" `
|
||||
--out "C:\Games\Asheron's Call\acdream.pak"
|
||||
```
|
||||
|
||||
A complete package is approximately 30 GB. It is machine-local and must not be
|
||||
committed. `ACDREAM_PAK_PATH` overrides the default
|
||||
`<DAT directory>\acdream.pak`.
|
||||
|
||||
## Run the graphical client
|
||||
|
||||
```powershell
|
||||
$env:ACDREAM_DAT_DIR = "C:\Games\Asheron's Call"
|
||||
$env:ACDREAM_PAK_PATH = "C:\Games\Asheron's Call\acdream.pak"
|
||||
$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
|
||||
$env:ACDREAM_LIVE = "1"
|
||||
$env:ACDREAM_TEST_HOST = "127.0.0.1"
|
||||
$env:ACDREAM_TEST_PORT = "9000"
|
||||
$env:ACDREAM_TEST_USER = "testaccount"
|
||||
$env:ACDREAM_TEST_PASS = "testpassword"
|
||||
$env:ACDREAM_RETAIL_UI = "1"
|
||||
|
||||
dotnet run --project src\AcDream.App\AcDream.App.csproj -c Release
|
||||
dotnet run --project src\AcDream.App\AcDream.App.csproj -c Debug
|
||||
```
|
||||
|
||||
The DAT directory can instead be supplied as the first positional argument:
|
||||
Offline CLI dat inspector (no server needed):
|
||||
|
||||
```powershell
|
||||
dotnet run --project src\AcDream.App\AcDream.App.csproj -c Release -- `
|
||||
"C:\Games\Asheron's Call"
|
||||
```
|
||||
dotnet run --project src/AcDream.Cli -- "C:\path\to\Asheron's Call"
|
||||
```
|
||||
|
||||
## Run a headless session
|
||||
|
||||
`AcDream.Headless` loads no App, UI, OpenGL, native-window, or audio assembly.
|
||||
Create a version-1 configuration such as `bot.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"process": {
|
||||
"content": {
|
||||
"datDirectory": "/opt/ac",
|
||||
"preparedAssetPath": "/opt/ac/acdream.pak"
|
||||
}
|
||||
},
|
||||
"sessions": [
|
||||
{
|
||||
"id": "bot-1",
|
||||
"endpoint": { "host": "127.0.0.1", "port": 9000 },
|
||||
"account": "testaccount",
|
||||
"character": { "index": 0 },
|
||||
"policy": { "id": "idle" },
|
||||
"credential": {
|
||||
"provider": "environment",
|
||||
"reference": "ACDREAM_BOT_PASSWORD"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Then validate and run it:
|
||||
|
||||
```bash
|
||||
export ACDREAM_BOT_PASSWORD='testpassword'
|
||||
dotnet run --project src/AcDream.Headless/AcDream.Headless.csproj -c Release -- \
|
||||
validate --config bot.json
|
||||
dotnet run --project src/AcDream.Headless/AcDream.Headless.csproj -c Release -- \
|
||||
run --config bot.json
|
||||
```
|
||||
|
||||
For a single local session, `run` also accepts
|
||||
`--user <account> --password <password>`. Add uniquely identified session
|
||||
entries and credential references for a multi-session process. Available
|
||||
built-in policies are `idle`, `lifecycle-smoke`, `observer-movement`, and
|
||||
`portal-route-smoke`.
|
||||
|
||||
## Useful startup options
|
||||
## Diagnostic env vars
|
||||
|
||||
| Variable | Effect |
|
||||
|---|---|
|
||||
| `ACDREAM_DAT_DIR` | Retail DAT directory |
|
||||
| `ACDREAM_PAK_PATH` | Prepared package path; defaults to `<DAT dir>/acdream.pak` |
|
||||
| `ACDREAM_LIVE=1` | Enable connected mode |
|
||||
| `ACDREAM_TEST_HOST` / `ACDREAM_TEST_PORT` | ACE endpoint |
|
||||
| `ACDREAM_TEST_USER` / `ACDREAM_TEST_PASS` | Graphical-client credentials |
|
||||
| `ACDREAM_RETAIL_UI=1` | Enable the retained retail gameplay UI |
|
||||
| `ACDREAM_DEVTOOLS=1` | Enable ImGui developer tools |
|
||||
| `ACDREAM_NO_AUDIO=1` | Suppress OpenAL initialization |
|
||||
| `ACDREAM_UNCAPPED_RENDER=1` | Disable normal frame pacing for diagnostics |
|
||||
| `ACDREAM_DISPLAY_PROTOCOL=auto\|x11\|wayland` | Select the Linux GLFW backend |
|
||||
| `ACDREAM_DAY_GROUP=N` | Force a day-group index for weather/lighting comparisons |
|
||||
| `ACDREAM_STREAM_RADIUS=N` | Legacy override over configured streaming radii |
|
||||
| `ACDREAM_DUMP_SKY=1` | Dump sky interpolation and draw diagnostics |
|
||||
| `ACDREAM_DUMP_MOTION=1` | Dump inbound movement and motion-cycle decisions |
|
||||
| `ACDREAM_DUMP_SKY=1` | Per-second dump of the interpolated `SkyKeyframe` values + per-SkyObject draw info + texture alpha histograms |
|
||||
| `ACDREAM_DUMP_MOTION=1` | Dump every inbound `UpdateMotion` + resulting `SetCycle` |
|
||||
| `ACDREAM_STREAM_RADIUS=N` | Tune landblock visible-window radius (default 2 = 5×5) |
|
||||
| `ACDREAM_NO_AUDIO=1` | Suppress OpenAL init |
|
||||
| `ACDREAM_DAY_GROUP=N` | Force a specific DayGroup index for A/B-testing weather presets |
|
||||
| `ACDREAM_RUN_SKILL=N` / `ACDREAM_JUMP_SKILL=N` | Client-side run/jump skill (default 200) |
|
||||
|
||||
Additional diagnostic and budget controls are documented beside their typed
|
||||
owners and in the linked research plans; they are not stable user settings.
|
||||
## Layout
|
||||
|
||||
## Repository layout
|
||||
|
||||
```text
|
||||
```
|
||||
src/
|
||||
AcDream.Runtime/ presentation-independent GameRuntime
|
||||
AcDream.App/ graphical host, retained UI, renderer, audio
|
||||
AcDream.Headless/ Windows/Linux no-window multi-session host
|
||||
AcDream.Core/ retail gameplay, movement, physics, world logic
|
||||
AcDream.Core.Net/ UDP, ISAAC, protocol and message routing
|
||||
AcDream.Content/ GL-free DAT and prepared-package content
|
||||
AcDream.Bake/ offline acdream.pak builder
|
||||
AcDream.Cli/ offline DAT inspector
|
||||
AcDream.UI.Abstractions/ shared UI/input models and contracts
|
||||
AcDream.UI.ImGui/ developer-tool presentation
|
||||
AcDream.Plugin.Abstractions/ BCL-only plugin contracts
|
||||
AcDream.Plugins.Smoke/ example plugin
|
||||
AcDream.App/ rendering + audio + main loop (Silk.NET)
|
||||
AcDream.Core/ game state, meshing, physics, sky, weather, lighting
|
||||
AcDream.Core.Net/ UDP + ISAAC + game-message layer
|
||||
AcDream.Cli/ offline dat-inspector console app
|
||||
AcDream.Plugin.Abstractions/ plugin host interfaces
|
||||
AcDream.Plugins.Smoke/ example plugin
|
||||
|
||||
tests/
|
||||
AcDream.*.Tests/ layer-matched xUnit projects
|
||||
AcDream.Core.Tests/ xUnit tests (742 passing)
|
||||
AcDream.Core.Net.Tests/ network-layer tests
|
||||
|
||||
tools/
|
||||
RetailTimeProbe/ Win32 P/Invoke ReadProcessMemory probe of
|
||||
the live retail acclient.exe — dumps
|
||||
TimeOfDay + sky-lighting globals so we
|
||||
can compare against acdream's state
|
||||
SkyObjectInspect/ dat-inspector for Region sky objects
|
||||
|
||||
references/ vendored read-only reference code — ACE,
|
||||
ACViewer, WorldBuilder, holtburger,
|
||||
AC2D, Chorizite, DatReaderWriter.
|
||||
Gitignored.
|
||||
|
||||
docs/
|
||||
README.md documentation authority and current map
|
||||
architecture/ ownership, structure, divergence, WB inventory
|
||||
plans/ milestone, roadmap, and execution plans
|
||||
research/ retail pseudocode, traces, fixtures, evidence
|
||||
audit/ completion and conformance audits
|
||||
|
||||
memory/ durable engineering references
|
||||
references/ gitignored external reference repositories
|
||||
architecture/ single-source-of-truth architecture doc
|
||||
plans/ phase roadmaps + per-phase specs
|
||||
research/ decompile-derived research, per-phase
|
||||
findings, deep-dive agent reports
|
||||
audit/ phase-completion audits
|
||||
```
|
||||
|
||||
## Development workflow
|
||||
|
||||
All AC-specific behaviour starts from the named retail oracle in
|
||||
`docs/research/named-retail/`:
|
||||
All AC-specific behaviour is ported from the decompiled retail client
|
||||
(`docs/research/decompiled/`). The workflow is:
|
||||
|
||||
1. Search the named retail pseudo-C and headers by `class::method`.
|
||||
2. Use the older Ghidra chunks only when the named oracle is insufficient.
|
||||
3. Cross-reference ACE and the relevant client/viewer implementation.
|
||||
4. Record readable pseudocode and exact constants/order.
|
||||
5. Port the retail mechanism into the correct modern owner.
|
||||
6. Add conformance, lifecycle, and failure-boundary tests.
|
||||
7. Run the automated gate and the appropriate connected or visual gate.
|
||||
8. Update architecture, roadmap, divergences, and durable memory with the
|
||||
same change.
|
||||
1. **Decompile first.** Find the matching function in the decompiled
|
||||
client.
|
||||
2. **Cross-reference.** Check against ACE's C# port and ACViewer /
|
||||
WorldBuilder.
|
||||
3. **Write pseudocode.** Translate C to readable pseudocode first.
|
||||
4. **Port faithfully.** Translate line-by-line, preserving variable
|
||||
names and control flow.
|
||||
5. **Conformance test.** Add tests using golden values from retail.
|
||||
6. **Integrate surgically.** Minimise churn in the surrounding pipeline.
|
||||
|
||||
Guessing at AC-specific algorithms is forbidden. See
|
||||
[AGENTS.md](AGENTS.md), [CLAUDE.md](CLAUDE.md), and the
|
||||
[architecture guide](docs/architecture/acdream-architecture.md) for the full
|
||||
rules.
|
||||
Guessing at AC-specific algorithms is explicitly forbidden — see
|
||||
`CLAUDE.md` for the full workflow rationale and the list of failure
|
||||
modes we've paid for in the past.
|
||||
|
||||
## Reference projects
|
||||
## Reference repos
|
||||
|
||||
- **ACE / ACEmulator:** authoritative server and protocol behaviour
|
||||
- **ACViewer:** character appearance and DAT presentation cross-check
|
||||
- **WorldBuilder:** extracted Silk.NET DAT/rendering foundation
|
||||
- **Chorizite.ACProtocol:** clean-room protocol reference
|
||||
- **holtburger:** broad non-retail client behaviour reference
|
||||
- **AC2D:** terrain and movement-packet cross-checks
|
||||
We cross-reference five external projects for every retail behaviour:
|
||||
|
||||
The retail binary/decomp remains the behavioural oracle when references
|
||||
disagree.
|
||||
- **ACE** (ACEmulator) — authoritative server-side protocol
|
||||
- **ACViewer** — MonoGame dat viewer; good for character appearance
|
||||
- **WorldBuilder** — Silk.NET dat editor; matches our stack
|
||||
- **Chorizite.ACProtocol** — clean-room C# protocol library
|
||||
- **holtburger** — most complete non-retail client; Rust TUI, full
|
||||
client-side behaviour
|
||||
- **AC2D** — C++ AC-client emulator; has the real terrain split
|
||||
formula and 0xF61C movement packet format
|
||||
|
||||
## Licence and game assets
|
||||
See `CLAUDE.md` for which reference is authoritative for which domain.
|
||||
|
||||
The acdream source has not yet been assigned a top-level licence and is not
|
||||
ready for public redistribution. External reference code retains its own
|
||||
licence.
|
||||
## Licence
|
||||
|
||||
Asheron's Call DAT files, art, names, and other game assets remain the property
|
||||
of Microsoft/Turbine. This repository does not distribute them; users must
|
||||
supply their own retail installation.
|
||||
Not yet chosen. All external reference code is vendored under its own
|
||||
licence; see `references/*/LICENSE`. The acdream source code itself is
|
||||
unreleased — not yet distributed to the public. Once the licence
|
||||
choice is made it will go in a top-level `LICENSE` file.
|
||||
|
||||
The AC dat files and the game's intellectual property remain the
|
||||
property of Microsoft / Turbine. This project does not distribute any
|
||||
of those files or assets — you must supply your own retail install.
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
import sys, re, math
|
||||
from collections import Counter
|
||||
|
||||
pat = re.compile(
|
||||
r'outRoot=(\w) flood=(\d+) eye=\(([^)]+)\) player=\(([^)]+)\) '
|
||||
r'rawPlayer=\(([^)]+)\) yaw=([-\d.]+)')
|
||||
rows = []
|
||||
for l in sys.stdin:
|
||||
m = pat.search(l)
|
||||
if not m:
|
||||
continue
|
||||
rows.append((
|
||||
m.group(1), int(m.group(2)),
|
||||
tuple(float(x) for x in m.group(3).split(',')), # eye
|
||||
tuple(float(x) for x in m.group(4).split(',')), # player (RenderPosition)
|
||||
tuple(float(x) for x in m.group(5).split(',')), # rawPlayer (physics body)
|
||||
float(m.group(6)))) # yaw
|
||||
print("parsed pv-input rows:", len(rows))
|
||||
if not rows:
|
||||
raise SystemExit
|
||||
|
||||
print("flood histogram (outRoot,flood)->count:", dict(Counter((r[0], r[1]) for r in rows)))
|
||||
|
||||
def rng(idx):
|
||||
return [max(r[idx][k] for r in rows) - min(r[idx][k] for r in rows) for k in range(3)]
|
||||
print(f"eye range over window (m): {[round(v,6) for v in rng(2)]}")
|
||||
print(f"render-pos range over window (m): {[round(v,6) for v in rng(3)]}")
|
||||
print(f"raw-phys range over window (m): {[round(v,6) for v in rng(4)]}")
|
||||
print(f"yaw range over window (rad): {round(max(r[5] for r in rows)-min(r[5] for r in rows),6)}")
|
||||
|
||||
flips = 0
|
||||
samples = []
|
||||
for i in range(1, len(rows)):
|
||||
a, b = rows[i-1], rows[i]
|
||||
if a[1] == b[1]:
|
||||
continue
|
||||
flips += 1
|
||||
ed = math.dist(a[2], b[2]); pd = math.dist(a[3], b[3])
|
||||
rd = math.dist(a[4], b[4]); yd = abs(b[5]-a[5])
|
||||
if len(samples) < 18:
|
||||
samples.append(f"{b[0]} {a[1]}->{b[1]:<2} eye={ed*1000:7.3f}mm rend={pd*1e6:8.1f}um raw={rd*1e6:8.1f}um yaw={yd*1000:8.4f}mrad")
|
||||
print(f"flood flips in window: {flips}")
|
||||
for s in samples:
|
||||
print(" ", s)
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
# acdream application icons
|
||||
|
||||
Two marks, one family.
|
||||
|
||||
| Mark | Files | Used by |
|
||||
|---|---|---|
|
||||
| **Client** — the mosswart head | `acdream-client-*.png`, `acdream-client.ico` | `AcDream.App` (PE icon + runtime window icon) |
|
||||
| **Launcher** — the ring and crescent | `acdream-launcher-*.png`, `acdream-launcher.ico` | `AcDream.Launcher` (PE icon + Avalonia `Window.Icon`) |
|
||||
|
||||
Each ships PNGs at 16/24/32/48/64/128/256/512/1024 plus a multi-size `.ico`
|
||||
carrying 16 through 256.
|
||||
|
||||
## Where the art comes from
|
||||
|
||||
**The client mark is the retail mosswart**, not a drawing of one. It is the
|
||||
actual creature head — `Setup 0x02000B4F` part 14, skin atlas `0x05001E11`,
|
||||
`ClothingBase 0x10000344` — pulled from `client_portal.dat`, smoothed, lit and
|
||||
graded. Palette values throughout both marks are sampled from that texture:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `#ACB820` | chartreuse upper skin |
|
||||
| `#A09800` | mustard belly — the "foul yellow" the lore names |
|
||||
| `#485010` | deep olive shadow |
|
||||
| `#F2ECD2` | tusk bone |
|
||||
| `#AC7438` | ear membrane / hide |
|
||||
|
||||
**The launcher mark is inspired by the Asheron's Call sigil** — a forged ring
|
||||
enclosing a hooked crescent — rebuilt from measurements of the retail wordmark
|
||||
and the `acclient.exe` icon resource. It is an original construction in the
|
||||
same visual language, not a copy of the logo. Its warm field matches the retail
|
||||
client icon's dark-to-gold interior.
|
||||
|
||||
> **Note on rights.** "Asheron's Call" and its logo are trademarks of their
|
||||
> owners, and the client mark is rendered from copyrighted game art. Unlike DAT
|
||||
> content — which stays on the user's own disk — these icons are compiled into
|
||||
> the shipped binaries. If acdream is ever distributed broadly, both marks
|
||||
> should be reviewed, and the client mark is the one most likely to want an
|
||||
> original redraw using these renders as reference.
|
||||
|
||||
## Regenerating
|
||||
|
||||
The launcher mark is fully procedural and rebuilds anywhere:
|
||||
|
||||
```bash
|
||||
py tools/IconForge/forge.py launcher
|
||||
```
|
||||
|
||||
That is byte-for-byte deterministic — it reproduces the committed PNGs exactly,
|
||||
so an accidental edit is visible as a diff.
|
||||
|
||||
The client mark renders real game geometry, so it needs the installed DATs.
|
||||
One command extracts both halves — the posed geometry and the surfaces it
|
||||
references — into `tools/IconForge/work/`:
|
||||
|
||||
```bash
|
||||
dotnet run --project tools/MosswartArt -- 0x02000B4F 0x10000344 tools/IconForge/work/mosswart_mesh.json 0x09000009
|
||||
```
|
||||
|
||||
The trailing MotionTable id is required. Creatures do not define an upright pose
|
||||
in `Setup.PlacementFrames`; without it every part stacks on the origin.
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
py tools/IconForge/forge.py client
|
||||
```
|
||||
|
||||
This is deterministic too — given the same DATs it reproduces the committed
|
||||
PNGs byte-for-byte.
|
||||
|
||||
Requires Python with `numpy`, `pillow` and `scipy`.
|
||||
|
||||
## How they are wired in
|
||||
|
||||
Neither icon is loaded from disk at runtime.
|
||||
|
||||
- **PE icon** — `<ApplicationIcon>` in each `.csproj`, pointing at the `.ico`
|
||||
here. This is what Explorer and the taskbar shortcut show.
|
||||
- **Client window icon** — `AcDream.App.Rendering.WindowIconLoader` hands GLFW
|
||||
four sizes **from the `Load` callback**. That timing is load-bearing: Silk's
|
||||
`Window.Create` only builds the managed object, and `IWindow.Initialize` is
|
||||
what creates the native window, so applying an icon any earlier throws
|
||||
"Window should be initialized". The failure is quiet and misleading — GLFW
|
||||
falls back to the stock Windows application icon rather than the
|
||||
executable's, so Explorer shows the mark and the running window does not.
|
||||
The PNGs are *embedded resources* linked from this directory, so there is one
|
||||
source of truth for the art and no missing-file case at runtime.
|
||||
`WindowIconLoaderTests` guards both the resource names, which are otherwise
|
||||
coupled to `LogicalName` in the csproj by string only, and the call-site
|
||||
ordering.
|
||||
- **Launcher window icon** — `AvaloniaResource` linked from here, referenced as
|
||||
`avares://acdream-launcher/Assets/acdream-launcher.png`.
|
||||
|
Before Width: | Height: | Size: 436 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 699 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 397 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 819 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 86 KiB |
19432
docs/ISSUES.md
132
docs/README.md
|
|
@ -1,132 +0,0 @@
|
|||
# acdream documentation map
|
||||
|
||||
This page is the entry point for project documentation. It distinguishes
|
||||
current sources of truth from implementation history so an old plan or issue
|
||||
banner cannot silently override the current program state.
|
||||
|
||||
## Current snapshot — 2026-07-27
|
||||
|
||||
- **Milestone state:** M3, “Cast a spell,” landed 2026-07-21. M4, “Live in the
|
||||
world,” is active.
|
||||
- **M4 gameplay program:** resume the pre-M4
|
||||
[world-interaction completion program](plans/2026-07-23-world-interaction-completion.md).
|
||||
Favorite-spell overflow, status Use/Assess, and the complete assessment
|
||||
surface are user-accepted. Equipped-child picking and vendor
|
||||
browse/transactions remain Slices 4–6.
|
||||
- **Structural/runtime state:** all eight `GameWindow` decomposition slices,
|
||||
Modern Runtime Slices A–J, and the connected visual/lifecycle gates are
|
||||
complete. `GameWindow` is a 1,622-line composition/callback shell.
|
||||
`AcDream.Runtime.GameRuntime` owns canonical session, entity/object,
|
||||
gameplay, movement, physics, projectile, environment, and portal state;
|
||||
graphical and no-window hosts borrow the same owner graph.
|
||||
- **Headless state:** Slice K is complete. `AcDream.Headless` is a
|
||||
presentation-free Windows/Linux host with deterministic commands/events,
|
||||
shared immutable content, multi-session isolation, reconnect, resource
|
||||
telemetry, and 1/5/10/30-session gates. The final two-account native-Linux
|
||||
soak completed ten minutes, logged out through ACE, and converged every
|
||||
ownership ledger.
|
||||
- **Linux graphical state:** Slice L0 and the L1 implementation checkpoint are
|
||||
complete at `66f114b2` and `11501d52`. Native Windows passes the active
|
||||
modern-GL/audio/window smoke. WSLg X11/Wayland correctly reject their
|
||||
missing `GL_ARB_bindless_texture`. Physical-Linux validation and L2–L6 are
|
||||
explicitly deferred; resume at the supported AMD/NVIDIA L1 gate.
|
||||
- **Completed gameplay gates:** R6 locomotion/collision/projectile/teleport/
|
||||
radar, two-client portal-out/materialization, indoor prepared collision,
|
||||
loot ordering, local/remote ground drops, and selection-marker lifetime.
|
||||
- **Separate visual verification:** issue `#225`, the shared-alpha
|
||||
lifestone/particle result; its connected resource-lifetime and performance
|
||||
routes pass.
|
||||
- **Carried behaviour debt:** issue `#153` (far teleport onto an unstreamed
|
||||
edge), `#116` (narrowed slide response), `#235` (capped/RDP jump cadence),
|
||||
and the active temporary-stopgap rows in the divergence register.
|
||||
- **Divergence audit:** 189 active rows — IA 18, AD 38, AP 91, TS 38, and
|
||||
UN 4 — plus retained struck/retired historical rows such as TS-37.
|
||||
- **Latest automated baseline:** the Release build succeeds with the 17
|
||||
test-project warnings tracked by issue `#228`; 8,826 tests pass and five are
|
||||
intentionally skipped. App passes 3,763 / 3 skips. The L1 Windows supported
|
||||
smoke and WSLg X11/Wayland negative-capability reports all end with zero
|
||||
window/GL/input/audio ownership.
|
||||
|
||||
## Sources of truth
|
||||
|
||||
Read these in this order when deciding what to do next:
|
||||
|
||||
1. [`plans/2026-05-12-milestones.md`](plans/2026-05-12-milestones.md) — the
|
||||
active playable outcome, freeze boundaries, and visual gates.
|
||||
2. [`plans/2026-04-11-roadmap.md`](plans/2026-04-11-roadmap.md) — strategic
|
||||
phase ledger: shipped, active, deferred, and future work.
|
||||
3. [`ISSUES.md`](ISSUES.md) — tactical defects and small follow-ups. The status
|
||||
inside an issue is authoritative; physical order is not.
|
||||
4. [`architecture/retail-divergence-register.md`](architecture/retail-divergence-register.md)
|
||||
— every known place runtime behavior can differ from retail.
|
||||
5. [`architecture/acdream-architecture.md`](architecture/acdream-architecture.md)
|
||||
and [`architecture/code-structure.md`](architecture/code-structure.md) —
|
||||
ownership, dependency, update-thread, and extraction rules.
|
||||
6. [`architecture/worldbuilder-inventory.md`](architecture/worldbuilder-inventory.md)
|
||||
— rendering/DAT code already owned in-tree versus mechanisms still ours to
|
||||
port.
|
||||
|
||||
If these disagree, milestones control the current outcome, the roadmap controls
|
||||
work ordering, the issue status controls the individual defect, and the
|
||||
architecture documents control implementation shape. Reconcile the stale
|
||||
document in the same change; do not leave both claims standing.
|
||||
|
||||
## Research and implementation records
|
||||
|
||||
- [`research/named-retail/`](research/named-retail/) is the primary retail
|
||||
oracle: named pseudo-C, headers, symbols, and types from the Sept 2013 build.
|
||||
- [`research/decompiled/`](research/decompiled/) is the older Ghidra fallback.
|
||||
- [`research/`](research/) contains focused pseudocode, traces, fixtures, and
|
||||
gate reports. A dated research note records evidence; it does not become a
|
||||
new roadmap.
|
||||
- [`superpowers/specs/`](superpowers/specs/) and
|
||||
[`superpowers/plans/`](superpowers/plans/) are per-slice design and execution
|
||||
records. Completed plans remain historical.
|
||||
- [`ci-and-releases.md`](ci-and-releases.md) is the SSOT for the Gitea CI
|
||||
pipeline, the self-hosted runners, and how alpha releases are published.
|
||||
Load-sensitive tests live in `Lane=Timing`; see
|
||||
[`release-gate.md`](release-gate.md) before adding to it.
|
||||
- [`launch-options.md`](launch-options.md) is the SSOT for every environment
|
||||
variable and command-line argument the client reads, including what each one
|
||||
changes about the run beyond its obvious effect. Read the side-effects column
|
||||
before trusting any measurement. Enforced by
|
||||
`LaunchOptionsDocumentationTests`: a flag without a row fails the build, and
|
||||
so does a row whose read site was deleted.
|
||||
- [`audit/`](audit/) contains completion and conformance audits.
|
||||
- [`reference/ace-commands.md`](reference/ace-commands.md) preserves the local
|
||||
ACE server's complete in-game command catalog and points to the authoritative
|
||||
per-command help surface.
|
||||
|
||||
## Durable memory
|
||||
|
||||
- [`../claude-memory/MEMORY.md`](../claude-memory/MEMORY.md) indexes the live
|
||||
subsystem memories and the render/physics digests. Read a domain digest
|
||||
before changing that subsystem, especially its DO-NOT-RETRY table.
|
||||
- [`../memory/`](../memory/) contains stable engineering references such as the
|
||||
modern rendering pipeline, two-tier streaming, and toolchain notes.
|
||||
|
||||
Memory accelerates recall; it does not outrank the canonical documents above.
|
||||
When current truth changes, update the relevant canonical document and distill
|
||||
only the durable lesson into memory.
|
||||
|
||||
## Historical and deprecated documents
|
||||
|
||||
- [`bugs.md`](bugs.md) is the April 2026 bug snapshot. It is preserved for
|
||||
archaeology and is not an active ledger.
|
||||
- Dated plans and specs describe the decision at that time. Their completion
|
||||
wording is historical unless the current milestone/roadmap explicitly links
|
||||
the item as active.
|
||||
- Old `R1→R8` architecture sequencing is superseded. Current execution comes
|
||||
from the milestones and strategic roadmap.
|
||||
|
||||
## Documentation maintenance rules
|
||||
|
||||
- Update milestone, roadmap, issue, divergence, architecture, and memory claims
|
||||
in the same commit when a shipped change affects them.
|
||||
- Keep one issue ID per defect. Narrow an issue in place; do not reuse another
|
||||
issue's number as a shorthand.
|
||||
- Mark automated, connected, and visual gates separately. An automated pass is
|
||||
not a visual acceptance, and an RDP throughput sample is not a local-display
|
||||
visual comparison.
|
||||
- Preserve research history, but remove stale “current/next” claims from living
|
||||
documents once the state advances.
|
||||
|
|
@ -1,854 +0,0 @@
|
|||
# acdream — code structure & extraction sequence
|
||||
|
||||
**Status:** Living document. Created 2026-05-16; implementation reconciliation
|
||||
completed 2026-07-21; Slices 1–8 and their automated closeout landed by
|
||||
2026-07-22. The connected visual matrix passed 2026-07-23; the campaign is
|
||||
complete and new M4 subsystems may enter through the extracted owners.
|
||||
**Purpose:** Describe the desired structural state of the App layer,
|
||||
explain the rules we've adopted, and lay out the safe extraction
|
||||
sequence from today's reality (one 15,723-line `GameWindow.cs` at the
|
||||
2026-07-21 audit) to the
|
||||
target (thin `GameWindow`, small focused collaborators).
|
||||
**Companion to:** [`acdream-architecture.md`](acdream-architecture.md)
|
||||
(the layered architecture) and
|
||||
[`worldbuilder-inventory.md`](worldbuilder-inventory.md) (what we take
|
||||
from WB vs port ourselves).
|
||||
|
||||
---
|
||||
|
||||
## 1. The structural problem we're solving
|
||||
|
||||
The layered architecture works: `AcDream.Core` is backend-free, the network
|
||||
layer is wire-compatible, the UI has a stable contract, plugins load.
|
||||
The structural debt is concentrated in **one file**:
|
||||
|
||||
```
|
||||
baseline cf50ee3d 15,723 lines / 278 fields / 205 methods
|
||||
after Slice 1 14,912 lines / 278 fields / 191 methods
|
||||
after Slice 2 14,546 lines / 277 fields / 190 methods
|
||||
after Slice 3 14,310 lines / 274 fields / 190 methods
|
||||
after Slice 4 10,301 lines / 267 fields / 163 methods
|
||||
after Slice 5 closeout 8,811 lines / 247 fields / 153 methods
|
||||
after Slice 6 closeout 7,026 lines / 241 fields / 108 methods
|
||||
after Slice 7 draw-frame cutover 4,666 lines / 196 fields / 70 methods
|
||||
after Slice 8 checkpoint D 4,330 lines / 192 fields / 67 methods
|
||||
after Slice 8 checkpoint E 4,266 lines / 194 fields / 65 methods
|
||||
after Slice 8 checkpoint F 4,057 lines / 198 fields / 54 methods
|
||||
after Slice 8 checkpoint G 3,663 lines / 162 fields / 37 methods
|
||||
after Slice 8 checkpoint I 1,945 lines / startup composition shell
|
||||
after Slice 8 checkpoint J 1,625 lines / focused lifetime shell
|
||||
after Slice 8 checkpoint K 1,622 lines / canonical soak shell
|
||||
```
|
||||
|
||||
At the campaign baseline, `GameWindow` was the single object that:
|
||||
|
||||
- Owns the Vulkan device, the window, input, and shaders.
|
||||
- Reads ~40 different environment variables across its lifetime.
|
||||
- Composes the shipped `LiveSessionController`/host/router boundary; it no
|
||||
longer owns a parallel session, command bus, subscription lifetime, connect,
|
||||
character-entry, reconnect, or graceful-close body.
|
||||
- Owns the adapters that hydrate canonical Runtime entity records into
|
||||
exact-key App projection sidecars and their animation, collision, rendering,
|
||||
and DAT-backed appearance resources.
|
||||
- Composes the shipped `WorldSelectionQuery` and
|
||||
`SelectionInteractionController`; it no longer owns world-picking,
|
||||
selection intent, Use/PickUp, or auto-walk deferral algorithms.
|
||||
- Drives per-frame render orchestration (sky → terrain → opaque mesh →
|
||||
transparent mesh → particles → debug lines → UI).
|
||||
- Builds and applies streamed landblock presentation (DAT decode, scenery,
|
||||
EnvCells, mesh publication, collision, and retirement) around the shipped
|
||||
`StreamingController` and `GpuWorldState` owners.
|
||||
- Wires up every plugin hook sink, every diagnostic, every panel.
|
||||
|
||||
The completed slices moved those bodies into focused, tested owners. The rule
|
||||
that governed the campaign still applies: a collaborator is a completed
|
||||
extraction only when it owns the state and behavior body; wrapping a
|
||||
`GameWindow` method in a delegate preserves ordering but remains a partial
|
||||
extraction.
|
||||
|
||||
The fix is **not** "rewrite `GameWindow` in one pass" — that is a
|
||||
high-risk change. The fix is to **extract one
|
||||
collaborator at a time**, verify behavior is unchanged, ship, and
|
||||
move on. This document defines that sequence.
|
||||
|
||||
---
|
||||
|
||||
## 2. Code Structure Rules — the discipline
|
||||
|
||||
Recap of the rules from `CLAUDE.md` with the rationale:
|
||||
|
||||
### Rule 1: No new substantial feature bodies in `GameWindow.cs`
|
||||
|
||||
**Why:** New feature bodies in the native shell would reverse the ownership
|
||||
campaign. Runtime work belongs in focused App owners, with only construction
|
||||
and narrow callback handoffs retained here.
|
||||
|
||||
**How to apply:** A new feature gets its own class under
|
||||
`src/AcDream.App/<Subsystem>/` (or deeper in `AcDream.Core` if it's pure
|
||||
logic). `GameWindow` owns a field and a wiring call, nothing more. If
|
||||
you find yourself adding a 200-line method to `GameWindow`, stop and
|
||||
extract.
|
||||
|
||||
**Exemption:** Trivial wiring that *must* stay in `GameWindow` because
|
||||
it touches device state during `OnLoad` is acceptable, but should still
|
||||
delegate to a collaborator for the substance.
|
||||
|
||||
### Rule 2: `AcDream.Core` must not depend on window / backend projects
|
||||
|
||||
**Why:** Core is the backend-free, testable layer. The moment Core imports
|
||||
a graphics or windowing namespace, we've lost the ability to test it without
|
||||
a device, and the layer split becomes fiction.
|
||||
|
||||
**How to apply:** Phase O removed both external WorldBuilder/backend project
|
||||
references. The only currently allowed seams are the backend-free helpers owned in
|
||||
our tree under `src/AcDream.Core/Rendering/Wb/`: `TerrainUtils`,
|
||||
`TerrainEntry`, `RegionInfo`, `SceneryHelpers`, and `TextureHelpers`.
|
||||
`ObjectMeshManager` and every GPU resource owner remain in App. If Core needs a
|
||||
new capability, define a narrow Core interface and implement it in App; adding
|
||||
a new project reference requires an inventory-doc update explaining why.
|
||||
|
||||
### Rule 3: UI panels target `AcDream.UI.Abstractions` only
|
||||
|
||||
**Why:** The rule was written to keep D.2b (the retail-look backend)
|
||||
viable while ImGui was still the developer stack, and **it paid off**: when
|
||||
Campaign V slice V11 deleted ImGui on 2026-07-29, not one panel contract,
|
||||
ViewModel or command had to change, because none of them had ever imported
|
||||
`ImGuiNET`. `AcDream.UI.Abstractions` survived a backend deletion intact.
|
||||
|
||||
**How to apply:** A panel's `using` block must mention
|
||||
`AcDream.UI.Abstractions.*` and nothing from any backend assembly. The panel
|
||||
writes against `IPanelRenderer`; a renderer implementation translates those
|
||||
calls at runtime. Plugin-facing UI follows the same rule.
|
||||
|
||||
The shared chat parser/router/catalog and its four command intents live in
|
||||
`AcDream.Runtime/Chat`, not in a panel or App. `AcDream.UI.Abstractions`
|
||||
references Runtime so retained `ChatVM` can implement the narrow
|
||||
`IChatCommandFeedback` seam and its existing panel input can call the shared
|
||||
router. That dependency does not permit panels to import App, windowing,
|
||||
rendering, audio, or another presentation backend; Runtime itself remains
|
||||
presentation-independent and its dependency guards enforce that boundary.
|
||||
|
||||
**Status:** there is currently no `IPanelRenderer` implementation in the tree —
|
||||
the ImGui one went with V11 and the replacement is issue **#258**. The contract
|
||||
is kept rather than deleted precisely because this rule proved its worth; a new
|
||||
host binds to it without touching a single panel.
|
||||
|
||||
### Rule 4: Startup env vars enter through `RuntimeOptions`
|
||||
|
||||
**Why:** Environment variables are global mutable state. Reading them
|
||||
at random call sites means (a) duplicated `Environment.GetEnvironmentVariable`
|
||||
boilerplate, (b) no single place to see "what flags does the client
|
||||
respond to?", (c) impossible to unit-test parsing.
|
||||
|
||||
**How to apply:** `src/AcDream.App/RuntimeOptions.cs` is the typed
|
||||
options object. `Program.cs` builds it once from args + env and passes
|
||||
it to `GameWindow`. New startup flags add a field to `RuntimeOptions`
|
||||
and a parser in `RuntimeOptions.FromEnvironment`. They don't add
|
||||
`Environment.GetEnvironmentVariable` reads.
|
||||
|
||||
**Scope:** `RuntimeOptions` is for **startup-time** configuration —
|
||||
things that don't change once the window is up. Runtime diagnostic
|
||||
toggles are Rule 5's domain.
|
||||
|
||||
### Rule 5: Runtime diagnostic toggles live in diagnostic owner classes
|
||||
|
||||
**Why:** Diagnostic flags (`ACDREAM_DUMP_MOTION`, `ACDREAM_PROBE_*`,
|
||||
etc.) need to be both env-readable at startup *and* runtime-toggleable
|
||||
from the DebugPanel. Per-call-site env reads can't be runtime-toggled.
|
||||
|
||||
**How to apply:** Today's template is
|
||||
`src/AcDream.Core/Physics/PhysicsDiagnostics.cs` — one static class with
|
||||
typed `Probe*` properties read from env vars once at startup, plus
|
||||
runtime setters that the DebugPanel binds. New diagnostic flags follow
|
||||
this shape, not the per-call-site pattern that dominates `GameWindow.cs`.
|
||||
|
||||
**Cleanup direction:** The dozens of existing `ACDREAM_DUMP_*` reads
|
||||
inside `GameWindow.cs` are tech debt. We do NOT bulk-migrate them as
|
||||
part of this refactor — they're working, they're scattered, and
|
||||
moving them carries risk without a current acceptor. We migrate them
|
||||
opportunistically: when a `GameWindow` extraction lands and a diagnostic
|
||||
moves with it, route it through the new owner's diagnostic class.
|
||||
|
||||
### Rule 6: Tests live in the project matching the layer
|
||||
|
||||
**Why:** Test discoverability + dependency hygiene. A test for a Core
|
||||
class belongs next to other Core tests; a test for an App class belongs
|
||||
in an App test project. Co-locating tests across layers makes the
|
||||
dependency graph dishonest.
|
||||
|
||||
**How to apply:** One test project per source project that has tests.
|
||||
Today:
|
||||
|
||||
- `tests/AcDream.Core.Tests/` ← `src/AcDream.Core/`
|
||||
- `tests/AcDream.Core.Net.Tests/` ← `src/AcDream.Core.Net/`
|
||||
- `tests/AcDream.Runtime.Tests/` ← `src/AcDream.Runtime/`
|
||||
- `tests/AcDream.UI.Abstractions.Tests/` ← `src/AcDream.UI.Abstractions/`
|
||||
- `tests/AcDream.App.Tests/` ← `src/AcDream.App/`
|
||||
|
||||
`tests/AcDream.App.Tests/` now exists and owns App-layer controller, streaming,
|
||||
render-resource lifetime, retained-UI, and `RuntimeOptions` tests. New App tests
|
||||
belong there; do not place backend-free Core behavior in that project merely because
|
||||
App currently wires it.
|
||||
|
||||
---
|
||||
|
||||
### Render-frame ownership versus recursive reachability
|
||||
|
||||
The typed `RenderFrameOrchestrator` graph owns frame sequencing and borrows
|
||||
long-lived runtime/presentation owners. The enforceable ownership invariant is
|
||||
that the orchestrator and its immediate phase owners retain neither
|
||||
`GameWindow`, an anonymous callback bag, nor direct window delegates. It is
|
||||
**not** an invariant that recursively walking every borrowed canonical owner
|
||||
can never reach an event subscriber or callback that was composed by
|
||||
`GameWindow`; that claim would be false and would confuse object-graph
|
||||
reachability with frame ownership.
|
||||
|
||||
Known recursive callback-bearing paths include (this is evidence and design
|
||||
documentation, not an exhaustive allowlist):
|
||||
|
||||
- `RetainedGameplayUiFrame -> RetailUiRuntime -> RetailUiRuntimeBindings` for
|
||||
retained gameplay UI state and commands.
|
||||
- `PrivatePresentationRenderer -> PaperdollFramePresenter ->
|
||||
RetailPaperdollFrameView/PaperdollInventoryVisibility -> UiElement tree` for
|
||||
private paperdoll visibility and texture publication. This is a second,
|
||||
independent retained-UI entry because `UiElement.Parent` links back to the
|
||||
shared root and sibling controls.
|
||||
- `DevToolsFramePresenter -> DevToolsPanelSet -> panel/ViewModel bindings` for
|
||||
the optional developer UI.
|
||||
- `WorldRenderFrameBuilder -> RuntimeWorldFrameSettingsPreview ->
|
||||
IRuntimeSettingsPreviewSource -> RuntimeSettingsController` for the settings
|
||||
snapshot read before world drawing. (The `SettingsVM` draft-preview tail of
|
||||
this seam was retired at Campaign OP slice OP9 — the preview source now
|
||||
mirrors the committed Display/Audio snapshot directly; the retail Options
|
||||
panel applies its edits live through `SaveDisplay`/`SaveAudio` instead of a
|
||||
draft layer.)
|
||||
- `LocalPlayerPortalViewport -> LocalPlayerTeleportController ->
|
||||
GameplayInputFrameController -> InputDispatcher.Fired -> GameWindow` for the
|
||||
canonical portal/input lifetime and the host's input-action subscription.
|
||||
|
||||
These are presentation state/command seams, not alternative owners of the
|
||||
window or frame transaction. The screenshot path is deliberately not on this
|
||||
list: viewport width/height now travel in `RenderFrameInput`, so
|
||||
`FrameScreenshotController` no longer stores a window-size callback. New
|
||||
immediate phase owners must still satisfy the direct ownership invariant;
|
||||
recursive shared-owner paths are reviewed against their canonical subsystem
|
||||
lifetime rather than an impossible global no-callback assertion.
|
||||
|
||||
---
|
||||
|
||||
## 3. Target structure of the App layer
|
||||
|
||||
The end state — not what we're shipping in one pass, but the shape
|
||||
we're aiming at.
|
||||
|
||||
```
|
||||
src/AcDream.App/
|
||||
├── Program.cs # parse args + env → RuntimeOptions, build GameWindow
|
||||
├── RuntimeOptions.cs # typed startup options (Rule 4)
|
||||
├── Rendering/
|
||||
│ ├── GameWindow.cs # thin: device/window lifecycle + delegates per-frame to RenderFrameOrchestrator
|
||||
│ ├── RenderFrameOrchestrator.cs # GPU-flight boundary + typed world/private/UI render phases
|
||||
│ ├── LiveEntityAnimationScheduler.cs # shipped: ordinary live-object update workset
|
||||
│ ├── LiveEntityAnimationPresenter.cs # final part-pose/mesh/effect composition after scheduler output
|
||||
│ ├── RetailStaticAnimatingObjectScheduler.cs # shipped: separate static-animation workset
|
||||
│ ├── StaticLiveRootCommitter.cs # live static root → pose + collision boundary
|
||||
│ ├── TerrainModernRenderer.cs # (already exists)
|
||||
│ ├── TextureCache.cs # (already exists)
|
||||
│ ├── ParticleRenderer.cs # (already exists)
|
||||
│ ├── Sky/ # (already exists)
|
||||
│ ├── Wb/ # WB seam + EnvCellLandblockBuild transaction
|
||||
│ └── Vfx/ # (already exists)
|
||||
├── Net/
|
||||
│ ├── LiveSessionController.cs # owns complete WorldSession connect/enter/logout/reconnect lifecycle
|
||||
│ ├── LiveSessionHost.cs # App reset/selection/entry/route-factory composition over the canonical controller
|
||||
│ ├── LiveSessionLifecycleHost.cs # exact borrowed-session attachment guard
|
||||
│ ├── RetailSkillFormula.cs # named-retail unsigned skill-credit calculation + DAT lookup
|
||||
│ └── LiveSessionEventRouter.cs # typed subscriptions → focused domain handlers
|
||||
├── Physics/
|
||||
│ ├── ProjectileController.cs # canonical live-record projectile orchestration
|
||||
│ ├── RemotePhysicsUpdater.cs # ordinary/Hidden remote narrow-tick integration
|
||||
│ ├── LiveEntityOrdinaryPhysicsUpdater.cs # manager-less canonical body Transition path
|
||||
│ ├── LiveEntityNetworkUpdateController.cs # Position/Vector/State/Motion App integration
|
||||
│ ├── LiveEntityInboundAuthorityGate.cs # exact per-channel timestamp/version admission
|
||||
│ ├── LiveEntityMotionRuntimeController.cs # shared host/setup/MoveTo/Sticky runtime policy
|
||||
│ ├── DeferredLiveEntityMotionRuntimeBindings.cs # fail-fast construction-order bridge
|
||||
│ ├── LiveEntityShadowPublisher.cs # authoritative exact-owner/residency collision gate
|
||||
│ ├── RemoteInboundMotionDispatcher.cs # shared animated/headless UpdateMotion funnel
|
||||
│ └── RemoteTeleportHook.cs # ordered retail teleport teardown actions (teleport_hook port; C4 4b-3 deleted RemoteTeleportController/RemoteTeleportPlacement/RemoteShadowPlacementSynchronizer — the teleport arm now routes through RuntimeRemotePlacementDriveController, the same canonical placement owner the far snap uses)
|
||||
├── World/
|
||||
│ ├── InboundPhysicsStateController.cs # timestamps + accepted spawn snapshots
|
||||
│ ├── LiveEntityRuntime.cs # shipped: logical lifetime + ServerGuid↔entity.Id translation
|
||||
│ ├── LiveEntityHydrationController.cs # spawn/appearance/parent/delete resource integration
|
||||
│ ├── RetailInboundEventDispatcher.cs # update-thread packet/frame FIFO barrier
|
||||
│ ├── RetailLiveFrameCoordinator.cs # shipped: object/network/command/reconcile phase order
|
||||
│ ├── LiveEntityPresentationController.cs # ordered Hidden/NoDraw/effect/collision side effects
|
||||
│ ├── WorldEnvironmentController.cs # one-shot clock/DAT-sky/day/weather/AdminEnvirons owner
|
||||
│ ├── LiveEntityTeardown.cs # failure-isolated multi-owner lifecycle drain
|
||||
│ └── ParentAttachmentState.cs # parent generations + pending ParentEvent relations
|
||||
├── Interaction/
|
||||
│ ├── WorldSelectionQuery.cs # read-only picking/classification/description queries
|
||||
│ └── SelectionInteractionController.cs # owns selection intents, Use/PickUp, auto-walk deferral
|
||||
├── Streaming/
|
||||
│ ├── LandblockPresentationPipeline.cs # DAT build/apply/retire transaction and resource publication
|
||||
│ ├── StreamingController.cs # typed frame scheduler + reveal destination reservation
|
||||
│ ├── WorldRevealCoordinator.cs # graphical reveal projection and exact host receipts
|
||||
│ └── ... # shipped streamer/world-state owners
|
||||
├── Input/ # (already exists)
|
||||
├── Audio/ # (already exists)
|
||||
└── Plugins/ # (already exists)
|
||||
```
|
||||
|
||||
What `GameWindow` keeps:
|
||||
|
||||
- `IWindow` / device / `IInputContext` lifecycle (constructor + `OnLoad` +
|
||||
`Run` + `OnClosing`).
|
||||
- `RuntimeOptions` reference (the typed startup config).
|
||||
- GPU resource construction and top-level collaborator composition. Construction
|
||||
is allowed here; feature algorithms and mutable subsystem state are not.
|
||||
- One field per top-level collaborator (`_liveSessionController`,
|
||||
`_liveEntityRuntime`, `_selectionInteraction`, `_streamingPresentation`,
|
||||
`_liveObjectFrame`, `_renderFrameOrchestrator`).
|
||||
- The Silk.NET event-handler stubs that delegate to collaborators.
|
||||
|
||||
What `GameWindow` loses:
|
||||
|
||||
- Live connect/enter/logout and event-subscription bodies → completed inside
|
||||
`LiveSessionController` / `LiveSessionEventRouter`.
|
||||
- Live-object hydration adapters and final animated-part presentation → focused
|
||||
world/render owners over exact-key App projection sidecars backed by
|
||||
canonical Runtime records.
|
||||
- `WorldPicker`, target queries, and selection-driven Use/PickUp/auto-walk →
|
||||
`WorldSelectionQuery` + `SelectionInteractionController`. Core
|
||||
`SelectionState` remains the injected session owner.
|
||||
- Landblock DAT build/apply/retirement presentation →
|
||||
`LandblockPresentationPipeline`; `StreamingController` remains the residency
|
||||
scheduler and `GpuWorldState` remains the spatial registry.
|
||||
- Per-frame draw orchestration and its frame-local scratch state →
|
||||
`RenderFrameOrchestrator`.
|
||||
|
||||
The eventual `GameEntity` aggregation (target state described in
|
||||
`acdream-architecture.md` §"GameEntity: The Unified Entity") happens
|
||||
**after** `RuntimeEntityDirectory` is the single owner of canonical entity
|
||||
state. That boundary is now shipped. App's former parallel presentation
|
||||
dictionaries are bounded by one exact-key projection store rather than spread
|
||||
across `GameWindow`.
|
||||
|
||||
`RuntimeEntityDirectory` owns the only active server-GUID/incarnation map,
|
||||
Runtime local-ID allocation/reverse lookup, accepted immutable CreateObject
|
||||
snapshot and nine-channel timestamp gates, parent relations, operation
|
||||
versions, and exact teardown tombstones. `RuntimeEntityRecord` is
|
||||
presentation-free. `LiveEntityRuntime` is the App projection/lifecycle host:
|
||||
it creates a `LiveEntityRecord` sidecar only after Runtime local-ID claim and
|
||||
stores it in `LiveEntityProjectionStore` by exact `RuntimeEntityKey`.
|
||||
Hydration, animation, effects, lights, equipped children, render identity,
|
||||
movement observations, liveness, teardown, and spatial worksets borrow that
|
||||
same exact identity. `LiveEntityPresentationController` projects Runtime state
|
||||
transitions into draw, collision, effects, child-NoDraw, and target visibility
|
||||
without becoming a second canonical owner.
|
||||
|
||||
`Rendering/Vfx/EntityEffectController` owns the focused mixed F754/F755 pending
|
||||
FIFO, effect profiles, typed-table resolution, and readiness markers. A GUID
|
||||
is retained there only as pending wire-delivery metadata; current-incarnation
|
||||
resolution always asks `RuntimeEntityDirectory` and then the exact projection
|
||||
store. `EntityScriptActivator` uses the same Runtime-issued `WorldEntity.Id` as
|
||||
rendering and physics; disjoint static ID allocators fail fast instead of
|
||||
wrapping into another landblock's namespace. All other non-Parent pre-create
|
||||
packet families still need the future general queue tracked by divergence
|
||||
AD-32.
|
||||
|
||||
The per-frame object scheduler is extracted, but final animated-part
|
||||
presentation is not. `LiveEntityAnimationScheduler` snapshots canonical spatial
|
||||
root records and advances the incarnation-stable object clock, PartArray,
|
||||
hooks, one selected movement owner, and manager tail in retail order. The
|
||||
remaining `GameWindow.TickAnimations` loop still composes part transforms,
|
||||
drawable `MeshRef`s, and effect poses from the scheduler output; moving that
|
||||
body into `LiveEntityAnimationPresenter` is an explicit pending slice.
|
||||
Manager-less
|
||||
bodies delegate their candidate/Transition/cell/shadow commit to
|
||||
`LiveEntityOrdinaryPhysicsUpdater`; retained projectile bodies and remote
|
||||
MovementManagers remain mutually exclusive movement owners. Static animation
|
||||
is deliberately separate: `RetailStaticAnimatingObjectScheduler` owns the
|
||||
`CPhysics::static_animating_objects` workset for DAT and live PhysicsState-
|
||||
Static owners with Setup DefaultAnimation. Both schedulers are wired by
|
||||
`GameWindow`, but neither owns GUID identity or logical resources.
|
||||
The typed animation view fills its reusable snapshot and render-ID set from
|
||||
the exact-key projection workset, avoiding interface-enumerator boxing on both
|
||||
update and render hot paths.
|
||||
`RemoteInboundMotionDispatcher` similarly keeps UpdateMotion protocol behavior
|
||||
outside `GameWindow`: GameWindow resolves the canonical record/body and the
|
||||
optional PartArray sink, while one dispatcher owns retail's interrupt, style,
|
||||
MoveTo/type-0, sticky, and standing-long-jump order. Static root projection is
|
||||
bounded by `StaticLiveRootCommitter`, which synchronizes changed roots to
|
||||
effects and collision without rebuilding zero-omega shadows or resurrecting a
|
||||
Hidden/withdrawn registration.
|
||||
|
||||
Synchronous network and lifecycle callbacks are bounded by
|
||||
`RetailInboundEventDispatcher`. It owns no wire state and no identity; it only
|
||||
serializes nested live-object operations until the current packet or full
|
||||
object-frame tail completes. State-bearing direct dispatch is allocation-free;
|
||||
only a genuinely nested operation allocates its retained queue wrapper.
|
||||
`LiveEntityRecord` then supplies exact-incarnation
|
||||
and per-channel authority versions at callback boundaries. Position, State,
|
||||
Vector, and Movement remain independent, while a separate velocity version
|
||||
invalidates only an older operation that would overwrite a newer velocity
|
||||
installed by Position, Vector, or Movement. This prevents re-entrant App
|
||||
observers from creating call-stack ordering that retail's update-thread packet
|
||||
FIFO cannot produce.
|
||||
|
||||
Pose-dependent hook deferral is similarly incarnation-scoped rather than GUID-
|
||||
or local-ID-scoped. `EntityEffectPoseRegistry` publishes a monotonic pose-owner
|
||||
lifetime, and `AnimationHookFrameQueue` captures it before semantic callbacks
|
||||
and rechecks it before each semantic AnimationDone and each routed hook. Static animation retains `process_hooks`
|
||||
until its root, live parts, and children are published; withdrawal invalidates
|
||||
both its prepared pose and pending hook tail.
|
||||
|
||||
Resolved ordinary motion commits its body/root/contact state before the
|
||||
canonical full-cell setter enters `LiveEntityRuntime.RebucketLiveEntity`.
|
||||
Because that setter may synchronously move the projection to pending or replace
|
||||
the GUID, both `RemotePhysicsUpdater` and `LiveEntityOrdinaryPhysicsUpdater`
|
||||
revalidate the exact incarnation before collision or manager-tail publication.
|
||||
Collision residency itself is projection-owned, not updater-owned:
|
||||
`LiveEntityPresentationController` suspends retained non-projectile shadows on
|
||||
every unavailable-projection edge, restores them on hydration, and reconciles
|
||||
the pending-first case at `OnLiveEntityReady` after collision registration.
|
||||
Local projection and both authoritative remote UpdatePosition tails commit the
|
||||
complete root before rebucketing, then publish collision only through an
|
||||
exact-record/spatial-residency gate. Remote reflood tracks translation,
|
||||
sign-invariant complete orientation, and cell changes so an in-place turn or a
|
||||
same-pose EnvCell crossing cannot leave offset Setup shapes stale.
|
||||
The projectile controller retains its separate body/InWorld/shadow edge owner.
|
||||
|
||||
During login/portal quiescence, loaded projection residency and gameplay
|
||||
visibility are separate. `LiveEntityRuntime` validates spatial ownership
|
||||
through `GpuWorldState.IsLiveEntityProjectionResident`, allowing destination
|
||||
renderer preparation to converge offscreen. World consumers continue through
|
||||
availability-gated `IsLiveEntityVisible`, so drawing, collision, picking,
|
||||
radar/status targeting, effects, and audio remain closed until reveal. This
|
||||
distinction is part of the Slice E connected gate, not an alternate live-entity
|
||||
lifetime.
|
||||
|
||||
**C4 route 4b-3 (2026-08-04) deleted `Physics/RemoteTeleportController` and
|
||||
`RemoteTeleportPlacement` outright** (605 + 85 lines, plus
|
||||
`RemoteShadowPlacementSynchronizer`, 49 lines). Remote teleport placement is
|
||||
no longer a separate App-layer incarnation-scoped machine — it is the SAME
|
||||
canonical `RuntimeRemotePlacementDriveController` route 4b-2's far snap
|
||||
already uses, dispatched via `ApplyAcceptedRemoteTeleport`, which shares
|
||||
`StoresAcceptedDestination`/`StoreAcceptedDestinationPose` unchanged.
|
||||
`teleport_hook` (@0x00514ED0) still runs first — `RemoteTeleportHook`,
|
||||
invoked from `LiveEntityNetworkUpdateController`'s teleport-arm dispatch —
|
||||
and its `report_collision_end(this, 1)` action now routes through
|
||||
`RuntimeCollisionReportingState.LeaveWorld` (the existing, exact port of
|
||||
that retail call) rather than `ShadowObjectRegistry.Suspend`, which ports a
|
||||
DIFFERENT retail function `teleport_hook` never calls. `GpuWorldState`
|
||||
performs remove+place as one spatial rebucket,
|
||||
then commits and serially drains visibility edges; `LiveEntityRuntime` filters
|
||||
delayed duplicates. A rollback inside an observer cannot race the outer
|
||||
destination-visible notification or expose an intermediate false pulse.
|
||||
`LiveEntityTeardown` executes those independent owner callbacks to completion
|
||||
and aggregates failures afterwards, so a throwing effect/plugin sink cannot
|
||||
strand teleport, movement, shadow, light, or GUID-scoped state.
|
||||
|
||||
---
|
||||
|
||||
## 4. Reconciled ownership ledger — 2026-07-21
|
||||
|
||||
This section replaces the original six-step sketch. That sketch correctly
|
||||
identified the target but overstated some extractions and omitted large bodies
|
||||
added during M2/M3. The current audit is based on implementation ownership, not
|
||||
whether a class with the planned name exists.
|
||||
|
||||
### 4.1 What counts as extracted
|
||||
|
||||
An extraction is **complete** only when:
|
||||
|
||||
1. the collaborator owns the mutable state and behavior body;
|
||||
2. `GameWindow` constructs it and delegates through a narrow method;
|
||||
3. the collaborator does not call back into arbitrary `GameWindow` methods;
|
||||
4. focused tests exercise the collaborator without constructing a Silk window;
|
||||
5. the accepted connected/visual behavior is unchanged.
|
||||
|
||||
A class that stores delegates back to substantial `GameWindow` methods is a
|
||||
useful ordering seam, but its ownership status is **partial**.
|
||||
|
||||
### 4.2 Current implementation truth
|
||||
|
||||
| Area | Status | Current truth |
|
||||
|---|---|---|
|
||||
| Startup options | **Complete** | `RuntimeOptions` owns startup configuration (`eda936dc`). Remaining direct environment reads are legacy runtime diagnostics, not startup configuration. |
|
||||
| Network session | **Complete Runtime ownership** | `LiveSessionController` owns the sole `WorldSession` generation and resolve/create/Connect/pre-world selection/EnterWorld/Tick/stop/reconnect/disposal transaction. Its `RuntimeCharacterSelectionState` owns the full active roster (including greyed entries and retained wire slots), highlight, delete confirmation, restore/delete/error state, generation/lifecycle, borrowed view, ordered deltas, and typed commands. A selector-free graphical launch pauses on that owner; explicit and headless selection retain the established fallback. Runtime route owners preserve exact inbound/outbound ordering and retryable teardown. App supplies immutable options, graphical/domain callbacks, and borrowed projections—no mirrored session, selection state, or reset plan. |
|
||||
| World environment | **J6.1 complete Runtime ownership** | `RuntimeWorldEnvironmentState` owns the instance-scoped Dereth calendar, synchronized clock, weather progression/state, selected day group, AdminEnvirons state, and typed debug overrides. App converts immutable DAT sky definitions once and projects the borrowed Runtime snapshot into rendering; no process-global Region origin or second App clock/weather owner remains (`902076c0`). TS-54/TS-55 register the remaining centered UI sound and full fog/ambient/radar behavior gaps. |
|
||||
| Live identity/lifetime | **J3 complete** | `RuntimeEntityObjectLifetime` owns the sole `RuntimeEntityDirectory`, live `ClientObjectTable`, direct views, and ordered entity/object stream. The directory owns canonical GUID/incarnation/local-ID identity, accepted snapshots/timestamps, parent state, operation versions, and tombstones. `LiveEntityProjectionStore` owns App graphical sidecars by exact `RuntimeEntityKey`; hydration, presentation components, `GpuWorldState` residence/visibility, and retryable teardown preserve that key without another authority. Exact receipts precede fallible callbacks, re-entrant commits drain synchronously in sequence, and stable reset/disposal must converge the complete ledger to zero (`f46ddb5c`, `420e5eea`, `e937cc36`, `5ef8b537`, `ce3ac310`, `119b7c11`). |
|
||||
| Inbound/object-frame order | **Complete App orchestration** | `UpdateFrameOrchestrator` owns the complete typed host phase graph; `RetailInboundEventDispatcher`, `RetailLiveFrameCoordinator`, `LiveObjectFrameController`, `LiveSpatialPresentationReconciler`, streaming/input/teleport/player-mode/camera owners preserve the accepted order. `GameWindow.OnUpdate` is one profiler-scoped handoff (`e91f3102`). |
|
||||
| World reveal/transit | **J6 complete Runtime ownership** | `RuntimeWorldTransitState` owns the sole monotonic login/portal reveal generation, wrap-safe F751 history, pending/active teleport sequence, both accepted F751/Position packet orders, exact destination, typed atomic readiness latch, generation/sequence/cell placement and materialization validation, simulation edge, viewport observation, completion, cancellation, retail wait cue, portal count, and exact typed graphical-host acknowledgement suffix. `WorldRevealCoordinator` and `LocalPlayerTeleportController` are graphical adapters over App readiness, generation-scoped resource receipts, render-space placement, tunnel, and UI; failure/re-entrancy retains the exact unacknowledged suffix. `WorldGenerationAvailabilityState` borrows Runtime truth and owns no mutable generation. The App transit coordinator and accepted-destination mirror are deleted (`a6860d55`, `acb845d8`, `6a063a27`, `18d17d8b`). |
|
||||
| Retained gameplay UI | **Mostly complete feature ownership** | `RetailUiRuntime` and focused panel/controllers own layout and behavior. `GameWindow.OnLoad` still performs substantial service composition, which is allowed until the final composition cleanup. |
|
||||
| Action/combat/magic/movement | **J5 complete** | Runtime `RuntimeActionState` owns the exact selection, temporary target mode, interaction transactions, combat attack/target/mode, and spell-cast intent children. Interaction owns use/appraisal/pickup identity, ordered FIFO, and exact post-arrival token while borrowing J4's sole busy gate. `RuntimeLocalPlayerMovementState` owns the exact local controller, construction seam, autorun latch, typed view, and outbound MTS/jump/AP cadence. Retained bars, physical input, world/content queries, transport, lighting, toasts, drag/drop, effects, and animation remain App adapters/presentation. One combined Runtime ledger spans entity/object, gameplay, physics, remote, and projectile ownership (`b298f99f`, `f5f7b417`, `20df9d15`, `aa3f4a60`, `cdee7a4b`). |
|
||||
| Physics/remote simulation | **J5.5 complete** | `RuntimePhysicsState` owns one per-session engine, production cache/cell graph, transition scratch, shadow registry, typed collision admissions, canonical bodies/hosts/remotes, keyed ordinary/remote worksets, simulation, and cell commits. App streaming supplies immutable prepared collision; animation/shape adapters supply graphical inputs and project committed snapshots only (`7e6033d0`). |
|
||||
| Projectile simulation | **J5.6 complete** | `RuntimeEntityRecord`/`RuntimePhysicsState` own the canonical projectile component, exact-key workset, prediction/correction state, unchanged retail quantum/integration/sweep/collision loop, and cell commits. App resolves immutable Setup/DAT shape input and projects committed render, shadow, and effect-pose results only (`2aee3356`). |
|
||||
| Landblock presentation | **Complete** | `LandblockBuildFactory` owns the captured-origin DAT transaction; concrete render/physics/DAT-static publishers and `LandblockPresentationPipeline` own typed-meter publication and exact retryable retirement. `StreamingController` owns stable destination/control/unload/Near/Far queues and destination reservation. CPU mesh-cache restaging requires an exact live owner. `StreamingOriginRecenterCoordinator` serializes old-window retirement with teleport/session origin lifetimes. `GameWindow` retains construction and one pipeline field only (`c79d0a49`, closeout `4a205a3e`; Slice E closeout `91e82c3c`). |
|
||||
| Render-frame orchestration | **Complete** | `RenderFrameOrchestrator` owns the GPU-flight, resource, world/PView/shared-alpha, private-presentation, diagnostics, screenshot, and recovery graph. `GameWindow.OnRender` takes one logical window-size snapshot and performs one immutable handoff (`9d7df1bf`). |
|
||||
| Unified `GameEntity` | **Slice J complete** | Canonical identity, retained-object lifetime, direct views, ordered entity/object deltas, gameplay state, action/combat/magic/movement/physics/remote/projectile simulation, world environment, reveal/transit truth, and host acknowledgement share failure-safe Runtime owners while exact graphical sidecars stay in App. One `GameRuntime` composes the graph. `RuntimeGenerationReset` is the sole retryable canonical-generation reset for graphical and no-window hosts; the deterministic direct host proves lifecycle, commands, portal, reconnect, GUID reuse, fault recovery, isolation, and terminal convergence without presentation assemblies (`a9a822f2`). |
|
||||
| Headless host | **Slice K complete** | `AcDream.Headless` references only Runtime and loads no App/UI/graphics/window/audio assembly. Portable paths/config/credentials, deterministic command/event scheduling, shared immutable content, exact per-session identity, failure quarantine, reconnect, resource telemetry, 1/5/10/30-session isolation, two-hour simulated endurance, and the exact two-account native-Linux connected soak pass through `776482da`. |
|
||||
|
||||
### 4.3 Revised extraction sequence
|
||||
|
||||
Every numbered slice is behavior-preserving and independently committed. A
|
||||
slice does not include feature work or opportunistic gameplay fixes.
|
||||
|
||||
#### Gate 0 — deterministic baseline — COMPLETE
|
||||
|
||||
The Release suite, connected R6 soak, world-lifecycle screenshots/checkpoints,
|
||||
graceful reconnect, local locomotion/collision/projectile/teleport gate, and
|
||||
two-client portal observer gate form the pre-refactor baseline. Each later
|
||||
slice runs the subset capable of detecting its risk; render/session slices run
|
||||
the complete connected lifecycle gate.
|
||||
|
||||
#### Slice 1 — finish selection/interaction ownership — COMPLETE 2026-07-21
|
||||
|
||||
Detailed execution plan:
|
||||
[`docs/plans/2026-07-21-gamewindow-slice-1-selection-interaction.md`](../plans/2026-07-21-gamewindow-slice-1-selection-interaction.md).
|
||||
|
||||
Split the old Step 4 into three reviewable commits:
|
||||
|
||||
1. `WorldSelectionQuery` receives camera/scene/live-object read seams and owns
|
||||
picking, target classification, selection bounds, closest-target lookup,
|
||||
names, and descriptions. It cannot mutate selection or send packets.
|
||||
2. `SelectionInteractionController` owns selection intents, double-click Use,
|
||||
Use/PickUp packet requests, range decisions, speculative facing, and the
|
||||
pending auto-walk action. It composes the existing item/combat controllers
|
||||
rather than duplicating their rules.
|
||||
3. Selection/Use/PickUp/combat-target `InputAction` cases delegate to the new
|
||||
controller; the old methods and fields are deleted from `GameWindow`.
|
||||
|
||||
Tests cover read-only query classification separately from stateful intents,
|
||||
including direct use, distant auto-walk completion, corpse/container opening,
|
||||
pickup placement, hostile-only targeting, Hidden objects, and same-GUID reuse.
|
||||
The cutover landed in `047a4c83`, `52dbb574`, `e74f2ca9`, `fa8d5232`,
|
||||
`d2bb5af4`, and review-correction commit `5acc3f01`. The final review also
|
||||
ported the shared `ACCWeenieObject::prevRequest` transaction shape across
|
||||
inventory, toolbar, paperdoll, external-container, give, split, merge, and
|
||||
drop routes. Queued actions, pending placements, and responses are bound to
|
||||
exact object incarnations/tokens; optimistic projection, rollback projection,
|
||||
and authoritative response notices are distinct.
|
||||
|
||||
Measured against `cf50ee3d`, `GameWindow` fell from 15,723 to 14,912 lines and
|
||||
from 205 to 191 methods; field count stayed at 278 because the slice exchanged
|
||||
legacy state for composed owners. Three independent retail, architecture, and
|
||||
adversarial review loops finished clean. Release build passed; 6,558 tests
|
||||
passed and five fixture/conformance tests skipped intentionally.
|
||||
|
||||
#### Slice 2 — finish live animation presentation — COMPLETE 2026-07-21
|
||||
|
||||
Move `TickAnimations`, final rigid/visual part composition, effect-pose
|
||||
publication, and motion-done binding into `LiveEntityAnimationPresenter`.
|
||||
`LiveEntityAnimationScheduler` remains the time/movement owner; the presenter
|
||||
only consumes one scheduler result and publishes the final draw/effect pose.
|
||||
Move the surviving motion diagnostics with this owner instead of adding more
|
||||
environment reads to `GameWindow`.
|
||||
|
||||
Tests pin legacy and sequencer paths, Hidden/static eligibility, ObjScale versus
|
||||
rigid effect poses, part availability, same-frame hook pose publication, and
|
||||
incarnation replacement. Run the R6 object-frame and projectile/effect suites
|
||||
plus the connected locomotion/projectile gate.
|
||||
|
||||
Result: `LiveEntityAnimationPresenter` owns final visual/rigid part
|
||||
composition, effect-pose publication, MotionDone binding, and typed diagnostics.
|
||||
Schedules carry exact record/entity/component/epoch/projection/presentation
|
||||
identity and own their authored frame prefix until the next scheduler tick.
|
||||
Appearance and static-owner rebinding invalidate stale work. Named-retail
|
||||
`CPartArray::UpdateParts` short-frame retention now preserves both visual and
|
||||
rigid trailing poses. `GameWindow` lost 366 lines, two methods, and one net
|
||||
field. Three independent retail, architecture, and adversarial review loops
|
||||
finished clean; focused App/Core tests and the complete App suite pass.
|
||||
|
||||
#### Slice 3 — complete live-session ownership — COMPLETE 2026-07-21
|
||||
|
||||
Detailed execution plan:
|
||||
[`docs/plans/2026-07-21-gamewindow-slice-3-live-session.md`](../plans/2026-07-21-gamewindow-slice-3-live-session.md).
|
||||
|
||||
`LiveSessionController` now owns the complete resolve/create/bind/Connect/
|
||||
CharacterList/select/EnterWorld/Tick/stop/reconnect/dispose transaction. Exact
|
||||
generation and operation-depth gates prevent synchronous lifecycle callbacks
|
||||
from resurrecting a superseded session. Staged teardown makes commands inert,
|
||||
detaches events, gracefully disposes the exact `WorldSession`, detaches the
|
||||
host, and runs the full reset manifest to convergence before reuse.
|
||||
|
||||
`LiveSessionLifecycleHost` is the narrow App adapter; the router and command
|
||||
owners are transactional and session-bound. `GameWindow` lost `_liveSession`,
|
||||
the router/command fields, and the old startup/reset/wiring/disposal methods.
|
||||
Both UI stacks resolve `controller.Commands`; direct feature sends borrow
|
||||
`CurrentSession` at call time. Forty-seven controller cases plus host,
|
||||
transport-allocation, and structural gates cover failure/reentrancy/reuse.
|
||||
Three independent retail, architecture, and adversarial reviews finished
|
||||
clean. The Release suite passes 6,714 tests with five intentional skips. The
|
||||
306-second connected lifecycle gate passed capped login, five travel/revisit
|
||||
checkpoints, exact graceful close, and uncapped fresh-process reconnect; both
|
||||
client processes exited normally.
|
||||
|
||||
`GameWindow` fell another 236 lines and three fields to 14,310 lines / 274
|
||||
fields / 190 methods. Slice 4 is next.
|
||||
|
||||
#### Slice 4 — extract live-entity App integration — COMPLETE 2026-07-21
|
||||
|
||||
Detailed execution ledger:
|
||||
[`docs/plans/2026-07-21-gamewindow-slice-4-live-entity-integration.md`](../plans/2026-07-21-gamewindow-slice-4-live-entity-integration.md).
|
||||
|
||||
This is two owners, not one replacement god object:
|
||||
|
||||
- `LiveEntityHydrationController` owns CreateObject/ObjDesc/Parent/Pickup/Delete
|
||||
integration, DAT-backed appearance hydration, resource registration, and
|
||||
exact teardown callbacks over `LiveEntityRuntime`.
|
||||
- `LiveEntityNetworkUpdateController` owns App routing for accepted
|
||||
Position/Vector/State/Movement updates into the existing remote physics,
|
||||
motion, projectile, presentation, and teleport controllers.
|
||||
|
||||
Neither may own a GUID dictionary. Both resolve the current incarnation only
|
||||
through `LiveEntityRuntime` and must preserve the inbound FIFO/authority-token
|
||||
rules. Run the complete live-entity stress suite, R6 connected route, inventory
|
||||
equip/parenting, death/corpse, and portal gates.
|
||||
|
||||
Checkpoints A and B are complete. `d68c83d1`, `5882b308`, and `fcb66198`
|
||||
established the shared origin, lifecycle, remote-motion, and exact-record
|
||||
physics-host seams. `69a2ca0c` extracted appearance/collision/default-pose and
|
||||
projection-withdrawal mechanics, including exact recursive `leave_world`
|
||||
subtree handling and retained retry ownership. Three independent reviews were
|
||||
clean and the Release suite passes 6,799 tests with five intentional skips.
|
||||
Checkpoints C and D are complete. `d10c5f2d` moved CreateObject, first
|
||||
materialization, origin bootstrap, and landblock recovery into
|
||||
`LiveEntityHydrationController`; `fe551496` moved ObjDesc, Parent, Pickup,
|
||||
child-ready, and exact leave-world/re-entry routing. Appearance and attached
|
||||
subtree recovery now use independent authority transactions and preserve the
|
||||
same logical/runtime identities. Checkpoint E is complete in `f38822c4`:
|
||||
Delete, visibility prune, retryable exact-incarnation component cleanup, and
|
||||
derived remote stop-observation lifetime now have focused owners. Checkpoint F
|
||||
completed in `aa90c646`: accepted Position/Vector/State/Movement and the
|
||||
equal-generation CreateObject tail now route through focused owners.
|
||||
Independent authority/version captures survive reentrant callbacks and GUID
|
||||
reuse; shared motion-runtime policy is one-way bound and no extracted owner
|
||||
keeps a second identity dictionary. ForcePosition serializes and stamps one
|
||||
exact canonical outbound Position. Three corrected-diff reviews, the complete
|
||||
6,940-test Release suite, and the 310-second seven-destination connected R6
|
||||
route all passed. `GameWindow` is 10,301 lines / 267 fields / 163 methods.
|
||||
Slice 5 checkpoints A–G are complete. The pipeline owns the serialized worker
|
||||
build, concrete render/physics/static publication, and retryable Near/full
|
||||
retirement. The direct cutover deleted the legacy apply/facade/scratch body and
|
||||
added one retained shared-origin lifetime transaction for teleport, logout,
|
||||
and fast relogin. `GameWindow.cs` was 8,793 raw lines at checkpoint G and no
|
||||
longer owns landblock build/apply/retirement behavior. H then corrected native
|
||||
shutdown ordering so live-session reset converges before the streamer and
|
||||
other reset dependencies are disposed. The final measurement is 8,811 raw
|
||||
lines / 247 fields / 153 methods. The complete Release suite (7,054 pass / 5
|
||||
skip), the 311-second capped/reconnect lifecycle gate, and the synchronized
|
||||
394-second nine-stop resource soak all pass (`4a205a3e`).
|
||||
|
||||
#### Slice 5 — extract landblock presentation — COMPLETE
|
||||
|
||||
Detailed execution ledger:
|
||||
[`docs/plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md`](../plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md)
|
||||
(complete).
|
||||
|
||||
Create `LandblockPresentationPipeline` around the existing immutable
|
||||
`LandblockBuild` transaction. It owns DAT build context, scenery/EnvCell
|
||||
construction, mesh/collision/light publication, demotion/full-retirement, and
|
||||
the single-reader DAT lock contract. `StreamingController` continues deciding
|
||||
what is resident; `GpuWorldState` continues owning spatial buckets.
|
||||
|
||||
Tests pin loaded/pending/demoted/unloaded symmetry, stale build generations,
|
||||
resource pin/release balance, collision footprints across landblock seams, and
|
||||
first-login bootstrap replacement. The deterministic world-lifecycle gate and
|
||||
nine-destination resource soak pass.
|
||||
|
||||
#### Slice 6 — extract update-frame orchestration — COMPLETE 2026-07-22
|
||||
|
||||
Detailed execution ledger:
|
||||
[`docs/plans/2026-07-21-gamewindow-slice-6-update-frame-orchestration.md`](../plans/2026-07-21-gamewindow-slice-6-update-frame-orchestration.md)
|
||||
(complete).
|
||||
|
||||
After the stateful bodies above have owners, make the update path a real
|
||||
orchestrator instead of delegates back into the window. It preserves the
|
||||
accepted production order—not an overclaimed retail host order—covering
|
||||
teardown/clock/streaming convergence, input, the retail-shaped object → inbound
|
||||
network → command barrier, conditional spatial reconciles, liveness, local
|
||||
teleport, player-mode entry, and camera presentation. The exact twelve-phase
|
||||
graph and registered TS-53 host-order adaptation live in the detailed ledger.
|
||||
`GameWindow.OnUpdate` becomes a short time/input handoff.
|
||||
|
||||
Frame-order tests and the existing R6 gate must produce the same lifecycle and
|
||||
movement traces before and after extraction.
|
||||
|
||||
The production cutover is complete in `e91f3102`. `GameWindow.OnUpdate` now
|
||||
contains the profiler scope and one `UpdateFrameOrchestrator.Tick` handoff;
|
||||
streaming, input, the object/network barrier, liveness, teleport, player-mode,
|
||||
and camera phases have typed owners with no substantial callback facade into
|
||||
the window. The complete Release suite passes 7,182 tests with five intentional
|
||||
skips. The 314-second lifecycle/reconnect gate and 394-second synchronized
|
||||
nine-stop resource soak both pass with graceful exits, stable live-owner
|
||||
counts, and a 0.8 ms update-frame p95 at the final Caul checkpoint.
|
||||
`GameWindow` is 7,026 raw lines / 241 fields / 108 methods: 8,697 lines (55.3%)
|
||||
smaller than the campaign baseline. TS-50, TS-51, and TS-53 remain accurately
|
||||
registered; this behavior-preserving extraction introduced no new divergence.
|
||||
|
||||
#### Slice 7 — extract `RenderFrameOrchestrator` — COMPLETE 2026-07-22
|
||||
|
||||
Detailed execution ledger:
|
||||
[`docs/plans/2026-07-22-gamewindow-slice-7-render-frame-orchestration.md`](../plans/2026-07-22-gamewindow-slice-7-render-frame-orchestration.md)
|
||||
(complete).
|
||||
|
||||
Move the complete draw graph and its reusable frame-local scratch state into a
|
||||
GPU-owning App collaborator. Preserve the exact modern pipeline order, clip
|
||||
routing, PView flood, landscape/opaque/shared-alpha flush boundaries,
|
||||
particles, debug draw, paperdoll, retained UI, and frame fences. Do not pass a
|
||||
hundred individual delegates or let the orchestrator reach back into
|
||||
`GameWindow`; inject a small immutable service set plus explicit per-frame
|
||||
input.
|
||||
|
||||
Automated acceptance uses framebuffer artifacts and render/resource
|
||||
checkpoints. Visual acceptance compares outdoor, building, dungeon, portal
|
||||
exit, translucent lifestone/particles, and UI at the same camera positions.
|
||||
|
||||
The production draw cutover is complete. `GameWindow.OnRender` now performs one
|
||||
value-only handoff to `RenderFrameOrchestrator`; focused resource, world,
|
||||
private-presentation, and diagnostic owners contain the former frame body and
|
||||
failure recovery. The Release suite passes 7,341 tests with five intentional
|
||||
skips. `GameWindow` is 4,666 raw lines / 196 fields / 70 methods: 11,057 lines
|
||||
(70.3%) smaller than the campaign baseline. The 315.6-second lifecycle and
|
||||
395.2-second synchronized nine-stop soak pass with code-zero graceful exits;
|
||||
the six stable framebuffer checkpoints preserve the Slice 6 presentation.
|
||||
Issue #232 records the coarse process-residency gate's run-to-run variance so
|
||||
future diagnostics can distinguish canonical owner growth from OS/driver/GC
|
||||
residency without weakening leak detection.
|
||||
|
||||
#### Slice 8 — composition and shutdown cleanup — AUTOMATED COMPLETE
|
||||
|
||||
Detailed execution ledger:
|
||||
[`docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md`](../plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md).
|
||||
|
||||
Keep device/window construction in `GameWindow.OnLoad`, but group creation into
|
||||
small composition functions and delete feature state left behind by prior
|
||||
slices. `OnClosing` delegates to the existing retryable shutdown transaction.
|
||||
Silk callbacks become narrow calls into the input, update, render, resize,
|
||||
focus, and shutdown owners.
|
||||
|
||||
Checkpoints E, F, G, and H are complete. `CameraPointerInputController` owns raw pointer,
|
||||
camera cursor/focus/scroll/sensitivity behavior; `FramebufferResizeController`
|
||||
alone publishes physical framebuffer changes. All Silk, dispatcher, retained
|
||||
UI, and optional devtools device edges are reversible, transactional, and
|
||||
terminal after disposal. Logical cutoff precedes live-session retirement;
|
||||
physical detach follows it, so neither copied callbacks nor a failed event
|
||||
remove can interfere with graceful transport teardown.
|
||||
|
||||
`GameplayInputActionRouter` is now the sole `InputDispatcher.Fired`
|
||||
subscriber and preserves the accepted input-priority graph. Focused combat,
|
||||
diagnostic, and general command owners replace the remaining window command
|
||||
bodies. The retained-root item-drop edge has one reversible owner, while its
|
||||
deferred toolbar targets become inert before session retirement. `GameWindow`
|
||||
now has no settings-store construction, duplicate settings loads, persisted
|
||||
settings mirrors, or display/quality feature bodies. `RuntimeSettingsController`
|
||||
is constructed before `Window.Create`, owns the one immutable startup snapshot
|
||||
and current/toon settings state, applies startup display/pacing/FOV/audio once,
|
||||
then borrows late-bound runtime targets without replay. Saved FOV now applies at
|
||||
startup even when devtools are disabled; this is a deliberate correction of the
|
||||
old accidental SettingsVM gate. The controller also preserves unrelated unsaved
|
||||
Gameplay draft fields when combat preferences change. `GameWindow` is now 3,663
|
||||
raw lines / 162 fields / 37 methods at G. H adds sole lifetime roots for the
|
||||
terrain atlas and dedicated sky shader, one retained Host/runtime lease, an
|
||||
atomic update/render frame-root slot, and a prepare-aware portal fallback and
|
||||
transfer slot. GPU construction and state mutations now use checked commit
|
||||
boundaries with exact retry ownership for failed names, bindless residency,
|
||||
and texture-binding restoration. `GameWindow` is 3,689 raw lines / 162 fields /
|
||||
37 methods at H. Checkpoint I.1–I.5 now provide the executable nine-phase
|
||||
oracle, platform/host/content/settings phases, and the production world/render
|
||||
phase. `WorldRenderCompositionPhase` owns Region/environment, the mandatory
|
||||
modern-renderer foundation, immutable terrain-worker inputs, and WB/texture/
|
||||
sampler construction; every Phase-4 GPU constructor has retryable prefix
|
||||
ownership. `GameWindow` is 3,522 raw lines after I.5. I.6 moves interaction,
|
||||
retained UI, live presentation, and landblock publication into ordered
|
||||
composition phases. Typed exact-owner sources bridge later session,
|
||||
selection, radar, view-plane, diagnostics, inventory, hydration, and automation
|
||||
owners without callbacks to the window. The old item-use and selection wrapper
|
||||
methods are deleted. I.7 now owns the complete streaming/session/hydration/
|
||||
local-player/teleport construction phase and every late edge through named
|
||||
exact-owner tokens; the former 432-line inline body and spawn-claim memo are
|
||||
gone. I.8a moves the exact live-session reset/router graph, combat and
|
||||
diagnostic command targets, and sole gameplay input subscriber into Phase 7
|
||||
before frame publication. I.8b moves the complete update/render construction
|
||||
body into `FrameRootCompositionPhase`, publishes the pair through an exact
|
||||
lease, and gives lifecycle resource sampling a focused source. `GameWindow` is
|
||||
1,945 raw lines after I.9 corrects production to invoke the same executable
|
||||
pipeline as the failure oracle, carries exact platform/settings results through
|
||||
the typed contract, and keeps live-session start terminal. Checkpoint J then
|
||||
moves the complete shutdown manifest into `GameWindowLifetime`: typed root
|
||||
groups, retry/no-replay progress, structured reportable physical-detach
|
||||
failures, hard session/GPU barriers, and native-window-last release now live
|
||||
outside the host. The shell captures roots once and makes one lifetime call
|
||||
from Closing/Dispose; no shutdown stage remains in it. `GameWindow` is 1,625
|
||||
raw lines after J. Checkpoint K adds acknowledged same-frame canonical resource
|
||||
checkpoints and leaves the shell at 1,622 lines. The App gate passes 3,451 tests
|
||||
/ 3 intentional skips and the complete Release suite passes 7,823 / 5. Two
|
||||
fresh-process nine-stop soaks and the connected lifecycle/reconnect route pass
|
||||
with graceful exits; deterministic framebuffer captures preserve accepted
|
||||
Slice 7 geometry, UI/paperdoll layering, private viewports, depth/alpha, and
|
||||
reveal. The clean solution build retains only the 17 test-project warnings
|
||||
tracked by #228, and all three final corrected-diff reviews are clean. The
|
||||
user's connected visual matrix passed 2026-07-23, including first-login radar,
|
||||
movement/combat, shared retained panels, recall/portal presentation, and
|
||||
graceful reconnect.
|
||||
|
||||
### 4.4 Exit criteria
|
||||
|
||||
The campaign is complete when:
|
||||
|
||||
- `GameWindow` contains no AC gameplay algorithm, entity scan, packet builder,
|
||||
DAT landblock builder, animation-part composer, or draw-graph body;
|
||||
- `OnUpdate`, `OnRender`, `OnInputAction`, and live-session callbacks are short
|
||||
delegation methods;
|
||||
- every extracted owner has App-layer tests and symmetric teardown;
|
||||
- the Release suite and connected lifecycle/resource gates stay green after
|
||||
every slice;
|
||||
- the final local visual matrix passes unchanged.
|
||||
|
||||
Line count is a progress signal, not the acceptance test. The expected result
|
||||
is below roughly 5,000 lines, but ownership and dependency direction decide
|
||||
completion. Full `GameEntity` type aggregation is evaluated only afterwards as
|
||||
a separate migration; it is not folded into this campaign.
|
||||
|
||||
---
|
||||
|
||||
## 5. Rules of the road during the extraction
|
||||
|
||||
1. **One slice at a time.** Each commit ships one ownership boundary or
|
||||
mechanical call-site cutover. Bundling slices makes failures hard to
|
||||
isolate.
|
||||
2. **Behavior preservation is the acceptance criterion.** Every slice
|
||||
must build clean, all tests pass, and visual verification at the
|
||||
appropriate accepted milestone scenarios must succeed. We're moving code, not
|
||||
changing it.
|
||||
3. **No new features during an extraction step.** If you spot a real
|
||||
bug while extracting, file it in `docs/ISSUES.md` and address it in
|
||||
a separate commit (before or after the extraction, not folded into
|
||||
it).
|
||||
4. **Diagnostic toggle migrations are opportunistic.** When a method
|
||||
moves to a new owner, the diagnostic flag inside it can move to a
|
||||
diagnostic class as part of the same commit. We do not do a bulk
|
||||
diagnostic-cleanup pass.
|
||||
5. **Update this document when the plan changes.** If a slice turns out
|
||||
to need a different ownership shape than described above, update §4 in the
|
||||
same session you discover the divergence.
|
||||
6. **No façade-only completion claims.** A delegate from a new class back to a
|
||||
substantial `GameWindow` method is a useful intermediate seam, not a
|
||||
completed extraction.
|
||||
7. **No duplicate ownership.** New collaborators query `LiveEntityRuntime`,
|
||||
`GpuWorldState`, `SelectionState`, or the relevant existing owner. They do
|
||||
not create replacement GUID, visibility, session, or resource maps.
|
||||
|
||||
---
|
||||
|
||||
## 6. What this document is **not**
|
||||
|
||||
- **Not a full rewrite plan.** The point is the *opposite* — small
|
||||
steps, verified at each boundary.
|
||||
- **Not a feature phase.** Following the 2026-07-21 user decision, this
|
||||
behavior-preserving campaign is the structural prerequisite before new M4
|
||||
subsystem bodies are added. Severe regressions remain fixable in separate
|
||||
commits; ordinary feature work waits.
|
||||
- **Not a substitute for the milestones / roadmap.** Those drive the
|
||||
feature work. This drives the structural work that runs underneath.
|
||||
|
|
@ -1,616 +0,0 @@
|
|||
# WorldBuilder Inventory — what we extracted, adapted, or left behind
|
||||
|
||||
> **Phase O shipped 2026-05-21.** The ~33 WB files we actually use have
|
||||
> been extracted into our tree. `references/WorldBuilder/` stays as a
|
||||
> **read-reference only** — nothing in `src/AcDream.*` references it as a
|
||||
> project dependency. `DatCollection` is now the only dat reader in process.
|
||||
>
|
||||
> Use this document to:
|
||||
> 1. Know **where our extracted code lives** (look for the "Extracted to"
|
||||
> column / notes in each section below).
|
||||
> 2. Know **what WB still has** that we haven't needed yet — grep
|
||||
> `references/WorldBuilder/` if you ever need to add something.
|
||||
> 3. Know **what WB never had** (the 🔴 list) — those are always ours.
|
||||
|
||||
**Pre-O status (archived for context):** As of Phase N.4 (2026-05-08)
|
||||
acdream relied heavily on WorldBuilder as a project reference for rendering
|
||||
and dat-handling. WorldBuilder is MIT-licensed, verified by visual inspection
|
||||
to render the AC world correctly (terrain, scenery, slabs, dungeons, slopes,
|
||||
particles), and uses the same Silk.NET + .NET stack we target.
|
||||
|
||||
**Post-O integration model:** Extracted WB code lives in two locations in
|
||||
our tree (see CLAUDE.md for the full breakdown):
|
||||
- `src/AcDream.Core/Rendering/Wb/` — pure helpers (no GL): `TerrainUtils`,
|
||||
`TerrainEntry`, `RegionInfo`, `SceneryHelpers`, `TextureHelpers`.
|
||||
- `src/AcDream.App/Rendering/Wb/` — GL infrastructure + mesh pipeline:
|
||||
`ObjectMeshManager`, `WbMeshAdapter`, `WbDrawDispatcher`, texture cache,
|
||||
shader infra, EnvCell/portal/scenery/terrain-blending pipeline classes.
|
||||
|
||||
**EnvCell streaming seam:** `EnvCellLandblockBuildBuilder` is an acdream-owned
|
||||
adapter around the extracted WB rendering path. One streaming job privately builds
|
||||
the complete portal-cell + shell-placement payload, and `EnvCellRenderer.CommitLandblock`
|
||||
publishes that completed snapshot on the render thread. WB's geometry-id arithmetic
|
||||
and mesh preparation remain unchanged; the transaction wrapper exists because
|
||||
acdream streams asynchronously while the WB editor's manager owned its own loading
|
||||
loop. Do not reintroduce worker-side `RegisterCell` calls or shared pending cell
|
||||
collections.
|
||||
|
||||
`DatCollectionAdapter` bridges the sole `DatCollection` to Content's
|
||||
`IDatReaderWriter`. Since MP1c, production `ObjectMeshManager` no longer reads
|
||||
DAT; the adapter remains the bounded typed-object access seam for runtime
|
||||
non-render content plus explicit bake/equivalence tooling.
|
||||
|
||||
**MP1a (2026-07-05): CPU mesh-extraction half moved to `AcDream.Content`.**
|
||||
The GL-free portion of the former `ObjectMeshManager` — dat read → polygon
|
||||
walk → vertex/index build → inline BCn/palette texture decode →
|
||||
`ObjectMeshData` — is now `MeshExtractor` in a new `src/AcDream.Content/`
|
||||
assembly (no Silk.NET dependency), so the MP1b bake tool can run the exact
|
||||
same extraction code offline without an OpenGL context. This was a
|
||||
mechanical, verbatim move (namespace + visibility only) per
|
||||
`docs/superpowers/plans/2026-07-05-mp1a-content-extraction.md` — no
|
||||
behavior change, no divergence-register row.
|
||||
|
||||
- `src/AcDream.Content/MeshExtractor.cs` — the `Prepare*` family
|
||||
(`PrepareMeshData`, `PrepareSetupMeshData`, `PrepareGfxObjMeshData`,
|
||||
`PrepareEnvCellMeshData`, `PrepareCellStructMeshData`,
|
||||
`PrepareCellStructEdgeLineData`) + private helpers (`CollectParts`,
|
||||
`CollectEmittersFromScript`, `ComputeBounds`, `BuildPolygonIndices`,
|
||||
`BuildCellStructPolygonIndices`) and the decoded-texture cache /
|
||||
`ThreadLocal<BcDecoder>` that back them.
|
||||
- `src/AcDream.Content/ObjectMeshData.cs` — the CPU-side boundary records:
|
||||
`VertexPositionNormalTexture`, `StagedEmitter`, `ObjectMeshData`,
|
||||
`MeshBatchData`, `TextureBatchData`.
|
||||
- `src/AcDream.Content/TextureKey.cs` — the atlas dedup key, lifted out of
|
||||
the GL-owning `TextureAtlasManager` (which stays in App and now
|
||||
references the lifted struct).
|
||||
- `src/AcDream.Content/UploadFormats.cs` — Content-owned
|
||||
`UploadPixelFormat`/`UploadPixelType` enums carried by
|
||||
`MeshBatchData`/`TextureBatchData` instead of
|
||||
`Silk.NET.OpenGL.PixelFormat`/`PixelType` (Content must stay
|
||||
Silk.NET-free — the bake tool must not ship GL binaries). Underlying
|
||||
values are the GL ABI constants, numerically identical to the Silk.NET
|
||||
members; App casts at its single upload boundary (the `AddTexture` call
|
||||
in `UploadGfxObjMeshData`) via a lifted nullable enum conversion —
|
||||
value- and null-preserving.
|
||||
- `src/AcDream.Content/IDatReaderWriter.cs`, `EdgeLineBuilder.cs` — GL-free
|
||||
dependencies of the extractor, moved (namespace-only) alongside it.
|
||||
- **Side-stage sink seam:** `CollectEmittersFromScript` pre-loads particle
|
||||
GfxObj meshes mid-extraction and, pre-MP1a, enqueued them directly onto
|
||||
`ObjectMeshManager._stagedMeshData`. The extractor now takes an
|
||||
`Action<ObjectMeshData>? sideStagedSink` constructor parameter; App wires
|
||||
it to `_stagedMeshData.Enqueue`, preserving the original
|
||||
immediate-enqueue semantics exactly — including on a mid-`Prepare*`
|
||||
throw (preloads staged before a malformed-dat texture-decode exception
|
||||
survive, as they always did). The MP1b bake tool passes its own
|
||||
collector.
|
||||
- **Stays in `src/AcDream.App/Rendering/Wb/`:** `ObjectMeshManager` (the
|
||||
staged-queue/worker-pool/Dispose-quiesce lifecycle and all GL upload;
|
||||
production workers now consume `IPreparedAssetSource`),
|
||||
`ObjectRenderData`/`ObjectRenderBatch`
|
||||
(hold a GL `TextureAtlasManager` field), `TextureAtlasManager`,
|
||||
`GeometryUtils` (used only by App-side raycasting, not by extraction),
|
||||
`AcSurfaceMetadata`/
|
||||
`AcSurfaceMetadataTable` (not on the extraction path), `Building.cs`
|
||||
(explicitly out of scope).
|
||||
- `AcDream.Core` is untouched; `AcDream.Content` references `AcDream.Core`
|
||||
(for `TextureHelpers`, `Sphere`/`BoundingBox` via `Chorizite.Core.Lib`);
|
||||
`AcDream.App` references `AcDream.Content`. `AcDream.Core` does NOT
|
||||
reference `AcDream.Content` (one-way dependency, per Code Structure Rule 2).
|
||||
- Reason: MP1 (`docs/superpowers/specs/2026-07-05-modern-pipeline-design.md`
|
||||
§6.1) — the bake tool needs the identical mesh/texture extraction code
|
||||
running with no GL context, so baked pak output and live-client output stay
|
||||
byte-identical.
|
||||
|
||||
**MP1b EnvCell content identity correction (2026-07-24).** The extracted
|
||||
WorldBuilder `EnvCellRenderManager.GetEnvCellGeomId` 31× polynomial is not a
|
||||
safe unique resource key. A guarded full retail-DAT catalog found the concrete
|
||||
collision `0x00030175` versus `0x01BC0105`: different environment/surface
|
||||
tuples both map to `0x00000002020E8C13` and contain different polygons.
|
||||
`AcDream.Core.Rendering.Wb.EnvCellGeometryIdentity` now owns one namespaced
|
||||
FNV-1a identity shared by the App streaming build and `acdream-bake`; the
|
||||
legacy calculation remains executable only for the conformance test that proves
|
||||
the collision. The bake additionally compares the complete source tuple before
|
||||
aliasing and fails on any collision. This is an acdream resource-ownership seam,
|
||||
not a second DAT interpreter; `DatCollection` and `MeshExtractor` remain the
|
||||
only reader/extractor path.
|
||||
|
||||
**MP1c production prepared-asset cutover (2026-07-24).** Production
|
||||
world-mesh workers no longer invoke `MeshExtractor` or rebuild Setup, GfxObj,
|
||||
EnvCell, Surface, palette, and texture graphs during portals. The validated
|
||||
machine-local `acdream.pak` is opened through Content's
|
||||
`IPreparedAssetSource`; typed GfxObj and EnvCell requests deserialize immutable
|
||||
`ObjectMeshData` while retaining the existing App worker, staging, render-thread
|
||||
upload, cache, ownership, and shutdown contracts. The original
|
||||
format-1/bake-tool-3 render payload persists exact batch translucency so App
|
||||
does not reconstruct a
|
||||
`GfxObjMesh` for metadata. Setup activation uses the package TOC as an explicit
|
||||
type-presence index before reading valid Setup records through the bounded DAT
|
||||
cache. `DatPreparedAssetSource` and `MeshExtractor` remain explicit
|
||||
bake/equivalence/UI-Studio tools, not a production fallback. Portal → HighRes
|
||||
→ Language → Cell lookup precedence is encoded directly in
|
||||
`DatCollectionAdapter.TryResolvePreferred`. The connected physical and
|
||||
installed-DAT gates are recorded in
|
||||
`docs/research/2026-07-24-slice-c-prepared-asset-cutover-report.md`.
|
||||
|
||||
**Launcher cumulative-overlay extension (2026-08-25).** Production still has
|
||||
no live-DAT fallback and consumes the same prepared-payload contracts. For a
|
||||
bounded recipe migration, App and Headless may receive one complete base pak
|
||||
plus one cumulative filtered pak through `LayeredPreparedAssetSource`. The
|
||||
overlay is probed first: Missing falls through to the base, while a present but
|
||||
corrupt render or collision payload remains authoritative corruption. Both
|
||||
mapped owners share one composite lifetime and there is never an overlay
|
||||
chain. The launcher binds the overlay to the base digest in the optional
|
||||
`pak/content.current.json` sidecar; format/global extraction migrations retain
|
||||
the explicit full-rebuild path. A tiny `pak/content.client-pending` marker
|
||||
keeps either result non-launchable until the matching client is confirmed,
|
||||
including across a crash/restart. Design and gates:
|
||||
`docs/plans/2026-08-25-launcher-content-stabilization.md`.
|
||||
|
||||
**Slice I3 prepared collision extension (2026-07-25).** The package remains
|
||||
format 1 and retains mesh type values 1–3; bake-tool 4 appends typed GfxObj,
|
||||
Setup, CellStruct, and EnvCell-topology collision payloads. Core owns the
|
||||
immutable flat records and deterministic raw-DAT flattener. Content owns the
|
||||
strict little-endian codec and `IPreparedCollisionSource`.
|
||||
`PakPreparedAssetSource` implements the render and collision interfaces over
|
||||
one mmap; it does not create a second DAT reader or mapping. CellStruct
|
||||
payloads alias only after exact serialized-byte comparison, while each
|
||||
EnvCell topology remains independently keyed. Production traversal remained
|
||||
on the parsed graph oracle until the later Slice-I cutover. Full-catalog evidence:
|
||||
`docs/research/2026-07-25-slice-i3-prepared-collision-package.md`.
|
||||
|
||||
**Slice I5 dual-publication seam (2026-07-25).** Near-tier
|
||||
`LandblockBuild` payloads now carry one immutable prepared-collision closure
|
||||
outside the DAT lock. `LandblockPhysicsPublisher` installs the parsed oracle
|
||||
and flat view under the same retained receipt; live objects use the strict
|
||||
`LiveCollisionAssetPublisher`. Cell/topology ownership is landblock-scoped and
|
||||
withdrawn on replacement, demotion, removal, and reset. The connected
|
||||
graph-authoritative referee completed 14,064 exact samples with no mismatch or
|
||||
fault. This is still one `DatCollection` and one prepared-package mmap, not a
|
||||
second reader or a WorldBuilder runtime dependency. Evidence:
|
||||
`docs/research/2026-07-25-slice-i5-dual-collision-shadow.md`.
|
||||
|
||||
**Slice I6/I7 flat-authoritative closeout (2026-07-25).** Production
|
||||
`PhysicsDataCache` now publishes only immutable flat GfxObj, Setup,
|
||||
CellStruct, and EnvCell-topology records from the validated package. Stable
|
||||
world state strips temporary `PhysicsDatBundle` source material, and near
|
||||
landblock builds discard raw Environment/GfxObj collision graphs once the
|
||||
flat closure exists. Parsed graph constructors remain explicit
|
||||
test/bake/equivalence oracles only; gameplay has no graph fallback or referee.
|
||||
Both exact-binary connected routes report `0/0/0` retained parsed collision
|
||||
graphs at every stable checkpoint while flat residency remains populated.
|
||||
This remains one `DatCollection`, one preparation algorithm, and one package
|
||||
mmap. Evidence:
|
||||
`docs/research/2026-07-25-slice-i7-closeout.md`.
|
||||
|
||||
**Prepared indoor-transit consumer correction (2026-07-26).** The package
|
||||
already retained exact portal planes through
|
||||
`FlatEnvCellTopology.PolygonIndex` plus the aliased CellStruct portal-polygon
|
||||
table. The production `CellTransit` consumer now reads that prepared
|
||||
relationship directly; it no longer treats the intentionally absent parsed
|
||||
`CellPhysics.PortalPolygons` dictionary as “this cell has no portals.” No
|
||||
package schema, bake, DAT reader, collision formula, or render portal graph
|
||||
changed. Evidence:
|
||||
`docs/research/2026-07-26-prepared-indoor-transit-regression.md`.
|
||||
|
||||
**Cell availability semantics (2026-07-31, corrected after full-catalog
|
||||
audit).** Raw and prepared CellStruct publication retains a `CellPhysics`
|
||||
record when the physics root is empty but requires a valid containment root.
|
||||
The installed 729,888-record raw and prepared catalogs contain zero rootless
|
||||
containment payloads. A malformed null/-1 root is quarantined atomically; the
|
||||
recursive inside base case applies only to a missing positive child below a
|
||||
valid root. Registration-side outdoor floods still add outside cells but skip
|
||||
transit when the active CLandCell is unavailable, and every later outdoor
|
||||
candidate independently requires its own visible landcell before building
|
||||
transit. The existing reflood retries after terrain/cell hydration. Both raw
|
||||
and prepared point-in-cell paths preserve retail's zero-portals guard. No
|
||||
package schema or DAT reader changed.
|
||||
Evidence: `docs/research/2026-07-31-cell-availability-semantics.md`.
|
||||
|
||||
**Retail VFX hook compatibility seam (2026-07-14).** Chorizite.DatReaderWriter
|
||||
2.1.7 models `CreateBlockingParticleHook` as the common hook header only, while
|
||||
retail inherits the complete `CreateParticleHook` payload. The narrow readers in
|
||||
`src/AcDream.Content/Vfx/` read raw bytes through the existing `DatCollection`
|
||||
database, delegate every ordinary hook to the package, and substitute only the
|
||||
retail blocking-particle shape. Both live animation playback and PhysicsScript
|
||||
loading use those cached readers; `MeshExtractor` uses the same PhysicsScript
|
||||
loader when preloading emitter meshes. This is not a second DAT reader:
|
||||
`DatCollection` remains the sole database owner and access path. Retail anchors:
|
||||
`CreateBlockingParticleHook::Execute` `0x00526EF0` and
|
||||
`ParticleManager::CreateBlockingParticleEmitter` `0x0051B8A0`.
|
||||
|
||||
**Retail particle visibility/degradation seam (2026-07-17).** WorldBuilder's
|
||||
particle simulator remains a useful DAT-integrator and batching reference, but
|
||||
it does not carry the live retail client's `CObjCell::IsInView` degradation
|
||||
path. `EmitterDescRegistry` now resolves the hardware particle GfxObj and its
|
||||
ordered `GfxObjDegradeInfo` entries through the same sole `DatCollection`;
|
||||
`ParticleSystem` ports the retail finite/infinite degraded branches; App feeds
|
||||
the unified PView interior set plus the landscape renderer's independently
|
||||
computed outdoor landcell set through a focused
|
||||
`ParticleVisibilityController`. Examination and dedicated-pass emitters carry
|
||||
an explicit bypass policy; missing/portal world views are empty rather than
|
||||
fail-open.
|
||||
No WorldBuilder dependency or second DAT access layer was introduced. Retail
|
||||
anchors: `CPhysicsPart::GetMaxDegradeDistance` `0x0050D510`,
|
||||
`GfxObjDegradeInfo::get_max_degrade_distance` `0x0051E2D0`,
|
||||
`CPhysicsObj::ShouldDrawParticles` `0x0050FE60`, and
|
||||
`ParticleEmitter::UpdateParticles` `0x0051D180`.
|
||||
|
||||
Hardwareless ParticleEmitterInfo records are also retained exactly. Retail
|
||||
`ParticleEmitter::SetInfo @ 0x0051CE90` returns false when
|
||||
`hw_gfxobj_id == INVALID_DID`; it does not substitute the software GfxObj.
|
||||
`EmitterDescRegistry` negative-caches that authored outcome and the hook sink
|
||||
reports it once per DAT ID rather than once per owner. This is classification
|
||||
and bounded diagnostics around the existing `DatCollection`, not a fallback
|
||||
reader or invented VFX. Evidence:
|
||||
`docs/research/2026-07-26-retail-hardwareless-particle-emitter-diagnostics.md`.
|
||||
|
||||
**Retail shared world-alpha seam (2026-07-18).** WorldBuilder's editor
|
||||
renderers classify translucent mesh batches correctly, but they have no live
|
||||
retail `CPartCell`/`CShadowPart` list and render particles in a separate
|
||||
batcher. `src/AcDream.App/Rendering/RetailAlphaQueue.cs` is therefore an
|
||||
acdream-owned runtime seam above the extracted mesh pipeline. During the main
|
||||
world frame, `WbDrawDispatcher` and `ParticleRenderer` submit transparent
|
||||
GfxObj subsets and scene particles into one stable far-to-near stream keyed by
|
||||
the transformed DAT `SortCenter`; only adjacent compatible entries may batch.
|
||||
Billboard particle textures are resident bindless `sampler2DArray` handles in
|
||||
the per-instance vertex ABI, so different textures preserve that sorted order
|
||||
inside one instanced draw; only a DAT blend-mode boundary splits the run. This
|
||||
keeps dense particle fields from becoming one GL draw per alternating texture.
|
||||
`RetailPViewRenderer` drains the landscape scope before the optional depth
|
||||
clear, and `GameWindow` drains the final scope before private viewports/UI.
|
||||
Sky and sealed off-screen render targets remain independent. No DAT reader,
|
||||
mesh decoder, or second scene graph was introduced. Retail anchors:
|
||||
`CPhysicsPart::UpdateViewerDistance` `0x0050E030`,
|
||||
`RenderDeviceD3D::DrawObjCellForDummies` `0x005A0760`,
|
||||
`CShadowPart::insertion_sort` `0x006B5130`,
|
||||
`D3DPolyRender::AddMeshToAlphaList` `0x0059C230`, and
|
||||
`D3DPolyRender::FlushAlphaList` `0x0059D2E0`. The modern per-cell-order and
|
||||
EnvCell-shell residual is tracked explicitly as AP-34.
|
||||
|
||||
**Retail portal-space viewport adapter (2026-07-15).**
|
||||
`src/AcDream.App/Rendering/PortalTunnelPresentation.cs` uses the extracted
|
||||
Setup/GfxObj mesh pipeline and mandatory `WbDrawDispatcher` for retail's
|
||||
synthetic CreatureMode tunnel object. The adapter does not duplicate mesh or
|
||||
DAT decoding: it resolves the two client-enum assets through `DatCollection`,
|
||||
uses the shared `RetailAnimationLoader`, registers Setup part refs through
|
||||
`WbMeshAdapter`, and submits the animated `SetupMesh` through the existing
|
||||
dispatcher. It clears world clip routing and point lights for the private scene
|
||||
before installing retail's distant light. The lifecycle, camera, animation,
|
||||
and draw ordering are acdream-owned ports of `gmSmartBoxUI`; WorldBuilder never
|
||||
implemented this UI viewport.
|
||||
|
||||
**Portal destination render-readiness seam (2026-07-16).**
|
||||
`GpuWorldState.IsRenderReady` does not treat dictionary publication as a draw
|
||||
barrier. `LandblockSpawnAdapter` retains the complete required-id set for each
|
||||
landblock: atlas-tier GfxObjs plus the synthetic geometry ids prepared by the
|
||||
independent EnvCell shell pipeline. `WbMeshAdapter.IsRenderDataReady` opens the
|
||||
gate only after `ObjectMeshManager` has real GPU render data. Its bounded CPU
|
||||
cache retains texture payloads and stages a missing GPU object through a
|
||||
deduplicated upload queue on cache hit only when the exact renderer owner is
|
||||
still live, covering eviction and revisit churn without letting an unowned
|
||||
cache hit recreate stale staged work. Reacquiring an exact owner stages once.
|
||||
GPU upload is deliberately not an ownership acquire: atlas GfxObjs use their
|
||||
landblock/entity pins, while synthetic EnvCell geometry uses a no-generic-decode
|
||||
pin balanced from each landblock snapshot. A late upload after all owners have
|
||||
released enters the evictable LRU instead of resurrecting a reference. After
|
||||
publication pins the synthetic ids, the controller replays their immutable
|
||||
environment/cell-structure/surface preparation descriptors; if a formerly
|
||||
unowned mesh was evicted between worker scheduling and publication, this
|
||||
schema-aware replay re-stages it without a generic GfxObj lookup.
|
||||
Near-to-Far demotion is a separate App transaction: it releases EnvCell
|
||||
rendering and landblock mesh pins while retaining the terrain slot. Core's
|
||||
matching physics demotion preserves the terrain surface but removes indoor
|
||||
cells, portals, buildings, and static shadow registrations.
|
||||
|
||||
**Cost-budgeted publication seam (2026-07-24).** WorldBuilder's editor path
|
||||
publishes a complete manager-owned scene; acdream's live streamer instead
|
||||
advances prepared render, physics, static, building, EnvCell, and spatial
|
||||
receipts under one typed frame meter. Stable cursor work may span frames, while
|
||||
building/EnvCell replacement and the final `GpuWorldState` spatial identity
|
||||
swap remain observer-atomic. Destination live-object render ownership prepares
|
||||
while the world is quiesced. `GpuWorldState.IsLiveEntityProjectionResident`
|
||||
answers that spatial ownership question; availability-gated drawing,
|
||||
collision, picking, radar/status targeting, effects, and audio continue to use
|
||||
`IsLiveEntityVisible`. This is an acdream async-integration seam around the
|
||||
extracted WB pipeline, not a second mesh or DAT implementation. Connected
|
||||
evidence:
|
||||
`docs/research/2026-07-24-slice-e-cost-budgeted-streaming-report.md`.
|
||||
|
||||
**Bounded residency and GPU retirement seam (2026-07-18).** Runtime DAT access
|
||||
keeps raw file payload caching disabled and layers bounded typed-object and
|
||||
decoded-pixel LRUs above the single `DatCollection`. `ObjectMeshManager`, the
|
||||
standalone bindless texture cache, and the owner-scoped composite texture-array
|
||||
cache all distinguish an active owner from an evictable unowned entry. Appearance
|
||||
changes and landblock demotion acquire-before-publish and withdraw-before-release;
|
||||
rebucketing never creates a second owner. `GlobalMeshBuffer` uses reclaimable,
|
||||
coalescing vertex/index ranges and migrates incrementally within explicit physical
|
||||
ceilings. Texture layers, terrain slots, mesh ranges, and old backing stores are
|
||||
returned only after `GpuFrameFlightController` observes the frame fence that can
|
||||
no longer reference them. This lifetime machinery is acdream-owned integration
|
||||
around the extracted WB mesh pipeline; it does not add a second DAT decoder or a
|
||||
reduced-distance rendering path.
|
||||
|
||||
**Unified residency policy seam (2026-07-24).** The extracted WB caches remain
|
||||
the physical owners of mesh, arena, atlas, and texture resources.
|
||||
`AcDream.App.Rendering.Residency.ResidencyManager` adds an acdream-owned typed
|
||||
policy/diagnostic layer over them: generation-safe asset handles, independent
|
||||
owner tokens and leases, immutable startup budgets, aggregate accounting, and
|
||||
bounded trim requests. It never stores or deletes a GL name. Existing owners
|
||||
perform logical eviction and fence-delayed physical release on the render
|
||||
thread. The same ledger observes the prepared-package mapping, prepared/staged
|
||||
CPU mesh data, decoded animation/audio data, and retained shared-alpha scratch
|
||||
without double-counting the package's clean memory-mapped pages as committed
|
||||
heap. Deterministic pressure tests exceed every configured ceiling and prove
|
||||
zero-charge teardown. Connected evidence:
|
||||
`docs/research/2026-07-24-slice-d-unified-residency-report.md`.
|
||||
|
||||
**Workflow:** Before re-implementing any AC-specific rendering or dat-handling
|
||||
algorithm, **check this inventory first**. If we already extracted it (🟢
|
||||
sections), it's in `src/AcDream.App/Rendering/Wb/` — use our copy. If WB has
|
||||
it but we haven't extracted it yet, grep `references/WorldBuilder/` and extract
|
||||
as needed. Retail decomp remains the oracle for things WB never had (🔴 list).
|
||||
|
||||
Attribution: WorldBuilder is MIT-licensed. `NOTICE.md` includes WB attribution.
|
||||
|
||||
---
|
||||
|
||||
## Read-reference layout (under `references/WorldBuilder/`, not project-referenced)
|
||||
|
||||
- **`Chorizite.OpenGLSDLBackend/`** — full OpenGL renderer (Silk.NET). The
|
||||
components we use are extracted into `src/AcDream.App/Rendering/Wb/`.
|
||||
- **`WorldBuilder.Shared/`** — data models, dat parsers, landscape module.
|
||||
The helpers we use are extracted into `src/AcDream.Core/Rendering/Wb/`.
|
||||
- **`WorldBuilder/`** — Avalonia desktop app shell (not taken).
|
||||
- **`WorldBuilder.{Windows,Linux,Mac}/`** — platform entry points (not taken).
|
||||
- **`WorldBuilder.Server/`** — collab editing backend (not taken).
|
||||
- **`Tests/` + `WorldBuilder.Shared.Benchmarks/`** — test harness (study only).
|
||||
|
||||
**Upstream NuGet dependencies** (these stay as NuGet packages, we don't
|
||||
vendor them):
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---|---|---|
|
||||
| `Chorizite.Core` | 0.0.18 | Plugin framework — contains `Chorizite.Core.Lib.BoundingBox`, `Chorizite.Core.Render.*` interfaces used by every render manager |
|
||||
| `Chorizite.DatReaderWriter` | 2.1.x | dat parsing (we already use 2.1.7) |
|
||||
| `Chorizite.DatReaderWriter.Extensions` | 1.1.x | extra dat helpers |
|
||||
| `BCnEncoder.Net` | 2.2.x | DXT decode (we already use) |
|
||||
| `SixLabors.ImageSharp` | 3.1.x | image loading |
|
||||
| `Silk.NET.OpenGL` + `Silk.NET.SDL` | 2.23.x | GL + windowing (we use Silk's own windowing, they use SDL) |
|
||||
| `MP3Sharp` | 1.0.5 | MP3 decode |
|
||||
|
||||
---
|
||||
|
||||
## 🟢 RENDERING — take wholesale or adapt
|
||||
|
||||
These are what makes WB "perfect". Anything in this section, we should
|
||||
use from WB rather than re-implement.
|
||||
|
||||
### Terrain
|
||||
|
||||
| Component | What it does |
|
||||
|---|---|
|
||||
| `TerrainRenderManager` | Full pipeline (per-chunk GPU buffers, draw orchestration) |
|
||||
| `LandSurfaceManager` | Texture blending atlas (palCode, alpha masks, road overlays) |
|
||||
| `TerrainGeometryGenerator` | Heightmap → mesh, normals, OnRoad, GetHeight, GetNormal |
|
||||
| `TerrainChunk` | 16×16 landblock chunk geometry |
|
||||
| `TextureAtlasManager` | Texture atlas builder |
|
||||
| `VertexLandscape` | Terrain vertex format |
|
||||
|
||||
**Modern terrain adapter:** acdream's bindless path uses `TerrainAtlas` plus
|
||||
`TerrainModernRenderer` rather than WB's draw manager, but retains
|
||||
`LandSurfaceManager`'s layer-indexed `TerrainTex.TexTiling` contract. The
|
||||
36-entry table is uploaded to `terrain_modern.frag`; base, overlay, and road
|
||||
layers each use their owning repeat count while alpha masks stay at cell scale.
|
||||
Retail `TexMerge::CopyAndTile` (`0x00503580`) and `TexMerge::Merge`
|
||||
(`0x005038C0`) are the behavior oracle; see
|
||||
`docs/research/2026-07-13-retail-terrain-texture-tiling-pseudocode.md`.
|
||||
|
||||
### Scenery (procedural placement: trees, bushes, rocks, fences)
|
||||
|
||||
| Component | What it does |
|
||||
|---|---|
|
||||
| `SceneryRenderManager` | Generate + render per-vertex scenery |
|
||||
| `SceneryHelpers` | Displace / RotateObj / ScaleObj / ObjAlign / CheckSlope |
|
||||
| `SceneryInstance` | Per-spawn instance data |
|
||||
|
||||
acdream's streamed projection assigns generated instances local runtime IDs
|
||||
through `AcDream.Core.World.ProceduralSceneryIdAllocator`. The namespace is
|
||||
`0x8XXYYIII` (full X/Y bytes plus a 12-bit counter); bit 31 remains the stable
|
||||
scenery classification seam. This is projection identity, not placement
|
||||
behavior. The former 8-bit counter rejected dense retail-DAT landblocks before
|
||||
their render transaction could publish (#218).
|
||||
|
||||
### Static objects (buildings, slabs, props — Setup + GfxObj + ObjDesc)
|
||||
|
||||
| Component | What it does |
|
||||
|---|---|
|
||||
| `StaticObjectRenderManager` | Master pipeline for static objects |
|
||||
| `ObjectRenderManagerBase` + `BaseObjectRenderManager` | Common render base |
|
||||
| `ObjectMeshManager` | Mesh extraction from Setup/GfxObj, ObjDesc application |
|
||||
|
||||
### Dungeons / interiors
|
||||
|
||||
| Component | What it does |
|
||||
|---|---|
|
||||
| `EnvCellRenderManager` | Dungeon interior cell geometry |
|
||||
| `PortalRenderManager` | Portal traversal / visibility |
|
||||
|
||||
### Sky + atmosphere
|
||||
|
||||
| Component | What it does |
|
||||
|---|---|
|
||||
| `SkyboxRenderManager` | Skybox rendering |
|
||||
| `ParticleEmitterRenderer` + `ParticleBatcher` + `ActiveParticleEmitter` | Particle systems (sky particles, weather, magic) |
|
||||
|
||||
### Visibility / culling
|
||||
|
||||
| Component | What it does |
|
||||
|---|---|
|
||||
| `VisibilityManager` + `VisibilitySnapshot` | Frustum + cell visibility |
|
||||
| `Frustum` | Frustum-cull math |
|
||||
|
||||
### Other rendering helpers
|
||||
|
||||
| Component | What it does |
|
||||
|---|---|
|
||||
| `MinimapRenderer` | Top-down minimap |
|
||||
| `GlobalMeshBuffer` | Shared GPU mesh buffer; one reclaimable vertex/index allocation per mesh, released by `ObjectMeshManager` eviction |
|
||||
| `GpuResourceManager` | GPU resource lifecycle |
|
||||
| `InstanceData` | Instanced draw data |
|
||||
| `TextureHelpers` | INDEX16, P8, BGRA, DXT decode + alpha (canonical port) |
|
||||
| `DebugRenderer` + `DebugRendererLineDrawer` + `EdgeLineBuilder` | Debug primitives |
|
||||
|
||||
### Shaders (22 total)
|
||||
|
||||
Located at `Chorizite.OpenGLSDLBackend/Shaders/`:
|
||||
|
||||
`Landscape.{vert,frag}` · `StaticObject.{vert,frag}` · `StaticObjectModern.{vert,frag}` · `Particle.{vert,frag}` · `PortalStencil.{vert,frag}` · `Outline.{vert,frag}` · `Simple3D.{vert,frag}` · `InstancedLine.{vert,frag}` · `Text.{vert,frag}` · `UI.{vert,frag}` · `Gizmo.{vert,frag}` (editor-only)
|
||||
|
||||
---
|
||||
|
||||
## 🟢 LOW-LEVEL GL / FRAMEWORK — take or replace with our own
|
||||
|
||||
Either take WB's wrappers wholesale, or keep our own and adapt the
|
||||
render managers to use ours. These wrappers are stateless or
|
||||
near-stateless and are the easiest to swap.
|
||||
|
||||
| Component | What it does |
|
||||
|---|---|
|
||||
| `OpenGLGraphicsDevice` | Silk.NET.OpenGL wrapper |
|
||||
| `OpenGLRenderer` | Render orchestration |
|
||||
| `GLSLShader` | Shader compile/link/uniforms |
|
||||
| `GLHelpers` + `GLStateScope` | GL state utility |
|
||||
| `ManagedGLFrameBuffer` / `ManagedGLIndexBuffer` / `ManagedGLTexture` / `ManagedGLTextureArray` / `ManagedGLUniformBuffer` / `ManagedGLVertexArray` / `ManagedGLVertexBuffer` | GL resource wrappers |
|
||||
| `TextureParameters` | Sampler config |
|
||||
| `GpuMemoryTracker` | Memory tracking |
|
||||
| `Camera2D` / `Camera3D` / `CameraBase` / `ICamera` / `CameraController` | Camera primitives |
|
||||
| `GameScene` + `SingleObjectScene` + `SceneData` + `ModernRenderData` + `RenderPass` | Scene / pass structures |
|
||||
|
||||
---
|
||||
|
||||
## 🟢 GEOMETRY / MATH UTILS — take wholesale
|
||||
|
||||
| Component | File |
|
||||
|---|---|
|
||||
| `TerrainUtils` (OnRoad, GetNormal, GetHeight, GetRoad, palCode) | `WorldBuilder.Shared/Modules/Landscape/Lib/TerrainUtils.cs` |
|
||||
| `TerrainCacheManager` | `…/Lib/TerrainCacheManager.cs` |
|
||||
| `TerrainRaycast` | `…/Lib/TerrainRaycast.cs` |
|
||||
| `GeometryUtils` | `WorldBuilder.Shared/Lib/GeometryUtils.cs` |
|
||||
| `RaycastingUtils` (ray-vs-sphere/AABB/triangle) | `WorldBuilder.Shared/Lib/RaycastingUtils.cs` |
|
||||
| `DoubleNumerics` (double-precision Vector/Matrix) | `WorldBuilder.Shared/Lib/DoubleNumerics.cs` |
|
||||
| `DatUtils` | `WorldBuilder.Shared/Lib/DatUtils.cs` |
|
||||
| `BoundingBoxExtensions` | `Chorizite.OpenGLSDLBackend/Lib/BoundingBoxExtensions.cs` |
|
||||
|
||||
---
|
||||
|
||||
## 🟢 DATA MODELS — take selectively
|
||||
|
||||
| Component | What it does |
|
||||
|---|---|
|
||||
| `RegionInfo` | Landblock metadata wrapper (LandblockSizeInUnits, CellSizeInUnits, etc.) |
|
||||
| `TerrainEntry` | Per-vertex terrain (Type/Scenery/Road/Height) |
|
||||
| `MergedLandblock` | Merged dat data |
|
||||
| `CellSplitDirection` | SW-NE vs NE-SW |
|
||||
| `Cell` | Generic cell wrapper |
|
||||
| `ObjectId` | Object identifier |
|
||||
| `Position` | World position |
|
||||
| `ACEnums` | AC-specific enums |
|
||||
| `WbBuildingPortal` / `WbCellPortal` | Portal structures |
|
||||
| `BuildingObject` | Building data |
|
||||
|
||||
---
|
||||
|
||||
## 🟡 EDITOR-ONLY — leave behind / delete in fork
|
||||
|
||||
These exist for the editor experience and have no place in a game
|
||||
client. Delete in fork.
|
||||
|
||||
- **`Modules/Landscape/Tools/*`** — `BrushTool`, `BucketFillTool`,
|
||||
`RoadLineTool`, `RoadVertexTool`, `InspectorTool`,
|
||||
`ObjectManipulationTool`, `Gizmo*` (DragHandler, HitTester, Renderer,
|
||||
State), `TexturePainting*`, `SceneRaycaster`,
|
||||
`LandscapeBrush`, `LandscapeToolBase`, `LandscapeToolContext`,
|
||||
`IToolSettingsProvider`, `ILandscapeBrush`, `ILandscapeEditorService`,
|
||||
`ILandscapeRaycastService`, `ILandscapeTool`, `ITexturePaintingTool`
|
||||
- **`Modules/Landscape/Commands/*`** — undo/redo command pattern for
|
||||
editor (Add/Delete/Move/Rename/Reorder/etc.)
|
||||
- **`LandscapeDocument` + `LandscapeLayer` + `LandscapeLayerGroup` + `LandscapeChunk` + `LandscapeLayerChunk` + `LandscapeLayerBase`** — editor document model
|
||||
- **`Modules/Landscape/Models/TerrainPatch*` + `LandblockChangedEventArgs`** — editor mutation events
|
||||
- **`Modules/Landscape/Services/ILandscapeCacheService` + `ILandscapeDataProvider` + `ILandscapeObjectService` + impls** — editor data flow
|
||||
- **All `Migrations/*`** — SQLite schema migrations (project file format)
|
||||
- **`Repositories/*`** + **`Services/*`** — project storage, dat repository, AceDb, SignalR sync, document manager, undo stack, world coordinates, keyword DB, project migration, semantic kernel AI helpers
|
||||
- **`Hubs/*`** — collaborative editing via SignalR
|
||||
- **`StaticObject` (editor model)** — replace with our own scene-state data model fed from network
|
||||
- **`BackendGizmoDrawer` + `GizmoRenderer`** — editor gizmos
|
||||
- **`ProjectStructures, IProject, Project`** — editor project files
|
||||
- **`KeyBinding`** — editor input binding
|
||||
- **`ViewportInputEvent[Extensions]`** — editor viewport input
|
||||
- **`EditorState`** — editor state container
|
||||
|
||||
---
|
||||
|
||||
## 🟡 AUDIO / FONT — we already have alternatives
|
||||
|
||||
Keep ours; don't take theirs.
|
||||
|
||||
- **`AudioPlaybackEngine`** — uses MP3Sharp. We have OpenAL.
|
||||
- **`FontRenderer`** — uses ImageSharp. We have BitmapFont/StbTrueTypeSharp + ImGui.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 NOT IN WORLDBUILDER — port from retail decomp ourselves
|
||||
|
||||
WorldBuilder is a dat editor; it does not have:
|
||||
|
||||
- **Network protocol** — UDP framing, ISAAC, packet codec, ACE message
|
||||
layer (we have this; oracle is `references/holtburger`)
|
||||
- **Physics** — collision (CPhysicsObj transitions, BSP queries, sphere
|
||||
sweeps), step-up, walkable validation (we have partial; oracle is the
|
||||
retail decomp at `docs/research/named-retail/`)
|
||||
- **Animation** — motion sequencer, cycle/non-cycle parts, animation
|
||||
frame interpolation (we have this; oracle is retail decomp)
|
||||
- **Movement** — local player WASD → MoveToState wire, remote-entity
|
||||
motion via UpdateMotion + dead-reckoning (we have this; oracle is
|
||||
`references/holtburger` + retail decomp)
|
||||
- **Game UI** — chat, vitals, inventory, spell book, allegiance, options
|
||||
(we have this; ImGui-based today, custom-toolkit later)
|
||||
- **Plugin API** — `IGameState`, `IEvents`, `IActions`, `IPacketPipeline`,
|
||||
`IOverlay` (we have this — acdream-unique)
|
||||
- **Game events** — combat, allegiance, spell casting, quest events
|
||||
(we have this; oracle is ACE for opcodes + retail for client behavior)
|
||||
- **Audio** — OpenAL pipeline, sound triggers (we have this)
|
||||
- **TurbineChat** + **slash commands** (we have this)
|
||||
- **Login + character selection flow** (we have this)
|
||||
- **World-object mouse selection** — WorldBuilder supplies mesh/DAT access but
|
||||
no retail client picker. Our narrow `RetailSelectionGeometryCache` reuses
|
||||
`DatCollection` to expose each GfxObj drawing-BSP root sphere and visual
|
||||
polygons; `WbDrawDispatcher` supplies the normal draw's current part
|
||||
transforms to the named-retail selection accumulator.
|
||||
|
||||
---
|
||||
|
||||
## What this means for the workflow (post-Phase O)
|
||||
|
||||
The CLAUDE.md "grep named → decompile → verify → port" workflow stays
|
||||
the rule for everything in the 🔴 list (network, physics, animation,
|
||||
movement, UI, plugin, audio, chat).
|
||||
|
||||
For anything in 🟢 that we've already extracted: **the code is in our
|
||||
tree at `src/AcDream.{Core,App}/Rendering/Wb/`**. Read it there — don't
|
||||
grep `references/WorldBuilder/` unless you want to compare against the
|
||||
original. Re-porting from retail decomp when we already have a tested
|
||||
port is still how we'd get the scenery edge-vertex bug back.
|
||||
|
||||
For anything in 🟢 that we have NOT yet extracted: grep
|
||||
`references/WorldBuilder/` to find the source, then extract it using the
|
||||
Phase O pattern (verbatim copy → adapt constructor to accept
|
||||
`IDatCollection` via `DatCollectionAdapter` where needed → add to
|
||||
`src/AcDream.App/Rendering/Wb/`). Do NOT add a new project reference back
|
||||
to `WorldBuilder.Shared` or `Chorizite.OpenGLSDLBackend` — Phase O
|
||||
permanently removed those.
|
||||
|
||||
When we discover a behavior mismatch with retail (rare — the extracted
|
||||
code is the same as the original), the resolution is: reconcile extracted
|
||||
code ↔ retail decomp ↔ holtburger ↔ ACE ↔ ACViewer (the existing
|
||||
reference hierarchy in CLAUDE.md). Our extracted code ranks at the top
|
||||
of that hierarchy for anything 🟢.
|
||||
17
docs/bugs.md
|
|
@ -1,18 +1,11 @@
|
|||
# acdream — historical bug snapshot (deprecated)
|
||||
# acdream — known bugs
|
||||
|
||||
> **Not an active issue tracker.** This file preserves the 2026-04-14 snapshot
|
||||
> for archaeology. Several “open” entries below were superseded, narrowed, or
|
||||
> fixed by later collision/rendering work. Do not update status here and do not
|
||||
> use these BUG numbers for new work. The canonical tactical ledger is
|
||||
> [`ISSUES.md`](ISSUES.md); current program navigation is in
|
||||
> [`README.md`](README.md).
|
||||
|
||||
The text below is intentionally retained as historical evidence of what was
|
||||
believed at the time.
|
||||
Track visual, gameplay, and protocol bugs here. Close by moving to
|
||||
the "Fixed" section with the commit hash that resolved it.
|
||||
|
||||
---
|
||||
|
||||
## Historical open snapshot (not current)
|
||||
## Open
|
||||
|
||||
### BUG-001: Wrong cloth textures on characters
|
||||
- **Observed:** 2026-04-13
|
||||
|
|
@ -60,7 +53,7 @@ believed at the time.
|
|||
|
||||
---
|
||||
|
||||
## Historical fixed snapshot
|
||||
## Fixed
|
||||
|
||||
### BUG-002: Jump not visible from retail client
|
||||
- **Fixed:** 5634e71 — Jump packet (opcode 0xF61B) now sent to server.
|
||||
|
|
|
|||
|
|
@ -1,150 +0,0 @@
|
|||
# Continuous integration and alpha releases (Gitea)
|
||||
|
||||
Single source of truth for how acdream builds, gates, and ships alpha builds.
|
||||
Landed 2026-08-19. Companion to [`release-gate.md`](release-gate.md), which
|
||||
owns the *local* bounded gate.
|
||||
|
||||
## What happens on a push to main
|
||||
|
||||
```
|
||||
git push origin main
|
||||
│
|
||||
├─ windows-gate (RARE-win) build + full lane-filtered suite
|
||||
├─ linux-portable (eriktestLinux) portable closure, Linux lanes
|
||||
│
|
||||
└─ release (needs BOTH green) publish a Gitea Release
|
||||
+ republish the `latest` pointer
|
||||
```
|
||||
|
||||
Workflow: [`.gitea/workflows/ci.yml`](../.gitea/workflows/ci.yml). Docs-only
|
||||
pushes (docs/, the memory trees, markdown) skip the pipeline entirely — no
|
||||
test can fail on them and a run costs ~7 minutes plus a 121 MB release. A red gate
|
||||
cannot publish: `release` uses `needs:`, not a `workflow_run` trigger, whose
|
||||
Forgejo support is unreliable.
|
||||
|
||||
## Why Gitea and not GitHub
|
||||
|
||||
GitHub Actions is **billing-blocked** on this account ("recent account payments
|
||||
have failed"), and the repo is private, so hosted runners consume paid minutes.
|
||||
Forgejo ships **no hosted runners at all**, so Actions there requires
|
||||
self-hosted ones — which are free on both platforms. The same two machines can
|
||||
serve GitHub later by registering a second agent; only the workflow's
|
||||
`runs-on` labels change.
|
||||
|
||||
## The runners
|
||||
|
||||
| | Windows | Linux |
|
||||
|---|---|---|
|
||||
| Host | `RARE` (10.6.0.3) | `eriktestLinux` (10.0.0.202) |
|
||||
| Agent | `act_runner` 0.2.13 | `forgejo-runner` 13.0.0 |
|
||||
| Persistence | Scheduled task `ForgejoRunner`, at logon of `acbot` | systemd `forgejo-runner`, `Restart=always` |
|
||||
| Labels | `windows`, `windows-latest`, `windows-x64` | `ubuntu-latest`, `ubuntu`, `linux`, `ubuntu-slim` |
|
||||
| Execution | host mode (`:host`) — no Docker on either box | host mode |
|
||||
|
||||
Both **poll outbound** over HTTPS. Gitea never connects to them, so no inbound
|
||||
ports, no port forwarding, and no static IP; they work behind NAT. The runner
|
||||
does not have to live next to the Gitea container (which runs on `bluesnake`,
|
||||
a host we have no shell on).
|
||||
|
||||
`forgejo-runner` publishes **no Windows binary in any release**, which is why
|
||||
Windows uses Gitea's `act_runner`. Forgejo speaks the same Actions protocol.
|
||||
|
||||
### Prerequisites on a runner
|
||||
|
||||
- **.NET SDK in the `global.json` band** — currently `10.0.3xx`. `10.0.400` is a
|
||||
different feature band and `rollForward: latestPatch` rejects it.
|
||||
- **Node.js** — `actions/checkout` and `actions/upload-artifact` are JavaScript
|
||||
actions. Docker images normally supply Node; in host mode the machine must.
|
||||
- **Git**, and outbound HTTPS to `git.snakedesert.se`.
|
||||
- **PowerShell 7** on Windows (`pwsh`); `tools/*.ps1` require it.
|
||||
|
||||
## Releases
|
||||
|
||||
Everything about distribution lives under **Releases** — nothing in git. A build
|
||||
is ~120 MB, so payloads are release attachments; and the pointer the launcher
|
||||
polls is itself a release asset, so there is no payload branch, no bot commit on
|
||||
`main`, and no push that could retrigger the pipeline.
|
||||
|
||||
```
|
||||
Release 0.1.0-build.<yyyyMMddHHmm> <- the actual build
|
||||
client-win-x64.zip AcDream.App.exe + acdream-headless.exe
|
||||
launcher-win-x64.zip acdream-launcher.exe + acdream-bake.exe
|
||||
manifest.json
|
||||
|
||||
Release latest <- pointer, replaced every publish
|
||||
manifest.json names the version above and its asset URLs
|
||||
```
|
||||
|
||||
The launcher polls the pointer at a URL that never changes
|
||||
(`ReleaseManifestClient.ProductionManifestUri`):
|
||||
|
||||
```
|
||||
https://git.snakedesert.se/erik/acdream/releases/download/latest/manifest.json
|
||||
```
|
||||
|
||||
A pointer is needed because **Forgejo has no `/releases/latest/download/`
|
||||
route** (verified: 404) — unlike GitHub, there is no built-in stable URL for
|
||||
"the newest release". Publishing it recreates the `latest` tag each time, which
|
||||
means deleting the old release *and* its tag; the tag outlives its release and
|
||||
would otherwise block recreation.
|
||||
|
||||
The newest **5** versioned releases are kept and older ones are pruned with
|
||||
their tags. Each build is ~121 MB of attachments, so retaining every one grew
|
||||
the server by that much per push — 5 builds had already reached 606 MB. Five is
|
||||
enough to grab a previous build or bisect a regression while staying bounded.
|
||||
The `latest` pointer is never pruned; it is the feed, not a build.
|
||||
|
||||
`tools/publish-bin.ps1 -BaseUrl <release asset base>` builds the payloads; CI
|
||||
passes the tag's asset base. Running it locally is for inspection only —
|
||||
publishing is CI's job.
|
||||
|
||||
### Verifying a release
|
||||
|
||||
```powershell
|
||||
dotnet test tests/AcDream.Launcher.Core.Tests --filter Lane=Live
|
||||
```
|
||||
|
||||
`LiveGiteaReleaseInstallTests` installs the advertised client from the real feed
|
||||
through the production updater — real SHA-256/size verification, extraction, and
|
||||
atomic activation — then asserts both hosts resolve out of the activated
|
||||
directory and `current.json` names the installed version.
|
||||
|
||||
## Landmines
|
||||
|
||||
Each of these cost a red pipeline; none was a config typo. Two rows record a
|
||||
fix that was tried and **disproved** — read those before repeating it.
|
||||
|
||||
| Symptom | Cause |
|
||||
|---|---|
|
||||
| `Cannot find: node in PATH` | JS actions need Node on the host in `:host` mode |
|
||||
| `actions/setup-dotnet` never resolves | `data.forgejo.org` does not mirror it (404). `checkout` and `upload-artifact` **are** mirrored. Self-hosted runners carry the SDK anyway |
|
||||
| Job "failed" while dotnet processes still run | `run-release-gate.ps1` redirects children to log files, so the step goes silent; Forgejo fails a non-reporting task as a zombie. CI runs `dotnet test` directly so output streams |
|
||||
| ~40 tests fail on formatted numbers | Runner's `HKCU` locale was `en-SE` (comma decimal): expected `"update:0.25"`, got `"update:0,25"`. `Set-Culture` does **not** reach a scheduled task without a loaded profile — set the registry directly |
|
||||
| `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1` as the locale fix | Too blunt — it breaks tests that legitimately construct a culture. Fix the machine locale instead |
|
||||
| `FileNotFoundException: client_cell_1.dat` | DAT-dependent tests missing `[Trait("Lane", "InstalledDat")]`. Build machines have no DATs |
|
||||
| Timing-sensitive test fails only under load | It belongs in `Lane=Timing` (see [`release-gate.md`](release-gate.md)). Do **not** chase these individually: four separate fixes each surfaced a different member of the same family, and serializing `Core.Net` to fix Linux regressed Windows from 1000 passed in 7 s to 999/1000 in 17 s |
|
||||
| Avalonia "calling thread cannot access this object" in cleanup | `MainWindowViewTests` needs a real desktop session and is `Lane=Manual`. Measured: PASSES on a dev desktop and on the CI Windows box over SSH; FAILS under `act_runner` and on Linux. Serializing the assembly does **not** fix it (tried via `xunit.runner.json` and a compiled-in `CollectionBehavior` attribute), and de-async-ing the test actively causes the failure. The stack shows a compositor being **constructed** during teardown — it is the headless session lifecycle, not parallelism |
|
||||
|
||||
## Do not leave load on a runner
|
||||
|
||||
A stress/diagnostic run left going on a runner competes with CI for the same
|
||||
machine and makes every job slower and more likely to trip a load-sensitive
|
||||
test — the exact failures you would then be trying to diagnose. Kill background
|
||||
work before trusting a timing result:
|
||||
|
||||
```powershell
|
||||
Get-Process dotnet -ErrorAction SilentlyContinue | Stop-Process -Force # Windows
|
||||
pkill -9 dotnet # Linux
|
||||
```
|
||||
|
||||
Leave `act_runner` / `forgejo-runner` itself alone; killing those unregisters
|
||||
nothing but stops the machine picking up jobs until it restarts.
|
||||
|
||||
## Culture note
|
||||
|
||||
The `en-SE` discovery is worth remembering beyond CI: config files, numeric
|
||||
parsing, and the wire are all culture-safe (`System.Text.Json` is invariant by
|
||||
spec, every `float/double.TryParse` passes `CultureInfo.InvariantCulture`, and
|
||||
the protocol is binary). Only **diagnostic strings** format with the current
|
||||
culture, so a European player sees `local=(8,00; 191,00)` in an F3 dump. The
|
||||
client installs and runs correctly in both the US and Europe.
|
||||
|
|
@ -1,382 +0,0 @@
|
|||
# acdream launch options — operator reference
|
||||
|
||||
Every environment variable and command-line argument the acdream client
|
||||
reads, what it does, and **what else it changes about the run**.
|
||||
|
||||
**This is an operator's reference, not user documentation.** Players never
|
||||
set these: the launcher owns installation and login, and the in-client
|
||||
Options panel (F11) owns settings. If a flag here looks like something a
|
||||
player would want, that is a signal it belongs in the Options panel, not a
|
||||
signal to document it better.
|
||||
|
||||
## How to use this document
|
||||
|
||||
- **Running the client for yourself?** Read *Production launch* and stop.
|
||||
- **Taking a measurement?** Read *Production launch*, then read the
|
||||
*Side effects* column of every flag you are about to set. A flag that
|
||||
changes what you are measuring is the normal case, not the exception.
|
||||
- **Adding a flag?** Add its row in the same commit. `LaunchOptionsDocumentationTests`
|
||||
fails the build otherwise — in both directions, so deleting a read site
|
||||
without deleting its row fails too.
|
||||
|
||||
### Why the side-effects column exists
|
||||
|
||||
Two flags in this list were believed to be inert and were not:
|
||||
|
||||
- `ACDREAM_AUTOMATION_ARTIFACT_DIR` reads like an output path. It also
|
||||
constructs a per-frame diagnostics referee that re-enabled a retired
|
||||
render pass, costing ~6 MB and ~14 ms **every frame** — three days of
|
||||
performance measurements were silently taxed before anyone noticed
|
||||
([#432](ISSUES.md)).
|
||||
- `ACDREAM_STREAM_RADIUS` reads like a radius knob. It forces the near
|
||||
radius, only ever *raises* the far radius, and is then silently
|
||||
discarded by any later quality apply — so a measurement taken with it
|
||||
set is measuring a window production never uses.
|
||||
|
||||
Assume a flag has a side effect until its row says otherwise.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Everything diagnostic is OFF by default.** Every probe, dump, capture,
|
||||
and measurement flag in this document is inert until its variable is
|
||||
explicitly set — an unset environment runs zero diagnostics. Exactly
|
||||
four flags default ON, and none is a diagnostic: `ACDREAM_RETAIL_CHASE`,
|
||||
`ACDREAM_CAMERA_COLLIDE`, `ACDREAM_CAMERA_ALIGN_SLOPE`, and
|
||||
`ACDREAM_RETAIL_CLOSE_DEGRADES` are retail *behaviors* wearing an A/B
|
||||
off-switch (`=0` disables the behavior for a comparison run). That
|
||||
four-flag set is frozen by `LaunchOptionsDocumentationTests` — a new
|
||||
default-on flag fails the build.
|
||||
- `=1` means the code tests for exactly the string `1`. Setting `true`,
|
||||
`yes`, or `0` does **not** enable such a flag (and `0` does not disable
|
||||
one whose test is "is the variable present").
|
||||
- **Default** is the behavior when the variable is unset.
|
||||
- **Kind** is one of:
|
||||
|
||||
| Kind | Meaning |
|
||||
|---|---|
|
||||
| `production` | Ordinary configuration; safe in a real run. |
|
||||
| `measurement` | Profiling/instrumentation. Read the side effects before trusting numbers taken with it on. |
|
||||
| `automation` | Drives scripted runs; usually implies extra machinery. |
|
||||
| `permanent-probe` | A diagnostic toggle owned by a subsystem's diagnostics class. Expected to persist. |
|
||||
| `temporary-probe` | Tied to an open investigation. Deleted with its issue — never build tooling on one. |
|
||||
| `deprecated` | Superseded. Do not use for new work. |
|
||||
|
||||
---
|
||||
|
||||
## Production launch
|
||||
|
||||
The canonical connected launch against a local ACE server. PowerShell,
|
||||
because the DAT path contains an apostrophe:
|
||||
|
||||
```powershell
|
||||
$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
|
||||
$env:ACDREAM_LIVE = "1"
|
||||
$env:ACDREAM_TEST_HOST = "127.0.0.1"
|
||||
$env:ACDREAM_TEST_PORT = "9000"
|
||||
$env:ACDREAM_TEST_USER = "testaccount"
|
||||
$env:ACDREAM_TEST_PASS = "testpassword"
|
||||
$env:ACDREAM_RETAIL_UI = "1"
|
||||
dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Release
|
||||
```
|
||||
|
||||
| Flag | Value | What it does | Side effects | Default | Read by |
|
||||
|---|---|---|---|---|---|
|
||||
| `ACDREAM_A2C` | `unset/""` keep preset; `"0"/"false"/"False"/"FALSE"` → off; any other non-empty → on | Overrides preset's `AlphaToCoverage` blend flag | Changes MSAA alpha-to-coverage blending mode for foliage/translucent draws — a visual-behavior change, not just perf | preset's `AlphaToCoverage` (High/Ultra=true, Low/Medium=false) | `QualitySettings.WithEnvOverrides` (`QualityPreset.cs:52`) |
|
||||
| `ACDREAM_AC_DIR` | `=<path>` | Points at a real retail AC install dir; loads `<dir>/controls/controls.ini` to source retail keybind display strings for the retained UI. | Only has any effect when `ACDREAM_RETAIL_UI=1` (retained UI composed). Unset → `ControlsIni.Parse(string.Empty)`, an empty (not error) controls table — silent, no fallback file is searched. | unset (null) → empty controls table | `RuntimeOptions.AcDir` → `InteractionRetainedUiComposition.cs:610` |
|
||||
| `ACDREAM_ANISOTROPIC` | `=<int>` (`int.TryParse`, invariant) | Overrides preset's `AnisotropicLevel` texture filtering | Changes GPU texture sampling filter level (visual sharpness), not just perf | preset's `AnisotropicLevel` (Low=4, Medium=8, High/Ultra=16) | `QualitySettings.WithEnvOverrides` (`QualityPreset.cs:49`) |
|
||||
| `ACDREAM_CACHE_DIR` | `=<path>` | Overrides the resolved cache-root directory (used for `DiagnosticsDirectory`, etc.) | none beyond redirecting cache I/O | Windows: `%LOCALAPPDATA%\acdream\cache`; Linux: `$XDG_CACHE_HOME/acdream` or `~/.cache/acdream` | `ApplicationPathSet.Resolve` (`ApplicationPathSet.cs:83`), via `IApplicationPathEnvironment` seam |
|
||||
| `ACDREAM_CAMERA_ALIGN_SLOPE` | `=0` disables (anything else/unset = on) | selects whether the chase camera basis tilts to the player's 5-frame averaged velocity vs staying flat/horizontal on slopes | alters camera orientation / rendered view every frame; startup-only in practice (its DebugPanel/DebugVM mirror is unreachable — #434)| true (on) | `AcDream.Core.Rendering.CameraDiagnostics.AlignToSlope` |
|
||||
| `ACDREAM_CAMERA_COLLIDE` | `=0` disables (anything else/unset = on) | selects whether the chase camera sweeps a 0.3 m collision sphere from head-pivot to eye and stops at the first wall (retail spring-arm) | alters camera position every frame (camera can clip into geometry when disabled); startup-only | true (on) | `CameraDiagnostics.CollideCamera` |
|
||||
| `ACDREAM_CONFIG_DIR` | `=<path>` | Overrides the resolved config-root directory (`settings.json`, `keybinds.json`) | none beyond redirecting config I/O | Windows: `%APPDATA%\acdream`; Linux: `$XDG_CONFIG_HOME/acdream` or `~/.config/acdream` | `ApplicationPathSet.Resolve` (`ApplicationPathSet.cs:79`), via `IApplicationPathEnvironment` seam |
|
||||
| `ACDREAM_DATA_DIR` | `=<path>` | Overrides the resolved data-root directory (logs, screenshots, plugins) | none beyond redirecting data I/O | Windows: `%LOCALAPPDATA%\acdream`; Linux: `$XDG_DATA_HOME/acdream` or `~/.local/share/acdream` | `ApplicationPathSet.Resolve` (`ApplicationPathSet.cs:81`), via `IApplicationPathEnvironment` seam |
|
||||
| `ACDREAM_DAT_DIR` | `=<path>` | Fallback dat-directory when no positional argument is given. App: single read at `Program.cs:58`. Cli: read independently per-subcommand (each subcommand does `args.ElementAtOrDefault(N) ?? Env.GetEnvironmentVariable("ACDREAM_DAT_DIR")`) plus once more for the default (no-subcommand) asset-inventory mode at line 152. | Two of the four `Program.cs` line numbers in the raw grep (91, 135) are **not reads** — they're the literal string `ACDREAM_DAT_DIR` inside `Log.Error` usage-text messages, not `GetEnvironmentVariable` calls. Only line 58 is a real read in `AcDream.App`. | none — hard usage error (exit 2) if unset and no positional arg | `Program.cs:58` (App); `Cli/Program.cs:24,35,47,59,71,84,113,125,137,152` (every Cli subcommand) |
|
||||
| `ACDREAM_DISPLAY_PROTOCOL` | `="auto"` / `"x11"` / `"wayland"` (case-insensitive, trimmed); any other value throws `InvalidOperationException` at startup | Linux-only: forces the GLFW 3.4 platform-init hint (X11 vs Wayland vs auto) before any window is created; ignored entirely on Windows (always `Windows` protocol) | An invalid value is fatal at startup (throws before any window exists), not a silent fallback | unset → auto-detected from `XDG_SESSION_TYPE`/`WAYLAND_DISPLAY`/`DISPLAY`, falling back to GLFW `Automatic` | `GraphicalWindowBackendSelection.Resolve` (`GraphicalWindowBackendSelection.cs:26-58`) |
|
||||
| `ACDREAM_FAR_RADIUS` | `=<int>` | Overrides preset's `FarRadius` (outer streaming/reveal window, landblocks) | Enlarging changes streaming memory budget and what's resident/rendered — CLAUDE.md: leave unset for measurement/gate runs (same family as legacy `ACDREAM_STREAM_RADIUS`) | preset's `FarRadius` (Low=5, Medium=8, High=12, Ultra=15) | `QualitySettings.WithEnvOverrides` (`QualityPreset.cs:47`) |
|
||||
| `ACDREAM_LIVE` | `=1` (exactly the literal string `"1"`) | Core switch: connect to a live ACE server instead of running offline/no-connect. | The 4 non-`RuntimeOptions.cs` line numbers in the raw grep are **all comments or log-message text**, not reads — `SessionStartComposition.cs:39` is inside the string `"live: ACDREAM_LIVE set but TEST_USER/TEST_PASS missing; skipping"`; `Program.cs:126` is inside a `--session-config` override log line; `GameWindow.cs:614,627` are doc comments. The only actual parse is `RuntimeOptions.cs:141`. Requires `ACDREAM_TEST_USER`/`ACDREAM_TEST_PASS` too (`HasLiveCredentials`) or the session silently reports `MissingCredentials` and skips. Forced to effectively-on (LiveMode=true) unconditionally by `--session-config` launches regardless of this var. | `false` | `RuntimeOptions.LiveMode` → `SessionStartComposition.cs` (log text only), `Program.cs:126` (log text only), `GameWindow.cs:614,627` (comments only), consumed for real via `RuntimeOptions.HasLiveCredentials` and `WorldSession`/`GameRuntime` session-start gating |
|
||||
| `ACDREAM_MAX_COMPLETIONS_PER_FRAME` | `=<int>` | Overrides preset's per-frame streaming-completion throughput cap | Directly changes the streaming admission budget measured by perf/completion gates — do not vary during a measurement run | preset's value (Low=2, Medium=3, High=4, Ultra=6) | `QualitySettings.WithEnvOverrides` (`QualityPreset.cs:59`) |
|
||||
| `ACDREAM_MSAA_SAMPLES` | `=<int>` (0/2/4/8) | Overrides preset's MSAA sample count | Changes GPU multisample anti-aliasing (visual + GPU-cost change) | preset's `MsaaSamples` (Low=0, Medium=2, High/Ultra=4) | `QualitySettings.WithEnvOverrides` (`QualityPreset.cs:48`) |
|
||||
| `ACDREAM_NEAR_RADIUS` | `=<int>` | Overrides preset's `NearRadius` (inner streaming ring radius) | Changes the streaming/render window size — CLAUDE.md: leave unset for measurement/gate runs | preset's `NearRadius` (Low=2, Medium=3, High=4, Ultra=5) | `QualitySettings.WithEnvOverrides` (`QualityPreset.cs:46`) |
|
||||
| `ACDREAM_NO_AUDIO` | `=1` | Suppresses OpenAL device init entirely (headless / driver-broken machines). | Fail-open design: even without this flag, a missing/broken OpenAL driver already makes `IsAvailable=false` and all `Play*` calls no-ops — this flag is the explicit, deliberate version of the same fallback. When set, `LiveSessionWorldRuntime.WorldAudio` is `null` and logout-audio reset/resume steps no-op. | `false` | `RuntimeOptions.NoAudio` → `GameWindow.cs:1430` → `ContentEffectsAudioCompositionPhase` → `OpenAlAudioEngine.cs` (fail-open doc), `LiveSessionRuntimeFactory.cs:71` (`WorldAudio` gate) |
|
||||
| `ACDREAM_PAK_PATH` | `=<path>` | Overrides the path to the prepared machine-local asset bake (`acdream.pak`) that production world-mesh streaming reads through `IPreparedAssetSource`. | none beyond the obvious | unset → `<datDir>/acdream.pak` | `RuntimeOptions.PreparedAssetPath` → `ContentEffectsAudioComposition.cs:53,372,379`, `GameWindow.cs:1420` |
|
||||
| `ACDREAM_RESIDENCY_ALPHA_SCRATCH_MIB` | `=<int MiB>` (`>0`, else default; overflow-checked) | Byte ceiling for the retail alpha (translucency) draw queue's scratch buffer | Shrinking below production working set changes translucency-queue eviction/reflow behavior — not comparable to a default-budget perf run | 16 MiB | `ResidencyBudgetOptions.Parse` (`ResidencyBudgetOptions.cs:88-89`), flows through `RuntimeOptions.ResidencyBudgets`→`AlphaScratchBudgetProfile.Create`→`RetailAlphaQueue` ctor (`GameWindow.cs:721-725`) |
|
||||
| `ACDREAM_RESIDENCY_ANIMATION_ENTRIES` | `=<int>` (`>0`, else default) | Entry-count ceiling for the retained animation-data cache | Changes cache eviction cadence for animation data — a perf/memory measurement under a non-default value is not representative | 512 | `ResidencyBudgetOptions.Parse` (`:82-84`) |
|
||||
| `ACDREAM_RESIDENCY_ANIMATION_MIB` | `=<int MiB>` (`>0`, else default) | Byte ceiling for the retained animation-data cache | Same cache-behavior-change caveat as the entries variant | 64 MiB | `ResidencyBudgetOptions.Parse` (`:79-81`) |
|
||||
| `ACDREAM_RESIDENCY_AUDIO_MIB` | `=<int MiB>` (`>0`, else default) | Byte ceiling for the retained audio-buffer cache | Shrinking can force more frequent audio buffer re-decode/eviction | 32 MiB | `ResidencyBudgetOptions.Parse` (`:85-87`), consumed by `ContentEffectsAudioComposition.cs` |
|
||||
| `ACDREAM_RESIDENCY_COMPOSITE_PHYSICAL_MIB` | `=<int MiB>` (`>0`, else default) | Byte ceiling for physically-resident composite (character palette/texture) GPU memory | Changes composite-texture eviction pressure — not representative of production if varied during a measurement run | 128 MiB | `ResidencyBudgetOptions.Parse` (`:67-69`), consumed by `TextureCache.cs` |
|
||||
| `ACDREAM_RESIDENCY_COMPOSITE_UNOWNED_MIB` | `=<int MiB>` (`>0`, else default) | Byte ceiling for unowned/retained (not currently referenced) composite textures kept for reuse | Same eviction-pressure caveat | 64 MiB | `ResidencyBudgetOptions.Parse` (`:70-72`), consumed by `TextureCache.cs` |
|
||||
| `ACDREAM_RESIDENCY_MESH_GPU_MIB` | `=<int MiB>` (`>0`, else default) | Byte ceiling for GPU-resident object mesh data | The single largest residency budget (1024 MiB default) — shrinking it directly forces more mesh re-upload/eviction; do not vary during an FPS/GPU-memory measurement run | 1024 MiB | `ResidencyBudgetOptions.Parse` (`:49-51`), consumed by `ObjectMeshManager.cs`/`WbDrawDispatcher.cs` |
|
||||
| `ACDREAM_RESIDENCY_MESH_STAGING_ENTRIES` | `=<int>` (`>0`, else default) | Entry-count ceiling for the mesh upload staging cache | Changes staging-buffer churn/eviction cadence | 256 | `ResidencyBudgetOptions.Parse` (`:64-66`) |
|
||||
| `ACDREAM_RESIDENCY_MESH_STAGING_MIB` | `=<int MiB>` (`>0`, else default) | Byte ceiling for the mesh upload staging cache | Same staging-churn caveat | 128 MiB | `ResidencyBudgetOptions.Parse` (`:61-63`) |
|
||||
| `ACDREAM_RESIDENCY_MESH_UNOWNED_ENTRIES` | `=<int>` (`>0`, else default) | Entry-count ceiling for unowned (retained-for-reuse) object mesh entries | Changes mesh-cache eviction cadence | 50 | `ResidencyBudgetOptions.Parse` (`:52-54`) |
|
||||
| `ACDREAM_RESIDENCY_PREPARED_MESH_ENTRIES` | `=<int>` (`>0`, else default) | Entry-count ceiling for the CPU-side "prepared mesh" cache (post-classification, pre-upload) | Changes eviction cadence for prepared-mesh CPU memory | 100 | `ResidencyBudgetOptions.Parse` (`:58-60`) |
|
||||
| `ACDREAM_RESIDENCY_PREPARED_MESH_MIB` | `=<int MiB>` (`>0`, else default) | Byte ceiling for the CPU-side prepared-mesh cache | Same eviction-cadence caveat | 128 MiB | `ResidencyBudgetOptions.Parse` (`:55-57`) |
|
||||
| `ACDREAM_RESIDENCY_STANDALONE_UNOWNED_ENTRIES` | `=<int>` (`>0`, else default) | Entry-count ceiling for unowned standalone (non-composite) texture entries | Changes standalone-texture eviction cadence | 256 | `ResidencyBudgetOptions.Parse` (`:76-78`), consumed by `TextureCache.cs` |
|
||||
| `ACDREAM_RESIDENCY_STANDALONE_UNOWNED_MIB` | `=<int MiB>` (`>0`, else default) | Byte ceiling for unowned standalone texture memory | Same eviction-cadence caveat | 32 MiB | `ResidencyBudgetOptions.Parse` (`:73-75`) |
|
||||
| `ACDREAM_RETAIL_CHASE` | `=0` disables (anything else/unset = on) | selects the retail-faithful `RetailChaseCamera` vs. the legacy rigid-follow `ChaseCamera` | swaps the entire active camera implementation — changes camera motion/feel; startup-only in practice (its DebugPanel/DebugVM mirror is unreachable — #434)| true (retail camera on) | `CameraDiagnostics.UseRetailChaseCamera` |
|
||||
| `ACDREAM_RETAIL_CLOSE_DEGRADES` | inverted: `="0"` disables; any other value (incl. unset) enables | Default-**on** real gameplay behavior: applies retail's close-range LOD mesh-part swap (`GfxObjDegradeResolver`) to humanoid setups (issue #47), matching retail's close-detail degrade. | Inverted default (opposite of every other boolean flag in this table — presence of the literal string `"0"` is what disables it, not presence of `"1"` enabling it). Documented explicitly as "set only for before/after diagnostic comparisons" — so although default-on production behavior, its *disable* path exists purely for A/B measurement. | `true` (enabled) unless value is exactly `"0"` | `RuntimeOptions.RetailCloseDegrades` → `DatLiveEntityProjectionMaterializer.cs:275-276,480-498` |
|
||||
| `ACDREAM_RETAIL_UI` | `=1` | Switches on the retained retail UI host tree (`UiHost`/`UiRoot`, D.2b). Without it, no retained UI is composed at all — e.g. no chargen Appearance page, no Summary page. | **Forced to `true` unconditionally** for every `--session-config` / launcher launch (`RuntimeOptions.cs:282`, "a session-config launch IS a product launch — the retail UI is the shipped UI, not a dev option"), regardless of this env var's value — the env var only matters for the bare env-var dev-flow launch path. | `false` for the env-var dev flow; `true` always for `--session-config` launches | `RuntimeOptions.RetailUi` → `LivePresentationComposition.cs:1108-1131` (gates retained-UI mount via `InteractionRetainedUiComposition`), `GameWindow.cs:455,566` (comments), `RuntimeOptions.cs:277` |
|
||||
| `ACDREAM_TEST_HOST` | `=<host>` | ACE server hostname for live-mode connect. | none | `"127.0.0.1"` | `RuntimeOptions.LiveHost` (`RuntimeOptions.cs:142`) |
|
||||
| `ACDREAM_TEST_PASS` | `=<string>` | ACE account password for live-mode connect. | Redacted in `RuntimeOptions.ToString()`/diagnostic printing by design (`PrintMembers` override, `RuntimeOptions.cs:326-342`) — defense-in-depth so it can never leak into a log/exception via the record's default printing. | `null` (empty → `HasLiveCredentials` false) | `RuntimeOptions.LivePass` (`RuntimeOptions.cs:145`) |
|
||||
| `ACDREAM_TEST_PORT` | `=<int>` | ACE server port for live-mode connect. | none | `9000` | `RuntimeOptions.LivePort` (`RuntimeOptions.cs:143`) |
|
||||
| `ACDREAM_TEST_USER` | `=<string>` | ACE account name for live-mode connect. | none | `null` (empty → `HasLiveCredentials` false) | `RuntimeOptions.LiveUser` (`RuntimeOptions.cs:144`) |
|
||||
| `ACDREAM_VULKAN_DEVICE` | `=<int>` (decimal index) or `=<substring>` (case-insensitive device-name match) | Overrides automatic Vulkan physical-device selection (normally: discrete > integrated > virtual > CPU, tie-broken by device-local heap size) — for multi-GPU machines. | A bare-digits value is matched as an index ONLY (never falls through to substring match) specifically because digits like `"7"` are substrings of real device names ("AMD Radeon RX 9070 XT") — a fallback would silently select the wrong device by coincidence. An override matching nothing falls back to the automatic choice (does not fail startup) and records why in the capability report. | `null` → automatic ranked choice | `RuntimeOptions.VulkanDeviceOverride` → `VulkanPhysicalDeviceSelection.Choose` (`VulkanPhysicalDeviceSelection.cs:54-100`), consumed at `VulkanGraphicsContext.cs:207,322` |
|
||||
|
||||
## Command-line arguments
|
||||
|
||||
### `AcDream.App`
|
||||
|
||||
| Arg | What it does | Side effects |
|
||||
|---|---|---|
|
||||
| `<dat-directory>` (positional) | Dat directory; outranks `ACDREAM_DAT_DIR`. | Not read at all once `--session-config` is present. |
|
||||
| `--session-config <path>` | The launcher's launch path: endpoint, account, credential reference, character selector, status file, plugins, login commands. | **Overrides `ACDREAM_LIVE` and every `ACDREAM_TEST_*`** (logged at startup). Diagnostic flags stay env-controlled. Missing value is a startup error. |
|
||||
|
||||
### `AcDream.Headless`
|
||||
|
||||
Its usage banner matches the parser exactly. `validate` loads and checks a
|
||||
config without connecting; `run` connects.
|
||||
|
||||
| Arg | What it does | Side effects |
|
||||
|---|---|---|
|
||||
| `validate` \| `run` (positional) | Selects the mode; must be the first argument. | Anything else is a parse error. |
|
||||
| `--config <path>` | The versioned headless session-configuration document. Required. | — |
|
||||
| `--config-dir` / `--data-dir` / `--cache-dir` `<path>` | Override each portable path root. | Merged over the config document's own `process.paths`; the command line wins. |
|
||||
| `-user` / `--user`, `-password` / `--password` | Direct single-session credentials, bypassing the config's credential source. | Plaintext in the process command line — prefer the config's credential reference. |
|
||||
| `--help` / `-h` (or no args) | Prints usage, exits 0. | — |
|
||||
|
||||
### `AcDream.Launcher`
|
||||
|
||||
| Arg | What it does | Side effects |
|
||||
|---|---|---|
|
||||
| `--verify-publish` | Packaging smoke probe: parses arguments and exits 0 without opening a display or resolving user paths. | — |
|
||||
| `--config-dir` / `--data-dir` / `--cache-dir` `<absolute path>` | Override each path root. | **All three or none** — supplying a subset is an error. Must be absolute. |
|
||||
| `--update-manifest-uri <uri>` | Points the self-updater at a different release manifest (test-feed seam). | Must be `https://` (or loopback `http://`). Changes where updates come from — do not point a real install at a test feed. |
|
||||
| `--acdream-self-update-helper-v1`, `--acdream-self-update-confirm-v1` | Internal re-exec markers for the self-update handoff. | Not user-facing; never pass these by hand. |
|
||||
|
||||
### `AcDream.Cli`
|
||||
|
||||
A dat-dump and measurement tool dispatched by a positional subcommand
|
||||
(`args[0]`); no `--flag` options. Most subcommands take a dat directory and
|
||||
fall back to `ACDREAM_DAT_DIR`.
|
||||
|
||||
- **Measurement:** `summarize-frame-history <frames.csv> <checkpoints.jsonl> <markers.log> <out.json>`,
|
||||
`compare-screenshots <expected.png> <actual.png> <out.json> [channelTolerance=2] [maxDifferentFraction=0.001] [mask.png]`,
|
||||
`probe <in.png> <x0> <y0> <x1> <y1>`.
|
||||
- **Dat inspection:** no subcommand (asset-type inventory), `dump-vitals-bars`,
|
||||
`dump-vitals-layout [0xLayoutId]`, `list-ui-layouts [0xRootType]`,
|
||||
`dump-sprite-sheet <0xId,...>`, `dump-font-atlas [0xFontId] [sample] [outBase]`,
|
||||
`dump-edges <0xId>`, `export-ui-sprite <0xId> [out.png]`.
|
||||
- **Mockup rendering:** `render-vitals-mockup [out.png]`, `mock-selbar [out.png]`,
|
||||
`crop <in.png> <x> <y> <w> <h> <zoom> <out.png>`.
|
||||
|
||||
## Measurement and profiling
|
||||
|
||||
| Flag | Value | What it does | Side effects | Default | Read by |
|
||||
|---|---|---|---|---|---|
|
||||
| `ACDREAM_CAPTURE_RESOLVE` | `=<path>` | appends one JSON-Lines record (full before/after `PhysicsBody` snapshot) per player-side `ResolveWithTransition` call, filtered to `IsPlayer` movers | real per-tick allocation (snapshot object graph + `System.Text.Json` serialize) and buffered file I/O (`AutoFlush=false`) for the local player only; will skew any perf measurement of local-player physics while active; feeds `CellarUpTrajectoryReplayTests` fixtures | unset (off) | `AcDream.Core.Physics.PhysicsResolveCapture` (`CapturePath`) |
|
||||
| `ACDREAM_COLLISION_SHADOW_DIR` | `=<dir>` | output directory for Slice I5 graph/flat collision-shadow mismatch artifacts | only takes effect when `ACDREAM_COLLISION_SHADOW_EVERY>0`; directory creation + file writes on mismatch | `<CurrentDirectory>/.test-out/collision-shadow` | `PhysicsDiagnostics.CollisionShadowArtifactDirectory` |
|
||||
| `ACDREAM_COLLISION_SHADOW_EVERY` | `=<positive int>` | when >0 and the cache is constructed with `requirePreparedCollision:false`, arms a `CollisionShadowVerifier` that re-runs the graph-vs-flat collision referee every Nth traversal entry (`PhysicsDataCache` ctor) | extra CPU on sampled ticks + mismatch-artifact file I/O; graph path stays authoritative regardless of mismatch (doc-asserted, not independently verified here) — does not change production physics results, but does add work when active | `0` (disabled) | `PhysicsDiagnostics.CollisionShadowSampleEvery` (parsed via `ParsePositiveInt`, non-positive → 0) |
|
||||
| `ACDREAM_DAY_GROUP` | `=<int>` | Forces Dereth's day-group (weather preset) selection instead of the retail hash-based pick, "useful for visually A/B-testing each weather preset against retail" (own doc comment). | **Dead second read**: the `SkyDescLoader.cs:252` raw read only feeds `SelectDayGroupIndex`, which is only called from `ActiveDayGroup(double)` and the `DefaultDayGroup` property — and grepping all of `src/` finds **zero production call sites** for either. That whole path is unreachable; only the typed `RuntimeOptions.ForcedDayGroupIndex` → Runtime path is live. Bounds differ too: the typed path only checks `>= 0` (`TryParseNonNegativeInt`) and Runtime clamps out-of-range to `null`; the dead Core-layer path checks `forced >= 0 && forced < DayGroups.Count` directly. `SkyState.cs:400,403` are doc-comment mentions only, not reads. | unset → normal server/date-driven hash selection | `RuntimeOptions.ForcedDayGroupIndex` (typed) → `GameWindow.cs:718` → `WorldEnvironmentController` → `RuntimeWorldEnvironmentState` (Runtime, live path); **also** raw `Environment.GetEnvironmentVariable` at `SkyDescLoader.cs:252` (Core layer, separate parse) |
|
||||
| `ACDREAM_DISABLE_TIER1_CACHE` | `="1"` (ordinal exact match; anything else = enabled) | A/B diagnostic that forces **every** static (non-animated) entity through the slow per-entity classification path, bypassing the Tier-1 classification cache (`#53`) | Materially changes per-frame CPU cost for entity classification — a perf/FPS measurement taken with this set is NOT representative of production and must not be compared against a normal run | unset (cache enabled) | `WbDrawDispatcher` ctor field `_tier1CacheDisabled` (`WbDrawDispatcher.cs:473-474`) |
|
||||
| `ACDREAM_FRAME_HISTORY` | `=<path>` | opts into a per-frame CSV history capture (frame idx, timestamps, per-stage CPU us, GPU us, alloc bytes) alongside the aggregated 5 s `[frame-prof]` report | allocates a `List<FrameHistoryRecord>` with ~131,072-record (~9 MiB) initial capacity, growing further for longer captures (~72 B/record, ~43 MB/hour at 165 fps) held in memory for the whole run; CSV write happens ONLY at `Dispose`/shutdown (no frame-thread I/O); only takes effect while `ACDREAM_FRAME_PROF` is ALSO on | unset (off) | `RenderingDiagnostics.FrameHistoryPath` / `AcDream.App.Diagnostics.FrameProfiler` |
|
||||
| `ACDREAM_ORBIT_DISTANCE_METERS` | `=<float>`, must be finite and `>0` | Diagnostic-only initial distance for the offline orbit camera, so deterministic renderer acceptance captures land inside a finite shadow reach. | Own doc comment: "used by deterministic renderer acceptance captures." Rejects non-finite/non-positive values silently (parses to `null`, camera default used). | `null` (unset) → normal camera default | `RuntimeOptions.InitialOrbitDistanceMeters` → `GameWindow.cs:1412` → offline orbit-camera composition |
|
||||
| `ACDREAM_ORBIT_PITCH_DEGREES` | `=<float>`, clamped `[-89, 89]` | Diagnostic-only initial orbit camera elevation. | Values outside `[-89,89]` or non-finite are silently rejected (→ `null`, default kept) rather than clamped. | `null` | `RuntimeOptions.InitialOrbitPitchDegrees` → `GameWindow.cs:1414` |
|
||||
| `ACDREAM_ORBIT_YAW_DEGREES` | `=<float>`, must be finite | Diagnostic-only initial orbit camera heading. | Non-finite values silently rejected (→ `null`). | `null` | `RuntimeOptions.InitialOrbitYawDegrees` → `GameWindow.cs:1413` |
|
||||
| `ACDREAM_PROBE_REVEAL_RADIUS` | `=<int>=1` (unparsable or `<1` → override absent; floor is 1, not 0) | #280 A/B measurement probe: forces the OUTDOOR reveal gate to use this landblock radius instead of the derived streaming window (near radius clamped to it), so a route can be measured with the pre-#280 behavior (`=1`, old `OutdoorNeighborhoodRadius`) vs. current | **Changes what gets revealed, not just measured** — genuinely resizes the reveal/visible window used by the live reveal gate. CLAUDE.md: "Leave it unset for any measurement or gate run — with it set you are measuring a different window than production." `=0` is rejected by the parser specifically because it would hang the very A/B route it exists to measure (`RequiredRenderRadius==0` fails `invalid-readiness-shape`). Not a user setting, not in Settings/RuntimeOptions, not persisted. | unset (derivation in charge, no override) | `StreamingDiagnostics.RevealRadiusOverride` (`StreamingDiagnostics.cs:25-27,76-80`), applied by `StreamingDiagnostics.ApplyRevealRadiusOverride` |
|
||||
| `ACDREAM_PROBE_WORLD_FRAME` | `=1` | gates one `[world-frame] agree` line per projected conversion in `DatLiveEntityProjectionMaterializer`, recording the world-frame center both `LiveWorldOriginState` (App) and Runtime's physics-state owner used (issue #283, "measurement only; it never gates placement") | print-only | off | `PhysicsDiagnostics.ProbeWorldFrameEnabled` |
|
||||
| `ACDREAM_SKY_PHASE_SECONDS` | `=<float>` (any finite value; negative accepted, taken mod 1 per axis) | Campaign V slice V7 instrument-determinism pin: freezes the sky's cloud-sheet UV scroll to a fixed elapsed-seconds value instead of wall-clock time, so two launches of a differential/offline gate agree about cloud position. | **Non-obvious dual effect**: this ONE var pins TWO independently-designed clocks that happen to share a name-adjacent purpose — the sky renderer's cloud scroll (`SkyRenderer.AnimationPhaseSecondsOverride`) AND, since Campaign VM slice VM6, the atmospheric post-process graph's foliage-wind clock (`_windClockSecondsOverride`). A gate that only knows about "sky clouds" and sets this to freeze them will *also* freeze foliage-wind evolution — deliberately snapped-to-target on the first advance per an A6 review fix, but still a second surface a naive reader wouldn't expect this var to touch. Distinct from `ACDREAM_DAY_GROUP`/`ACDREAM_WORLD_TIME`, which pin the OTHER sky clock (day group/sun angle) — retail's clouds drift independently of the calendar date by design. | `null` → wall-clock driven (every ordinary run) | `RuntimeOptions.SkyAnimationPhaseSeconds` → `SkyRenderer.cs:79,85` (cloud UV scroll) **and** `AtmosphericPostProcessGraph.cs:560,586,671` (foliage-wind clock) |
|
||||
| `ACDREAM_STREAM_WORK_COMPLETIONS` | `=<int>` (`>0`, else default) | Per-frame ceiling on streaming completion admissions on the update thread | Class doc comment states explicitly: this whole `ACDREAM_STREAM_WORK_*` family "exists for A/B measurement only" — not a user/production setting. Directly changes streaming throughput per frame; do not compare a measurement taken with this set against a default run. | 64 | `StreamingWorkBudgetOptions.Parse` (`StreamingWorkBudgetOptions.cs:56-58`) |
|
||||
| `ACDREAM_STREAM_WORK_CPU_MIB` | `=<int MiB>` (`>0`, else default) | Per-frame ceiling on adopted (newly resident) CPU bytes on the update thread | A/B-measurement-only family; changes per-frame CPU admission budget | 8 MiB | `StreamingWorkBudgetOptions.Parse` (`:59-61`) |
|
||||
| `ACDREAM_STREAM_WORK_DEST_RESERVE_PERCENT` | `=<float percent>`, exclusive `0 < x < 100`, else default; stored as fraction (`percent/100`) | Fraction of the per-frame work budget reserved for the active reveal destination lane vs. background streaming | A/B-measurement-only family; reallocates frame budget between destination-lane and background streaming work, changing reveal-latency characteristics | 0.75 (75%) | `StreamingWorkBudgetOptions.Parse`/`ParseReservePercent` (`:71-73,154-169`) |
|
||||
| `ACDREAM_STREAM_WORK_ENTITY_OPS` | `=<int>` (`>0`, else default) | Per-frame ceiling on entity-cursor operations (small ops, e.g. one dictionary/index write each) on the update thread | A/B-measurement-only family. Doc comment: elapsed-time ceiling (`ACDREAM_STREAM_WORK_MS`) remains the authoritative CPU guard — this is a secondary cap, deliberately loose (leaves >90% of the time budget unused at default) | 4,096 | `StreamingWorkBudgetOptions.Parse` (`:62-64`) |
|
||||
| `ACDREAM_STREAM_WORK_GL_RETIRE_OPS` | `=<int>` (`>0`, else default) | Per-frame ceiling on GL/GPU resource-retirement operations on the update thread | A/B-measurement-only family; changes retirement cadence, which changes when GPU memory is actually reclaimed | 64 | `StreamingWorkBudgetOptions.Parse` (`:68-70`) |
|
||||
| `ACDREAM_STREAM_WORK_GPU_MIB` | `=<int MiB>` (`>0`, else default) | Per-frame ceiling on GPU upload bytes on the update thread | A/B-measurement-only family; directly changes per-frame upload throughput | 8 MiB | `StreamingWorkBudgetOptions.Parse` (`:65-67`) |
|
||||
| `ACDREAM_STREAM_WORK_HOLD_DEST_MS` | `=<double ms>` (`>0` and finite, else default `8.0`) | Absolute (not quality-scaled) time ceiling for destination-lane work during a portal/login hold; never shrinks a profile whose own ceiling is already ≥ this value | Explicitly documented as "NOT a user setting... exists for A/B measurement only, matching the rest of the `ACDREAM_STREAM_WORK_*` family" — do not set outside a deliberate hold-latency A/B comparison | 8.0 ms | `StreamingWorkBudgetOptions.Parse` (`:74-76`); `HoldDestinationCeilingMilliseconds` widens the frame meter via `StreamingWorkBudget.WidenForDestinationHold` while a destination reservation hides the world behind the authored tunnel (#418) |
|
||||
| `ACDREAM_STREAM_WORK_MS` | `=<double ms>` (`>0` and finite, else default) | Per-frame elapsed-time ceiling for update-thread streaming work — "the authoritative CPU guard" per the entity-ops comment | A/B-measurement-only family; this is the primary per-frame time budget for streaming — changing it changes both perceived streaming latency and measured frame cost | 2.0 ms | `StreamingWorkBudgetOptions.Parse` (`:53-55`) |
|
||||
| `ACDREAM_UNCAPPED_RENDER` | `=1` | Removes the normal VSync/refresh-rate software pacer, so the render loop runs as fast as the GPU/CPU allow. | Own doc comment (`RuntimeOptions.cs:147-150`): "Normal presentation is always bounded by VSync or a refresh-rate software pacer. This explicit diagnostic is the sole way to measure truly uncapped renderer throughput." Not representative of what a real player experiences — exists purely for throughput measurement. | `false` → VSync/pacer-bounded | `RuntimeOptions.UncappedRendering` → `GameWindow.cs:765` → `DisplayFramePacingController`; also `VulkanBringUpHost.cs:75` |
|
||||
| `ACDREAM_WB_DIAG` | `=1` (raw `string.Equals` ordinal compare) | (a) `GameWindow`: gates the `[FRAME-DIAG]` render-thread entity-upload-distribution report; (b) `WbDrawDispatcher`: gates `BeginRhiTimer`/`SampleRhiTimers`, wrapping the opaque/detail/transparent draw passes in extra Vulkan GPU timer-scope queries and periodically logging a `[WB-DIAG]` CPU/GPU median/p95 report | adds extra per-pass GPU timestamp queries every frame while on — genuine measurement overhead; NOT read through `RenderingDiagnostics` or any diagnostics-owner class, unlike every other flag in this set — flag for whitelisting (see Notes #2); the flag's supposed interaction with `ACDREAM_FRAME_PROF`'s GPU query is stale documentation (see Notes #1) | unset (off) | read directly at `WbDrawDispatcher.cs:2061-2064` (every `Draw()`/`BeginEntityDispatch` call, i.e. effectively per frame, NOT cached) and cached once as a readonly field at `GameWindow.cs:153-156` |
|
||||
| `ACDREAM_WORLD_TIME` | `=<float>`, accepted only in `[0, 1)` | Campaign V slice V7 instrument-determinism pin: freezes the Dereth day fraction (and therefore sun direction, sky keyframe, and every lit surface) instead of following the server clock. | Outranks BOTH the server `TimeSync` clock and the `/time` slash command's `SetDebugTime` (which is deliberately transient — the next `TimeSync` clears it); this pin does not clear. Distinct axis from `ACDREAM_DAY_GROUP` (day-group/weather-preset selection) and `ACDREAM_SKY_PHASE_SECONDS` (cloud scroll + foliage wind) — the calendar DATE still advances, only the intra-day fraction freezes. Anything outside `[0,1)` (including negative, unparseable, or unset) leaves the server clock alone entirely — no partial/clamped behavior. | `null` → server clock | `RuntimeOptions.PinnedWorldDayFraction` → `GameWindow.cs:720` → `WorldEnvironmentController` → `Runtime.WorldTime.PinnedDayFraction` |
|
||||
|
||||
## Automation
|
||||
|
||||
A scripted route run adds three things at once — a session config so the
|
||||
client self-selects a character, a route script, and an artifact directory:
|
||||
|
||||
```powershell
|
||||
$env:ACDREAM_UI_PROBE_SCRIPT = "$scratch\route.txt"
|
||||
$env:ACDREAM_AUTOMATION_ARTIFACT_DIR = "$scratch\artifacts"
|
||||
$env:ACDREAM_FRAME_PROF = "1"
|
||||
$env:ACDREAM_FRAME_HISTORY = "$scratch\frames.csv"
|
||||
& $exe --session-config "$scratch\session.json"
|
||||
```
|
||||
|
||||
**Two traps this recipe exists to document:**
|
||||
|
||||
1. **Without `--session-config`, the client stops at character select** and
|
||||
the route never runs. The session JSON supplies the endpoint, account,
|
||||
and a character `index` for auto-selection.
|
||||
2. **`ACDREAM_AUTOMATION_ARTIFACT_DIR` is not free.** It constructs the
|
||||
render-scene oracle, which fingerprints every resident entity every
|
||||
frame. The allocation cost was fixed in
|
||||
[#432](ISSUES.md), but the CPU walk remains — automation-run frame
|
||||
rates are diagnostics-loaded and must only be compared against other
|
||||
automation runs, never against a plain run. Some route verbs
|
||||
(`wait world-*`) additionally do nothing unless this is set.
|
||||
|
||||
| Flag | Value | What it does | Side effects | Default | Read by |
|
||||
|---|---|---|---|---|---|
|
||||
| `ACDREAM_AUTOMATION_ARTIFACT_DIR` | `=<path>` | Output directory for the retail-UI automation probe's checkpoint JSON + screenshot PNG artifacts; gates whether the full `WorldLifecycleAutomationController` (checkpoint/screenshot/render-pack-automation capable) is composed at all vs. the cheaper facts-only `WorldRevealFactsAutomationRuntime` fallback (`wait world-ready/visible` verbs work either way per issue #415's fix; checkpoint/screenshot verbs report "requires ACDREAM_AUTOMATION_ARTIFACT_DIR" without it). | **Known #432 surprise, confirmed still live**: `FrameRootComposition.cs:349-353` — `AutomationArtifactDirectory is not null` (together with `RetainedUi?.Screenshots is not null`) unconditionally constructs a `CurrentRenderSceneOracle` **and** a `RenderSceneShadowComparisonController` — a per-frame diagnostics referee — regardless of whether any checkpoint/screenshot is ever actually requested that session. Merely setting this var for its "just an output path" purpose pays the per-frame comparison cost for the whole run. | unset (null) → facts-only automation runtime, no per-frame referee constructed | `RuntimeOptions.AutomationArtifactDirectory` → `FrameRootComposition.cs:351,543-627`, `WorldLifecycleAutomationController.cs`, `RetailUiAutomationScriptRunner.cs:108` |
|
||||
| `ACDREAM_AUTOMATION_EXACT_FRAMEBUFFER` | `=1` | Forces the graphical host to use the persisted display resolution as the *initial* size of a **borderless** window at creation, so the OS window manager cannot clamp a decorated window to the desktop work area — needed for pixel-exact automated screenshot comparison. | Changes window chrome (borderless) at startup — a visible difference from an ordinary launch, not just an internal measurement knob. | `false` → normal decorated window | `RuntimeOptions.ExactAutomationFramebuffer` → `GameWindow.cs:852` (`CreateStartupWindowOptions`) |
|
||||
| `ACDREAM_BAKE_PUBLISH_NONCE_V1` | `=<32-hex GUID "N" format>` | Launcher-to-bake-child authorization token: when present and valid, the bake child takes a cross-process publish file lock + writes an authorization file before atomic publication (serializes with launcher recovery) | If present but fails `IsValidNonce` (not a 32-char Guid "N"), throws `InvalidOperationException` and aborts the bake. When absent, bake runs unguarded (standalone mode). Never set this manually outside the launcher's own child-process spawn. | unset (standalone unguarded bake) | `BakePublicationGuardPaths.cs:12`, read by `BakePublicationGuard.AcquireIfRequested` (`AcDream.Bake/BakePublicationGuard.cs:18`); set by `BakeProcessRunner.cs:150/162` |
|
||||
| `ACDREAM_NET_DROP_DIR` | `="out"`/`"in"`/anything-else (incl. unset) → `Both` (case-insensitive) | Selects which direction(s) — outbound, inbound, or both — the deterministic loss-injection decorator drops | Only takes effect when `ACDREAM_NET_DROP_PCT>0` (decorator is structurally absent otherwise). Drives real datagram loss on the live connection — the injection point for `tools/run-connected-loss-gate.ps1`. Never set during a normal/measurement run. | `Both` | `NetDiagnostics.NetDropDir` (`NetDiagnostics.cs:88-90,98-104`), consumed by `LossyTransportDecorator.WrapIfConfigured` (`Transport/LossyTransportDecorator.cs:21-22`), also read at `WorldSession.cs:901-907` (comment only) |
|
||||
| `ACDREAM_NET_DROP_PCT` | `=<int 0-100>` (out-of-range or unparsable → 0) | Percent chance (post-handshake-arming, per droppable datagram) that the deterministic `LossyTransportDecorator` drops a packet in the configured direction(s) | **Fault injection.** `>0` wraps the real socket transport in a packet-dropping decorator for the whole session — genuinely breaks/delays delivery to exercise N1-N4 reliable-transport recovery. At 0 the decorator is never constructed (zero structural cost). Must be 0/unset for any normal run or non-loss-gate measurement. | `0` (off, decorator absent) | `NetDiagnostics.NetDropPercent` (`NetDiagnostics.cs:60-69,92-96`), consumed by `LossyTransportDecorator.WrapIfConfigured` (`:21`), wired at `WorldSession.cs:901-907` |
|
||||
| `ACDREAM_NET_DROP_SEED` | `=<int>` (unparsable → `1`) | PRNG seed for the loss decorator (outbound seeded with `seed`, inbound with `~seed`) — same seed reproduces an identical drop pattern | Only matters when `ACDREAM_NET_DROP_PCT>0`; makes fault injection deterministic/reproducible for the connected loss gate | `1` | `NetDiagnostics.NetDropSeed` (`NetDiagnostics.cs:75-82`), consumed by `LossyTransportDecorator` (`:21-22`) |
|
||||
| `ACDREAM_OPEN_CHARGEN` | `=1` | Campaign CC slice CC4 interim env/test-only seam: opens the character-creation screen (`gmCharGenMainUI`) automatically once Runtime's chargen view goes active, bypassing the real retail Create-Character-button transition. Fires once per mount (`_openOnStartConsumed` latch). | Own doc comment explicitly calls this "interim env/test-only" — Campaign CC (closed 2026-08-16, user-accepted) later wired the real Create button with its roster<55-slot ghost gate, so this flag is now a bypass of that gate for automation/testing rather than the only way in. | `false` | `RuntimeOptions.OpenCharacterCreationOnStart` → `CharacterCreationUiController.cs:21,524-530` |
|
||||
| `ACDREAM_UI_PROBE_DUMP` | `=1` | Enables the retail-UI automation probe's diagnostic dump path and feeds `RetailUiProbeBindings`/`RetailUiAutomationScriptRunner`. Also part of `RuntimeOptions.UiProbeEnabled` (`UiProbeDump \ | \ | UiProbeScript is set`). | `RuntimeOptions.UiProbeDump` → `LivePresentationComposition.cs:1465-1495`, `InteractionRetainedUiComposition.cs:1092-1100` |
|
||||
| `ACDREAM_UI_PROBE_SCRIPT` | `=<path>` | Path to a script file the `RetailUiAutomationScriptRunner` executes against the retained UI (pointer/semantic-input command playback) for scripted UI regression testing. | Also flips `RuntimeOptions.UiProbeEnabled` true even without `ACDREAM_UI_PROBE_DUMP=1`. | `null` | `RuntimeOptions.UiProbeScript` → `InteractionRetainedUiComposition.cs:1094` |
|
||||
| `ACDREAM_VULKAN_FORCE_UNSUPPORTED` | `=<feature-name>` (case-insensitive property name, e.g. `MultiDrawIndirect`) | Test knob (Slice V5): clears one named required Vulkan feature from the capability record to synthetically fail the gate, so the `NotSupportedException` → exit-code-4 → report path can be exercised on hardware that actually supports everything. | Deliberately breaks Vulkan startup when set to a matched feature name — this is a "make it fail on purpose" gate-testing flag, never appropriate for a normal or measurement run. | `null` → real capabilities used unmodified | `RuntimeOptions.VulkanForcedUnsupportedFeature` → `VulkanCapabilityRecord.Without` (`VulkanCapabilityRecord.cs:113-119`), consumed at `VulkanGraphicsContext.cs:339` |
|
||||
| `ACDREAM_VULKAN_PROBE` | `=1` | Runs the standalone Vulkan capability-probe/bring-up harness (opens its own window, runs the capability gate, presents synthetic V6c/V6d verification scenes, captures one screenshot) **instead of** the real client composition host, then exits. | This flag ALONE gates entry (`GameWindow.cs:828`); the former `ACDREAM_RENDER_BACKEND=vulkan` co-requisite died with the OpenGL backend (its class doc was corrected 2026-08-24). | `false` → normal composition host | `RuntimeOptions.VulkanCapabilityProbe` → `GameWindow.cs:828` → `VulkanBringUpHost` |
|
||||
| `ACDREAM_VULKAN_PROBE_FRAMES` | `=<int>` (non-negative) | Bounds the bring-up probe harness to N presented frames so it can run unattended in CI, instead of presenting until a human closes the window. | The frame budget never cuts a pending screenshot capture short — the loop stays open until the screenshot has been attempted even past the budget, so an unattended run's whole product (a PNG) is guaranteed. Zero (unset/unparseable/explicit `0`) keeps the interactive wait-for-close behavior. | `0` → interactive (wait for window close) | `RuntimeOptions.VulkanCapabilityProbeFrames` → `VulkanBringUpHost.cs:141-249` |
|
||||
| `ACDREAM_DUMP_MOVE_TRUTH` | `=1` | Emits one `move-truth OUT` line per outbound movement record (MoveToState / AutonomousPosition): local resolved position vs the wire position/cell, ground contact, velocity (`MovementTruthDiagnosticController`). | **Automation apparatus, NOT a spent probe** — the canonical nine-stop soak (`tools/run-connected-r6-soak.ps1`) hard-gates on ≥2 of these lines per destination as its proof that production input produced outbound movement traffic; deleting it fails the soak at every stop (#437, deleted-and-restored 2026-08-24). Print volume follows the outbound send cadence. | off | `RuntimeOptions.DumpMoveTruth` → `GameWindow.cs` → `MovementTruthDiagnosticController` |
|
||||
|
||||
## Permanent diagnostics
|
||||
|
||||
| Flag | Value | What it does | Side effects | Default | Read by |
|
||||
|---|---|---|---|---|---|
|
||||
| `ACDREAM_CAPTURE_PLAYER_QUANTA` | `=<path>` (any non-whitespace path) | Opt-in JSON-Lines trace of every admitted player physics quantum (position/orientation/velocity/contact-plane snapshots at each stage boundary of `CPhysicsObj::UpdateObjectInternal`) | Appends+flushes one JSON line per physics quantum to the file (real file I/O on the physics tick when enabled); disabled path costs one static string null/empty check, no allocation. Read once into a mutable static property (settable via `ResetForTest`) rather than a typed options object. | unset (disabled, zero-alloc) | `PlayerPhysicsQuantumCapture` static class (`AcDream.Runtime/Gameplay/PlayerPhysicsQuantumCapture.cs:22`) |
|
||||
| `ACDREAM_DUMP_MOTION` | `=1` | prints `UM`/`[UM_STALE]`/`[MOTIONDONE]`/`VU.land`/raw-hex wire dump lines tracing inbound `UpdateMotion` handling, remote ground-contact edges, and motion-done callbacks (bug-a/#32 stuck-cast subthread is temporary; core trace is long-lived) | print-only, but the raw-site reads in `LiveEntityNetworkUpdateController.cs` and `UpdateMotion.cs` fire on EVERY inbound motion/UM event (not cached) — `Environment.GetEnvironmentVariable` call per packet even when off; `UpdateMotion.cs`'s branch additionally builds a `StringBuilder` hex dump when on. Rule-5 violation (raw reads outside a diagnostics-owner class) at 5+ call sites | off | THREE independent readers: `PhysicsDiagnostics.DumpMotionEnabled` (owner, appears unconsumed — see Notes), `AnimationPresentationDiagnostics.FromEnvironment()` (App owner record, cached once at startup, consumed by `LiveEntityAnimationPresenter`), and raw `Environment.GetEnvironmentVariable` reads scattered across `LiveEntityNetworkUpdateController.cs` (4 sites) + `Core.Net/Messages/UpdateMotion.cs:163` + `Runtime/Physics/RuntimeRemotePhysicsUpdater.cs:630` |
|
||||
| `ACDREAM_DUMP_PLAYSCRIPT` | `="1"` (ordinal) | Traces PhysicsScript playback: missing/empty script resolution, malformed `StartTime` entries, and other `[pes]`-prefixed hook-dispatch events | print-only (`Console.WriteLine`) at all 4 use sites (`:85-86,136,300,328`) | unset (off) | `PhysicsScriptRunner.DiagEnabled` (`PhysicsScriptRunner.cs:61-62`) — per-instance settable property seeded from the env var, not a shared static diagnostics-owner class |
|
||||
| `ACDREAM_DUMP_SURFACES` | `="1"` (ordinal) | One-shot (per session) surface-format histogram dump for the atlas-opportunity audit — fires once after `_dumpFrameCounter>=600` OnRender ticks AND `_uploadMetadata.Count>=100` uploaded textures; writes to the host diagnostics directory | Doc comment claims "Zero cost when off" but `_uploadMetadata[name]=(w,h,fmt)` (`TextureCache.cs:1042`) is written **unconditionally on every texture upload regardless of the flag** — real (small) always-on dictionary-write cost. `TickSurfaceHistogramDumpIfEnabled` also re-reads `Environment.GetEnvironmentVariable` every OnRender frame (not cached) until the one-shot fires. Dump-write failures are caught and logged to stderr, not fatal. | unset (off) | `TextureCache` (`TextureCache.cs:102-113` fields, gate at `TextureCache.cs:802-812`, dump at `TextureCache.cs:814-829`), Phase N.6 slice 1 |
|
||||
| `ACDREAM_FRAME_PROF` | `=1` | master toggle for the frame profiler: CPU frame time, GPU time samples, per-stage CPU attribution, per-frame alloc/GC, `[frame-prof]` report every ~5 s (doc: "permanent apparatus ... do not strip with session probes") | when on, samples `GC.GetAllocatedBytesForCurrentThread()` and stage-scope timing every frame (cheap, by design); its own XML doc claims a GPU-query self-disable tied to `ACDREAM_WB_DIAG=1` that `FrameProfiler.cs` says no longer exists — see Notes #1; startup-only in practice (its DebugPanel/DebugVM mirror is unreachable — #434)| false (off) | `RenderingDiagnostics.FrameProfEnabled` / `FrameProfiler` |
|
||||
| `ACDREAM_PROBE_ENVCELL` | `=1` | emits one `[envcells]` line per indoor frame: `CellsRendered`/`TrianglesDrawn` + ourBldgs/otherBldgs/filter counts (phase a8 relic; its own render pass was removed but the probe was kept) | print-only; implicitly turned on whenever `ACDREAM_PROBE_VIS` is on (getter is `_probeEnvCellEnabled \ | \ | `RenderingDiagnostics.ProbeEnvCellEnabled` (backing field OR'd with `ProbeVisibilityEnabled`) |
|
||||
| `ACDREAM_PROBE_INDOOR_ALL` | `=1` | master switch that reads as AND / writes as cascade across Walk, Lookup, Upload, Xform, Cull | print-only (every underlying probe is print-only); startup-only in practice (its DebugPanel/DebugVM mirror is unreachable — #434)| false (off) | `RenderingDiagnostics.IndoorAll` (cascades to the 5 flags below) |
|
||||
| `ACDREAM_PROBE_INDOOR_CULL` | `=1` (also set by `ACDREAM_PROBE_INDOOR_ALL=1`) | emits `[indoor-cull]` per culled cell entity with cull reason (visibleCellIds-miss / frustum / landblock) | print-only; startup-only (its DebugPanel/DebugVM mirror is unreachable — #434) | false (off) | `RenderingDiagnostics.ProbeIndoorCullEnabled` |
|
||||
| `ACDREAM_PROBE_INDOOR_LOOKUP` | `=1` (also via `ACDREAM_PROBE_INDOOR_ALL`) | emits `[indoor-lookup]` per visible cell entity/sec: render-data hit/miss, IsSetup, parts-hit/parts-miss tallies | print-only; startup-only (its DebugPanel/DebugVM mirror is unreachable — #434) | false (off) | `RenderingDiagnostics.ProbeIndoorLookupEnabled` |
|
||||
| `ACDREAM_PROBE_INDOOR_UPLOAD` | `=1` (also via `ACDREAM_PROBE_INDOOR_ALL`) | emits `[indoor-upload]` requested/completed lines per EnvCell id at `WbMeshAdapter`'s staged-drain time | print-only; startup-only (its DebugPanel/DebugVM mirror is unreachable — #434) | false (off) | `RenderingDiagnostics.ProbeIndoorUploadEnabled` |
|
||||
| `ACDREAM_PROBE_INDOOR_WALK` | `=1` (also via `ACDREAM_PROBE_INDOOR_ALL`) | emits `[indoor-walk]` per visible cell entity/sec: world position, parent cell, landblock/AABB-visible flags, "drew" flag | print-only; startup-only (its DebugPanel/DebugVM mirror is unreachable — #434) | false (off) | `RenderingDiagnostics.ProbeIndoorWalkEnabled` |
|
||||
| `ACDREAM_PROBE_INDOOR_XFORM` | `=1` (also via `ACDREAM_PROBE_INDOOR_ALL`) | emits `[indoor-xform]` per visible cell entity/sec: cell-geometry SetupPart's composed world-matrix translation | print-only; startup-only (its DebugPanel/DebugVM mirror is unreachable — #434) | false (off) | `RenderingDiagnostics.ProbeIndoorXformEnabled` |
|
||||
| `ACDREAM_PROBE_LOGIN_FRAMES` | `="1"` | Per-completed-frame login/portal-wormhole presentation classification (`world`/`tunnel`/`black`/`void`); logs `[login-frames]` on each classification transition | print-only. "Not a user setting; not in RuntimeOptions; not persisted" (doc comment). | unset (off) | `RenderPresentationDiagnostics.ProbeLoginFrames` (`LoginPresentationFrameProbe.cs:28-29`), consumed by `LoginPresentationFrameProbe.Process` |
|
||||
| `ACDREAM_PROBE_NET` | `="1"` | Emits `[net-out]` (per outbound reliable message), `[net-tick]` (1 Hz WorldSession.Tick summary incl. reliable-transport rates), `[net-final]` (cumulative stats at Dispose), and `[cmd-gate]` (generation-gated command rejections) | print-only. Doc comment: "the counters themselves increment unconditionally in `TransportStats`; only the string work is gated" — i.e. the underlying stats tracking has a small always-on cost independent of this flag, but this flag itself gates only string/console formatting. | unset (off) | `NetDiagnostics.ProbeNet` (`NetDiagnostics.cs:56-57`), issue #260 probe family |
|
||||
| `ACDREAM_PROBE_RESOLVE` | `=1` | gates one structured `[resolve]` line per `PhysicsEngine.ResolveWithTransition` call (in/target/out position+cell, ok-vs-partial, grounded/contact status, wall normal, walkable-polygon validity, responsible entity) (l.2a slice 1, general-purpose resolver probe) | print-only, ~30 Hz per moving entity while on | off | `PhysicsDiagnostics.ProbeResolveEnabled` |
|
||||
| `ACDREAM_PROBE_REVEAL` | `="1"` | While a reveal destination's composite warmup is incomplete, emits one `[composite-warmup]` line/second: pending queue depth, scan state, upload-budget gate, first few unresolved GfxObj ids | print-only | unset (off) | `NetDiagnostics.ProbeReveal` (`NetDiagnostics.cs:115-116`), issue #260 |
|
||||
| `ACDREAM_PROBE_REVEAL_TIMING` | `="1"` | Wall-clock attribution of each login/portal reveal hold: `[reveal-timing]` lines for `begin`/first-true readiness edges (render/composites/collision/gate/materialized), 1 Hz progress, and one `SUMMARY` line at viewport reveal; paired low-frequency `[reveal-resource]` snapshots report mesh staging/uploads/arena state, prepared-asset activity, composite warmup/uploads, managed memory, and tracked GPU residency | print-only; the probe object and render-resource sampler are not constructed when unset. When enabled, canonical resource owners are sampled only at begin, readiness edges, 1 Hz progress, and summary—not every frame. Use with `ACDREAM_FRAME_PROF=1` / `ACDREAM_FRAME_HISTORY` for per-frame CPU/GPU/alloc timing. | unset (off) | `StreamingDiagnostics.ProbeRevealTiming`, `RevealTimingProbe`, `RuntimeRenderFrameResourceDiagnosticsSource`, `PublicationTimingProbe` |
|
||||
| `ACDREAM_PROBE_TUNNEL_FREEZE` | `=1` or `=N` | #419 RenderDoc apparatus: holds the teleport state in stable `Tunnel` after destination readiness and freezes the portal-space animation/roll at frame 72 (`=1`) or an explicit frame 2–120 (`=N`); emits one `[tunnel-freeze]` line with the actual frame and retail Setup/animation ids | **behavior-changing diagnostic:** placement, world viewport reveal, and LoginComplete are intentionally withheld until transition cancellation/process exit. For static visual inspection only; never use in a performance or lifecycle measurement. | unset (off) | `StreamingDiagnostics.TunnelFreezeFrame`; consumed by `LocalPlayerTeleportPresentation` and `PortalTunnelPresentation` |
|
||||
| `ACDREAM_PROBE_SOUND_WIRE` | `="1"` | One line per inbound server Sound event (`0xF750`) and per wire-sound play decision, with the drop reason when nothing plays — used to determine whether missing interior soundscapes are server- or client-side | print-only, consumed at `AudioHookSink.cs:159` and `EntityEffectController.cs:123` | unset (off) | `AudioDiagnostics.ProbeWireSoundsEnabled` (`AudioDiagnostics.cs:20-21`) |
|
||||
| `ACDREAM_PROBE_USEABILITY_FALLBACK` | `=1` | gates a per-call log of `IsUseableTarget` calls that take the null-useability fallback path (creature/door/lifestone passes) (measures a real ace-vs-retail data gap, not a bug investigation) | print-only; measures how often ACE ships entities without `_useability` set | off | `PhysicsDiagnostics.ProbeUseabilityFallbackEnabled` |
|
||||
| `ACDREAM_PROBE_VIS` | `=1` | emits `[vis]` line on root-cell CHANGE: visible cell ids, OutsideView poly/plane counts, per-cell plane counts, scissor-fallback count (phase u.2d repurposed the flag; its DebugPanel mirror is unreachable — #434) | print-only; ALSO implicitly enables the separate `ACDREAM_PROBE_ENVCELL` probe (its getter ORs with this flag — see Notes #3); startup-only in practice (its DebugPanel/DebugVM mirror is unreachable — #434)| false (off) | `RenderingDiagnostics.ProbeVisibilityEnabled` |
|
||||
| `ACDREAM_REMOTE_VEL_DIAG` | `=1` | prints per-UM/per-tick remote-velocity and animation-cycle diagnostic lines; `Runtime/Physics/RemoteMotion.cs` carries diagnostic-only fields (`PrevServerPos`, `PrevServerPosTime`, `MaxRootMotionSpeedSinceLastUP`, `LastOmegaDiagLogTime`) unconditionally on every remote — small fixed per-instance memory regardless of the flag, not gated (long-lived remote-velocity/animation diagnostic, commit a.1) | print-only, but the raw-site reads in `LiveEntityNetworkUpdateController.cs` fire on every UM/tick even when off (rule-5 violation, `Environment.GetEnvironmentVariable` call per event, 6+ call sites) | off | THREE readers: `AnimationPresentationDiagnostics.FromEnvironment()` (App owner record, cached at startup, consumed by `LiveEntityAnimationPresenter` for `[SEQSTATE]`/`[CURRNODE]`/other part-diagnostic lines, throttled to 1/sec/entity) + raw `Environment.GetEnvironmentVariable` reads scattered across `LiveEntityNetworkUpdateController.cs` (6+ sites: `[UM_RAW]`, `[FWD_WIRE]`, `[VEL_DIAG]`, `[UPCYCLE_SRC]`, `[UM_STALE]`) + `RemoteServerControlledVelocityCycle.cs:68` (`[UPCYCLE]`) |
|
||||
| `ACDREAM_DUMP_CELLS` | `=<comma list of hex cell ids>` | one-shot JSON dump of any cached EnvCell whose id matches the list, to `ProbeDumpCellsPath` (issue #98 fixture capture) — Standing fixture-extraction tooling (A6.P3/#98 lineage) for the physics replay harness; roundtrip-tested. Not investigation-scoped. | file I/O once per matching cell id (no-op on repeat); fixture-generation tool, not a perf-neutral no-op when ids are listed | off/unset | `PhysicsDiagnostics.ProbeDumpCellIds` (`ParseHexIdList`) |
|
||||
| `ACDREAM_DUMP_CELLS_DIR` | `=<dir>` | overrides the output directory for `ACDREAM_DUMP_CELLS` — Companion output-directory knob for ACDREAM_DUMP_CELLS. | print/file-path only; no effect unless `ACDREAM_DUMP_CELLS` is also set | off/unset | `PhysicsDiagnostics.ProbeDumpCellsPath` |
|
||||
| `ACDREAM_DUMP_GFXOBJS` | `=<comma list of hex GfxObj ids>` | one-shot JSON dump of any cached GfxObj's polygon table + BSP root metadata matching the list, to `ProbeDumpGfxObjsPath` (issue #98 fixture capture) — Standing fixture-extraction tooling (A6.P3/#98 lineage), pair of DUMP_CELLS. | file I/O once per matching id (no-op on repeat) | off/unset | `PhysicsDiagnostics.ProbeDumpGfxObjIds` (`ParseHexIdList`) |
|
||||
| `ACDREAM_DUMP_GFXOBJS_DIR` | `=<dir>` | overrides the output directory for `ACDREAM_DUMP_GFXOBJS` — Companion output-directory knob for ACDREAM_DUMP_GFXOBJS. | print/file-path only; no effect unless `ACDREAM_DUMP_GFXOBJS` is also set | off/unset | `PhysicsDiagnostics.ProbeDumpGfxObjsPath` |
|
||||
| `ACDREAM_DUMP_SKY` | `=1` | Print-only: dumps decoded `SkyDesc` raw values on region load (`SkyDescLoader.cs`) and per-GfxObj `Surface.Type`/translucency flags on first upload (`SkyRenderer.cs`), plus gates a `TimeSync` console diagnostic in `GameWindow`. Built to resolve specific open questions about retail sky units and GfxObjReplace timing (2026-04-23 research), now answered but the dumps remain wired. — Generic sky-keyframe isolation dump (introduced with the phase-1 tint revert); a tool, not a bug probe. | Three independent reads of the SAME env var, only one of which (`RuntimeOptions.DumpSky`) goes through the typed options object; the other two are raw scattered reads (see Notes). `SkyRenderer.cs:582`'s raw read is in the App layer and has no architectural excuse for bypassing `RuntimeOptions` — `_options.DumpSky` was already available to that composition. `print-only` in all three sites. | off/unset | `RuntimeOptions.DumpSky` (typed) → `GameWindow.cs:704` (`TimeSyncDiagnostic`); **also** two independent raw `Environment.GetEnvironmentVariable` reads at `SkyDescLoader.cs:392` (Core) and `SkyRenderer.cs:582` (App) |
|
||||
| `ACDREAM_DUMP_STEEP_ROOF` | `=1` | gates `[steep-roof] KILL-VELOCITY-APPLIED` in `PhysicsEngine.ResolveWithTransition` when retail's `kill_velocity` zeroes body velocity on steep-slope impact, plus per-frame plane-normal traces in `TransitionTypes`/`PlayerMovementController` — KEEP: observes LIVE divergence-register row AD-56 (the plumb-fall freeze on steep-but-walkable polys, restored 2026-08-07). The only runtime lens on that active divergence; delete only with the AD-56 row itself. | print-only | off/unset | `PhysicsDiagnostics.DumpSteepRoofEnabled` |
|
||||
| `ACDREAM_HIDE_PART` | `=<int>` | Hides one mesh part by index on entities with ≥10 parts (humanoids) — a debugging aid for equipment/clothing part-visibility issues. — Generic model-part isolation tool (issue #37 lineage but general-purpose since); a tool, not a bug probe. | Real (visible) behavior change, not print-only, but scoped to a single diagnostic index and off by default. | off/unset | `RuntimeOptions.HidePartIndex` → `LivePresentationComposition.cs:608` → `LiveEntityAnimationPresenter.cs:21,38,243` |
|
||||
| `ACDREAM_PROBE_CELL` | `=1` | gates one `[cell-transit]` line per `PlayerMovementController.CellId` change (old→new cell, position, reason tag) — Standing cell-transit tracer (L.2a slice 1), pair of the permanent ACDREAM_PROBE_RESOLVE; recurs in every membership investigation. | print-only; low volume (only on actual cell crossings) | off/unset | `PhysicsDiagnostics.ProbeCellEnabled` |
|
||||
|
||||
## Temporary probes
|
||||
|
||||
Each row names the issue that owns it. **A temporary probe is deleted in
|
||||
the same commit as its investigation's fix** — if you find one here whose
|
||||
issue is closed, the strip was missed; delete both.
|
||||
|
||||
> **Probe debt, measured 2026-08-24:** 64 temporary probes existed, citing 21
|
||||
> distinct issues with 14 already closed. [#435](ISSUES.md) part 1 stripped
|
||||
> the 17 rows whose investigation had ended without the strip — see the
|
||||
> Retired section below for their removal record — leaving 47. Part 2
|
||||
> traced each of the (then-)14 unattributed rows to its introducing commit
|
||||
> and stripped the 7 that belonged to closed investigations
|
||||
> (`ACDREAM_A8_DUMP_PV`/Phase A8, `ACDREAM_DUMP_CLOTHING`/#37,
|
||||
> `ACDREAM_DUMP_EDGE_SLIDE`/#32, `ACDREAM_DUMP_LIVE_SPAWNS`/Phase A8,
|
||||
> `ACDREAM_DUMP_STEPUP`/L.2.3d-f, `ACDREAM_DUMP_VENDOR`/the vendor
|
||||
> campaign, `ACDREAM_DUMP_VITALS`/#5). An eighth,
|
||||
> `ACDREAM_DUMP_MOVE_TRUTH`, was deleted and then RESTORED the same day:
|
||||
> it turned out to be automation apparatus, not a probe — the canonical
|
||||
> nine-stop soak hard-gates on its output (see its row under Automation;
|
||||
> #437 is the record). The rest of the attributed rows were reclassified
|
||||
> into Permanent diagnostics as standing tools rather than investigation
|
||||
> probes, leaving **31 temporary probes, every one attributed to an owning
|
||||
> issue or campaign**. Each still costs a branch on its hot path even when
|
||||
> unset, and a handful re-read the environment per frame rather than
|
||||
> caching (see their side-effects column).
|
||||
|
||||
| Flag | Owning investigation | Value | What it does | Side effects | Read by |
|
||||
|---|---|---|---|---|---|
|
||||
| `ACDREAM_CLIP_DEBUG` | #176 | `=1` | forces the EnvCell SHELL pass to map every instance to clip slot 0 (no-clip) instead of its cell's portal-slice region | ALTERS RENDERED OUTPUT: shells draw whole/unclipped instead of trimmed — a visual isolation mode, not a log-only probe; no DebugPanel mirror | `RenderingDiagnostics.ClipDebugNoShellTrim` |
|
||||
| `ACDREAM_DUMP_APPEARANCE` | #5 | `="1"` | Logs every `0xF625` ObjDescEvent + `0xF7DB` UpdateObject with body length, target guid, hex preview — used to debug remote-player appearance asymmetry | print-only (`Console.WriteLine`) | `WorldSession` static field `DumpAppearanceEnabled` (`WorldSession.cs:792-793`), raw scattered read, issue #5 diagnostic |
|
||||
| `ACDREAM_DUMP_OPCODES` | #5 | `="1"` | Logs first occurrence of each genuinely-unhandled inbound opcode (deduped by opcode) | print-only. Must stay the LAST else-if in the dispatch chain per comment (else it would intercept handled opcodes) — currently correct. | `WorldSession` static field `DumpOpcodesEnabled` (`WorldSession.cs:788-789`, consumed `WorldSession.cs:2391-2398`), issue #5 diagnostic. Also mirrored (display-only, non-functional) via `DebugPanel.cs:241`/`DebugVM.cs:227`. |
|
||||
| `ACDREAM_DUMP_SCENERY_Z` | #48 | `=1` | Per-spawn Z-placement diagnostic for procedural scenery (trees/bushes/rocks), added for issue #48 (the "trees-in-sky" bug). | **NOT print-only** — this is a real behavior fork, not just added logging. `LandblockBuildFactory.cs:167-178`: when the flag is on, the streaming worker calls a **separate, duplicate scenery-building method** (`BuildSceneryEntitiesForStreaming`, a full parallel reimplementation of GfxObj/Setup mesh resolution + placement inline in this file) instead of production's `LandblockPhysicsContentBuilder.HydrateProceduralScenery`. Any visual/measurement run taken with this flag set is exercising a different scenery-placement code path than production, which can drift from it silently. | `RuntimeOptions.DumpSceneryZ` → `SessionPlayerComposition.cs:280` → `LandblockBuildFactory.cs:23,42,168,335` |
|
||||
| `ACDREAM_DUMP_TRANSIT_FAIL` | #345 | `=1` | buffers per-tick `[transit-fail-insert]`/`[transit-fail-stepup]`/`[transit-fail-walk]`/`[transit-fail-adjust]` trace lines into a `[ThreadStatic]` list and flushes them to console ONLY when a tick requested nonzero XY movement but delivered zero (self-selecting "stuck tick" predicate) | print-only, zero allocation when off (flag checked before touching any buffer per its own doc); buffer/list allocation only on ticks that are already stuck | `PhysicsDiagnostics.DumpTransitFailEnabled` |
|
||||
| `ACDREAM_LIGHT_DEBUG` | #176 | `=<int>` (`int.TryParse`; unset/invalid → 0) | shader isolation mode uploaded as `uLightDebug` by `EnvCellRenderer` + `WbDrawDispatcher`: 0=off, 1=ambient-only vertex lighting, 2=kill dynamic point lights, 3=raw vLit visualization (texture ignored) | ALTERS RENDERED OUTPUT directly every draw pass (changes fragment-shader lighting/texturing) — not a log probe; no DebugPanel mirror | `RenderingDiagnostics.LightDebugMode` |
|
||||
| `ACDREAM_PROBE_BUILDING` | l.2d slice 1 | `=1` | gates the multi-line `[resolve-bldg]` BSP-shadow-hit trace in `TransitionTypes.FindObjCollisions`, one-time `[entity-source]` registration logs in `GameWindow`, `[door-cycle]` UM dispatch trail, and a one-shot `[setstate-hex]` wire dump of the first `SetState` (0xF74B) packet in `WorldSession` | print-only; also un-gates the `PhysicsDiagnostics.LastBspHitPoly` diagnostic side-channel (a static field write in `BSPQuery`/`FlatBspQuery`, read back by the `[resolve-bldg]` line) — no gameplay effect, but an extra static-field write per BSP hit while on; heavy output (one multi-line entry per BSP hit per physics tick) | `PhysicsDiagnostics.ProbeBuildingEnabled` |
|
||||
| `ACDREAM_PROBE_CELLSET` | a6.p5 | `=1` | gates `PhysicsDiagnostics.LogCellSetBuild`, one `[cellset-build]` line per `BuildCellSetAndPickContaining` call (seed cell, sphere XY, candidate list) from `CellTransit.cs:1468` | print-only; builds a `StringBuilder` of the candidate id list only when the flag is on | `PhysicsDiagnostics.ProbeCellSetEnabled` |
|
||||
| `ACDREAM_PROBE_CELL_CACHE` | indoor walking phase d | `=1` | gates one `[cell-cache]` line per EnvCell first-cached in `PhysicsDataCache.CacheCellStruct` (poly counts, BSP root structure) | print-only; fires at most once per EnvCell (cache is no-op after first population); no DebugPanel mirror | `PhysicsDiagnostics.ProbeCellCacheEnabled` |
|
||||
| `ACDREAM_PROBE_CHILD_CELL` | c4 route 7 | `=1` | gates one `[child-cell]` line per Runtime committed-child canonical-cell write in `RuntimeLiveEntitySessionController`, `RuntimeEntityObjectLifetime`, `RuntimeEntityDirectory` (parent/child guid, old/new cell, cause tag) | print-only | `PhysicsDiagnostics.ProbeChildCellEnabled` |
|
||||
| `ACDREAM_PROBE_CLIPROUTE` | "throwaway apparatus — strip once §4 ships" | `=1` | print-on-change `[clip-route]` / `[clip-route-disp]` / `[clip-route-scis]` lines: outside-slice clip routing, region-SSBO bytes, terrain-UBO head, actual GL/RHI scissor state | print-only | `RenderingDiagnostics.ProbeClipRouteEnabled` |
|
||||
| `ACDREAM_PROBE_CONTACT_PLANE` | spike-only, 2026-05-20 | `=1` | gates one `[cp-write]` line per write to `CollisionInfo.ContactPlane*`/`LastKnownContactPlane*` fields (field, old→new, caller method via stack walk, source line); only logs on actual value changes | print-only, but performs a stack walk to identify the caller method when firing — real CPU cost per write while on (not just a string format); suppresses no-op writes to bound volume | `PhysicsDiagnostics.ProbeContactPlaneEnabled` |
|
||||
| `ACDREAM_PROBE_ENT` | #138 | `="1"` | Traces the persistent player entity across teleport streaming churn: presence in the render draw-set flat view vs. survival of the dynamics cull, to distinguish "missing from draw set" vs "present but culled" | print-only, "Observation-only — emits no behavior change" (doc comment). `LogPlayerDynOnChange` dedupes by transition to avoid per-frame spam. Marked STRIP-once-root-caused (like the dense-town FPS apparatus). | `EntityVanishProbe.Enabled` (`EntityVanishProbe.cs:23-24`), issue #138-B |
|
||||
| `ACDREAM_PROBE_FLAP` | "throwaway apparatus — strip once the flap mechanism is confirmed" | `=1` | EVERY FRAME (unthrottled, not change-gated) while the camera root is indoor: `[flap]` from `PortalVisibilityBuilder.Build` (portal side-test/traverse/cull/projection) + paired `[flap-cam]` from `PhysicsCameraCollisionProbe`/`[flap-sweep]` (FindCameraCell resolution, eye positions) | print-only, but unthrottled per-frame `StringBuilder` allocation + `Console.WriteLine` on multiple call sites while indoor — heavy log volume/allocation under sustained indoor play; does not alter rendered output | `RenderingDiagnostics.ProbeFlapEnabled` |
|
||||
| `ACDREAM_PROBE_GLSTATE` | "throwaway apparatus — strip once §4 ships" | `=1` | print-on-change `[gl-state]` line: depth/blend/cull/scissor/viewport/draw-FBO/color-mask/`glGetError` snapshot | print-only per its docstring; the actual state-snapshot/comparison call site lives outside `RenderingDiagnostics.cs` and was outside this pass's cited read sites | `RenderingDiagnostics.ProbeGlStateEnabled` |
|
||||
| `ACDREAM_PROBE_INDOOR_BSP` | indoor walking phase 1 / cellar-lip wedge | `=1` | gates `[indoor-bsp]` (per `BSPQuery.FindCollisions` indoor call), `[neg-poly]` (near-miss polygon detail in `BSPQuery`), and `[stepdown-decide]` (step-down accept/reject inputs in `TransitionTypes`) trace lines | print-only; also un-gates the `LastBspHitPoly` diagnostic side-channel write (same as `ACDREAM_PROBE_BUILDING`) | `PhysicsDiagnostics.ProbeIndoorBspEnabled` |
|
||||
| `ACDREAM_PROBE_INDOOR_LIGHT` | #176/#177 discriminator, a7.l1 | `=1` | rate-limited (1 Hz) `[indoor-light]` line from `LightManager.BuildPointLightSnapshot`: point-light pool set composition (pool/cellLess/registered/capped/byCell histogram) | print-only, explicitly "inert unless set" per the call-site comment (LightManager.cs:368-370); no DebugPanel mirror | `RenderingDiagnostics.ProbeIndoorLightEnabled` |
|
||||
| `ACDREAM_PROBE_JUMP` | campaign ch round 2 | `=1` | gates the `[jump]` line in `PlayerMovementController.ReportJumpRefusal`, printed UNCONDITIONALLY (even when `OnInterfaceText` is null) to distinguish "branch never fired" from "branch fired, callback dropped it" | print-only; `Headless/Policies/HeadlessBotPolicy.cs`'s `JumpProbeHeadlessBotPolicy` doc comment references this flag as a companion but does not itself read it — it is a headless bot behavior meant to be run alongside `ACDREAM_PROBE_JUMP=1`, not a second consumer | `PhysicsDiagnostics.ProbeJumpEnabled` |
|
||||
| `ACDREAM_PROBE_LOCAL_TELEPORT` | c4 route 3 d-t8 | `=1` | gates one `[local-tp]` line per local-player portal-arrival attempt (committed AND refused) from `LocalPlayerTeleportController` and `RuntimeAcceptedPositionDriveController.LogPortalArrivalAttempt` — the single Runtime chokepoint both graphical and headless hosts share | print-only; dual-host parity evidence (same line shape from both hosts) | `PhysicsDiagnostics.ProbeLocalTeleportEnabled` |
|
||||
| `ACDREAM_PROBE_PARK` | issue #309 | `=1` | gates `[park]`/`[park-restore]` lines when a `RuntimeSetPositionState` placement parks or a cancelled park's withdrawal is rolled back | print-only, low volume (parks are rare); in a MULTI-session headless host, `HeadlessStaticStateAudit.ValidateProcessIsolation` THROWS `HeadlessConfigurationException` at startup if this (or any other process-global `Probe*`/`Dump*` boolean, `CollisionShadowSampleEvery`, or `PhysicsResolveCapture`) is enabled — refusal is waived only when `sessionCount==1` (logs loudly and proceeds instead) | `PhysicsDiagnostics.ProbeParkEnabled` |
|
||||
| `ACDREAM_PROBE_PLACEMENT_FAIL` | issue #98 | `=1` | gates one `[place-fail]` line per Path-1 (Placement/Ethereal) `Collided` return in `BSPQuery.FindCollisions`, plus one per `Transition.DoStepDown` placement-insert rejection | print-only; low volume (fires only on actual rejection) | `PhysicsDiagnostics.ProbePlacementFailEnabled` |
|
||||
| `ACDREAM_PROBE_POLY_DUMP` | a6.p3 slice 4, issue #98 | `=1` | gates one `[poly-dump]` line (full polygon geometry: cell, poly index, sides, plane, all vertices) per `AdjustSphereToPlane` push-back call | print-only; HEAVY output (one full-geometry dump per push-back call) — doc explicitly says "use briefly, then turn off" | `PhysicsDiagnostics.ProbePolyDumpEnabled` |
|
||||
| `ACDREAM_PROBE_PORTAL_CHURN` | "throwaway apparatus — strip once the bound ships" | `=1` | one `[portal-churn]` summary per `PortalVisibilityBuilder.Build` call: per-cell pop/re-pop counts, re-enqueue totals, reciprocal-clip pre→post region growth | print-only | `RenderingDiagnostics.ProbePortalChurnEnabled` |
|
||||
| `ACDREAM_PROBE_PUSH_BACK` | phase a6.p1 | `=1` | gates `[push-back]` (`BSPQuery.AdjustSphereToPlane`), `[push-back-disp]` (`BSPQuery.FindCollisions` 6-path dispatcher), `[push-back-cell]` (`Transition.CheckOtherCells` multi-cell BSP) lines | print-only; the `DebugVM.cs:380` "runtime mirror" is dead code — `DebugVM`/`DebugPanel` (`AcDream.UI.Abstractions/Panels/Debug/`) are never instantiated anywhere in `src/` (the ImGui frontend they required was removed at Campaign V slice V11); only the startup env var takes effect | `PhysicsDiagnostics.ProbePushBackEnabled` |
|
||||
| `ACDREAM_PROBE_PVINPUT` | "throwaway apparatus — strip once the jitter source is pinned" | `=1` | one `[pv-input]` line/frame with 6-dp-precision `PortalVisibilityBuilder.Build` inputs (camera eye, player position, VP elements) + resulting flood-cell count; deliberately runs WITHOUT the heavier `[flap]` probe so the log stays diffable | print-only | `RenderingDiagnostics.ProbePvInputEnabled` |
|
||||
| `ACDREAM_PROBE_REMOTE_SLIDE` | bug b, temporary — strip once two-client roof capture lands | `=1` OR `=<comma-separated hex GUID list>` | gates `[remote-slide-up]`/`[remote-slide-vec]`/`[remote-slide-snap]`/`[remote-slide-enq]` lines across `LiveEntityNetworkUpdateController`, `InterpolationManager`, `RuntimeRemotePhysicsUpdater`, `RuntimeRemoteSteadyStatePosition` tracing two candidate remote-slide "blip" producers | print-only; `BeginRemoteSlideAttribution`/GUID-stamping calls are UNCONDITIONAL at several call sites (self-guard is internal), so a `[ThreadStatic]` field write happens on every remote tick regardless of the flag (cheap, non-allocating); a GUID allow-list narrows output to specific entities for a readable two-client capture | `PhysicsDiagnostics.ProbeRemoteSlideEnabled` + `ProbeRemoteSlideGuids` (raw string parsed via `ParseHexIdList` unless it's the literal `"1"`) |
|
||||
| `ACDREAM_PROBE_REMOTE_TELEPORT` | c4 route 4b-3, temporary | `=1` | gates one `[remote-teleport]` line per routed remote teleport arm in `LiveEntityNetworkUpdateController.ApplyRemoteContactRouting` | print-only; a 2026-08-04 fix moved the enabled-check to the CALL SITE because the probe's internal self-guard did not prevent `teleportStatus.ToString()` from being evaluated/allocated on every teleport regardless of flag state — now properly guarded | `PhysicsDiagnostics.ProbeRemoteTeleportEnabled` |
|
||||
| `ACDREAM_PROBE_SEAMDRAW` | #176, "throwaway apparatus" | `"1"`/`"true"`/blank → default #176 Facility Hub cell set (7 fixed hex ids); otherwise comma-separated hex cell-id list | change-deduped + 2 s-heartbeat `[seam-cell]`/`[seam-snap]`/`[seam-ent]`/`[seam-mask]` lines from `EnvCellRenderer.Render` and `WbDrawDispatcher` describing per-instance transforms and resolved light-set identities at target cells | print-only | `RenderingDiagnostics.ProbeSeamDrawEnabled` / `SeamDrawTargetCells` |
|
||||
| `ACDREAM_PROBE_STEP_WALK` | a6.p3 issue #98 | `=1` | gates `[step-walk]` lines at select points in the transition sub-step loop and step-down probe (requested vs adjusted offset, sphere positions, contact planes, walkable flags) | print-only; no DebugPanel mirror | `PhysicsDiagnostics.ProbeStepWalkEnabled` |
|
||||
| `ACDREAM_PROBE_SWEPT` | phase w stage 0 | `=1` | gates one `[cell-swept]` line per `ResolveWithTransition` call comparing the transition's swept cell vs the legacy static `ResolveCellId` path | print-only | `PhysicsDiagnostics.ProbeSweptEnabled` |
|
||||
| `ACDREAM_PROBE_TELEPORT` | 2026-06-22, "removable diagnostic" | `=1` | gates `[tp-probe]` lines (`LogTeleport`) at AIM/ENQ/BUILD/APPLY/PLACED teleport-pipeline events across `LocalPlayerTeleportController` and `RuntimeAcceptedPositionDriveController`, with cross-thread monotonic timestamps | print-only | `PhysicsDiagnostics.ProbeTeleportEnabled` |
|
||||
## Deprecated
|
||||
|
||||
| Flag | Value | What it does | Side effects | Default | Read by |
|
||||
|---|---|---|---|---|---|
|
||||
| `ACDREAM_DEVTOOLS` | `=1` | logs a one-time "ImGui dev UI removed" notice; the only remaining functional consumer is `VulkanGraphicsContext.cs:184` (`enableOptionalExtensions: _options.DevTools`, selects optional Vulkan validation/debug-utils extensions) | real effect: turns on Vulkan validation/debug-utils extensions (can change perf and can surface validation-layer errors that don't occur when off) — NOT measurement-neutral for a perf gate; `GameWindow.DevToolsEnabled` is a hardcoded `false` const (dead — no ImGui dev UI exists to gate); `DevToolsInputCaptureSource(bool enabled)` explicitly discards its `enabled` ctor arg (`_ = enabled;`) — dead parameter, always reports `WantCaptureKeyboard=false` | off | `RuntimeOptions.DevTools` (typed, `Program.cs`/`RuntimeOptions.Parse`) |
|
||||
| `ACDREAM_STREAM_RADIUS` | `=<int>` (non-negative) | Legacy override for the streaming near/far radii, applied on top of the quality-preset's radii at session-start composition. | **CLAUDE.md explicitly documents this as "legacy" and warns against using it for measurement.** Confirmed in code (`SessionPlayerComposition.cs:256-259`): `nearRadius = legacyRadius; farRadius = Math.Max(legacyRadius, farRadius)` — it FORCES `NearRadius` and only ever RAISES (never lowers) `FarRadius`. It is set once at session-start composition and is **silently discarded** by any later Settings quality change: `RuntimeSettingsController.ApplyQuality` → `RuntimeSettingsTargets.ApplyQuality` → `StreamingController.ReconfigureRadii` recomputes radii straight from the quality preset with no knowledge of this override. A measurement/gate run taken with this set is measuring a different streaming window than production and than any run that later touches Settings. | `null` → quality-preset radii unmodified (production default: High preset, Near 4 / Far 12) | `RuntimeOptions.LegacyStreamRadius` → `SessionPlayerComposition.cs:254-268` |
|
||||
|
||||
---
|
||||
|
||||
<!-- retired -->
|
||||
|
||||
## Retired
|
||||
|
||||
Flags that no longer exist, kept only so a stale script or an old research
|
||||
document does not send someone hunting. Rows below this marker are exempt
|
||||
from the "must still exist" check.
|
||||
|
||||
| Flag | Retired | Replacement |
|
||||
|---|---|---|
|
||||
| `ACDREAM_RUN_SKILL` | Client-side run-skill override for local motion prediction. Skills now arrive from the server (`LiveMovementStatsApplier`); the hardcoded fallback is 200. | none — server-authoritative |
|
||||
| `ACDREAM_JUMP_SKILL` | As above. The fallback is 300, not the 200 that CLAUDE.md advertised. | none — server-authoritative |
|
||||
| `ACDREAM_RENDER_BACKEND` | Selected the GL-vs-Vulkan backend. Campaign V deleted the OpenGL backend; Vulkan is the only one. Two comments still named it as a live co-requisite until 2026-08-24. | none |
|
||||
| `ACDREAM_ANIM_SPEED_SCALE` | Animation-speed multiplier from the pre-retail-sequencer era; died with the 1.248x factor. | none |
|
||||
| `ACDREAM_A8_AUDIT` | Phase A8 EnvCell batch/cull audit dump. Its only caller never existed; `EnvCellRenderer.CollectCellAuditLines` was unreachable and was deleted 2026-08-24. | `ACDREAM_PROBE_ENVCELL` |
|
||||
| `ACDREAM_AIRBORNE_DIAG` | #42 airborne-sweep `[SWEEP]`/`[SWEEP-OBJ]` XY-drift trace. Investigation closed; stripped 2026-08-24 (#435) along with its 16 siblings below. | none |
|
||||
| `ACDREAM_DUMP_ENTITY` | #119 tower-staircase HYDRATE/DRAW/WALK-REJECT entity watchlist. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_AUTOWALK` | Issue #63 server-initiated auto-walk trace. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_LIGHT` | #133 A7 dungeon-lighting `[light]`/`[light-detail]` trace. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_OUTSTAGE` | #131 outside-stage dynamics routing trace (also the `ACDREAM_DUMP_ENTITY` `[outstage-own]` watchlist consumer). Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_PHANTOM` | #113 phantom-shell/phantom-objs draw-mechanism trace. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_REACH` | #334 broadphase candidate-disposition trace. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_REMOTE_LANDING` | Bug A / issue #32 remote ground-contact landing trace. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_SHELL` | #78 cell-shell opaque-pass render trace. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_STEP_HEIGHTS` | Issue #338 step-up/step-down height provenance trace (including its unconditional once-per-process `AnnounceStepHeightProbeOnce` self-report). Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_STICKY` | R5-V3 issue #171 sticky-melee lifecycle/steer trace. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_SUPPORT` | Issue #337 `[support]`/`[geom]` collision-vs-visual classifier trace. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_TEXFLUSH` | #105 white-indoor-textures staged-upload trace. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_VIEWER` | #119-residual viewer/flood capture (tower-ascent replay). Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_PROBE_WALK_MISS` | Issue #83 indoor walkable-plane miss trace. Investigation closed; stripped 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_WIRE_MESH` | Issue #337 F2 overlay upgrade to real physics-BSP polygon edges. Investigation closed; stripped 2026-08-24 (#435) — F2 reverted to its proxy-cylinder overlay. | none |
|
||||
| `ACDREAM_WIRE_RADIUS` | Companion radius knob for `ACDREAM_WIRE_MESH`. Stripped alongside it 2026-08-24 (#435). | none |
|
||||
| `ACDREAM_A8_DUMP_PV` | Phase A8.F portal-frame visual-gate triage dump (camera-cell portal census + EXIT-PROJ/EXIT-CLIP/EXIT trace in `PortalVisibilityBuilder.Build`). Phase A8 closed; stripped 2026-08-24 (#435 part 2). | none |
|
||||
| `ACDREAM_DUMP_CLOTHING` | Issue #37 humanoid-coat clothing/part-swap trace. #37 closed 2026-05-11; stripped 2026-08-24 (#435 part 2). | none |
|
||||
| `ACDREAM_DUMP_EDGE_SLIDE` | Issue #32 L.2c edge-slide/cliff-slide branch trace (five `edge-slide:` lines). #32 closed 2026-08-07; stripped 2026-08-24 (#435 part 2). | none |
|
||||
| `ACDREAM_DUMP_LIVE_SPAWNS` | Phase A8 indoor-visibility batch live-spawn/DROP trace. Phase A8 closed; stripped 2026-08-24 (#435 part 2). | none |
|
||||
| `ACDREAM_DUMP_STEPUP` | L.2.3d/e/f step-up `stepup: enter/SUCCESS/FAILED` trace. Investigation closed; stripped 2026-08-24 (#435 part 2) — its content is still covered by the separate `[transit-fail-stepup]` line under `ACDREAM_DUMP_TRANSIT_FAIL`. | `ACDREAM_DUMP_TRANSIT_FAIL` |
|
||||
| `ACDREAM_DUMP_VENDOR` | `[vendor-diag]` trace (~25 call sites) for two vendor-approach/split-stack regressions. Vendor campaign closed 2026-08-08; stripped 2026-08-24 (#435 part 2) along with its owner class `VendorDiagnostics.cs`. | none |
|
||||
| `ACDREAM_DUMP_VITALS` | Issue #5 `PrivateUpdateVital`/`PlayerDescription`/parse-failure trace across 4 sites. #5 closed 2026-04-25; stripped 2026-08-24 (#435 part 2). | none |
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
# UI framework plan
|
||||
|
||||
**Date:** 2026-04-24 (design), shipped 2026-04-25
|
||||
**Status:** **Phase D.2a and the D.2b retained gameplay UI have shipped.**
|
||||
ImGui remains the `ACDREAM_DEVTOOLS=1` developer stack. Retail gameplay UI is
|
||||
the independent `UiHost`/`UiRoot` retained tree under `AcDream.App/UI`, built
|
||||
from production LayoutDesc/DAT assets. The stable cross-stack seam is game
|
||||
state, ViewModels, and commands — not an `IPanelRenderer` backend swap.
|
||||
**Status:** **Phase D.2a shipped** — `AcDream.UI.Abstractions` + ImGui backend
|
||||
+ `VitalsPanel` gated on `ACDREAM_DEVTOOLS=1`. Backend pivoted from
|
||||
`Hexa.NET.ImGui` to `ImGui.NET` + `Silk.NET.OpenGL.Extensions.ImGui` during
|
||||
first-light integration — see the pivot note below. Phase D.2b (custom
|
||||
retail-look backend) remains design-only.
|
||||
**Owner:** lead engineer (erik) + Claude
|
||||
|
||||
Captures the UI strategy agreed via discussion on 2026-04-24. Documents
|
||||
|
|
@ -53,12 +53,12 @@ panel, skills, spellbook, fellowship, allegiance, trade, options, map,
|
|||
quest log, tooltips — and a first-class plugin API so plugin authors can
|
||||
ship their own panels.
|
||||
|
||||
## Current strategy: two coexisting stacks, shared state contracts
|
||||
## Strategy: two-phase, one stable interface
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Developer UI ─ ImGui + IPanelRenderer │
|
||||
│ Gameplay UI ─ UiRoot retained widgets │
|
||||
│ UI backend ─ ImGui (short-term) │ ← swappable
|
||||
│ ─ Custom retail (later) │
|
||||
├─────────────────────────────────────────┤
|
||||
│ ViewModels + Commands (per panel) │ ← stable contracts
|
||||
├─────────────────────────────────────────┤
|
||||
|
|
@ -66,16 +66,14 @@ ship their own panels.
|
|||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **Developer stack:** `AcDream.UI.Abstractions` panels render through
|
||||
`IPanelRenderer` on ImGui. This stack is permanent for diagnostics, packet
|
||||
inspection, settings development, and other devtools.
|
||||
- **Gameplay stack:** `UiHost` owns a retained `UiRoot` tree. `LayoutImporter`
|
||||
builds retail windows from LayoutDesc/DAT assets and focused `gm*UI`-style
|
||||
controllers bind runtime values and actions.
|
||||
- **Shared seam:** both stacks consume the same session state, ViewModels, and
|
||||
command/event services. A panel is ported to retained UI by binding those
|
||||
shared models to its DAT-authored tree, not by implementing
|
||||
`IPanelRenderer` a second time.
|
||||
- **Near term:** wire an ImGui-based overlay so we can iterate on game
|
||||
logic fast — chat that actually sends + receives, inventory that
|
||||
reflects real state, vitals bar that reads real HP/stam/mana. Looks
|
||||
like a debugger, that's fine for now.
|
||||
- **Later:** replace the visual layer with a custom toolkit that uses
|
||||
retail dat assets (icons, panels, fonts) and matches retail's feel.
|
||||
- **Always:** ViewModels and Commands stay stable across the swap. The
|
||||
game logic never learns which backend is drawing it.
|
||||
|
||||
## Choice: Hexa.NET.ImGui for the short-term backend
|
||||
|
||||
|
|
@ -160,7 +158,7 @@ public sealed record EquipItemCmd(uint ItemGuid, EquipSlot Slot);
|
|||
Dispatched to an `ICommandBus` that routes to the appropriate subsystem
|
||||
(`WorldSession.SendSelect`, `ChatService.Send`, etc.).
|
||||
|
||||
### Layer 3a — ImGui developer backend
|
||||
### Layer 3 — UI backend
|
||||
|
||||
New module: `src/AcDream.UI.ImGui/`. References `AcDream.UI.Abstractions`.
|
||||
|
||||
|
|
@ -170,34 +168,11 @@ New module: `src/AcDream.UI.ImGui/`. References `AcDream.UI.Abstractions`.
|
|||
per frame.
|
||||
- Keyboard / mouse / focus handled by ImGui natively.
|
||||
|
||||
This layer remains the permanent devtools surface. It is not the production
|
||||
retail gameplay renderer.
|
||||
Later: `src/AcDream.UI.Retail/` references the same `AcDream.UI.Abstractions`
|
||||
and implements the same `IPanel` / `IPanelHost` interfaces — but draws
|
||||
with our own retained-mode toolkit + retail dat assets.
|
||||
|
||||
### Layer 3b — retained retail gameplay UI
|
||||
|
||||
`src/AcDream.App/UI/` contains the GL-free widget tree, LayoutDesc importer,
|
||||
window runtime, and panel controllers. Rendering dependencies enter through
|
||||
small sprite/font/viewport resolver seams. Controllers consume the same
|
||||
ViewModels and command services used by the ImGui panels, while binding
|
||||
behavior to existing retail element ids instead of procedurally redrawing the
|
||||
panel through `IPanelRenderer`.
|
||||
|
||||
## Plugin UI API
|
||||
|
||||
The shipped plugin-facing gameplay UI contract is
|
||||
`AcDream.Plugin.Abstractions.IUiRegistry.AddMarkupPanel`: a plugin provides
|
||||
KSML-style markup and a binding object; the host builds it into the retained
|
||||
`UiRoot` tree. `IPanel`/`IPanelRenderer` remains a first-party developer-panel
|
||||
contract and is intentionally not referenced by `Plugin.Abstractions`.
|
||||
|
||||
This makes plugin gameplay panels independent of ImGui while allowing them to
|
||||
share the retained input, window, and DAT-sprite runtime. Registrations made
|
||||
before the GL host exists are buffered. In builds where retail UI is disabled,
|
||||
they remain registered but have no gameplay surface; the long-term release
|
||||
configuration enables retained gameplay UI.
|
||||
|
||||
The following was the original pre-D.2b proposal and remains historical
|
||||
context, not the shipped plugin contract:
|
||||
## Plugin API (must be backend-agnostic)
|
||||
|
||||
```csharp
|
||||
public interface IPanel
|
||||
|
|
@ -252,34 +227,32 @@ walk around / take damage / regen.
|
|||
to equip, drag target for future move.
|
||||
- `CharacterPanel` — attributes, skills, XP.
|
||||
|
||||
### Sprint 3 — Plugin API hardening (superseded shape)
|
||||
### Sprint 3 — Plugin API hardening
|
||||
|
||||
- Document the `IPanel` contract.
|
||||
- The shipped route is `IUiRegistry.AddMarkupPanel`, not plugin-owned
|
||||
`IPanel` implementations.
|
||||
- Confirm plugins can subscribe to game events and expose retained markup
|
||||
bindings without referencing App or ImGui assemblies.
|
||||
- Port the smoke plugin to register a demo panel via the API.
|
||||
- Confirm plugins can subscribe to game events AND draw UI through the
|
||||
same interface.
|
||||
|
||||
### Sprint 4+ — More panels
|
||||
|
||||
Spellbook, allegiance, fellowship, trade, map, quest log, options.
|
||||
Continue to expand `InventoryPanel` with drag-drop, split, appraise.
|
||||
|
||||
### D.2b — retained retail-look gameplay UI (shipped, expanding panel by panel)
|
||||
### Later — Custom retail-look backend
|
||||
|
||||
`AcDream.App/UI` imports LayoutDesc trees and draws retail DAT assets through
|
||||
the retained `UiRoot` runtime. ImGui remains available for devtools; shared
|
||||
ViewModels and commands prevent duplicate game-state logic.
|
||||
`AcDream.UI.Retail` implements `IPanelRenderer` with our own toolkit +
|
||||
retail dat assets. Swap panels one at a time. ImGui overlay remains for
|
||||
devtools.
|
||||
|
||||
## Non-goals for this first pass
|
||||
|
||||
- **Not** going to theme ImGui to look retail. Waste of effort when we'll
|
||||
swap the backend. Devtools aesthetic is fine.
|
||||
- **Not** byte-porting Keystone internals that are unavailable. Observable
|
||||
widget behavior is recovered from the named client call sites, DAT
|
||||
properties, and live retail evidence.
|
||||
- **Not** hand-authoring first-party gameplay layouts where retail LayoutDesc
|
||||
data exists. KSML-style markup remains the plugin/extension layout surface.
|
||||
- **Not** porting retail's widget code. We use their ASSETS later, not
|
||||
their widget implementation.
|
||||
- **Not** building layout DSL / XAML-like markup. Panels register and
|
||||
draw procedurally, same as ImGui.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
|
@ -298,13 +271,13 @@ ViewModels and commands prevent duplicate game-state logic.
|
|||
**Mitigation:** code review every addition; if a feature only exists
|
||||
in ImGui and the retail toolkit can't express it, don't add it.
|
||||
|
||||
- **Risk:** ImGui and retained controllers grow separate game-state truth.
|
||||
**Mitigation:** one session model/ViewModel and one command path per
|
||||
subsystem; each surface is only a projection.
|
||||
- **Risk:** Swap to custom backend breaks a dozen panels simultaneously.
|
||||
**Mitigation:** swap one panel at a time, keep ImGui rendering the
|
||||
rest until all are ported.
|
||||
|
||||
- **Risk:** Plugin markup relies on App-only widget details.
|
||||
**Mitigation:** keep `IUiRegistry` BCL-only, resolve bindings by contract,
|
||||
and use a smoke plugin as the retained-runtime canary.
|
||||
- **Risk:** Plugin authors write panels that only work in ImGui.
|
||||
**Mitigation:** smoke plugin registers a panel early; use it as a
|
||||
canary whenever backend changes.
|
||||
|
||||
- **Risk:** Hexa.NET.ImGui stops being maintained.
|
||||
**Mitigation:** integration is small (<100 LOC), switching to
|
||||
|
|
|
|||
|
|
@ -92,35 +92,6 @@ Goal: make every bad movement outcome explainable.
|
|||
- Build real-DAT fixture capture for known walls, building ledges, rooftops,
|
||||
slopes, landblock seams, and dungeon entrances.
|
||||
|
||||
Current shipped slices:
|
||||
|
||||
- 2026-04-30: cdb + TTD retail-observer toolchain (`tools/pdb-extract/`,
|
||||
`tools/ttd-record.ps1`, `tools/ttd-query.ps1`) with PDB pairing checker
|
||||
and ring-buffer trace replay. The "retail observer harness" line item.
|
||||
- 2026-04 (pre-L.2 rename): `ACDREAM_DUMP_MOVE_TRUTH` paired
|
||||
outbound/server-echo dumper in `GameWindow` covers outbound packet
|
||||
fields + server echo + correction delta with cell-id mismatch.
|
||||
- Pre-L.2: scenario-specific dumps `ACDREAM_DUMP_MOTION`,
|
||||
`ACDREAM_DUMP_STEEP_ROOF`, `ACDREAM_DUMP_STEPUP`,
|
||||
`ACDREAM_DUMP_EDGE_SLIDE` for the codepaths hit during prior bug chases.
|
||||
- 2026-05-12 (slice 1): general-purpose probes via new
|
||||
`AcDream.Core.Physics.PhysicsDiagnostics` static class.
|
||||
`ACDREAM_PROBE_RESOLVE` emits one `[resolve]` line per
|
||||
`PhysicsEngine.ResolveWithTransition` call (input/output pos+cell,
|
||||
ok-vs-partial, grounded-in, contact-plane status, wall normal if hit,
|
||||
walkable polygon valid, moving entity id).
|
||||
`ACDREAM_PROBE_CELL` emits one `[cell-transit]` line per
|
||||
`PlayerMovementController.CellId` change with old→new + position +
|
||||
reason tag (`resolver`/`teleport`). Both flippable live via the
|
||||
DebugPanel "Diagnostics" section — checkbox toggles take effect on
|
||||
the next resolve, no relaunch required.
|
||||
|
||||
Remaining L.2a work: contact-plane probe (general, not just steep-roof),
|
||||
ShadowObjectRegistry hit log ("you collided with entity X"), water probe,
|
||||
real-DAT fixture-capture pipeline, and folding the older sticky-at-startup
|
||||
`ACDREAM_DUMP_*` flags into `PhysicsDiagnostics` for unified runtime
|
||||
toggling.
|
||||
|
||||
### L.2b - Movement Wire / Contact Authority
|
||||
|
||||
Goal: stop sending movement packets that claim more certainty than the local
|
||||
|
|
@ -169,41 +140,6 @@ fallback.
|
|||
- Audit `Setup.Radius` and cylinder fallback behavior against retail before
|
||||
relying on them for conformance.
|
||||
|
||||
Current sub-direction (revised 2026-05-13 evening after slice 1 + 1.5
|
||||
shipped and Holtburg-doorway capture analyzed — third reframe):
|
||||
L.2d as scoped ("shape fidelity: Sphere / CylSphere / Building Objects")
|
||||
is **essentially closed at the Holtburg site that motivated this phase**.
|
||||
Building BSP collision works correctly — the slice-1.5 probe captured
|
||||
real triangles in plausible world positions for `gfxObj=0x01000A2B` with
|
||||
`bspR=13.99m`. The 121 wall hits the L.2a probe attributed to
|
||||
`obj=0xA9B47900` were **side effects of the player already being pushed
|
||||
back by a separate Door cylinder entity** at the same doorway threshold.
|
||||
|
||||
The actual blocker is a server-spawned **Door** entity — Setup
|
||||
`0x020019FF` named `"Door"` — that ACE places at each Holtburg-town
|
||||
building threshold (five doors total observed across `0xA9B40029`,
|
||||
`0xA9B40154`, `0xA9B40155`). It registers as a Cylinder shadow entry
|
||||
via the server-spawn path; its Cylinder collision blocks the player
|
||||
walking into the doorway. That's **door-state handling**, a different
|
||||
class of problem from L.2d's shape-fidelity scope — it touches network
|
||||
(`CreateObject` PhysicsState bits), interaction (Use action on door
|
||||
entity), animation (door open/close), and collision-state-toggle.
|
||||
|
||||
Recommend: **leave L.2d in "watch-and-wait" mode** with slice 1's probe
|
||||
infrastructure in place. No more L.2d slices until a NEW shape-fidelity
|
||||
bug is observed at a different site (dungeon walls, stairs, roofs) with
|
||||
the probe-armed client. The door-state work becomes its own sub-phase
|
||||
(probably nested under B.4 interaction or filed as a new L.2 sub-phase
|
||||
like L.2g) scoped separately.
|
||||
|
||||
Full slice 1 + 1.5 handoff:
|
||||
[docs/research/2026-05-13-l2d-slice1-shipped-handoff.md](../research/2026-05-13-l2d-slice1-shipped-handoff.md).
|
||||
Design spec (now mostly historical, framing was wrong but probe
|
||||
infrastructure shipped from it):
|
||||
[docs/superpowers/specs/2026-05-13-l2d-cbuildingobj-collision-design.md](../superpowers/specs/2026-05-13-l2d-cbuildingobj-collision-design.md).
|
||||
Predecessor L.2a handoff:
|
||||
[docs/research/2026-05-12-l2a-shipped-l2d-handoff.md](../research/2026-05-12-l2a-shipped-l2d-handoff.md).
|
||||
|
||||
### L.2e - Cell Ownership: Outdoor Seams, CELLARRAY, cell_bsp
|
||||
|
||||
Goal: the resolver knows which cell owns the movement and which adjacent cells
|
||||
|
|
@ -228,62 +164,6 @@ client sees when observing acdream.
|
|||
- Require conformance notes in tests or research docs for every AC-specific
|
||||
algorithm ported under L.2.
|
||||
|
||||
### L.2g - Dynamic PhysicsState Toggling
|
||||
|
||||
Goal: server-driven post-spawn state changes (chiefly `ETHEREAL` flips) are
|
||||
honored by the local collision stack.
|
||||
|
||||
Triggered 2026-05-12 evening by the L.2d slice 1.5 trace: the Holtburg
|
||||
doorway blocker is a closed Door entity (Setup `0x020019FF`) whose
|
||||
`PhysicsState.Ethereal` bit flips when the player Uses the door. The L.2d
|
||||
shape-fidelity work doesn't cover this — the door's collision shape is
|
||||
already correct; what's missing is honoring the *runtime* state change.
|
||||
|
||||
Scope is intentionally narrow:
|
||||
|
||||
- Parse inbound `GameMessageSetState (opcode 0xF74B)`.
|
||||
- Plumb the new `PhysicsState` value into `ShadowObjectRegistry`'s cached
|
||||
per-entity state so the existing `CollisionExemption.IsExempt(...)` check
|
||||
sees the up-to-date bits.
|
||||
- Verify the Holtburg inn-door scenario: walk into doorway → blocked, Use
|
||||
door → door swings open AND player can walk through, auto-close after
|
||||
30s → door closes AND player is blocked again.
|
||||
- Confirm the existing `UpdateMotion` pipeline drives `(NonCombat, On/Off)`
|
||||
on non-creature entities (door swing animation). If not, one-line fix.
|
||||
|
||||
Excluded from L.2g scope (deferred):
|
||||
|
||||
- Door-specific UX polish: "door is locked" sound, creature-AI bump-open.
|
||||
- Any Door-specific class hierarchy — generic state-flip infrastructure
|
||||
is enough; doors are the verification scenario, not a privileged case.
|
||||
|
||||
Lane: informal sixth lane "dynamic state." The existing five-lane table
|
||||
treats per-entity state as static-after-spawn; L.2g makes it dynamic.
|
||||
|
||||
Full design spec:
|
||||
[docs/superpowers/specs/2026-05-12-l2g-dynamic-physicsstate-design.md](../superpowers/specs/2026-05-12-l2g-dynamic-physicsstate-design.md).
|
||||
|
||||
M1 critical path: this slice unblocks the *"open the inn door"* demo
|
||||
scenario.
|
||||
|
||||
Current shipped slice (2026-05-12):
|
||||
|
||||
| Commit | Subject |
|
||||
|---|---|
|
||||
| `2459f28` | `feat(phys L.2g slice 1): inbound SetState (0xF74B) parser` |
|
||||
| `d538915` | `feat(phys L.2g slice 1): ShadowObjectRegistry.UpdatePhysicsState` |
|
||||
| `536a608` | `feat(phys L.2g slice 1): WorldSession dispatches SetState (0xF74B) + hex probe` |
|
||||
| `108e386` | `feat(phys L.2g slice 1): GameWindow routes SetState + extends [entity-source] log` |
|
||||
|
||||
Slice 1 is CODE-COMPLETE: parser + registry mutator + WorldSession
|
||||
dispatcher + GameWindow subscriber. 6 new tests pass (3 parser + 3
|
||||
registry). Build clean. Per-commit + final integration code reviews
|
||||
all approved. **Visual verification deferred to Phase B.4b** — the
|
||||
inbound SetState chain can't fire at runtime until B.4b finishes the
|
||||
outbound Use handler. See
|
||||
[docs/research/2026-05-12-l2g-slice1-shipped-handoff.md](../research/2026-05-12-l2g-slice1-shipped-handoff.md)
|
||||
for full evidence + the 4 minor + 1 Important review notes.
|
||||
|
||||
## Named Retail Anchors
|
||||
|
||||
Primary source: `docs/research/named-retail/acclient_2013_pseudo_c.txt`.
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
# Phase N.5 perf baseline
|
||||
|
||||
**Captured:** 2026-05-08, against N.5 head (post-Task 12) on local machine.
|
||||
**Method:** `ACDREAM_WB_DIAG=1` + character at Holtburg spawn position +
|
||||
roaming. Numbers below are 5-second window medians from `[WB-DIAG]`.
|
||||
|
||||
## Holtburg courtyard (steady state)
|
||||
|
||||
| Metric | N.5 measured | N.4 (estimated*) | Gate |
|
||||
|---|---|---|---|
|
||||
| CPU dispatcher (median) | **1227 µs / frame** | ≥2500 µs / frame | ≤70% of N.4 → **PASS** |
|
||||
| CPU dispatcher (p95) | 1303 µs / frame | — | — |
|
||||
| GPU rendering (median) | unmeasured (see below) | — | within ±10% — **DEFERRED** |
|
||||
| `drawsIssued` per 5s | 4.85M (= 1662 groups × ~580 fps) | far higher per frame | — |
|
||||
| `drawsIssued` per pass (CPU GL calls) | **2** (1 opaque + 1 transparent indirect) | ~hundreds per pass | ≤5 → **PASS** |
|
||||
| `groups` (working set) | 1662 | ~similar | sanity |
|
||||
| Frame rate (inferred) | ~810 fps | ~100-200 fps | substantial uplift |
|
||||
|
||||
*N.4 baseline NOT measured directly in this run. The "≥2500 µs / frame"
|
||||
estimate assumes N.4's per-group glBindTexture + glBindBuffer +
|
||||
glDrawElementsInstancedBaseVertexBaseInstance hot path costs ≥1.5 µs per
|
||||
group and N.4 has ~1700 groups in this scene, putting the GL portion alone
|
||||
at ~2.5 ms before adding the entity-walk overhead. N.5's measurement
|
||||
includes ALL dispatcher work (entity walk + group bucketing + 3 SSBO
|
||||
uploads + 2 indirect calls + state changes) at 1230 µs total — comfortably
|
||||
half of the lower bound estimate.
|
||||
|
||||
## Acceptance gates (spec §8.3)
|
||||
|
||||
- [x] **Visual identity to N.4** — confirmed at Task 10 USER GATE: Holtburg
|
||||
courtyard renders identical, no missing entities, no z-fighting, no
|
||||
exploded parts.
|
||||
- [x] **CPU dispatcher time ≤ 70% of N.4** — N.5 measures 1.23 ms/frame
|
||||
median; estimated N.4 ≥2.5 ms/frame; **comfortably under 70%**.
|
||||
- [ ] **GPU rendering time within ±10% of N.4** — DEFERRED. The
|
||||
`GL_TIME_ELAPSED` query polling never reports `avail != 0` in our
|
||||
single-frame poll loop; the driver hasn't finalized the result by the
|
||||
time we check. The fix is double-buffering (issue queryA on frame N,
|
||||
read result on frame N+2). N.6 perf polish item.
|
||||
- [x] **`drawsIssued` ≤ 5 per pass (CPU GL calls)** — exactly 2 indirect
|
||||
calls per frame regardless of scene size.
|
||||
- [x] **All tests green** — 70/70 in
|
||||
`FullyQualifiedName~Wb|FullyQualifiedName~MatrixComposition`.
|
||||
8 pre-existing failures in `MotionInterpreter` / `BSPStepUp` /
|
||||
`PositionManager` / `PlayerMovementController` / `Dispatcher` are
|
||||
carry-forward from before N.5 and unrelated to rendering.
|
||||
- [N/A] **`ACDREAM_USE_WB_FOUNDATION=0` still works** — escape hatch
|
||||
formally retired in N.5 ship amendment. `InstancedMeshRenderer`,
|
||||
`StaticMeshRenderer`, and `WbFoundationFlag` deleted. Missing
|
||||
bindless throws `NotSupportedException` at startup with a clear
|
||||
error message. No fallback path.
|
||||
|
||||
## Visual verification (Task 14)
|
||||
|
||||
- [x] **Holtburg courtyard** — PASS at Task 10 USER GATE.
|
||||
- [ ] **Foundry interior / dense static-object scene** — TODO Task 14.
|
||||
- [ ] **Indoor → outdoor cell transition** — TODO Task 14.
|
||||
- [ ] **Drudge / character close-up (Issue #47 close-detail mesh)** — TODO Task 14.
|
||||
- [ ] **Magic content (Decision 2 additive fallback check)** — TODO Task 14.
|
||||
- [ ] **Long-session sanity** — DEFERRED (N.6 watchlist; not load-bearing for ship).
|
||||
|
||||
## Open follow-ups for N.6
|
||||
|
||||
1. **GPU timer query double-buffering** — the current single-frame poll
|
||||
pattern never sees `QueryResultAvailable=true`. Issue queryA on frame N,
|
||||
queryB on frame N+1, read queryA on frame N+2. ~30 lines of state.
|
||||
2. **Direct N.4 vs N.5 perf comparison** — re-run with `git checkout`ed N.4
|
||||
SHIP (`c445364`) for a side-by-side measurement. Not load-bearing but
|
||||
useful for N.6 ship message.
|
||||
3. **Persistent-mapped buffers** — Decision 7 deferral. If profiling shows
|
||||
the per-frame `glBufferData` cost is the residual hot spot, layer it on
|
||||
top of the modern path.
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
# Phase N.5b — terrain perf baseline
|
||||
|
||||
**Captured:** 2026-05-09 at Holtburg town dueling field, radius=5, ~30s standstill.
|
||||
|
||||
## Methodology
|
||||
|
||||
Same build (commit at perf measurement: `da56063`), `ACDREAM_WB_DIAG=1`. The build
|
||||
included a TEMPORARY `ACDREAM_LEGACY_TERRAIN=1` env-var toggle (since retired in T9
|
||||
deletion of the legacy renderer) that routed Draw through the legacy renderer for
|
||||
direct comparison. Both renderers were constructed and fed AddLandblock / RemoveLandblock
|
||||
in parallel; only one drew per frame; the same Stopwatch wrapped whichever ran.
|
||||
|
||||
## Numbers
|
||||
|
||||
| Renderer | cpu_us median | cpu_us p95 | draws/frame | Visible LBs |
|
||||
|---|---|---|---|---|
|
||||
| **Legacy** (`TerrainChunkRenderer`) | 1.5 | 3.0 | 1 (1 chunk) | 132-143 (whole chunk) |
|
||||
| **Modern** (`TerrainModernRenderer`) | 6.4-7.0 | 9-14 | ~36-51 | 36-51 (per-LB cull) |
|
||||
|
||||
(Legacy `draws=1` because its 16×16-LB chunking collapses radius=5's 121 visible
|
||||
landblocks into a single chunk, dispatched as one `glDrawElements`. Modern issues
|
||||
one `glMultiDrawElementsIndirect` with N=36-51 sub-commands.)
|
||||
|
||||
## Acceptance criterion
|
||||
|
||||
The N.5b spec acceptance criterion 5 read: "CPU dispatcher time at radius=5 ≥10%
|
||||
lower than today's per-LB-binds path." The captured numbers show modern is ~4×
|
||||
HIGHER on CPU at radius=5. **The criterion was wrong** — at radius=5 in Holtburg,
|
||||
legacy's chunked path was already collapsed to one draw call. The architectural
|
||||
wins of multi-draw indirect manifest at higher chunk counts (A.5 territory).
|
||||
|
||||
The spec is amended via this doc: ship N.5b on visual identity + structural
|
||||
correctness rather than CPU savings at radius=5.
|
||||
|
||||
## Architectural wins of the modern path (real, even when CPU is higher)
|
||||
|
||||
1. **Zero `glBindTexture` per frame.** Bindless atlas handles are made resident
|
||||
once at startup; the modern shader samples via `sampler2DArray(uvec2 handle)`.
|
||||
Legacy issued 2 `glBindTexture(Texture2DArray)` calls per frame.
|
||||
|
||||
2. **Constant-cost dispatch.** As A.5 raises the streaming radius (next phase),
|
||||
the visible chunk count grows. Legacy scales linearly: at radius=10 (4× chunks)
|
||||
it's 4 `glDrawElements` calls; at radius=15 (≥9 chunks) it's 9+ calls. Modern
|
||||
stays at exactly 1 `glMultiDrawElementsIndirect` regardless.
|
||||
|
||||
3. **Per-LB frustum culling.** Legacy culled at chunk granularity (16×16 LBs);
|
||||
modern culls per-LB. At a typical Holtburg view, ~36-51 of 132 loaded LBs are
|
||||
actually visible; legacy drew the entire 132-LB chunk (3.5× the visible work
|
||||
pushed to GPU vertex/fragment stages, even though CPU dispatch was cheap).
|
||||
|
||||
## Why modern's CPU was higher at radius=5
|
||||
|
||||
Per-frame work in modern (in microseconds-ish budget on this scene):
|
||||
- Walk all loaded slots checking visibility (~120 slots) → AABB test each
|
||||
- Build DEIC array (51 entries × 20 bytes = 1020 bytes)
|
||||
- `glBufferSubData(DRAW_INDIRECT_BUFFER, ...)` — driver memcpy
|
||||
- 2× `glProgramUniform2(..., handle.low, handle.high)` for atlas handles
|
||||
- `glBindVertexArray` + `glMemoryBarrier(GL_COMMAND_BARRIER_BIT)` + `glMultiDrawElementsIndirect`
|
||||
|
||||
Legacy's per-frame work:
|
||||
- Bind 2 textures
|
||||
- Bind one VAO (the chunk)
|
||||
- One `glDrawElements`
|
||||
|
||||
The DEIC array build + buffer upload alone is ~3-5µs at radius=5 on this hardware,
|
||||
which is the bulk of the modern overhead. At higher radius, this overhead amortizes:
|
||||
the buffer is similar size, but the alternative (legacy's N draws) grows.
|
||||
|
||||
## Follow-up work
|
||||
|
||||
- **A.5 (next phase)** will exercise the higher-radius case where modern wins.
|
||||
Capture a fresh baseline at radius=8 / 10 once A.5 lands.
|
||||
- **N.6 perf polish** can investigate persistent-mapped buffers for the indirect
|
||||
buffer, which would eliminate the per-frame `glBufferSubData`. Likely small win
|
||||
at radius=5 (single ~1KB upload), bigger at higher radii.
|
||||
- **GPU-side culling** (compute shader generating the DEIC array directly into
|
||||
the indirect buffer) eliminates the CPU slot walk + DEIC build entirely. N.6 or
|
||||
later territory; only worth it if profiling shows the CPU walk is hot.
|
||||
|
||||
## Lessons captured to memory
|
||||
|
||||
`memory/project_phase_n5b_state.md` records the high-value gotchas surfaced
|
||||
during N.5b implementation. Three particularly bitable ones:
|
||||
|
||||
1. **`uniform sampler2DArray` + `glProgramUniformHandleARB` is unreliable.** Some
|
||||
drivers (NVIDIA Windows in this case) reject the combination with
|
||||
`GL_INVALID_OPERATION`. Use the `uniform uvec2` + `sampler2DArray(handle)`
|
||||
constructor pattern instead — N.5's mesh_modern uses this, and N.5b's
|
||||
terrain_modern adopted it after the black-terrain regression.
|
||||
|
||||
2. **`MaybeFlushTerrainDiag` underflow.** A naive median calc (`copy[N - nz/2]`)
|
||||
underflows to `copy[N]` when only one sample has been recorded. Use
|
||||
`copy[N - 1 - (nz - 1) / 2]` instead.
|
||||
|
||||
3. **Visual gate must actually be visually confirmed.** "Go" doesn't mean
|
||||
"verified." During N.5b's gate the user said "go" without launching, which
|
||||
masked the black-terrain regression for hours. The gate must include the
|
||||
user reporting actual visual confirmation, not assent to proceed.
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
# Performance Tiers 2 + 3 — Future Roadmap
|
||||
|
||||
**Created:** 2026-05-10 during Phase A.5 polish.
|
||||
**Status:** Future planning — not for current execution.
|
||||
**Context:** A.5 shipped two-tier streaming with the entity dispatcher landing at ~3.5ms median (post-Bug-A and Bug-B fixes). Tier 1 (entity-classification cache) lands as A.5 polish and brings the dispatcher inside the 2.0ms spec budget. Tiers 2 + 3 are the "next big perf wins" beyond Tier 1.
|
||||
|
||||
---
|
||||
|
||||
## Background — why this exists
|
||||
|
||||
Discussion captured 2026-05-10: user observed 200-240 FPS at radius=12 on a Radeon 9070 XT @ 1440p and asked why an "old game like AC" doesn't deliver Unreal-level (1000+ FPS) on this hardware.
|
||||
|
||||
The honest answer: the bottleneck is *architectural*, not hardware. The CPU is single-threaded and rebuilds the entire draw plan from scratch every frame. Modern engines pre-bake static-world batches at content-cook time and rebuild only what changes.
|
||||
|
||||
AC's design — server-spawned per-entity world streamed at runtime — doesn't naturally batch the way Unreal's pre-cooked content does. Closing the gap requires backporting modern techniques while preserving AC's data model. Tiers 2 and 3 are that backporting work.
|
||||
|
||||
---
|
||||
|
||||
## Tier 2 — Static/dynamic split with persistent groups
|
||||
|
||||
**Estimated effort:** ~10-15 days (2-week phase).
|
||||
**Estimated win:** entity dispatcher ~3.5ms → **~0.5-1ms median** at radius=12.
|
||||
**Total frame time:** ~4-5ms → **~2-3ms = 400-600 FPS at standstill.**
|
||||
|
||||
### The core idea
|
||||
|
||||
Today, `WbDrawDispatcher._groups` (the dictionary of "(mesh + texture + blend) → list of instances to draw") is cleared and rebuilt from scratch every frame.
|
||||
|
||||
For trees, rocks, buildings, and other static entities (~95% of the world), the answer is identical every frame forever. Tier 2 makes the static-group instance buffers **persistent GPU-resident data**, just like Unreal's pre-baked world. The CPU only orchestrates "which groups are visible" per frame.
|
||||
|
||||
### Architectural shift
|
||||
|
||||
```csharp
|
||||
class StaticInstancedGroup
|
||||
{
|
||||
public GroupKey Key;
|
||||
public Matrix4x4[] Matrices; // grown as entities spawn
|
||||
public BitArray ActiveSlots; // for free-list reuse
|
||||
public bool NeedsGpuUpload; // dirty flag for delta upload
|
||||
public Dictionary<uint, int> EntityToSlot; // for despawn lookup
|
||||
public uint InstanceBufferOffset; // start of group's slice in global SSBO
|
||||
}
|
||||
```
|
||||
|
||||
**On entity spawn (atlas-tier static):** allocate a slot in each relevant group, write the matrix, mark dirty.
|
||||
|
||||
**On entity despawn:** free the slot, mark dirty.
|
||||
|
||||
**Per frame:**
|
||||
- Static groups: LB-cull each group (cheap). For visible groups, flag for draw. **No matrix copy. No list rebuild.**
|
||||
- Dynamic entities (~50 NPCs/players): today's per-frame walk-and-classify. Keeps the existing slow path for things that legitimately change every frame.
|
||||
- Upload only the dirty groups' matrix slices (delta upload, not full reupload).
|
||||
- Issue 2 multi-draw-indirect calls.
|
||||
|
||||
### Sub-decisions
|
||||
|
||||
**Frustum cull granularity at the group level:** at group level you can't reject individual instances; you draw the whole group or none of it. Two strategies:
|
||||
|
||||
- **Per-LB subgroups:** split each group into per-landblock subgroups. LB-frustum-culls reject subgroups whose LB is invisible. ~2K groups × ~5 LBs per group on average = ~10K subgroups. Each subgroup AABB cull is ~0.3 µs → ~3 ms per frame. Roughly a wash with today's per-entity cull.
|
||||
- **Per-instance GPU cull (Tier 3):** compute pre-pass on the GPU writes which instances are visible to a draw-indirect buffer. ~0.05ms CPU. The right long-term answer.
|
||||
|
||||
For Tier 2 alone, per-LB subgroups are the recommended approach — keep CPU culling, just at coarser granularity than per-entity.
|
||||
|
||||
**Dynamic entities crossing LB boundaries:** when an NPC walks across a landblock boundary, it stays in the same group key but its "spatial bucket" changes. Solution: dynamic entities are tracked in a single global "dynamic group" outside the per-LB structure; they don't need spatial bucketing because there are only ~50 of them.
|
||||
|
||||
**Palette override invalidation:** server event swaps an NPC's clothing color → group key changes. Treat as despawn-from-old + spawn-into-new. NPCs are dynamic so this just rebuckets them.
|
||||
|
||||
**Animation overrides on static entities:** static entities don't animate. Trees don't bend (foliage wave is a vertex shader effect, not a group-key change). Buildings don't move. So the static path never invalidates.
|
||||
|
||||
**EnvCell visibility:** dungeon entities are gated by per-cell visibility state. Need to track which group instances are tied to which cell, and during visibility cull, gate per-cell. Keep using existing `ParentCellId` field on WorldEntity.
|
||||
|
||||
**Streaming load/unload integration:** when an LB unloads, all its static entity matrices need to be removed from their groups. Free-list management. Matches existing `LandblockSpawnAdapter` lifecycle.
|
||||
|
||||
### Effort breakdown
|
||||
|
||||
| Task | Days |
|
||||
|---|---|
|
||||
| Design + invariants document | 2 |
|
||||
| Spawn-time slot allocator + free-list | 3 |
|
||||
| Per-frame visibility + dirty-flag delta upload | 2 |
|
||||
| Dynamic entity path (NPCs, projectiles) | 2 |
|
||||
| Invalidation (palette/ObjDesc events) | 2 |
|
||||
| EnvCell visibility integration | 1 |
|
||||
| Streaming load/unload integration | 1 |
|
||||
| Conformance testing | 2-3 |
|
||||
| **Total** | **~10-15 days** |
|
||||
|
||||
### Risks
|
||||
|
||||
- **Slot management bugs** = double-frees or leaks (entities draw at random positions — visible).
|
||||
- **Invalidation bugs** = stale matrices (entity teleports back to spawn point when palette changes).
|
||||
- **Dynamic entity tracking** adds complexity around the static/dynamic boundary.
|
||||
|
||||
### Mitigations
|
||||
|
||||
- **Conformance test:** render a fixed scene through both pipelines, compare draw output. Adds CI infrastructure.
|
||||
- **Per-frame validation in debug:** walk all groups, assert no orphan slots.
|
||||
- **Hash invariant test:** static entities should produce stable group keys frame-over-frame. Add a debug assertion that fires once per frame in Debug builds.
|
||||
|
||||
---
|
||||
|
||||
## Tier 3 — GPU-side culling (compute pre-pass)
|
||||
|
||||
**Estimated effort:** ~1 month (longer phase).
|
||||
**Estimated win:** entity dispatcher ~0.5-1ms (post-Tier-2) → **~0.05ms median.**
|
||||
**Total frame time:** ~2-3ms → **~1.5-2ms = 600-1000+ FPS at standstill.**
|
||||
|
||||
### The core idea
|
||||
|
||||
Today (and after Tier 2), the CPU does per-LB or per-subgroup frustum culling and tells the GPU which groups to draw.
|
||||
|
||||
Tier 3 moves per-instance frustum cull to the GPU via a compute shader pre-pass. The CPU just uploads "here are all 1M instance matrices" once; the GPU compute shader writes which ones are visible to a draw-indirect buffer; the rasterizer draws only those.
|
||||
|
||||
This is the level Unreal is at. With this, per-frame CPU work for the entity dispatcher becomes essentially "tell the GPU what to do" + a tiny scratch upload.
|
||||
|
||||
### Why Tier 3 needs Tier 2 first
|
||||
|
||||
Without Tier 2's persistent group structure, GPU culling has nothing stable to operate on. The compute shader needs an addressable "here are the static instances" buffer to read from; that buffer only exists after Tier 2.
|
||||
|
||||
### Sub-decisions to be made
|
||||
|
||||
**Compute shader API:** OpenGL 4.3+ compute shaders are sufficient. We're already at GL 4.3+ for bindless. No additional capability requirement.
|
||||
|
||||
**Indirect draw command generation:** the compute shader writes a `DrawElementsIndirectCommand[]` buffer per pass. Render thread issues `glMultiDrawElementsIndirect` reading from that buffer. No CPU readback.
|
||||
|
||||
**LOD selection:** opportunity to add per-instance LOD selection in the compute shader (distance-based mesh detail). Not needed for A.5's scope; could be a Tier 4 follow-up.
|
||||
|
||||
**Per-light shadow map culling:** if shadows ship, GPU culling extends naturally to per-light frustum cull. Significant win for shadow rendering.
|
||||
|
||||
### Effort breakdown
|
||||
|
||||
| Task | Days |
|
||||
|---|---|
|
||||
| Compute shader design + GLSL implementation | 4 |
|
||||
| Buffer layout coordination with Tier 2 | 2 |
|
||||
| Silk.NET compute dispatch integration | 3 |
|
||||
| Indirect command compaction logic | 4 |
|
||||
| LOD selection (optional, ~stretch) | 4 |
|
||||
| Validation: per-instance cull matches CPU cull within epsilon | 3 |
|
||||
| Conformance + regression testing | 5 |
|
||||
| **Total** | **~21-25 days, ~1 month** |
|
||||
|
||||
### Risks
|
||||
|
||||
- **GPU stalls** if the compute shader takes longer than expected (esp. on lower-end GPUs).
|
||||
- **Sync overhead** between compute pre-pass and rasterizer pass.
|
||||
- **Debugging difficulty** — GPU compute bugs are harder to diagnose than CPU bugs.
|
||||
|
||||
### Mitigations
|
||||
|
||||
- **Profile-driven design:** measure compute shader runtime on target hardware before committing.
|
||||
- **Fallback path:** keep CPU cull as a runtime-toggleable option (env var) so we can A/B compare.
|
||||
- **GPU debugging tools:** RenderDoc captures + frame-by-frame compute shader inspection.
|
||||
|
||||
---
|
||||
|
||||
## When to schedule these
|
||||
|
||||
**Tier 2:**
|
||||
- Best fit: dedicated 2-week phase after a SHIP cycle. Treat it like a Phase B/C/N (i.e., name it Phase A.6 or N.7).
|
||||
- Trigger: user wants to push radius beyond 12 (e.g., to 15 or 20 for true continent-scale horizon).
|
||||
- Trigger: user wants to add 100+ active NPCs in a city without dropping below 240Hz.
|
||||
|
||||
**Tier 3:**
|
||||
- Best fit: after Tier 2 has been live and stable for at least one cycle.
|
||||
- Trigger: shadow map work begins (GPU cull + shadow cull share the same compute pre-pass infrastructure).
|
||||
- Trigger: user wants 500+ FPS sustained for very-high-refresh scenarios (360Hz monitors, future hardware).
|
||||
|
||||
**Both:**
|
||||
- Don't bundle with other phases. These are dedicated perf phases with their own brainstorm + spec + plan + SHIP cycles.
|
||||
|
||||
---
|
||||
|
||||
## What's "free" or smaller (out of Tier 1/2/3 scope but worth noting)
|
||||
|
||||
- **Plumb `JobKind` properly through `BuildLandblockForStreaming`** (~30 min). Today's Bug A patch wastes worker-thread CPU on hydration that gets thrown away for far-tier. Cleaner code, slight CPU savings on worker.
|
||||
- **Eliminate `ToEntries` adapter allocation in `Draw`** (~15 min). Tiny win (~25 KB / frame). Could fold into Tier 1.
|
||||
- **Persistent-mapped indirect buffer** (~2 days). Today's `glBufferData` per frame becomes a pre-mapped persistent buffer. Marginal win on RDNA 4; meaningful on lower-end GPUs.
|
||||
- **Multi-thread mesh-build worker pool** (~1 day). 2.7s first-traversal horizon-fill drops to 0.7s with 4 workers. UX win on first walk-into-region.
|
||||
|
||||
These are good candidates for a "perf polish" mini-phase or to backfill into Tier 2.
|
||||
|
||||
---
|
||||
|
||||
## The architectural ceiling
|
||||
|
||||
Even with all three tiers, **a faithful AC client written in C# with bindless OpenGL tops out around 800-1500 FPS at radius=12 on RDNA 4 hardware**. Beyond that requires:
|
||||
|
||||
- Native C++ rendering core (eliminate .NET GC + JIT overhead)
|
||||
- DX12/Vulkan API (eliminate driver state validation)
|
||||
- Offline content cooking (eliminate runtime mesh/texture decode)
|
||||
|
||||
Each of those is a several-month undertaking and represents "becoming a different engine." The realistic target for acdream is 240-500 FPS at the user's monitor refresh, comfortably ahead of the visible-stutter threshold. Tier 1 + Tier 2 alone should deliver that for radius=12-15.
|
||||
|
||||
For "Unreal-level FPS at full quality," that's a different project.
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
# Phase N.6 slice 1 — perf baseline at Holtburg
|
||||
|
||||
**Created:** 2026-05-11.
|
||||
**Spec:** [docs/superpowers/specs/2026-05-11-phase-n6-slice1-design.md](../superpowers/specs/2026-05-11-phase-n6-slice1-design.md)
|
||||
**Measured against commit:** `25cb147` (Task 1 final — gpu_us fix + diag-gate symmetry follow-up)
|
||||
**Purpose:** Capture authoritative CPU+GPU dispatch numbers so the next-phase decision (slice 2 vs C.1.5 vs Tier 2) rests on real data.
|
||||
|
||||
---
|
||||
|
||||
## §1. Setup
|
||||
|
||||
- **Hardware:** Radeon RX 9070 XT
|
||||
- **Resolution:** 1440p (2560×1440)
|
||||
- **Quality preset:** High (default)
|
||||
- **Connection:** live ACE at `127.0.0.1:9000`
|
||||
- **Character:** `+Acdream` at Holtburg
|
||||
- **Sky / time:** clear midday (F7 → Noon, F10 → Clear)
|
||||
- **Build:** Debug
|
||||
- **Date measured:** 2026-05-11
|
||||
- **Environment overrides:** `ACDREAM_WB_DIAG=1`, `ACDREAM_STREAM_RADIUS=<per-run>`
|
||||
|
||||
Note: `ACDREAM_STREAM_RADIUS=N` forces N₁=N (all N near-tier landblocks at full detail).
|
||||
This is NOT the production A.5 default (N₁=4 / N₂=12), which was characterized in
|
||||
CLAUDE.md as comfortable 200–400 FPS at the default preset. These measurements
|
||||
characterize the scaling curve — what happens as near-tier radius grows — not current
|
||||
production behavior. FPS was not captured directly (no window-title screenshot per run);
|
||||
it can be derived from `(1e6 / total_frame_time_us)` but the dispatcher's `cpu_us` is
|
||||
only part of the frame (terrain, sky, particles, UI, GL submission overhead, and
|
||||
swap-buffer wait are not included).
|
||||
|
||||
## §2. Dispatch CPU / GPU numbers
|
||||
|
||||
Each cell records the median of the last 3 `[WB-DIAG]` lines from a ~30s stable window.
|
||||
`entSeen / entDrawn / groups / drawsIssued` are also from those lines (values per 5s bucket).
|
||||
FPS column omitted — not captured per the note above.
|
||||
|
||||
| Radius | Motion | cpu_us median | cpu_us p95 | gpu_us median | gpu_us p95 | entSeen (per 5s) | entDrawn (per 5s) | groups | drawsIssued (per 5s) |
|
||||
|--------|------------|---------------|------------|---------------|------------|------------------|-------------------|--------|----------------------|
|
||||
| 4 | standstill | 3,208 | 3,313 | 93 | 95 | 16.9M | 15.5M | 1,216 | 1.65M |
|
||||
| 4 | walking | 2,967 | 3,112 | 95 | 120 | 13.9M | 13.9M | 1,850 | 1.45M |
|
||||
| 8 | standstill | 6,732 | 7,199 | 126 | 130 | 19.8M | 19.8M | 333 | 218K |
|
||||
| 8 | walking | 6,572 | 6,927 | 96 | 113 | 18.1M | 18.0M | 534 | 245K |
|
||||
| 12 | standstill | 12,853 | 13,525 | 344 | 507 | 19.6M | 19.6M | 541 | 184K |
|
||||
| 12 | walking | 16,320 | 17,241 | 553 | 603 | 17.8M | 17.8M | 898 | 200K |
|
||||
|
||||
**Notable:** `meshMissing` counts at r4 standstill (~1.45M per 5s) drop to near-zero while
|
||||
walking. This suggests the static-entity slow path's mesh-load lifecycle has some delay
|
||||
before populating for newly-streamed content. Not fatal — doesn't affect rendered output —
|
||||
but worth a follow-up issue in `docs/ISSUES.md` if it persists in normal play.
|
||||
|
||||
## §3. Surface-format histogram
|
||||
|
||||
From `ACDREAM_DUMP_SURFACES=1` at radius=12, ~30s after enter-world.
|
||||
Output written to `%LOCALAPPDATA%\acdream\n6-surfaces.txt`.
|
||||
|
||||
- **Total unique GL textures:** 760
|
||||
- **Total bytes (sum of W×H×4):** 96,387,584 (~96.4 MB)
|
||||
|
||||
**Top 10 (W, H) dimension buckets:**
|
||||
|
||||
| Dimensions | Count | Share |
|
||||
|------------|-------|-------|
|
||||
| 128×128 | 236 | 31% |
|
||||
| 64×64 | 111 | 15% |
|
||||
| 256×256 | 102 | 13% |
|
||||
| 128×256 | 71 | 9% |
|
||||
| 64×128 | 69 | 9% |
|
||||
| 256×128 | 48 | 6% |
|
||||
| 128×64 | 39 | 5% |
|
||||
| 512×512 | 30 | 4% |
|
||||
| 8×8 | 18 | 2% |
|
||||
| 32×32 | 14 | 2% |
|
||||
|
||||
**Format distribution:**
|
||||
|
||||
| Format | Count | Share |
|
||||
|---------------|-------|-------|
|
||||
| RGBA8_DECODED | 760 | 100% |
|
||||
|
||||
All uploads land as RGBA8 regardless of source format (INDEX16, P8, DXT, BGRA, etc.
|
||||
all decode through `TextureHelpers` before upload). The source-format diversity is real
|
||||
but invisible to GL after the decode step.
|
||||
|
||||
**Top 10 (W, H, format) triples — atlas-opportunity input:**
|
||||
|
||||
Same as the dimension buckets above since there is only one format. The top-3 triples
|
||||
(128×128, 64×64, 256×256) cover 449 of 760 surfaces = **59%**.
|
||||
|
||||
**Atlas-opportunity score: 59%** of surfaces fall into the top-3 (W, H, format) triples.
|
||||
A conventional rule-of-thumb is that >30% concentration into the top buckets makes atlas
|
||||
packing worth the implementation cost for memory savings; this measurement is well above
|
||||
that. However, see §4 for why atlas is not the right next step despite the high score.
|
||||
|
||||
## §4. Conclusion + next-phase recommendation
|
||||
|
||||
### What the data shows
|
||||
|
||||
**The entity dispatcher is strongly CPU-bound.** At every radius, CPU dominates GPU by
|
||||
30–50×. At radius=12 standstill: 12.9 ms CPU vs 0.34 ms GPU. At radius=12 walking the
|
||||
ratio is 16.3 ms CPU vs 0.55 ms GPU. There is no GPU bottleneck.
|
||||
|
||||
**GPU is wildly under-utilized.** The highest gpu_us p95 observed is 603 µs at radius=12
|
||||
walking — against a 16,600 µs frame budget at 60 FPS. The GPU is working at roughly
|
||||
3.6% of its 60fps capacity for entity rendering alone. Even accounting for terrain, sky,
|
||||
particles, UI, and swap-buffer overhead, there is substantial headroom. The "GPU
|
||||
comfortable" threshold (gpu_us p95 < 8,000 µs) is not even close to being challenged.
|
||||
|
||||
**CPU grows more than linearly with N₁ (near-tier radius), but sublinearly with
|
||||
visible-LB count.** As N₁ grows from 4 → 8 → 12, median cpu_us grows from 3.2 ms →
|
||||
6.7 ms → 12.9 ms — roughly 1.0× → 2.1× → 4.0× the r4 baseline. The visible-LB count
|
||||
scales as `(2N+1)²`: 81 → 289 → 625, so CPU growth is sublinear in LB count (4.0×
|
||||
vs 7.7× expected if every LB cost the same). Frustum culling discards most far LBs
|
||||
early, but the outer per-LB walk still has to touch each one. The Tier 1 entity-
|
||||
classification cache (`EntityClassificationCache`, shipped as #53) wins on the inner
|
||||
loop (per-entity classification avoided on cache hits) but the outer walk dominates
|
||||
as N₁ grows. This is exactly what the Tier 2 plan (persistent groups) at
|
||||
`docs/plans/2026-05-10-perf-tiers-2-3-roadmap.md` addresses by eliminating the
|
||||
per-frame LB scan entirely.
|
||||
|
||||
**Radius=12 is not the production scenario.** `ACDREAM_STREAM_RADIUS=12` forces N₁=12
|
||||
(625 near LBs at full detail). The production A.5 default preset is N₁=4 / N₂=12 (81
|
||||
full-detail near + 544 terrain-only far), which CLAUDE.md already characterizes as
|
||||
comfortable 200–400 FPS at the default preset. The numbers above characterize the scaling
|
||||
curve for headroom analysis, not the experience a typical player sees.
|
||||
|
||||
**Atlas opportunity is high (59%) but the win is memory-only — and modest.** With 96 MB
|
||||
of textures and 59% in the top-3 dimension buckets, atlas consolidation would let the
|
||||
top buckets share single `Texture2DArray` objects rather than each surface owning its
|
||||
own 1-layer array. The primary wins of atlas — fewer sampler switches, fewer texture
|
||||
binds — are already near-zero because bindless textures are made resident once at upload
|
||||
and never bound per draw. The remaining win is the per-array metadata overhead × N
|
||||
surfaces, which is bounded but not dramatic given all surfaces are already power-of-two
|
||||
and same-format (RGBA8). Even on the optimistic side, the absolute memory saving is on
|
||||
the order of low-MB to ~10 MB, not a 40–50% halving. GPU is not bottlenecked on sampler
|
||||
switches or memory bandwidth (0.6 ms gpu_us p95 at radius=12 walking demonstrates this
|
||||
directly), so atlas adoption would cost 1–2 weeks of implementation risk for a memory
|
||||
saving the process doesn't currently need at 96 MB.
|
||||
|
||||
### Recommendation
|
||||
|
||||
**Primary: do C.1.5 next (PES emitter wiring — portals, chimneys, fireplaces).** Four
|
||||
reasons: (a) the production dispatcher is already comfortable at the default N₁=4 preset
|
||||
per the CLAUDE.md notes; (b) the two slice-2 items that were "conditional on baseline"
|
||||
data (atlas adoption and persistent-mapped buffers) are not justified — GPU is not
|
||||
bottlenecked; (c) C.1.5 fills a visible content gap that has been open since C.1 shipped
|
||||
and is in the roadmap queue ahead of N.6 slice 2; (d) C.1.5 stabilizes the particle path
|
||||
before any future shader migration work in slice 2 touches `particle.frag`. Starting
|
||||
point for C.1.5 scoping: `docs/plans/2026-04-27-phase-c1-pes-particles.md` lines 285–295.
|
||||
|
||||
**Secondary (after C.1.5 lands): N.6 slice 2 with reduced scope.** The baseline data
|
||||
justifies dropping atlas adoption and persistent-mapped buffers from slice 2 entirely.
|
||||
What remains is a ~1-day cleanup: retire orphan `mesh.frag` (verify zero callers post-N.5
|
||||
amendment), collapse dead `_handlesByOverridden` / `_handlesByPalette` legacy caches once
|
||||
their callers are confirmed gone, migrate `particle.frag` to bindless sampling after C.1.5
|
||||
stabilizes the path. Slice 2 is a cleanup sprint, not a performance phase.
|
||||
|
||||
**Tertiary option (if perf escalation becomes pressing): Tier 2 first.** The scaling
|
||||
curve (3.2 → 6.7 → 12.9 ms as N₁ grows 4 → 8 → 12) confirms the per-LB walk is the
|
||||
bottleneck — exactly what Tier 2's persistent-group structure at
|
||||
`docs/plans/2026-05-10-perf-tiers-2-3-roadmap.md` addresses. Not urgent at the current
|
||||
default N₁=4; worth revisiting if a future quality preset wants N₁=8 as default or if the
|
||||
200–400 FPS range at N₁=4 shrinks after more content is streamed.
|
||||
|
||||
**Decision rule for revisiting:** if future measurement at the default preset shows
|
||||
cpu_us median > 5,000 µs or gpu_us p95 > 8,000 µs, re-open the escalation question.
|
||||
Otherwise, hold the C.1.5 → reduced-slice-2 sequence.
|
||||
|
||||
## §5. Reproducing the measurements
|
||||
|
||||
Raw `[WB-DIAG]` output from each run was inspected live during measurement and the
|
||||
median of the last three steady-state lines from each scenario was transcribed into §2.
|
||||
The raw launch logs were not preserved — the captured medians in §2 are the canonical
|
||||
record. To reproduce on the same hardware:
|
||||
|
||||
```powershell
|
||||
$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
|
||||
$env:ACDREAM_LIVE = "1"
|
||||
$env:ACDREAM_TEST_HOST = "127.0.0.1"
|
||||
$env:ACDREAM_TEST_PORT = "9000"
|
||||
$env:ACDREAM_TEST_USER = "testaccount"
|
||||
$env:ACDREAM_TEST_PASS = "testpassword"
|
||||
$env:ACDREAM_WB_DIAG = "1"
|
||||
$env:ACDREAM_STREAM_RADIUS = "4" # or 8, 12
|
||||
dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 | Tee-Object -FilePath "baseline.log"
|
||||
```
|
||||
|
||||
Stand still for ~30 s at the target radius (60 s at radius 12 to let streaming settle),
|
||||
or walk N→E→S→W across one landblock. Then `Select-String -Path baseline.log -Pattern
|
||||
"\[WB-DIAG\]" | Select-Object -Last 3` captures the steady-state numbers.
|
||||
|
||||
For the surface histogram, also set `$env:ACDREAM_DUMP_SURFACES = "1"`, stay in-world
|
||||
~30 s after streaming has loaded ≥100 textures (the cache-size gate), then read
|
||||
`$env:LOCALAPPDATA\acdream\n6-surfaces.txt`.
|
||||
|
|
@ -1,320 +0,0 @@
|
|||
# Phase C.1.5b handoff — issue #56 + EnvCell statics + animation-hook verification
|
||||
|
||||
**Created:** 2026-05-12, immediately after Phase C.1.5a merged to `main` (commit `88bda12`).
|
||||
**Audience:** the fresh-session Claude (or human) picking up C.1.5b.
|
||||
**Predecessor:** [C.1.5a portal PES wiring](../superpowers/specs/2026-05-12-phase-c1.5a-portals-design.md) — slice 1, shipped.
|
||||
|
||||
---
|
||||
|
||||
## §1 Startup prompt (copy this into a fresh session)
|
||||
|
||||
Everything below this fence is the prompt to paste into a new Claude Code session. The detailed context the session needs lives in §2+ of this same file.
|
||||
|
||||
```
|
||||
Pick up Phase C.1.5b — issue #56 (multi-emitter per-part collapse) first,
|
||||
then EnvCell static-object DefaultScript dispatch + animation-hook
|
||||
particle path verification.
|
||||
|
||||
## Context
|
||||
|
||||
Phase C.1.5a (portal PES wiring) merged to main 2026-05-12 (merge commit
|
||||
88bda12). The PhysicsScriptRunner now fires Setup.DefaultScript on every
|
||||
server-spawned WorldEntity via the new EntityScriptActivator. Visual
|
||||
verification at the Holtburg Town network portal confirmed the mechanism
|
||||
works end-to-end (10-hook portal script fires correctly, color +
|
||||
persistence + orientation match retail), but exposed a pre-existing C.1
|
||||
limitation now tracked as ISSUE #56: ParticleHookSink ignores
|
||||
CreateParticleHook.PartIndex, so all 10 of the portal's emitters
|
||||
collapse to one root position → compressed, partly-ground-buried swirl.
|
||||
|
||||
The C.1.5a final cross-task reviewer recommended #56 be resolved FIRST
|
||||
in this slice, before the EnvCell static-object walker, because slice
|
||||
2's natural visual gate (Holtburg inn interior fireplace, cottage
|
||||
chimney) uses the same multi-emitter pattern — without #56 fixed,
|
||||
slice 2 ships with the same visual gap.
|
||||
|
||||
## Read first (in order)
|
||||
|
||||
1. docs/plans/2026-05-12-phase-c1.5b-handoff.md (this file's §2+)
|
||||
2. docs/ISSUES.md #56 (the per-part collapse problem with reproducible
|
||||
identifiers from the C.1.5a verification session)
|
||||
3. docs/superpowers/specs/2026-05-12-phase-c1.5a-portals-design.md §10
|
||||
(slice 2 preview written during C.1.5a brainstorming)
|
||||
4. docs/plans/2026-04-27-phase-c1-pes-particles.md lines 285–295 (the
|
||||
original C.1.5 scope source)
|
||||
|
||||
## Two slices in this session
|
||||
|
||||
### Slice A — issue #56 fix (per-part transform handling for static entities)
|
||||
|
||||
For static entities (portals, EnvCell statics, building decorations —
|
||||
no animation), precompute the per-part offset from
|
||||
Setup.PlacementFrames[Resting] at spawn time and surface those offsets
|
||||
to the ParticleHookSink so SpawnFromHook can apply them. The handoff
|
||||
doc §3 has the suggested architecture + decision space.
|
||||
|
||||
Acceptance: relaunch + walk to the Holtburg Town network portal. The
|
||||
10 emitters should distribute across the portal Setup's parts instead
|
||||
of collapsing — swirl extends vertically through the arch with
|
||||
retail-like shape, not buried in the ground.
|
||||
|
||||
### Slice B — EnvCell static-object DefaultScript dispatch + animation-hook verification
|
||||
|
||||
Walk EnvCell.StaticObjects for newly-loaded landblocks; for each
|
||||
StaticObject whose Setup has a non-zero DefaultScript, fire the
|
||||
activator with a synthetic entity ID (suggested scheme: hash of
|
||||
(landblockId, cellIndex, staticIndex) with a high-bit marker so it
|
||||
doesn't collide with server guids — see handoff §4). Then verify the
|
||||
animation-hook particle path (already shipped in C.1; just needs
|
||||
visual confirmation): cast a spell on +Acdream and compare to retail.
|
||||
|
||||
Acceptance: Holtburg inn fireplace flames, cottage chimney smoke, and
|
||||
a spell-cast particle effect on +Acdream all match retail.
|
||||
|
||||
## What this is NOT
|
||||
|
||||
- Not a renderer change. particle.frag stays as-is; bindless migration
|
||||
waits for N.6 slice 2 after this slice lands.
|
||||
- Not a perf phase. The N.6 baseline at radius=4 still holds; the
|
||||
per-part precompute cost is bounded by N parts × M emitters per
|
||||
spawned entity (small).
|
||||
- Not adding new emitter types. Use the existing PES emitter data.
|
||||
- Not touching the animated-entity path. For animated entities (NPCs,
|
||||
monsters), per-part transforms vary per frame and would need a
|
||||
per-tick refresh similar to UpdateEntityAnchor. Defer to a future
|
||||
phase; C.1.5b stays scoped to static entities only.
|
||||
|
||||
## Suggested workflow
|
||||
|
||||
1. Read the handoff doc + the four referenced docs above.
|
||||
2. Invoke superpowers:brainstorming to settle:
|
||||
- For slice A: precompute-per-part-at-spawn vs render-thread-side-table
|
||||
approach (handoff §3 has the tradeoff analysis).
|
||||
- For slice B: the synthetic-entity-id scheme; whether the EnvCell
|
||||
walker piggybacks LandblockSpawnAdapter or gets its own class.
|
||||
- Visual verification locations.
|
||||
3. After brainstorm: spec at
|
||||
docs/superpowers/specs/2026-05-13-phase-c1.5b-design.md (one spec
|
||||
for both slices since they share the activator and tests), then plan
|
||||
at docs/superpowers/plans/2026-05-13-phase-c1.5b.md, then execute
|
||||
via superpowers:subagent-driven-development.
|
||||
|
||||
## Open issues from C.1.5a worth knowing
|
||||
|
||||
- #56 — multi-emitter per-part collapse. This slice's headline.
|
||||
- #55 — meshMissing diagnostic spam at radius=4 standstill. LOW
|
||||
severity, not blocking; only touch if you're already in the
|
||||
dispatcher for unrelated reasons.
|
||||
- Cold-path timing observation (C.1.5a Task 2 review): the activator
|
||||
fires DefaultScript before pending-bucket entities are merged into
|
||||
a loaded landblock. Mirrors existing _wbEntitySpawnAdapter pattern;
|
||||
not a regression; defer.
|
||||
|
||||
## Three doc-drift items from C.1.5a (trivial — fold into the new spec)
|
||||
|
||||
1. C.1.5a spec §4 says "fifth (optional) parameter" — actually fourth.
|
||||
2. C.1.5a spec §4 says "~50 lines" — file ships at 93 lines.
|
||||
3. GpuWorldState.AddEntitiesToExistingLandblock (A.5 Far→Near
|
||||
promotion path) does not fire the activator. No-op today because
|
||||
promotion-tier entities are atlas-tier and the activator's
|
||||
ServerGuid==0 guard would skip them anyway, but worth a code
|
||||
comment explaining why the call is intentionally omitted there
|
||||
(parallel to existing comments at the RemoveEntitiesFromLandblock
|
||||
block in the same file).
|
||||
|
||||
Start by reading the handoff doc, then ask me what slice-A/slice-B
|
||||
boundary feels right and what visual verification locations I want
|
||||
to target.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §2 What shipped in C.1.5a (so you don't re-do it)
|
||||
|
||||
### Commits on `main` (oldest to newest under merge `88bda12`)
|
||||
|
||||
| SHA | Title |
|
||||
|---|---|
|
||||
| `06d7fbd` | docs(vfx): Phase C.1.5a — portal PES wiring design spec |
|
||||
| `ed5335b` | docs(vfx #C.1.5a): implementation plan + spec wiring-location fixes |
|
||||
| `003c502` | feat(vfx #C.1.5a): add EntityScriptActivator (no wiring yet) |
|
||||
| `e0529b0` | test(vfx #C.1.5a): real-emitter verification in OnRemove test + unused using |
|
||||
| `44d8502` | feat(vfx #C.1.5a): wire EntityScriptActivator into GpuWorldState lifecycle |
|
||||
| `65d833d` | feat(vfx #C.1.5a): construct EntityScriptActivator in GameWindow |
|
||||
| `849690c` | refactor(vfx #C.1.5a): reuse SequencerFactory's capturedDats in resolver |
|
||||
| `334f0c6` | fix(vfx #C.1.5a): seed entity rotation in activator so hook offset rotates |
|
||||
| `9009318` | docs(vfx #C.1.5a): ship Phase C.1.5a + file issue #56 for per-part collapse |
|
||||
| `88bda12` | Merge branch 'claude/lucid-burnell-aab524' — Phase C.1.5a |
|
||||
|
||||
### New files
|
||||
|
||||
- [`src/AcDream.App/Rendering/Vfx/EntityScriptActivator.cs`](../../src/AcDream.App/Rendering/Vfx/EntityScriptActivator.cs) — 93 lines including doc comments. Constructor `(PhysicsScriptRunner, ParticleHookSink, Func<WorldEntity, uint>)`; `OnCreate(WorldEntity)` resolves the entity's `Setup.DefaultScript.DataId`, seeds `_particleSink.SetEntityRotation(entity.ServerGuid, entity.Rotation)`, and calls `_scriptRunner.Play(scriptId, entity.ServerGuid, entity.Position)`; `OnRemove(uint serverGuid)` calls `_scriptRunner.StopAllForEntity(serverGuid)` + `_particleSink.StopAllForEntity(serverGuid, fadeOut: false)`.
|
||||
- [`tests/AcDream.Core.Tests/Rendering/Vfx/EntityScriptActivatorTests.cs`](../../tests/AcDream.Core.Tests/Rendering/Vfx/EntityScriptActivatorTests.cs) — 4 xUnit `[Fact]` tests with mutation-check teeth verified during the C.1.5a code-quality reviews.
|
||||
|
||||
### Modified files
|
||||
|
||||
- [`src/AcDream.App/Streaming/GpuWorldState.cs`](../../src/AcDream.App/Streaming/GpuWorldState.cs) — fourth optional ctor parameter `EntityScriptActivator? entityScriptActivator = null`, field `_entityScriptActivator`, and two `?.OnCreate(entity)` / `?.OnRemove(serverGuid)` calls immediately after the matching `_wbEntitySpawnAdapter?.OnCreate` / `?.OnRemove` calls in `AppendLiveEntity` and `RemoveEntityByServerGuid`.
|
||||
- [`src/AcDream.App/Rendering/GameWindow.cs`](../../src/AcDream.App/Rendering/GameWindow.cs) — new field declaration alongside `_wbEntitySpawnAdapter` and inline construction of the activator + resolver lambda inside the existing `OnLoad` block (~line 1620), passed to `GpuWorldState` as a named argument.
|
||||
|
||||
### What's working
|
||||
|
||||
- Server-spawned entities (`ServerGuid != 0`) with `Setup.DefaultScript.DataId != 0` fire that script through `PhysicsScriptRunner.Play` on enter-world.
|
||||
- Multi-hook scripts dispatch all their hooks in order (timed by `StartTime` offsets — more retail-faithful than WB's "all at once" collection).
|
||||
- `CreateParticleHook.Offset.Origin` rotates correctly from entity-local to world frame via the activator's `SetEntityRotation` seed.
|
||||
- Despawn cleanly stops all scripts + emitters for the entity.
|
||||
- 4 unit tests cover all three branches plus the rotation-seed correctness.
|
||||
- Visual verification at the Holtburg Town network portal passed for the mechanism: 10-hook portal script fires correctly with matching color, persistence, orientation, multi-emitter dispatch.
|
||||
|
||||
## §3 Issue #56 decision space (slice A)
|
||||
|
||||
### The problem
|
||||
|
||||
`ParticleHookSink.SpawnFromHook` computes:
|
||||
|
||||
```csharp
|
||||
var rotation = _rotationByEntity.TryGetValue(entityId, out var rot) ? rot : Quaternion.Identity;
|
||||
var anchor = worldPos + Vector3.Transform(offset, rotation);
|
||||
```
|
||||
|
||||
…where `worldPos` is `entity.Position` and `offset` is `cph.Offset.Origin`. The `CreateParticleHook.PartIndex` field is recorded into the per-handle tracking dict but never applied to the anchor. Retail's intended geometry is:
|
||||
|
||||
```
|
||||
anchor = entityWorldPose × partLocalTransform[partIndex] × hookOffsetInPartLocal
|
||||
```
|
||||
|
||||
Without the part transform multiplication, every emitter in a multi-emitter script lands at the same root position. Visible symptom: the Holtburg portal's 10 emitters compress to one point and the swirl appears partially buried because the offset's local-up direction goes off in world axes instead of the part's local axes.
|
||||
|
||||
### Where part transforms come from
|
||||
|
||||
For STATIC entities (no animation), per-part transforms come from `Setup.PlacementFrames[Resting].Frames[partIndex]` — see how `ObjectMeshManager.CollectParts` walks them in `references/WorldBuilder` (worktree-relative path; submodule must be initialized to read):
|
||||
|
||||
- For each `i` in `0..setup.Parts.Count`, the per-part transform is `Matrix4x4.CreateScale(setup.DefaultScale[i]) * Matrix4x4.CreateFromQuaternion(placementFrame.Frames[i].Orientation) * Matrix4x4.CreateTranslation(placementFrame.Frames[i].Origin)`.
|
||||
- `DefaultScale` only applies when `SetupFlags.HasDefaultScale` is set.
|
||||
- Fall back to `PlacementFrames[Default]` if `Resting` isn't present.
|
||||
|
||||
For ANIMATED entities (NPCs, monsters, the player), per-part transforms vary per animation frame and live in `AnimatedEntityState` / the animation tick. **Out of scope for C.1.5b.**
|
||||
|
||||
### Approach options
|
||||
|
||||
**Option A — precompute per-spawn, pass at activator-call time.**
|
||||
|
||||
`EntityScriptActivator` reads the Setup's `PlacementFrames[Resting]` once per spawn, builds a `Matrix4x4[] partTransforms` array, and passes it to a new sink method `_particleSink.SetEntityPartTransforms(entityId, partTransforms)` before calling `_scriptRunner.Play(...)`. `ParticleHookSink.SpawnFromHook` then reads `_partTransformsByEntity` to apply per-hook:
|
||||
|
||||
```csharp
|
||||
var partXf = _partTransformsByEntity.TryGetValue(entityId, out var pts) && partIndex < pts.Length
|
||||
? pts[partIndex] : Matrix4x4.Identity;
|
||||
var anchor = worldPos + Vector3.Transform(Vector3.Transform(offset, partXf), rotation);
|
||||
```
|
||||
|
||||
Pros: clean ownership (activator owns the lifecycle of part transforms keyed by entityId), matches existing sink-state patterns (`_rotationByEntity`, `_renderPassByEntity`), small code surface, fully testable.
|
||||
|
||||
Cons: stores per-entity array (matrix per part) — bounded but allocates. Doesn't compose with the animated-entity case (which would need per-tick refresh).
|
||||
|
||||
**Option B — render-thread side-table populated by the dispatcher.**
|
||||
|
||||
The `WbDrawDispatcher` already computes per-part world transforms each frame. Surface them via a side-table the sink queries. Per-frame.
|
||||
|
||||
Pros: free composition with animated entities (the dispatcher transforms whether the entity is animated or not).
|
||||
|
||||
Cons: render-thread / sink-thread coordination concern, bigger architectural surface, the dispatcher would need a new responsibility (publish part transforms) outside its draw-loop hot path. Risk of touching the modern bindless dispatcher's perf budget that N.5/N.5b worked to lock in.
|
||||
|
||||
**Option C — sink-side dat lookup on demand.**
|
||||
|
||||
`ParticleHookSink` calls `_dats.Get<Setup>(...)` on the hook fire to look up the part transform. Pros: zero state on activator. Cons: introduces dat coupling into the sink (currently dat-free), per-hook-fire dat lookup is a hidden allocation, doesn't compose with animated entities either, and we'd be reading the same Setup multiple times for the same entity.
|
||||
|
||||
### Recommended approach
|
||||
|
||||
**Option A.** It's the smallest surface, matches the existing sink-state pattern, doesn't expand any other layer's responsibilities, and the "doesn't compose with animated entities" downside is intentional — animated entities are explicitly out of scope and will get their own treatment later, possibly via Option B at that time.
|
||||
|
||||
### Test approach
|
||||
|
||||
Mirror the C.1.5a `OnCreate_SetsEntityRotationForHookOffsetTransform` test: construct an entity whose Setup has 2 parts (root at origin + part 1 lifted at (0, 0, 1)), fire a CreateParticleHook with `PartIndex=1` and `Offset.Origin=(0, 0, 0)`, assert the spawned particle's world position is `(0, 0, 1)` (the part's offset, not the root). Add a mutation check: delete the `SetEntityPartTransforms` line and confirm the test fails.
|
||||
|
||||
## §4 EnvCell static-object dispatch decision space (slice B)
|
||||
|
||||
### The problem
|
||||
|
||||
`EnvCell.StaticObjects` are interior decoration objects inside dungeon / building cells. Each StaticObject has a Setup reference and a placement frame. They have NO `ServerGuid` — they're dat-hydrated, not server-spawned.
|
||||
|
||||
Our `EntityScriptActivator.OnCreate` early-returns when `entity.ServerGuid == 0` (atlas-tier guard). So as-is, the activator won't fire DefaultScript for EnvCell statics.
|
||||
|
||||
### Two architectural questions
|
||||
|
||||
**Q1 — synthetic entity ID for tracking + cleanup.**
|
||||
|
||||
`PhysicsScriptRunner` keys active scripts by `(scriptId, entityId)`. `ParticleHookSink` keys per-entity emitter handles by `entityId`. EnvCell statics need a stable, unique 32-bit ID for these tables that won't collide with server guids (and won't collide between two EnvCell statics in different cells).
|
||||
|
||||
Suggested scheme:
|
||||
|
||||
```
|
||||
uint syntheticId = 0xC0000000u
|
||||
| ((landblockId & 0x0000FF00u) << 16) // landblock X byte → bits 24-31 minus high marker
|
||||
| ((landblockId & 0xFF000000u) >> 8) // landblock Y byte → bits 16-23
|
||||
| ((cellIndex & 0x0000FFFFu) << 0); // bits 0-15: cell index within landblock
|
||||
```
|
||||
|
||||
…leaving 4 bits for the static-object index within the cell. Adjust bit layout for the actual `(LandblockId, CellIndex, StaticIndex)` distribution. The `0xC0_______u` marker is **above** server guid range and **above** the anonymous-emitter range (`0x80_______u`) used by `ParticleHookSink._anonymousEmitterSerial`, so no collision.
|
||||
|
||||
Sanity check: `WorldEntity.ServerGuid` is `uint`; the `(scriptId, entityId)` dedupe key in the runner only needs uniqueness, not semantic meaning. Either scheme works as long as it's collision-free.
|
||||
|
||||
**Q2 — which adapter walks EnvCell.StaticObjects?**
|
||||
|
||||
Three options:
|
||||
|
||||
- **Option α — piggyback `LandblockSpawnAdapter`.** That adapter already walks `landblock.Entities` for atlas-tier mesh-ref counting. Extending it to also walk `EnvCell.StaticObjects` and fire DefaultScript via the activator keeps the per-landblock-load flow in one place. Cons: blurs the adapter's single responsibility.
|
||||
|
||||
- **Option β — new `EnvCellStaticActivator` class.** Mirror `EntityScriptActivator`'s shape but key by synthetic-id, walking each loaded landblock's EnvCells on load and firing per-static-object. Cons: more code; slight duplication of the activator pattern.
|
||||
|
||||
- **Option γ — `EntityScriptActivator` learns a "static-object" entry point.** Add `OnEnvCellStaticCreate(LoadedLandblock landblock, int cellIndex, int staticIndex, Setup setup, Vector3 worldPos, Quaternion worldRot)` to the existing activator. Compute the synthetic ID inside. Cons: signature creep on the activator.
|
||||
|
||||
Recommended: **Option β.** Keeps the existing activator's `WorldEntity`-shaped contract pure; the new class has a clean per-static-object contract; both share `_scriptRunner` and `_particleSink` instances so no architectural duplication, just two thin orchestrators.
|
||||
|
||||
### Lifecycle
|
||||
|
||||
EnvCell statics live as long as their parent landblock is loaded. On landblock unload, the new activator should stop all scripts for all its synthetic IDs from that landblock. Mirror `LandblockSpawnAdapter`'s `OnLandblockLoaded` / `OnLandblockUnloaded` lifecycle.
|
||||
|
||||
## §5 Animation-hook verification (slice B's quick half)
|
||||
|
||||
Already shipped in C.1: `MotionInterpreter` fires per-keyframe hooks through `IAnimationHookSink` → `ParticleHookSink`. We just haven't verified visually in the current codebase state.
|
||||
|
||||
Procedure:
|
||||
1. Cast a spell on `+Acdream` (the test character likely has at least one spell + components configured — check or grant if needed).
|
||||
2. Watch the cast-anim particle effect (sparkles, glyphs, etc.) — does it match retail's casting animation?
|
||||
3. Optional: trigger an emote with a particle hook (the `\dance` / `\drink` emotes are good candidates if they have particle data).
|
||||
|
||||
If broken, file an issue with the symptom. If working, mark slice B complete on verification.
|
||||
|
||||
## §6 Verification locations
|
||||
|
||||
All in or near Holtburg, within ~30s of `+Acdream`'s spawn:
|
||||
|
||||
- **#56 fix re-verify** — the Town network portal used in C.1.5a. Same procedure as C.1.5a's Task 4 (see [the C.1.5a spec §8](../superpowers/specs/2026-05-12-phase-c1.5a-portals-design.md)).
|
||||
- **EnvCell chimney** — any cottage / inn within the Holtburg outer perimeter with a smoking chimney in retail. Confirm via dual-client.
|
||||
- **EnvCell fireplace** — Holtburg Inn interior. Walk inside and stand near the fireplace. Confirm flame particles match retail.
|
||||
- **Animation-hook verify** — cast a spell standing somewhere safe (outside any aggro range). Compare to retail.
|
||||
|
||||
## §7 File pointers for slice 2
|
||||
|
||||
- Particle pipeline (Core): [`src/AcDream.Core/Vfx/ParticleSystem.cs`](../../src/AcDream.Core/Vfx/ParticleSystem.cs), [`ParticleHookSink.cs`](../../src/AcDream.Core/Vfx/ParticleHookSink.cs), [`PhysicsScriptRunner.cs`](../../src/AcDream.Core/Vfx/PhysicsScriptRunner.cs).
|
||||
- Activator (App): [`src/AcDream.App/Rendering/Vfx/EntityScriptActivator.cs`](../../src/AcDream.App/Rendering/Vfx/EntityScriptActivator.cs).
|
||||
- Streaming bridge (App): [`src/AcDream.App/Streaming/GpuWorldState.cs`](../../src/AcDream.App/Streaming/GpuWorldState.cs), [`LandblockSpawnAdapter.cs`](../../src/AcDream.App/Rendering/Wb/LandblockSpawnAdapter.cs).
|
||||
- Renderer: [`src/AcDream.App/Rendering/ParticleRenderer.cs`](../../src/AcDream.App/Rendering/ParticleRenderer.cs) — **don't touch** in C.1.5b; bindless migration is N.6 slice 2.
|
||||
- EnvCell loader: search for `LoadedCell` / `EnvCell.StaticObjects` in `src/AcDream.App/Streaming/` and `src/AcDream.Core/World/`.
|
||||
- C.1.5a tests as a reference: [`tests/AcDream.Core.Tests/Rendering/Vfx/EntityScriptActivatorTests.cs`](../../tests/AcDream.Core.Tests/Rendering/Vfx/EntityScriptActivatorTests.cs).
|
||||
|
||||
## §8 Open questions to surface during brainstorming
|
||||
|
||||
- Slice A: does the C.1.5a final reviewer's "static-only fix is self-contained" claim hold up? (Section §3 Option A says yes; brainstorming should verify by checking `EntityScriptActivator`'s spawn path doesn't depend on animation state.)
|
||||
- Slice B: which Setup field actually lives on `EnvCell.StaticObjects` — is it a `SetupId` reference or an inline Setup? Different shape changes the synthetic-ID hash input.
|
||||
- Slice B: are EnvCell statics ALSO subject to the cold-path timing observation from C.1.5a Task 2 review (firing before the cell is rendered)?
|
||||
|
||||
## §9 Worktree cleanup reminder (one-time, from outside the worktree)
|
||||
|
||||
The C.1.5a worktree directory at `C:/Users/erikn/source/repos/acdream/.claude/worktrees/lucid-burnell-aab524` was not auto-removed because the controller session held a file lock. After this session ends, from any other directory:
|
||||
|
||||
```powershell
|
||||
git -C "C:/Users/erikn/source/repos/acdream" worktree remove --force `
|
||||
"C:/Users/erikn/source/repos/acdream/.claude/worktrees/lucid-burnell-aab524"
|
||||
```
|
||||
|
||||
The branch `claude/lucid-burnell-aab524` was successfully deleted; only the worktree directory needs manual cleanup.
|
||||
|
|
@ -1,381 +0,0 @@
|
|||
# The holistic building-render port plan (Phase B) — one drawing discipline
|
||||
|
||||
**EXECUTION STATUS (2026-06-11, post-BR-7): BR-2…BR-7 are ALL CODE-COMPLETE
|
||||
on the branch — the render arc as the fused tasks T1–T4 (T1 `579c8b0` frame
|
||||
order; T2 `cf8a2c3`/`529dfcf`/`88f3ce1` flood fidelity, two retail constants
|
||||
refuted by the conformance gate and kept at documented tolerances; T3
|
||||
`a6aec8c` viewconeCheck; T4 `4a307d3` one-gate deletions), and BR-7 (T6,
|
||||
collision A6.P4) as `6ec4cde` (signed OtherPortalId gate) + `abf36e2`
|
||||
(BuildShadowCellSet flood) + `dbfbf85` (per-cell architecture: flood
|
||||
registration, building channel, per-cell query, b3ce505 DELETED — closes
|
||||
#99) + `ca4b482` (straddle-only outside-add, A6.P5 widening + #90
|
||||
stickiness removed). Of the 4 #99-era Core reds, 3 flipped green as
|
||||
designed (door apparatus + tick-13558 + tick-22760's blocking invariant);
|
||||
the 4th (BSPStepUp D4) + 22760's lateral-slide delta proved to be a
|
||||
SEPARATE pre-existing slide-response family — filed #116, D4 skipped with
|
||||
the reference (probes show the cell-set layer innocent). Suites: Core
|
||||
1416/0/2skip, App 225, UI 420, Net 294.
|
||||
|
||||
**T5 EXECUTED 2026-06-11 (the single comprehensive user gate) — PARTIAL
|
||||
PASS.** ✅ Confirmed by the user: doors block both ways incl. off-center
|
||||
(#99 visual), cellar descent/ascent clean + #108 grass-sweep GONE, inn
|
||||
2nd floor clean (#97 closed), interiors stable through doorways incl.
|
||||
edge-on, #109 far-door oscillation GONE, formerly-popping stairs now
|
||||
STABLE at all ranges (the distance-pop class is dead). ❌ Remaining —
|
||||
four filed render artifacts: **#117** aperture-shaped see-through
|
||||
(doors/interiors through terrain hills + through nearer buildings — the
|
||||
punch erases occluder depth), **#118** character clipped+vanishes for a
|
||||
moment on house exit, **#119** old-tower stairs partially invisible +
|
||||
extraneous barrel (pre-existing; `[up-null]` permanently-invisible mesh
|
||||
lead in the T5 log), **#120** `[pv-ERROR]` in-place-propagation
|
||||
convergence tripwire at depth 128 on the cottage cells (self-detected
|
||||
T2 invariant break — investigate first). Rain-indoors not verifiable
|
||||
(clear weather). NEXT: fix #120 → #117 → #118 → #119 at the mechanism
|
||||
level, then a focused re-gate on just those spots.**
|
||||
|
||||
**Status: APPROVED + AMENDED (2026-06-11). EXECUTION DIRECTIVE CHANGED BY THE
|
||||
USER: "I don't care if it is non-playable… I want everything ported, then we
|
||||
test."** The per-phase playability constraint and per-phase user visual gates
|
||||
are DROPPED. BR-2 through BR-6 execute as ONE continuous port (the fused
|
||||
render discipline), with build + unit/conformance tests green at every commit
|
||||
(engineering hygiene, not gates), and **ONE comprehensive visual test pass at
|
||||
the end**. Rationale: the first BR-2 attempt failed precisely because the
|
||||
phase slicing cut retail's frame order in half (the punch shipped without
|
||||
entities-drawn-last and erased characters in apertures — reverted `88be519`);
|
||||
the installment-must-be-a-complete-retail-behavior rule replaces the
|
||||
playability rule. BR-7 (collision) runs as an independent track; BR-8b
|
||||
(lighting) still wants the verification resume first.
|
||||
Companion to the Phase A comparison:
|
||||
[`docs/research/2026-06-11-building-render-acdream-vs-retail-comparison.md`](../research/2026-06-11-building-render-acdream-vs-retail-comparison.md)
|
||||
(evidence appendices in
|
||||
[`docs/research/2026-06-11-holistic-map/`](../research/2026-06-11-holistic-map/)).
|
||||
Mandate: *"one solution that works every time I walk to a new landblock and
|
||||
walk into a dungeon"* (2026-06-11).
|
||||
|
||||
---
|
||||
|
||||
## 0. The invariant (what "one drawing discipline" means, retail-cited)
|
||||
|
||||
Every phase below moves us toward — and no phase may move us away from — this
|
||||
frame shape, which is retail's (Ghidra-cited in the comparison doc §2):
|
||||
|
||||
1. **Geometry is flattened at load** into surface-batched meshes (we already
|
||||
do this). World geometry is **never geometrically clipped at draw time**.
|
||||
2. **Untextured (solid) surface batches never draw** on building shells and
|
||||
cell meshes (`skipNoTexture`); they do draw on plain objects.
|
||||
3. **Portal polygons are not wall geometry.** They exist per frame only as
|
||||
(a) flood admission tests (`ConstructView`: eye-side ε=0.0002 → clip vs
|
||||
current view → cell loaded) and (b) **invisible depth writes** — far-Z
|
||||
*punch* before an interior draws through an aperture; true-depth *seal*
|
||||
on portals to the outside after the landscape draws.
|
||||
4. **Cells draw whole, far→near, once** (frame stamp); the z-buffer plus the
|
||||
punches/seals produce pixel-exact apertures.
|
||||
5. **Objects and particles are culled per portal view** (sphere vs the view's
|
||||
edge planes — `viewconeCheck`), never clipped, never scissored.
|
||||
6. **One visibility computation feeds everything** — the PView flood. No
|
||||
second BFS, no parallel gate, no distance constants in admission.
|
||||
|
||||
## 1. Keep-list (the code worth saving — explicitly not touched/rewritten)
|
||||
|
||||
- **Mesh pipeline**: `ObjectMeshManager` flatten + global VAO + bindless MDI
|
||||
(`WbDrawDispatcher`) — retail-faithful architecture, confirmed by the
|
||||
`ConstructMesh`/`RemoveNonPortalNodes` finding.
|
||||
- **The flood port**: `PortalVisibilityBuilder` (homogeneous clipper, side
|
||||
tests, reciprocal clip, exact-match skip) + conformance gates
|
||||
(`CornerFloodReplayTests`, `Issue113MeetingHallFloodTests`) — BR-4 adjusts
|
||||
constants/heuristics, it does not rewrite the clipper.
|
||||
- **Membership** (P1 9/9 golden) + **straddle gate** (`414c3de`) +
|
||||
**camera collision sweep** (verbatim `update_viewer`) + **znear=0.1** +
|
||||
**#105 texture flush** + **two-tier streaming** + spawn/snap validation
|
||||
(#107/#111/#112).
|
||||
- Diagnostics/probes and the dat dump harness.
|
||||
|
||||
The M0 freeze list is superseded *for rendering only* by the 2026-06-11
|
||||
mandate; nothing outside building/interior render + interior collision is in
|
||||
scope.
|
||||
|
||||
## 2. Phases
|
||||
|
||||
Ordering rule: each phase lands green (build + full suites + named visual
|
||||
gate) and the client stays playable after every phase. Conformance pins come
|
||||
from the dat harness + the flood replay harnesses; retail constants are cited
|
||||
inline when ported.
|
||||
|
||||
### BR-1 — The surface gate — ✅ RESOLVED AS ALREADY-EQUIVALENT (2026-06-11, execution day 1)
|
||||
|
||||
**Premise falsified before implementation (the BR-1 pre-check,
|
||||
`Diagnostic_ReplicateProductionEmission_OnPortalFills`):** acdream **already suppresses
|
||||
every portal fill** — all four extraction paths skip `Stippling.NoPos`
|
||||
positive sides (`ObjectMeshManager.PrepareGfxObjMeshData:1046`,
|
||||
`PrepareCellStructMeshData:1394`, `CellMesh.Build:44`, `GfxObjMesh.Build:71`),
|
||||
and the Holtburg fills have no negative surface. The planned "draw-time
|
||||
surface gate" has nothing to gate.
|
||||
|
||||
**What shipped instead — the equivalence pin**
|
||||
(`StipplingSurfaceEquivalenceTests`): 2,607 polys across 13 building models +
|
||||
13 environments, **zero violations both directions** — `NoPos ⇔ untextured
|
||||
surface`. Our build-time skip is therefore *proven equivalent* to retail's
|
||||
draw-time `skipNoTexture` rule on this content; the
|
||||
`portal-poly-suppression-criterion` divergence closes as
|
||||
equivalent-with-proof. The pin fails loudly if future content breaks the
|
||||
invariant (the cue to implement the draw-time gate then).
|
||||
|
||||
**Consequences (the honest part):**
|
||||
- The **#113 phantom residual is NOT GfxObj fills** — it cannot be, they
|
||||
never reach a vertex buffer. The "root cause #2" attribution from the
|
||||
e46d3d9 session is corrected; the e46d3d9 user-gate observations (filter
|
||||
removed phantom/doors) were confounded — the filter was a provable mesh
|
||||
no-op on both shells and door parts.
|
||||
- The phantom's plausible true sites are cell-side: flood-admitted stair
|
||||
CELLS drawn with a pass-all slice when the assembler hands them no slot
|
||||
(`RetailPViewRenderer.cs:71` draws ALL visible cells; `NoClipSlice`
|
||||
default), and/or stair-cell STATICS drawn unclipped + un-viewcone'd by
|
||||
design (`object-lists-skip-portal-view-gate`, confirmed). **BR-2's first
|
||||
task is a 10-minute probe at the hall bisect spot pinning which** —
|
||||
the closure moves to BR-2/BR-3 (shells) and BR-5 (statics).
|
||||
- **Closes:** the `portal-poly-suppression-criterion` divergence (as
|
||||
proven-equivalent); #113's closure moves to BR-2/BR-3/BR-5.
|
||||
- **Shipped:** the pre-check + equivalence pin tests; no production code
|
||||
(none needed).
|
||||
|
||||
### BR-2 — Aperture depth machinery (punch / seal / clear)
|
||||
|
||||
**What:** port the invisible depth writes:
|
||||
(a) wire `DrawExitPortalMasks` (today an unwired no-op) as a depth-only draw
|
||||
of each outside-leading portal polygon, software-clipped to its view slice
|
||||
(the `ClipToRegion` math already exists), at the portal's **true projected
|
||||
depth** (retail `maxZ2`) — after the landscape slices, indoor roots;
|
||||
(b) add the **far-Z punch** (retail `maxZ1`) on building-aperture flood
|
||||
success on the outdoor + look-in paths, before the interior cells draw;
|
||||
(c) replace the per-slice scissored `ClearDepthSlice` AABB clear with
|
||||
retail's discipline: one full depth clear between the outside stage and the
|
||||
interior stage, gated on whether any seal was drawn (`portalsDrawnCount`);
|
||||
(d) on the look-in path, draw interior-through-aperture **before** the shell
|
||||
mesh (retail `DrawBuilding` order) so the shell's depth closes everything
|
||||
outside the punch.
|
||||
|
||||
- **First task (from BR-1's falsification):** the 10-minute probe at the
|
||||
hall bisect spot — when the phantom is visible, log per stair cell
|
||||
(0x100..0x106) whether it drew with a real clip slot or the pass-all
|
||||
`NoClipSlice`, and whether its statics drew — pinning the phantom's true
|
||||
draw site (shells → fixed here/BR-3; statics → BR-5).
|
||||
- **Closes:** #108 (outdoor terrain sweeping across the upstairs door — the
|
||||
missing true-depth seal is the confirmed `missing-portal-depth-fence`
|
||||
divergence); the outdoor-root depth-discipline gap; part of #109; the
|
||||
#113 phantom residual if the probe pins it on pass-all shell slices.
|
||||
- **Acceptance:** cellar↔main-floor walk shows no grass sweep (user gate);
|
||||
phantom-spot check at the hall (user gate, replaces the old BR-1
|
||||
acceptance); new harness fact: seal depth = portal plane depth inside the
|
||||
clipped aperture polygon (GL readback test or probe assertion); suites
|
||||
green.
|
||||
- **Size:** ~3 commits (~80 lines of GL + clipper reuse per the area
|
||||
estimate, plus the clear re-shape and order swap).
|
||||
|
||||
### BR-3 — Retire the geometric shell chop; whole-shell far→near draws
|
||||
|
||||
**What:** remove `gl_ClipDistance` as the *enforcement* mechanism for cell
|
||||
shells (both the outdoor-scoped enable from `927fd8f`/`9ce335e` and the
|
||||
never-enabled indoor half — i.e. #114 closes by *deleting* the chop, not
|
||||
perfecting it). Shells draw whole, far→near per `OrderedVisibleCells`
|
||||
(already the order), drawn-once. Clip regions remain for admission, punch
|
||||
shapes, and (BR-5) object culling. The landscape-through-aperture pass keeps
|
||||
its per-slice plane clip for now (open Q: `LScape::draw` internals) — revisit
|
||||
after BR-2 proves the seal protects terrain.
|
||||
|
||||
- **Closes:** #114 (chopped stairs / vanished candle area / barrel-through-
|
||||
wall were artifacts of clipping geometry retail never clips) — jointly
|
||||
with BR-2. Removes the 8-plane budget + slot-0 PASS-ALL as load-bearing
|
||||
for shells.
|
||||
- **Acceptance:** meeting-hall interior + multi-room cottages render
|
||||
unchopped from indoor and outdoor eyes (user gate vs the #114 screenshot
|
||||
set); phantom stays gone (BR-1 unaffected); flood replay gates green.
|
||||
- **Order constraint:** must not land before BR-2 (the depth fence replaces
|
||||
the chop's job at apertures).
|
||||
- **Size:** ~2 commits (mostly deletions + the draw-order assertion).
|
||||
|
||||
### BR-4 — Shell-draw-driven floods + flood fidelity
|
||||
|
||||
**What:** make the building's own draw the flood trigger, retail-shaped:
|
||||
pair the shell GfxObj's `PortalRef.PortalIndex` with its `BuildInfo.Portals`
|
||||
entry (the `outdoor_portal_list` correspondence) and, when a shell survives
|
||||
the cull for a view slice, run each aperture through the ported
|
||||
`ConstructView(CBldPortal)` chain under that slice. Then remove the
|
||||
non-retail machinery the trigger replaces: the 48 m seed constant, the
|
||||
Chebyshev≤1 candidate gather, the `EyeInsidePortalOpening` full-view rescue;
|
||||
adopt retail constants (ε=0.0002; in-plane rejects for building portals);
|
||||
add the 1-px screen-space vertex dedup to `ClipToRegion` output (retail's
|
||||
fixpoint floor) and switch late view growth to in-place propagation
|
||||
(`AddToCell`/`FixCellList`/`AdjustCellView` shape), removing the
|
||||
`MaxReprocessPerCell=16` cap; make `MergeBuildingFrame` union views instead
|
||||
of first-wins and retire single-slot consumers (`CellIdToSlot[0]`); bind
|
||||
nested floods to their originating slot (the `building_view` latch).
|
||||
|
||||
- **Closes:** #109 (binary 48 m pop + first-wins view loss + missing punch
|
||||
are its named mechanisms); the flood-stability family (edge-on doorway
|
||||
residuals); enables interior-visible-through-window parity.
|
||||
- **Acceptance:** flood replay harnesses extended: (a) building flood
|
||||
triggers with no distance constant — admission matches the
|
||||
clip-survival rule across an eye sweep; (b) two-aperture cell holds two
|
||||
views; (c) growth propagates without the cap on a portal-dense fixture;
|
||||
#109 spot user gate; suites green.
|
||||
- **Size:** ~4–5 commits (trigger + pairing; constants/dedup; growth
|
||||
in-place; merge union; deletions).
|
||||
|
||||
### BR-5 — Per-view object + particle culling (viewconeCheck)
|
||||
|
||||
**What:** port `Render::viewconeCheck`: per view slice, lift the per-edge
|
||||
eye planes (each NDC edge + the eye defines a plane — the `view_vertex.plane`
|
||||
analog) and sphere-test every entity and emitter against the slice before
|
||||
draw; route particles through the same gate and the same clip/punch
|
||||
discipline (delete the `BeginDoorwayScissor` AABB path); fix the
|
||||
outdoor-root unattached-emitter drop; gate the weather pass on
|
||||
`is_player_outside` (player cell, not viewer root).
|
||||
|
||||
- **Closes:** particles-through-walls (candle flames in other buildings);
|
||||
rain-indoors-through-doorways; the neighbour-room object over-inclusion
|
||||
half of the old #114 report.
|
||||
- **Acceptance:** flame-through-wall spot at Holtburg (user gate); a
|
||||
conformance fact pinning sphere-vs-slice culling on a fixture; no
|
||||
regression in entity draw counts outdoors (perf probe within noise).
|
||||
- **Size:** ~3 commits.
|
||||
|
||||
### BR-6 — One gate: consolidate visibility + delete legacy paths
|
||||
|
||||
**What:** make the PView flood the only visibility computation:
|
||||
remove the per-frame ACME BFS (`CellVisibility.ComputeVisibilityFromRoot`)
|
||||
by folding its remaining consumers (lighting indoor flag etc.) onto PView/
|
||||
membership outputs; delete or quarantine the confirmed legacy remnants
|
||||
(`InteriorRenderer`, `IndoorDrawPlan` consumers of the old path, the
|
||||
`clipRoot==null` second render branch, the dormant exit-mask wiring once
|
||||
BR-2 rewires it, duplicate frustum implementation); one frustum, one
|
||||
center/radius window.
|
||||
|
||||
- **Closes:** the `dual-live-visibility-computations` inconsistency class
|
||||
(the one-gate rule, `feedback_render_one_gate`); removes the surface area
|
||||
where two gates disagree (future flap-class bugs).
|
||||
- **Acceptance:** gate-audit re-run shows ONE visibility computation per
|
||||
frame; every deletion verified by a launch + the visual gate set; suites
|
||||
green.
|
||||
- **Size:** ~3 commits, mostly deletions (each independently revertable).
|
||||
|
||||
### BR-7 — Interior collision: per-cell shadow lists (A6.P4, verified) — ✅ CODE-COMPLETE 2026-06-11 (`6ec4cde`+`abf36e2`+`dbfbf85`+`ca4b482`; visual confirmation rides T5)
|
||||
|
||||
**What:** ship the A6.P4 architecture with the investigation's corrections:
|
||||
registration builds the cell set by sphere-overlap portal flood (not an XY
|
||||
grid; crosses landblocks), per-cell `shadow_object_list` iteration on the
|
||||
query side (`CheckOtherCells` runs env AND shadow objects per other cell),
|
||||
buildings dispatch through a per-LandCell building channel
|
||||
(`CSortCell.building` shape), `OtherPortalId` widened to signed with the
|
||||
`>= 0` gate (sign-extension Ghidra-proven). Then remove the `b3ce505`
|
||||
stopgap, the A6.P5 `hasExitPortal` widening, and the #90 stickiness
|
||||
workaround.
|
||||
|
||||
- **Closes:** #99 (doors block from both sides), very likely #97; retires
|
||||
three flagged workarounds.
|
||||
- **Acceptance:** A6.P4 spec acceptance (doors block both ways at Holtburg
|
||||
inn + cottages; #98 cellar ascent stays fixed — `CellarUp` harness green);
|
||||
capture/replay comparison on the door apparatus; suites green.
|
||||
- **Size:** the A6.P4 spec's estimate stands (~5 commits); independent of
|
||||
BR-2..BR-5 — may run in parallel with them.
|
||||
|
||||
### BR-8 — Feel tier: camera, lighting, LOD (post-discipline polish)
|
||||
|
||||
- **BR-8a Camera (#115, verified root cause; can land any time):** damp the
|
||||
sought eye FROM the published collided viewer each frame (retail
|
||||
`PlayerPhysicsUpdatedCallback` shape) and apply the computed player fade
|
||||
over the 0.45→0.20 m band. Acceptance: cramped-interior turn feel (user
|
||||
gate). ~1–2 commits.
|
||||
- **BR-8b Lighting (pending verifier confirmation):** interior sun mask
|
||||
(never sun-light interiors), static cell-light burn-in (all lights, not
|
||||
8-nearest), viewer light, per-object light selection, surface
|
||||
luminosity/diffuse. Acceptance: side-by-side interior look vs retail
|
||||
screenshots. Phase-sized; spec before code.
|
||||
- **BR-8c LOD + dedup (low):** per-part degrade selection beyond humanoids;
|
||||
frame-stamp draw dedup. Optional per-cell interleave for draw-order parity
|
||||
is explicitly NOT planned (z-buffer makes it unnecessary; revisit only on
|
||||
evidence).
|
||||
- **Picking refinements** (all-low area): defer; file as issues when the
|
||||
port changes what is clickable.
|
||||
|
||||
## 3. What this plan deliberately does NOT do
|
||||
|
||||
- No per-frame BSP traversal of ordinary geometry (retail doesn't either).
|
||||
- No rewrite of the mesh/MDI pipeline, the flood clipper, membership, or
|
||||
streaming (keep-list).
|
||||
- No `leaf_cells`/`CPartCell` port (path dormant in the 2013 binary — needs
|
||||
runtime proof first).
|
||||
- No transparency-sorting work yet — that area's map is still re-running;
|
||||
fold its findings in as a BR-9 candidate after review (the AlphaList
|
||||
deferral machinery is already decompiled in the Area 1 file).
|
||||
|
||||
## 4. Explicitly out of scope — tracked follow-ups (NOT covered by BR-1…BR-8)
|
||||
|
||||
Completing BR-1 through BR-8 lands the building/interior **drawing
|
||||
discipline** and the collision rearchitecture. It does **not** cover the
|
||||
items below. They are named here so the boundary of what the campaign
|
||||
delivers is written down, not assumed — each becomes its own roadmap item or
|
||||
issue, none blocks BR-1…BR-8.
|
||||
|
||||
- **FU-1 — Transparency / draw-sorting (→ BR-9 candidate).** Retail's
|
||||
`DrawSortCell` + AlphaList deferral (decompiled in
|
||||
`2026-06-11-holistic-map/wf1-gfxobj-draw.md`) governs water surfaces,
|
||||
translucent windows, and alpha-blend ordering. The area's *map never
|
||||
completed* (agent hit the token limit), so there are no divergences yet —
|
||||
scope it before promoting to BR-9. **Severity: medium; user-visible as
|
||||
wrong window/water compositing.**
|
||||
- **FU-2 — Dungeon visibility scaling (#95).** The 8 phases are
|
||||
Holtburg-building-shaped. Dungeons share the EnvCell/portal discipline so
|
||||
they benefit *automatically*, and BR-4's tighter flood admission
|
||||
(no-distance-constant + screen-clip rejection + cell-loaded gate)
|
||||
**plausibly** shrinks #95's 135-cells/frame blowup — but #95 is a
|
||||
disconnected-landblock *seeding* problem that BR-4 is not guaranteed to
|
||||
fix. **Re-measure #95 after BR-4/BR-6 land; if still blown, it needs its
|
||||
own phase.** Do not assume the building port closes it.
|
||||
- **FU-3 — Distance LOD / degrades (= BR-8c, optional).** Per-part degrade
|
||||
selection beyond humanoids; far models stay base-detail until picked up.
|
||||
- **FU-4 — Picking refinements** (4 low-severity divergences,
|
||||
`wf2-picking-selection.md`). Defer; file as issues if/when the port
|
||||
changes what is clickable (e.g. building shells, baked fills).
|
||||
- **FU-5 — The ~30 open questions** live in the comparison doc §6
|
||||
(`2026-06-11-building-render-acdream-vs-retail-comparison.md`). The
|
||||
load-bearing ones are referenced inline in the phases that consume them
|
||||
(e.g. `LScape::draw` clip behavior for BR-2/BR-3, the near-W constant,
|
||||
`DrawPortal` mode-3 seal-on-failure for unstreamed interiors); the rest
|
||||
are pinned during implementation, not before.
|
||||
- **FU-6 — Verification top-up.** ~36/76 divergences remain UNVERIFIED (the
|
||||
overnight resume was stopped to preserve budget; both runs are resumable
|
||||
by ID — see comparison §7). Run a cheap resume before **BR-8b lighting**
|
||||
scoping (the one phase that leans on unverified rows) and before promoting
|
||||
FU-1 to BR-9.
|
||||
|
||||
## 5. Sequencing summary
|
||||
|
||||
```
|
||||
BR-1 (surface gate) — ✅ RESOLVED as already-equivalent (pin shipped,
|
||||
no production code; #113 closure moved to
|
||||
BR-2/3/5 — see BR-1 section)
|
||||
BR-2 (depth punch/seal) — FIRST implementation phase; opens with the
|
||||
phantom-site probe; enables BR-3
|
||||
BR-3 (delete shell chop) — closes #114 with BR-2
|
||||
BR-4 (draw-driven floods) — closes #109; flood fidelity
|
||||
BR-5 (viewconeCheck) — particles/objects through the same gate;
|
||||
closes the phantom if it is statics-side
|
||||
BR-6 (one gate + deletions) — consolidation after the discipline is in
|
||||
BR-7 (collision A6.P4) — independent track; may interleave with BR-2..5
|
||||
BR-8 (camera/lighting/LOD) — feel tier; BR-8a may land early
|
||||
```
|
||||
|
||||
Every phase: `dotnet build` + full suites green, conformance pins added with
|
||||
retail citations, named user visual gate, roadmap/ISSUES updated in the same
|
||||
session, and the render digest updated when a phase closes one of the named
|
||||
bugs.
|
||||
|
||||
## 6. Approval asks
|
||||
|
||||
1. Approve the plan shape + ordering (BR-1 → BR-8, BR-7 parallel-capable).
|
||||
2. Approve the deletions implied by BR-3/BR-6 (shell-chop enforcement,
|
||||
ACME BFS visibility, legacy render branches) — all on the strength of the
|
||||
cited evidence that retail has no counterpart.
|
||||
3. Note the verification caveat: ~36/76 divergences still carry UNVERIFIED
|
||||
(resume in flight); BR-1..BR-3's load-bearing claims are either verified
|
||||
or dat-confirmed locally, so approval need not wait on the rest.
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
# Phase R — retail motion & animation stack, ground-up reconstruction
|
||||
|
||||
Date: 2026-07-02. **Mandate (user, verbatim intent):** a complete new
|
||||
movement + animation system, verbatim retail equivalent — all movement,
|
||||
inbound and outbound, all animation, for players, NPCs, and monsters. No
|
||||
frozen code, no bandaids, no guessing, no approximation. Total overhaul.
|
||||
|
||||
**Execution shape:** a staged verbatim RECONSTRUCTION, not a big-bang
|
||||
cutover — each stage builds a retail class 1:1 with a conformance harness,
|
||||
cuts consumers over, and DELETES the legacy path in the same stage. "New
|
||||
system" is the destination; the client keeps running between stages. The
|
||||
cdb-golden technique is proven (183/183 live-retail dispatch conformance in
|
||||
S2a) and is the acceptance mechanism for every stage: the user's eyes are a
|
||||
final sanity pass only.
|
||||
|
||||
Supersedes the L.2g S3–S6 slice plan (S1/S2/S5 landed and are absorbed as
|
||||
components). This doc is the plan of record; the deviation map
|
||||
(`docs/research/2026-07-02-inbound-motion-deviation-map.md`), the funnel
|
||||
pseudocode (`…-s2-inbound-funnel-pseudocode.md`), and the 2026-06-04
|
||||
sequencer deep-dive are its research base.
|
||||
|
||||
## The retail module map to reconstruct (per physics object, ALL classes)
|
||||
|
||||
```
|
||||
CPhysicsObj (local player, remote player, NPC, monster — ONE pipeline)
|
||||
├─ MovementManager unpack_movement 0x00524440 (10-way dispatch),
|
||||
│ │ PerformMovement, MotionDone relay
|
||||
│ ├─ CMotionInterp raw_state + interpreted_state + pending_motions,
|
||||
│ │ DoMotion / DoInterpretedMotion / StopInterpretedMotion,
|
||||
│ │ apply_raw/interpreted/current_movement, my_run_rate,
|
||||
│ │ HitGround / LeaveGround / ReportExhaustion, jump family
|
||||
│ └─ MoveToManager movement types 6/7/8/9 (MoveToObject/Position,
|
||||
│ TurnToObject/Heading), node stepping, arrival radii,
|
||||
│ fail distance, CanCharge walk/run selection
|
||||
├─ CPartArray → MotionTableManager pending_animations, add_to_queue 0x0051bfe0,
|
||||
│ │ remove_redundant_links 0x0051bf20,
|
||||
│ │ CheckForCompletedMotions 0x0051be00 → AnimationDone
|
||||
│ └─ CMotionTable::GetObjectSequence 0x00522860
|
||||
│ │ same-substate fast path (change_cycle_speed +
|
||||
│ │ subtract/combine_motion), link path (get_link +
|
||||
│ │ style-default double-hop), re_modify, is_allowed
|
||||
│ └─ CSequence anim-node DLList, append_animation 0x00525510,
|
||||
│ remove_cyclic_anims, clear_physics, velocity/omega
|
||||
│ accumulators, update/update_internal + apply_physics
|
||||
│ (root motion), placement frames, hook dispatch
|
||||
├─ PositionManager InterpolationManager [PORTED ✓ L.3] + StickyManager
|
||||
│ + ConstraintManager
|
||||
└─ per-tick UpdateObjectInternal 0x005156b0 order:
|
||||
CPartArray.Update (CSequence root motion) → PositionManager.adjust_offset
|
||||
(chase REPLACES) → Frame.combine → UpdatePhysicsInternal → FULL transition
|
||||
sweep (ALL entities — retires the remote no-sweep fork) → DetectionManager /
|
||||
TargetManager / MovementManager.UseTime / CPartArray.HandleMovement /
|
||||
PositionManager.UseTime. `process_hooks` is inside UpdatePositionInternal,
|
||||
before the sweep and manager tail (corrected 2026-07-19 from the named
|
||||
retail body; see `docs/research/2026-07-19-r6-update-object-order-pseudocode.md`).
|
||||
```
|
||||
|
||||
## Already-verbatim components (absorbed, not rewritten)
|
||||
|
||||
- `MotionSequenceGate` (S1) — is_newer 3-stamp gate, live-validated.
|
||||
- Inbound funnel: `MoveToInterpretedState` / `ApplyInterpretedMovement` /
|
||||
`DispatchInterpretedMotion` / verbatim `contact_allows_move` (S2a) +
|
||||
183-case live-trace conformance suite.
|
||||
- `InterpolationManager` (L.3) — chase constants re-verified 2026-07-02.
|
||||
- Outbound: `RawMotionState::Pack` default-difference, MoveToStatePack
|
||||
trailer, JumpPack (L.2b/D6.2b, golden-byte tests); dual command catalogs
|
||||
(L.1b); `adjust_motion`/`apply_run_to_command`/`apply_raw_movement`/
|
||||
`get_state_velocity` (D6).
|
||||
|
||||
Everything else is reconstruction scope — especially: `AnimationSequencer`
|
||||
internals (becomes the CSequence port's host or is replaced by it), the
|
||||
three per-tick drive paths in `GameWindow.TickAnimations`, `RemoteMotionSink`
|
||||
(temporary S2b seam — dissolves into GetObjectSequence), `RemoteMoveToDriver`,
|
||||
`ServerControlledLocomotion`, the motion half of `PlayerMovementController`,
|
||||
the 300 ms stop-detection window, NPC UP hard-snaps.
|
||||
|
||||
## Stage plan (each: pseudocode → harness → port → cutover → DELETE legacy → register sweep)
|
||||
|
||||
- **R1 — CSequence verbatim. SHIPPED 2026-07-02** (commits 1371c2a1 P0/P1, 778744bf P2, 5138b8fb P3, 658b91d8 P4, 9147344a P5, +P6): the verbatim core (AnimSequenceNode/CSequence/FrameOps, 56 conformance tests) + the AnimationSequencer adapter rehost deleting the legacy epsilon/stale-head/safety-cap/per-node-flag mechanisms; root motion flows through Advance(dt, Frame) = retail update(Frame*). Registers AD-33/AD-34. Node list, framerate/rate math, velocity+
|
||||
omega accumulators (set/combine/subtract), update/update_internal root
|
||||
motion, apply_physics, placement frames, hook dispatch. Goldens: dat
|
||||
MotionData fixtures + a cdb trace of append_animation/remove_cyclic_anims
|
||||
args (script pattern: tools/cdb/l2g-observer.cdb). Cutover: becomes the
|
||||
sequencer core behind the existing AnimationSequencer API, then the API
|
||||
narrows to retail's.
|
||||
- **R2 — GetObjectSequence + MotionTableManager. SHIPPED 2026-07-02 (pending
|
||||
the stage visual pass).** Fast path,
|
||||
link path (restores the walk↔run link pose — old S4), re_modify (modifier
|
||||
blend — retires AP-73), pending_animations + remove_redundant_links +
|
||||
CheckForCompletedMotions → AnimationDone→MotionDone chain (old S3).
|
||||
RemoteMotionSink's single-cycle pick DELETED — GetObjectSequence decides.
|
||||
Progress: Q0 pins (dc54a3e4) + Q1 MotionState (2345da30), Q2 CMotionTable all-4-branch
|
||||
GetObjectSequence + statics (98f58db9, 44 tests), Q3 MotionTableManager+
|
||||
pending_animations (aa65990a, 47 tests), Q4 adapter cutover (3b9d9bb6 —
|
||||
SetCycle/PlayAction → PerformMovement; Fix B / fast-path / stop-anim
|
||||
fallback / G17 gate DELETED; 11-scenario trace conformance a6235a36).
|
||||
Q5 RemoteMotionSink DELETED (d82f07d4 — funnel → MotionTableDispatchSink →
|
||||
PerformMovement; AP-73 retired; spawn/despawn run initialize_state/
|
||||
HandleExitWorld; live smoke green: MOTIONDONE chain firing in-world).
|
||||
Q6 sweep done with Q5. OUTSTANDING: the ONE user visual pass (walk↔run
|
||||
stride continuity, turn-while-running legs, emote overlay, stop settle).
|
||||
R3 prep done in parallel: research base 8eff3978 + W0 pins cd0289be (all
|
||||
10 ambiguities resolved, adversarially verified).
|
||||
- **R3 — CMotionInterp completion. SHIPPED 2026-07-03 (pending the stage
|
||||
visual pass).** pending_motions/MotionDone, DoMotion, jump family
|
||||
(jump_charge_is_allowed/motion_allows_jump verbatim — the misattribution
|
||||
found in S2a), HitGround/LeaveGround/ReportExhaustion, enter_default_state.
|
||||
LOCAL PLAYER unified (edge-driven CommandInterpreter altitude; the
|
||||
synthesis layer + UpdatePlayerAnimation deleted).
|
||||
Trail: W-1 research 8eff3978, W0 pins cd0289be (A1-A10 adversarially
|
||||
verified), W1 state completion 86649591, W2 pending_motions 37167991,
|
||||
W3 jump family af476444, W4 ground transitions + K-fix18 DELETED
|
||||
e214acdf, W5 one-DoInterpretedMotion + zero-tick flush df7b096d
|
||||
(discovery: the dispatch RESULT gates queue+state writes — sink returns
|
||||
bool), W6 local-player unification fb7beb70 (map fc5a2cda; discoveries:
|
||||
ChargeJump never wired, CurrentHoldKey shadow-field staleness, the
|
||||
autonomous-flag clobber). Registers through the arc: retired AP-73/
|
||||
AP-74/AP-78/TS-34/IA-4 + the Fix-B-class inventions; added AD-36
|
||||
(narrowed), AP-75/76/77, TS-35/36/37/38. EXPECTED-DIFFS for the visual
|
||||
pass: #45 sidestep factor + ANIM_SPEED_SCALE retired (local now matches
|
||||
remotes), auto-walk-at-run walk-pace legs (R4), ApplyServerRunRate echo
|
||||
live through fast re-speed. R3+R2 share ONE visual pass.
|
||||
- **R4 — MoveToManager verbatim. SHIPPED 2026-07-03 (pending the stage
|
||||
visual pass).** Types 6/7/8/9 (TurnToObject/TurnToHeading — the dropped
|
||||
D9/DEV-5 commands), node stepping, arrival, fail distance, CanCharge.
|
||||
RemoteMoveToDriver + ServerControlledLocomotion.PlanMoveToStart + B.6
|
||||
auto-walk all DELETED.
|
||||
Trail: research base 988304e1, V0 pins 386b1ce5 (P1-P7 resolved; P1
|
||||
autonomous-echo gate + P3 heading_diff mirror adversarially sealed, P3
|
||||
down to instruction bytes), V1 command-selection family e0d2492c
|
||||
(GetCommand + CanCharge fast-path + MoveToMath), V2 the verbatim manager
|
||||
addc8e97 (all 33 members, 101 tests, seam-injected harness), V3 wire
|
||||
completion a144e873 (mt 8/9 parse + full params exposure + sticky
|
||||
trailer), V4 remote cutover 7016b26c (per-remote manager, P4
|
||||
TargetTracker adapter, retail unpack dispatch; retired AD-8/AD-9/AP-8/
|
||||
AP-9; smoke log verified clean), V5 local-player cutover b3decdfa (P1
|
||||
gate ported verbatim — ACE's autonomous echo dropped before unpack;
|
||||
B.6 deleted; TS-36 bound — input/jump/teleport cancel through the retail
|
||||
interrupt chain; run-rate re-anchored to PD skills + mt-6/7 my_run_rate,
|
||||
echo tap deleted with NO AD row; MoveToComplete client seam widened to
|
||||
natural completion for AD-27; adversarial-review fixes: remote HitGround
|
||||
relay, mt-8 wire_heading degrade, remote curTime clock), V6 register/
|
||||
docs sweep (AD-34 widened with the MoveToNode rename, NEW TS-39 StickTo/
|
||||
Unstick no-op seams → R5, TS-33 extended with the orientation-diff gap,
|
||||
TS-21/AD-25/AP-24/AP-30 re-anchored). EXPECTED-DIFFS for the visual
|
||||
pass: melee-range stop distance (retail cylinder distance — the AD-8
|
||||
max() class is gone), auto-walk legs now walk/run per CanCharge with
|
||||
real turn cycles during corrections, walk-pace close-in demote.
|
||||
OUTSTANDING: the stage visual pass (folds into the pending R2+R3 pass).
|
||||
- **R5 — MovementManager + MovementSystem facade.** One per-object pipeline
|
||||
for every entity class; StickyManager (stick_to_object — the motionFlags
|
||||
0x100 bit) + ConstraintManager ports; GameWindow's OnLiveMotionUpdated
|
||||
shrinks to parse→MovementSystem.HandleMovementEvent.
|
||||
- **R6 — per-tick UpdateObjectInternal order.** Retail tick order for ALL
|
||||
entities incl. the transition sweep for remotes (retires the L.3-M2
|
||||
no-sweep fork + the path A/B split + the 300 ms stop window + NPC UP
|
||||
hard-snap special cases). GameWindow.TickAnimations sheds its motion
|
||||
logic entirely (Code Structure Rule 1).
|
||||
- **R7 — outbound autonomy cadence.** ShouldSendPositionEvent (0x006b45e0)
|
||||
+ the MTS/AP stamp split — retires TS-33. Outbound becomes 100% verbatim.
|
||||
- **R8 — cutover audit.** grep-sweep for legacy motion code, register
|
||||
reconciliation (every AD/AP/TS motion row retired or re-justified), full
|
||||
live protocol (walk/run/toggle/turn/circle/stop/jump/MoveTo/TurnTo,
|
||||
player+NPC+monster), ONE final user visual pass.
|
||||
|
||||
## Standing rules for every stage
|
||||
|
||||
1. Decomp is the oracle; ACE the interpretation aid; cdb/TTD the runtime
|
||||
arbiter when they disagree (the S0/S2 pattern).
|
||||
2. No stage ships without its conformance fixtures. Golden sources: dat
|
||||
tables, cdb traces (both observer + actor side), captured wire logs.
|
||||
3. Every commit: build + full suite green; register rows added/retired in
|
||||
the same commit; roadmap stage table updated on stage completion.
|
||||
4. New code lives in `src/AcDream.Core/Physics/Motion/` (pure logic; GL-free)
|
||||
with App seams only for rendering handoff (Structure Rule 2).
|
||||
5. Delete, don't gate: when a stage cuts over, the legacy path is REMOVED in
|
||||
that stage (mandate: no frozen code, no bandaids).
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
# Automated world-lifecycle gate
|
||||
|
||||
**Status:** Complete (2026-07-20)
|
||||
**Owner:** M3 stabilization / R6 rendering-lifecycle rebaseline
|
||||
**Depends on:** `WorldRevealReadinessBarrier`, retained-UI automation,
|
||||
`FrameProfiler`, connected local ACE
|
||||
|
||||
## Outcome
|
||||
|
||||
Create a deterministic connected Release gate that catches the structural
|
||||
failures which previously required someone to stare at the client for several
|
||||
minutes:
|
||||
|
||||
- the first login frame cannot expose world geometry before the destination's
|
||||
render meshes, composite textures, and collision data are all ready;
|
||||
- every portal/recall lifetime crosses the same readiness edge, materializes
|
||||
once, and returns to the normal viewport once;
|
||||
- dungeon entry and exit use the same lifecycle without grey frames,
|
||||
stale-source geometry, or an uncollidable destination;
|
||||
- graceful logout followed by a fresh reconnect starts a new reveal lifetime
|
||||
and cannot inherit readiness or resources from the previous process;
|
||||
- repeated destinations do not cause cumulative memory, owner-count,
|
||||
allocation, update-time, or GPU-time growth;
|
||||
- each stable checkpoint produces machine-readable state plus a screenshot for
|
||||
later human comparison.
|
||||
|
||||
This is a correctness and resource-lifetime gate. Screenshots preserve visual
|
||||
evidence, but automation does **not** claim retail visual equivalence. Color,
|
||||
composition, animation feel, and subtle geometry artifacts remain user gates.
|
||||
|
||||
## Canonical runtime contract
|
||||
|
||||
`WorldRevealReadinessBarrier` remains the only definition of destination
|
||||
readiness. It will expose a structured snapshot rather than forcing telemetry
|
||||
to repeat the readiness formula. A snapshot records the destination cell,
|
||||
indoor/outdoor classification, required radius, render publication, composite
|
||||
texture readiness, collision readiness, an impossible/unhydratable claim, and
|
||||
the final `Ready` result.
|
||||
|
||||
A focused App-layer `WorldRevealLifecycleTelemetry` owns diagnostic reveal
|
||||
generations. It has no GL calls and no knowledge of `GameWindow`. For each
|
||||
login or portal generation it records:
|
||||
|
||||
1. begin;
|
||||
2. destination acquisition;
|
||||
3. each distinct readiness transition;
|
||||
4. the first frame where the normal world viewport is actually eligible to
|
||||
draw;
|
||||
5. completion or cancellation.
|
||||
|
||||
Making the normal viewport visible before `Ready` is a hard invariant failure,
|
||||
not a warning. A newer generation retires the older one, so stale asynchronous
|
||||
completion cannot satisfy the new destination.
|
||||
|
||||
`GameWindow` only supplies facts and invokes the controller at existing
|
||||
lifecycle seams. It does not gain a new feature body.
|
||||
|
||||
## Production automation seam
|
||||
|
||||
The retained-UI automation runner gains runtime commands through an injected
|
||||
interface:
|
||||
|
||||
- `wait world-ready [timeout-ms]` waits on canonical lifecycle state instead
|
||||
of a guessed sleep;
|
||||
- `wait materialized <occurrence> [timeout-ms]` waits for the authoritative
|
||||
portal completion count;
|
||||
- `checkpoint <name>` emits one JSON record containing reveal state,
|
||||
landblock/entity/animation and resource-owner counts, managed memory, and
|
||||
the latest frame-profiler summary;
|
||||
- `screenshot <name> [timeout-ms]` queues a default-framebuffer capture after
|
||||
the complete world and retained UI have drawn, then waits for the render
|
||||
thread to finish the PNG;
|
||||
- existing `input` commands perform deterministic turns and movement through
|
||||
`InputDispatcher`.
|
||||
|
||||
All state mutation and GL readback stay on the update/render thread. The
|
||||
external gate only launches the process, observes files/logs and OS process
|
||||
metrics, and requests normal `WM_CLOSE` shutdown.
|
||||
|
||||
## Connected route
|
||||
|
||||
The gate runs two capped/uncapped-aware sessions against local ACE:
|
||||
|
||||
1. fresh login → wait for canonical readiness → checkpoint + screenshot;
|
||||
2. outdoor dense scene → turn → stable checkpoint + screenshot;
|
||||
3. world-edge scene → turn → stable checkpoint;
|
||||
4. known dungeon destination → checkpoint + screenshot → local movement;
|
||||
5. return outdoors → checkpoint + screenshot;
|
||||
6. revisit the first dense scene → stable resource comparison;
|
||||
7. graceful close;
|
||||
8. reconnect the same account → repeat the fresh-login readiness and
|
||||
screenshot gate → graceful close.
|
||||
|
||||
The existing seven-destination R6 soak remains the longer performance route.
|
||||
The lifecycle gate is shorter and more diagnostic; it composes with that soak
|
||||
rather than replacing it.
|
||||
|
||||
## Hard failures
|
||||
|
||||
- world viewport observed before the active reveal snapshot is ready;
|
||||
- destination never reaches readiness or materialization within the bounded
|
||||
connected test timeout;
|
||||
- missing or duplicate reveal/materialization/completion edges;
|
||||
- missing checkpoint JSON or screenshot, zero-sized/corrupt screenshot;
|
||||
- absent required destination landblock/EnvCell/collision readiness;
|
||||
- retained per-owner resources after delete/session teardown in deterministic
|
||||
tests;
|
||||
- same-location resource counts or memory/per-frame allocation grow beyond the
|
||||
route's documented relative tolerance;
|
||||
- unhandled exception, access violation, OOM, GL/device loss, disconnect,
|
||||
unexpected WeenieError, nonzero exit, or failure of graceful shutdown.
|
||||
|
||||
Absolute FPS is recorded, never compared across local and RDP sessions. The
|
||||
gate compares like-for-like samples within one process and reports CPU and GPU
|
||||
frame time separately.
|
||||
|
||||
## Implementation sequence
|
||||
|
||||
1. Add a structured readiness snapshot and exhaustive barrier tests.
|
||||
2. Add lifecycle telemetry as a pure App owner with stale-generation,
|
||||
cancellation, duplicate, and early-visible tests.
|
||||
3. Add the automation runtime interface, deterministic waits, checkpoints, and
|
||||
screenshot request/completion protocol.
|
||||
4. Add the render-thread screenshot owner and structured resource snapshots.
|
||||
5. Wire the owners at login, F751 portal start, TAS placement/completion, world
|
||||
draw eligibility, and session teardown.
|
||||
6. Add the connected route and PowerShell report/gate orchestration.
|
||||
7. Run capped and uncapped Release gates, fix root causes, and retain artifacts.
|
||||
8. Extract the now-protected streaming/portal/reveal frame orchestration from
|
||||
`GameWindow` without changing the accepted lifecycle trace.
|
||||
9. Run focused tests, full Release build/test, connected gates, graceful-close
|
||||
verification, and documentation reconciliation.
|
||||
|
||||
## Completion evidence
|
||||
|
||||
- Focused App tests cover readiness composition, stale generations,
|
||||
cancellation, duplicate lifecycle edges, early-visible rejection, artifact
|
||||
capture, automation timeouts, and resource snapshots.
|
||||
- `dotnet build AcDream.slnx -c Release` succeeds. The complete repository
|
||||
suite is 6,480 passed / 5 skipped; the 17 existing test-project warnings
|
||||
remain tracked by issue #228.
|
||||
- The final two-process connected report passed with no failures. The capped
|
||||
process completed fresh login, Aerlinthe, Rynthid, Facility Hub, Holtburg,
|
||||
and an Aerlinthe revisit in 244.424 seconds. The uncapped process then
|
||||
reconnected the same account and completed in 61.717 seconds.
|
||||
- Both processes closed through `WM_CLOSE`, received the authoritative
|
||||
character-logoff confirmation, exited with code 0, and were matched to
|
||||
ACE's exact client UDP endpoint and `PacketHeader Disconnect` record.
|
||||
- Every checkpoint had zero pending live teardowns, landblock retirements,
|
||||
staged mesh uploads, composite warmups, and reveal invariant failures.
|
||||
- All six PNG artifacts were valid and inspected. Login, indoor dungeon,
|
||||
dungeon exit, Aerlinthe revisit, and reconnect showed complete world
|
||||
geometry rather than a grey or partially revealed destination.
|
||||
- The uncapped reconnect sampled 172.7 FPS / 5.79 ms at the checkpoint; its
|
||||
latest profiler window measured 5.7 ms CPU p50, 6.1 ms p95 and 5.7 ms GPU
|
||||
p50, 6.0 ms p95 with zero pacing time. This is machine-specific throughput
|
||||
evidence, not a cross-display FPS requirement.
|
||||
- The Aerlinthe revisit retained balanced owners and stayed inside the
|
||||
documented relative memory/resource tolerances. One circuit is not claimed
|
||||
as a permanent memory plateau proof; the longer seven-destination R6 soak
|
||||
remains the endurance gate.
|
||||
- `WorldRevealCoordinator` now owns the protected lifecycle outside
|
||||
`GameWindow`; the accepted trace did not change after extraction.
|
||||
|
||||
## Landed commits
|
||||
|
||||
- `db45b81f` — expose canonical readiness snapshots and lifecycle telemetry;
|
||||
- `354c2adc` — add deterministic checkpoints and framebuffer screenshots;
|
||||
- `b03371c0` — add the connected lifecycle/reconnect gate;
|
||||
- `68578fa5` — port retail graceful character logout and transport disconnect;
|
||||
- `a4ef5788` — centralize the reveal lifetime in `WorldRevealCoordinator`;
|
||||
- `f7b09617` — preserve singleton endpoint/checkpoint collections in the gate.
|
||||
|
||||
The remaining visual work is deliberately outside this gate: the R6
|
||||
locomotion/collision/projectile/teleport retail comparison, the two-client
|
||||
portal-out/materialization observer comparison, and issue #225's translucent
|
||||
lifestone/particle ordering comparison.
|
||||
|
|
@ -1,352 +0,0 @@
|
|||
# GameWindow Slice 1 — selection and interaction ownership
|
||||
|
||||
**Status:** COMPLETE — landed 2026-07-21.
|
||||
**Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 1.
|
||||
**Baseline:** `cf50ee3d`; `GameWindow.cs` is 15,723 lines before this slice.
|
||||
**Behavior rule:** Existing retail behavior moves unchanged. Any defect found by
|
||||
the extraction audit is fixed in a separately identified commit before the
|
||||
mechanical cutover.
|
||||
|
||||
## Landing record
|
||||
|
||||
- Plan/oracle: `c2713837`.
|
||||
- Exact-incarnation lifetime hardening: `047a4c83`.
|
||||
- Retail pending keyboard-pickup presentation: `52dbb574`.
|
||||
- Read-only query extraction: `e74f2ca9`.
|
||||
- Stateful interaction owner: `fa8d5232`.
|
||||
- `GameWindow` cutover: `d2bb5af4`.
|
||||
- Three-review correction pass: `5acc3f01`.
|
||||
|
||||
The correction pass unified retail's one-inventory-request-at-a-time owner
|
||||
across every mutation surface, separated optimistic/rollback/authoritative
|
||||
placement notices, made response completion atomic and reentrancy-safe, and
|
||||
bound pending work to exact object identities and placement tokens.
|
||||
|
||||
Final metrics: `GameWindow.cs` 15,723 → 14,912 lines and 205 → 191 methods;
|
||||
fields remained 278. Release build passed, the focused selection/inventory
|
||||
gate passed, the full suite passed 6,558 tests with five intentional skips,
|
||||
and the retail-conformance, architecture/integration, and adversarial reviews
|
||||
all finished with no actionable findings.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
`GameWindow` stops owning world selection and selection-driven interaction.
|
||||
After this slice it retains only composition and narrow lifecycle calls:
|
||||
|
||||
- construct the query/controller;
|
||||
- feed the completed render-selection frame;
|
||||
- drain outbound interactions at the existing retail frame boundary;
|
||||
- forward input actions and MoveTo completion;
|
||||
- forward entity hidden/removal and session-reset edges.
|
||||
|
||||
Two App owners replace the current body:
|
||||
|
||||
1. `WorldSelectionQuery` owns read-only world selection, eligibility,
|
||||
classification, naming, closest-hostile lookup, target bounds, and range
|
||||
queries over canonical live state.
|
||||
2. `SelectionInteractionController` owns selection intents, click routing,
|
||||
Use/PickUp ordering, speculative facing/movement, deferred close-range
|
||||
requests, and the input-action boundary.
|
||||
|
||||
Core `SelectionState` remains the sole selected-object truth.
|
||||
`ItemInteractionController` remains the sole retail ItemHolder policy owner.
|
||||
`LiveEntityRuntime` remains the sole GUID/incarnation/visibility owner.
|
||||
|
||||
## 2. Retail and project oracles
|
||||
|
||||
No AC algorithm is invented in this slice. Move the existing ports with these
|
||||
references attached:
|
||||
|
||||
- [`2026-07-17-retail-world-selection-pseudocode.md`](../research/2026-07-17-retail-world-selection-pseudocode.md)
|
||||
— render-coupled part selection, selection sphere, vivid target, click pulse.
|
||||
- [`2026-07-10-retail-toolbar-interaction-pseudocode.md`](../research/2026-07-10-retail-toolbar-interaction-pseudocode.md)
|
||||
— target-mode precedence, ItemHolder Use, selection notices.
|
||||
- [`2026-07-17-retail-external-container-looting-pseudocode.md`](../research/2026-07-17-retail-external-container-looting-pseudocode.md)
|
||||
— corpse/container open, range, pending loot placement.
|
||||
- [`2026-07-12-death-and-auto-target-pseudocode.md`](../research/2026-07-12-death-and-auto-target-pseudocode.md)
|
||||
— synchronous selection clear and reentrant Auto Target.
|
||||
- `Render::GfxObjUnderSelectionRay @ 0x0054C740`.
|
||||
- `ACCWeenieObject::SetSelectedObject @ 0x0058C2E0`.
|
||||
- `CPlayerSystem::OnAction @ 0x00561890`.
|
||||
- `CPlayerSystem::PlaceInBackpack @ 0x0055D8C0`.
|
||||
- `ItemHolder::UseObject @ 0x00588A80`.
|
||||
- `UIElement_SmartBoxWrapper::RecvNotice_SmartBoxObjectFound @ 0x004E5AD0`.
|
||||
|
||||
Existing registered adaptations remain adaptations; this structural slice does
|
||||
not silently “improve” them:
|
||||
|
||||
- IA-19: automatic acquisition is narrowed to hostile non-player monsters.
|
||||
- AD-11: ACE null-useability fallback.
|
||||
- AD-27: natural `MoveToComplete` is the client completion seam.
|
||||
- AP-23: speculative local Use movement and 3/2/0.6-unit radius buckets.
|
||||
|
||||
Their file pointers move in the same commit as their implementation bodies.
|
||||
|
||||
## 3. Invariants
|
||||
|
||||
### 3.1 Render selection
|
||||
|
||||
- The renderer builds a private list, then publishes it only at
|
||||
`CompleteFrame`; input reads the previous complete frame.
|
||||
- A hit carries server GUID **and local entity ID**. The query accepts it only
|
||||
if `LiveEntityRuntime` still resolves the same current, visible top-level
|
||||
incarnation.
|
||||
- Plain selection excludes self. Active target mode includes self.
|
||||
- A successful hit starts the HIGH click-light pulse before target-mode or
|
||||
selection routing.
|
||||
- Target-mode success or rejection consumes the click and never falls through
|
||||
to ordinary selection/use.
|
||||
- A miss does not clear selection. A miss during target mode leaves target mode
|
||||
active.
|
||||
- Drag release may pulse the target, but does not select it.
|
||||
- Vivid target markers deliberately query materialized state so selected
|
||||
objects remain indicated through walls and long distances. Picking and new
|
||||
combat acquisition use visible interaction state only.
|
||||
|
||||
### 3.2 Input and outbound ordering
|
||||
|
||||
- Keep the one `InputDispatcher.Fired` subscription in `GameWindow`.
|
||||
- Combat attack handling, Press/DoubleClick filtering, and retained-UI capture
|
||||
occur before delegation exactly as today.
|
||||
- The controller handles only the currently implemented semantic actions:
|
||||
previous selection, closest monster, left selection, double-left selection,
|
||||
Use selected, and PickUp selected.
|
||||
- Input callbacks enqueue Use/PickUp. The queue drains after local movement
|
||||
output and before inbound packet dispatch.
|
||||
- The queue drains only its boundary snapshot; reentrant enqueue waits until
|
||||
the next frame.
|
||||
|
||||
### 3.3 Use/PickUp and movement
|
||||
|
||||
- `ItemInteractionController` decides Use/open/target mode. The new controller
|
||||
does not copy ItemHolder policy.
|
||||
- Far target: install speculative MoveTo and send exactly one wire request
|
||||
immediately. ACE owns its arrival callback.
|
||||
- Close target: install TurnToObject and defer the **first** request until
|
||||
natural `MoveToComplete(None)`.
|
||||
- Cancel/error completion never sends.
|
||||
- The movement intent preserves target cylinder radius/height, the AP-23 use
|
||||
radius, and the 7.5-unit CanCharge prediction.
|
||||
- Every queued/deferred request is bound to the captured live incarnation and
|
||||
revalidated before sending.
|
||||
- Pickup preserves destination container and placement. Retail pending-slot
|
||||
presentation occurs before the request.
|
||||
|
||||
### 3.4 Lifecycle
|
||||
|
||||
- Hidden, logical removal, session reset, and GUID replacement invalidate old
|
||||
hits and pending actions immediately.
|
||||
- Token-specific pending cleanup is allowed even after GUID replacement;
|
||||
GUID-only selection cleanup retains the existing incarnation guard so old
|
||||
teardown cannot clear a newly selected replacement.
|
||||
- Session reset atomically clears selection history, outbound queue, deferred
|
||||
action, item target mode/busy state, published selection frame, and click
|
||||
pulse.
|
||||
- `CombatTargetController` must not auto-acquire during `SessionReset`.
|
||||
- No new GUID map, selection store, visibility store, or retry timer is added.
|
||||
|
||||
## 4. Target interfaces
|
||||
|
||||
### 4.1 Render hit identity
|
||||
|
||||
`RetailSelectionPart` and `RetailSelectionHit` carry `LocalEntityId` alongside
|
||||
`ServerGuid`. `RetailSelectionScene.Pick` returns the complete hit. Its lighting
|
||||
source is also keyed by both identities so a reused GUID cannot inherit an old
|
||||
click pulse.
|
||||
|
||||
`RetailSelectionScene.Reset` clears building/published lists, frame keys,
|
||||
frustum state, and pulse state. Geometry resolution receives a narrow internal
|
||||
source interface so publication behavior is testable without installed DAT.
|
||||
|
||||
### 4.2 `WorldSelectionQuery`
|
||||
|
||||
The query receives:
|
||||
|
||||
- canonical `LiveEntityRuntime`;
|
||||
- `ClientObjectTable`;
|
||||
- the shared `RetailSelectionScene`;
|
||||
- player GUID and current player position providers;
|
||||
- camera/viewport and cursor providers;
|
||||
- a DAT selection-sphere resolver;
|
||||
- the existing Setup-cylinder resolver.
|
||||
|
||||
It exposes read-only operations for:
|
||||
|
||||
- pick at cursor/screen point with exact-incarnation validation;
|
||||
- current interaction record/entity lookup;
|
||||
- item type, name, and description;
|
||||
- creature/hostile/health eligibility;
|
||||
- closest visible hostile result without mutating selection;
|
||||
- combat camera target;
|
||||
- vivid target information and selection sphere;
|
||||
- useability/pickupability;
|
||||
- AP-23 use radius, close-range, CanCharge, and target movement data;
|
||||
- external-container cylinder range.
|
||||
|
||||
The query never mutates selection, queues work, sends packets, or starts
|
||||
movement.
|
||||
|
||||
### 4.3 `SelectionInteractionController`
|
||||
|
||||
The controller receives:
|
||||
|
||||
- `SelectionState` and `WorldSelectionQuery`;
|
||||
- `ItemInteractionController` provider;
|
||||
- a typed Use/PickUp transport;
|
||||
- a narrow movement-intent sink;
|
||||
- toast/system diagnostic sinks;
|
||||
- Auto Target setting provider.
|
||||
|
||||
It owns `OutboundInteractionQueue` and an incarnation-bound
|
||||
`PendingPostArrivalAction`. Public methods form the complete host seam:
|
||||
|
||||
```text
|
||||
HandleInputAction
|
||||
PickAtCursor / PlaceDraggedItem
|
||||
SelectClosestCombatTarget
|
||||
GetSelectedOrClosestCombatTarget
|
||||
SendUse / SendPickUp
|
||||
DrainOutbound
|
||||
OnNaturalMoveToComplete
|
||||
OnEntityHidden / OnEntityRemoved
|
||||
ResetSession
|
||||
```
|
||||
|
||||
The controller does not subscribe directly to Silk input and does not own
|
||||
rendering, combat state, the session, or item policy.
|
||||
|
||||
## 5. Execution sequence
|
||||
|
||||
### Commit 0 — plan and defect registration
|
||||
|
||||
- Land this plan.
|
||||
- Register #230 for GUID-incarnation/session-reset leakage.
|
||||
- Register #231 for F-key pickup missing the pending destination reservation.
|
||||
- No runtime behavior change.
|
||||
|
||||
### Commit 1 — lifecycle identity hardening (#230)
|
||||
|
||||
- Add local-entity identity to render parts/hits and selection lighting.
|
||||
- Add scene reset and deterministic publication tests.
|
||||
- Validate published hits against current visible incarnation.
|
||||
- Bind pending post-arrival actions to a live record/local identity.
|
||||
- Clear only the matching pending token on teardown, including GUID reuse.
|
||||
- Make session reset clear scene and item interaction state.
|
||||
- Prevent combat Auto Target during `SessionReset`.
|
||||
- Keep this root-cause correction independently bisectable.
|
||||
|
||||
### Commit 2 — retail pending pickup presentation (#231)
|
||||
|
||||
- Add one ItemInteractionController entry point for a validated world pickup.
|
||||
- Publish `PendingBackpackPlacementRequested` before calling the existing
|
||||
placement delegate.
|
||||
- Route F-key pickup through it with destination/placement unchanged.
|
||||
- Do not optimistically move ownership; server confirmation/failure remains
|
||||
authoritative.
|
||||
|
||||
### Commit 3 — extract `WorldSelectionQuery`
|
||||
|
||||
- Move every read-only selection/classification/range body from `GameWindow`.
|
||||
- Preserve materialized-versus-visible semantics.
|
||||
- Extend the live animation read seam with current motion so dead eligibility
|
||||
does not depend on `GameWindow.AnimatedEntity`.
|
||||
- Rewire cursor, vivid target, toolbar health, combat camera, external-container
|
||||
range, drag target, and door-name reads through the query.
|
||||
- Delete the corresponding `GameWindow` methods.
|
||||
|
||||
### Commit 4 — extract `SelectionInteractionController`
|
||||
|
||||
- Move world click routing, selection mutation, closest-target selection,
|
||||
outbound queue, Use/PickUp, movement intent, deferred completion, and
|
||||
hidden/removal/reset handling.
|
||||
- Compose `ItemInteractionController`; do not duplicate its policies.
|
||||
- Update IA-19/AD-11/AD-27/AP-23 file pointers.
|
||||
- Add focused controller tests for every invariant in §3.
|
||||
|
||||
### Commit 5 — input cutover and host cleanup
|
||||
|
||||
- Replace the six selection/interaction switch cases with one controller
|
||||
delegation call at the same ordered position.
|
||||
- Forward render-frame, update-frame, MoveTo completion, hidden/removal,
|
||||
session-reset, cursor, and drag hooks.
|
||||
- Delete old fields/records/wrappers from `GameWindow`.
|
||||
- Prove `GameWindow` no longer owns a selection algorithm or interaction
|
||||
request body.
|
||||
|
||||
### Commit 6 — review, gate, and ledger close
|
||||
|
||||
- Run retail-conformance, architecture/integration, and adversarial read-only
|
||||
reviews against the complete slice diff.
|
||||
- Fix confirmed findings and repeat review until clean.
|
||||
- Run focused Core/App tests, Release solution build, and full Release suite.
|
||||
- Update code-structure ownership status, roadmap/current queue if necessary,
|
||||
both session instruction files, divergence pointers, and durable interaction
|
||||
memory.
|
||||
- Record the new `GameWindow` line/field/method counts.
|
||||
|
||||
## 6. Automated acceptance matrix
|
||||
|
||||
### Render/query
|
||||
|
||||
- Building frame is invisible until `CompleteFrame`.
|
||||
- Last complete frame remains queryable while the next frame builds.
|
||||
- Empty completion clears prior hits.
|
||||
- Hidden, pending, deleted, and replaced incarnations reject stale hits.
|
||||
- A replacement GUID is not pickable until its own part is published.
|
||||
- Pulse is identity-bound and cannot color a replacement.
|
||||
- Plain pick excludes self; target-mode pick includes self.
|
||||
- Vivid target can use materialized state while combat candidates cannot.
|
||||
- Closest hostile excludes self, players, pets, friendly NPCs, dead, Hidden,
|
||||
pending, and deleted objects.
|
||||
|
||||
### Interaction
|
||||
|
||||
- Pulse precedes target-mode and selection routing.
|
||||
- Target-mode success/rejection consumes the click without selection drift.
|
||||
- Single click selects; double click captures the clicked GUID and queues one
|
||||
activation.
|
||||
- Miss preserves selection and target mode.
|
||||
- Previous selection preserves current SelectionState semantics.
|
||||
- Queue drains after movement and only once per captured boundary.
|
||||
- Far Use/PickUp sends once; close sends only on natural completion.
|
||||
- Cancel/error/Hidden/delete/reset sends zero.
|
||||
- Delete then same-GUID create sends zero to the replacement.
|
||||
- Useability and pickupability fallbacks remain byte-for-byte behaviorally
|
||||
equivalent.
|
||||
- Creature and Stuck pickup rejection messages remain exact.
|
||||
- Corpse double-click/Use opens it and never attempts to pick up the corpse.
|
||||
- F pickup reserves the first destination slot before wire send.
|
||||
- Pickup destination/placement survive the deferred path.
|
||||
|
||||
### Lifecycle/input
|
||||
|
||||
- Session reset clears previous selection, target mode, busy state, queue,
|
||||
pending completion, published picks, and pulse; repeated reset is safe.
|
||||
- SessionReset cannot trigger combat auto-target.
|
||||
- Modal/retained UI capture still prevents controller invocation upstream.
|
||||
- Escape target-mode cancellation remains ahead of window-close behavior.
|
||||
|
||||
## 7. Connected gate
|
||||
|
||||
Run one normal capped Release client and verify:
|
||||
|
||||
1. select a nearby door/NPC and see the click pulse and target corners;
|
||||
2. double-click/Use the door or NPC;
|
||||
3. select a distant corpse, press R, approach, and open it;
|
||||
4. double-click one loot item and use F on another; both reserve the expected
|
||||
backpack slot and settle after ACE confirmation;
|
||||
5. enter combat, select a hostile, use closest-target and previous-selection;
|
||||
6. kill the selected target with Auto Target enabled and observe only a hostile
|
||||
replacement;
|
||||
7. portal or relog and confirm no old selection/pulse/action survives.
|
||||
|
||||
Any failure returns to the owning commit. No retry, grace period, or
|
||||
GUID-only suppression is acceptable.
|
||||
|
||||
## 8. Subagent policy for this slice
|
||||
|
||||
The implementation is tightly coupled across `GameWindow`, live lifetime,
|
||||
render publication, input order, and player MoveTo. One primary agent performs
|
||||
all edits. Independent subagents are useful for read-only retail,
|
||||
architecture, and adversarial reviews; they do not edit the shared slice
|
||||
concurrently.
|
||||
|
|
@ -1,392 +0,0 @@
|
|||
# GameWindow Slice 2 — live animation presentation ownership
|
||||
|
||||
**Status:** Complete 2026-07-21. Automated and three-agent review gates pass;
|
||||
connected visual regression gate is carried to the final campaign gate.
|
||||
**Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 2.
|
||||
**Baseline:** `9ad8113c`; `GameWindow.cs` is 14,912 lines, 278 fields, and
|
||||
191 methods before this slice.
|
||||
**Result:** `GameWindow.cs` is 14,546 lines, 277 fields, and 190 methods.
|
||||
The slice also closed stale-schedule ABA across replacement, appearance
|
||||
rebinding, static rebind, and borrowed sequencer buffers. Forty-one focused
|
||||
Release tests and the full 6,575-pass / 5-skip Release suite are green.
|
||||
**Behavior rule:** This is an ownership extraction over the accepted R6 object
|
||||
frame plus one named-retail conformance correction: short AnimFrames retain
|
||||
the prior/rest pose of trailing CPartArray parts. It must not otherwise change
|
||||
sequence time, root motion, collision, manager order, hook semantics, part
|
||||
transforms, render eligibility, or effect attachment.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
`GameWindow` stops owning final animated-part presentation. A focused
|
||||
`LiveEntityAnimationPresenter` consumes the already-completed scheduler output
|
||||
and publishes the exact same draw and effect state:
|
||||
|
||||
- final drawable `MeshRef` transforms;
|
||||
- indexed rigid part poses and availability;
|
||||
- `EntityEffectPoseRegistry` publication;
|
||||
- live static-animation frame handoff;
|
||||
- legacy animation interpolation state;
|
||||
- one-time `MotionDone` binding;
|
||||
- presentation-specific sequence/part diagnostics.
|
||||
|
||||
The shipped owners remain unchanged:
|
||||
|
||||
- `LiveEntityAnimationScheduler` owns ordinary-object clocks, PartArray
|
||||
advance, root motion, one movement owner, control-hook capture, and the
|
||||
retail manager tail;
|
||||
- `RetailStaticAnimatingObjectScheduler` owns the separate retail
|
||||
`static_animating_objects` time/workset and its deferred process-hooks tail;
|
||||
- `EquippedChildRenderController` owns equipped-child composition;
|
||||
- `AnimationHookFrameQueue` owns incarnation-scoped semantic/presentation-hook
|
||||
delivery;
|
||||
- `LiveEntityRuntime` remains the only identity, incarnation, component, and
|
||||
spatial-lifetime owner.
|
||||
|
||||
`GameWindow.AdvanceLiveObjectRuntimeCore` retains only the explicit frame-order
|
||||
calls between these owners.
|
||||
|
||||
## 2. Retail and project oracles
|
||||
|
||||
No new AC algorithm is introduced. The extraction is pinned to:
|
||||
|
||||
- [`2026-07-19-r6-update-object-order-pseudocode.md`](../research/2026-07-19-r6-update-object-order-pseudocode.md)
|
||||
— `UpdatePositionInternal`, `process_hooks`, and manager-tail ordering.
|
||||
- [`2026-07-19-r6-complete-root-frame-pseudocode.md`](../research/2026-07-19-r6-complete-root-frame-pseudocode.md)
|
||||
— complete root `Frame`, ordinary/static worksets, part/effect publication.
|
||||
- [`memory/project_animation_runtime.md`](../../memory/project_animation_runtime.md)
|
||||
— appearance-update and description-before-enter-world lifetime invariants.
|
||||
- `CPhysicsObj::UpdatePositionInternal @ 0x00512C30`.
|
||||
- `CPhysicsObj::UpdateObjectInternal @ 0x005156B0`.
|
||||
- `CPhysicsObj::process_hooks @ 0x00511550`.
|
||||
- `CPhysicsObj::animate_static_object @ 0x00513DF0`.
|
||||
- `CPartArray::Update @ 0x00517DB0`.
|
||||
- `CPartArray::UpdateParts @ 0x005190F0`.
|
||||
- `CPartArray::AnimationDone @ 0x00517D30`.
|
||||
- `CPhysicsObj::MotionDone @ 0x0050FDB0`.
|
||||
- `Frame::combine @ 0x005122E0`.
|
||||
|
||||
ACE/DatReaderWriter and the installed retail DAT remain secondary fixtures for
|
||||
motion-table/animation schema interpretation. Named retail is authoritative
|
||||
for ordering and transform semantics.
|
||||
|
||||
## 3. Fixed frame-order contract
|
||||
|
||||
One ordinary live-object frame remains:
|
||||
|
||||
```text
|
||||
local player pre-network object phase
|
||||
selection outbound boundary
|
||||
publish PhysicsScript clock
|
||||
LiveEntityAnimationScheduler.Tick
|
||||
RetailStaticAnimatingObjectScheduler.Tick
|
||||
LiveEntityAnimationPresenter.Present
|
||||
EquippedChildRenderController.Tick
|
||||
RetailStaticAnimatingObjectScheduler.ProcessHooks
|
||||
translucency advance
|
||||
AnimationHookFrameQueue.Drain
|
||||
effect-root refresh
|
||||
attached-emitter refresh
|
||||
live-light refresh
|
||||
particle visibility
|
||||
particle simulation
|
||||
PhysicsScript simulation
|
||||
```
|
||||
|
||||
The presenter advances no clock and drains no hook. It only consumes poses
|
||||
produced earlier in the same frame. Static `process_hooks` remains after live
|
||||
parts and equipped children, as retail's `animate_static_object` requires.
|
||||
|
||||
## 4. Ownership and interfaces
|
||||
|
||||
### 4.1 Animation component state
|
||||
|
||||
Move `GameWindow.AnimatedEntity` and `GameWindow.AnimatedPartTemplate` to
|
||||
top-level App rendering types:
|
||||
|
||||
```text
|
||||
LiveEntityAnimationState
|
||||
LiveAnimationPartTemplate
|
||||
```
|
||||
|
||||
`LiveEntityAnimationState` remains the exact component stored in
|
||||
`LiveEntityRecord.AnimationRuntime`. It owns no GUID map and no lifetime. It
|
||||
retains:
|
||||
|
||||
- the exact `WorldEntity`, Setup, Animation, and optional sequencer;
|
||||
- legacy low/high/current frame and framerate;
|
||||
- ObjScale, part template, and part availability;
|
||||
- prepared local-player pose and complete root-frame scratch;
|
||||
- reusable `MeshRefs`, effect-part-pose, and diagnostics scratch.
|
||||
|
||||
Appearance updates mutate this same component and preserve sequencer/playback
|
||||
state. Logical replacement creates a new component.
|
||||
|
||||
### 4.2 Presentation context
|
||||
|
||||
Define a narrow App interface equivalent to:
|
||||
|
||||
```csharp
|
||||
interface ILiveAnimationPresentationContext
|
||||
{
|
||||
uint LocalPlayerGuid { get; }
|
||||
MotionInterpreter? ResolveMotionInterpreter(LiveEntityRecord record);
|
||||
}
|
||||
```
|
||||
|
||||
`GameWindow` implements only these constant-time lookups. The presenter cannot
|
||||
reach windowing, GL, networking, input, streaming, physics, or arbitrary
|
||||
`GameWindow` methods.
|
||||
|
||||
### 4.3 Presenter
|
||||
|
||||
Use an API equivalent to:
|
||||
|
||||
```csharp
|
||||
void Present(
|
||||
IReadOnlyDictionary<uint, LiveEntityAnimationSchedule> schedules);
|
||||
|
||||
void PrepareAnimation(
|
||||
LiveEntityRecord record,
|
||||
LiveEntityAnimationState state);
|
||||
```
|
||||
|
||||
The presenter receives:
|
||||
|
||||
- canonical `LiveEntityRuntime` provider for exact-record validation;
|
||||
- `EntityEffectPoseRegistry`;
|
||||
- `ILiveAnimationPresentationContext`;
|
||||
- a late-bound `ILiveStaticPartFrameSource` (the static scheduler is created
|
||||
after the presenter);
|
||||
- startup-resolved hidden-part diagnostic index;
|
||||
- typed animation diagnostic flags.
|
||||
|
||||
The presenter owns its own reusable `LiveEntityRecord` snapshot. It does not
|
||||
borrow `LiveEntityAnimationRuntimeView`'s reusable enumerator snapshot across
|
||||
the synchronous `EffectPoseChanged` callback boundary. Nested enumeration by a
|
||||
subscriber therefore cannot invalidate the outer presentation pass.
|
||||
|
||||
Each schedule carries the exact `LiveEntityRecord`, `WorldEntity`, animation
|
||||
component, `ObjectClockEpoch`, and projection-mutation version which produced
|
||||
it. The presenter validates all of those identities before consuming ordinary
|
||||
or static frames and again before each externally observable publication. A
|
||||
dictionary key/local ID is an index only; it is never proof of incarnation.
|
||||
The static prepared-frame source is queried late, only after this validation.
|
||||
Its take operation receives the exact record, entity, animation component,
|
||||
object-clock epoch, and projection/residency versions; it validates those
|
||||
tokens before clearing and returning prepared frames.
|
||||
|
||||
It keeps no GUID map, no animation clock, no packet/session reference, and no
|
||||
render-backend object.
|
||||
|
||||
### 4.4 Diagnostics
|
||||
|
||||
Move the surviving `SEQSTATE`, `CURRNODE`, `PARTSDIAG`, and `MOTIONDONE` gates
|
||||
behind one typed animation diagnostic owner. Environment variables are read
|
||||
once; the presenter does not perform per-frame environment lookups. Throttle
|
||||
state follows the exact animation component lifetime instead of living on the
|
||||
remote-motion component.
|
||||
|
||||
## 5. Transform invariants
|
||||
|
||||
For every part index, preserve these two deliberately different transforms.
|
||||
|
||||
### Drawable visual transform
|
||||
|
||||
```text
|
||||
Setup DefaultScale
|
||||
× animated part orientation
|
||||
× animated part translation
|
||||
× entity ObjScale
|
||||
```
|
||||
|
||||
This is the existing renderer/hydration contract. It scales geometry and the
|
||||
part's translated placement.
|
||||
|
||||
### Rigid effect/attachment transform
|
||||
|
||||
```text
|
||||
animated part orientation
|
||||
× translation(animated origin × entity ObjScale)
|
||||
```
|
||||
|
||||
Retail `CPartArray::UpdateParts` scales the part-frame origin only. Setup
|
||||
DefaultScale is `gfxobj_scale`, not rigid part-frame state, so particles,
|
||||
lights, attachments, and multipart collision must never inherit it.
|
||||
|
||||
Additional invariants:
|
||||
|
||||
- Sequencer frames win when present. Retail updates only
|
||||
`min(CPartArray.num_parts, AnimFrame.num_parts)`; missing trailing frames
|
||||
retain both their previous drawable visual transform and their previous
|
||||
indexed rigid/effect pose (the two distinct initial rest transforms on first
|
||||
presentation) instead of snapping either channel to identity.
|
||||
- Legacy animation interpolates origin linearly and orientation spherically,
|
||||
clamps interpolation to `[0,1]`, and wraps over the inclusive frame span.
|
||||
- Live static prepared frames override the ordinary schedule for that owner.
|
||||
- `ComposeParts == false` changes no mesh or indexed pose.
|
||||
- Non-drawable/debug-hidden parts remain in indexed effect poses but not
|
||||
drawable `MeshRef`s.
|
||||
- `PartAvailability` is published unchanged and controls effect part lookup.
|
||||
- Mesh identity, surface overrides, and list reuse remain unchanged.
|
||||
- Entity mesh/indexed-part state is installed before effect-pose publication.
|
||||
|
||||
## 6. MotionDone invariants
|
||||
|
||||
- Bind before any sequencer can complete; repeated calls are idempotent.
|
||||
- The scheduler validates the exact owner before invoking `PrepareAnimation`.
|
||||
- Resolve the player/remote `MotionInterpreter` at callback time because that
|
||||
consumer may be created after the animation component.
|
||||
- Never capture a legitimate null interpreter permanently.
|
||||
- Never complete a motion from pose publication; `AnimationHookFrameQueue`
|
||||
remains the semantic `AnimationDone` boundary.
|
||||
- Logical removal/replacement stops the old component from advancing; no
|
||||
presenter-owned callback queue survives it.
|
||||
- The callback revalidates the stable captured record, entity, and animation
|
||||
component before resolving a same-GUID interpreter. It deliberately does
|
||||
not capture object-clock/projection epochs: the same retail CPartArray and
|
||||
completion consumer survive legitimate rebucketing and leave/re-enter.
|
||||
GUID reuse still cannot deliver an old completion to a replacement.
|
||||
- `MOTIONDONE` diagnostics observe but never alter queue state.
|
||||
|
||||
## 7. Execution sequence
|
||||
|
||||
### Commit 0 — plan and baseline
|
||||
|
||||
- Land this plan.
|
||||
- Record exact baseline metrics and current frame order.
|
||||
- Register no new divergence; this slice moves an accepted mechanism.
|
||||
|
||||
### Commit 1 — extract component types and pure presenter
|
||||
|
||||
- Move animation component/template types out of `GameWindow`.
|
||||
- Add the presentation context and typed diagnostics owner.
|
||||
- Implement `LiveEntityAnimationPresenter` by moving the current composition
|
||||
body line-for-line, retaining the baseline trailing-part fallback initially.
|
||||
- Add focused tests before runtime cutover.
|
||||
|
||||
### Commit 2 — cut over runtime ownership
|
||||
|
||||
- Construct the presenter beside the scheduler.
|
||||
- Route scheduler preparation, local-player pre-advance, spawn-time binding,
|
||||
and final presentation through it.
|
||||
- Delete `TickAnimations`, `EnsureMotionDoneBinding`, nested animation types,
|
||||
and presentation-only remote diagnostic fields from `GameWindow`.
|
||||
- Keep child/static-hook/effect ordering visible in
|
||||
`AdvanceLiveObjectRuntimeCore`.
|
||||
|
||||
### Commit 3 — retail trailing-part conformance
|
||||
|
||||
- Record the `CPartArray::UpdateParts` minimum-count/retain behavior in the
|
||||
R6 pseudocode note.
|
||||
- Preserve both visual and rigid prior/rest trailing transforms.
|
||||
- Add first-presentation and later-shorter-frame conformance tests plus
|
||||
ordinary/live-static/DAT-static parity coverage.
|
||||
- Update the divergence register in this commit if the audited register
|
||||
contains a corresponding row; introduce no silent behavior delta.
|
||||
|
||||
### Commit 4 — review, gate, and ledger close
|
||||
|
||||
- Run retail-conformance, architecture/integration, and adversarial read-only
|
||||
reviews against the complete slice.
|
||||
- Fix every confirmed finding and repeat review until clean.
|
||||
- Run focused animation, scheduler, static, effect, projectile, remote-motion,
|
||||
and local-player tests.
|
||||
- Run Release solution build and full Release suite.
|
||||
- Record new `GameWindow` line/field/method counts.
|
||||
- Update code structure, roadmap, milestones, issues if any, durable animation
|
||||
memory, `AGENTS.md`, and `CLAUDE.md`.
|
||||
|
||||
## 8. Automated acceptance matrix
|
||||
|
||||
### Composition
|
||||
|
||||
- Sequencer pose composes every part and preserves surface overrides.
|
||||
- Missing sequencer trailing parts retain their previous/rest indexed pose.
|
||||
- Legacy current/next frame interpolation and inclusive wrapping are exact.
|
||||
- Legacy hidden/single-frame owners compose only when scheduled.
|
||||
- Static prepared frames override ordinary frames exactly once.
|
||||
- `ComposeParts == false` preserves prior mesh/pose state.
|
||||
|
||||
### Visual versus rigid transforms
|
||||
|
||||
- Setup nonuniform DefaultScale affects drawable geometry only.
|
||||
- ObjScale affects drawable geometry and translated placement.
|
||||
- Rigid effect poses scale only the translated origin.
|
||||
- Nondrawable and debug-hidden parts remain available to effects.
|
||||
- Unavailable parts remain indexed but fail effect-part lookup.
|
||||
|
||||
### Lifetime and ordering
|
||||
|
||||
- A removed/replaced animation component is skipped by the runtime-view
|
||||
snapshot and cannot publish into its replacement.
|
||||
- A schedule prepared for incarnation A is rejected if A is withdrawn,
|
||||
replaced, or reprojected before presentation, including same-local-ID ABA.
|
||||
- Reentrant deletion/replacement during pose publication prevents every later
|
||||
write for the stale owner; no borrowed scratch buffer crosses a callback.
|
||||
- Effect-pose publication sees the entity's already-installed indexed parts.
|
||||
- Same-frame animation hooks drain only after current parts and children.
|
||||
- Static process-hooks runs only after static parts and children are current.
|
||||
- Presentation allocates no per-frame mesh/effect lists in steady state.
|
||||
|
||||
### Motion completion
|
||||
|
||||
- Binding is idempotent.
|
||||
- A consumer created after binding receives completion.
|
||||
- Player and remote resolution select the correct interpreter.
|
||||
- Missing consumer is a no-op.
|
||||
- Diagnostics do not duplicate `MotionDone`.
|
||||
- A legitimate rebucket/object-clock rebase preserves the binding.
|
||||
- Delete plus GUID/local-ID reuse rejects completion from the old component.
|
||||
|
||||
### Cross-owner order and reconciliation
|
||||
|
||||
The presenter-level portions below are covered by Slice 2. The full
|
||||
instrumented owner-order trace and non-advancing spatial-reconcile assertion
|
||||
remain assigned to Slice 6, where the update sequence becomes one testable
|
||||
orchestrator instead of a `GameWindow` method. Slice 2 preserves that order but
|
||||
does not claim an end-to-end orchestrator test prematurely.
|
||||
|
||||
- One instrumented object-frame test pins presenter, children, static hooks,
|
||||
fades, ordinary hook drain, effect roots, emitters, lights, particles, and
|
||||
scripts. A PES-created particle first simulates on the following frame.
|
||||
- Authoritative spatial reconciliation performs only root/effect refresh,
|
||||
child composition, emitter refresh, and light refresh; it advances no
|
||||
animation, hook, fade, particle, script, or object clock.
|
||||
- Appearance shrink from three parts to one preserves playback while removing
|
||||
stale MeshRefs, indexed poses, availability, and effect-registry entries.
|
||||
- Ordinary, live-static handoff, and DAT-static composition share conformance
|
||||
fixtures for scale, sparse availability, and surface overrides.
|
||||
- An equipped child consumes the just-published parent indexed pose in the
|
||||
same frame, including ObjScale and nested attachment composition.
|
||||
- An animated projectile's trail/light anchor observes the same-quantum root
|
||||
and part pose, never the previous frame.
|
||||
|
||||
`EquippedChildRenderController` callback-mutation hardening is an adjacent
|
||||
Slice-6 frame-orchestration concern. Slice 2 does not claim it: the known
|
||||
reentrancy case is recorded as a follow-up unless a focused test demonstrates
|
||||
the existing traversal is already safe.
|
||||
- A nested animation-runtime enumeration triggered by `EffectPoseChanged`
|
||||
cannot overwrite or truncate the presenter's private outer snapshot.
|
||||
|
||||
## 9. Connected gate
|
||||
|
||||
Run one normal capped Release client and verify behavior remains unchanged:
|
||||
|
||||
1. local idle, walk, run, strafe, turn, jump, combat, and stop transitions;
|
||||
2. observe a remote player walk/run/turn without pose stalls or blips;
|
||||
3. open/close a door and kill a creature; static/reactive final poses persist;
|
||||
4. equip/unequip armor and switch melee/bow/wand without animation loss;
|
||||
5. fire an arrow and cast a projectile spell; trails remain attached to the
|
||||
current missile/hand pose;
|
||||
6. cast a protection and recall/portal; body effects follow current parts;
|
||||
7. portal/relog and confirm no old pose or completion reaches a replacement.
|
||||
|
||||
Any failure returns to the owning commit. No fallback pose cache, delayed
|
||||
retry, grace period, or GUID-only suppression is acceptable.
|
||||
|
||||
## 10. Subagent policy
|
||||
|
||||
One primary agent performs all edits. Read-only agents independently audit
|
||||
retail conformance, architecture/lifetime, and adversarial tests before the
|
||||
plan and after every implementation correction. No subagent edits the shared
|
||||
slice concurrently.
|
||||
|
|
@ -1,414 +0,0 @@
|
|||
# GameWindow Slice 3 — complete live-session ownership
|
||||
|
||||
**Status:** Complete 2026-07-21.
|
||||
**Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 3.
|
||||
**Baseline:** `9a150e24`; `GameWindow.cs` is 14,546 lines, 277 fields, and
|
||||
190 methods before this slice.
|
||||
**Behavior rule:** Preserve the accepted connect, EnterWorld, inbound dispatch,
|
||||
chat/command, and retail graceful-close behavior while correcting the
|
||||
named-retail character-list, unattended-selection, canonical-account, and
|
||||
disconnect-state gaps discovered by the slice audit. This slice changes App
|
||||
ownership and fixes proven session-lifetime defects; it does not add a retained
|
||||
character-selection UI or redesign the transport.
|
||||
|
||||
**Progress ledger:**
|
||||
|
||||
- [x] A0 — retail lifecycle conformance corrections (`aea957f8`): retail
|
||||
active/deleted CharacterSet parsing, validated unattended selection,
|
||||
canonical-account F657, exact F653 confirmation, negotiated-state
|
||||
Disconnect symmetry, closed-channel/flood-safe confirmation wait, and
|
||||
deterministic shutdown tests. Release: 6,605 passed / 5 skipped.
|
||||
- [x] A — owned subscription primitives (`7d452aa6`): exact nested
|
||||
GameEvent registration tokens, reverse/idempotent subscription ownership,
|
||||
transactional wiring construction, and adversarial unregister,
|
||||
reentrancy, replacement, and concurrent-dispose coverage. Release: 6,621
|
||||
passed / 5 skipped.
|
||||
- [x] B — session event and command routers (`961bdd07`): focused inbound
|
||||
event and outbound command owners, exact reverse teardown, copied/in-flight
|
||||
callback acceptance gates, inactive-before-EnterWorld publication,
|
||||
concurrent and reentrant disposal serialization, and delayed-dialog
|
||||
indirection that makes displaced transports both inert and collectible.
|
||||
Three-agent review clean. Release: 6,636 passed / 5 skipped.
|
||||
- [x] C — convergent session reset (`4f31a508`): fixed ordered manifest,
|
||||
all-attempt named transaction, retry-observable owner resets, canonical
|
||||
live-runtime convergence/identity gate, retained-world preservation,
|
||||
retail dialog completion semantics, and actual-owner A → failed drain →
|
||||
retry → clean-state coverage. Three-agent review clean. Release: 6,659
|
||||
passed / 5 skipped.
|
||||
- [x] D — complete lifecycle controller (`d9ccf8a6`): sole App ownership
|
||||
of resolve/create/bind/Connect/select/EnterWorld/Tick/stop/reconnect,
|
||||
generation-gated reentrancy, pre-publication command activation,
|
||||
convergent staged teardown, transport-free deterministic session tests,
|
||||
and invalid-endpoint validation before UDP allocation. Three-agent review
|
||||
clean. Release: 6,707 passed / 5 skipped.
|
||||
- [x] E — GameWindow cutover (`6a5d9e2e`): one controller field, focused
|
||||
lifecycle host, transactional event/command binding, call-time borrowed
|
||||
session resolution, controller-backed retained/dev UI command providers,
|
||||
unified shutdown, and structural regression gates proving the displaced
|
||||
fields and lifecycle bodies remain absent. Three-agent review clean.
|
||||
Release: 6,714 passed / 5 skipped.
|
||||
- [x] F — final review, connected gate, and documentation closeout. All three
|
||||
independent review tracks finished clean. Release: 6,714 passed / 5 skipped.
|
||||
The 306-second connected lifecycle gate passed capped login, five travel/
|
||||
revisit checkpoints, exact graceful exit, and uncapped fresh-process
|
||||
reconnect; both client processes exited normally. The sole warning was 25
|
||||
expected world-edge landblock misses already classified by the gate.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
`LiveSessionController` becomes the sole App owner of one exact
|
||||
`WorldSession` lifetime:
|
||||
|
||||
- endpoint resolution and session construction;
|
||||
- event routing before the first network receive;
|
||||
- Connect → CharacterList validation → first-character selection → EnterWorld;
|
||||
- active command-bus publication;
|
||||
- per-frame Tick;
|
||||
- graceful stop, replacement/reconnect, and disposal;
|
||||
- session-generation and reentrant-operation gates;
|
||||
- failure convergence back to a clean offline state.
|
||||
|
||||
`LiveSessionEventRouter` owns typed session subscriptions and exact
|
||||
unsubscription. It routes into focused entity/environment/chat/vital and Core
|
||||
state handlers but owns no entity map, renderer, UI model, or gameplay state.
|
||||
`LiveSessionResetPlan` owns the ordered, failure-isolated reset transaction but
|
||||
does not become a second state owner.
|
||||
|
||||
At slice exit, `GameWindow` retains one `_liveSessionController` field plus
|
||||
narrow composition/lifecycle callbacks. `_liveSession`, `_commandBus`,
|
||||
`_combatChatTranslator`, `TryStartLiveSession`, `ClearInboundEntityState`, and
|
||||
`WireLiveSessionEvents` are removed. Every outbound caller resolves the current
|
||||
borrowed session from the controller at call time; no feature caches it.
|
||||
|
||||
## 2. Retail and shipped oracles
|
||||
|
||||
The accepted transport behavior remains pinned to:
|
||||
|
||||
- [`2026-07-20-retail-graceful-logout-pseudocode.md`](../research/2026-07-20-retail-graceful-logout-pseudocode.md);
|
||||
- `Proto_UI::LogOffCharacter @ 0x00546A20`;
|
||||
- `CharacterSet::UnPack @ 0x004FE340`;
|
||||
- `gmCharacterManagementUI::EnterGame @ 0x004ED440`;
|
||||
- `CPlayerSystem::LogOnCharacter @ 0x0055F890`;
|
||||
- `CPlayerSystem::RequestLogOff @ 0x00562DD0`;
|
||||
- inbound login-message dispatch `@ 0x0055C963`;
|
||||
- `CPlayerSystem::ExecuteLogOff @ 0x0055D780`;
|
||||
- `ClientNet::ExitWorldDisconnect @ 0x00541E00`;
|
||||
- `ClientNet::LogOffServer @ 0x00543EF0`;
|
||||
- `SharedNet::SendOptionalHeader @ 0x00543160`;
|
||||
- ACE `CharacterHandler.CharacterLogOff` and
|
||||
`Session.SendFinalLogOffMessages`;
|
||||
- Holtburger's connect/PlayerCreate/LoginComplete flow as a protocol
|
||||
cross-check, not as the logout oracle where its shortcut differs from retail;
|
||||
- [`2026-07-20-automated-world-lifecycle-gate.md`](2026-07-20-automated-world-lifecycle-gate.md),
|
||||
whose accepted connected trace proves F653 confirmation, transport
|
||||
disconnect, endpoint release, and fresh-process reconnect.
|
||||
|
||||
The controller must preserve these fixed facts:
|
||||
|
||||
1. Complete routing is installed before `Connect`; Connect can synchronously
|
||||
publish initial server time before character selection.
|
||||
2. Retail's CharacterList contains separate active and deleted collections.
|
||||
EnterGame requires a selected GUID in the active collection whose
|
||||
`GetGreyedOutFor` result is not positive. acdream's unattended auto-entry is
|
||||
an explicit adaptation: select the first active, non-greyed character and
|
||||
reject an empty/all-greyed list. A future retained character-select screen
|
||||
is separate M4/UI work.
|
||||
3. `WorldSession.EnterWorld` retains its shipped request → ServerReady →
|
||||
CharacterEnterWorld order. F657 uses `CharacterList.AccountName`, the
|
||||
canonical server-returned spelling, rather than the startup username.
|
||||
4. Closing an in-world session sends the eight-byte `[0xF653, active GUID]`
|
||||
request, drains without App world callbacks until the server's exact
|
||||
four-byte opcode-only F653 confirmation, sends transport Disconnect, and
|
||||
only then releases the socket. Once ConnectRequest negotiation has supplied
|
||||
receiver id/iteration, character-select, entering-world, and failed
|
||||
post-negotiation sessions also send transport Disconnect before socket
|
||||
release. `WorldSession.Dispose` remains the one wire owner.
|
||||
5. The existing close-only adaptation in divergence row AD-44 remains: acdream
|
||||
exits instead of returning to a retained character-select connection.
|
||||
6. The registered fixed ConnectResponse delay (UN-6) and initial
|
||||
LoginComplete-readiness gap (TS-28) remain explicit Core.Net follow-ups.
|
||||
They are not copied into, disguised by, or claimed by the App controller.
|
||||
|
||||
No new automatic network retry or logout shortcut is introduced. Core.Net
|
||||
changes stay surgical: retail CharacterSet parsing/selection data, exact F653
|
||||
confirmation, and negotiated-state Disconnect symmetry. The accepted packet
|
||||
codec, handshake loops, confirmation wait, and receive-thread design are not
|
||||
rewritten.
|
||||
|
||||
## 3. Proven baseline defects closed by this slice
|
||||
|
||||
The pre-slice audit found real lifetime defects, not merely style debt:
|
||||
|
||||
- repeated startup can overwrite an active controller/session without stopping
|
||||
the exact prior lifetime;
|
||||
- no-character and failure paths can leave a `LiveCommandBus` whose delegates
|
||||
capture a disposed session;
|
||||
- the no-character path leaks `CombatChatTranslator` subscriptions;
|
||||
- anonymous event wiring cannot be explicitly detached;
|
||||
- `GameEventDispatcher.Unregister(type)` cannot safely distinguish an old
|
||||
registration from a replacement;
|
||||
- the reset boundary leaves player identity, Core character state, Turbine
|
||||
rooms/cookie, shortcuts/components, combat/item mana/social state, motion
|
||||
metadata, and player-mode references from the prior session;
|
||||
- one reset-stage failure can prevent later network/effect cleanup;
|
||||
- a synchronous handler can request stop/reconnect while `Tick`, Connect, or
|
||||
EnterWorld is still unwinding.
|
||||
|
||||
Each is fixed at its owner. No suppression flag, delay, retry loop, or stale-GUID
|
||||
filter is acceptable.
|
||||
|
||||
## 4. Architecture and interfaces
|
||||
|
||||
### 4.1 Controller state
|
||||
|
||||
`LiveSessionController` exposes:
|
||||
|
||||
```csharp
|
||||
LiveSessionStartResult Start(RuntimeOptions options, ILiveSessionLifecycleHost host);
|
||||
LiveSessionStartResult Reconnect(RuntimeOptions options, ILiveSessionLifecycleHost host);
|
||||
void Stop();
|
||||
void Tick();
|
||||
|
||||
WorldSession? CurrentSession { get; } // borrowed; never cache
|
||||
ICommandBus Commands { get; } // NullCommandBus outside active InWorld
|
||||
bool IsInWorld { get; }
|
||||
ulong SessionGeneration { get; }
|
||||
```
|
||||
|
||||
The controller uses an injected internal operations seam for deterministic
|
||||
tests (resolve, create, connect, select, enter, tick, dispose). Production
|
||||
operations call the real `WorldSession`; tests do not open UDP sockets. This is
|
||||
an App orchestration seam, not a duplicate protocol implementation.
|
||||
|
||||
Every start attempt and accepted session has a monotonically increasing
|
||||
generation. Lifecycle calls made synchronously from a host/event callback mark
|
||||
the current attempt non-accepting and defer stop/reconnect until the outer
|
||||
operation unwinds. The outer attempt rechecks its generation after every
|
||||
callback and blocking network phase, so it cannot publish an old session after
|
||||
reentrant replacement.
|
||||
|
||||
Replacement order is fixed:
|
||||
|
||||
1. disable outbound commands and mark the router non-accepting;
|
||||
2. detach exact subscriptions and session-scoped translators;
|
||||
3. gracefully dispose the exact old `WorldSession`;
|
||||
4. detach the borrowed session from the host;
|
||||
5. execute the complete reset plan to convergence;
|
||||
6. create and bind the new session transactionally;
|
||||
7. Connect, validate/select, EnterWorld;
|
||||
8. publish active commands only after success.
|
||||
|
||||
### 4.2 Lifecycle host
|
||||
|
||||
`ILiveSessionLifecycleHost` is a narrow composition boundary, not a context
|
||||
bag. It has focused lifecycle calls only:
|
||||
|
||||
- bind one exact session and return an owned `LiveSessionBinding`;
|
||||
- reset session-scoped App state;
|
||||
- report connecting/connected status;
|
||||
- apply the selected character identity;
|
||||
- apply post-EnterWorld UI/settings state;
|
||||
- detach the exact session.
|
||||
|
||||
Entity, environment, and effect packets continue through focused sink
|
||||
interfaces. Later Slice 4 replaces the GameWindow entity sink with the two
|
||||
planned live-entity integration controllers without changing the session
|
||||
controller.
|
||||
|
||||
### 4.3 Exact subscription ownership
|
||||
|
||||
`LiveSessionEventRouter` stores named delegates against one exact event source,
|
||||
sets `Accepting = false` before teardown, and unsubscribes in reverse order.
|
||||
Construction is transactional: a partially created router unwinds every prior
|
||||
registration.
|
||||
|
||||
`ObjectTableWiring.Wire`, `CombatStateWiring.Wire`, and
|
||||
`GameEventWiring.WireAll` return idempotent owned registrations. The GameEvent
|
||||
dispatcher gains registration tokens that restore/remove only when their exact
|
||||
handler is still current. Nested registration A → B is safe when A disposes
|
||||
first: disposing B skips the retired A node and restores the session-owned
|
||||
predecessor (not a dead callback).
|
||||
|
||||
The router groups dependencies by focused responsibility:
|
||||
|
||||
- live entity/physics/effect sink;
|
||||
- environment/time sink;
|
||||
- Core object/combat/spell/player/social state bridge;
|
||||
- chat/vital bridge;
|
||||
- command router.
|
||||
|
||||
There is no `GameWindowContext`, GUID table, renderer registry, or generic
|
||||
service locator.
|
||||
|
||||
### 4.4 Command lifetime
|
||||
|
||||
`LiveSessionCommandRouter` owns one `LiveCommandBus`, the server/chat routing,
|
||||
and the exact session captured by its handlers. The controller exposes this bus
|
||||
only while that session is the active in-world generation. Before stop or
|
||||
replacement it returns `NullCommandBus`, so a retained panel can never send via
|
||||
the displaced socket.
|
||||
|
||||
Turbine channel resolution and labels move with the chat command/event owner.
|
||||
`ClientCommandController` remains the retail command behavior owner; GameWindow
|
||||
supplies its existing focused bindings during composition.
|
||||
|
||||
### 4.5 Reset convergence
|
||||
|
||||
`LiveSessionResetPlan` is an ordered list of named owner-reset operations. It
|
||||
runs every stage even when one fails, aggregates errors, and forbids a new
|
||||
session from starting until all stages converge. The plan clears:
|
||||
|
||||
- mouse-look, player mode/cameras, auto-entry, and teleport/reveal transit;
|
||||
- session origin/counters and selected-character identity;
|
||||
- equipped/external-container projections;
|
||||
- object, spell, magic, combat, item-mana, local-player, friends, squelch,
|
||||
Turbine-chat, shortcut, and desired-component state;
|
||||
- interaction/selection and selection-presentation state;
|
||||
- liveness and canonical live entities (including GpuWorldState persistence);
|
||||
- remote teleport, pending F754/F755 effects, and deferred animation hooks.
|
||||
|
||||
`LocalPlayerState.Clear`, `TurbineChatState.Reset`, and `SquelchState.Clear`
|
||||
are added to their real state owners. Chat history is intentionally retained
|
||||
across an in-process reconnect, matching the existing UI behavior, but its
|
||||
local-player GUID resets to zero. Vitals GUID, diagnostic player GUID, run/jump
|
||||
skills, character options, motion table, movement truth/shadow, active toon,
|
||||
shortcuts, and desired components reset to pre-login values.
|
||||
|
||||
## 5. Implementation sequence
|
||||
|
||||
### Commit A0 — retail lifecycle conformance corrections
|
||||
|
||||
- Write one consolidated pseudocode note for `CharacterSet::UnPack`,
|
||||
`gmCharacterManagementUI::EnterGame`, `CPlayerSystem::LogOnCharacter`, and
|
||||
negotiated logout/disconnect ordering.
|
||||
- Parse active and deleted CharacterList collections with cursor/truncation
|
||||
tests and preserve status/account/slot/boolean fields.
|
||||
- Add the unattended first-active-non-greyed selector and canonical-account
|
||||
EnterWorld input.
|
||||
- Require an exact four-byte F653 confirmation and send transport Disconnect
|
||||
for every negotiated session state.
|
||||
- Keep UN-6, TS-28, and AD-44 explicitly registered and unchanged.
|
||||
|
||||
### Commit A — owned subscription primitives
|
||||
|
||||
- Add nested-safe owned registrations to `GameEventDispatcher`.
|
||||
- Make ObjectTable, CombatState, and GameEvent wiring return disposables.
|
||||
- Add exact teardown, nested replacement, reverse disposal, and idempotence
|
||||
tests in Core.Net.
|
||||
|
||||
### Commit B — session event and command routers
|
||||
|
||||
- Add focused live-session event source/sink contracts.
|
||||
- Route every current direct WorldSession event exactly once.
|
||||
- Move chat/Turbine/vital routing and the live command bus out of GameWindow.
|
||||
- Own `CombatChatTranslator` and all wiring registrations transactionally.
|
||||
- Test post-dispose silence, partial-construction unwind, nested events, and
|
||||
router A/B replacement.
|
||||
|
||||
### Commit C — convergent session reset
|
||||
|
||||
- Add missing reset APIs to Core state owners.
|
||||
- Add `LiveSessionResetPlan` with named, failure-isolated stages.
|
||||
- Compose the complete current session manifest and test mutated A → reset →
|
||||
clean B state, including one throwing stage.
|
||||
|
||||
### Commit D — complete lifecycle controller
|
||||
|
||||
- Move resolve/create/Connect/CharacterList/selection/EnterWorld/stop/reconnect
|
||||
into `LiveSessionController`.
|
||||
- Add generation and operation-depth gates.
|
||||
- Add typed start results for Disabled, MissingCredentials, NoCharacters,
|
||||
Connected, Deferred, and Failed.
|
||||
- Test ordering, duplicate/reentrant start/stop/reconnect, Connect and
|
||||
EnterWorld failures, tick generation, and exact-once cleanup.
|
||||
|
||||
### Commit E — GameWindow cutover
|
||||
|
||||
- Implement the narrow host and packet sink interfaces.
|
||||
- Replace all direct session field reads with borrowed controller resolution.
|
||||
- Replace both UI command-bus providers with `controller.Commands`.
|
||||
- Delete the three old lifecycle/wiring methods and the displaced fields.
|
||||
- Preserve OnLoad, frame Tick, and shutdown stage order.
|
||||
|
||||
### Commit F — review corrections and documentation
|
||||
|
||||
- Run three independent read-only reviews: retail conformance,
|
||||
architecture/lifetime, and adversarial tests.
|
||||
- Fix every confirmed issue and repeat review until clean.
|
||||
- Run focused App/Core.Net tests, Release build, and the full Release suite.
|
||||
- Run the existing connected login/command/chat/portal/graceful-close/
|
||||
fresh-process-reconnect gate when local ACE is available.
|
||||
- Update code structure, roadmap, milestones, issues/divergence if needed,
|
||||
durable session memory, `AGENTS.md`, and `CLAUDE.md`.
|
||||
|
||||
## 6. Automated acceptance matrix
|
||||
|
||||
### Controller
|
||||
|
||||
- Routing is complete before Connect's synchronous server-time callback.
|
||||
- First active non-greyed CharacterList entry, GUID, name, canonical account,
|
||||
and active-list index are exact; deleted/greyed entries are rejected.
|
||||
- Empty list, Connect failure, EnterWorld failure, and binding failure leave no
|
||||
active session/router/bus and clean exactly once.
|
||||
- Duplicate `Start` is idempotent and cannot disturb a healthy active scope;
|
||||
explicit `Reconnect` gracefully stops A before B state is reset/wired.
|
||||
- Stop/reconnect requested during bind, Connect, selected-character callback,
|
||||
EnterWorld, or Tick cannot resurrect the outer generation.
|
||||
- Tick reaches only the exact current session; stopped/disposed Tick is a no-op.
|
||||
- Stop and Dispose are idempotent.
|
||||
|
||||
### Router and command bus
|
||||
|
||||
- Every direct session event routes once while accepting.
|
||||
- Dispose is reverse-order, idempotent, and post-dispose events are ignored.
|
||||
- Partial construction unwinds prior registrations.
|
||||
- GameEvent A/B nested ownership restores the nearest live predecessor.
|
||||
- Old command delegates are unreachable before old-session disposal and cannot
|
||||
send after replacement.
|
||||
- Domain-handler exceptions follow an explicit policy without corrupting
|
||||
subscription ownership.
|
||||
|
||||
### Reset
|
||||
|
||||
- Every named stage is attempted once even when another throws.
|
||||
- Failure blocks construction of session B and reports all stage errors.
|
||||
- Player identity, Vitals/Chat identity, diagnostic GUID, selected-character
|
||||
metadata, Core state, social/chat-room state, projections, live runtime,
|
||||
pending effects, and hooks all return to pre-login values.
|
||||
- Chat history is retained but no old own-GUID classification remains.
|
||||
- Live runtime convergence removes the old persistent player classification.
|
||||
|
||||
### Integration
|
||||
|
||||
- `GameWindow` has no `_liveSession`, `_commandBus`, or
|
||||
`_combatChatTranslator` field.
|
||||
- `TryStartLiveSession`, `ClearInboundEntityState`, and
|
||||
`WireLiveSessionEvents` no longer exist.
|
||||
- No substantial session callback body remains in `GameWindow`.
|
||||
- WorldSession retains the 8-byte request → exact 4-byte confirmation →
|
||||
Disconnect order and extends Disconnect symmetry to every negotiated state.
|
||||
- Release build and full suite are green.
|
||||
|
||||
## 7. Connected gate
|
||||
|
||||
Run a normal capped Release client against local ACE and prove:
|
||||
|
||||
1. login selects the same first character and reveals a complete world;
|
||||
2. system chat, say/tell/channel/server commands, vitals, combat state, and
|
||||
shortcuts/components still update;
|
||||
3. one portal/recall completes with the same reveal lifecycle;
|
||||
4. native close logs the F653 request/confirmation and transport Disconnect;
|
||||
5. ACE releases the exact endpoint;
|
||||
6. a fresh process reconnects cleanly with no old character state, command
|
||||
route, entities, effects, or persistent GUIDs.
|
||||
|
||||
The gate is behavioral and resource-oriented. It does not create a new visual
|
||||
approval pause; final campaign visual acceptance remains after Slice 8.
|
||||
|
||||
## 8. Review policy
|
||||
|
||||
One primary agent performs edits. Read-only agents independently audit retail
|
||||
conformance, architecture/lifetime, and adversarial tests after each ownership
|
||||
commit. Confirmed findings are fixed at their root and re-reviewed. No reviewer
|
||||
edits the shared worktree, and no callback facade back into a substantial old
|
||||
GameWindow body counts as extraction completion.
|
||||
|
|
@ -1,442 +0,0 @@
|
|||
# GameWindow Slice 4 — live-entity App integration
|
||||
|
||||
**Status:** Complete 2026-07-21.
|
||||
**Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 4.
|
||||
**Baseline:** `ed9dc366`; `GameWindow.cs` is 14,310 lines, 274 fields, and
|
||||
190 methods before this slice. Release baseline: 6,714 passed / 5 skipped.
|
||||
**Behavior rule:** Preserve the accepted live-object identity, packet FIFO,
|
||||
independent timestamp authorities, render/physics/effect lifetime, and connected
|
||||
R6 behavior while moving their App integration to focused owners. Correct only
|
||||
proven retail-conformance defects with an explicit oracle and test; do not hide
|
||||
them inside a mechanical extraction.
|
||||
|
||||
**Progress ledger:**
|
||||
|
||||
- [x] A — canonical seams: `LiveWorldOriginState`, single-bind runtime-component
|
||||
lifecycle bridge, extracted `RemoteMotion`, and canonical physics-host
|
||||
ownership. Landed in `d68c83d1`, `5882b308`, and `fcb66198`. The final
|
||||
exact-record host checkpoint passed three clean reviews plus 6,743 Release
|
||||
tests / 5 skipped on 2026-07-21.
|
||||
- [x] B — pure appearance/projection helpers and controller-level
|
||||
characterization tests. Landed in `69a2ca0c`. Exact appearance/collision
|
||||
replacement, default-pose resolution, local-shadow ownership, and recursive
|
||||
retained projection withdrawal passed three clean reviews plus 6,799 Release
|
||||
tests / 5 skipped on 2026-07-21.
|
||||
- [x] C — hydration create/materialize/landblock-recovery ownership and direct
|
||||
CreateObject/streaming cutover. Landed in `d10c5f2d`; 6,854 Release tests
|
||||
passed / 5 skipped after three clean retail, architecture, and adversarial
|
||||
re-reviews on 2026-07-21.
|
||||
- [x] D — ObjDesc/Parent/Pickup/ready/withdraw ownership and exact retained
|
||||
identity across leave-world/re-entry. Landed in `fe551496`; 6,877 Release
|
||||
tests passed / 5 skipped after three clean retail, architecture, and
|
||||
adversarial re-reviews on 2026-07-21.
|
||||
- [x] E — Delete/prune and retryable exact-incarnation teardown ownership.
|
||||
- [x] F — Position/Vector/State/Movement controller, same-generation CreateObject
|
||||
tail, independent authority gates, and direct inbound cutover.
|
||||
- [x] G — `GameWindow` cleanup, three-agent review cycle, full Release suite,
|
||||
connected gates, documentation, and durable memory closeout.
|
||||
|
||||
## 1. Outcome and non-goals
|
||||
|
||||
This slice creates two cooperating App owners:
|
||||
|
||||
- `LiveEntityHydrationController` owns CreateObject, ObjDesc, Parent, Pickup,
|
||||
Delete/prune, DAT-backed appearance hydration, first materialization,
|
||||
landblock rehydration, ready publication, projection withdrawal, and exact
|
||||
App-component teardown over `LiveEntityRuntime`.
|
||||
- `LiveEntityNetworkUpdateController` owns accepted Position, Vector, State,
|
||||
and Movement routing into the existing motion, physics, projectile,
|
||||
presentation, and teleport owners. It also consumes the same-generation
|
||||
CreateObject update tail without reconstructing the entity.
|
||||
|
||||
`LiveEntityRuntime` remains the only owner of server GUID ↔ local identity,
|
||||
incarnation, accepted snapshots, independent authority versions, spatial
|
||||
worksets, and teardown tombstones. Neither new controller may contain a GUID
|
||||
dictionary or cache an incarnation outside the runtime record it is currently
|
||||
validating.
|
||||
|
||||
This is not a new entity model, physics algorithm, renderer, packet decoder, or
|
||||
DAT reader. It does not retire the documented future-packet, visibility-PVS,
|
||||
or null-parent-queue divergences. It does not redesign the accepted update
|
||||
thread or `RetailInboundEventDispatcher` FIFO.
|
||||
|
||||
## 2. Retail and shipped oracles
|
||||
|
||||
The extraction is pinned to:
|
||||
|
||||
- `SmartBox::HandleCreateObject @ 0x00454C80`;
|
||||
- `ACCObjectMaint::CreateObject @ 0x00558870`;
|
||||
- `SmartBox::ProcessObjectNetBlobs @ 0x00454B20` and
|
||||
`QueueBlobForObject @ 0x00451B90`;
|
||||
- `SmartBox::HandleReceivedPosition @ 0x00453FD0`;
|
||||
- `SmartBox::DoPickupEvent @ 0x00452240` and
|
||||
`DoParentEvent @ 0x00452290`;
|
||||
- `SmartBox::HandleDeleteObject @ 0x00451EA0`;
|
||||
- `CPhysicsObj::set_description @ 0x00514F40`;
|
||||
- `CPhysicsObj::set_state @ 0x00514DD0`;
|
||||
- `CPhysicsObj::change_cell @ 0x00513390`;
|
||||
- `CPhysicsObj::exit_world @ 0x00514E60` and
|
||||
`leave_world @ 0x005155A0`;
|
||||
- [`2026-07-13-retail-projectile-vfx-pseudocode.md`](../research/2026-07-13-retail-projectile-vfx-pseudocode.md);
|
||||
- [`2026-07-18-retail-object-liveness-and-mesh-reclamation-pseudocode.md`](../research/2026-07-18-retail-object-liveness-and-mesh-reclamation-pseudocode.md);
|
||||
- [`2026-07-19-r6-update-object-order-pseudocode.md`](../research/2026-07-19-r6-update-object-order-pseudocode.md);
|
||||
- ACE and Holtburger object-update handlers as interpretation and protocol
|
||||
cross-checks, never as replacements for the named-retail ordering.
|
||||
|
||||
The following rules are load-bearing:
|
||||
|
||||
1. One logical object exists per accepted `INSTANCE_TS`. Equal generation
|
||||
mutates it; newer generation tears down and replaces it; older generation
|
||||
is ignored using retail's wrap-safe comparison, including the `0x8000`
|
||||
boundary.
|
||||
2. A fresh CreateObject constructs, describes, attaches, enters world, then
|
||||
replays queued blobs. An equal-generation CreateObject applies ObjDesc,
|
||||
Parent or Position/Pickup, Movement, State, Vector, then WeenieDesc without
|
||||
recreating the object.
|
||||
3. Position, Movement, State, Vector, ObjDesc, Teleport, ForcePosition,
|
||||
ServerControlledMove, and Instance authorities remain independent. After
|
||||
every reentrant callback, the exact record and relevant authority version
|
||||
are revalidated.
|
||||
4. Movement consumes strict `MOVEMENT_TS` before it can discover a stale
|
||||
server-controlled timestamp; equality on the latter is accepted.
|
||||
5. Parent, Pickup, and Position share `POSITION_TS`. Pickup unparents and
|
||||
leaves world; a later fresh Position unparents and restores the same object
|
||||
identity/resources.
|
||||
6. `leave_world` is not deletion. It clears cell/shadow/contact/transient
|
||||
presentation while retaining PartArray, movement, animation, scripts,
|
||||
effects, timestamps, and identity. Logical destruction alone performs
|
||||
`exit_world` manager cleanup.
|
||||
7. Inbound processing is one FIFO. Nested heterogeneous packets wait until the
|
||||
active packet reaches its complete tail.
|
||||
|
||||
## 3. Architecture and interfaces
|
||||
|
||||
### 3.1 Shared origin state
|
||||
|
||||
`LiveWorldOriginState` owns the session-scoped world center currently spread
|
||||
across `_liveCenterX`, `_liveCenterY`, and `_liveCenterKnown`:
|
||||
|
||||
```csharp
|
||||
bool IsKnown { get; }
|
||||
double CenterX { get; }
|
||||
double CenterY { get; }
|
||||
bool TryInitialize(double worldX, double worldY);
|
||||
void Recenter(double worldX, double worldY);
|
||||
void Reset();
|
||||
```
|
||||
|
||||
Hydration, accepted Position/teleport, streaming, and render consumers share
|
||||
this state directly. No GameWindow callback conceals origin mutation.
|
||||
|
||||
### 3.2 Runtime component lifecycle
|
||||
|
||||
Introduce:
|
||||
|
||||
```csharp
|
||||
interface ILiveEntityRuntimeComponentLifecycle
|
||||
{
|
||||
void TearDown(LiveEntityRecord exactRecord);
|
||||
}
|
||||
```
|
||||
|
||||
`DeferredLiveEntityRuntimeComponentLifecycle` is a composition-only,
|
||||
single-assignment bridge. Construction order is fixed:
|
||||
|
||||
1. construct the unbound bridge;
|
||||
2. construct `LiveEntityRuntime` with the bridge;
|
||||
3. construct effect, projectile, presentation, child, and related owners;
|
||||
4. construct hydration;
|
||||
5. bind the bridge exactly once to hydration;
|
||||
6. construct and bind the network-update sink;
|
||||
7. only then permit a live session to start.
|
||||
|
||||
Calling before bind fails fast; second bind is rejected. This removes the
|
||||
circular GameWindow teardown façade without making runtime mutation optional.
|
||||
The existing `CompositeLiveEntityResourceLifecycle` remains the sole logical
|
||||
mesh/script registration transaction; hydration does not become a second
|
||||
resource owner.
|
||||
|
||||
### 3.3 Canonical runtime components
|
||||
|
||||
Move `RemoteMotion` out of `GameWindow` into `AcDream.App.Physics` without
|
||||
semantic changes. Store physics-host resolution on the exact
|
||||
`LiveEntityRecord` (or an explicit runtime-owned component index) rather than
|
||||
the duplicate `_physicsHosts` GUID dictionary. Local-player host publication
|
||||
uses the same focused owner, not a second identity table. Preserve the full-cell
|
||||
binding and remote-placement contract.
|
||||
|
||||
### 3.4 Hydration API
|
||||
|
||||
The controller surface is narrow and event-shaped:
|
||||
|
||||
```csharp
|
||||
void OnCreate(WorldSession.EntitySpawn spawn);
|
||||
void OnObjDesc(ObjDescEvent.Parsed update);
|
||||
void OnDelete(DeleteObject.Parsed update);
|
||||
void OnPrune(LiveEntityPruneCandidate candidate);
|
||||
void OnPickup(PickupEvent.Parsed update);
|
||||
void OnParent(ParentEvent.Parsed update);
|
||||
bool TryAcceptParentForRender(ParentEvent.Parsed update);
|
||||
void OnLandblockLoaded(uint landblockId);
|
||||
bool EnsureWorldOriginAndRecover(
|
||||
LiveEntityRecord expected,
|
||||
ulong positionAuthorityVersion,
|
||||
WorldSession.EntitySpawn acceptedSpawn);
|
||||
void OnEntityReady(uint guid);
|
||||
bool WithdrawWorldProjection(uint guid);
|
||||
```
|
||||
|
||||
Hydration receives focused concrete collaborators/ports: runtime and object
|
||||
table, the sole `DatCollection` and shared DAT lock, physics data cache,
|
||||
spawn/animation/collision builders, spatial state/origin/streaming, and the
|
||||
existing effect/projectile/child/presentation/light owners. It must not receive
|
||||
a generic GameWindow context or a bundle of callbacks to old substantial
|
||||
methods.
|
||||
|
||||
ObjDesc mutates the captured exact entity. Acquire-before-publish rollback and
|
||||
post-callback incarnation checks preserve local ID, body, motion, physics host,
|
||||
effect owner, and parent state. Parent-on-spawn may reenter; the canonical
|
||||
snapshot is reread afterward. A materialized object only rebuckets; a pending,
|
||||
never-materialized object hydrates once. Landblock load never replays
|
||||
create-time logical resource activation.
|
||||
|
||||
### 3.5 Network-update API
|
||||
|
||||
The network owner accepts Position, Vector, State, Movement, and the equal-
|
||||
generation CreateObject tail. It resolves through `LiveEntityRuntime`, captures
|
||||
the relevant authority version, calls existing focused runtime owners, and
|
||||
revalidates exact record plus authority after every callback.
|
||||
|
||||
Hydration calls a once-bound `ILiveEntityNetworkUpdateSink` for the
|
||||
same-generation tail. Network position recovery calls
|
||||
`EnsureWorldOriginAndRecover`; no substantial dispatch returns to GameWindow.
|
||||
Local ForcePosition, teleport presentation, projectile corrections, shadow
|
||||
updates, movement animation, and static/headless branches retain their shipped
|
||||
ordering.
|
||||
|
||||
### 3.6 Direct composition
|
||||
|
||||
At slice exit these callbacks point directly to the new owners:
|
||||
|
||||
- session Create/Delete/Pickup/Parent/ObjDesc/Position/Vector/State/Movement;
|
||||
- streaming landblock-loaded rehydration;
|
||||
- liveness prune;
|
||||
- equipped-child parent acceptance and entity-ready replay;
|
||||
- runtime exact-component teardown.
|
||||
|
||||
`GameWindow` keeps controller construction plus narrow frame calls. The old
|
||||
handler, hydration, collision-registration, appearance-rebind, ready,
|
||||
leave-world, and teardown method bodies must be absent, not retained as
|
||||
one-line façades.
|
||||
|
||||
## 4. Commit sequence and gates
|
||||
|
||||
### A — canonical seams
|
||||
|
||||
- Add and test `LiveWorldOriginState`.
|
||||
- Add and test the fail-fast, single-bind lifecycle bridge.
|
||||
- Extract `RemoteMotion` mechanically; retarget all 51 App-test references.
|
||||
- Canonicalize physics-host ownership and remove `_physicsHosts` only after
|
||||
resolution/teardown/local-player tests prove equivalence.
|
||||
- Run App remote-motion, runtime, physics-host, session-reset, and structural
|
||||
suites; full Release build/test; three-agent review and re-review.
|
||||
|
||||
### B — presentation helpers and characterization
|
||||
|
||||
- Extract `AppearanceUpdateState`, appearance rebind, default-pose, and
|
||||
collision-build helpers behind focused App types.
|
||||
- Preserve the exact DAT lock and existing extracted WorldBuilder pipeline.
|
||||
- Add controller-harness fakes that require no Silk/GL context.
|
||||
- Characterize current leave-world failure ordering rather than silently
|
||||
redesigning it.
|
||||
- Run appearance, animation, spawn-adapter, collision, resource-lifecycle,
|
||||
and new characterization suites; full Release build/test; review cycle.
|
||||
|
||||
Completed in `69a2ca0c`. The checkpoint also pinned the retail `add_child` /
|
||||
`set_parent` / `leave_world` boundary required by C and D: parent timestamps
|
||||
are staged before validation, accepted relationships are committed one at a
|
||||
time, and world withdrawal recursively removes the exact parent-first
|
||||
projection subtree while retaining logical identity and retry ownership.
|
||||
Release build completed with zero warnings and errors; 6,799 tests passed with
|
||||
five intentional skips; retail, architecture, and adversarial re-reviews were
|
||||
clean.
|
||||
|
||||
### C — create and materialization
|
||||
|
||||
- Implement hydration CreateObject registration, first materialization,
|
||||
pending deferral, landblock recovery, and ready publication.
|
||||
- Preserve prior-incarnation cleanup failure aggregation and rollback.
|
||||
- Route session CreateObject and streaming callbacks directly.
|
||||
- Tests: loaded create order; pending→loaded; materialized rebucket; same/
|
||||
older/new/wrapped generations; nested callback replacement; registration
|
||||
failure/retry; no duplicate mesh/script/effect activation.
|
||||
- Full Release build/test; review cycle.
|
||||
|
||||
Completed in `d10c5f2d`. `LiveEntityHydrationController` now owns the exact
|
||||
CreateObject registration transaction, world-origin bootstrap, pending
|
||||
deferral, first DAT projection materialization, landblock recovery, and the
|
||||
ready/PES replay boundary. Same-generation CreateObject supersession carries
|
||||
a monotonic integration version and a persistent retry obligation, so an
|
||||
older reentrant materialization cannot publish or erase the newer canonical
|
||||
snapshot. Parent and pickup creates complete through their retail cell-less
|
||||
paths, while interrupted initial hydration reuses the same animation
|
||||
sequencer and completed owners retain their exact phase and callback queues.
|
||||
Session CreateObject and streaming callbacks route directly to the extracted
|
||||
owner; the old GameWindow bodies are gone. Release build completed with zero
|
||||
warnings and errors; 6,854 tests passed with five intentional skips; retail,
|
||||
architecture, and adversarial re-reviews were clean.
|
||||
|
||||
### D — appearance, parenting, pickup, withdrawal
|
||||
|
||||
- Move ObjDesc, Parent, Pickup, child-ready, collision registration, and
|
||||
projection withdrawal.
|
||||
- Tests: identity/component preservation; acquire failure rollback; stale or
|
||||
reentrant ObjDesc; parent-before-create canonical reread; Pickup/Parent
|
||||
leave-world; Position re-entry with unchanged local ID/resources.
|
||||
- Bind child callbacks directly and remove their GameWindow façades.
|
||||
- Full Release build/test; review cycle.
|
||||
|
||||
Completed in `fe551496`. `LiveEntityHydrationController` now owns accepted
|
||||
ObjDesc, Parent, Pickup, child-ready, and exact leave-world/re-entry routing.
|
||||
ObjDesc carries an independent authority/retry transaction and reports success
|
||||
only when the current visual projection is published; delayed landblock or
|
||||
Position recovery therefore refreshes the player paperdoll exactly once.
|
||||
Attached objects re-realize through the equipped-child owner without requiring
|
||||
a world Position, retain their local identity/resources, and restore their
|
||||
complete descendant chain synchronously. Ready publication pins the exact
|
||||
create, Position, ObjDesc, projection-mutation, projection-kind, spatial, and
|
||||
entity authorities across every callback. The retail absent AnimationFrame
|
||||
default is placement zero. Release build completed with zero warnings and
|
||||
errors; 6,877 tests passed with five intentional skips; retail, architecture,
|
||||
and adversarial re-reviews were clean.
|
||||
|
||||
### E — delete and exact teardown
|
||||
|
||||
- Move Delete/prune, teardown-plan construction, leave-world component cleanup,
|
||||
and retry tombstone behavior.
|
||||
- Bind runtime bridge and liveness directly.
|
||||
- Tests: unknown/stale/current Delete; callback deletion; dual failure;
|
||||
teardown retry; same-GUID newer generation during old teardown; reset
|
||||
convergence; no surviving renderer/collision/script/effect/light owner.
|
||||
- Full Release build/test; review cycle.
|
||||
|
||||
Completed in `f38822c4`. `LiveEntityRuntimeTeardownController` now owns the
|
||||
retryable App-component half of retail `CObjectMaint::DeleteObject` and keeps
|
||||
the stable cleanup plan on the exact record tombstone. Hydration rejects the
|
||||
local player before every lookup or side effect, gates remote Delete by the
|
||||
exact Instance timestamp, discards only unknown-owner pending effects, and
|
||||
routes the 25-second prune through the same transaction. Successful cleanup
|
||||
steps never replay after a failure; a same-GUID replacement cannot be mutated
|
||||
by the old incarnation's delayed cleanup. The derived remote stop-observation
|
||||
state also has a focused session owner instead of a loose GameWindow
|
||||
dictionary. Full Release validation passed 6,891 tests with five intentional
|
||||
skips and no errors; all three corrected-diff re-reviews were clean.
|
||||
|
||||
### F — network-update owner
|
||||
|
||||
- Move Movement, Vector, State, and Position routing plus the equal-generation
|
||||
CreateObject tail.
|
||||
- Preserve FIFO and independent authorities; test stale, equal, wrap, nested,
|
||||
and callback-invalidated updates for every channel.
|
||||
- Preserve ForcePosition, server-controlled move, teleport, projectile,
|
||||
shadow, static, airborne, autonomous, animated, and headless branches.
|
||||
- Route session callbacks directly and remove old GameWindow handlers.
|
||||
- Full Release build/test; review cycle.
|
||||
|
||||
Completed in `aa90c646`. `LiveEntityNetworkUpdateController` now owns accepted
|
||||
Position, Vector, State, and Movement routing;
|
||||
`LiveEntitySameGenerationUpdateRouter` owns the retail equal-generation
|
||||
CreateObject tail. `LiveEntityInboundAuthorityGate` pins the exact record,
|
||||
independent channel authority, and velocity authority before any reentrant App
|
||||
callback. `LiveEntityMotionRuntimeController` owns the shared physics-host,
|
||||
Setup, MoveTo/Sticky, Hidden-target, and server-controlled-cycle policy behind
|
||||
a once-bound construction bridge. ForcePosition preserves retail's
|
||||
blip-then-immediate-F7B1 order and stamps the exact Position snapshot serialized
|
||||
on the wire. Focused tests cover stale/equal/wrapped timestamps, callback
|
||||
replacement, GUID reuse, projectile/body/remote Vector precedence, mixed
|
||||
same-generation routing, bridge binding, and the exact 56-byte autonomous
|
||||
position payload. All three corrected-diff re-reviews were clean.
|
||||
|
||||
### G — closeout
|
||||
|
||||
- Structural tests prove both controllers lack identity dictionaries and
|
||||
`GameWindow` lacks every moved field/type/method/body.
|
||||
- Run all live-runtime, object-table, parent/attachment, appearance/animation,
|
||||
effect/light/projectile, remote-motion/physics, inbound FIFO, liveness,
|
||||
stress, session-reset, and router suites.
|
||||
- Run `dotnet build AcDream.slnx -c Release` and full Release tests.
|
||||
- Run the connected R6 route and graceful reconnect gate, then user visual
|
||||
checks for inventory/equip/parenting, death/corpse, and portals when
|
||||
available.
|
||||
- Update architecture, roadmap, milestones, issues/divergence register, this
|
||||
ledger, `AGENTS.md`/`CLAUDE.md` when current-state pointers change, and
|
||||
durable GameWindow/live-runtime memory.
|
||||
|
||||
Completed 2026-07-21. `GameWindow` fell from the Slice-3 baseline of 14,310
|
||||
lines to 10,301 lines; its top-level field and method declarations are now 267
|
||||
and 163. Release build completed with zero warnings/errors and the full suite
|
||||
passed 6,940 tests with five intentional skips. The 310-second connected R6
|
||||
route passed login, Caul/Sawato/Rynthid/Aerlinthe/Sawato-revisit/Holtburg/
|
||||
Caul-return materialization, turn sampling, three locomotion/jump/combat
|
||||
exercises, and graceful close. Live-server population churn, previously known
|
||||
missing DAT VFX diagnostics, and empty world-edge landblocks remained warnings,
|
||||
not lifecycle failures. Slice 5 is the next structural owner boundary.
|
||||
|
||||
## 5. Adversarial acceptance matrix
|
||||
|
||||
Automated acceptance includes:
|
||||
|
||||
- equal/old/new/wrapped `INSTANCE_TS`, including `0x8000`;
|
||||
- full same-generation call trace and WeenieDesc tail ordering;
|
||||
- nested heterogeneous FIFO and exact authority invalidation;
|
||||
- fresh loaded create and pending→loaded without replay;
|
||||
- loaded→loaded rebucket and loaded↔pending oscillation;
|
||||
- ObjDesc identity/body/motion/host/effect/parent preservation;
|
||||
- Parent/Pickup→cell-less→Position re-entry with unchanged local ID;
|
||||
- unknown/stale/current Delete, callback delete, GUID reuse, and retry;
|
||||
- state side-effect order and movement/ForcePosition timestamp order;
|
||||
- projectile and remote-position corrections across landblocks;
|
||||
- bridge unbound/second-bind failures;
|
||||
- session reset with zero surviving live owners;
|
||||
- 96-owner and repeated-recall stress with no resource growth;
|
||||
- source/reflection ownership gates.
|
||||
|
||||
Connected acceptance includes:
|
||||
|
||||
- capped login and populated radar/world;
|
||||
- remote creature motion and target/status projection;
|
||||
- wield/dewield, bow/wand/melee switching, ammo parenting, inventory pickup;
|
||||
- open and loot a corpse after approach;
|
||||
- kill/death/corpse lifetime;
|
||||
- recall and portal travel across landblocks, revisit, graceful close, and
|
||||
fresh-process reconnect;
|
||||
- uncapped post-reconnect performance checkpoint.
|
||||
|
||||
## 6. Review discipline
|
||||
|
||||
Every ownership commit receives three independent read-only reviews:
|
||||
|
||||
1. retail conformance — symbols, ordering, timestamps, and documented
|
||||
divergences;
|
||||
2. architecture/integration — identity ownership, dependency direction,
|
||||
update-thread rules, lifecycle symmetry, and GameWindow reduction;
|
||||
3. adversarial testing — malformed/stale packets, callback reentrancy,
|
||||
registration failure, GUID reuse, landblock churn, reset, and leaks.
|
||||
|
||||
The primary agent reproduces and fixes confirmed findings, reruns focused and
|
||||
full gates, and requests re-review until no actionable finding remains. No
|
||||
subagent writes the implementation.
|
||||
|
||||
## 7. Explicit carried divergences
|
||||
|
||||
Slice 4 preserves, rather than disguises:
|
||||
|
||||
- AD-32 future-generation non-effect packet dropping;
|
||||
- AP-65 picked-up data-only weenie retention;
|
||||
- AP-69 approximate 384-unit visibility envelope;
|
||||
- TS-32 missing complete child-before-parent null-object queue;
|
||||
- the equal-generation PES-table refresh and VectorUpdate airborne/state
|
||||
adaptations recorded during Slice 4 planning.
|
||||
|
||||
These remain visible in the divergence register and are not evidence that the
|
||||
ownership extraction itself is incomplete.
|
||||
|
|
@ -1,470 +0,0 @@
|
|||
# GameWindow Slice 5 — landblock presentation
|
||||
|
||||
**Status:** Complete 2026-07-21 (`4a205a3e`).
|
||||
**Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 5.
|
||||
**Baseline:** `2b10e91e`; `GameWindow.cs` is 10,301 lines, 267 fields, and
|
||||
163 methods. Release baseline: 6,940 passed / 5 skipped. The connected R6
|
||||
baseline passed seven destinations/revisits, three movement/jump/combat
|
||||
exercises, and graceful close in 310 seconds.
|
||||
**Behavior rule:** Preserve the accepted two-tier streaming quality, immutable
|
||||
per-job EnvCell transaction, render-readiness barrier, collision/light/script/
|
||||
plugin lifetime, and retryable detach-first retirement. This is an ownership
|
||||
move. Any proven defect needed to make that boundary safe gets its own oracle,
|
||||
test, and bisectable commit rather than being hidden in a mechanical move.
|
||||
|
||||
## Progress ledger
|
||||
|
||||
- [x] A — freeze production order and introduce explicit build/publication
|
||||
seams plus structural guards.
|
||||
- A1 landed in `4f965d06`: accepted results now enter one staged
|
||||
`LandblockPresentationPipeline` transaction; `GpuWorldState` returns an
|
||||
exact spatial-publication receipt before retryable render-pin/script
|
||||
activation; retries preserve FIFO suffixes, pending-live merges, and
|
||||
promotion identity across policy/generation changes. Three corrected-diff
|
||||
reviews are clean. Release gate: 6,955 passed / 5 skipped.
|
||||
- A2 landed in `090b0354`: every production load now captures a specified
|
||||
immutable origin (including valid map origin `(0,0)`) and carries it through
|
||||
worker construction, priority/supersession, completion, Far normalization,
|
||||
and publication. Origin-less or mismatched request-aware work fails before
|
||||
it can publish. Three corrected-diff reviews are clean. Release gate: 6,965
|
||||
passed / 5 skipped.
|
||||
- [x] B — capture one immutable world-origin snapshot per load and extract the
|
||||
serialized DAT-backed `LandblockBuildFactory`.
|
||||
- Landed in `5af101b2`: `LandblockBuildFactory` now owns the complete
|
||||
serialized worker-side DAT transaction, immutable height-table snapshot,
|
||||
stab/scenery/interior hydration, complete EnvCell build, and physics-DAT
|
||||
bundle. `GameWindow` only constructs the owner and passes its `Build`
|
||||
operation to the streamer; it no longer contains any build helper body.
|
||||
Hermetic tests pin Far/Near/Promote shape, zero-submesh visibility cells,
|
||||
missing-record behavior, the exact supplied DAT gate, non-interleaved full
|
||||
Near transactions, exception recovery, and immutable inputs. The installed
|
||||
Holtburg gate independently derives visibility completeness from
|
||||
`LandBlockInfo.NumCells`. Three corrected-diff reviews are clean. Release
|
||||
gate: 6,979 passed / 5 skipped; `GameWindow.cs` is 9,573 lines.
|
||||
- [x] C — extract terrain, visibility, EnvCell, building, and render-pinning
|
||||
publication.
|
||||
- Landed in `1029f837`: `LandblockRenderPublisher` owns captured-origin
|
||||
terrain publication, complete visibility-cell transactions, AABBs,
|
||||
building registries, EnvCell commit/preparation, and matching retirement
|
||||
operations. Invalid EnvCell identities fail before any presentation
|
||||
mutation; visibility-only cells and zero-submesh transactions remain
|
||||
first-class. Tests pin building-before-EnvCell order, duplicate and failed
|
||||
completion behavior, foreign receipts, post-pin preparation, real
|
||||
publish-to-retire balance, and hidden AABB ownership. Three corrected-diff
|
||||
reviews are clean. Release gate: 6,988 passed / 5 skipped;
|
||||
`GameWindow.cs` is 9,490 lines.
|
||||
- [x] D — extract physics/cache/collision publication and cross-landblock
|
||||
reflood ownership.
|
||||
- Landed in `3613d393`: `LandblockPhysicsPublisher` now owns captured-origin
|
||||
terrain/cell/portal/building physics, cached GfxObj shapes, mutually
|
||||
exclusive multipart BSP or Setup fallback collision, exact static-owner
|
||||
replacement, final reflood, demotion, and full removal. Every Setup shape
|
||||
shares the retail logical entity ID; building shells remain exclusive to
|
||||
the per-landcell building channel. Reapply retires stale cell/building/UCG
|
||||
state and omitted static owners by landblock prefix, rebinds `CurrCell` to
|
||||
the replacement object, and preserves adjacent owners even when collision
|
||||
footprints cross the seam. Three corrected-diff reviews are clean. Release
|
||||
gate: 7,008 passed / 5 skipped; zero-warning build; `GameWindow.cs` is
|
||||
9,043 lines.
|
||||
- [x] E — extract static lighting, translucency, default-script, and plugin
|
||||
presentation; compose the small `LandblockPresentationPipeline`.
|
||||
- Landed in `ea0da8c8`: render, physics, and DAT-static publishers now
|
||||
preflight immutable receipts before mutation and retain exact per-stage
|
||||
progress through retry. `LandblockStaticPresentationPublisher` owns static
|
||||
lights, translucency lifetime, and plugin projection; `GpuWorldState` and
|
||||
`EntityScriptActivator` explicitly rebind same-ID static snapshots without
|
||||
replaying Setup defaults or resetting animation/light/translucency state,
|
||||
while omitted IDs take full teardown. Source changes and duplicate IDs fail
|
||||
before presentation mutation, missing DefaultAnimation never claims an
|
||||
owner, and fallible reconciliation precedes pending-map consumption. Three
|
||||
corrected-diff reviews are clean. Release gate: 7,026 passed / 5 skipped;
|
||||
`GameWindow.cs` is temporarily 9,073 lines because production wiring lands
|
||||
before checkpoint G deletes the old callback body.
|
||||
- [x] F — move retryable demotion/full-retirement presentation under the
|
||||
pipeline without changing `LandblockRetirementCoordinator`'s ledger.
|
||||
- Landed in `ea7ffbc1`: the pipeline now owns a focused
|
||||
`LandblockPresentationRetirementOwner` and its exact detach-first ledger;
|
||||
`GameWindow` no longer owns a coordinator or retirement callback body.
|
||||
Full and Near retirements preserve their distinct terrain/physics/live
|
||||
owner matrices, successful stages and entity cursors never replay, and
|
||||
reentrant Begin/Advance requests serialize without mutating an active
|
||||
ticket enumeration. A committed visibility-observer failure retains the
|
||||
detach receipt, completes presentation teardown, drains deferred work,
|
||||
then rethrows. Constructor identity guards enforce one world-state,
|
||||
publisher, lighting, and translucency graph, while explicit concrete and
|
||||
legacy controller overloads make ignored presentation callbacks
|
||||
impossible. Three corrected-diff reviews are clean. Release gate: 7,039
|
||||
passed / 5 skipped; build green with 17 pre-existing test-project warnings;
|
||||
`GameWindow.cs` is 9,011 lines.
|
||||
- [x] G — cut `StreamingController` and `GameWindow` directly to the pipeline;
|
||||
delete old bodies, scratch state, and callback facades.
|
||||
- [x] H — three-agent corrected-diff review, full Release suite, deterministic
|
||||
lifecycle gate, nine-destination resource soak, documentation, and durable
|
||||
memory closeout.
|
||||
- Landed in `4a205a3e`: native shutdown now keeps the live-session reset
|
||||
dependencies alive until deferred/reentrant disposal actually converges;
|
||||
source and behavioral tests pin that completion barrier. Both connected
|
||||
harnesses scan the final post-close logs for shutdown/disposed-resource
|
||||
failures. The resource gate uses an already-warm Caul → Sawato → Caul
|
||||
plateau instead of comparing a cold first load to a warmed cache, and
|
||||
every route command blocks on its exact portal-materialization count.
|
||||
- All three final reviews are clean. Release gate: 7,054 passed / 5 skipped.
|
||||
The capped/reconnect lifecycle gate passed in 311.4 seconds. The
|
||||
synchronized nine-stop soak passed in 394.1 seconds; Caul return → plateau
|
||||
changed working/private memory by only +56.4/+48.4 MiB and update p95 by
|
||||
0.8 → 0.7 ms. `GameWindow.cs` closes Slice 5 at 8,811 raw lines, 247
|
||||
fields, and 153 methods—1,490 lines below the Slice-5 baseline and 6,912
|
||||
lines (44%) below the campaign baseline.
|
||||
|
||||
Every checked checkpoint is committed as a bisectable architectural unit.
|
||||
Documentation records the exact commit and accepted test count as work lands.
|
||||
|
||||
## 1. Outcome and non-goals
|
||||
|
||||
At slice exit, `GameWindow` only composes and retains the landblock presentation
|
||||
owner. It no longer builds a landblock from DAT records, hydrates scenery or
|
||||
interiors, constructs a physics DAT bundle, publishes terrain/cells/collision/
|
||||
lights/plugins, or advances presentation retirement.
|
||||
|
||||
The final owner graph is deliberately not a replacement god object:
|
||||
|
||||
```text
|
||||
StreamingController (tier, region, generation, priority, budget)
|
||||
│ accepted Loaded / Promoted / Unloaded result
|
||||
▼
|
||||
LandblockPresentationPipeline (transaction order only)
|
||||
├── LandblockBuildFactory worker-only DAT transaction
|
||||
├── LandblockRenderPublisher terrain/cells/EnvCell/building/render pins
|
||||
├── LandblockPhysicsPublisher terrain/cell/portal/static collision
|
||||
├── LandblockStaticPresentation light/translucency/script/plugin projection
|
||||
└── LandblockRetirementCoordinator detach-first retry ledger
|
||||
│
|
||||
▼
|
||||
GpuWorldState (spatial buckets, pending live projections, tier, AABB)
|
||||
```
|
||||
|
||||
`StreamingController` remains the sole desired-tier and stale-generation
|
||||
authority. `GpuWorldState` remains the sole spatial-bucket and retained-live-
|
||||
projection owner. `LandblockStreamer` remains the sole worker queue. Existing
|
||||
renderers, physics/cache registries, lighting, scripts, plugin state, and
|
||||
`WorldRevealCoordinator` remain independent owners.
|
||||
|
||||
This slice does not change Near/Far radii, mesh or texture quality, world view
|
||||
distance, portal visibility, GPU frame fences, the draw graph, live-entity
|
||||
identity, or the update/render phase order. It does not re-port algorithms
|
||||
already inventoried under `Rendering/Wb` or introduce another DAT reader.
|
||||
|
||||
## 2. Retail, WorldBuilder, and shipped oracles
|
||||
|
||||
The extraction preserves the following retail order and already-shipped
|
||||
adaptations:
|
||||
|
||||
- `LScape::grab_visible_cells @ 0x00504EC0`: initialize buildings and gather
|
||||
visible cells before static/dynamic activation;
|
||||
- `CLandBlock::init_buildings @ 0x0052FD80`;
|
||||
- `CLandBlock::grab_visible_cells @ 0x0052F460`;
|
||||
- `CLandBlock::init_static_objs @ 0x00530A40` and
|
||||
`CLandBlock::init_dyn_objs @ 0x0052F3B0`;
|
||||
- `CLandBlock::destroy_static_objects @ 0x0052FA50`,
|
||||
`CLandBlock::Destroy @ 0x0052FAA0`, and
|
||||
`CLandBlock::release_all @ 0x0052FCF0` for static/building/cell teardown;
|
||||
- `CObjCell::init_objects @ 0x0052B420` →
|
||||
`CPhysicsObj::recalc_cross_cells @ 0x00515A30`; acdream's per-landblock
|
||||
`RefloodLandblock` remains the registered asynchronous adaptation;
|
||||
- `PView::InitCell @ 0x005A4B70` and
|
||||
`CEnvCell::find_visible_child_cell @ 0x0052DC50`;
|
||||
- `CBuildingObj::find_building_collisions @ 0x006B5300`;
|
||||
- `CPhysicsObj::add_shadows_to_cells @ 0x00514AE0`;
|
||||
- `SmartBox::UseTime @ 0x00455410`; acdream's shared reveal coordinator is the
|
||||
asynchronous equivalent of retail's synchronous cell readiness;
|
||||
- [`worldbuilder-inventory.md`](../architecture/worldbuilder-inventory.md),
|
||||
especially the one-job/one-complete-EnvCell-transaction and sole-DAT-reader
|
||||
rules;
|
||||
- the current render and two-tier streaming digests, including logical
|
||||
withdrawal before fence-delayed GPU reuse and the prohibition on reducing
|
||||
view distance as an optimization;
|
||||
- [`2026-07-13-envcell-landblock-transaction.md`](../research/2026-07-13-envcell-landblock-transaction.md)
|
||||
and
|
||||
[`2026-07-18-retail-texture-resource-lifetime-pseudocode.md`](../research/2026-07-18-retail-texture-resource-lifetime-pseudocode.md).
|
||||
|
||||
The named retail decomp is the oracle for lifecycle ordering. The extracted
|
||||
WorldBuilder code is the oracle for already-ported terrain, scenery, EnvCell,
|
||||
mesh, and texture mechanics. ACE and the retained WorldBuilder reference tree
|
||||
are cross-checks, not new dependencies.
|
||||
|
||||
## 3. Load-bearing invariants
|
||||
|
||||
### 3.1 Worker build
|
||||
|
||||
1. One `Load` captures landblock ID, job kind, generation, and the accepted
|
||||
world-origin center on the update thread. The worker never reads the
|
||||
update-thread-confined `LiveWorldOriginState`.
|
||||
2. All DAT reads for one build execute under the existing single-reader gate.
|
||||
The worker performs CPU work only: no GL call, renderer/physics publication,
|
||||
registry mutation, or shared pending cell bag.
|
||||
3. Far reads the heightmap only and emits no static entities, EnvCells, or
|
||||
physics bundle. Near/Promote emits hydrated stabs, procedural scenery,
|
||||
interiors, a complete `PhysicsDatBundle`, and one immutable
|
||||
`EnvCellLandblockBuild`.
|
||||
4. Every valid EnvCell is present in the visibility transaction even when it
|
||||
has zero drawable shell submeshes. Geometry IDs retain their current
|
||||
deterministic arithmetic.
|
||||
5. Missing records keep the current skip/null semantics. A failed build has no
|
||||
externally visible partial publication.
|
||||
|
||||
### 3.2 Publication
|
||||
|
||||
For an accepted Near load or promotion, the transaction order remains:
|
||||
|
||||
1. publish terrain and rebase it to the captured origin;
|
||||
2. commit the complete cell-visibility snapshot and AABB;
|
||||
3. populate physics data and publish terrain/cells/portals/buildings;
|
||||
4. commit building registries and the EnvCell renderer transaction;
|
||||
5. publish static BSP/cylinder collision, lights, translucency, and DAT-static
|
||||
plugin snapshots; reflood the landblock;
|
||||
6. publish/merge the canonical spatial bucket in `GpuWorldState`, acquiring
|
||||
ordinary and synthetic EnvCell render IDs and activating static defaults;
|
||||
7. only after the pins exist, replay schema-aware EnvCell mesh preparation;
|
||||
8. only after spatial publication, restore retained live projections.
|
||||
|
||||
Render readiness means Near tier plus real uploaded ownership for every
|
||||
required ordinary and synthetic EnvCell render ID. Dictionary publication is
|
||||
not readiness. Duplicate Near completion is a no-op; stale Far cannot erase a
|
||||
Near layer. Promotion-before-base and the accepted Near-completion-while-Far-
|
||||
desired synthesis remain `StreamingController` policy.
|
||||
|
||||
### 3.3 Demotion and retirement
|
||||
|
||||
1. `GpuWorldState.DetachNearLayer` or `DetachLandblock` commits first. No
|
||||
presentation cleanup failure can resurrect spatial membership.
|
||||
2. Near→Far keeps terrain and all live projections, but removes DAT statics,
|
||||
static scripts/classification, lights/translucency/plugins, indoor physics,
|
||||
cell visibility, building registries, and EnvCells.
|
||||
3. Full retirement additionally releases terrain and full landblock physics.
|
||||
Persistent player projection is rescued and still-live server projections
|
||||
are parked for rebucketing with the same identity.
|
||||
4. The retryable order is exactly: mesh references → static scripts →
|
||||
classification → entity lights → entity translucency → DAT-static plugin
|
||||
projection → terrain for Full only → physics remove/demote → cell visibility
|
||||
→ building registry → EnvCell renderer.
|
||||
5. Successful stages and entity cursors never replay. Same-ID replacement is
|
||||
fenced until every required stage converges, including across controller or
|
||||
radius reconfiguration.
|
||||
6. GPU range/layer reuse remains fence-delayed by `GpuFrameFlightController`.
|
||||
|
||||
## 4. Architecture and interfaces
|
||||
|
||||
### 4.1 Immutable build request
|
||||
|
||||
Introduce a request equivalent to:
|
||||
|
||||
```csharp
|
||||
public readonly record struct LandblockBuildRequest(
|
||||
uint LandblockId,
|
||||
LandblockStreamJobKind Kind,
|
||||
ulong Generation,
|
||||
int OriginCenterX,
|
||||
int OriginCenterY);
|
||||
```
|
||||
|
||||
The generation remains for matching and diagnostics; the builder never uses it
|
||||
to decide residency. Results carry the same origin snapshot so apply cannot
|
||||
combine worker geometry from one center with render-thread placement from a
|
||||
later center.
|
||||
|
||||
### 4.2 DAT build factory
|
||||
|
||||
`LandblockBuildFactory` receives `IDatReaderWriter`, the one shared DAT gate,
|
||||
the immutable height table, and worker-safe mesh/physics-cache collaborators.
|
||||
Its public surface is one operation:
|
||||
|
||||
```csharp
|
||||
LandblockBuild? Build(in LandblockBuildRequest request);
|
||||
```
|
||||
|
||||
The extracted `BuildPhysicsDatBundle`, stab hydration, scenery generation,
|
||||
interior/EnvCell construction, and terrain sampling remain private focused
|
||||
helpers. No method accepts `GameWindow`, `LiveWorldOriginState`, GL objects, or
|
||||
presentation registries.
|
||||
|
||||
### 4.3 Focused publishers
|
||||
|
||||
`LandblockRenderPublisher` owns terrain slot publication, AABB, cell
|
||||
visibility, building registries, EnvCell commit, static render pin/preparation,
|
||||
and a typed immutable diagnostics snapshot.
|
||||
|
||||
`LandblockPhysicsPublisher` owns cache population, terrain/cell/portal/building
|
||||
physics, static multipart BSP and exclusive Setup-cylinder/sphere fallback,
|
||||
demotion/full removal, and final cross-cell reflood.
|
||||
|
||||
`LandblockStaticPresentationPublisher` owns idempotent static light and
|
||||
translucency replacement, static script activation/removal at the existing
|
||||
spatial registration boundary, and DAT-static plugin spawn/removal projection.
|
||||
|
||||
The pipeline calls concrete publishers in the fixed order. Publish/retire
|
||||
interfaces expose no DAT reader. Diagnostics move with the owner and the Debug
|
||||
UI reads a snapshot rather than `GameWindow` scratch counters.
|
||||
|
||||
### 4.4 Policy cutover
|
||||
|
||||
`StreamingController` keeps result validation and calls the pipeline only after
|
||||
generation/tier/duplicate/retirement-fence checks. It must not gain DAT, GL,
|
||||
physics, plugin, light, or live-GUID state. `GpuWorldState` keeps its buckets,
|
||||
pending Near/render-ID parking, and live rescue. Existing hidden presentation
|
||||
side effects may be made explicit only when tests prove identical ordering and
|
||||
balance.
|
||||
|
||||
`GameWindow` retains construction/wiring plus one pipeline field. Structural
|
||||
tests forbid these methods from returning:
|
||||
|
||||
```text
|
||||
BuildLandblockForStreaming
|
||||
BuildLandblockForStreamingLocked
|
||||
BuildPhysicsDatBundle
|
||||
BuildSceneryEntitiesForStreaming
|
||||
BuildInteriorEntitiesForStreaming
|
||||
EnsureEnvCellMeshesAfterPin
|
||||
ApplyLoadedTerrain
|
||||
ApplyLoadedTerrainLocked
|
||||
AdvanceLandblockPresentationRetirement
|
||||
```
|
||||
|
||||
## 5. Checkpoint execution
|
||||
|
||||
### A — characterization and seams
|
||||
|
||||
- Add owner-call trace tests around the current production order and payload
|
||||
multiplicity.
|
||||
- Introduce immutable build/publication request/result seams without changing
|
||||
the live call graph. The typed apply-diagnostics snapshot moves with its
|
||||
concrete render owner in C instead of creating a temporary A-stage owner.
|
||||
- Add dependency/reflection guards: no origin-state read from worker, no DAT in
|
||||
publish/retire interfaces, no residency/generation map in the pipeline.
|
||||
|
||||
### B — origin snapshot and build factory
|
||||
|
||||
- Capture origin with every enqueued load; preserve it through priority queue,
|
||||
completion, promotion, and Far synthesis.
|
||||
- Move the complete serialized DAT build verbatim into
|
||||
`LandblockBuildFactory`; keep `DatCollection` access behind
|
||||
`IDatReaderWriter` and the shared gate.
|
||||
- Test Far/Near payload shape, repeat-build determinism, EnvCell completeness,
|
||||
physics-DAT closure, missing records, lock serialization, and zero DAT reads
|
||||
during publication.
|
||||
|
||||
### C — render publication
|
||||
|
||||
- Extract terrain upload/rebase, CellVisibility, AABB, building registry,
|
||||
EnvCell renderer commit, render-ID pin/preparation, and diagnostics.
|
||||
- Test visibility-only cells, zero-submesh EnvCells, promotion-before-base,
|
||||
real render readiness, reapply idempotency, and release balance.
|
||||
|
||||
### D — physics publication
|
||||
|
||||
- Extract physics cache commit, terrain/cell/portal/building setup, static
|
||||
multipart BSP/cylinder registration, reflood, demote, and full removal.
|
||||
- Use two adjacent landblocks to prove one retirement cannot erase a neighbor's
|
||||
seam collision. Pin Far terrain-only, Near reapply, promotion, demotion, and
|
||||
full unload symmetry.
|
||||
|
||||
### E — static presentation and transaction coordinator
|
||||
|
||||
- Extract static lights/translucency/plugin projection and make existing static
|
||||
script ownership explicit without replaying defaults.
|
||||
- Compose the small `LandblockPresentationPipeline`; add exact order,
|
||||
multiplicity, rollback/blocked-publication, and owner-balance tests.
|
||||
|
||||
### F — retirement
|
||||
|
||||
- Move production retirement callbacks under the pipeline while keeping
|
||||
`LandblockRetirementCoordinator` as the exact retry ledger.
|
||||
- Inject failure before/after each boundary; prove only unfinished suffixes and
|
||||
exact entity cursors resume. Test Near and Full requirements separately.
|
||||
|
||||
### G — direct cutover and cleanup
|
||||
|
||||
- Route `LandblockStreamer` build and `StreamingController` apply/retire calls
|
||||
directly to the new owners.
|
||||
- Delete the old GameWindow bodies, callback facades, building-registry and
|
||||
apply-diagnostic scratch state that now have focused owners.
|
||||
- Add structural source tests and remeasure GameWindow lines/fields/methods.
|
||||
|
||||
**Complete in `c79d0a49`.** Production constructs one concrete pipeline;
|
||||
legacy callback constructors are internal test seams and structural tests keep
|
||||
them out of the composition root. The old GameWindow apply methods and their
|
||||
building/diagnostic scratch state are deleted. A retained
|
||||
`StreamingOriginRecenterCoordinator` now joins complete old-window
|
||||
presentation retirement to teleport recenter and session cancellation before
|
||||
the shared origin may be reused. Reviews closed full-window overlap, radius
|
||||
reconfiguration, sealed-dungeon bootstrap, committed-callback retry,
|
||||
reentrancy, fast-relogin, and ordinary-logout gaps. Three final reviews are
|
||||
clean; the zero-warning Release build and 7,052 tests pass with five skips.
|
||||
`GameWindow.cs` was 8,793 raw lines at checkpoint G, 1,508 below the Slice-5
|
||||
baseline and 6,930 (44%) below the campaign baseline.
|
||||
|
||||
### H — closeout
|
||||
|
||||
**Complete in `4a205a3e`.** H proved the landed graph under deterministic and
|
||||
connected lifecycle churn, corrected shutdown-stage ordering so session reset
|
||||
converges before its streaming dependencies are disposed, and strengthened the
|
||||
resource oracle to compare already-warm steady-state revisits.
|
||||
|
||||
- Run focused App/Core/Net tests after each checkpoint.
|
||||
- After each code checkpoint, run three independent read-only reviews: retail/
|
||||
WorldBuilder conformance, architecture/integration, and adversarial failure
|
||||
analysis. Fix confirmed findings and repeat review until clean.
|
||||
- Run `dotnet build AcDream.slnx -c Release` and the full Release test suite.
|
||||
- Run deterministic first-login/portal lifecycle automation and the connected
|
||||
nine-destination resource soak. Verify retirement/preparation queues drain,
|
||||
residence matches the region, resource counts remain bounded, and graceful
|
||||
close succeeds.
|
||||
- Update the divergence register pointers in IA-14, AD-2, AD-22, and any other
|
||||
row whose cited ownership moved. Keep AD-6, AD-24, AP-31, and TS-52 unless a
|
||||
separate retail mechanism actually lands.
|
||||
- Update architecture, milestones, roadmap, issues if needed, synchronized
|
||||
`AGENTS.md`/`CLAUDE.md`, and durable decomposition/render memory.
|
||||
|
||||
The final IA-14/AD-2/AD-22 audit found no new retail divergence. IA-14 and
|
||||
AD-22 still point at the correct WB/mesh owners; AD-2 now names the extracted
|
||||
origin-recenter/presentation owners. AD-6, AD-24, AP-31, and TS-52 remain open
|
||||
as required.
|
||||
|
||||
## 6. Acceptance matrix
|
||||
|
||||
Automated acceptance includes:
|
||||
|
||||
- Far heightmap-only and complete immutable Near/Promote payloads;
|
||||
- deterministic static/scenery/interior IDs, bounds, offsets, cells, and shell
|
||||
geometry IDs across repeated builds;
|
||||
- single-reader DAT gate and no publish-time DAT access;
|
||||
- exact owner-call sequence for load, promotion, Far synthesis, demotion, full
|
||||
unload, replacement, and failure retry;
|
||||
- stale/duplicate completions invoke no presentation participant;
|
||||
- complete render pins and real readiness before reveal;
|
||||
- static lights/translucency/scripts/plugins and mesh references remain
|
||||
balanced through load → reapply → demote → promote → full unload;
|
||||
- adjacent-landblock collision survives one neighbor's retirement;
|
||||
- first login with unknown origin starts no build, and stale placeholder/old-
|
||||
center completions cannot publish or satisfy reveal;
|
||||
- reflection/source gates keep moved methods and scratch ownership out of
|
||||
`GameWindow` and keep policy/state out of the pipeline.
|
||||
|
||||
Connected acceptance includes fresh login, outdoor travel, world-edge empty
|
||||
blocks, dungeon travel, same-location revisit, turn sampling at every stop,
|
||||
movement/jump/combat exercises, portal reveal, and graceful close. No view-
|
||||
distance or quality reduction is an acceptable way to pass the resource gate.
|
||||
|
||||
## 7. Known risks and explicit traps
|
||||
|
||||
- A worker must not read a later live origin than the job it is building.
|
||||
- A Near promotion may complete before its Far base; parking behavior and
|
||||
required render IDs must survive unchanged.
|
||||
- Duplicate Near/ForceReload must not stack lights, scripts, plugin events,
|
||||
meshes, collision, or EnvCells.
|
||||
- Demotion must never retire live entities or terrain.
|
||||
- Publication must not be mistaken for uploaded render readiness.
|
||||
- Failed retirement must survive controller/radius replacement.
|
||||
- Do not move `WbDrawDispatcher` or any draw-graph body; that is Slice 7.
|
||||
- Do not reintroduce worker-side `RegisterCell`, global pending renderer lists,
|
||||
legacy rendering, or another DAT reader.
|
||||
- Do not reduce Near/Far radii, texture detail, or mesh quality.
|
||||
|
|
@ -1,545 +0,0 @@
|
|||
# GameWindow Slice 6 — update-frame orchestration
|
||||
|
||||
**Status:** Complete 2026-07-22.
|
||||
**Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 6.
|
||||
**Baseline:** `d4ecac1d`; `GameWindow.cs` is 8,811 raw lines, 247 fields, and
|
||||
153 methods. Release baseline: 7,054 passed / 5 skipped. The connected
|
||||
lifecycle gate and synchronized nine-stop resource soak both pass.
|
||||
**Behavior rule:** This is an ownership extraction. Preserve the accepted
|
||||
retail object/network order, asynchronous streaming-readiness adaptation,
|
||||
portal transit, input semantics, and camera presentation. A defect exposed by
|
||||
the boundary receives its own deterministic oracle and bisectable fix; it is
|
||||
never hidden behind a retry, delay, suppression flag, or reordered callback.
|
||||
|
||||
## Progress ledger
|
||||
|
||||
- [x] A — freeze the complete production phase graph and introduce the typed
|
||||
orchestration contract plus structural/order guards.
|
||||
- [x] B — extract the pre-network live-object presentation phase and the
|
||||
non-advancing post-network spatial reconciler; remove callbacks into
|
||||
`GameWindow` from `RetailLiveFrameCoordinator`.
|
||||
- [x] C — extract streaming-origin convergence, observer selection, dungeon
|
||||
collapse, streaming tick, and rescued-entity rebucketing.
|
||||
- [x] D — extract dispatcher/raw-mouse/combat input sampling without changing
|
||||
movement-command or UI-capture semantics.
|
||||
- [x] E — extract the complete local-player teleport owner and wire it between
|
||||
the existing post-network liveness and player-mode auto-entry phases.
|
||||
- [x] F — extract the shared player-mode lifecycle plus fly/chase/player
|
||||
camera presentation and cut production over to one
|
||||
`UpdateFrameOrchestrator`.
|
||||
- [x] G — delete the old `OnUpdate` bodies, callback facades, and obsolete
|
||||
frame state; run focused corrected-diff reviews after each ownership edge.
|
||||
- [x] H — full Release suite, connected lifecycle/reconnect gate, synchronized
|
||||
resource soak, documentation, durable memory, and line/field/method closeout.
|
||||
|
||||
Every checked checkpoint is committed as one bisectable architectural unit.
|
||||
Documentation records the exact commit and accepted test count as work lands.
|
||||
|
||||
Checkpoint A introduced the internal `AcDream.App.Update` contract, one typed
|
||||
`UpdateFrameClock`/`IPhysicsScriptTimeSource`, the twelve-phase outer-order
|
||||
oracle, exact teardown and invalid-delta tests, and guards against a transitive
|
||||
`GameWindow` clock capture. Production now publishes PES time once per update;
|
||||
18 focused Release tests and the full 2,709-test App project pass, and all
|
||||
three corrected-diff reviews are clean.
|
||||
|
||||
Checkpoint B moved the complete local/ordinary/static/effect body into
|
||||
`LiveObjectFrameController`, made `RetailLiveFrameCoordinator`'s
|
||||
object → inbound batch → command → spatial-reconcile barrier fully typed,
|
||||
and removed animation/runtime/projectile back-references to `GameWindow`.
|
||||
`GameWindow.cs` is now 8,716 raw lines. The corrected diff passes 239 focused
|
||||
Release tests, the 2,732-test App suite, and the full 7,090-test Release suite
|
||||
(5 fixture/environment skips); all three independent reviews are clean.
|
||||
|
||||
Checkpoint C moved retained origin convergence, readiness, observer selection,
|
||||
sealed-dungeon collapse, streaming publication, and persistent live-projection
|
||||
rescue into `StreamingFrameController`. Production keeps streaming publication
|
||||
ahead of input and the live object/network barrier, so a same-frame
|
||||
CreateObject can enter the newly resident bucket. The focused 146-test Release
|
||||
selection, 2,754-test App suite, and complete 7,112-test Release suite (5
|
||||
fixture/environment skips) pass. Login-before-position, portal hold, outdoor
|
||||
and sealed/SeenOutside EnvCells, pending recenter, exact rescue rebucketing,
|
||||
GUID deletion, and session reset are pinned; all three corrected-diff reviews
|
||||
are clean. `GameWindow.cs` is now 8,569 raw lines.
|
||||
|
||||
Checkpoint D moved semantic hold delivery, movement sampling, autorun,
|
||||
instant mouse-look, cursor capture, raw filtering/idle handling, and combat
|
||||
intent into `GameplayInputFrameController` and focused typed owners. Devtools
|
||||
keyboard capture pauses all gameplay movement without clearing the autorun
|
||||
latch; retained chat suppresses held keys while autorun continues. Every
|
||||
focus, teleport, camera/player-mode, reset, and shutdown boundary now releases
|
||||
mouse-look through one lifecycle path and clears stale RMB orbit state.
|
||||
Production combat requests, automatic hostile selection, UI-capture probes,
|
||||
and movement-truth diagnostics no longer retain callbacks into `GameWindow`.
|
||||
The 2,787-test App suite and complete 7,145-test Release suite pass (5
|
||||
fixture/environment skips); all three corrected-diff reviews are clean.
|
||||
`GameWindow.cs` is now 8,205 raw lines.
|
||||
|
||||
## 1. Outcome and non-goals
|
||||
|
||||
At slice exit, `GameWindow.OnUpdate` starts the profiler scope and delegates one
|
||||
host delta to `UpdateFrameOrchestrator`. `GameWindow` composes concrete owners;
|
||||
it no longer selects a streaming observer, advances animations/effects,
|
||||
samples raw mouse input, sequences inbound network traffic, advances portal
|
||||
transit, or updates gameplay cameras.
|
||||
|
||||
The owner graph is explicit rather than a list of anonymous callbacks:
|
||||
|
||||
```text
|
||||
UpdateFrameOrchestrator
|
||||
├── live teardown convergence retry retained tombstones
|
||||
├── UpdateFrameClock normalized host time / PES clock
|
||||
├── StreamingFrameController residence/readiness pre-pass
|
||||
├── GameplayInputFrameController semantic input + mouse/combat intent
|
||||
├── RetailLiveFrameCoordinator retail SmartBox object barrier
|
||||
│ ├── LiveObjectFrameController local/ordinary/static/effect tails
|
||||
│ ├── LiveSessionController inbound network drain
|
||||
│ ├── RetailLocalPlayerFrame CommandInterpreter position tail
|
||||
│ └── LiveSpatialReconciler non-advancing authoritative refresh
|
||||
├── LiveEntityLivenessController expiry after accepted inbound state
|
||||
├── LocalPlayerTeleportController local transit/reveal/tunnel state
|
||||
├── PlayerModeAutoEntry one-shot ready-world entry
|
||||
├── PlayerModeController enter/exit/current-player lifetime
|
||||
└── CameraFrameController fly/chase/player presentation
|
||||
```
|
||||
|
||||
Concrete names may be narrowed during checkpoint A's source audit, but the
|
||||
ownership boundaries and order do not change. The orchestrator receives a
|
||||
small immutable per-frame input and concrete phase owners. It does not receive
|
||||
a hundred delegates, one mega `IUpdateFrameServices` context, or a service
|
||||
locator, and no transitive update collaborator calls a substantial method back
|
||||
on `GameWindow`.
|
||||
|
||||
This slice does not change simulation rate, object quantum rules, movement
|
||||
speed, interpolation, streaming radii, reveal readiness, portal visuals,
|
||||
particle range, combat cadence, key bindings, camera formulas, render order,
|
||||
or shutdown order. It adds no GL work and does not move retained-UI timers out
|
||||
of their render seam. Slice 7 owns the draw graph; Slice 8 owns final
|
||||
composition and Silk callback cleanup.
|
||||
|
||||
## 2. Retail oracle and accepted adaptation
|
||||
|
||||
The object/network barrier is pinned by
|
||||
[`2026-07-19-r6-update-object-order-pseudocode.md`](../research/2026-07-19-r6-update-object-order-pseudocode.md):
|
||||
|
||||
- `CPhysicsObj::update_object @ 0x00515D10` applies the strict object quantum;
|
||||
- `CPhysicsObj::UpdatePositionInternal @ 0x00512C30` advances the PartArray,
|
||||
position/physics candidate, then processes semantic hooks;
|
||||
- `CPhysicsObj::UpdateObjectInternal @ 0x005156B0` runs Detection → Target →
|
||||
Movement → `CPartArray::HandleMovement` → Position → Particle → Script;
|
||||
- `SmartBox::UseTime @ 0x00455410` advances object/physics time before the
|
||||
inbound queue and then calls `CommandInterpreter::UseTime`;
|
||||
- `gmSmartBoxUI::UseTime @ 0x004D6E30` owns UI/viewport time outside that
|
||||
object barrier.
|
||||
|
||||
At the retail host level, `Client::UseTime @ 0x00411C40` calls
|
||||
`UIElementManager::UseTime` before `SmartBox::UseTime`; the UI time broadcast
|
||||
reaches `gmSmartBoxUI::UseTime`. Retail also publishes player-camera work from
|
||||
the physics/player callback rather than from one post-network camera tail.
|
||||
acdream currently advances teleport/UI-camera presentation after its
|
||||
object/network/command barrier. Slice 6 preserves that existing order as
|
||||
registered TS-53; it does not cite `gmSmartBoxUI` as proof that the current
|
||||
post-network placement is retail-exact.
|
||||
|
||||
acdream preserves two documented presentation adaptations:
|
||||
|
||||
1. only semantic `AnimationDone` executes at retail's in-object hook slot;
|
||||
every other captured animation hook retains the current deferred path after
|
||||
final root, part, and equipped-child poses are published, including
|
||||
particles, audio, lights, translucency, `CallPES`, and default-script
|
||||
chaining (TS-50);
|
||||
2. shared particle simulation and PhysicsScript queues tick once per host
|
||||
update after both worksets rather than once per object's admitted quantum.
|
||||
This also means the current shared Particle → Script order does not reproduce
|
||||
the static workset's retail Script → Particle → hooks order (TS-51).
|
||||
|
||||
Asynchronous landblock streaming has no one-call retail equivalent. Its
|
||||
convergence pre-pass remains before inbound CreateObject dispatch so a newly
|
||||
accepted projection can find a resident bucket in the same frame. This
|
||||
pre-pass does not advance an object's retail clock. `WorldRevealCoordinator`
|
||||
remains the single login/portal readiness owner.
|
||||
|
||||
## 3. Fixed production phase order
|
||||
|
||||
The complete host-update graph is frozen as follows:
|
||||
|
||||
1. retry canonical live-entity teardown tombstones; an `AggregateException` is
|
||||
logged and later phases continue, while any other exception propagates;
|
||||
2. normalize the host delta, advance the shared PES clock once, and publish it;
|
||||
3. advance pending origin retirement/recenter, select the observer, tick
|
||||
streaming, and rebucket rescued persistent entities;
|
||||
4. tick `InputDispatcher`, consume one raw mouse sample, and advance combat
|
||||
attack intent;
|
||||
5. enter the non-reentrant inbound-event envelope and advance the pre-network
|
||||
live-object phase:
|
||||
- local object and pre-network movement/jump output;
|
||||
- queued selection/use output after movement edges;
|
||||
- ordinary live objects;
|
||||
- the distinct static-animation workset;
|
||||
- equipped children and static hook completion;
|
||||
- fades, every deferred non-`AnimationDone` hook, and current effect/light
|
||||
anchors;
|
||||
- particle visibility and particle simulation;
|
||||
- PhysicsScript queues;
|
||||
6. drain inbound session traffic inside one `GpuWorldState.MutationBatch`;
|
||||
7. run the local player's post-network `CommandInterpreter` position check;
|
||||
8. perform the ordinary post-network non-advancing root/child/emitter/light
|
||||
reconcile;
|
||||
9. advance liveness expiry from the absolute client timer;
|
||||
10. activate/tick local teleport transit. A Place edge commits the player and
|
||||
live root, immediately performs its own non-advancing reconcile, then
|
||||
notifies world reveal and continues tunnel state;
|
||||
11. evaluate one-shot player-mode auto-entry;
|
||||
12. update fly or chase/player camera presentation. If the local player was
|
||||
first created by this frame's inbound pass, publish its non-advancing
|
||||
projection, immediately reconcile a second time, then update the camera.
|
||||
|
||||
Those three reconcile edges—ordinary post-network, teleport Place, and
|
||||
inbound-created-player projection—are separate accepted production edges. Any
|
||||
future consolidation is a behavior change with its own oracle, not part of
|
||||
this extraction.
|
||||
|
||||
An invalid/non-positive host delta becomes zero for the PES/object/particle/
|
||||
tunnel delta but never blocks inbound traffic, absolute-time liveness,
|
||||
recenter convergence, or camera-state publication. Raw mouse idle uses its
|
||||
independent monotonic input clock. A missing gameplay camera/input owner never
|
||||
suppresses outer phases; the streaming controller may skip observer-driven
|
||||
streaming when it has no valid live or offline observer, while origin
|
||||
retirement still advances. Devtools keyboard capture pauses player/camera
|
||||
control including autorun; retained chat capture suppresses held WASD while
|
||||
autorun continues; mouse capture ends mouse-look but does not suppress
|
||||
keyboard, combat, network, streaming, teleport, or teardown.
|
||||
|
||||
## 4. Architecture and interfaces
|
||||
|
||||
### 4.1 Per-frame input
|
||||
|
||||
Use an immutable value equivalent to:
|
||||
|
||||
```csharp
|
||||
public readonly record struct UpdateFrameInput(double HostDeltaSeconds);
|
||||
```
|
||||
|
||||
Derived facts are read by their owning controller from stable runtime owners
|
||||
or from a narrowly typed state source. Do not pass `GameWindow`, expose its
|
||||
private fields wholesale, or create a service locator. Mutable player/session
|
||||
owners that legitimately change during login are represented by one focused
|
||||
state/source interface per domain, not anonymous closures into window methods.
|
||||
|
||||
`UpdateFrameClock` is constructed before any materializer/network owner that
|
||||
needs the current PES time. It owns the normalized host/PES clock and exposes a
|
||||
narrow `CurrentScriptTime` source; `_physicsScriptGameTime` does not remain a
|
||||
`GameWindow` field captured by later owners. Absolute liveness time and raw
|
||||
mouse idle time remain distinct monotonic sources and are not derived from the
|
||||
normalized simulation delta.
|
||||
|
||||
### 4.2 Object phase
|
||||
|
||||
`LiveObjectFrameController` owns the body currently in
|
||||
`AdvanceLiveObjectRuntimeCore`. It composes existing focused owners and keeps
|
||||
their retail order; it does not absorb their algorithms or identity maps.
|
||||
`LiveSpatialPresentationReconciler` owns the current no-time refresh body.
|
||||
`RetailLiveFrameCoordinator` depends on those typed owners plus the existing
|
||||
session and local-player owners. Its public `Tick` remains the one explicit
|
||||
object → network → command → reconcile barrier.
|
||||
|
||||
`RetailInboundEventDispatcher` continues wrapping the complete object phase.
|
||||
Reentrant packets queue until the current object's manager tail completes.
|
||||
The hot path remains allocation-free after warmup.
|
||||
|
||||
The extraction inventory includes the complete transitive update graph, not
|
||||
only `RetailLiveFrameCoordinator`: `RetailLocalPlayerFrameController` loses
|
||||
substantial window captures in favor of a focused local-player slot, movement
|
||||
input source, outbound owner, and projection owner;
|
||||
`LiveEntityAnimationPresenter` receives a concrete animation-presentation
|
||||
context instead of retaining `GameWindow` as
|
||||
`ILiveAnimationPresentationContext`; scheduler clock/player/projectile sources
|
||||
are similarly narrow. Existing small value/policy callbacks are acceptable
|
||||
only when they do not call a substantial body back on the window or create a
|
||||
second identity/state owner.
|
||||
|
||||
### 4.3 Streaming phase
|
||||
|
||||
`StreamingFrameController` owns observer selection and the sealed-dungeon
|
||||
gate. It receives the existing `StreamingOriginRecenterCoordinator`,
|
||||
`StreamingController`, `LiveWorldOriginState`/center state, camera/player
|
||||
state, physics current cell, `GpuWorldState`, and `LiveEntityRuntime` through
|
||||
typed seams. It never builds, publishes, or retires a landblock; those remain
|
||||
Slice 5 owners.
|
||||
|
||||
The controller preserves:
|
||||
|
||||
- no pre-login hardcoded-Holtburg streaming;
|
||||
- real-player-origin readiness before first streaming;
|
||||
- destination-origin pinning while in portal space;
|
||||
- authoritative player landblock during the login hold;
|
||||
- offline fly-camera observation only outside live in-world mode;
|
||||
- sealed EnvCell collapse using the cell's own landblock identity;
|
||||
- teleport-hold suppression of the stale source-cell dungeon gate;
|
||||
- rescued-entity rebucketing after the accepted streaming tick.
|
||||
|
||||
### 4.4 Input and camera phases
|
||||
|
||||
`GameplayInputFrameController` owns per-frame dispatcher hold delivery,
|
||||
UI-capture transitions, raw mouse sample/filter/idle handling, combat attack
|
||||
ticking, and the complete mouse-look/cursor-capture lifetime. It is the single
|
||||
owner of mouse-look state, saved cursor mode, begin/end/hide/restore, and
|
||||
movement teardown across press/release, capture change, focus loss, teleport,
|
||||
session reset, player-mode exit, and shutdown. Action press/release callbacks
|
||||
remain on `InputDispatcher` and movement stays owned by
|
||||
`PlayerMovementController`.
|
||||
|
||||
`PlayerModeController` owns enter/exit/toggle construction and the current
|
||||
player-controller, physics-host, chase-camera, and chase-mode slot. Manual
|
||||
input, `PlayerModeAutoEntry`, and local teleport activation all enter through
|
||||
that one owner. Streaming, camera, local-frame, and teleport owners receive
|
||||
narrow read sources from it; no path calls the substantial current
|
||||
`EnterPlayerModeNow`/exit bodies back on `GameWindow` or duplicates player-mode
|
||||
state.
|
||||
|
||||
`CameraFrameController` owns held fly-camera controls, retail chase offsets,
|
||||
post-network player presentation sampling, and both chase-camera updates. It
|
||||
does not own physics, target selection, or camera formulas. The local player
|
||||
created by the inbound pass gets a non-advancing projection before its first
|
||||
draw exactly as today, followed by the current second spatial reconcile before
|
||||
the camera samples that projection.
|
||||
|
||||
### 4.5 Teleport phase
|
||||
|
||||
`LocalPlayerTeleportController` owns only the current local-player transit
|
||||
state machine and presentation edges. It composes
|
||||
`TeleportTransitCoordinator`,
|
||||
`TeleportViewPlaneController`, `PortalTunnelPresentation`,
|
||||
`WorldRevealCoordinator`, streaming priority, and the session LoginComplete
|
||||
send. Checkpoint E first extracts the currently window-owned local aim,
|
||||
destination, hold/forced, activation, placement, reset, and session-clear
|
||||
state into this owner plus a concrete local placement collaborator where
|
||||
needed. It does not reuse `RemoteTeleportPlacement`, and network/session sinks
|
||||
do not retain `AimTeleportDestination` or reset callbacks into `GameWindow`.
|
||||
`LiveEntityLivenessController`, tombstone retry, and `PlayerModeAutoEntry`
|
||||
remain separate focused phases; teleport does not absorb them.
|
||||
|
||||
### 4.6 Composition, thread, and lifetime
|
||||
|
||||
The clock and focused dynamic sources are constructed before their first
|
||||
consumer. The final orchestrator is composed exactly once at the end of a
|
||||
successful `OnLoad`; `OnUpdate` fails fast if that invariant is broken rather
|
||||
than running a nullable partial graph. Every phase mutates state only on the
|
||||
existing update/render thread. Streaming worker completion remains behind
|
||||
`StreamingController.Tick`; the extraction adds no lock, task, or async entry.
|
||||
|
||||
Phase controllers borrow their input, session, streaming, portal, camera, and
|
||||
render-presentation owners; they do not dispose shared dependencies. Any new
|
||||
event subscription has explicit symmetric detach. During shutdown the update
|
||||
collaborators remain alive through the first-stage `LiveSessionController`
|
||||
reset, then retire before their underlying input/camera/portal/streaming
|
||||
resources. Existing retryable shutdown ordering remains authoritative.
|
||||
|
||||
## 5. Checkpoints and acceptance
|
||||
|
||||
### A — contract and guards
|
||||
|
||||
- record the exact production phase graph and every mutable dependency;
|
||||
- correct and pin the retail oracle for physics-hook retention/reentrant
|
||||
append, inactive object behavior, and static Script → Particle → hooks; pin
|
||||
the existing TS-50/TS-51 divergences without reordering runtime behavior in
|
||||
this ownership-only slice;
|
||||
- add an explicit orchestrator order test covering all twelve phases and the
|
||||
three conditional reconcile edges;
|
||||
- pin normalized simulation/PES/particle/tunnel time separately from absolute
|
||||
liveness and raw-mouse idle clocks for NaN, both infinities, non-positive
|
||||
values, and finite values above `float.MaxValue`;
|
||||
- pin missing live/offline camera behavior and the separate devtools keyboard,
|
||||
retained-chat keyboard, and mouse-capture contracts;
|
||||
- pin teardown retry success, `AggregateException` log-and-continue, other
|
||||
exception propagation, and next-frame retry;
|
||||
- add source guards that prevent `OnUpdate` or the coordinator from regaining
|
||||
substantial callback bodies, and prevent any transitive update owner from
|
||||
retaining `GameWindow` or one mega service context;
|
||||
- verify current production traces before cutover.
|
||||
|
||||
### B — object/network barrier
|
||||
|
||||
- move the pre-network animation/effect body into
|
||||
`LiveObjectFrameController`;
|
||||
- move no-time root/child/effect refresh into
|
||||
`LiveSpatialPresentationReconciler`;
|
||||
- replace coordinator callbacks with typed collaborators;
|
||||
- preserve semantic/visual hook separation, owner-incarnation revalidation,
|
||||
static-after-ordinary order, ordinary Particle-before-Script, the current
|
||||
shared-tail TS-51 adaptation, and once-per-host tails;
|
||||
- delete `AdvanceLiveObjectRuntimeCore` and
|
||||
`ReconcileLiveObjectSpatialPresentation` from `GameWindow`.
|
||||
|
||||
Focused tests cover `RetailObjectActivityGate` and
|
||||
`RetailObjectQuantumClock`: parented/cell-less/Frozen suspension, inactive
|
||||
Particle/Script-only time, Hidden+Active adjustment/hooks/manager time, and
|
||||
separate ordinary epsilon/Min/Max/Huge plus static epsilon/Huge boundaries.
|
||||
They also cover incomplete physics-hook
|
||||
retention, same-drain animation-hook append, deferred `CallPES`/default-script/
|
||||
translucency routing, physics-hook append deferred until the next drain,
|
||||
completed physics-hook removal preserving the snapshotted successor, static
|
||||
hooks, callback GUID reuse, and reentrant inbound packets. Particle then Script
|
||||
must run exactly once for an empty world,
|
||||
multiple ordinary/static owners, invalid delta, and reentrant inbound work;
|
||||
the one published PES clock remains visible from hook capture through the
|
||||
script tick.
|
||||
|
||||
Checkpoint B removes the coordinator, animation presenter/scheduler, runtime
|
||||
view, static-root, and projectile callbacks into `GameWindow`. The callback
|
||||
composition still retained by `RetailLocalPlayerFrameController` is explicitly
|
||||
deferred: checkpoint D owns its input/capture sources and checkpoint F owns its
|
||||
mutable player-mode/controller/session sources. A structural test permits that
|
||||
one named legacy owner only; D/F must remove the exception before final cutover.
|
||||
|
||||
### C — streaming frame
|
||||
|
||||
- extract readiness/recenter advancement, observer selection, dungeon gate,
|
||||
streaming tick, and rescued projection rebucketing;
|
||||
- test login-before-player-spawn, outdoor/dungeon/portal-hold/offline observer
|
||||
cases, missing live/offline camera sources, and cell-landblock identity;
|
||||
- prove same-frame streaming completion precedes CreateObject projection into
|
||||
the resident bucket, unknown pre-login origin enqueues nothing, and recenter
|
||||
retirement continues while ordinary streaming is gated;
|
||||
- verify no build/publication/retirement ownership leaks back from Slice 5.
|
||||
|
||||
### D — input frame
|
||||
|
||||
- extract dispatcher tick, UI mouse-capture edge, raw sample/filter/idle path,
|
||||
combat intent tick, and the one cross-lifetime mouse-look/cursor owner;
|
||||
- test capture entry while mouse-look is active, zero-idle drift stop,
|
||||
production action ordering, devtools-vs-chat autorun semantics, mouse-only
|
||||
capture, and focus/teleport teardown;
|
||||
- prove every non-input phase still runs under each capture mode;
|
||||
- retain diagnostic semantic input as the unattended connected gate seam.
|
||||
|
||||
### E — local teleport and post-network world frame
|
||||
|
||||
- extract the currently window-owned local-player teleport state and every
|
||||
aim/destination/activation/place/reset/session port before moving its tick;
|
||||
- retain liveness and auto-entry as separate orchestrator calls around pending
|
||||
transit activation, readiness/hold, tunnel events, materialization, and
|
||||
reveal completion;
|
||||
- test loaded and pending destinations, forced-readiness boundary, same-cell
|
||||
respawn, LoginComplete ordering, delete/reset, and no second reveal owner;
|
||||
- pin Place → immediate reconcile → reveal and old-GUID liveness candidate →
|
||||
inbound delete/recreate generation safety;
|
||||
- preserve the completed portal visuals and world-reveal barrier unchanged.
|
||||
|
||||
**Completed 2026-07-22.** `LocalPlayerTeleportController` now owns the full
|
||||
local transit lifetime and its immutable network handoff, while
|
||||
`PlayerModeController` is the sole writer of controller, physics-host,
|
||||
player-mode, and chase-camera slots. Local animation, shadow synchronization,
|
||||
sealed-dungeon classification, viewport aspect, and approach-completion
|
||||
handoff are focused collaborators rather than window callbacks. Portal entry
|
||||
retires the one-shot auto-entry guard, host publication is transactional, and
|
||||
teleport work revalidates the live generation after every reentrant edge.
|
||||
Focused App/Core tests cover destination readiness, forced holds, same-cell
|
||||
placement, LoginComplete order, reset/disposal, GUID reuse, reentrancy,
|
||||
host rollback, auto-entry idempotence, and completion-mailbox ordering. The
|
||||
full App suite is green at 2,814 passed / 3 skipped. `GameWindow.cs` is 7,351
|
||||
lines at this checkpoint, down from the 15,723-line campaign baseline.
|
||||
|
||||
### F/G — camera, cutover, and deletion
|
||||
|
||||
- extract player-mode enter/exit/toggle and current-owner state before camera
|
||||
or teleport cutover; retain `PlayerModeAutoEntry` only as its one-shot
|
||||
readiness guard;
|
||||
- extract fly camera, retail chase adjustment, local post-network projection,
|
||||
combat target tracking, and chase-camera publication;
|
||||
- pin inbound-created projection → immediate second reconcile → camera, with
|
||||
exact root/child/emitter/light refresh counts and no duplicate time advance;
|
||||
- compose `UpdateFrameOrchestrator` only after all dependencies exist;
|
||||
- replace `OnUpdate` with profiler scope plus one `Tick` call;
|
||||
- remove obsolete frame fields/facades and prove there is no back-reference to
|
||||
`GameWindow` from an extracted owner;
|
||||
- measure line/field/method count. The target is below 8,000 lines, but the
|
||||
ownership/ordering exit criteria outrank the count.
|
||||
|
||||
**Checkpoint F completed 2026-07-22.** `CameraFrameController` now owns fly
|
||||
held input, retail chase adjustment, the inbound-created-player projection
|
||||
and second reconcile, both chase-camera publications, and combat-target
|
||||
tracking. `RetailLocalPlayerFrameController` and
|
||||
`LocalPlayerProjectionController` resolve current identity, session, world,
|
||||
shadow, and player slots through focused typed runtimes instead of callbacks
|
||||
into `GameWindow`; the camera receives only the two-read player-presentation
|
||||
seam. Projection and frame owners remain composition locals, so the window
|
||||
retains no duplicate lifecycle state. Focused Release tests are green at
|
||||
37/37, the App suite at 2,820 passed / 3 skipped, and the full suite at 7,179
|
||||
passed / 5 skipped. All three corrected-diff reviews are clean.
|
||||
`GameWindow.cs` is 7,160 lines, down 8,563 lines (54.5%) from the 15,723-line
|
||||
campaign baseline.
|
||||
|
||||
**Checkpoint G completed 2026-07-22.** `GameWindow.OnUpdate` now owns only the
|
||||
update profiler scope and one `UpdateFrameOrchestrator.Tick` call. Teardown,
|
||||
absolute liveness time, local timestamp publication, expiry deletion,
|
||||
player-mode auto-entry, live-origin initialization, Hidden PartArray
|
||||
boundaries, remote teleport placement, and all twelve live-entity session
|
||||
packet routes resolve through typed runtime owners; the former transitive
|
||||
window callbacks and obsolete frame fields are gone. The App suite is green
|
||||
at 2,823 passed / 3 skipped. Focused orchestration, lifecycle, presentation,
|
||||
teleport, origin, and session tests are green, and all three corrected-diff
|
||||
reviews are clean. `GameWindow.cs` is 7,026 raw lines / 241 fields / 108
|
||||
methods, down 1,785 lines and 45 methods from the Slice-6 baseline and 8,697
|
||||
lines (55.3%) from the 15,723-line pre-extraction class.
|
||||
|
||||
### H — release and connected gates
|
||||
|
||||
After all three independent corrected-diff reviews are clean:
|
||||
|
||||
1. run every focused App/Core/Net/UI test project;
|
||||
2. run `dotnet build AcDream.slnx -c Release`;
|
||||
3. run the full Release suite;
|
||||
4. run the connected lifecycle/reconnect gate;
|
||||
5. run the synchronized nine-stop resource soak with movement, turn, jump,
|
||||
combat, portal materialization waits, fatal-log scan, and graceful close;
|
||||
6. compare lifecycle, outbound movement, materialization, entity/animation,
|
||||
frame-profile, resource, and teardown traces with the Slice 5 baseline;
|
||||
7. audit the divergence register, architecture, milestones, roadmap, issues,
|
||||
`AGENTS.md`, `CLAUDE.md`, and durable memory.
|
||||
|
||||
The slice requires no new visual behavior. If connected screenshots or traces
|
||||
change, return to the responsible checkpoint; do not defer the difference to
|
||||
Slice 7.
|
||||
|
||||
**Completed 2026-07-22.** The complete Release suite is green at 7,182 passed /
|
||||
5 fixture or environment skips. The deterministic lifecycle/reconnect gate
|
||||
passed in 314.195 seconds across capped login/travel checkpoints, graceful close,
|
||||
and a fresh-process reconnect. Its only warning was the expected set of 25
|
||||
world-edge empty landblocks. The synchronized nine-stop resource soak passed in
|
||||
393.581 seconds with movement, turn, jump, combat, teleport waits, fatal-log scan,
|
||||
and graceful exit. The final Caul plateau held the same 21,025 entity and 13
|
||||
animation-owner counts as the earlier Caul return, with update-frame p95 at
|
||||
0.8 ms; working/private memory increased by 112.2/111.0 MiB across that retained
|
||||
route, inside the existing deterministic gate. The soak's only diagnostics were
|
||||
35 known DAT-driven missing VFX table/emitter records and the same 25 expected
|
||||
world-edge misses. The divergence audit keeps TS-50, TS-51, and TS-53 unchanged
|
||||
and found no new behavior adaptation. Final `GameWindow` size is 7,026 raw
|
||||
lines / 241 fields / 108 methods, versus 8,811 / 247 / 153 at Slice 6 entry and
|
||||
15,723 / 278 / 205 at the campaign baseline.
|
||||
|
||||
## 6. Review gate for every checkpoint
|
||||
|
||||
After each implementation checkpoint:
|
||||
|
||||
1. run focused tests and inspect the complete diff;
|
||||
2. retail-conformance review checks named functions, exact order, Hidden/
|
||||
quantum semantics, and adaptation boundaries;
|
||||
3. architecture review checks dependency direction, absence of a replacement
|
||||
god object/service locator, same-thread mutation, and symmetric teardown;
|
||||
4. adversarial review checks independent clocks, callback reentrancy,
|
||||
owner/GUID reuse, login/portal transitions, capture modes, missing observer
|
||||
sources, teardown failures, and early-return phase suppression;
|
||||
5. fix every confirmed root cause and repeat review until clean;
|
||||
6. build/test Release and commit the checkpoint before starting the next one.
|
||||
|
||||
## 7. Divergence and documentation bookkeeping
|
||||
|
||||
TS-50, TS-51, and the newly documented existing host-order TS-53 remain until
|
||||
their exact adaptations are ported; this slice must not overclaim retail
|
||||
parity. IA-14/AD-22 and the lifetime/resource rows are audited against the
|
||||
final owner graph but changed only if their mechanism actually changes. Any
|
||||
newly discovered unavoidable adaptation gets a register row in the same
|
||||
commit. The Slice 6 closeout updates the code-structure table,
|
||||
roadmap, milestones, session docs, and
|
||||
`project_gamewindow_decomposition.md` with exact commits, test counts, gates,
|
||||
and final metrics.
|
||||
|
|
@ -1,602 +0,0 @@
|
|||
# GameWindow Slice 7 — render-frame orchestration
|
||||
|
||||
**Status:** Complete 2026-07-22.
|
||||
**Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 7.
|
||||
**Baseline:** `9512404e`; `GameWindow.cs` is 7,026 raw lines, 241 fields, and
|
||||
108 methods. The Release suite passes 7,182 tests / 5 fixture or environment
|
||||
skips. The 314-second lifecycle/reconnect gate and 394-second synchronized
|
||||
nine-stop resource soak pass.
|
||||
**Behavior rule:** This is an ownership extraction. Preserve every accepted
|
||||
world/PView/alpha/private-viewport/UI draw edge, render-thread publication
|
||||
edge, GL-state boundary, and GPU-flight failure contract. Do not combine the
|
||||
extraction with a render-quality change, visibility correction, frame-order
|
||||
port, or resource-lifetime redesign.
|
||||
|
||||
## Progress ledger
|
||||
|
||||
- [x] A — freeze the complete render graph, correct the architecture SSOT, and
|
||||
introduce data-only frame contracts plus deterministic order/failure tests.
|
||||
- [x] B — extract paperdoll, panel-layout/devtools, and render-diagnostics leaf
|
||||
owners while `GameWindow` still orders the frame.
|
||||
- [x] C — extract frame-resource begin/upload/publication and live display,
|
||||
and weather/display foundations that do not depend on camera/root
|
||||
classification.
|
||||
- [x] D — extract frame-data classification and reusable world scratch without
|
||||
duplicating live identity, spatial, visibility, or resource ownership; then
|
||||
run the dependent audio, sky, lighting, and listener preparation.
|
||||
- [x] E — replace `RetailPViewDrawContext`'s callback bag with a typed
|
||||
`RetailPViewPassExecutor` and data-only inputs.
|
||||
- [x] F — extract `WorldSceneRenderer`, including both shared-alpha scopes,
|
||||
PView/fallback world branches, particles, debug world draw, and completion.
|
||||
- [x] G — compose `RenderFrameOrchestrator`, cut `GameWindow.OnRender` to one
|
||||
handoff, and delete obsolete frame bodies, fields, helpers, and callbacks.
|
||||
- [x] H — corrected-diff reviews, full Release suite, connected lifecycle and
|
||||
soak gates, framebuffer comparison, documentation, memory, and metrics.
|
||||
|
||||
Every checked checkpoint is one bisectable architectural commit. A checkpoint
|
||||
does not complete while a new owner merely delegates a substantial body back to
|
||||
`GameWindow`.
|
||||
|
||||
## 1. Outcome and non-goals
|
||||
|
||||
At slice exit, `GameWindow.OnRender` supplies one immutable delta/viewport input
|
||||
to `RenderFrameOrchestrator`. The orchestrator owns the GPU-flight transaction
|
||||
and composes focused frame-resource, world-scene, private-presentation, and
|
||||
diagnostic phases. `GameWindow` remains the construction and shutdown shell; it
|
||||
does not keep the draw graph, PView pass callbacks, frame scratch, paperdoll
|
||||
assembly, or performance-title body.
|
||||
|
||||
The target ownership graph is:
|
||||
|
||||
```text
|
||||
RenderFrameOrchestrator
|
||||
├── RenderFrameResourceController GPU slot + resource BeginFrame/upload
|
||||
├── WorldRenderFrameBuilder camera/root/light/classification facts
|
||||
├── WorldSceneRenderer accepted world draw graph
|
||||
│ ├── RetailPViewRenderer visibility/order owner
|
||||
│ ├── RetailPViewPassExecutor concrete GL pass implementation
|
||||
│ └── WorldRenderDiagnostics probes/debug counters/signatures
|
||||
├── PrivatePresentationRenderer portal + paperdoll + retained UI + ImGui
|
||||
│ ├── PaperdollFramePresenter
|
||||
│ └── DevToolsFramePresenter
|
||||
└── RenderFrameDiagnosticsController title/resource/frame snapshots
|
||||
```
|
||||
|
||||
Names may narrow as code lands, but the ownership boundaries do not collapse
|
||||
into one replacement god object. No new owner may receive `GameWindow`, a broad
|
||||
service locator, or an anonymous list of callbacks into the window.
|
||||
|
||||
This slice does **not** change:
|
||||
|
||||
- the viewer-cell render root or player-cell lighting root;
|
||||
- synthetic outdoor-cell behavior or the null-root safety path;
|
||||
- portal flood, building pre-gates, clip-plane/scissor limits, punch/seal
|
||||
depth behavior, shell lift, viewcone culling, or draw distances;
|
||||
- shared-alpha sort keys, scope boundaries, blend/cull behavior, or immediate
|
||||
EnvCell/private alpha;
|
||||
- weather, fog, point-light selection, particle visibility/range, terrain
|
||||
quality, FOV, frame pacing, or audio-listener formulas;
|
||||
- portal-space, paperdoll, retained gameplay UI, ImGui, or screenshot visuals;
|
||||
- resource ownership, shutdown/disposal order, or three-frame GPU retirement;
|
||||
- TS-53's retained-UI time placement on the render seam.
|
||||
|
||||
Issue #225's translucent lifestone/particle comparison remains a separate user
|
||||
visual gate. Passing this structural slice must not mark it resolved.
|
||||
|
||||
## 2. Retail oracle and accepted adaptations
|
||||
|
||||
This extraction reuses the existing named-retail research rather than
|
||||
reinterpreting the draw algorithms:
|
||||
|
||||
- `SceneTool::BeginScene @ 0x0043DAD0` establishes the black scene-clear
|
||||
baseline used by portal CreatureMode replacement;
|
||||
- `SmartBox::DrawNoBlit @ 0x00454C20` updates the viewer and invokes the normal
|
||||
world render before post-world selection work;
|
||||
- `SmartBox::RenderNormalMode @ 0x00453AA0` owns landscape/inside dispatch and
|
||||
the final retail alpha flush;
|
||||
- `RenderDeviceD3D::DrawInside @ 0x0059F0D0`,
|
||||
`PView::DrawInside @ 0x005A5860`, and
|
||||
`PView::ConstructView @ 0x005A57B0` own the portal traversal;
|
||||
- `PView::DrawCells @ 0x005A4840` fixes the outside-landscape → alpha flush →
|
||||
conditional depth clear → exit masks → shells → object-list order;
|
||||
- `LScape::draw @ 0x00506330`, `GameSky::Draw @ 0x00506FF0`,
|
||||
`DrawBuilding @ 0x0059F2A0`, `DrawBlock @ 0x005A17C0`, and
|
||||
`DrawObjCellForDummies @ 0x005A0760` constrain the landscape/building/cell
|
||||
phases;
|
||||
- `AddMeshToAlphaList @ 0x0059C230` and
|
||||
`FlushAlphaList @ 0x0059D2E0` constrain alpha submission/flush boundaries;
|
||||
- `CPhysicsPart::UpdateViewerDistance @ 0x0050E030` and
|
||||
`CShadowPart::insertion_sort @ 0x006B5130` constrain transformed DAT
|
||||
SortCenter/CYpt and stable far-to-near ordering;
|
||||
- `CreatureMode::Render @ 0x004529D0` constrains portal and paperdoll private
|
||||
viewports;
|
||||
- `SceneTool::EndFrame @ 0x0043FB30` proves retail gameplay UI precedes retail
|
||||
profiler/debug overlays; placing ImGui above gameplay UI is acdream's IA-12
|
||||
modern coexistence adaptation, not literal Keystone parity.
|
||||
|
||||
The readable pseudocode oracle is
|
||||
[`docs/research/2026-06-05-retail-pview-indoor-render-pseudocode.md`](../research/2026-06-05-retail-pview-indoor-render-pseudocode.md),
|
||||
with the current full reference in
|
||||
[`docs/research/2026-06-02-retail-render-pipeline-full-reference.md`](../research/2026-06-02-retail-render-pipeline-full-reference.md).
|
||||
Shared-alpha ordering is pinned separately by
|
||||
[`docs/research/2026-07-18-retail-shared-alpha-list-pseudocode.md`](../research/2026-07-18-retail-shared-alpha-list-pseudocode.md).
|
||||
|
||||
This slice preserves, rather than retires, the registered modern adaptations:
|
||||
IA-8/9/10/12/14/15; AD-16/17/19/24;
|
||||
AP-21/32/33/34/35/85/92/115/116/117;
|
||||
TS-6/7; and TS-53. Source pointers move with ownership in the same commit, but
|
||||
mechanism/risk language changes only if behavior actually changes.
|
||||
|
||||
## 3. Frozen production render order
|
||||
|
||||
The complete accepted order is:
|
||||
|
||||
1. call `GpuFrameFlightController.BeginFrame` outside the render `try`;
|
||||
2. begin the composite-texture frame and cache tick;
|
||||
3. begin, in order, dispatcher, EnvCell, portal depth, world text, retained-UI
|
||||
text, clip, terrain, lighting UBO, then profiler frame state for the current
|
||||
GPU slot;
|
||||
4. select the portal replacement viewport; reset particle visibility when it
|
||||
is active;
|
||||
5. choose portal black or normal fog clear color, force depth writes, clear
|
||||
color/depth/stencil, and establish CW/back-face frame-global state;
|
||||
6. run GL-state/surface diagnostics;
|
||||
7. advance render-thread publication in order: `WbMeshAdapter.Tick`, destination
|
||||
reveal preparation/evaluation, then `ParticleRenderer.BeginFrame`;
|
||||
8. begin ImGui, tick weather, and begin retail selection-scene accumulation;
|
||||
9. when a normal camera/world viewport is active:
|
||||
- begin the one frame-scoped `RetailAlphaQueue` collection; its landscape
|
||||
`Flush` drains the first sub-scope and `EndFrame` later drains the final
|
||||
world sub-scope without a second begin;
|
||||
- resolve camera/projection/frustum and begin particle/terrain visibility;
|
||||
- publish world-reveal visibility/completion;
|
||||
- preview audio/display settings and publish the listener;
|
||||
- resolve player lighting cell and collided viewer render cell;
|
||||
- build prior-visible-cell point-light candidacy, sun/fog/lighting UBO, and
|
||||
animated/equipped classification;
|
||||
- construct the synthetic outdoor root/building candidates when required;
|
||||
- run the null-root safety world or the one `RetailPViewRenderer.DrawInside`
|
||||
graph;
|
||||
- draw the post-world Scene particles/weather fallback and debug geometry;
|
||||
- sample current-frame DebugVM visible-landblock and nearest-collision facts
|
||||
before the developer UI can consume them;
|
||||
- close the final shared-alpha scope and publish terrain/cell particle
|
||||
visibility;
|
||||
10. complete retail selection-scene accumulation;
|
||||
11. draw the portal CreatureMode replacement viewport;
|
||||
12. render the private paperdoll FBO before its 2-D widget blit;
|
||||
13. tick/update-cursor/draw retained gameplay UI;
|
||||
14. render ImGui menus and developer panels above gameplay UI;
|
||||
15. capture a requested default-framebuffer screenshot;
|
||||
16. publish window-title, resource-dump, and frame-profile diagnostics;
|
||||
17. close the exact GPU frame once in `finally`.
|
||||
|
||||
Within `RetailPViewRenderer.DrawInside`:
|
||||
|
||||
1. construct the main PView; merge per-building exterior floods only for the
|
||||
synthetic outdoor root and build separate look-in frames for interior roots;
|
||||
2. assemble/upload the one clip arena, prepare EnvCell batches, partition
|
||||
statics/dynamics, and classify outside-stage dynamics;
|
||||
3. draw each outside slice early: sky, terrain, and outdoor statics;
|
||||
4. draw building look-ins: all far-Z punches, opaque shells, then transparent
|
||||
shells plus statics/dynamics/emitters;
|
||||
5. draw outside-stage dynamic meshes, Scene particles, and weather late;
|
||||
6. draw one interior-root unattached Scene-particle pass;
|
||||
7. flush the **landscape** shared-alpha scope;
|
||||
8. for an interior root, perform the full depth clear, then write true-depth
|
||||
exit seals; for an outdoor root, retain world depth and use far-Z entry
|
||||
punches;
|
||||
9. draw opaque EnvCell shells once, then transparent shell cells immediate
|
||||
far-to-near;
|
||||
10. draw cell statics as one cross-cell batch, then their particles;
|
||||
11. draw the surviving main-flood dynamics last, then their particles; look-in
|
||||
and outside-stage dynamics remain in their earlier landscape phases.
|
||||
|
||||
The landscape alpha flush must never move across the depth clear. The final
|
||||
world alpha close must never move after portal/paperdoll private viewports.
|
||||
|
||||
## 4. Architecture and interfaces
|
||||
|
||||
### 4.1 Frame transaction
|
||||
|
||||
Use immutable values equivalent to:
|
||||
|
||||
```csharp
|
||||
internal readonly record struct RenderFrameInput(
|
||||
double DeltaSeconds,
|
||||
int ViewportWidth,
|
||||
int ViewportHeight);
|
||||
|
||||
internal readonly record struct WorldRenderFrameOutcome(
|
||||
int VisibleLandblocks,
|
||||
int TotalLandblocks,
|
||||
bool NormalWorldDrawn);
|
||||
```
|
||||
|
||||
Portal-viewport selection and drawing belong to `PrivatePresentationRenderer`,
|
||||
which runs after the world phase. If a combined public outcome becomes useful,
|
||||
the top-level orchestrator composes it only after private presentation; the
|
||||
world owner never claims that a later viewport was drawn.
|
||||
|
||||
`RenderFrameOrchestrator` receives a small fixed set of typed phase owners. It
|
||||
does not receive every renderer separately, a mutable frame-services bag, or
|
||||
`GameWindow`.
|
||||
|
||||
`BeginFrame` remains outside the `try`: if it fails, no `EndFrame` is attempted.
|
||||
After begin succeeds, every failure closes exactly once. Render failure plus
|
||||
close failure becomes the current explicit `AggregateException`; close-only
|
||||
failure propagates directly. Alpha, particle-visibility, and selection-scene
|
||||
completion retain their current non-finally behavior; changing that recovery
|
||||
contract requires separate approval and evidence.
|
||||
|
||||
### 4.2 Borrowed resources and shutdown
|
||||
|
||||
The frame orchestrator and phase owners borrow the already-created GL/render
|
||||
owners and do not dispose them. The developer-tools backend is itself the
|
||||
canonical owner of its ImGui bootstrap and disposes that bootstrap when the
|
||||
existing retryable shutdown transaction retires the backend; the frame
|
||||
presenter only borrows it. Shutdown keeps its proven dependency order: wait for
|
||||
submitted work; withdraw UI/render publications; release textures and mesh
|
||||
owners; then dispose GL backing stores, with `GpuFrameFlightController` last
|
||||
among render resources. Slice 8 may group construction/disposal after the
|
||||
render cutover is stable.
|
||||
|
||||
`DisplayFramePacingController` is the focused shared owner for requested VSync,
|
||||
live display preview, monitor/window callbacks, and the separate
|
||||
`OnFrameRendered` pacing event. Render preparation and window lifecycle wiring
|
||||
invoke that typed owner directly; neither calls back into a substantial
|
||||
`GameWindow` method. Its existing disposal remains in the Slice 7 shutdown
|
||||
transaction.
|
||||
|
||||
### 4.3 World frame data
|
||||
|
||||
`WorldRenderFrameBuilder` owns reusable classification scratch:
|
||||
|
||||
- animated/equipped entity IDs;
|
||||
- synthetic outdoor node and candidate building cells;
|
||||
- previous-frame drawable cells used by point-light candidacy;
|
||||
- camera/root/classification facts with one-frame borrowed lifetime.
|
||||
|
||||
The builder owns only facts available before PView execution. Slice-local
|
||||
visible/outdoor/unattached particle-owner IDs belong to
|
||||
`RetailPViewPassExecutor`. The current interior partition is produced by
|
||||
`RetailPViewRenderer.DrawInside` and remains in its borrowed result/diagnostic
|
||||
snapshot; it is never stored as stale pre-draw builder state.
|
||||
|
||||
It queries canonical `LiveEntityRuntime`, `GpuWorldState`, player-mode/camera,
|
||||
physics cell graph, cell visibility, and landblock presentation sources. It
|
||||
does not copy their dictionaries or create a second visibility computation.
|
||||
Returned collections are borrowed until the next build; tests pin this
|
||||
lifetime and consumers cannot retain them.
|
||||
|
||||
### 4.4 PView pass execution
|
||||
|
||||
Replace the current `RetailPViewDrawContext` callback bag with a data-only
|
||||
`RetailPViewFrameInput`, a typed cell-visibility source, and one explicit
|
||||
`IRetailPViewPassExecutor`/`RetailPViewPassExecutor`. The executor owns the GL
|
||||
resources needed for these named operations:
|
||||
|
||||
```text
|
||||
SetTerrainClip
|
||||
DrawLandscapeSlice
|
||||
DrawLandscapeSliceLate
|
||||
ClearInteriorDepth
|
||||
DrawExitPortalMask
|
||||
DrawLookInPortalPunch
|
||||
DrawUnattachedSceneParticles
|
||||
FlushLandscapeAlpha
|
||||
DrawCellParticles
|
||||
DrawDynamicsParticles
|
||||
EmitDiagnostics
|
||||
```
|
||||
|
||||
`RetailPViewRenderer` remains the retail ordering owner and calls these typed
|
||||
operations. The executor may not call `GameWindow` or make independent
|
||||
visibility/order decisions. Existing borrowed `RetailPViewFrameResult` scratch
|
||||
semantics remain explicit.
|
||||
|
||||
### 4.5 Presentation and diagnostics
|
||||
|
||||
`PaperdollFramePresenter` owns dirty state, DAT-backed clone/re-dress, pose
|
||||
selection/application, and FBO rendering. Inventory/session paths call
|
||||
`MarkDirty`; they do not mutate its internals.
|
||||
|
||||
`PrivatePresentationRenderer` owns portal viewport, paperdoll, retained UI,
|
||||
ImGui, and screenshot order. A focused panel-layout/devtools owner handles menu
|
||||
actions and is also reused by framebuffer resize; it cannot call a substantial
|
||||
window method.
|
||||
|
||||
`WorldRenderDiagnostics` owns pass-local PView/GL/scissor state, render
|
||||
signatures, and world-pass timings. `RenderFrameDiagnosticsController` owns
|
||||
FPS/frame-time accumulation, final visible-landblock counters, performance
|
||||
title/resource publication, and the public render-frame snapshot. Only
|
||||
render-derived DebugVM providers move in Slice 7. Update/session/streaming
|
||||
providers and the cross-domain world-lifecycle resource sampler stay at the
|
||||
composition boundary until Slice 8 rather than turning either diagnostics
|
||||
owner into a replacement service locator. Diagnostics observe the frame; they
|
||||
never choose a draw branch or mutate canonical world ownership.
|
||||
|
||||
## 5. Implementation checkpoints
|
||||
|
||||
### A — contracts, architecture, and oracles
|
||||
|
||||
- correct the stale render-pipeline and per-frame architecture sections;
|
||||
- add `RenderFrameInput`, phase interfaces, and a recording orchestrator;
|
||||
- pin the outer resource/world/private/diagnostic order across normal,
|
||||
portal-replacement, login-wait, and screenshot outcome shapes; defer the
|
||||
concrete indoor/outdoor/null-root branch tests to the world builder and scene
|
||||
owner in D/F; camera is an initialized runtime invariant, so the slice does
|
||||
not invent a new graceful missing-camera path;
|
||||
- pin the exact GPU begin/end exception matrix;
|
||||
- add source guards against `GameWindow` back-references, mega contexts, and a
|
||||
new PView delegate bag;
|
||||
- capture the current connected framebuffer/resource baseline.
|
||||
|
||||
Completed 2026-07-22. `RenderFrameOrchestrator` now owns the inert five-owner
|
||||
outer transaction contract; `GpuFrameFlightController` implements its narrow
|
||||
lifetime seam. Twenty-six focused render/GPU tests pin value-only contracts,
|
||||
required-owner construction, exact phase prefixes, per-phase input propagation,
|
||||
and every begin/render/close failure combination. Release build and the full
|
||||
suite pass (7,199 passed / 5 skipped after adding 17 tests). All three
|
||||
corrected-diff reviews are clean; production `GameWindow.OnRender` is unchanged.
|
||||
|
||||
### B — leaf presentation and diagnostics
|
||||
|
||||
- extract paperdoll refresh/pose/dirty/FBO ownership;
|
||||
- extract panel-layout reuse and devtools menu/panel rendering;
|
||||
- extract pass-local render signature, PView/GL/scissor probes, and timing state
|
||||
into `WorldRenderDiagnostics`; extract FPS, final counts, title/resource
|
||||
publication, and the public render snapshot into
|
||||
`RenderFrameDiagnosticsController`;
|
||||
- preserve the pre-presentation render-derived DebugVM sampling site separately
|
||||
from the post-screenshot title/resource publication site; leave non-render
|
||||
DebugVM providers and cross-domain world-lifecycle sampling at composition;
|
||||
- keep call sites in their current `GameWindow.OnRender` positions.
|
||||
|
||||
Completed 2026-07-22. Paperdoll clone/re-dress/pose/FBO work now belongs to
|
||||
`PaperdollFramePresenter`; retained surfaces are narrow and optional, while
|
||||
live identity and DAT pose application resolve through focused adapters.
|
||||
`DevToolsFramePresenter` owns the ImGui frame, menus, panels, layout, and input
|
||||
actions while borrowing the bootstrap; `ImGuiDevToolsFrameBackend` explicitly
|
||||
owns and disposes that bootstrap at shutdown.
|
||||
`WorldRenderDiagnostics`, `DebugVmRenderFactsPublisher`, and
|
||||
`RenderFrameDiagnosticsController` own the former render probes, debug facts,
|
||||
title/resource cadence, and public snapshot. The production handoff now reports
|
||||
the observed login/world and screenshot outcomes, preserves the original shared
|
||||
TERRAIN→FRAME diagnostic transaction, and keeps the devtools-off scan path
|
||||
allocation-free. `GameWindow.cs` is 6,270 raw lines, 60.1% below the 15,723-line
|
||||
campaign baseline. Sixty-two focused tests, the Release build, and the complete
|
||||
suite pass (7,255 passed / 5 skipped). Retail, architecture, and adversarial
|
||||
corrected-diff reviews are clean.
|
||||
|
||||
### C — frame resources and live preparation
|
||||
|
||||
- extract ordered per-resource `BeginFrame` calls, clear/state establishment,
|
||||
histogram probe, mesh upload, reveal evaluation, and particle begin;
|
||||
- extract weather accumulation and display foundations that do not consume the
|
||||
camera/root classification, preserving their current render-time positions;
|
||||
- consolidate requested-VSync/live-preview, monitor callbacks, and
|
||||
`OnFrameRendered` pacing through `DisplayFramePacingController`, while
|
||||
leaving its disposal in the existing shutdown transaction;
|
||||
- prove `WbMeshAdapter.Tick` remains before reveal evaluation and drawing;
|
||||
- prove dispatcher begin occurs once for the whole world/paperdoll frame.
|
||||
|
||||
Completed 2026-07-22. `RenderFrameResourceController` now owns the exact
|
||||
resource-begin → atmosphere/GL-clear → live-upload/reveal/particle transaction,
|
||||
with a single captured GPU slot shared by every phase. Focused sources own the
|
||||
login reveal cell, teleport render state, upload timing, and render-time weather
|
||||
clock. `DisplayFramePacingController` owns requested VSync, the event-refreshed
|
||||
monitor-rate cache, software pacing, and the post-render callback without adding
|
||||
a monitor query to the frame hot path. Borrowed frame owners are withdrawn before
|
||||
render teardown; portal fallback ownership transfers before the throwable sink
|
||||
bind; pacing releases its borrowed surface/profiler before their owners close.
|
||||
`GameWindow.cs` is 6,181 raw lines, 60.7% below the 15,723-line campaign
|
||||
baseline. Twenty-one focused tests, the Release build, and the complete suite
|
||||
pass (7,269 passed / 5 skipped). Retail, architecture, and adversarial
|
||||
corrected-diff reviews are clean.
|
||||
|
||||
### D — world frame builder
|
||||
|
||||
- move animated/equipped classification, camera/root selection, outdoor-node
|
||||
building gather, and prior-visible-cell light scratch out of the window;
|
||||
- after those facts exist, run focused audio/listener, live preview, sky PES,
|
||||
sun/fog/point-light, and lighting-UBO preparation; do not build an interim
|
||||
`GameWindow` mega-context;
|
||||
- keep PView-generated particle-owner sets in `RetailPViewPassExecutor` and the
|
||||
post-draw partition in the borrowed PView result/diagnostic snapshot;
|
||||
- pin viewer-cell versus player-cell roles, portal replacement, login wait,
|
||||
fallback/null-root, sealed/SeenOutside, and borrowed scratch reset;
|
||||
- preserve allocation-free warmed frames and hysteretic scratch retention.
|
||||
|
||||
Completed 2026-07-22. `WorldRenderFrameBuilder` now owns the ordered camera,
|
||||
visibility, live settings/listener, player-lighting versus collided-viewer root,
|
||||
sky/lighting/fog, animated-ID, and building-candidate preparation. Reusable
|
||||
animated/building/light-cell collections retain their explicit one-frame
|
||||
borrowed lifetime; PView drawable-cell feedback returns through the builder and
|
||||
is copied before the renderer reuses its own scratch. The diagnostic sky-PES
|
||||
experiment moved to `SkyPesFrameController`, and fog startup overrides now enter
|
||||
through typed `RuntimeOptions`. The frame graph is withdrawn before its borrowed
|
||||
equipped/effect/audio owners during shutdown. `GameWindow.cs` is 5,715 raw
|
||||
lines, 63.6% below the 15,723-line campaign baseline. Twenty-nine focused tests,
|
||||
the Release build, and the complete suite pass (7,281 passed / 5 skipped).
|
||||
Retail, architecture, and adversarial corrected-diff reviews are clean.
|
||||
|
||||
### E — typed PView pass executor
|
||||
|
||||
- replace `Action`/`Func` draw callbacks in `RetailPViewDrawContext`;
|
||||
- move landscape early/late, scissor/clip, depth seal/punch, particle passes,
|
||||
landscape alpha flush, and PView diagnostics to the executor;
|
||||
- add a recording executor test for the complete `DrawInside` call sequence;
|
||||
- verify no executor operation rebuilds portal visibility or retains a borrowed
|
||||
frame result across calls.
|
||||
|
||||
Completed 2026-07-22. `RetailPViewRenderer` is now the retail visibility and
|
||||
ordering owner only; `RetailPViewPassExecutor` owns the concrete clip upload,
|
||||
EnvCell/entity, landscape, portal depth, particle, alpha, and diagnostic pass
|
||||
implementations. The old callback bag and broad window closures are gone.
|
||||
`WorldRenderDiagnostics` now owns every PView print-on-change signature and
|
||||
probe sink, while a focused terrain-diagnostics controller preserves the
|
||||
shared five-second terrain/frame publication transaction across fallback and
|
||||
PView draws. Real `DrawInside` fixtures cover outdoor, interior-exit,
|
||||
no-outside, entity/static/dynamic particle, transparent-shell, look-in, and
|
||||
second-call scratch-reset behavior; structural tests pin concrete forwarding,
|
||||
no delegate/back-reference leakage, no borrowed frame/result retention, and
|
||||
early shutdown withdrawal. `GameWindow.cs` is 5,303 raw lines, 66.3% below the
|
||||
15,723-line campaign baseline. Release build and the complete suite pass
|
||||
(7,295 passed / 5 skipped). Retail, architecture, and adversarial
|
||||
corrected-diff reviews are clean.
|
||||
|
||||
### F — world scene owner
|
||||
|
||||
- extract alpha/selection/particle-visibility begin/end and the normal-world
|
||||
conditional;
|
||||
- extract sky/terrain/null-root fallback and unified PView branches;
|
||||
- extract post-world particles/weather and debug collision drawing;
|
||||
- return a small `WorldRenderFrameOutcome` for diagnostics/presentation;
|
||||
- delete the world draw body and helper closure from `GameWindow`.
|
||||
|
||||
Completed 2026-07-22. `WorldSceneRenderer` now owns the normal-world
|
||||
transaction, one shared-alpha frame, retail selection accumulation, PView versus
|
||||
null-root fallback branching, post-world particles/weather, visibility
|
||||
publication, and world diagnostics. `WorldScenePassExecutor`, focused runtime
|
||||
sources, and `WorldSceneDiagnosticsController` carry the concrete GL and data
|
||||
seams without retaining `GameWindow`, delegates, or borrowed PView products.
|
||||
Failed frames abort PView, pass routing, particle visibility, alpha, and
|
||||
selection in reverse ownership order; one shared `RenderFrameGlStateController`
|
||||
restores scissor/stencil/blend/clip/mask/depth/cull/program/buffer state before
|
||||
the next clear. Normal portal-depth exits now restore the same cull-off
|
||||
convention, and alpha cleanup preserves the original draw failure while
|
||||
attempting every source reset. `GameWindow.cs` is 4,765 raw lines, 69.7% below
|
||||
the 15,723-line campaign baseline. Release build and the complete suite pass
|
||||
(7,313 passed / 5 skipped). Retail, architecture, and adversarial
|
||||
corrected-diff reviews are clean.
|
||||
|
||||
### G — final orchestration cutover
|
||||
|
||||
- compose concrete resource, world, private-presentation, and diagnostic owners;
|
||||
- replace `OnRender` with one `RenderFrameOrchestrator.Render` handoff;
|
||||
- remove obsolete fields/helpers/callbacks; prove immediate frame owners retain
|
||||
no direct `GameWindow`/anonymous-delegate back-reference, and document rather
|
||||
than deny recursive paths through borrowed canonical UI/input owners;
|
||||
- preserve the existing shutdown dependency order while making the
|
||||
developer-tools backend's pre-existing bootstrap ownership explicit;
|
||||
- measure line/field/method counts. Ownership decides completion; the expected
|
||||
signal is roughly 5,000–5,500 lines before Slice 8 composition cleanup.
|
||||
|
||||
Completed 2026-07-22. `GameWindow.OnRender` now creates one value-only
|
||||
`RenderFrameInput` and hands the complete frame to `RenderFrameOrchestrator`.
|
||||
Focused preparation, world-scene, private-presentation, and diagnostic owners
|
||||
preserve the accepted resource → ImGui begin → weather → world → portal →
|
||||
paperdoll → retained UI → ImGui submit → screenshot → diagnostics → GPU close
|
||||
order. Recovery now closes an active ImGui frame through the owning Silk
|
||||
controller and restores the exact text-render GL state after success or
|
||||
failure. Screenshot dimensions travel in the frame input rather than through a
|
||||
window callback, and immediate frame owners have no direct `GameWindow` or
|
||||
anonymous-delegate back-reference. `GameWindow.cs` is 4,666 raw lines / 196
|
||||
fields / 70 methods: 11,057 lines (70.3%) below the 15,723-line campaign
|
||||
baseline. The Release build and complete suite pass (7,341 passed / 5 skipped),
|
||||
and retail-conformance, architecture, and adversarial corrected-diff reviews
|
||||
are clean.
|
||||
|
||||
### H — release and connected gates
|
||||
|
||||
After three clean corrected-diff reviews:
|
||||
|
||||
1. run focused App/Core render, PView, alpha, portal, screenshot, and resource
|
||||
tests;
|
||||
2. run `dotnet build AcDream.slnx -c Release`;
|
||||
3. run the complete Release suite;
|
||||
4. run `tools/run-connected-world-lifecycle-gate.ps1 -SkipBuild`;
|
||||
5. run `tools/run-connected-r6-soak.ps1 -SkipBuild`;
|
||||
6. require code-zero graceful exits, exact lifecycle checkpoints/PNGs, no
|
||||
fatal/invariant/reveal/pending-resource failures, and unchanged same-location
|
||||
resource tolerances;
|
||||
7. compare the six existing lifecycle/reconnect PNGs (`capped_login`,
|
||||
`aerlinthe_first`, `facility_hub`, `holtburg_after_dungeon`,
|
||||
`aerlinthe_revisit`, and `uncapped_reconnect`) with the Slice 6 baseline;
|
||||
8. audit architecture, divergence pointers, roadmap, milestones, issues,
|
||||
`AGENTS.md`, `CLAUDE.md`, and durable memory.
|
||||
|
||||
Completed 2026-07-22. The 315.6-second lifecycle/reconnect gate passed at
|
||||
`9d7df1bf` with six valid PNG checkpoints, code-zero graceful capped exit, and
|
||||
fresh-process uncapped reconnect. Side-by-side comparison with the Slice 6
|
||||
artifacts preserved world geometry, UI/paperdoll layering, private viewports,
|
||||
and depth/presentation; only live weather, particle timing, authoritative
|
||||
vitals, and small scripted camera-settling differences changed. The unchanged
|
||||
nine-stop gate then passed in 395.2 seconds with nine materializations, all
|
||||
Caul/Holtburg movement exercises, code-zero graceful shutdown, update p95 at
|
||||
or below 0.8 ms, and same-location Caul plateau deltas of +85.7 MiB working set
|
||||
and +59.3 MiB private memory. Two immediately preceding identical-binary runs
|
||||
crossed the coarse process-residency limit while every other deterministic
|
||||
lifecycle, timing, and shutdown check passed; #232 records the need to add
|
||||
canonical managed/GPU/cache owner snapshots to that gate instead of weakening
|
||||
its leak threshold. Release build and the complete suite remain green at 7,341
|
||||
passed / 5 skipped. Slice 7 is closed with no new retail divergence.
|
||||
|
||||
The accepted baseline at docs-only commit `9512404e` is behavior-equivalent to
|
||||
production cutover `e91f3102`: 7,182 passed / 5 skipped, 314.195-second
|
||||
lifecycle/reconnect, and 393.581-second soak. Exact counters are recorded in
|
||||
`memory/project_gamewindow_decomposition.md` and the Slice 6 closeout ledger.
|
||||
Building/doorway poses, an open paperdoll, transient portal-exit visuals, and
|
||||
#225 remain user visual gates; the stable post-materialization PNGs do not
|
||||
overclaim them.
|
||||
|
||||
## 6. Mandatory review gate
|
||||
|
||||
After every implementation checkpoint, three read-only reviews run:
|
||||
|
||||
1. **Retail conformance:** named anchors, PView/alpha/depth/private-viewport/UI
|
||||
order, and registered adaptation boundaries.
|
||||
2. **Architecture/integration:** focused owner scope, no replacement god object,
|
||||
no service locator/back-callback, render-thread mutation, borrowed lifetime,
|
||||
GL-state symmetry, and shutdown dependency direction.
|
||||
3. **Adversarial:** begin/end failures, missing resources, portal/login
|
||||
branches, stale borrowed scratch, callback reentrancy, frame spikes,
|
||||
mid-frame resource churn, and GUID/session replacement.
|
||||
|
||||
The primary agent fixes confirmed findings at their root, then requests
|
||||
corrected-diff re-review until clean. Each checkpoint then runs focused tests,
|
||||
Release build/full suite, divergence/doc audit, and lands as one commit.
|
||||
|
||||
## 7. Final acceptance matrix
|
||||
|
||||
### Automated
|
||||
|
||||
- exact orchestrator order for normal, login, portal, indoor, outdoor, and
|
||||
fallback frames, plus construction-time rejection of missing required phase
|
||||
owners;
|
||||
- exact GPU begin/end/aggregate failure behavior;
|
||||
- PView landscape/look-in/flush/clear/seal/shell/static/dynamics ordering;
|
||||
- screenshot after retained UI/ImGui, before title/resource/frame-profile
|
||||
diagnostics, and before GPU `EndFrame`;
|
||||
- dispatcher frame begin once across world and paperdoll scopes;
|
||||
- frame scratch reset/reuse after skipped, portal, normal, and failed frames;
|
||||
- no `GameWindow` reference or anonymous draw delegate bag in extracted owners;
|
||||
- existing PView replay, shared-alpha, particle visibility, portal, paperdoll,
|
||||
GPU-fence, screenshot, resource, and structural suites remain green;
|
||||
- Release build and all tests green;
|
||||
- connected lifecycle/reconnect and synchronized soak reports pass.
|
||||
|
||||
### In-client campaign gate
|
||||
|
||||
- outdoor dense scenes, terrain, sky, weather, particles, and selection match;
|
||||
- buildings, doorway look-in/out, depth seals/punches, and dynamics stay stable;
|
||||
- sealed dungeons keep correct visibility and lighting;
|
||||
- portal replacement and destination reveal preserve the accepted flow;
|
||||
- paperdoll, retained gameplay UI, cursor, and ImGui layering are unchanged;
|
||||
- no resource growth, GPU reset, render exception, or shutdown regression.
|
||||
|
||||
## 8. Fixed assumptions
|
||||
|
||||
- all render/GL mutation remains on the existing render thread;
|
||||
- `DatCollection` remains the only DAT reader;
|
||||
- `LiveEntityRuntime`, `GpuWorldState`, cell visibility, session, and resource
|
||||
owners remain canonical and are not copied;
|
||||
- existing render resources are borrowed through Slice 7 and disposed by the
|
||||
current shutdown transaction;
|
||||
- frame-local results and scratch collections are borrowed until the next
|
||||
frame/call unless explicitly copied;
|
||||
- no quality/range/performance tradeoff is introduced to make the extraction
|
||||
easier;
|
||||
- missing optional diagnostic/UI owners skip only their own presentation and
|
||||
never suppress world drawing or GPU frame closure.
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
# GameWindow Slice 8 — Checkpoint G Runtime Settings Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Replace `GameWindow`'s duplicated pre-window and `OnLoad` settings paths with
|
||||
one two-phase `RuntimeSettingsController`. The controller owns persistence and
|
||||
live settings state; render, window, audio, UI, and streaming objects remain
|
||||
borrowed targets. This is a structural cutover, not a redesign of the settings
|
||||
format or retail option behavior.
|
||||
|
||||
Two carried behaviors are now explicit rather than accidental. Saved FOV is
|
||||
applied during startup even when developer tools are disabled; previously it
|
||||
was incorrectly gated on construction of `SettingsVM`. The named-retail
|
||||
`/framerate` live toggle and notice remain intact, while acdream's already
|
||||
shipped cross-launch ShowFps persistence is documented as AP-121 rather than
|
||||
being attributed to the retail command body.
|
||||
|
||||
## Frozen behavior and ordering
|
||||
|
||||
1. Construct the controller before `Window.Create` and load Display, Audio,
|
||||
Gameplay, Chat, and the `default` Character bag exactly once.
|
||||
2. Resolve the preset plus environment overrides once for the immutable startup
|
||||
snapshot. `WindowOptions.VSync` comes from the pacing policy initialized with
|
||||
that snapshot; `WindowOptions.Samples` comes from the same resolved quality.
|
||||
3. After window/GL input, camera, DAT effects, and optional audio exist, but
|
||||
before devtools or world/render factories, apply startup settings in this
|
||||
order: monitor-aware pacing, resolution/fullscreen, field of view, then audio.
|
||||
4. Downstream terrain, dispatcher, render-range, and streaming factories read
|
||||
the same startup quality snapshot. The legacy startup stream-radius override
|
||||
remains at its current composition boundary.
|
||||
5. Bind one complete `RuntimeSettingsTargets` only after window, UI (when
|
||||
enabled), terrain atlas, dispatcher, render-range, and streaming exist.
|
||||
Binding performs no display, quality, UI-lock, or audio replay.
|
||||
6. Settings-panel Save preserves its callback order: keybindings, Display,
|
||||
Audio, Gameplay, Chat, Character. Each section retains its existing
|
||||
persistence/error boundary.
|
||||
7. Display Save persists first, applies resolution/fullscreen, publishes the
|
||||
new display snapshot, then reapplies quality. A changed MSAA sample count is
|
||||
logged as restart-required and is not applied to the live GL context.
|
||||
8. Runtime quality reapply preserves the existing order: alpha-to-coverage,
|
||||
terrain anisotropy, render-range publication, streaming reconciliation,
|
||||
completion budget.
|
||||
9. `/framerate`, UI lock, loot permits, and combat preferences mutate the
|
||||
controller's canonical state, synchronize the optional Settings draft using
|
||||
the existing draft-preservation rules, and retain their existing save-error
|
||||
behavior.
|
||||
10. Enter-world changes the active toon key, loads that character bag even when
|
||||
devtools are disabled, and publishes it to the optional Settings view model.
|
||||
Session reset restores the startup/default character context before the
|
||||
later identity-reset operation restores the key to `default`.
|
||||
11. Per-frame display/audio preview reads the optional Settings draft through a
|
||||
typed source. Particle range and FPS visibility read the same source so an
|
||||
unsaved draft still previews exactly as it does today.
|
||||
12. Shutdown first withdraws the Settings view-model and runtime-target loans;
|
||||
the controller never disposes the window, renderer, UI, streaming, audio, or
|
||||
input objects it borrows.
|
||||
|
||||
## Types and ownership
|
||||
|
||||
- `IRuntimeSettingsStorage` is the test seam for the five settings bags.
|
||||
`JsonRuntimeSettingsStorage` is the production owner of the one concrete
|
||||
`SettingsStore` and exposes that store only for retained-window layout
|
||||
persistence.
|
||||
- `RuntimeSettingsSnapshot` is the immutable startup value.
|
||||
- `IRuntimeSettingsStartupTarget` receives the one startup Display/Audio apply.
|
||||
- `IRuntimeSettingsTargets` receives future display-window, quality, and UI-lock
|
||||
changes. `BindRuntimeTargets` stores it without invoking it.
|
||||
- `IRuntimeSettingsPreviewSource` exposes current Display/Audio preview values
|
||||
and whether a live draft exists.
|
||||
- `RuntimeSettingsController` owns persistence, current values, active toon,
|
||||
resolved quality, and the optional `SettingsVM` binding. It also implements
|
||||
the combat settings source; no second gameplay-state mirror remains.
|
||||
- `SilkRuntimeDisplayWindowTarget`, `RuntimeSettingsStartupTargets`, and
|
||||
`RuntimeSettingsTargets` are App-layer adapters over borrowed runtime objects.
|
||||
|
||||
## Automated gates
|
||||
|
||||
- Construction loads every bag once and populates one immutable snapshot.
|
||||
- Startup apply is ordered, exactly once, and independent of GL in controller
|
||||
tests.
|
||||
- Runtime target binding produces zero calls; unbinding prevents future target
|
||||
calls while state and persistence still advance.
|
||||
- Display Save ordering, quality transition, and restart-required MSAA logging.
|
||||
- Quality target receives one complete resolved value per reapply.
|
||||
- Draft preview/cancel, external FPS mutation, UI lock, loot permits, and combat
|
||||
option synchronization.
|
||||
- Toon activate/load/reset/save behavior with and without a Settings view model.
|
||||
- Save failures preserve each established state/target boundary.
|
||||
- Source-boundary test proves `GameWindow` no longer constructs or loads a
|
||||
`SettingsStore`, owns persisted settings fields, or contains quality/display
|
||||
feature bodies.
|
||||
- Focused App tests, full App tests, Release solution build, and full Release
|
||||
suite.
|
||||
|
||||
## Review and commit gate
|
||||
|
||||
After the focused tests pass, the existing retail-conformance,
|
||||
architecture/integration, and adversarial reviewers inspect the complete diff
|
||||
in parallel. Confirmed findings are corrected and the same reviewers repeat
|
||||
until clean. Documentation, roadmap, issues, memory, `CLAUDE.md`, and
|
||||
`AGENTS.md` are reconciled in the same bisectable Checkpoint G commit.
|
||||
|
||||
## Implementation result
|
||||
|
||||
- `RuntimeSettingsController` is constructed before `Window.Create`; all five
|
||||
bags load once and `WindowOptions` consumes its immutable startup snapshot.
|
||||
- Startup substeps commit independently, so an audio failure retries audio
|
||||
without replaying already successful pacing/display/FOV work.
|
||||
- Late target binding is inert and reversible; expected-instance SettingsVM
|
||||
withdrawal cannot detach a replacement binding.
|
||||
- The concrete quality adapter preserves all five application steps and the
|
||||
unchanged MSAA restart boundary.
|
||||
- Combat changes merge their three fields into a live Gameplay draft rather
|
||||
than overwriting unrelated unsaved fields; successful external changes also
|
||||
advance the SettingsVM persisted baseline so Cancel cannot resurrect stale
|
||||
command state.
|
||||
- UI-lock target, persistence, and baseline publication form one explicit
|
||||
convergence state. A transient failure leaves the same requested value
|
||||
retryable instead of letting the equality fast-path strand unfinished work.
|
||||
- Enter/reset/toon changes load and publish Character state independently of
|
||||
developer-tool availability.
|
||||
- `GameWindow` no longer constructs or loads `SettingsStore`, owns persisted
|
||||
settings mirrors, or contains display/quality/settings feature bodies.
|
||||
- Candidate metrics: 3,663 raw lines / 162 fields / 37 methods, down 394 lines /
|
||||
36 fields / 17 methods from Checkpoint F and 12,060 lines (76.7%) from the
|
||||
campaign baseline.
|
||||
- Thirty-five focused App settings/boundary tests, all 43 `SettingsVMTests`, the
|
||||
UI Abstractions Release suite (534 pass), and the App Release suite (3,183
|
||||
pass / 3 intentional skips) pass. All three independent corrected-diff
|
||||
reviews are clean. The production App Release build has zero warnings/errors;
|
||||
the solution Release build retains the 17 existing #228 test-project warnings;
|
||||
and the complete Release suite passes 7,553 tests / 5 intentional skips.
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
# GameWindow Slice 8 — Checkpoint H Resource Ownership Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Replace the remaining implicit, constructor-local, and nullable-field resource
|
||||
ownership in `GameWindow` with explicit single-owner transactions. This is a
|
||||
behavior-preserving lifetime cutover: draw/update order, portal presentation,
|
||||
retained UI behavior, settings behavior, and the frozen shutdown order do not
|
||||
change.
|
||||
|
||||
Checkpoint H does not move the complete shutdown manifest; that is Checkpoint
|
||||
J. It supplies the typed roots and retry semantics that J will consume.
|
||||
|
||||
## Frozen invariants
|
||||
|
||||
1. Every GL/kernel-owning object is published to exactly one owner immediately
|
||||
after successful construction.
|
||||
2. Renderers borrow the terrain atlas and dedicated sky shader. They never
|
||||
dispose those resources. The renderer borrower retires before the sole
|
||||
resource owner releases the borrowed atlas/shader.
|
||||
3. A leaf factory that throws before returning must release every GL name it
|
||||
created. Outer phase/lifetime rollback cannot recover a name that was never
|
||||
published.
|
||||
4. Bindless residency is a resource prefix: partial handle acquisition rolls
|
||||
back, successful non-residency is not replayed after a later failure, and
|
||||
textures are not deleted while a resident handle remains.
|
||||
5. Retained UI input is quiesced before the live-session barrier and physically
|
||||
detached afterward, independently of runtime/host disposal.
|
||||
6. Exactly one retained-UI ownership chain exists. Before runtime construction
|
||||
the lease owns the `UiHost`; after a runtime object is published the lease
|
||||
retains that exact partial/full runtime and delegates final disposal to it.
|
||||
Constructor or initialization failure never leaves a host in a local only.
|
||||
7. Update and render roots publish atomically as one frame graph. Silk callbacks
|
||||
resolve only through the slot; an empty/withdrawn slot is safely inert.
|
||||
8. Portal tunnel ownership starts in one fallback slot. Transfer clears the
|
||||
fallback only after the complete local-teleport controller factory returns;
|
||||
any earlier failure leaves the same tunnel reachable by shutdown.
|
||||
9. Successful disposal is never replayed. Failed disposal remains retryable.
|
||||
Terminal abandonment is explicit and retains references for native/process
|
||||
fallback rather than silently losing ownership.
|
||||
|
||||
## Typed owners
|
||||
|
||||
### `OwnedResourceSlot<T>` and `GameRenderResourceLifetime`
|
||||
|
||||
- `OwnedResourceSlot<T>` acquires through a factory, rejects duplicate
|
||||
publication, exposes a typed borrower, and clears ownership only after
|
||||
`Dispose` succeeds.
|
||||
- `GameRenderResourceLifetime` contains separate slots for `TerrainAtlas` and
|
||||
the dedicated sky `Shader`.
|
||||
- `GameWindow` acquires both through this owner. Shutdown releases the sky
|
||||
renderer and terrain renderer first, then the two resource slots, then GL.
|
||||
|
||||
### `RetailUiRuntimeLease`
|
||||
|
||||
- The lease exists before `OnLoad` and acquires the `UiHost` through a factory.
|
||||
- Runtime construction and initialization are separate. The runtime is stored
|
||||
in the lease before initialization begins.
|
||||
- Initialization failure attempts immediate cleanup. Successful cleanup
|
||||
rethrows the original initialization error; cleanup failure reports both and
|
||||
leaves the exact runtime in the lease for lifetime retry.
|
||||
- The lease exposes idempotent `QuiesceInput` and retryable `DeactivateInput`
|
||||
operations against the retained host.
|
||||
- If no runtime was constructed, lease disposal directly disposes the host. If
|
||||
a runtime exists, runtime disposal remains the one path that disposes the
|
||||
host. There are never two host disposers.
|
||||
- Explicit terminal abandonment is allowed only after a failed disposal pass;
|
||||
it makes later disposal inert while retaining the unresolved references for
|
||||
the native/process fallback handled by Checkpoint J.
|
||||
|
||||
### `GameFrameGraphSlot`
|
||||
|
||||
- `IGameUpdateFrameRoot` and `IGameRenderFrameRoot` are the narrow frame seams.
|
||||
- `UpdateFrameOrchestrator` and `RenderFrameOrchestrator` implement those seams.
|
||||
- `Publish(update, render)` is atomic and one-shot until withdrawal.
|
||||
- `Tick`/`Render` are inert before publication and after withdrawal.
|
||||
- Shutdown withdraws the pair only after live-session convergence and before
|
||||
any frame borrower retires.
|
||||
|
||||
### `TransferableResourceSlot<T>`
|
||||
|
||||
- `Acquire` owns one fallback resource.
|
||||
- `Transfer(factory)` retains fallback ownership while the complete destination
|
||||
factory runs and clears it only after that factory returns successfully.
|
||||
- Failed acquisition, resource preparation, destination construction, binding,
|
||||
and normal shutdown prefixes are covered without double disposal.
|
||||
|
||||
## Exception-safe GL leaves
|
||||
|
||||
### Shader program construction
|
||||
|
||||
`ShaderProgramConstruction` uses a narrow `IShaderProgramBuildApi`. It tracks
|
||||
the vertex shader, fragment shader, and linked program as soon as each name is
|
||||
returned. Compile, attach, link, detach, or cleanup failure attempts every
|
||||
remaining rollback operation. A clean rollback preserves the original failure;
|
||||
rollback failures are aggregated with it. `Shader.Dispose` becomes idempotent
|
||||
and retryable.
|
||||
|
||||
### Terrain atlas construction and residency
|
||||
|
||||
`GlTextureConstructionTransaction` tracks every texture name allocated by
|
||||
terrain, alpha, and fallback paths. `TerrainAtlas.Build`, `BuildAlphaAtlas`, and
|
||||
`BuildFallback` allocate only through that transaction. The transaction commits
|
||||
only after the complete `TerrainAtlas` object exists; failure rolls back all
|
||||
names in reverse acquisition order and reports cleanup failures with the
|
||||
original error.
|
||||
|
||||
`BindlessTexturePair` owns the terrain/alpha residency pair. Partial acquisition
|
||||
releases the first handle. Release attempts both handles, remembers successful
|
||||
substeps, and is retryable. `TerrainAtlas.Dispose` releases residency first,
|
||||
then independently deletes both texture names through the existing retryable
|
||||
shutdown transaction.
|
||||
|
||||
## Production integration order
|
||||
|
||||
1. Construct render-resource lifetime, UI lease, frame slot, and portal
|
||||
fallback slot before `Window.Create`/`OnLoad` acquisitions.
|
||||
2. Acquire the atlas through `GameRenderResourceLifetime`; construct
|
||||
`TerrainModernRenderer` as a borrower.
|
||||
3. Acquire `UiHost` through `RetailUiRuntimeLease`, wire input/assets, construct
|
||||
and publish `RetailUiRuntime` before initialization, then expose host/runtime
|
||||
only as borrower aliases.
|
||||
4. Acquire/prepare portal presentation in the fallback slot.
|
||||
5. Acquire the dedicated sky shader through the render-resource lifetime, then
|
||||
construct `SkyRenderer` as a borrower.
|
||||
6. Transfer the portal fallback only through the complete
|
||||
`LocalPlayerTeleportController` factory.
|
||||
7. Construct update/render roots as locals and publish them atomically through
|
||||
`GameFrameGraphSlot`.
|
||||
8. Update/render callbacks resolve only through the slot.
|
||||
9. Shutdown quiesces/deactivates UI through the lease, withdraws frame roots,
|
||||
disposes the UI lease and renderer borrowers, releases atlas/sky roots, then
|
||||
reaches GL.
|
||||
|
||||
## Automated gates
|
||||
|
||||
- owned-resource acquire/borrow/release, duplicate acquire, transient failure,
|
||||
retry, and no replay;
|
||||
- UI host acquisition, runtime constructor failure, Initialize failure with
|
||||
successful cleanup, transient cleanup then lifetime retry, persistent cleanup
|
||||
then explicit abandonment, separate input quiescence/deactivation, and exact
|
||||
host disposal count;
|
||||
- frame pair atomic publication, duplicate publication, update/render routing,
|
||||
withdrawal, pre-publication/post-withdrawal silence, and republish policy;
|
||||
- portal fallback acquisition/preparation/transfer failures plus successful
|
||||
transfer and every shutdown prefix, with exact one-resource disposal;
|
||||
- vertex/fragment compile failure, program creation/link failure, and cleanup
|
||||
failure after every created shader/program name;
|
||||
- terrain/alpha/fallback texture allocation and upload failure after every
|
||||
created name, reverse rollback, cleanup aggregation, and committed-name
|
||||
non-deletion;
|
||||
- bindless first/second acquisition failure, release partial failure/retry, and
|
||||
no deletion before residency release;
|
||||
- source boundary proves direct frame-root fields, constructor-local atlas/sky
|
||||
ownership, direct UI host/runtime ownership, and nullable portal fallback are
|
||||
removed from `GameWindow`;
|
||||
- focused App tests, production App Release build, solution Release build, and
|
||||
complete Release suite.
|
||||
|
||||
## Review and commit gate
|
||||
|
||||
The existing architecture/integration, retail-conformance, and adversarial
|
||||
reviewers inspect the complete tracked and untracked diff independently. Every
|
||||
confirmed finding is corrected and all three repeat until clean. Architecture,
|
||||
roadmap, milestones, issues, memory, `CLAUDE.md`, and `AGENTS.md` are reconciled
|
||||
in the same bisectable Checkpoint H commit.
|
||||
|
||||
## Implementation result — 2026-07-22
|
||||
|
||||
Checkpoint H landed the planned ownership graph without changing the accepted
|
||||
update/render order or gameplay behavior:
|
||||
|
||||
- `GameRenderResourceLifetime` owns the terrain atlas and dedicated sky shader;
|
||||
renderers borrow them and retire first.
|
||||
- `RetailUiRuntimeLease` owns Host/runtime construction, initialization, input
|
||||
cutoff, retryable disposal, and explicit terminal abandonment as one chain.
|
||||
- `GameFrameGraphSlot` publishes update/render roots atomically and makes empty
|
||||
or withdrawn callbacks inert.
|
||||
- `TransferableResourceSlot.AcquirePrepared` publishes the portal presentation
|
||||
before mesh preparation, retries the same partial resource, and refuses
|
||||
transfer until preparation has committed.
|
||||
- GL name creation records the returned name before post-command validation.
|
||||
Shader/program, texture, text-renderer, buffer/VAO, bindless-residency, and
|
||||
texture-binding operations advance ownership only after an always-on checked
|
||||
GL commit boundary.
|
||||
- Failed construction cleanup is retained by `GlConstructionCleanupLedger`;
|
||||
exact pending names and binding-restore obligations remain retryable, while
|
||||
successful cleanup stages never replay.
|
||||
|
||||
Four corrected-diff cycles closed issues found by the architecture, retail,
|
||||
and adversarial reviewers: partial bindless mutation, lost GL rollback names,
|
||||
constructor-local text resources, reentrant UI ownership, portal preparation,
|
||||
unchecked production GL calls, leaked texture binding, and the bindless post-
|
||||
commit verification gap. All three final reviews are clean.
|
||||
|
||||
Automated acceptance:
|
||||
|
||||
- focused Checkpoint H ownership/lifetime gate: 61 passed;
|
||||
- App Release suite: 3,236 passed / 3 intentional skips;
|
||||
- production App Release build: zero warnings and zero errors;
|
||||
- complete solution Release suite: 7,606 passed / 5 intentional skips; solution
|
||||
build has zero errors and the 17 existing test-project warnings tracked by
|
||||
#228;
|
||||
- `GameWindow.cs`: 3,689 raw lines / 162 fields / 37 methods, versus 15,723 /
|
||||
278 / 205 at the campaign baseline.
|
||||
|
||||
No AC-specific algorithm or intended retail behavior changed, so Checkpoint H
|
||||
adds no retail-divergence row. Checkpoint I is the next active unit.
|
||||
|
|
@ -1,374 +0,0 @@
|
|||
# GameWindow Slice 8 Checkpoint I — ordered production composition
|
||||
|
||||
**Status:** Complete 2026-07-22.
|
||||
**Parent:**
|
||||
[`2026-07-22-gamewindow-slice-8-composition-lifecycle.md`](2026-07-22-gamewindow-slice-8-composition-lifecycle.md),
|
||||
Checkpoint I.
|
||||
**Integrated baseline:** `c791f138`; the reviewed Checkpoint-H tree plus the
|
||||
complete-world-reveal history is merged. `GameWindow.cs` is 3,689 raw lines,
|
||||
162 fields, and 37 methods. The Release build succeeds with the 17 warnings
|
||||
tracked by #228 and all 7,606 tests pass / 5 intentionally skip.
|
||||
**Behavior rule:** startup ownership and failure behavior may become explicit;
|
||||
accepted input priority, settings, session/reset, update/render order, world
|
||||
presentation, and retail gameplay behavior do not change.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Replace the 2,149-line `GameWindow.OnLoad` body with one fixed production
|
||||
composition pipeline. `GameWindow` remains the native host and write-only
|
||||
publication shell. It does not become a service locator, and no extracted
|
||||
phase stores or calls back into `GameWindow`.
|
||||
|
||||
The final startup shape is:
|
||||
|
||||
```text
|
||||
GameWindow.OnLoad
|
||||
-> GameWindowPlatformAcquisition.Acquire
|
||||
-> GameWindowCompositionPipeline.Run
|
||||
1 Host input / camera
|
||||
2 Content / effects / audio
|
||||
3 Settings / optional devtools
|
||||
4 World / render resources
|
||||
5 Interaction / retained UI
|
||||
6 Live presentation / landblock publication
|
||||
7 Streaming / session / local player / teleport
|
||||
8 Atomic update + render roots
|
||||
9 LiveSessionHost.Start, and nothing after it
|
||||
```
|
||||
|
||||
Every phase consumes only the exact immutable prior results it needs and
|
||||
returns a cohesive typed result. Results are borrowers. Newly acquired owners
|
||||
move immediately into the existing lifetime slots, where shutdown remains the
|
||||
sole release authority.
|
||||
|
||||
## 2. Fixed architecture
|
||||
|
||||
### 2.1 Platform prelude
|
||||
|
||||
`GameWindowPlatformAcquisition` is the exact production method used by
|
||||
`OnLoad`. Its order is fixed:
|
||||
|
||||
1. create GL;
|
||||
2. publish GL to the lifetime shell;
|
||||
3. create the input context;
|
||||
4. publish input;
|
||||
5. return borrowed platform handles.
|
||||
|
||||
GL publication must happen before the input factory runs. An input-factory
|
||||
failure leaves GL lifetime-owned for normal shutdown; local rollback never
|
||||
double-disposes it. Viewport, diagnostics, camera, and GPU-frame resources are
|
||||
not part of this narrow prelude.
|
||||
|
||||
### 2.2 Pipeline contract
|
||||
|
||||
Use nine explicit phase interfaces and immutable result records. The pipeline
|
||||
holds no cumulative context and stores no final runtime graph. Locals pass only
|
||||
the required sub-results forward.
|
||||
|
||||
Representative result grouping:
|
||||
|
||||
```csharp
|
||||
HostInputCameraResult(InputGraph, CameraGraph, HostRenderGraph)
|
||||
ContentEffectsAudioResult(ContentCatalogs, EffectsGraph, AudioGraph?)
|
||||
SettingsDevToolsResult(ResolvedQuality, DevToolsGraph?)
|
||||
WorldRenderResult(TerrainBuildContext, RenderFoundation)
|
||||
InteractionUiResult(InteractionGraph, RetainedUiGraph?)
|
||||
LivePresentationResult(LiveEntityGraph, WorldPresentationGraph)
|
||||
SessionPlayerResult(StreamingGraph, SessionGraph, PlayerGraph, FrameParts)
|
||||
FrameRootResult(LiveSessionHost)
|
||||
SessionStartResult
|
||||
```
|
||||
|
||||
Nested records are cohesive typed graphs, not dictionaries, `object` bags,
|
||||
general service providers, or wrappers around the window.
|
||||
|
||||
### 2.3 Publication seam
|
||||
|
||||
`GameWindow` implements narrow write-only phase publishers. Publication methods
|
||||
only validate single assignment and assign the existing shell fields. They do
|
||||
not construct resources, invoke gameplay behavior, or provide runtime lookup.
|
||||
The pipeline and phases release the publisher reference when `Run` returns.
|
||||
|
||||
Platform publication is narrower still so GL can publish before input
|
||||
creation. Checkpoint J will move the exact shutdown manifest into the lifetime
|
||||
owner; Checkpoint I does not duplicate or reorder that manifest.
|
||||
|
||||
### 2.4 Acquisition and rollback
|
||||
|
||||
Every concrete phase uses `CompositionAcquisitionScope` and named typed leases:
|
||||
|
||||
- unpublished acquisitions roll back in strict reverse dependency order;
|
||||
- publication or transfer removes the resource from local rollback
|
||||
immediately;
|
||||
- lifetime-owned resources are never released locally;
|
||||
- cleanup continues after an independent cleanup failure;
|
||||
- the original construction failure and cleanup failures are retained;
|
||||
- retry invokes only unfinished cleanup actions;
|
||||
- successful cleanup never replays;
|
||||
- duplicate publication and duplicate transfer are rejected.
|
||||
|
||||
This is not a generic shutdown bag. Long-lived owners remain in their focused
|
||||
H-era slots and are retired in dependency order by the existing shutdown
|
||||
transaction.
|
||||
|
||||
## 3. Frozen phase order and dependencies
|
||||
|
||||
### Phase 1 — host input and camera
|
||||
|
||||
Acquire viewport target, GPU-frame owner, world render diagnostics, keyboard
|
||||
and mouse sources, input dispatcher, movement/camera bindings, mouse-look
|
||||
cursor, orbit/fly camera, framebuffer camera target, and raw pointer owner.
|
||||
|
||||
Preserve:
|
||||
|
||||
- keyboard/mouse source attach before dispatcher attach;
|
||||
- dispatcher attach before raw `MouseMove` attach;
|
||||
- raw pointer attach before retained-UI device wiring;
|
||||
- physical framebuffer size remains distinct from logical window size.
|
||||
|
||||
### Phase 2 — content, effects, and audio
|
||||
|
||||
Open the sole `DatCollection`; load magic, animation, collision, emitter,
|
||||
particle, hook-frame, physics-script, lighting, and translucency owners. Audio
|
||||
is optional, but failure can be swallowed only after its entire unpublished
|
||||
prefix has converged cleanup. Hook-router registrations are reversible leases.
|
||||
|
||||
The physics-script advancement gate resolves the later entity-effect authority
|
||||
through one typed deferred source; it must not capture the window or a null
|
||||
snapshot.
|
||||
|
||||
### Phase 3 — settings and optional devtools
|
||||
|
||||
Apply the immutable startup settings snapshot after camera/audio exist and
|
||||
before world/render/UI factories consume it. Compose optional devtools with a
|
||||
transactional input/bootstrap/backend/framebuffer-target lease. Disabled
|
||||
devtools acquire nothing. A devtools construction failure disables the feature
|
||||
only after cleanup converges; cleanup failure fails startup rather than
|
||||
stranding callbacks or native state.
|
||||
|
||||
Devtools live facts use focused sources for render diagnostics, canonical
|
||||
world state, player mode, and player controller. No callback captures the host.
|
||||
|
||||
### Phase 4 — world and render resources
|
||||
|
||||
Load Region/environment/height/blend data, validate bindless support, then
|
||||
construct terrain atlas, terrain shader, scene lighting, debug/text resources,
|
||||
terrain renderer, WB mesh/render infrastructure, texture/sampler caches, and
|
||||
the render foundation.
|
||||
|
||||
Required order includes bindless before atlas/terrain, and the final immutable
|
||||
height/blend/surface build inputs are captured directly for worker use.
|
||||
Constructor prefixes for debug lines, bitmap font, OpenGL graphics device,
|
||||
particle batcher, global mesh buffers, portal depth, particles, samplers, and
|
||||
terrain renderer must have a strong transactional guarantee.
|
||||
|
||||
### Phase 5 — interaction and retained UI
|
||||
|
||||
Compose combat target/attack, external-container and item interaction,
|
||||
character/magic controllers, radar/chat/cursor assets, `UiHost`, and the
|
||||
retained runtime lease. Retained bindings to later session, reveal, player,
|
||||
diagnostic, and live-entity authorities use focused deferred sources. Host,
|
||||
partial runtime, mounted runtime, and input bindings retain exact ownership.
|
||||
|
||||
### Phase 6 — live presentation and landblock publication
|
||||
|
||||
Compose WB/spawn adapters, canonical `GpuWorldState`, `LiveEntityRuntime`, live
|
||||
motion/projectile/animation/effect/presentation owners, selection/paperdoll,
|
||||
EnvCell/portal/sky/particle renderers, landblock publishers, and render
|
||||
diagnostics. Bind the canonical live-runtime slot immediately after runtime
|
||||
creation. Anonymous retained event handlers become named reversible bindings.
|
||||
|
||||
Portal tunnel ownership remains acquire -> prepare -> transfer. The fallback
|
||||
slot and final controller never co-own the same resource.
|
||||
|
||||
### Phase 7 — streaming, session, local player, and teleport
|
||||
|
||||
Compose and publish the streamer before `Start`, then streaming/recenter/reveal,
|
||||
session/hydration/network/liveness, player-mode/local-animation/shadow,
|
||||
gameplay-input, commands, action-router, live-session host, and teleport
|
||||
owners. All UI and devtool late sources bind here.
|
||||
|
||||
The current premature frame-root publication defect is removed: command
|
||||
bindings and gameplay-action attachment complete in Phase 7, before Phase 8.
|
||||
|
||||
### Phase 8 — atomic frame roots
|
||||
|
||||
Construct all update/render leaves locally, then both roots, and publish the
|
||||
pair once through `GameFrameGraphSlot` as the final phase action. A failure
|
||||
before publication leaves the slot empty. A failure after publication leaves
|
||||
the lifetime owner responsible for exactly one paired graph. No callback can
|
||||
observe a half-pair.
|
||||
|
||||
### Phase 9 — terminal session start
|
||||
|
||||
Invoke only `LiveSessionHost.Start`. It is the absolute last startup operation.
|
||||
There is no publication, callback attachment, command binding, or diagnostics
|
||||
work afterward.
|
||||
|
||||
## 4. Focused live sources and reversible edges
|
||||
|
||||
Replace current host captures with focused authorities:
|
||||
|
||||
- `ILocalPlayerIdentitySource` for the server GUID;
|
||||
- `ILocalPlayerModeSource` for player/fly state;
|
||||
- a player-controller slot for current movement facts;
|
||||
- `RenderRangeState` for near radius;
|
||||
- `LiveWorldOriginState` for center coordinates;
|
||||
- external-container state for current container;
|
||||
- focused deferred session command/host authority;
|
||||
- a canonical world-state source that cannot freeze the pre-runtime
|
||||
placeholder;
|
||||
- typed item/use/pick/selection/debug-fact operations;
|
||||
- immutable phase-4 terrain worker inputs.
|
||||
|
||||
Add expected-owner unbind tokens for one-shot deferred composition slots that
|
||||
can be bound before a later fallible edge. A failed phase must be disposable
|
||||
and retryable without stale bindings or “already bound” failures.
|
||||
|
||||
Named reversible bindings replace anonymous subscriptions for projection
|
||||
visibility, projection pose, entity-ready, appearance-applied, hook sinks, and
|
||||
streamer start/stop.
|
||||
|
||||
## 5. Implementation checkpoints
|
||||
|
||||
1. **I.1 — contracts and executable oracle:** pipeline, platform prelude,
|
||||
acquisition scope, fake phases, prefix/failure tests.
|
||||
2. **I.2 — platform + Phase 1:** exact GL/input publication and host
|
||||
input/camera cutover.
|
||||
3. **I.3 — Phase 2:** content/effects/audio, reversible hooks, transactional
|
||||
OpenAL construction.
|
||||
4. **I.4 — Phase 3:** settings/devtools, complete optional rollback.
|
||||
5. **I.5 — Phase 4:** world/render foundation and strong constructor
|
||||
guarantees.
|
||||
6. **I.6 — Phases 5–6:** retained UI, live presentation, landblock publishers,
|
||||
typed late sources and event leases.
|
||||
7. **I.7 — Phase 7:** streaming/session/player/teleport and all pre-frame
|
||||
bindings.
|
||||
8. **I.8 — Phases 8–9:** atomic roots, terminal start, delete old `OnLoad`
|
||||
body, structural gates.
|
||||
9. **I.9 — corrected-diff review, full gates, docs/memory, one bisectable
|
||||
Checkpoint-I closeout commit.**
|
||||
|
||||
Small implementation commits may be used while the checkpoint is active, but
|
||||
each must build and preserve production startup. The final I commit closes the
|
||||
ledger only after the complete pipeline is live.
|
||||
|
||||
### Current implementation progress — 2026-07-22
|
||||
|
||||
- I.1–I.4 are committed: the executable pipeline/rollback oracle, platform
|
||||
acquisition, host input/camera, content/effects/audio, and settings/devtools
|
||||
phases are the production startup path.
|
||||
- I.5 is complete. `WorldRenderCompositionPhase` now owns the ordered Region,
|
||||
environment, mandatory bindless, terrain-atlas, shader, lighting, debug/HUD,
|
||||
terrain, WB, texture, and sampler foundation. `GameWindow` is a write-only
|
||||
publisher for this phase and no longer contains the Phase-4 construction
|
||||
body.
|
||||
- Every fallible GL constructor in the Phase-4 prefix now publishes each name
|
||||
into retryable construction ownership before later GL work. Failure testing
|
||||
covers every composition boundary, every disposable publication, partial HUD
|
||||
construction, reverse cleanup, retry, and no replay.
|
||||
- The I.5 tree is 3,522 raw `GameWindow.cs` lines. The App gate passes 3,373
|
||||
tests / 3 intentional skips; the complete Release suite passes 7,745 tests /
|
||||
5 intentional skips. Production App builds with zero warnings/errors; the
|
||||
solution retains only the 17 warnings tracked by #228.
|
||||
- I.6 (retained UI plus live-presentation composition) is complete. Its detailed
|
||||
two-commit ownership and failure-test plan is
|
||||
[`2026-07-22-gamewindow-slice-8-checkpoint-i6-ui-live-presentation.md`](2026-07-22-gamewindow-slice-8-checkpoint-i6-ui-live-presentation.md).
|
||||
Phase 5 now composes interaction and retained UI through typed dependencies,
|
||||
exact-owner late bindings, and transactional rollback. Phase 6 now composes
|
||||
the canonical live runtime/world, presentation/effect controllers,
|
||||
selection/radar, landblock publishers, and portal/sky/particle resources
|
||||
under the same ownership contract. `GameWindow.cs` is 2,840 raw lines; the
|
||||
App gate passes 3,407 tests / 3 intentional skips and the complete Release
|
||||
suite passes 7,779 tests / 5 intentional skips. I.7 session, streaming, and
|
||||
hydration composition is complete; its ownership, rollback, and gate
|
||||
contract is
|
||||
[`2026-07-22-gamewindow-slice-8-checkpoint-i7-session-player.md`](2026-07-22-gamewindow-slice-8-checkpoint-i7-session-player.md).
|
||||
- I.7 removes the former 432-line Phase-7 body. Streaming, session, hydration,
|
||||
local-player, combat, and teleport startup now compose through one typed
|
||||
result; all late edges have named reversible exact-owner tokens. The
|
||||
spawn-claim DAT range memo has a focused owner. `GameWindow.cs` is 2,479 raw
|
||||
lines. App Release passes 3,420 tests / 3 skips; the complete Release suite
|
||||
passes 7,792 tests / 5 skips with the same 17 warnings tracked by #228. I.8
|
||||
frame-root publication and terminal session start is the active cut. Its
|
||||
detailed session-binding, atomic-root, and terminal-start plan is
|
||||
[`2026-07-22-gamewindow-slice-8-checkpoint-i8-frame-roots-session-start.md`](2026-07-22-gamewindow-slice-8-checkpoint-i8-frame-roots-session-start.md).
|
||||
I.8a is complete: the session factory, command targets, and sole gameplay
|
||||
input subscriber now belong to Phase 7 and publish before any frame root.
|
||||
I.8b composes the unchanged update/render graph in a focused phase, publishes
|
||||
the pair through an exact owned slot lease, and moves lifecycle resource
|
||||
sampling out of the window. I.8c makes the exact session start and its
|
||||
existing diagnostics the terminal `OnLoad` operation. `GameWindow.cs` is
|
||||
1,910 raw lines.
|
||||
- I.9 found and corrected one structural defect: production manually repeated
|
||||
the phase order instead of invoking the executable pipeline used by failure
|
||||
tests, and several later phases received real platform/settings results only
|
||||
through surrounding captures. Production now enters the same fixed pipeline;
|
||||
platform and settings are explicit typed phase inputs and concrete phases
|
||||
reject mismatched result instances. Session start remains the terminal
|
||||
operation. Three corrected-diff passes covering behavior order, architecture/
|
||||
ownership, and adversarial failure/reentrancy are clean. `GameWindow.cs` is
|
||||
1,945 raw lines. App Release passes 3,431 tests / 3 intentional skips; the
|
||||
complete Release suite passes 7,803 tests / 5 intentional skips with only the
|
||||
17 pre-existing test-project warnings tracked by #228. No retail behavior or
|
||||
divergence changed. Checkpoint I is complete; Checkpoint J is active.
|
||||
|
||||
## 6. Automated acceptance
|
||||
|
||||
### Platform and pipeline
|
||||
|
||||
- Success trace is exactly `gl.factory -> gl.publish -> input.factory ->
|
||||
input.publish`.
|
||||
- GL-factory failure never calls input; input-factory failure retains GL in the
|
||||
lifetime owner.
|
||||
- Failure at each of nine phases runs exactly the preceding prefix and no
|
||||
suffix.
|
||||
- Every phase receives the exact immutable prior result instance.
|
||||
- Success trace is phases 1–8 followed by terminal session start.
|
||||
- Failure after frame publication withdraws the pair exactly once.
|
||||
|
||||
### Acquisition and concrete phases
|
||||
|
||||
- A/B/C prefix tests prove reverse rollback, transfer, aggregation, retry, and
|
||||
no replay.
|
||||
- Every concrete production phase has injected fault points at each
|
||||
acquisition/attach/bind/publication edge.
|
||||
- Optional audio/devtools continue only after successful rollback.
|
||||
- Hook and event registrations unregister exactly once.
|
||||
- Deferred slots unbind only the exact owner and reject stale release.
|
||||
- Streamer start is reversed on later Phase-7 failure.
|
||||
- Portal fallback/controller never co-own.
|
||||
- Phase 8 never exposes one frame root without the other.
|
||||
|
||||
### Structural and repository gates
|
||||
|
||||
- `OnLoad` is a small platform/pipeline forwarder.
|
||||
- No phase stores `GameWindow`, accepts a callback facade for substantial
|
||||
host methods, or uses a service locator / `object` dictionary.
|
||||
- `GameWindow` contains no construction body larger than one paragraph.
|
||||
- `AcDream.Core` gains no App/GL dependency.
|
||||
- App production build has zero warnings/errors; solution warnings remain only
|
||||
the 17 tracked by #228.
|
||||
- Focused tests, all App tests, and full Release suite pass.
|
||||
- Connected lifecycle/reconnect and synchronized soak gates remain green when
|
||||
the local ACE/visual environment is available.
|
||||
|
||||
## 7. Review gate
|
||||
|
||||
After the complete production cutover, run three independent read-only reviews:
|
||||
|
||||
1. retail/behavior-order conformance;
|
||||
2. architecture, dependency direction, ownership, and shutdown symmetry;
|
||||
3. adversarial failure, partial construction, reentrancy, and stale-binding
|
||||
analysis.
|
||||
|
||||
Fix every confirmed finding at its root and repeat review until all three are
|
||||
clean. No review finding is waived merely because the happy-path tests pass.
|
||||
|
||||
## 8. Documentation and commit exit
|
||||
|
||||
Checkpoint I closes only when the code-structure ledger, architecture, roadmap,
|
||||
milestones, issues, `memory/project_gamewindow_decomposition.md`, `AGENTS.md`,
|
||||
and `CLAUDE.md` agree on the same production pipeline and measured class size.
|
||||
No retail-divergence row is added for a behavior-preserving ownership move; any
|
||||
discovered behavioral adaptation is recorded in the same commit that keeps it.
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
# GameWindow Slice 8 — Checkpoint I.6 UI and Live Presentation
|
||||
|
||||
**Status:** Complete; I.7 active
|
||||
|
||||
**Parent:** `2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md`
|
||||
|
||||
**Scope:** Ordered-composition phases 5 and 6 only
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Remove the interaction, retained gameplay UI, live-entity presentation, and
|
||||
landblock-publication construction bodies from `GameWindow.OnLoad`. The window
|
||||
remains a write-only publication surface and runtime coordinator; it does not
|
||||
remain an implicit service locator for callbacks created during startup.
|
||||
|
||||
I.6 lands as two independently buildable commits:
|
||||
|
||||
1. **I.6a — interaction and retained UI composition.**
|
||||
2. **I.6b — live presentation and landblock publication composition.**
|
||||
|
||||
Neither commit changes retail behavior, rendering order, input priority,
|
||||
network messages, selection policy, DAT lookup, or resource quality. This is a
|
||||
structural ownership move, so it does not add a divergence-register row.
|
||||
|
||||
## 2. I.6a — interaction and retained UI
|
||||
|
||||
### 2.1 Owned construction
|
||||
|
||||
`InteractionRetainedUiCompositionPhase` constructs, in the existing order:
|
||||
|
||||
1. combat attack controller;
|
||||
2. combat target controller;
|
||||
3. external-container lifecycle;
|
||||
4. item interaction controller;
|
||||
5. optional retained UI host;
|
||||
6. cursor feedback and retail cursor resources;
|
||||
7. character-sheet and magic runtimes;
|
||||
8. retained mouse and keyboard bindings;
|
||||
9. chrome/icon/font assets;
|
||||
10. chat, radar, persistence, and optional probe owners;
|
||||
11. uninitialized retained UI runtime mount.
|
||||
|
||||
The non-UI combat and item owners exist even when retail UI is disabled. The
|
||||
disabled path acquires no UI host, input callback, font, cursor, probe, or
|
||||
runtime resource.
|
||||
|
||||
### 2.2 Focused authorities
|
||||
|
||||
Callbacks that formerly captured `GameWindow` resolve through typed owners:
|
||||
|
||||
- `DeferredLiveSessionUiAuthority` exposes the current session, command bus,
|
||||
link facts, character account, and the exact outbound operations needed by
|
||||
interaction/UI. It binds to the Phase-7 `LiveSessionController` with an
|
||||
expected-owner token and safely supplies disconnected/no-op values before
|
||||
that bind.
|
||||
- `DeferredSelectionUiAuthority` exposes world picking, use/pickup dispatch,
|
||||
closest-target selection, range/health classification, vivid-target facts,
|
||||
and camera facts. Phase 6 binds its exact query/controller pair.
|
||||
- `DeferredRadarSnapshotSource` returns an empty-but-valid snapshot until
|
||||
Phase 6 publishes the canonical `LiveEntityRuntime`/`GpuWorldState` view. It
|
||||
must never retain the pre-runtime placeholder world.
|
||||
- `DeferredWorldLifecycleAutomationSource` lets the optional UI probe mount in
|
||||
Phase 5 while Phase 7 supplies reveal/resource facts later.
|
||||
- the already focused local-player identity, controller, mode, external-
|
||||
container, settings, and input-dispatch sources replace raw host fields.
|
||||
- a shared `DeferredRenderFrameDiagnosticsSource` exists regardless of whether
|
||||
developer tools are enabled; Phase 6 binds the diagnostics owner once.
|
||||
|
||||
Every deferred bind returns or is paired with an expected-owner unbind. A stale
|
||||
release can neither clear a newer owner nor make retry report “already bound”.
|
||||
Deactivation makes all callbacks inert before runtime teardown.
|
||||
|
||||
### 2.3 Ownership and publication
|
||||
|
||||
`InteractionRetainedUiResult` carries the stable owners required by later
|
||||
phases. `InteractionUiLateBindings` owns the retained input-capture lease and
|
||||
the late authorities; `RetailUiRuntimeLease` remains the sole owner of the
|
||||
optional host/runtime resources. Construction uses
|
||||
`CompositionAcquisitionScope`; the owner is published only after all mandatory
|
||||
interaction resources and every enabled retained-UI resource are complete.
|
||||
|
||||
`IGameWindowInteractionRetainedUiPublication` publishes exact field identities
|
||||
needed by existing steady-state and reset code. Publication is write-only and
|
||||
rejects replacement.
|
||||
|
||||
Normal shutdown and construction rollback preserve these stages:
|
||||
|
||||
1. deactivate late sources and release retained input capture;
|
||||
2. dispose the retained-UI lease, which detaches device callbacks, the mounted
|
||||
runtime, and the host in its established order;
|
||||
3. dispose item/external-container/combat owners.
|
||||
|
||||
The existing retained UI lease remains the sole host/runtime owner until its
|
||||
full lifecycle is migrated; the phase owner controls it but never duplicates
|
||||
its resources.
|
||||
|
||||
### 2.4 I.6a automated gates
|
||||
|
||||
- exact success order and disabled-UI acquisition trace;
|
||||
- injected failure after every construction, attach, mount, and publication
|
||||
edge;
|
||||
- reverse rollback, retry after partial cleanup, and no replay;
|
||||
- late authority default behavior, exact-owner unbind, stale-token rejection,
|
||||
deactivation, and rebind after rollback;
|
||||
- session operations preserve the former outbound message selection;
|
||||
- radar cannot observe a pre-runtime `GpuWorldState` placeholder;
|
||||
- retained mouse-before-keyboard wiring and input capture are unchanged;
|
||||
- publication identity and replacement rejection;
|
||||
- App Release build/tests, then full solution Release build/tests;
|
||||
- three independent self-review passes: retail behavior, architecture and
|
||||
ownership, adversarial failure/reentrancy.
|
||||
|
||||
### 2.5 I.6a completion evidence
|
||||
|
||||
I.6a moved the complete Phase-5 body into
|
||||
`InteractionRetainedUiCompositionPhase`. Exact-owner leases now guard retained
|
||||
input capture plus the late session, selection, radar, view-plane, diagnostics,
|
||||
inventory, and automation bindings. The old `GameWindow` item-use/pick/query
|
||||
wrappers are deleted; shared Vitals identity, mouse-before-keyboard ordering,
|
||||
direct contained-item use, and the disabled-retail-UI path remain unchanged.
|
||||
|
||||
The focused success/failure/rollback matrix passes 28 tests. The App Release
|
||||
gate passes 3,401 tests / 3 intentional skips, the complete Release suite
|
||||
passes 7,773 tests / 5 intentional skips, and the solution build has only the
|
||||
17 warnings already tracked by #228. `GameWindow.cs` is 3,246 raw lines after
|
||||
I.6a. Behavior, ownership, and adversarial self-reviews found no remaining
|
||||
actionable issue. I.6b is the next active cut.
|
||||
|
||||
## 3. I.6b — live presentation and landblock publication
|
||||
|
||||
### 3.1 Owned construction
|
||||
|
||||
`LivePresentationCompositionPhase` constructs, in existing order:
|
||||
|
||||
1. deferred live-component lifecycle;
|
||||
2. landblock and entity spawn adapters;
|
||||
3. static animation/script activation owners;
|
||||
4. canonical `GpuWorldState`;
|
||||
5. `LiveEntityRuntime`, followed immediately by canonical slot bind;
|
||||
6. motion, projectile, withdrawal, light, animation, equipped-child, effect,
|
||||
presentation, and remote-teleport owners;
|
||||
7. WB dispatcher and retail selection scene/query/controller;
|
||||
8. optional paperdoll renderer/presenter;
|
||||
9. EnvCell/portal/clip/tunnel/sky/particle render resources;
|
||||
10. landblock render/physics/static publishers and presentation pipeline;
|
||||
11. render diagnostics and canonical late-source bindings.
|
||||
|
||||
The phase consumes immutable Phase-4 terrain/build inputs. It does not read
|
||||
mutable `GameWindow` terrain fields.
|
||||
|
||||
### 3.2 Named reversible edges
|
||||
|
||||
Replace anonymous subscriptions with named lease owners:
|
||||
|
||||
- live projection visibility → WB presentation residency;
|
||||
- live projection visibility → particle presentation visibility;
|
||||
- equipped-child projection pose → live-entity lights.
|
||||
|
||||
Each lease stores the exact delegate and publisher, detaches once, supports
|
||||
retry if a later release fails, and is inert before dependent teardown.
|
||||
Phase-7 entity-ready and appearance-applied subscriptions are deliberately
|
||||
handled in I.7, where both publishers exist.
|
||||
|
||||
### 3.3 Canonical publication and late binding
|
||||
|
||||
The result publishes one canonical world/runtime identity. Phase 5 radar and
|
||||
selection authorities bind only after those identities exist. The live-runtime
|
||||
slot binds immediately after runtime construction and receives an exact-owner
|
||||
unbind token for rollback.
|
||||
|
||||
Landblock-loaded hydration uses a deferred sink owned by the result. Phase 7
|
||||
binds it to `LiveEntityHydrationController`; no Phase-6 callback captures a
|
||||
future nullable controller.
|
||||
|
||||
Portal tunnel ownership remains:
|
||||
|
||||
```text
|
||||
fallback slot acquire -> prepared transfer -> final frame owner
|
||||
```
|
||||
|
||||
The fallback slot and final owner never co-own the tunnel.
|
||||
|
||||
### 3.4 I.6b automated gates
|
||||
|
||||
- exact production construction order and canonical slot-bind position;
|
||||
- failure at every construction/subscription/bind/publication edge;
|
||||
- canonical runtime/world identity through radar, selection, devtools, and
|
||||
frame diagnostics sources;
|
||||
- loaded, pending, rebucketed, hidden, and deleted live records retain the
|
||||
existing presentation behavior;
|
||||
- event leases detach exact delegates once and survive callback reentrancy;
|
||||
- no duplicate mesh, particle, light, script, selection, or collision owner;
|
||||
- portal fallback acquire/prepare/transfer failure matrix;
|
||||
- landblock publisher ordering and retirement symmetry;
|
||||
- App Release build/tests, full Release build/tests, divergence/source audit;
|
||||
- three independent self-review passes as in I.6a.
|
||||
|
||||
### 3.5 I.6b completion evidence
|
||||
|
||||
I.6b moved the complete Phase-6 body into
|
||||
`LivePresentationCompositionPhase`. The phase now owns the canonical live
|
||||
runtime/world identity, live presentation and effect controllers, WB draw and
|
||||
landblock publication owners, selection/radar authorities, optional paperdoll,
|
||||
and portal/sky/particle resources. Named exact-owner leases replace the former
|
||||
anonymous Phase-6 subscriptions. A deferred landblock-loaded sink preserves
|
||||
the Phase-7 hydration edge without capturing a future window field.
|
||||
|
||||
Construction uses transactional acquisition ownership throughout, including
|
||||
portal-fallback and sky-shader lifetime rollback. The focused binding,
|
||||
rollback, retry, stale-release, and source-boundary tests pass. The App Release
|
||||
gate passes 3,407 tests / 3 intentional skips, the complete Release suite
|
||||
passes 7,779 tests / 5 intentional skips, and the clean solution build retains
|
||||
only the 17 test-project warnings tracked by #228. `GameWindow.cs` is 2,840
|
||||
raw lines after I.6b.
|
||||
Behavior/fidelity, architecture/ownership, and adversarial failure reviews
|
||||
found no remaining actionable issue. I.7 session, streaming, and hydration
|
||||
composition is the next active cut.
|
||||
|
||||
## 4. Production cutover rules
|
||||
|
||||
- Each subcommit leaves `OnLoad` calling the new phase and leaves later phases
|
||||
consuming the typed result; no duplicate inline construction remains.
|
||||
- Existing steady-state code may temporarily read published fields until I.8,
|
||||
but new phase code cannot capture the window.
|
||||
- I.6a may bind its selection/session/diagnostic authorities at the existing
|
||||
Phase-6/7 construction sites. I.6b moves the Phase-6 binds into its result;
|
||||
I.7 moves the remaining Phase-7 binds into the session/streaming phase.
|
||||
- No broad interface exposes the window. Dependencies are explicit records of
|
||||
stable owners and focused sources.
|
||||
- No behavior change is accepted merely to simplify composition.
|
||||
|
||||
## 5. Exit evidence
|
||||
|
||||
I.6 is complete only when both production phase bodies have moved, the old
|
||||
inline blocks and host callback wrappers are deleted, the late-source and
|
||||
failure matrices pass, full Release gates are green, documentation/memory
|
||||
reflect the new boundary, and both commits are individually buildable.
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
# GameWindow Slice 8 — Checkpoint I.7 Session and Player Composition
|
||||
|
||||
**Status:** Complete 2026-07-22
|
||||
|
||||
**Parent:** `2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md`
|
||||
|
||||
**Scope:** Ordered-composition Phase 7 only
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Move the complete streaming, session, hydration, local-player, and teleport
|
||||
construction body out of `GameWindow.OnLoad` without changing accepted
|
||||
movement, network, world-reveal, combat, portal, or render-frame behavior.
|
||||
`GameWindow` remains a write-only publication surface and shutdown shell.
|
||||
|
||||
This is a structural ownership move. It does not alter retail algorithms or
|
||||
add a divergence-register row.
|
||||
|
||||
## 2. Exact production order
|
||||
|
||||
`SessionPlayerCompositionPhase` preserves the current order:
|
||||
|
||||
1. resolve near/far streaming radii from the immutable quality snapshot and
|
||||
legacy override;
|
||||
2. construct and start `LandblockStreamer` under immediate rollback ownership;
|
||||
3. construct `StreamingController`, origin recentering, runtime settings
|
||||
targets, and `WorldRevealCoordinator`;
|
||||
4. construct the network-update bridge, sealed-dungeon classifier,
|
||||
projection materializer, and world-origin coordinator;
|
||||
5. construct and bind `LiveSessionController` plus local physics timestamps;
|
||||
6. construct teardown, deletion, hydration, network-update, liveness, and
|
||||
inbound-session owners;
|
||||
7. bind component teardown, landblock hydration, parent acceptance,
|
||||
same-generation network updates, entity-ready, and appearance-applied;
|
||||
8. bind live combat operations;
|
||||
9. construct mouse-look/gameplay input, streaming-frame, effect-frame,
|
||||
spatial reconciliation, local animation/shadow/projection/frame, and
|
||||
live-object frame owners;
|
||||
10. construct player-mode and auto-entry owners and bind developer commands;
|
||||
11. transfer the prepared portal tunnel into the sole local-teleport owner;
|
||||
12. bind the teleport network sink and retained selection view plane;
|
||||
13. publish one complete `SessionPlayerResult` and transfer rollback ownership.
|
||||
|
||||
No live connection starts in this phase. Terminal session start remains Phase
|
||||
9 so a server cannot publish into a partial frame graph.
|
||||
|
||||
## 3. Typed inputs and outputs
|
||||
|
||||
`SessionPlayerDependencies` contains only stable App owners and state sources.
|
||||
It never stores `GameWindow` or accepts a callback to a substantial window
|
||||
method. Phase results supply content, render, interaction, and live-presentation
|
||||
owners directly.
|
||||
|
||||
`SessionPlayerResult` carries the exact owners required by Phase 8 and
|
||||
shutdown:
|
||||
|
||||
- streamer, streaming controller, origin recenter, and world reveal;
|
||||
- live-session, hydration, network-update, liveness, and inbound-session
|
||||
controllers;
|
||||
- gameplay input, streaming-frame, local animation/shadow, live-object frame,
|
||||
player-mode, and auto-entry owners;
|
||||
- local teleport owner;
|
||||
- one named runtime-binding aggregate.
|
||||
|
||||
`IGameWindowSessionPlayerPublication` publishes those exact identities only
|
||||
after all mandatory resources and binds succeed. Replacement is rejected
|
||||
before any field changes.
|
||||
|
||||
## 4. Focused owners and seams
|
||||
|
||||
### 4.1 Spawn-claim classifier
|
||||
|
||||
Move the memoized indoor spawn-claim range check from `GameWindow` into
|
||||
`DatSpawnClaimHydrationClassifier`. It retains the exact cell-number boundary,
|
||||
DAT lock, and one-claim memoization. The world-reveal coordinator receives its
|
||||
typed `IsUnhydratable` method rather than a window callback.
|
||||
|
||||
### 4.2 Runtime bindings
|
||||
|
||||
`SessionPlayerRuntimeBindings` owns named exact-owner leases and releases them
|
||||
in reverse order. Successful releases are removed immediately; failed releases
|
||||
remain retryable without replaying completed work.
|
||||
|
||||
The aggregate owns:
|
||||
|
||||
- retained-UI live-session authority;
|
||||
- runtime settings targets;
|
||||
- live parent acceptance;
|
||||
- hydration network bridge;
|
||||
- exact `EquippedChildRenderController.EntityReady` delegate;
|
||||
- exact `LiveEntityHydrationController.AppearanceApplied` delegate;
|
||||
- combat attack operations;
|
||||
- camera-pointer gameplay frame;
|
||||
- developer player-mode and command-bus targets;
|
||||
- local teleport network sink;
|
||||
- retained selection view-plane authority.
|
||||
|
||||
The live-runtime component lifecycle remains attached through the Phase-6
|
||||
binding owner because it must outlive session teardown and stay callable until
|
||||
`LiveEntityRuntime.Clear` completes. Landblock-loaded hydration may use that
|
||||
same late lifetime when its publisher outlives the session result.
|
||||
|
||||
### 4.3 Portal transfer
|
||||
|
||||
The prepared tunnel follows the existing single-owner transaction:
|
||||
|
||||
```text
|
||||
Phase 6 fallback slot -> Phase 7 LocalPlayerTeleportController
|
||||
```
|
||||
|
||||
If the destination factory throws, the fallback retains the tunnel. Once the
|
||||
controller exists, Phase 7 immediately owns it for rollback. A later failure
|
||||
disposes the controller and tunnel exactly once; the fallback is already empty.
|
||||
|
||||
### 4.4 Streamer and session rollback
|
||||
|
||||
The streamer is scope-owned before `Start`. Any later Phase-7 failure disposes
|
||||
it, which joins the worker and releases pending work. The live-session
|
||||
controller is also scope-owned immediately. Rollback order is:
|
||||
|
||||
1. detach external bindings;
|
||||
2. dispose local teleport if transfer completed;
|
||||
3. dispose live-session ownership;
|
||||
4. dispose the streamer;
|
||||
5. aggregate and retain any incomplete cleanup for retry.
|
||||
|
||||
Normal shutdown remains behaviorally ordered: quiesce input, gracefully retire
|
||||
the live session and reset graph, withdraw frame borrowers, detach Phase-7
|
||||
bindings, stop the streamer, clear live entities, then release presentation and
|
||||
GL owners.
|
||||
|
||||
## 5. Publication and cutover
|
||||
|
||||
- `OnLoad` calls one `SessionPlayerCompositionPhase.Compose(...)` paragraph.
|
||||
- The old radius, streamer, session, hydration, player, and teleport
|
||||
construction body is deleted.
|
||||
- Phase 8 consumes `SessionPlayerResult`; it does not rediscover the new owners
|
||||
through nullable window fields.
|
||||
- Existing steady-state/reset code may temporarily read published fields until
|
||||
Checkpoint I.8 completes the root cutover.
|
||||
- No second GUID map, world state, input subscriber, portal owner, or session
|
||||
authority is introduced.
|
||||
|
||||
## 6. Automated gates
|
||||
|
||||
### Construction and rollback
|
||||
|
||||
- exact construction trace, including streamer-start and portal-transfer
|
||||
positions;
|
||||
- failure before and after streamer start stops the worker exactly once;
|
||||
- failure at every bind/publication boundary releases in reverse order;
|
||||
- partial cleanup retries only failed edges and never replays completed ones;
|
||||
- portal destination-factory failure preserves fallback ownership;
|
||||
- later failure disposes the transferred teleport owner exactly once;
|
||||
- publication rejects replacement atomically.
|
||||
|
||||
### Binding and lifetime
|
||||
|
||||
- exact-owner unbind and stale-token rejection for every new deferred seam;
|
||||
- `EntityReady` and `AppearanceApplied` detach the exact delegates once;
|
||||
- callbacks are safe before bind and inert after deactivation where the source
|
||||
contract permits it;
|
||||
- live-runtime component teardown stays bound until live records are cleared;
|
||||
- session/UI/command targets cannot retain a failed Phase-7 graph;
|
||||
- runtime settings targets do not replay startup values when bound;
|
||||
- no duplicate input, combat, streamer, hydration, or portal owner.
|
||||
|
||||
### Behavior and structure
|
||||
|
||||
- live/offline gates and initial placeholder origin remain unchanged;
|
||||
- near/far and legacy radius selection are byte-for-byte equivalent;
|
||||
- spawn-claim cell boundaries preserve the former memoized behavior;
|
||||
- session packet routing, hydration order, combat target selection, player-mode
|
||||
auto-entry, and portal presentation remain unchanged;
|
||||
- `GameWindow` contains no Phase-7 construction body or spawn-claim algorithm;
|
||||
- App Release tests, clean solution Release build, and full Release tests pass;
|
||||
- source/divergence audit and three self-review passes are clean.
|
||||
|
||||
## 7. Exit evidence
|
||||
|
||||
I.7 is complete when Phase 7 is the production path, every Phase-7 external
|
||||
edge has explicit reversible ownership, failure and retry gates pass, the old
|
||||
window body is gone, documentation/memory report the measured class size and
|
||||
test totals, and the commit is independently buildable. I.8 then composes the
|
||||
update/render roots and terminal session start over the typed result graph.
|
||||
|
||||
## 8. Completion evidence
|
||||
|
||||
- `SessionPlayerCompositionPhase` is the production Phase-7 path. The former
|
||||
432-line radius/streaming/session/hydration/player/teleport block is absent
|
||||
from `GameWindow`.
|
||||
- `SessionPlayerRuntimeBindings` plus exact-owner tokens cover every Phase-7
|
||||
late edge. Focused tests cover reverse retry, rebind, and stale-token safety.
|
||||
- `DatSpawnClaimHydrationClassifier` owns the former window memo and exact
|
||||
indoor-cell boundaries, including missing/empty DAT cases and reset.
|
||||
- `GameWindow.cs` is 2,479 raw lines, down 13,244 lines (84.2%) from the
|
||||
15,723-line campaign baseline.
|
||||
- App Release: 3,420 passed / 3 intentional skips. Complete Release suite:
|
||||
7,792 passed / 5 intentional skips. Clean solution build: 0 errors and the
|
||||
17 pre-existing test warnings tracked by #228.
|
||||
- Behavior/order, architecture/ownership, and adversarial rollback reviews are
|
||||
clean. No gameplay mechanism changed and no divergence row was introduced.
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
# GameWindow Slice 8 — Checkpoint I.8 Frame Roots and Session Start
|
||||
|
||||
**Status:** Complete 2026-07-22
|
||||
|
||||
**Parent:** `2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md`
|
||||
|
||||
**Scope:** Complete Phase 7's session-facing owners, then implement ordered
|
||||
composition Phases 8 and 9.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Finish the production startup graph without changing accepted input, update,
|
||||
render, network, portal, or UI behavior:
|
||||
|
||||
1. complete Phase 7 before any frame root is visible;
|
||||
2. construct update and render roots locally and publish the pair atomically;
|
||||
3. start the optional live session as the absolute final startup operation.
|
||||
|
||||
The current ordering defect is structural: `GameWindow.OnLoad` publishes the
|
||||
frame pair before the combat/diagnostic command targets and sole gameplay input
|
||||
subscriber are attached. I.8 moves those pre-frame owners into the Phase-7
|
||||
result, so native callbacks can never observe an incomplete input/command
|
||||
graph.
|
||||
|
||||
This is an ownership move. Retail algorithms and wire behavior remain
|
||||
unchanged, so no divergence-register row is added.
|
||||
|
||||
## 2. I.8a — complete session/player composition
|
||||
|
||||
**Completed 2026-07-22.** `LiveSessionRuntimeFactory` now owns the exact
|
||||
reset/router/command graph without a window reference. Phase 7 publishes the
|
||||
session host, exact-owner combat and diagnostic command bindings, and the sole
|
||||
gameplay input subscriber before Phase 8 can expose a frame root. Desired
|
||||
components have a focused canonical snapshot owner. The component-lifecycle
|
||||
handoff into the earlier live-presentation phase is an exact, retryable
|
||||
adoption lease, so any later Phase-7 fault rolls it back without stranding or
|
||||
replaying teardown.
|
||||
|
||||
### 2.1 Focused session runtime factory
|
||||
|
||||
Add `LiveSessionRuntimeFactory` under `AcDream.App/Net`. It owns the existing
|
||||
domain-specific construction of:
|
||||
|
||||
- `LiveSessionHostBindings`;
|
||||
- reset manifest bindings;
|
||||
- selection and entered-world callbacks;
|
||||
- event and command routers;
|
||||
- inventory, character, and social session bindings.
|
||||
|
||||
The factory receives focused owners and state slots, never `GameWindow` and
|
||||
never a callback to a substantial window method. Move the existing reset and
|
||||
router bodies verbatim. Preserve the exact reset order and the existing
|
||||
retail citations around player-module identity/shortcut/component cleanup.
|
||||
|
||||
Add `DesiredComponentSnapshotState` beside `ShortcutSnapshotState` so session
|
||||
routers mutate one focused state owner. `GameWindow.DesiredComponents` remains
|
||||
an ABI-compatible read-only projection over that owner. Existing
|
||||
`LocalPlayerIdentityState`, `LocalPlayerControllerSlot`, and
|
||||
`ShortcutSnapshotState` remain canonical; no mirrored values are introduced.
|
||||
|
||||
### 2.2 Commands and sole gameplay subscriber
|
||||
|
||||
At the tail of `SessionPlayerCompositionPhase`, after player mode and teleport
|
||||
exist but before result publication:
|
||||
|
||||
1. create `LiveSessionHost` through the focused factory;
|
||||
2. create and bind `LiveCombatModeCommandController`;
|
||||
3. create and bind `RuntimeDiagnosticCommandController`;
|
||||
4. create `GameplayInputCommandController` and priority targets;
|
||||
5. create and attach the sole `GameplayInputActionRouter`.
|
||||
|
||||
Add exact-owner `BindOwned` tokens to the combat and diagnostic command slots.
|
||||
The existing gameplay router already owns both subscriptions transactionally;
|
||||
Phase 7 scope-owns it immediately after construction and before `Attach`.
|
||||
|
||||
Extend `SessionPlayerResult` and its publication seam with the exact
|
||||
`LiveSessionHost`, command owners, and optional gameplay router. Publication
|
||||
rejects replacement before changing any field. Failure before publication
|
||||
disposes the router, detaches command targets, and rolls back the existing
|
||||
Phase-7 suffix in reverse order.
|
||||
|
||||
## 3. I.8b — atomic frame-root composition
|
||||
|
||||
**Completed 2026-07-22.** `FrameRootCompositionPhase` now constructs the
|
||||
unchanged render and update graphs from explicit prior-phase results and
|
||||
focused runtime owners. `GameFrameGraphSlot.PublishOwned` publishes and
|
||||
withdraws one exact pair. Optional lifecycle automation has a Phase-8 binding
|
||||
owner, and `WorldLifecycleResourceSnapshotSource` samples the same canonical
|
||||
owners without window callbacks or mirrored counters. The Phase-8 contract now
|
||||
receives the Phase-2 content result explicitly because scripts and particles
|
||||
are real frame dependencies; this removes a hidden reach-back through the
|
||||
window shell.
|
||||
|
||||
Add `FrameRootCompositionPhase` implementing the existing
|
||||
`IFrameRootCompositionPhase` contract. Its dependencies are focused App owners;
|
||||
prior phase results supply all resources created during startup.
|
||||
|
||||
Preserve the exact current construction order:
|
||||
|
||||
1. teleport/login/GL-state and render-live preparation;
|
||||
2. render resource begin/clear/live phases;
|
||||
3. weather, sky-PES, world environment, camera, visibility, settings preview,
|
||||
root, animated-object, and building sources;
|
||||
4. terrain, PView, scene-pass, and world-scene diagnostics;
|
||||
5. optional lifecycle automation;
|
||||
6. retained/devtools/private presentation;
|
||||
7. render orchestrator;
|
||||
8. live-frame coordinator, camera frame, and update orchestrator;
|
||||
9. atomic update/render pair publication as the final Phase-8 action.
|
||||
|
||||
Add `GameFrameGraphSlot.PublishOwned`. Its lease withdraws only the exact pair
|
||||
it published, is idempotent, and cannot withdraw a later replacement. Scope-own
|
||||
the lease immediately. A failure before publication leaves the slot empty; a
|
||||
failure after publication withdraws the exact pair unless result publication
|
||||
has transferred lifetime to the window shutdown owner.
|
||||
|
||||
### 3.1 Automation and resource snapshots
|
||||
|
||||
Add `WorldLifecycleResourceSnapshotSource` now rather than retaining
|
||||
`CaptureWorldLifecycleResourceSnapshot` on the window. It samples the same
|
||||
canonical world, live-runtime, effect, particle, light, script, mesh, texture,
|
||||
GPU-memory, managed-memory, render-diagnostic, and frame-profiler owners.
|
||||
|
||||
This advances the owner-extraction portion of Checkpoint K; K still changes
|
||||
checkpoint timing, acknowledgement barriers, JSONL validation, and soak
|
||||
comparison. No checkpoint semantics change in I.8.
|
||||
|
||||
`FrameRootRuntimeBindings` owns the optional automation late binding and any
|
||||
other Phase-8 external edge. It uses reverse, retryable, no-replay cleanup.
|
||||
Shutdown withdraws the frame graph first, then detaches these frame-owned
|
||||
bindings before borrowed session/presentation owners retire.
|
||||
|
||||
### 3.2 Publication
|
||||
|
||||
`FrameRootResult` contains:
|
||||
|
||||
- exact update and render roots;
|
||||
- optional lifecycle automation owner;
|
||||
- Phase-8 runtime bindings;
|
||||
- the `LiveSessionHost` borrowed from Phase 7 for terminal start.
|
||||
|
||||
`IGameWindowFrameRootPublication` stores only owners needed by steady-state or
|
||||
shutdown. Replacement is rejected atomically.
|
||||
|
||||
## 4. I.8c — terminal start and window cutover
|
||||
|
||||
**Completed 2026-07-22.** `SessionStartCompositionPhase` invokes the exact
|
||||
Phase-7 host through the Phase-8 result and owns the existing missing-credential
|
||||
and failed-start diagnostics. The phase call is the final statement in
|
||||
`GameWindow.OnLoad`; no binding, publication, allocation, or diagnostics setup
|
||||
follows it.
|
||||
|
||||
Add `SessionStartCompositionPhase` implementing
|
||||
`ISessionStartCompositionPhase<FrameRootResult>`. It invokes only
|
||||
`LiveSessionHost.Start(RuntimeOptions)` and preserves the existing diagnostics
|
||||
for missing credentials and failed startup.
|
||||
|
||||
`GameWindow.OnLoad` ends with:
|
||||
|
||||
```text
|
||||
Phase 7 Compose
|
||||
Phase 8 Compose and atomic pair publication
|
||||
Phase 9 Start
|
||||
return
|
||||
```
|
||||
|
||||
There is no callback attachment, binding, publication, diagnostics setup, or
|
||||
other work after Phase 9. Delete the old inline frame construction, session
|
||||
factory/reset/router methods, snapshot method, and terminal status switch.
|
||||
|
||||
The existing typed nine-phase `GameWindowCompositionPipeline` remains the
|
||||
executable order/failure oracle. Production `OnLoad` uses the same phase
|
||||
interfaces and result chain explicitly because later-phase dependency records
|
||||
are assembled from earlier concrete results; it must not introduce a retained
|
||||
mega-context, service locator, or delegate façade around `GameWindow`.
|
||||
|
||||
## 5. Ownership and rollback order
|
||||
|
||||
Before frame publication, Phase-7 rollback is:
|
||||
|
||||
1. gameplay router detach;
|
||||
2. diagnostic and combat command unbind;
|
||||
3. remaining session/player bindings;
|
||||
4. teleport, live session, and streamer teardown.
|
||||
|
||||
After frame publication, Phase-8 rollback is:
|
||||
|
||||
1. exact frame-pair withdrawal;
|
||||
2. automation/other frame binding detach;
|
||||
3. no disposal of resources borrowed from earlier successful phases.
|
||||
|
||||
Normal shutdown retains the frozen barrier order:
|
||||
|
||||
1. logical input/command quiescence;
|
||||
2. live-session convergence;
|
||||
3. physical input callback detach;
|
||||
4. frame-pair withdrawal;
|
||||
5. frame then session/player late-binding detach;
|
||||
6. remaining session, live-entity, effect, render, content, GL, and native
|
||||
owners.
|
||||
|
||||
## 6. Automated gates
|
||||
|
||||
### Session/runtime
|
||||
|
||||
- exact live-session host construction and reset trace;
|
||||
- no `GameWindow` field or delegate capture in the focused factory;
|
||||
- exact-owner combat/diagnostic bind, competing-owner rejection, idempotent
|
||||
release, and rebind;
|
||||
- gameplay router attaches before frame publication and rolls back from either
|
||||
partial subscription;
|
||||
- failed Phase-7 publication leaves no command/input/session target behind.
|
||||
|
||||
### Frame roots
|
||||
|
||||
- exact render/update leaf construction order;
|
||||
- frame slot is empty at every pre-publication fault;
|
||||
- exact pair publishes once and both native callbacks see the same generation;
|
||||
- rollback withdraws only the published pair and never a later replacement;
|
||||
- automation disabled path acquires nothing;
|
||||
- automation enabled path binds once, detaches exactly, and samples the same
|
||||
resource values as the former window method;
|
||||
- frame result publication rejects replacement atomically.
|
||||
|
||||
### Terminal start and structure
|
||||
|
||||
- terminal start runs only after frame publication and every input/command
|
||||
attachment;
|
||||
- missing credentials and failed starts preserve current diagnostics;
|
||||
- no operation follows `Start`;
|
||||
- `GameWindow` contains no live-session binding factory, frame-root
|
||||
construction body, resource-snapshot algorithm, or direct frame-pair
|
||||
publication;
|
||||
- no backend dependency enters Core and panels remain on UI abstractions;
|
||||
- App Release tests, clean solution Release build, and complete Release suite
|
||||
pass;
|
||||
- behavior/order, architecture/ownership, and adversarial failure reviews are
|
||||
clean.
|
||||
|
||||
## 7. Commit sequence
|
||||
|
||||
1. `docs(architecture): plan frame roots and terminal session start`
|
||||
2. `refactor(app): complete session startup composition`
|
||||
3. `refactor(app): compose atomic frame roots`
|
||||
4. `refactor(app): make session start terminal`
|
||||
|
||||
Each implementation commit is independently buildable and preserves the
|
||||
protected pre-existing `TransitionTypes.cs`, `.test-out/`, and `logs/` changes.
|
||||
I.9 performs the complete Checkpoint-I corrected-diff review and documentation
|
||||
closeout after these cuts land.
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
# GameWindow Slice 8 Checkpoint J — lifetime and shutdown
|
||||
|
||||
**Status:** Complete 2026-07-22.
|
||||
**Parent:**
|
||||
[`2026-07-22-gamewindow-slice-8-composition-lifecycle.md`](2026-07-22-gamewindow-slice-8-composition-lifecycle.md),
|
||||
Checkpoint J.
|
||||
**Integrated baseline:** `530b4bd8`; Checkpoint I is complete, the production
|
||||
startup path uses the tested nine-phase pipeline, `GameWindow.cs` is 1,945 raw
|
||||
lines, App Release passes 3,431 tests / 3 skips, and the complete Release suite
|
||||
passes 7,803 tests / 5 skips.
|
||||
**Behavior rule:** preserve the accepted disconnect, reset, callback cutoff,
|
||||
frame withdrawal, GL dependency, and native-window-last order. This checkpoint
|
||||
changes lifetime ownership and failure reporting, not gameplay behavior.
|
||||
|
||||
**Result:** `GameWindowLifetime` now owns the immutable typed root snapshot,
|
||||
retryable staged transaction, structured terminal report, and native window.
|
||||
Persistent physical-detach failures are retried and reported without stranding
|
||||
later owners; session convergence and GPU drain remain hard barriers. Clean
|
||||
native release drops the completed transaction/root graph, while terminal
|
||||
abandonment retains it. `GameWindow.cs` is 1,625 raw lines, down 14,098 lines
|
||||
(89.7%) from the campaign baseline. The App Release gate passes 3,441 tests / 3
|
||||
skips; the complete Release gate passes 7,813 / 5 skips; the solution build has
|
||||
only #228's 17 existing test warnings. Three corrected-diff review passes are
|
||||
clean and no retail-divergence row changed.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Replace `GameWindow.CreateShutdownTransaction` and the window-owned transaction
|
||||
field with one pre-window `GameWindowLifetime`. The lifetime owns:
|
||||
|
||||
- the single staged `ResourceShutdownTransaction`;
|
||||
- the first immutable typed shutdown-root snapshot;
|
||||
- the native window root once `Window.Create` succeeds;
|
||||
- explicit `Active`, `RetryableIncomplete`, `Complete`,
|
||||
`CompleteWithCleanupFailures`, and `AbandonedIncomplete` state;
|
||||
- one immutable report returned by every terminal repeated call.
|
||||
|
||||
`GameWindow.OnClosing` performs a synchronous non-terminal attempt while the GL
|
||||
context is current. `GameWindow.Dispose` retries the retained transaction and
|
||||
then releases the native window. A persistent hard barrier forces the named,
|
||||
logged last-resort native fallback and terminal abandonment; it never reports a
|
||||
clean shutdown.
|
||||
|
||||
## 2. Typed ownership boundary
|
||||
|
||||
`GameWindowShutdownRoots` is a teardown-only aggregate of cohesive records, not
|
||||
a runtime service locator. It exposes no gameplay operations and is captured
|
||||
exactly once when shutdown first starts:
|
||||
|
||||
1. `IngressShutdownRoots` — quiescence, command/input logical cutoff, physical
|
||||
callback owners, session controller, and native callback binding.
|
||||
2. `FrameShutdownRoots` — atomic frame publication, frame/session bindings, and
|
||||
UI/session late bindings.
|
||||
3. `LiveShutdownRoots` — retained UI, interaction/streaming owners, canonical
|
||||
live runtime, hook/effect/audio owners, and effect state.
|
||||
4. `RenderShutdownRoots` — GPU-flight barrier, private/render frontends,
|
||||
texture/mesh/render resources, dedicated atlas/sky roots, and construction
|
||||
cleanup.
|
||||
5. `PlatformShutdownRoots` — DAT mapping, input context, and GL.
|
||||
|
||||
The records retain exact object identities. Operations call those identities
|
||||
directly; no stage stores `GameWindow`, a callback facade into it, a dictionary,
|
||||
or an untyped service provider. The window may retain borrowed fields until the
|
||||
host itself dies, but only `GameWindowLifetime` drives release after capture.
|
||||
|
||||
## 3. Transaction policy
|
||||
|
||||
Extend `ResourceShutdownOperation` with a defaulted policy:
|
||||
|
||||
```csharp
|
||||
ResourceShutdownOperationPolicy.HardBarrier
|
||||
ResourceShutdownOperationPolicy.ReportAndContinue
|
||||
```
|
||||
|
||||
Existing call sites remain hard by default. All operations in a stage are
|
||||
attempted independently. Successful operations never replay. A hard failure
|
||||
remains pending and protects later stages. A report-and-continue operation is
|
||||
retried once; a second failure becomes a structured cleanup failure, marks that
|
||||
operation settled, and allows the stage to converge. The transaction exposes
|
||||
its current stage and immutable cleanup failures without swallowing exceptions.
|
||||
|
||||
Only physical ingress removal is soft. Logical quiescence, live-session
|
||||
convergence, frame withdrawal, owner disposal, GPU drain, DAT/input/GL release,
|
||||
and all dependency barriers remain hard.
|
||||
|
||||
## 4. Frozen stage order
|
||||
|
||||
The manifest outside `GameWindow` uses this exact order:
|
||||
|
||||
1. `host and session barriers`
|
||||
- stop accepting callbacks;
|
||||
- deactivate combat/diagnostic/retained/gameplay/pointer/dispatcher/device
|
||||
paths;
|
||||
- quiesce retained/devtools input;
|
||||
- dispose the live-session controller and require converged disposal.
|
||||
2. `physical ingress cleanup` — settings VM, retained/gameplay/pointer/
|
||||
dispatcher/device bindings, retained/devtools input, and native window
|
||||
callbacks; every operation is report-and-continue.
|
||||
3. `frame borrowers` — withdraw the exact atomic frame pair, then frame-root and
|
||||
session/player bindings.
|
||||
4. `session dependents` — late bindings, mouse presentation, retained UI,
|
||||
combat/item/container owners, streamer, and equipped children.
|
||||
5. `live entities` — clear canonical runtime while teardown callbacks live.
|
||||
6. `effect dispatch edges` — live bindings, effect advance, hook registrations.
|
||||
7. `live entity dependents` — lights, presentation, remote teleport, effect
|
||||
state, and audio.
|
||||
8. `submitted GPU work` — hard wait barrier.
|
||||
9. `render frontends`.
|
||||
10. `shared texture owners`.
|
||||
11. `mesh adapter`.
|
||||
12. `remaining render owners`.
|
||||
13. `dedicated render resources` — sky shader then terrain atlas.
|
||||
14. `failed render construction cleanup`.
|
||||
15. `frame flight owner`.
|
||||
16. `content mappings`.
|
||||
17. `input context`.
|
||||
18. `OpenGL context`.
|
||||
19. Native window release/fallback outside the transaction and last.
|
||||
|
||||
The former late native-callback operation moves to stage 2. Logical cutoff makes
|
||||
copied callbacks inert before the potentially long session close, so a broken
|
||||
event remove cannot prevent F653/transport teardown or strand unrelated owners.
|
||||
|
||||
## 5. State and reporting
|
||||
|
||||
`GameWindowLifetimeReport` contains status, blocked stage, structured soft
|
||||
cleanup failures, and the retained hard/native error when present.
|
||||
|
||||
- `TryComplete(roots)` captures roots once and never performs native fallback.
|
||||
It is used by `OnClosing`.
|
||||
- `CompleteAndReleaseNativeWindow(roots)` retries hard work, then disposes the
|
||||
native window. If hard convergence still fails, it retains the blocked-stage
|
||||
report, marks eligible partial owners abandoned, releases the native fallback,
|
||||
and becomes `AbandonedIncomplete`.
|
||||
- Clean transaction + no soft failures becomes `Complete`.
|
||||
- Clean transaction + soft failures becomes `CompleteWithCleanupFailures`.
|
||||
- Reentrant completion is inert while the outer call owns progress.
|
||||
- Every terminal repeated call returns the same report and touches no owner.
|
||||
- Native window publication is single-owner, pre-Run-safe, and released once.
|
||||
|
||||
## 6. Implementation sequence
|
||||
|
||||
1. Add operation policy, structured cleanup-failure exposure, and focused
|
||||
transaction tests for transient/persistent soft cleanup, mixed hard/soft
|
||||
stages, retry/no-replay, and reentrancy.
|
||||
2. Add `GameWindowLifetime`, report/state types, typed root records, and the
|
||||
manifest factory. Test every stage trace using fakes, optional-null roots,
|
||||
persistent session/GPU barriers, persistent soft detach, clean/native-failure
|
||||
finalization, terminal abandonment, and repeated/reentrant calls.
|
||||
3. Publish the native window immediately after creation; replace the window
|
||||
transaction/manifest with one typed root capture and the two narrow lifetime
|
||||
calls. Remove all shutdown feature bodies and update source-shape tests to
|
||||
inspect the focused owner.
|
||||
4. Exercise constructed-never-run, Load-never-fired, partial composition
|
||||
prefixes, normal close then Dispose, direct Dispose, transient retry, and
|
||||
optional audio/devtools/retained-UI absence.
|
||||
5. Run behavior-order, architecture/ownership, and adversarial failure review
|
||||
passes; correct every finding. Run focused tests, App Release, solution
|
||||
Release build, and complete Release tests.
|
||||
6. Reconcile architecture, roadmap, milestones, issues, AGENTS/CLAUDE, and
|
||||
durable memory; commit Checkpoint J as one bisectable ownership unit.
|
||||
|
||||
## 7. Acceptance
|
||||
|
||||
- No shutdown stage or substantial release body remains in `GameWindow`.
|
||||
- No lifetime/manifest type stores `GameWindow` or a general callback facade.
|
||||
- Session and GPU hard failures never run protected dependent stages.
|
||||
- Persistent physical-detach failure does not strand frame/live/render/content/
|
||||
input/GL owners and produces `CompleteWithCleanupFailures`.
|
||||
- Both frame roots become unreachable before any borrower retires.
|
||||
- Every acquired owner releases once; absent owners are no-ops; failed owners
|
||||
retry without replaying completed work.
|
||||
- Direct, repeated, concurrent-safe/reentrant, close-then-dispose, and every
|
||||
partial-load path are deterministic.
|
||||
- Native window release is last. Hard non-convergence becomes named terminal
|
||||
abandonment; repeated calls are inert and preserve the same report.
|
||||
- App production builds without warnings; only #228's 17 test warnings remain;
|
||||
focused, App, and complete Release suites pass.
|
||||
- No retail-divergence row is added unless review discovers an actual behavior
|
||||
change; this ownership correction itself is behavior-preserving.
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
# GameWindow Slice 8 Checkpoint K — canonical soak snapshots
|
||||
|
||||
**Status:** Complete 2026-07-22 (`bca41487`, corrected gate semantics
|
||||
`6c5e0604`).
|
||||
**Parent:**
|
||||
[`2026-07-22-gamewindow-slice-8-composition-lifecycle.md`](2026-07-22-gamewindow-slice-8-composition-lifecycle.md),
|
||||
Checkpoint K.
|
||||
**Integrated result:** `GameWindow.cs` is 1,622 raw lines, App Release passes
|
||||
3,451 tests / 3 skips, and the complete Release suite passes 7,823 tests / 5
|
||||
skips.
|
||||
**Issue:** [`#232`](../ISSUES.md#232--nine-stop-soak-process-memory-gate-lacks-canonical-owner-snapshots).
|
||||
**Behavior rule:** this checkpoint changes diagnostic capture and connected-gate
|
||||
evidence only. It does not change rendering, streaming, gameplay, resource
|
||||
budgets, or the existing process working/private-memory thresholds.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Make every scripted `checkpoint <name>` a deferred, acknowledged render-frame
|
||||
barrier. A checkpoint is written only after private presentation and the normal
|
||||
render diagnostics phase have both observed the same immutable
|
||||
`RenderFrameOutcome`. Its JSON contains the canonical world/reveal/resource
|
||||
owner snapshot and that exact frame outcome. The script cannot execute the next
|
||||
command until serialization and both writes succeed, fail, or are explicitly
|
||||
cancelled during shutdown.
|
||||
|
||||
The nine-stop soak then consumes exactly nine ordered checkpoints, gates
|
||||
canonical owner stability at the Caul return → Caul plateau same-location pair,
|
||||
labels server-controlled entity/animation population changes as workload
|
||||
warnings, and retains process residency as an unchanged secondary guard.
|
||||
|
||||
## 2. Runtime contract
|
||||
|
||||
### 2.1 Acknowledgement token
|
||||
|
||||
Replace the synchronous automation method with a request contract equivalent
|
||||
to:
|
||||
|
||||
```csharp
|
||||
enum RetailUiAutomationCheckpointStatus
|
||||
{
|
||||
Pending,
|
||||
Succeeded,
|
||||
Failed,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
interface IRetailUiAutomationCheckpoint
|
||||
{
|
||||
RetailUiAutomationCheckpointStatus Status { get; }
|
||||
string? Error { get; }
|
||||
}
|
||||
|
||||
bool TryRequestCheckpoint(
|
||||
string name,
|
||||
out IRetailUiAutomationCheckpoint? checkpoint,
|
||||
out string error);
|
||||
```
|
||||
|
||||
The production token also retains the assigned request sequence and validated
|
||||
name across the update → render edge. Only the controller can transition it,
|
||||
exactly once, from Pending to a terminal state.
|
||||
|
||||
`RetailUiAutomationScriptRunner` stores one token together with the active
|
||||
command index. While Pending, repeated ticks return without enqueueing again or
|
||||
advancing the command. Success clears the token and advances once. Failure or
|
||||
cancellation clears it and stops on the token's exact error. Disposal still
|
||||
releases held input; runtime shutdown is responsible for cancelling queued
|
||||
tokens.
|
||||
|
||||
### 2.2 FIFO request owner
|
||||
|
||||
`WorldLifecycleAutomationController` becomes the sole FIFO owner and an
|
||||
`IDisposable` frame-phase participant:
|
||||
|
||||
- validation failure creates no request and consumes no sequence;
|
||||
- accepted requests receive one monotonic sequence and enter one FIFO;
|
||||
- a successful render drains accepted requests in order;
|
||||
- each request serializes one `WorldLifecycleCheckpoint` containing its
|
||||
sequence/name, current reveal state, the exact current `RenderFrameOutcome`,
|
||||
and one canonical resource snapshot;
|
||||
- Succeeded is published only after the JSONL append and named JSON write both
|
||||
complete;
|
||||
- capture/serialization/I/O failure marks only that request Failed with
|
||||
contextual sequence/name error and does not throw out of rendering;
|
||||
- shutdown marks every still-pending request Cancelled with an explicit error;
|
||||
- enqueue after shutdown fails synchronously and cannot create an orphan token.
|
||||
|
||||
The controller is adopted by `FrameRootRuntimeBindings` separately from its
|
||||
retained-UI late binding, so the binding detaches and pending requests cancel
|
||||
under the existing retryable frame-root lifetime.
|
||||
|
||||
### 2.3 Post-diagnostics render phase
|
||||
|
||||
Add a narrow `IRenderFramePostDiagnosticsPhase` with a no-op implementation.
|
||||
`RenderFrameOrchestrator` calls it exactly once after:
|
||||
|
||||
1. resource preparation;
|
||||
2. world rendering;
|
||||
3. private presentation and screenshots;
|
||||
4. `IRenderFrameDiagnosticsPhase.Publish`;
|
||||
5. then checkpoint drain;
|
||||
6. finally the existing GPU-flight close.
|
||||
|
||||
If an earlier render phase fails, post-diagnostics is not called and the FIFO
|
||||
remains pending for the next successful frame. An unexpected post-phase throw
|
||||
uses the existing render failure/recovery path, although production capture and
|
||||
I/O failures are represented on tokens rather than thrown. No previous-frame
|
||||
diagnostic value is paired with a current-frame owner snapshot:
|
||||
`WorldLifecycleResourceSnapshotSource.Capture(RenderFrameOutcome)` takes
|
||||
visible/total landblocks from the supplied outcome and reads the already-
|
||||
published aggregate FPS/frame-time snapshot only after diagnostics runs.
|
||||
|
||||
## 3. Artifact and soak schema
|
||||
|
||||
`WorldLifecycleCheckpoint` adds the exact frame outcome. Existing reveal and
|
||||
resource fields retain their names. The route adds these nine commands in this
|
||||
order at the end of each settled destination block:
|
||||
|
||||
1. `caul-baseline`
|
||||
2. `sawato-baseline`
|
||||
3. `rynthid`
|
||||
4. `aerlinthe`
|
||||
5. `sawato-return`
|
||||
6. `holtburg`
|
||||
7. `caul-return`
|
||||
8. `sawato-plateau`
|
||||
9. `caul-plateau`
|
||||
|
||||
`run-connected-r6-soak.ps1` reads the JSONL only after the route completes and
|
||||
requires one row for every expected name, exact sequence 1–9, exact order, and
|
||||
the current process id. It embeds those records in the report.
|
||||
|
||||
Every checkpoint hard-gates zero:
|
||||
|
||||
- pending live teardowns;
|
||||
- pending landblock retirements;
|
||||
- staged mesh uploads and bytes;
|
||||
- composite warmup work.
|
||||
|
||||
Every route teleport supplies an explicit identity quaternion so the timed turn
|
||||
begins from a reproducible heading. The same-location Caul return → Caul
|
||||
plateau comparison requires exact equality for the deterministic loaded/total
|
||||
world domain, while visible-landblock and authoritative entity/animation/live-
|
||||
entity changes are named workload warnings. Teardown/retirement queues, staged
|
||||
work, and composite warmup remain hard zero at every checkpoint. Mesh, atlas,
|
||||
tracked-GPU, composite/particle-texture, and VFX owner counts may retire and
|
||||
shrink normally; any growth is a hard leak failure when the visible and
|
||||
authoritative workload is stable, and a named owner-growth warning when that
|
||||
workload changed. Particles, emitters, and active scripts are always transient
|
||||
workload warnings rather than silent exclusions. The report therefore says
|
||||
which canonical owner grew without mistaking legitimate retirement for a leak.
|
||||
Managed used/committed deltas are reported diagnostically. Existing
|
||||
working/private-memory, update-p95, and allocation-p50 thresholds remain byte-
|
||||
for-byte unchanged as the secondary residency/performance guard.
|
||||
|
||||
## 4. Implementation sequence
|
||||
|
||||
1. Add token states and change the automation runtime/script runner to the
|
||||
one-token-per-command polling contract. Cover delayed success, repeated
|
||||
pending ticks, failure, cancellation, invalid request, and disposal while
|
||||
pending.
|
||||
2. Refactor `WorldLifecycleAutomationController` into the FIFO request owner;
|
||||
pass the frame outcome into snapshot capture and write it into artifacts.
|
||||
Cover FIFO order, sequence stability, write failure, no duplicate drain,
|
||||
shutdown cancellation, and enqueue-after-dispose.
|
||||
3. Add the post-diagnostics phase to `RenderFrameOrchestrator`, wire the
|
||||
controller through `FrameRootCompositionPhase`, and adopt its lifetime
|
||||
separately from the late UI binding. Pin exact phase order, skipped drain on
|
||||
prior failure, post-phase failure recovery, and partial-composition cleanup.
|
||||
4. Add the nine route commands. Parse/validate JSONL in the soak, attach the
|
||||
canonical checkpoints to the report, add zero-work and same-location owner
|
||||
gates, and preserve the old process/performance formulas verbatim.
|
||||
5. Run behavior/order, architecture/ownership, and adversarial failure review
|
||||
passes. Correct findings and re-run focused App tests, App Release, Release
|
||||
build, and the complete Release suite.
|
||||
6. Run two fresh-process connected nine-stop routes before closing #232. Both
|
||||
must exit gracefully, produce exact ordered checkpoints, pass canonical
|
||||
owner/cache gates, and pass the unchanged process residency guard.
|
||||
7. Reconcile architecture, roadmap, milestones, issues, AGENTS/CLAUDE, and
|
||||
durable memory; commit Checkpoint K as its own bisectable diagnostic unit.
|
||||
|
||||
## 5. Automated acceptance
|
||||
|
||||
- one checkpoint command creates one request, one sequence, and one artifact;
|
||||
- a pending token blocks all later script commands without duplicate enqueue;
|
||||
- render failure delays rather than loses or duplicates the request;
|
||||
- diagnostics publication precedes same-frame capture;
|
||||
- independent accepted requests drain FIFO and retain name/sequence identity;
|
||||
- capture/write failure reaches the script as the exact failed-token error;
|
||||
- shutdown reaches pending scripts as explicit cancellation;
|
||||
- no controller/runtime reference points back to `GameWindow`;
|
||||
- all nine route names and sequences are exact and appear in the soak report;
|
||||
- canonical pending/staging/warmup work is zero at every checkpoint;
|
||||
- Caul return → plateau deterministic owners/caches are unchanged, while
|
||||
authoritative workload deltas are explicitly warned;
|
||||
- working/private-memory thresholds and update/allocation formulas are
|
||||
unchanged;
|
||||
- focused, App, and complete Release suites pass with no new warnings.
|
||||
|
||||
No visual gate is required for K because it changes diagnostic observation
|
||||
only. Checkpoint L owns the final connected framebuffer and user visual gate.
|
||||
|
||||
## 6. Completion evidence
|
||||
|
||||
The focused checkpoint suite passes 74 tests. The Release build succeeds with
|
||||
only the 17 pre-existing test-project warnings tracked by #228. Two independent
|
||||
fresh-process routes produced all nine ordered render-frame checkpoints and
|
||||
closed gracefully:
|
||||
|
||||
- `connected-r6-soak-20260722-201335.report.json`: 403.139 seconds; Caul
|
||||
return-to-plateau working/private deltas +20.2/+10.3 MiB; update p95 1.5 ms.
|
||||
- `connected-r6-soak-20260722-202025.report.json`: 403.021 seconds; Caul
|
||||
return-to-plateau working/private deltas +165.0/+157.2 MiB; update p95 1.3 ms.
|
||||
|
||||
Every checkpoint had zero pending teardown/retirement, staged upload work, and
|
||||
composite warmup work. Neither run found unconfounded canonical owner growth,
|
||||
and both passed the unchanged process-residency and performance limits.
|
||||
|
||||
The first connected implementation run also proved that exact cache equality
|
||||
was the wrong leak predicate: visible landblocks and authoritative entities
|
||||
changed while mesh/GPU/cache owner counts decreased through legitimate
|
||||
retirement. The corrected contract therefore hard-fails owner *growth* when
|
||||
the workload is stable, reports named growth warnings when the authoritative
|
||||
or visible workload changed, and permits shrinking. It does not weaken the
|
||||
process limits or hide an identified owner leak.
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
# GameWindow Slice 8 Checkpoint L — campaign closeout
|
||||
|
||||
**Status:** Automated closeout complete 2026-07-22; final user visual matrix
|
||||
pending.
|
||||
**Parent:**
|
||||
[`2026-07-22-gamewindow-slice-8-composition-lifecycle.md`](2026-07-22-gamewindow-slice-8-composition-lifecycle.md),
|
||||
Checkpoint L.
|
||||
**Integrated baseline:** `6c5e0604`; Checkpoints A–K are implemented.
|
||||
`GameWindow.cs` is 1,622 raw lines, App Release passes 3,451 tests / 3
|
||||
intentional skips, and the complete Release suite passes 7,823 tests / 5
|
||||
intentional skips. Two fresh-process canonical nine-stop soaks pass with
|
||||
graceful exits and the unchanged residency/performance limits.
|
||||
**Behavior rule:** L proves and records the shipped ownership state. It may
|
||||
correct documentation, test/gate coverage, or a defect demonstrated by the
|
||||
audit, but it does not add gameplay, alter retail behavior, loosen a gate, or
|
||||
perform the separately deferred `GameEntity` aggregation.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Close the eight-slice thin-`GameWindow` campaign only after every exit
|
||||
criterion has current evidence:
|
||||
|
||||
- the host is a native construction/callback shell with no AC gameplay
|
||||
algorithm, entity scan, packet builder, DAT landblock builder,
|
||||
animation-part composer, or draw-graph body;
|
||||
- update, render, input, session, framebuffer, and shutdown callbacks are
|
||||
narrow typed handoffs;
|
||||
- startup and shutdown have one ordered, transactional, retryable ownership
|
||||
graph without a service locator or callback facade into the host;
|
||||
- extracted owners have focused tests and symmetric teardown;
|
||||
- the Release build/test, connected lifecycle/reconnect, canonical soak, and
|
||||
framebuffer gates preserve the accepted behavior;
|
||||
- architecture, roadmap, milestones, issues, divergence bookkeeping, durable
|
||||
memory, and both session instruction files agree with the code.
|
||||
|
||||
Line count is reported as a structural signal, not substituted for those
|
||||
ownership requirements.
|
||||
|
||||
## 2. Completion audit
|
||||
|
||||
### 2.1 Host boundary and dependency direction
|
||||
|
||||
Measure raw lines, fields, methods, and the remaining callback bodies from the
|
||||
actual source. Run the Slice 8 boundary/source-shape suites and inspect every
|
||||
remaining `GameWindow` method. Search for prohibited network packet assembly,
|
||||
entity/world scans, DAT build loops, part-transform composition, render graph
|
||||
bodies, direct environment reads, and anonymous device subscriptions. Any
|
||||
substantial body or callback into the host reopens the responsible checkpoint;
|
||||
it is not waived because the file is below the target size.
|
||||
|
||||
Verify the composed owners and their tests cover:
|
||||
|
||||
- ordered partial-load rollback and terminal session start;
|
||||
- exact frame-pair publication and one update/render handoff;
|
||||
- logical callback cutoff before session retirement and physical detach;
|
||||
- hard session/GPU shutdown barriers, soft physical-cleanup reporting,
|
||||
retry/no-replay progress, and native-window-last release;
|
||||
- deferred checkpoint acknowledgement, same-frame post-diagnostics capture,
|
||||
FIFO order, cancellation, and shutdown.
|
||||
|
||||
### 2.2 Three corrected-diff review passes
|
||||
|
||||
Review the complete Slice 8 diff and final source in three independent passes:
|
||||
|
||||
1. **Behavior/retail boundary:** no input, session, update/render, UI, camera,
|
||||
settings, teleport, or gameplay behavior was silently redesigned; every
|
||||
retained adaptation remains registered.
|
||||
2. **Architecture/ownership:** no duplicate canonical state, broad runtime bag,
|
||||
window back-reference, leaked subscription, conflicting disposer, or layer
|
||||
inversion; all ownership transfer and teardown paths are symmetric.
|
||||
3. **Adversarial failure:** partial acquisition at every phase, callback
|
||||
concurrency, repeated/reentrant close, failed detach, session/GPU barrier
|
||||
failure, deferred render failure, checkpoint cancellation, and process/GUID
|
||||
reuse cannot strand or replay work.
|
||||
|
||||
Confirmed findings are fixed at their owner and all affected gates are rerun.
|
||||
|
||||
## 3. Automated and connected gates
|
||||
|
||||
Run, in order:
|
||||
|
||||
1. focused Slice 8 composition, callback, settings, frame, lifetime,
|
||||
automation, and source-boundary tests;
|
||||
2. App Release;
|
||||
3. `dotnet build AcDream.slnx -c Release --no-restore`;
|
||||
4. the complete Release suite;
|
||||
5. `tools/run-connected-world-lifecycle-gate.ps1 -SkipBuild`, requiring the
|
||||
capped six-stop lifecycle, exact semantic checkpoints, five capped PNGs,
|
||||
code-zero authoritative graceful close, and immediate uncapped fresh-process
|
||||
reconnect with its sixth PNG;
|
||||
6. consume the two Checkpoint-K fresh-process nine-stop reports, requiring
|
||||
exact nine-name/sequence/process identity, zero pending teardown/retirement,
|
||||
staging and warmup work, no unconfounded canonical owner growth, unchanged
|
||||
process/update/allocation limits, and graceful exit;
|
||||
7. compare the six current lifecycle/reconnect PNGs with the accepted Slice 7
|
||||
`9d7df1bf` baseline. If the old generated artifacts are absent, reconstruct
|
||||
them from that exact commit in an isolated disposable worktree rather than
|
||||
inventing a new baseline. Accept only live weather/particle timing,
|
||||
authoritative vitals, and sub-frame camera-settling differences; geometry,
|
||||
UI/paperdoll layering, private viewports, alpha/depth, and world reveal must
|
||||
remain equivalent.
|
||||
|
||||
Generated logs and the isolated baseline worktree are evidence only and remain
|
||||
untracked. The user's unrelated `TransitionTypes.cs` edit remains untouched.
|
||||
|
||||
## 4. Reconciliation and handoff
|
||||
|
||||
After all nonvisual evidence is green:
|
||||
|
||||
- mark K and the automated portion of L in the parent ledger;
|
||||
- close #232 with both canonical soak reports and preserve #225, #153, #116,
|
||||
#228, TS-50/TS-51/TS-53, and other carried work at their true scope;
|
||||
- update `code-structure.md`, the architecture, roadmap, milestones,
|
||||
`AGENTS.md`, `CLAUDE.md`, and `memory/project_gamewindow_decomposition.md`
|
||||
with the same counts, commits, gates, next-work boundary, and do-not-retry
|
||||
lessons;
|
||||
- audit the retail divergence register and add/remove no row unless runtime
|
||||
behavior actually changed;
|
||||
- commit the evidence/doc closeout as a bisectable unit and rerun final
|
||||
source/status checks.
|
||||
|
||||
The sole remaining pause is the user's connected visual matrix: first login
|
||||
and radar; movement/mouse/resize/focus/combat; the shared inventory/skills/
|
||||
spellbook panel; outdoor/building/dungeon/portal/paperdoll/particle/alpha
|
||||
presentation; graceful close and fresh reconnect. Only that confirmation marks
|
||||
L and the overall structural campaign complete.
|
||||
|
||||
## 5. Automated closeout evidence
|
||||
|
||||
- The final host is 1,622 raw lines. Source-boundary inspection finds no AC
|
||||
algorithm, packet builder, entity scan, render graph, animation-part
|
||||
composer, or stored `GameWindow` back-reference in an extracted owner.
|
||||
- All 293 focused Slice 8 ownership, composition, callback, frame, lifetime,
|
||||
automation, and source-boundary tests pass. App Release passes 3,451 tests / 3
|
||||
intentional skips; the complete Release suite passes 7,823 / 5. The build's
|
||||
17 test-project warnings are the unchanged #228 set.
|
||||
- The connected lifecycle/reconnect gate passed in 314.4 seconds with graceful
|
||||
capped and uncapped exits. Its uncapped reconnect reached 174.74 FPS / 5.72
|
||||
ms at the final checkpoint. All six PNG artifacts are valid; the only route
|
||||
warning is the expected 25 world-edge empty-landblock misses.
|
||||
- Both Checkpoint-K nine-stop reports passed in about 403 seconds, produced the
|
||||
exact names/sequences/process identity, and found no unconfounded canonical
|
||||
owner growth under the unchanged residency/performance limits.
|
||||
- The six current lifecycle PNGs were compared against artifacts rebuilt from
|
||||
exact accepted Slice 7 commit `9d7df1bf` in an isolated worktree. The five
|
||||
deterministic destinations preserve geometry, UI/paperdoll layering,
|
||||
private viewports, depth/alpha, and world reveal. Facility Hub was nearly
|
||||
pixel-identical (mean absolute RGB delta 0.91/255); the Aerlinthe captures
|
||||
were 3.42 and 3.87/255. Holtburg and reconnect differences are live
|
||||
entities, particles, vitals, and sub-frame camera timing. Capped login used a
|
||||
different server-saved location and was checked only for complete rendering
|
||||
and presentation layering.
|
||||
- Three corrected-diff reviews are clean: behavior/retail boundary,
|
||||
architecture/ownership, and adversarial lifecycle/failure handling. The
|
||||
divergence audit found no new runtime behavior in K or L and requires no
|
||||
register change.
|
||||
|
||||
The generated reports, screenshots, and disposable baseline worktree are
|
||||
untracked evidence. The user's unrelated `TransitionTypes.cs` edit was not
|
||||
modified or staged.
|
||||
|
|
@ -1,792 +0,0 @@
|
|||
# GameWindow Slice 8 — composition and lifecycle shell
|
||||
|
||||
**Status:** Automated implementation and closeout complete 2026-07-22; final
|
||||
user visual matrix pending.
|
||||
**Parent program:** [`docs/architecture/code-structure.md`](../architecture/code-structure.md), Slice 8.
|
||||
**Baseline:** `96f8bfcf`; `GameWindow.cs` is 4,666 raw lines, 196 fields, and
|
||||
70 methods. The Release suite passes 7,341 tests / 5 fixture or environment
|
||||
skips. The connected lifecycle/reconnect and synchronized nine-stop soak gates
|
||||
pass.
|
||||
**Behavior rule:** This is the final behavior-preserving ownership slice. It
|
||||
must not change accepted input priority, session/reset behavior, update/render
|
||||
order, world presentation, quality settings, or retail gameplay behavior. It
|
||||
may fix resource and callback lifetime defects proven by this slice's ownership
|
||||
audit.
|
||||
|
||||
## Progress ledger
|
||||
|
||||
- [x] A — freeze construction/callback/shutdown order and delete proven dead or
|
||||
test-facade residue.
|
||||
- [x] B — make native-window callback intake an explicit reversible owner and
|
||||
define host-quiescence failure semantics.
|
||||
- [x] C — extract live-session host/reset/binding callbacks and verify the
|
||||
embedded skill formula against named retail.
|
||||
- [x] D — extract retail world-environment/day/weather behavior.
|
||||
- [x] E — extract two-phase raw pointer/camera input, focus, and framebuffer
|
||||
resize behind symmetric named subscriptions.
|
||||
- [x] F — extract the sole gameplay input-action router plus focused combat and
|
||||
diagnostic command owners.
|
||||
- [x] G — extract two-phase persisted settings/display/quality ownership.
|
||||
- [x] H — give terrain atlas, sky shader, retained `UiHost`, and both frame roots
|
||||
explicit single ownership and transfer seams.
|
||||
- [x] I — group `OnLoad` into small ordered, fakeable composition phases with
|
||||
transactional partial-acquisition rollback. Production and failure tests now
|
||||
invoke the same pipeline and carry exact typed prior results. Detailed plan:
|
||||
[`2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md`](2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md).
|
||||
- [x] J — move the exact retryable shutdown manifest to a focused lifetime
|
||||
owner and prove all partial-load/reentrant/retry paths. Detailed plan:
|
||||
[`2026-07-22-gamewindow-slice-8-checkpoint-j-lifetime-shutdown.md`](2026-07-22-gamewindow-slice-8-checkpoint-j-lifetime-shutdown.md).
|
||||
- [x] K — in separate #232 commits, add canonical owner snapshots to every soak
|
||||
checkpoint without weakening the process-memory guard. Detailed plan:
|
||||
[`2026-07-22-gamewindow-slice-8-checkpoint-k-canonical-soak.md`](2026-07-22-gamewindow-slice-8-checkpoint-k-canonical-soak.md).
|
||||
- [ ] L — corrected-diff reviews, focused and full Release gates, connected
|
||||
lifecycle/soak, framebuffer comparison, documentation, and memory are
|
||||
complete; only the final user visual handoff remains. Detailed plan:
|
||||
[`2026-07-22-gamewindow-slice-8-checkpoint-l-closeout.md`](2026-07-22-gamewindow-slice-8-checkpoint-l-closeout.md).
|
||||
|
||||
Checkpoint K leaves `GameWindow.cs` at 1,622 raw lines. The focused lifetime
|
||||
owner holds the typed shutdown roots, exact 18-stage dependency manifest,
|
||||
retry/no-replay state, structured soft-cleanup failures, hard-barrier fallback,
|
||||
and native-window-last release. Deferred same-frame checkpoint capture and two
|
||||
fresh-process nine-stop routes close #232 without weakening the residency
|
||||
guard. App Release passes 3,451 tests / 3 skips and the complete Release suite
|
||||
passes 7,823 / 5 skips. All automated Checkpoint-L gates are complete.
|
||||
|
||||
Each checked checkpoint lands as a bisectable commit. A checkpoint is not done
|
||||
while a new class delegates a substantial body back into `GameWindow`, stores
|
||||
`GameWindow`, or replaces 196 fields with one runtime/service-locator bag.
|
||||
|
||||
## 1. Outcome and non-goals
|
||||
|
||||
At slice exit `GameWindow` is the native construction shell:
|
||||
|
||||
```text
|
||||
GameWindow
|
||||
├── creates Window + GL + input context
|
||||
├── invokes ordered composition functions
|
||||
├── forwards tiny Silk callbacks to typed owners
|
||||
├── starts LiveSessionController last
|
||||
└── asks GameWindowLifetime to close synchronously
|
||||
```
|
||||
|
||||
The shell may retain injected Core/UI game-state roots whose replacement would
|
||||
be the separately deferred `GameEntity`/client-state aggregation migration. It
|
||||
must not retain AC algorithms, entity scans, session binding/reset bodies,
|
||||
settings algorithms, the input switch, anonymous device subscriptions, or the
|
||||
shutdown operation manifest.
|
||||
|
||||
This slice does **not**:
|
||||
|
||||
- alter the frozen twelve-phase update graph or ten-phase render graph;
|
||||
- port a new retail host loop or retire TS-53;
|
||||
- change input bindings, mouse feel, focus/reset semantics, camera formulas,
|
||||
resize/DPI behavior, frame pacing, VSync, MSAA, quality, draw distance, or
|
||||
resource budgets;
|
||||
- change live-session authority, packet parsing, reset order, the 35-second
|
||||
graceful F653 confirmation, reconnect, or transport teardown;
|
||||
- change UI layout, magic, portal presentation, selection, world rendering, or
|
||||
gameplay behavior;
|
||||
- fold full `GameEntity` aggregation, headless mode, Linux support, or GPU
|
||||
particle work into this campaign;
|
||||
- loosen the soak's working/private-memory threshold.
|
||||
|
||||
## 2. Frozen host behavior
|
||||
|
||||
No fresh retail research is required for the host boundaries. One audited
|
||||
exception exists: the embedded skill-formula helper currently cites ACE only,
|
||||
so Checkpoint C must complete the named-retail pseudocode/cross-reference/test
|
||||
workflow before moving or claiming that formula.
|
||||
|
||||
### 2.1 Startup and callbacks
|
||||
|
||||
Preserve this exact order:
|
||||
|
||||
1. before `Window.Create`, load persisted Display and resolve VSync/MSAA because
|
||||
these are native-context attributes;
|
||||
2. create the window and bind `DisplayFramePacingController` immediately;
|
||||
3. register Load, Update, main Render, pacing Render, Closing, FocusChanged,
|
||||
Move pacing, StateChanged pacing, and FramebufferResize in that order;
|
||||
4. in `OnLoad`, acquire GL and input from the native window;
|
||||
5. compose camera/DAT/effects/audio, persisted settings, optional devtools,
|
||||
world/render resources, retained UI, live presentation, streaming, session,
|
||||
teleport, then update/render roots;
|
||||
6. call `LiveSessionController.Start` last, including when live mode or
|
||||
credentials are absent, because its disabled path still performs the
|
||||
canonical reset.
|
||||
|
||||
Main Render remains registered before pacing so the wait occurs after all frame
|
||||
work and before Silk's swap. Direct raw MouseMove remains ordered after
|
||||
SilkKeyboard/MouseSource + InputDispatcher subscriptions and before retained
|
||||
`UiHost` device wiring.
|
||||
|
||||
### 2.2 Input, focus, and resize
|
||||
|
||||
Preserve the exact input-action priority:
|
||||
|
||||
1. pointer press/release transitions;
|
||||
2. scroll;
|
||||
3. combat press/release transitions;
|
||||
4. Press/DoubleClick gate;
|
||||
5. retained UI semantic actions;
|
||||
6. selection interactions;
|
||||
7. pressed movement/autorun;
|
||||
8. the remaining UI/debug/player/combat/escape commands.
|
||||
|
||||
Mouse capture remains three distinct operations:
|
||||
|
||||
- focus loss and camera-mode exit use lifecycle end and may publish a final
|
||||
movement update;
|
||||
- session reset uses reset semantics and cannot send into the ending session;
|
||||
- process close retires the session first, then releases presentation without
|
||||
an active session.
|
||||
|
||||
FramebufferResize continues to ignore non-positive sizes, then updates GL
|
||||
viewport, `ViewportAspectState`, camera aspect, and the forced devtools layout
|
||||
reset. It must not mutate retained `UiRoot` size; the next UI draw does that.
|
||||
|
||||
### 2.3 Session and reset
|
||||
|
||||
`LiveSessionController` remains the only session lifecycle owner. The extracted
|
||||
host adapter may hold typed binding/reset collaborators but no second session,
|
||||
generation, identity, GUID, routing, or command map.
|
||||
|
||||
Preserve `LiveSessionResetManifest` exactly: projection/capture/UI first;
|
||||
equipped projection before the canonical live runtime; live-runtime convergence
|
||||
before identity reset; pending effects/hooks/presentation last. The AC skill
|
||||
formula currently embedded in the window moves only after Checkpoint C adds its
|
||||
missing named-retail citation and conformance tests (or records the proven
|
||||
remaining adaptation).
|
||||
|
||||
### 2.4 Update and render
|
||||
|
||||
`OnUpdate` remains profiler scope plus one immutable
|
||||
`UpdateFrameOrchestrator.Tick`. `OnRender` takes the existing one window-size
|
||||
snapshot and calls one `RenderFrameOrchestrator.Render`. PView's
|
||||
`FramebufferSize` source remains distinct from the logical Window.Size used by
|
||||
the frame/UI/portal/screenshot path. TS-33 and TS-53 remain registered.
|
||||
|
||||
### 2.5 Shutdown
|
||||
|
||||
Closing synchronously completes the retryable transaction while the GL context
|
||||
is current. Direct `Dispose` is the constructed-never-run, Run-failure, and
|
||||
retry fallback. The native window is disposed only after successful completion
|
||||
or the explicitly reported last-resort incomplete fallback described below.
|
||||
|
||||
The frozen dependency order is:
|
||||
|
||||
1. set a no-throw host-quiescence gate and converge live-session shutdown as
|
||||
independent all-attempted **hard** operations; this guarantees F653 and
|
||||
transport teardown before dependents;
|
||||
2. attempt physical detachment of all window/Silk/UI ingress as explicitly
|
||||
reportable **soft** operations: retry them, record any persistent failure,
|
||||
but do not block now-safe owner/GPU teardown after quiescence;
|
||||
3. withdraw **both** update and render frame graphs through their shared slot;
|
||||
4. retire session/UI dependents;
|
||||
5. clear the live runtime while teardown callbacks remain alive;
|
||||
6. retire live dependents;
|
||||
7. wait for submitted GPU work;
|
||||
8. retire private/render frontends;
|
||||
9. retire shared textures;
|
||||
10. retire the mesh adapter;
|
||||
11. retire remaining render borrowers, including terrain renderer;
|
||||
12. retire separately owned borrowed assets, including terrain atlas and sky
|
||||
shader, only after their respective renderer stage converges;
|
||||
13. retire remaining render owners and frame-flight ownership;
|
||||
14. close DAT mappings;
|
||||
15. dispose the already-detached input context;
|
||||
16. dispose GL last;
|
||||
17. return to `GameWindow.Dispose`, which disposes the native window.
|
||||
|
||||
The logical quiescence gate makes callbacks inert even when a physical event
|
||||
unsubscribe reports failure. `ResourceShutdownTransaction` therefore gains an
|
||||
explicit non-blocking/reportable operation policy for physical detach only.
|
||||
Those failures remain in the final result and are never swallowed, but they do
|
||||
not strand unrelated resources. Session convergence and GPU drain remain hard
|
||||
barriers. Operations within a stage remain independent and all-attempted. Later
|
||||
stages remain protected until the current stage converges. Successful operations are
|
||||
never replayed on retry or reentrant completion.
|
||||
|
||||
Silk's `Closing` event is not cancellable. `OnClosing` therefore performs the
|
||||
first synchronous completion attempt and reports an incomplete transaction
|
||||
without pretending success. The outer `Dispose` retries the same retained
|
||||
transaction. If a persistent native/driver failure still prevents convergence,
|
||||
the native window/context is disposed as the explicit last-resort safety net
|
||||
and the incomplete result is logged with the blocked stage; it is never
|
||||
reported as a clean shutdown. Tests pin this fallback instead of assuming a
|
||||
close-cancellation mechanism Silk does not provide.
|
||||
|
||||
The lifetime state is explicit: `Active`, `RetryableIncomplete`, clean
|
||||
`Complete`, `CompleteWithCleanupFailures`, or terminal `AbandonedIncomplete`.
|
||||
Persistent soft-detach failure followed by otherwise complete teardown produces
|
||||
`CompleteWithCleanupFailures`; repeated Dispose is inert and returns the same
|
||||
immutable non-clean report. Hard-barrier retry is allowed only while the native
|
||||
context is retained. Last-resort native disposal moves to
|
||||
`AbandonedIncomplete`; every later Dispose is inert and reports the retained
|
||||
blocked-stage result without touching GL, callbacks, or the already-destroyed
|
||||
window.
|
||||
|
||||
## 3. Architecture and interfaces
|
||||
|
||||
### 3.1 Native window intake
|
||||
|
||||
Add a focused reversible binding equivalent to:
|
||||
|
||||
```csharp
|
||||
internal sealed class SilkWindowCallbackBinding : IDisposable
|
||||
{
|
||||
public static SilkWindowCallbackBinding Create(
|
||||
IWindow window,
|
||||
WindowCallbackTargets targets,
|
||||
DisplayFramePacingController pacing);
|
||||
|
||||
public void Attach();
|
||||
}
|
||||
```
|
||||
|
||||
`WindowCallbackTargets` is a fixed typed set for Load, Update, Render, Closing,
|
||||
FocusChanged, and FramebufferResize. It is not a general callback list. The
|
||||
binding is published into its lifetime slot after `Create` and before `Attach`
|
||||
begins, so even an attach failure plus rollback failure retains a cleanup owner.
|
||||
Attach treats the current edge as possibly acquired before calling a custom
|
||||
event add accessor and rolls back in reverse order if registration fails.
|
||||
The explicit lifecycle state monitor is released around every external event
|
||||
accessor; external cleanup waits for Attaching/Detaching to converge, while a
|
||||
gate-owned reentrant cleanup reports typed deferred completion instead of
|
||||
deadlocking or falsely succeeding. Dispose detaches in reverse registration
|
||||
order, joins concurrent callers, is idempotent/reentrant-safe, and makes later
|
||||
window events inert. `GameWindow` clears the retained slot only after terminal
|
||||
disposal. The separate pacing Render callback remains ordered after main Render.
|
||||
Every callback enters the shared no-throw host-quiescence monitor, so external
|
||||
shutdown drains an admitted callback, Closing can stop reentrantly, and a failed
|
||||
physical unsubscribe cannot re-enter a retired owner.
|
||||
|
||||
### 3.2 Input owners
|
||||
|
||||
Use two focused owners:
|
||||
|
||||
- `CameraPointerInputController` owns named raw MouseMove subscriptions,
|
||||
pointer position, camera-mode cursor transitions, focus loss, typed scroll
|
||||
operations, and the existing mode-specific sensitivities. Construction is
|
||||
deliberately two-phase: `AttachRaw` runs after dispatcher/source wiring and
|
||||
before retained `UiHost` wiring; `BindGameplayFrame` fills a focused deferred
|
||||
slot after `GameplayInputFrameController` exists. It consumes canonical
|
||||
`LocalPlayerModeState`, `ChaseCameraInputState`, input capture, and camera
|
||||
owners.
|
||||
- `GameplayInputActionRouter` is the **only** gameplay subscriber to
|
||||
`InputDispatcher.Fired` and owns the frozen priority graph above. Its pointer
|
||||
and scroll edges call typed operations on `CameraPointerInputController`; the
|
||||
pointer owner does not independently subscribe to `Fired`. The router calls existing typed
|
||||
retained-UI, selection, movement, player-mode, interaction, combat-command,
|
||||
and diagnostic-command owners.
|
||||
|
||||
The window binding, router Fired subscription, pointer raw MouseMove,
|
||||
Combat/Camera events, dispatcher-to-source links, source-to-Silk links, and
|
||||
retained-UI-to-Silk links must all be named and reversible. Shutdown's first
|
||||
stage deactivates them without disposing the input context or the UI owners
|
||||
needed by session reset. `UiHost` therefore exposes a separate idempotent input
|
||||
deactivation seam; its later full Dispose owns windows and rendering. Do not
|
||||
replace these edges with anonymous lambdas retained by a generic subscription
|
||||
bag.
|
||||
|
||||
Every wrapper on those edges—including Silk sources, dispatcher, raw pointer,
|
||||
retained UiHost, and optional devtools input—checks the same quiescence gate (or
|
||||
performs its no-throw logical deactivate in stage 1). No device callback can
|
||||
re-enter during the potentially 35-second live-session close while physical
|
||||
event removal waits for the following soft-detach stage.
|
||||
|
||||
Every multi-event Attach is transactional: if the Nth subscription fails, the
|
||||
already-attached prefix is removed in reverse order before the exception
|
||||
escapes. This applies to native window, dispatcher/source/Silk, raw pointer,
|
||||
Combat/Camera, retained `UiHost`, and optional devtools input bindings.
|
||||
|
||||
### 3.3 Session and world environment
|
||||
|
||||
- A focused App session host owns the current selection/entered/reset/binding
|
||||
factories around `LiveSessionLifecycleHost`, `LiveSessionEventRouter`, and
|
||||
`LiveSessionResetManifest`. It resolves the current session through
|
||||
`LiveSessionController`; it does not mirror it.
|
||||
- `WorldEnvironmentController` owns current loaded sky/day state,
|
||||
`AdminEnvirons`, provider refresh, WorldTime synchronization, and Weather
|
||||
changes. The existing retail day-group/weather algorithm moves verbatim.
|
||||
- A named skill-credit resolver owns the formula formerly nested in the session
|
||||
binding only after Checkpoint C verifies it against named retail; until then
|
||||
the current ACE-only interpretation is not overclaimed as retail-faithful.
|
||||
|
||||
### 3.4 Settings and diagnostics
|
||||
|
||||
`RuntimeSettingsController` owns SettingsStore, active toon key, persisted
|
||||
display/audio/gameplay/chat/character values, quality reapply, and UI
|
||||
lock/FPS/combat preferences. It is constructed before `Window.Create` and is
|
||||
the single source for one immutable startup snapshot and Display/VSync/MSAA.
|
||||
After GL/input sources/input dispatcher/camera/audio exist—but still before
|
||||
devtools, the world render dispatcher, terrain, retained UI, or streaming—it
|
||||
applies window/pacing/audio values and resolves the final quality snapshot.
|
||||
Later factories consume that resolved snapshot. A fixed
|
||||
typed `RuntimeSettingsTargets` implementation is late-bound only to support
|
||||
future runtime changes; binding it does not replay startup display or quality
|
||||
transitions. The controller exposes typed state/commands to existing consumers;
|
||||
it does not own those renderers, UI trees, or session state.
|
||||
|
||||
`RuntimeDiagnosticCommandController` owns command routing for collision wires,
|
||||
time/weather, sensitivity, and nearby-world dumps. State remains solely in its
|
||||
canonical owner: time/weather operations call `WorldEnvironmentController`,
|
||||
sensitivity calls `CameraPointerInputController.AdjustSensitivity`, and world
|
||||
queries use canonical runtime views. It creates no duplicate state or entity
|
||||
cache. Diagnostic algorithms do not remain in the window merely because their
|
||||
input actions originate there.
|
||||
|
||||
`FramebufferResizeController` is a separate GL-aware typed target. It owns the
|
||||
frozen viewport → `ViewportAspectState` → camera aspect → devtools layout-reset
|
||||
sequence and is not folded into the pointer owner.
|
||||
|
||||
### 3.5 Frame-root slots
|
||||
|
||||
A focused `GameFrameGraphSlot` is the sole publication point for the current
|
||||
`UpdateFrameOrchestrator` and `RenderFrameOrchestrator`. The two Silk stubs
|
||||
resolve through this slot; `GameWindowLifetime` withdraws it after session
|
||||
convergence and before borrowed owners retire. Clearing a lifetime snapshot is
|
||||
not withdrawal while a direct window field can still invoke the graph.
|
||||
|
||||
### 3.6 Ordered composition
|
||||
|
||||
`OnLoad` invokes the narrow production platform phase that owns
|
||||
`GL.GetApi(_window)` and `_window.CreateInput()`, then invokes small composition
|
||||
functions at these existing boundaries:
|
||||
|
||||
1. host input/camera;
|
||||
2. content/effects/audio;
|
||||
3. persisted settings/devtools;
|
||||
4. world/render resources;
|
||||
5. interactions/retained UI;
|
||||
6. live presentation/landblock publishers;
|
||||
7. streaming/session/local player/teleport;
|
||||
8. update/render roots;
|
||||
9. session start.
|
||||
|
||||
Each function receives only typed dependencies and may return a small immutable
|
||||
result needed by the next function. There is no stored mega-context. Stable
|
||||
owners are copied to locals before retained delegates are constructed so those
|
||||
delegates cannot capture `GameWindow` accidentally. Construction cycles use
|
||||
the existing focused deferred slots; they do not use callbacks into the window.
|
||||
|
||||
The production ordering lives in a fixed `GameWindowCompositionPipeline` with
|
||||
typed phase interfaces/factories. Tests invoke that same pipeline with fake
|
||||
platform/acquisition phases; no test-only clone or source-text assertion stands
|
||||
in for partial-load execution. A narrow production
|
||||
`GameWindowPlatformAcquisition` phase is invoked by `OnLoad` and alone calls
|
||||
`GL.GetApi(window)` and `window.CreateInput()` through injectable factories. It
|
||||
publishes GL to the lifetime before attempting input, then publishes input
|
||||
before later phases. Tests execute this exact method with fake factories,
|
||||
including GL success followed by input failure. Each concrete phase uses a
|
||||
transactional acquisition scope so failure after any internal acquisition
|
||||
unwinds the exact prefix.
|
||||
|
||||
Every acquired disposable is published to the lifetime owner immediately. A
|
||||
composer that cannot publish incrementally must unwind its own partial prefix
|
||||
before throwing. The portal-tunnel transfer remains explicit: publish fallback,
|
||||
prepare GL resources, transfer to `LocalPlayerTeleportController`, then clear
|
||||
the fallback before frame graphs are published.
|
||||
|
||||
Retained UI uses one `RetailUiRuntimeLease` retained by `GameWindowLifetime`
|
||||
from Host construction through final teardown. The lease initially owns
|
||||
`UiHost`, retains a partially constructed `RetailUiRuntime` before Initialize,
|
||||
and exposes the runtime as a borrower only after Mount succeeds. Constructor
|
||||
failure leaves Host in the lease; Initialize/cleanup failure leaves the exact
|
||||
partial runtime in the lease for later retry; success makes lease disposal call
|
||||
runtime disposal. There is no fallback-to-runtime ownership gap and never two
|
||||
independent host disposers.
|
||||
|
||||
### 3.7 Lifetime owner
|
||||
|
||||
`GameWindowLifetime` owns the one `ResourceShutdownTransaction` and typed
|
||||
teardown-only roots. It does not expose runtime service lookup or gameplay
|
||||
operations. It exists before `Window.Create`, so window/bootstrap acquisitions
|
||||
can publish their ownership immediately. Its transaction preserves §2.5 and
|
||||
fixes these audited gaps:
|
||||
|
||||
- update and render orchestrators are both withdrawn;
|
||||
- `TerrainAtlas` residency/textures are explicitly released after submitted
|
||||
work and before GL;
|
||||
- the dedicated sky `Shader` is explicitly disposed;
|
||||
- a published `UiHost` is disposed even if `RetailUiRuntime.Mount` fails;
|
||||
- window, dispatcher, pointer, combat, camera, and UI-root callbacks detach
|
||||
before the owners they target.
|
||||
|
||||
## 4. Detailed checkpoint execution
|
||||
|
||||
### A — freeze and prune
|
||||
|
||||
- Add structural freeze tests for the current startup/session-start, input,
|
||||
resize, update/render, and shutdown boundaries; later checkpoints replace
|
||||
these with functional owner tests rather than treating source checks as final
|
||||
acceptance.
|
||||
- Extend `ResourceShutdownTransactionTests` for multiple same-stage failures,
|
||||
failure then explicit retry, empty stages, and reentrant completion.
|
||||
- Delete only proven dead/test-facade residue: `_capturedMouse`, obsolete
|
||||
`_streamingRadius`, unused snap constants, `IsPlayerGuid`, `IsDoorName`, and
|
||||
test-only forwarding helpers. Tests call canonical owners directly.
|
||||
|
||||
Result: the exact native attributes/callbacks, input subscription priority,
|
||||
frame-root/session-start boundary, framebuffer behavior, shutdown stages, and
|
||||
native-window-last edge are frozen. Dead duplicate state and two test-only
|
||||
window facades are removed. Three corrected-diff reviews are clean; 56 focused
|
||||
tests and the complete App suite (2,991 pass / 3 intentional skips) pass.
|
||||
|
||||
### B — native window binding and host quiescence
|
||||
|
||||
- Implement exact attach/reverse-detach/rollback and the no-throw quiescence
|
||||
gate.
|
||||
- Replace direct `Run` event wiring with one owned binding while preserving the
|
||||
two Render handlers and their order.
|
||||
- Add post-detach silence, failure-after-Nth-attach, repeated/reentrant Dispose,
|
||||
callback-during-detach, and persistent physical-detach tests.
|
||||
|
||||
Result: one fixed typed binding now owns the exact nine Silk edges, including
|
||||
the two ordered Render callbacks. Create/publish/Attach and the explicit
|
||||
lifecycle state preserve cleanup ownership across partial event-accessor
|
||||
failure, rollback failure, concurrent or reentrant shutdown, and condition
|
||||
wakeups. The shared host gate drains admitted callbacks and makes failed
|
||||
physical detach logically inert. Three corrected-diff review loops are clean;
|
||||
54 focused tests, the App suite (3,027 pass / 3 intentional skips), and the full
|
||||
Release suite (7,386 pass / 5 intentional skips) pass. No connected gate was
|
||||
required because this checkpoint changes ownership only and preserves the
|
||||
frozen callback behavior.
|
||||
|
||||
### C — live-session host and skill formula
|
||||
|
||||
- Extract selection/entry/reset/binding factories around the existing canonical
|
||||
session, router, command, and reset owners.
|
||||
- Before moving the formula, write pseudocode for
|
||||
`SkillFormula::Calculate @ 0x00591960`, cross-check the DAT field semantics
|
||||
against ACE plus a second reference, then add conformance tests. If current
|
||||
behavior differs, register it rather than silently calling the ACE-only
|
||||
formula retail-faithful inside a structural commit.
|
||||
- Keep `LiveSessionController.Start` last; both disabled-live and
|
||||
missing-credential starts must execute the reset path.
|
||||
|
||||
Result: `LiveSessionHost` now owns reset-plan construction, exact selection and
|
||||
entered-world ordering, and create→attach route factories while resolving all
|
||||
session/command/in-world state through the sole `LiveSessionController`.
|
||||
Partially attached routes and every individual subscription edge retain failed
|
||||
cleanup work; successful edges are never replayed, and reset/new generations
|
||||
remain blocked until teardown converges. Named-retail research corrected the
|
||||
former ACE-only skill shortcut to exact unsigned `SkillFormula::Calculate @
|
||||
0x00591960` semantics. Three corrected-diff review loops are clean; focused
|
||||
session/formula/ledger tests, the App suite (3,048 pass / 3 intentional skips),
|
||||
Core.Net (548 pass), the Release build, and the full suite (7,408 pass / 5
|
||||
intentional skips) pass. No connected gate was required for this ownership
|
||||
checkpoint; the final connected lifecycle gate remains Checkpoint L.
|
||||
|
||||
### D — world environment
|
||||
|
||||
- Move loaded sky/day state, `RefreshSkyForCurrentDay`, `AdminEnvirons`,
|
||||
WorldTime synchronization, provider swaps, and Weather changes verbatim into
|
||||
`WorldEnvironmentController`.
|
||||
- Preserve existing named-retail citations and registered sound adaptation;
|
||||
diagnostics call typed commands rather than owning time/weather state.
|
||||
|
||||
Result: `WorldEnvironmentController` is now the sole owner of the clock,
|
||||
loaded sky descriptor, selected day group, Weather state, server time sync,
|
||||
AdminEnvirons bridge, and time/weather debug cycles. `GameWindow` preserves its
|
||||
public readonly clock/weather aliases but only composes the owner into render
|
||||
and session seams. Initialization is explicitly one-shot, and missing GameTime
|
||||
restores the documented fallback origin instead of inheriting process-global
|
||||
state. Named-oracle review corrected the day picker to
|
||||
`SkyDesc::CalcPresentDayGroup @ 0x00500E10` and AdminEnvirons to
|
||||
`CPlayerSystem::Handle_Admin__Environs @ 0x0055DE20`; TS-54/TS-55 now register
|
||||
the carried centered-audio and complete fog/ambient/radar gaps. Three
|
||||
corrected-diff reviews are clean; 17 focused tests, the App suite (3,059 pass /
|
||||
3 intentional skips), the warning-free Release build, and the full suite
|
||||
(7,419 pass / 5 intentional skips) pass. No connected gate was required for
|
||||
this behavior-preserving ownership checkpoint.
|
||||
|
||||
### E — pointer, focus, and framebuffer resize
|
||||
|
||||
- Make SilkKeyboard/Mouse sources and dispatcher links reversible, then attach
|
||||
the raw pointer owner in the existing pre-UiHost order.
|
||||
- Late-bind the gameplay-frame slot without resubscribing or reordering input.
|
||||
- Extract camera mode, lifecycle focus loss, scroll, and sensitivity state.
|
||||
- Extract the independent framebuffer resize target and pin logical Window.Size
|
||||
versus FramebufferSize behavior.
|
||||
- Require rollback after every Nth event add and silence between every shutdown
|
||||
stage for raw/retained/devtools device events.
|
||||
|
||||
Result: `CameraPointerInputController` now owns raw move, camera-mode cursor,
|
||||
focus-loss, scroll, and per-mode sensitivity policy; its gameplay-frame edge is
|
||||
late-bound without resubscription. Silk keyboard/mouse sources,
|
||||
`InputDispatcher`, retained UI bindings, and the devtools
|
||||
`QuiescentInputContext` are publish-before-attach, terminal after disposal,
|
||||
transactional on every side-effecting event add, and retry only the physical
|
||||
edges that remain pending. Mouse-only hosts retain camera/cursor behavior.
|
||||
`FramebufferResizeController` is the sole physical framebuffer publisher and
|
||||
seeds viewport/aspect/camera state from `FramebufferSize`, while logical UI and
|
||||
frame dimensions remain `Window.Size`. Shutdown now performs no-throw logical
|
||||
input cutoff, retires the live session, then removes physical callbacks; the
|
||||
post-session mouse-look release therefore cannot send a final movement packet,
|
||||
and a bad Silk remove accessor cannot block F653/transport teardown. TS-56
|
||||
records the carried non-retail camera input scalars. Three corrected-diff review
|
||||
loops are clean; 66 focused App tests, 9 dispatcher-lifetime tests, 5 Core input
|
||||
integration tests, the warning-free production Release build, and the complete
|
||||
Release suite (7,477 pass / 5 intentional skips) pass. `GameWindow` is 4,266
|
||||
raw lines / 194 fields / 65 methods. No connected gate was required for this
|
||||
behavior-preserving ownership checkpoint; Checkpoint L retains the final
|
||||
connected lifecycle and visual gates.
|
||||
|
||||
### F — gameplay action and command routing
|
||||
|
||||
- Move the frozen action-priority graph into the sole Fired subscriber.
|
||||
- Extract combat mode command behavior and diagnostic command routing to typed
|
||||
owners; retained UI, selection, movement, player mode, and item interaction
|
||||
remain canonical.
|
||||
- Make Combat, Camera, dispatcher, and retained-root subscriptions named,
|
||||
transactional, and symmetrically detached.
|
||||
|
||||
Result: `GameplayInputActionRouter` is the sole gameplay subscriber to
|
||||
`InputDispatcher.Fired` and preserves the frozen pointer → scroll → combat →
|
||||
Press/DoubleClick gate → retained UI → selection → movement → command
|
||||
priority. Focused command owners now route combat, diagnostics, window/player
|
||||
mode, item-target mode, and Escape without calling feature bodies on
|
||||
`GameWindow`. `RetainedUiGameplayBinding` owns the outside-UI item-drop edge;
|
||||
the combat/diagnostic deferred slots release their targets before session
|
||||
retirement, and the retained binding detaches transactionally afterward.
|
||||
Named-retail review
|
||||
also corrected `ToggleCombatMode @ 0x0056C8C0`: active combat short-circuits to
|
||||
peace without an equipment lookup, while an incompatible Held object prints
|
||||
the exact retail notice and sends no mode request. Three corrected-diff review
|
||||
loops are clean; 54 focused App tests, 20 Core combat tests, the App suite
|
||||
(3,155 pass / 3 intentional skips), the Release build, and the complete suite
|
||||
(7,524 pass / 5 intentional skips) pass. `GameWindow` is 4,057 raw lines / 198
|
||||
fields / 54 methods.
|
||||
|
||||
### G — two-phase runtime settings
|
||||
|
||||
- Construct `RuntimeSettingsController` before Window.Create and source startup
|
||||
Display/VSync/MSAA from it.
|
||||
- After GL/camera/audio and before devtools/world, apply startup
|
||||
display/pacing/audio and resolve the immutable quality snapshot consumed by
|
||||
downstream factories.
|
||||
- Late-bind typed runtime targets without replaying startup transitions, then
|
||||
move runtime display/quality changes, frame-rate/UI-lock/combat preferences,
|
||||
and toon-scoped state.
|
||||
- Preserve restart-required quality behavior and test transitions/previews
|
||||
without requiring GL.
|
||||
|
||||
**Implementation result:** one pre-window `RuntimeSettingsController` now owns
|
||||
the concrete settings store, the immutable startup snapshot, current Display /
|
||||
Audio / Gameplay / Chat / Character values, the active toon, resolved quality,
|
||||
and the optional Settings draft. Startup applies pacing, window state, saved
|
||||
FOV, and audio in order exactly once; a failed later substep retries without
|
||||
replaying a successful earlier substep. Complete runtime targets bind only
|
||||
after their borrowers exist and binding performs no replay. Display saves keep
|
||||
the persistence → live window → snapshot → quality order; quality applies
|
||||
alpha-to-coverage → anisotropy → render range → streaming → completion budget.
|
||||
Combat preference changes merge only their three fields into a live draft, so
|
||||
unrelated unsaved Gameplay edits survive. Saved FOV now applies without
|
||||
devtools, correcting the old accidental SettingsVM gate. `/framerate` retains
|
||||
the named-retail live toggle/notice and keeps acdream's shipped cross-launch
|
||||
persistence as documented AP-121. UI-lock convergence requires target,
|
||||
persistence, and Settings baseline publication; a failed substep keeps the
|
||||
same-value command retryable. `GameWindow` is 3,663 raw lines / 162 fields / 37
|
||||
methods. Thirty-five focused App settings/boundary tests, all 43
|
||||
`SettingsVMTests`, the UI Abstractions Release suite (534 pass), and the App
|
||||
Release suite (3,183 pass / 3 intentional skips) pass. All three independent
|
||||
corrected-diff reviews are clean; the production App Release build has zero
|
||||
warnings/errors, and the complete Release suite passes 7,553 tests / 5
|
||||
intentional skips (17 existing test-project warnings remain tracked by #228).
|
||||
|
||||
### H — explicit single resource ownership and frame slots
|
||||
|
||||
- Publish TerrainAtlas and the dedicated sky Shader as separate lifetime roots;
|
||||
renderers remain borrowers and retire before the sole atlas/shader disposal.
|
||||
- Publish `RetailUiRuntimeLease` before Host/Mount acquisition; add separate
|
||||
idempotent input deactivation and test constructor failure, Initialize failure
|
||||
with successful cleanup, transient cleanup followed by lifetime retry,
|
||||
persistent cleanup followed by terminal abandonment, and exactly one Host
|
||||
disposal/no unowned Host on every path.
|
||||
- Publish update/render roots only through `GameFrameGraphSlot` and withdraw the
|
||||
slot after session convergence.
|
||||
- Pin portal fallback/transfer unchanged and test every normal/partial prefix
|
||||
for no leak or double delete.
|
||||
- Make `Shader` construction and `TerrainAtlas.Build` internally exception-safe;
|
||||
phase scopes cannot recover GL names created inside a factory that throws.
|
||||
Inject GL compile/link/texture-allocation failure after every internal name
|
||||
and prove exact rollback/residency release.
|
||||
|
||||
Checkpoint H is complete. `GameRenderResourceLifetime` is the sole terrain-
|
||||
atlas and dedicated-sky-shader owner; renderers borrow and retire before those
|
||||
roots. `RetailUiRuntimeLease` retains the Host/runtime chain through partial
|
||||
construction, input cutoff, retryable disposal, and explicit terminal
|
||||
abandonment. `GameFrameGraphSlot` publishes update/render roots atomically, and
|
||||
the portal tunnel now uses one prepare-aware fallback/transfer transaction.
|
||||
Shader, terrain, text-renderer, buffer/VAO, bindless, and texture-binding
|
||||
construction/mutation paths use always-on checked GL commit boundaries. A
|
||||
failed cleanup retains the exact name/handle/binding obligation for retry, and
|
||||
successful substeps never replay. `GameWindow` is 3,689 raw lines / 162 fields
|
||||
/ 37 methods. The focused ownership gate passes 61 tests, the App Release suite
|
||||
passes 3,236 tests / 3 intentional skips, and all three independent final
|
||||
corrected-diff reviews are clean. The complete Release suite passes 7,606 tests
|
||||
/ 5 intentional skips; the 17 existing test-project warnings remain tracked by
|
||||
#228.
|
||||
|
||||
### I — ordered production composition
|
||||
|
||||
- Split the 2,289-line `OnLoad` body at §3.6 boundaries using the fixed typed
|
||||
production pipeline, small immutable phase results, and no stored mega-context.
|
||||
- Remove retained GameWindow captures via stable locals, typed adapters, and
|
||||
existing deferred slots.
|
||||
- Run the same pipeline with fake acquisition phases and inject failure after
|
||||
platform, GL, input, DAT/effects, render resources, retained UI, live/session,
|
||||
teleport transfer, and frame-root publication. Each concrete phase also
|
||||
proves transactional rollback of its internal acquisition prefix.
|
||||
|
||||
Result: all nine production phases now run through the same executable pipeline
|
||||
as the prefix/failure oracle. Platform and settings results are explicit typed
|
||||
inputs wherever consumed; phases reject mismatched instances, all long-lived
|
||||
owners publish to focused lifetime slots, the frame pair publishes atomically,
|
||||
and session start is terminal. `GameWindow.cs` is 1,945 raw lines. Three
|
||||
corrected-diff passes are clean; App Release passes 3,431 tests / 3 skips and
|
||||
the complete Release suite passes 7,803 tests / 5 skips with the existing 17
|
||||
#228 warnings. No connected gate was required for the final structural
|
||||
correction; Checkpoint L retains the connected lifecycle and soak gates.
|
||||
|
||||
### J — lifetime cutover and shutdown failure policy
|
||||
|
||||
- Move the teardown-only stage records and shutdown manifest out of GameWindow.
|
||||
- Co-stage no-throw quiescence and live-session convergence as hard operations;
|
||||
retry/report physical detach as non-blocking operations, withdraw both frame
|
||||
graphs next, then preserve the remaining frozen order.
|
||||
- Cover constructed-never-run, Load-never-fired, normal WM_CLOSE, WM_CLOSE then
|
||||
Dispose, direct/repeated/reentrant Dispose, transient/persistent failures,
|
||||
session deferred disposal, optional-devtools absence, GPU-drain failure, and
|
||||
every partial-load prefix.
|
||||
- Verify acquired owners dispose exactly once, unacquired owners never dispose,
|
||||
completed operations never replay, and native-window fallback is last and
|
||||
explicitly reported incomplete when a non-cancellable close cannot converge.
|
||||
- Verify persistent physical-detach failure still retires downstream owners and
|
||||
is reported, while persistent session/GPU barriers still protect dependents;
|
||||
after native fallback the terminal abandoned state makes repeated Dispose
|
||||
inert.
|
||||
|
||||
Result: `GameWindowLifetime` owns the typed root snapshot, 18-stage manifest,
|
||||
transaction progress, terminal report, and native window. Clean, soft-failure,
|
||||
hard-failure, native-failure, retry, reentrant, and never-run paths are pinned.
|
||||
The UI host remains explicitly rooted through failed physical detach until the
|
||||
native edge is gone; successful finalization releases the completed lifetime
|
||||
graph, while abandonment retains it. No shutdown stage remains in `GameWindow`,
|
||||
which is now 1,625 raw lines. App Release passes 3,441 tests / 3 skips and the
|
||||
complete Release suite passes 7,813 / 5 skips.
|
||||
|
||||
### K — canonical soak checkpoints (#232, separate commit)
|
||||
|
||||
- Extract `WorldLifecycleResourceSnapshotSource` from the window and reuse the
|
||||
existing render diagnostics source where fields overlap.
|
||||
- Change `checkpoint <name>` from an immediate retained-UI-time capture to a
|
||||
FIFO request with an acknowledgement token. The script runner treats that
|
||||
token as a command barrier: it executes no later command until the token is
|
||||
resolved. After private presentation returns and
|
||||
`RenderFrameDiagnosticsController.Publish` has published the current frame,
|
||||
a post-diagnostics checkpoint phase drains requests using that same immutable
|
||||
`RenderFrameOutcome` plus one sample from every canonical source. This avoids
|
||||
previous-frame diagnostics mixed with current-frame owner counts.
|
||||
- Mark the token Succeeded only after serialization/write completes. Deferred
|
||||
capture/write failure marks it Failed, and the runner stops with that exact
|
||||
error on its next tick. Preserve request sequence/name across the deferred
|
||||
edge, drain independent producers in FIFO order, and cancel pending tokens
|
||||
with an explicit shutdown result.
|
||||
- Store one token against the active script-command index. Repeated pending
|
||||
ticks poll that token and never enqueue again—even when a render failed before
|
||||
the post-diagnostics drain. Clear it only after Success, Failure, or
|
||||
Cancellation is consumed; one command therefore produces one request, write,
|
||||
and sequence increment.
|
||||
- Add exactly nine ordered named `checkpoint` commands, parse the JSONL into the
|
||||
soak report, and assert checkpoint identity/order.
|
||||
- Gate pending teardown, retirement, staging, and warmup at zero; compare exact
|
||||
canonical owner/cache counts and bytes at Caul return → Caul plateau; label
|
||||
authoritative entity/animation population changes as workload warnings.
|
||||
- Preserve process working/private memory as a separately labelled secondary
|
||||
residency guard with unchanged thresholds. Require two fresh-process clean
|
||||
route runs before closing #232.
|
||||
|
||||
Result: deferred acknowledged checkpoints now drain after current-frame render
|
||||
diagnostics, persist the exact frame outcome plus canonical resource owners,
|
||||
and block the script until both artifacts commit. Seventy-four focused tests,
|
||||
App Release, the full Release suite, and two 403-second fresh-process routes
|
||||
pass. Every route produced the exact nine checkpoints, zero pending/staged/
|
||||
warmup work, no unconfounded canonical owner growth, graceful exit, and the
|
||||
unchanged process/performance limits. Commits: `bca41487`, `6c5e0604`.
|
||||
|
||||
### L — final closeout
|
||||
|
||||
- Run three independent corrected-diff reviews: retail conformance,
|
||||
architecture/integration, and adversarial lifecycle/failure analysis.
|
||||
- Resolve every confirmed finding and re-review until clean.
|
||||
- Run focused App/Core/UI tests, Release build, and the complete Release suite.
|
||||
- Run connected lifecycle/reconnect and two fresh-process synchronized
|
||||
nine-stop soaks; compare stable framebuffer checkpoints with Slice 7.
|
||||
- Audit every active divergence row that still points at `GameWindow`; retarget
|
||||
ownership paths only. Keep TS-53 and other unresolved mechanisms open.
|
||||
- Update architecture, milestones, roadmap, issues, AGENTS/CLAUDE, and durable
|
||||
memory with final metrics and exact gates.
|
||||
|
||||
Automated result: 293 focused tests and the 7,823/5 complete Release suite
|
||||
pass. The 314.4-second capped/reconnect lifecycle route passes with six valid
|
||||
PNGs and graceful exits. Its current deterministic frames preserve Slice 7
|
||||
commit `9d7df1bf` geometry, UI/paperdoll layering, private viewports,
|
||||
depth/alpha, and reveal; live entity/particle/vitals/camera timing account for
|
||||
the accepted differences. All three final corrected-diff reviews are clean.
|
||||
The user's connected visual matrix is the sole remaining acceptance item.
|
||||
|
||||
## 5. Automated acceptance
|
||||
|
||||
- exact window callback attach order, reverse detach, rollback, reentrancy, and
|
||||
post-detach silence;
|
||||
- transactional rollback after every Nth add for all multi-event bindings, plus
|
||||
retained/devtools/raw device silence between every shutdown stage;
|
||||
- exact input subscription/priority order and focus/reset/close semantics;
|
||||
- exact resize order and non-positive-size rejection;
|
||||
- disabled-live and missing-credentials startup still perform reset; live
|
||||
startup remains last;
|
||||
- PView still reads FramebufferSize while RenderFrameInput, retained UI,
|
||||
portal/private presentation, and screenshots share one Window.Size snapshot;
|
||||
- exact session reset trace and current-session borrowing;
|
||||
- partial-load rollback at every composition checkpoint;
|
||||
- TerrainAtlas, sky Shader, UiHost, frame graphs, GPU-flight, DAT, input, GL,
|
||||
and native window have explicit tested lifetimes; terrain/sky renderers remain
|
||||
borrowers and cannot also dispose the separately owned atlas/shader;
|
||||
- throwing Shader/TerrainAtlas leaf factories release every internal GL name
|
||||
and bindless-residency prefix;
|
||||
- shutdown retry/all-attempted/no-replay semantics remain intact;
|
||||
- persistent non-cancellable close fallback names the blocked stage and never
|
||||
reports a clean shutdown;
|
||||
- completed teardown with soft-detach failures reports immutable
|
||||
`CompleteWithCleanupFailures`, and repeated Dispose is inert;
|
||||
- no anonymous MouseMove subscription and no substantial Silk callback body;
|
||||
- no stored `GameWindow`, broad service locator, or feature-owner callback
|
||||
facade into the window; the fixed native `WindowCallbackTargets` binding is
|
||||
the intentional host-boundary exception;
|
||||
- `OnLoad` is ordered composition, `OnUpdate`/`OnRender` remain one handoff,
|
||||
and `OnClosing`/`Dispose` delegate to one lifetime owner;
|
||||
- all canonical soak snapshots are post-diagnostics same-frame atomic,
|
||||
round-trip, preserve request/name order across the deferred edge and nine-name
|
||||
order across two fresh processes, and name the exact owner/property on growth;
|
||||
- checkpoint acknowledgement blocks following script commands, propagates
|
||||
deferred write failure, and reports shutdown cancellation;
|
||||
- repeated pending ticks enqueue exactly once and cannot duplicate a checkpoint
|
||||
after a failed/delayed render;
|
||||
- complete Release suite, lifecycle gate, soak gate, and framebuffer comparison
|
||||
pass.
|
||||
|
||||
## 6. Final visual handoff
|
||||
|
||||
The only user pause is one connected visual smoke gate after all automation is
|
||||
green:
|
||||
|
||||
- first login world bootstrap and radar;
|
||||
- movement, mouse look/orbit, resize, focus loss/return, combat toggle;
|
||||
- inventory/skills/spellbook shared panel and retained UI input;
|
||||
- outdoor, building, dungeon, portal/recall, paperdoll, particles, alpha;
|
||||
- graceful close and fresh-process reconnect.
|
||||
|
||||
After that pass the GameWindow structural campaign is complete. The roadmap
|
||||
returns to the carried M3 magic/spell-bar/spellbook/component-book and final
|
||||
two-client portal-out/materialization visual gates before new M4 feature work.
|
||||
|
|
@ -1,764 +0,0 @@
|
|||
# World interaction completion — pre-M4 program
|
||||
|
||||
**Status:** Slice 1 user-accepted 2026-07-23. Slice 2 Use-hand selection,
|
||||
zero-useability, carried direct-use, AutoWear correction, and the requested
|
||||
shared item-cooldown follow-up passed their connected gates. Slice 3's first
|
||||
connected gate exposed an incorrect IdentifyResponse flag table, a non-retail
|
||||
shared-panel mount, and its missing inscription transaction. All three are
|
||||
corrected and live-confirmed. The creature-page follow-up now has retail's
|
||||
ordered stat rows and animated private preview. The follow-up item report and
|
||||
authored 310 x 400 layout correction are implemented and user-accepted.
|
||||
Favorite-spell press-time selection and right-click local SpellPanel
|
||||
examination are implemented and user-accepted. The follow-up maps
|
||||
component-disabled ACE characters to the modern scarab/prismatic formula,
|
||||
resolves formula icons by their DAT icon DIDs, installs those icons as each
|
||||
authored template root's own foreground image, and migrates stale examination
|
||||
dimensions once to the authored 310 x 400 extent. The final connected
|
||||
assessment gate passed on 2026-07-24. Slice 4 equipped-child world picking
|
||||
(with the Opus F1 wielded-pickup-legality correction) passed its connected
|
||||
visual gate on Coldeve and was user-accepted 2026-07-29. Slices 1–4 are
|
||||
complete; resume at Slice 5, vendor browsing.
|
||||
**Milestone:** M4 prerequisite/preamble.
|
||||
**Architecture:** retained gameplay UI over shared selection, object, and
|
||||
interaction state. `GameWindow` remains a composition/callback shell.
|
||||
|
||||
## Outcome
|
||||
|
||||
Close the remaining retail interaction surfaces before the larger M4
|
||||
quest/emote/character-creation bodies begin:
|
||||
|
||||
1. Favorite spell bars expose their DAT-authored overflow arrows and scroll
|
||||
through every server-persisted favorite.
|
||||
2. The status bar's hand and magnifying-glass controls invoke the same Use and
|
||||
Assess commands as their keyboard paths.
|
||||
3. Assessing a creature, player, NPC, or object opens its retail information in
|
||||
the independent movable/resizable retail floaty examination window.
|
||||
4. World picking can resolve visible equipped children, such as a character's
|
||||
wielded weapon, while selection markers remain anchored to the picked child.
|
||||
5. Vendor use opens the authored vendor surface, publishes its inventory, and
|
||||
supports retail selection/browsing.
|
||||
6. Vendor buy/sell transactions, quantities, pending-state ownership, and
|
||||
authoritative inventory reconciliation complete the loop.
|
||||
|
||||
The program reuses the existing retained-window host, `SelectionState`,
|
||||
`ClientObjectTable`, interaction transaction owner, and server-authoritative
|
||||
inventory updates. It does not create parallel panel positions, item tables,
|
||||
selection state, or optimistic inventory outcomes.
|
||||
|
||||
## Ordered slices
|
||||
|
||||
| Slice | Deliverable | Principal owner |
|
||||
|---|---|---|
|
||||
| 1 | Spell-bar overflow arrows | `SpellcastingUiController` + generic retained scrollbar/list |
|
||||
| 2 | Status Use/Assess commands | focused status-bar controller binding to the existing action router |
|
||||
| 3 | Assessment information panel | retained controller in an independent floaty examination window |
|
||||
| 4 | Equipped-child world picking | pure world-query/picking policy plus presentation anchor |
|
||||
| 5 | Vendor browse lifecycle | vendor session/controller plus authored panel |
|
||||
| 6 | Vendor transactions | server-authoritative buy/sell command and reconciliation owner |
|
||||
|
||||
Each slice begins with named-retail research, produces pseudocode and
|
||||
conformance tests, updates the divergence register if required, and lands as a
|
||||
separate bisectable commit. A visual gate follows each UI-bearing slice.
|
||||
|
||||
Slice 1 now imports both 23-pixel arrow buttons, their rollover/pressed media,
|
||||
and HideDisabled from the real combat fixture; places their artwork by the
|
||||
authored leading/trailing positions; shares the list's single pixel-scroll
|
||||
model; advances one 32-pixel cell per press; and exposes only actively selected
|
||||
spells. Passive object/endowment refresh preserves a manual offset. The mixed
|
||||
610/800-pixel DAT anchor chain is solved to a fixed 747-pixel combat root,
|
||||
producing exactly 18 visible 32-pixel favorite cells (nine numbered plus nine
|
||||
unnumbered) without consuming the overflow. The initial 57-test/full-suite gate passed;
|
||||
the corrective 104-test focus set, 3,472 App tests / 3 skips, Release solution
|
||||
build, and 7,845 complete-solution tests / 5 skips pass. The corrected
|
||||
18-cell bar and both overflow directions passed the connected gate.
|
||||
|
||||
Slice 2 now drives the toolbar hand from canonical `SelectionState` changes and
|
||||
live `ClientObjectTable` updates. The pure Core predicate ports
|
||||
`gmToolbarUI::HandleSelectionChanged`: combat-use items, armor/clothing/jewelry,
|
||||
and `ItemUses` values without `USEABLE_NO` remain active. Clicking still enters
|
||||
the existing `ItemInteractionController` command path, so weapons use the
|
||||
server-confirmed AutoWield transaction and targeted tools enter the existing
|
||||
use-on-target cursor. Empty selection and explicitly unusable spell components
|
||||
are ghosted and ignore clicks. Empty-selection ghosting is the user's explicit
|
||||
choice over retail's generic `TARGET_MODE_USE` entry and is registered as
|
||||
AP-122. The 14-test Core interaction focus, 49-test toolbar focus, Release
|
||||
solution build, 3,474 App tests / 3 skips, and 7,848 complete-solution tests /
|
||||
5 skips pass.
|
||||
|
||||
The connected selection-state gate passed. Its follow-up exposed three deeper
|
||||
shared-policy/delivery defects, now corrected from named retail plus the
|
||||
matching binary. `ItemUses::IsUseable @ 0x004FCCC0` tests only `USEABLE_NO`;
|
||||
reset/absent value zero is usable. `ItemHolder::UseObject @ 0x00588A80` sends a
|
||||
Use event immediately for both owned and world objects; acdream had
|
||||
incorrectly routed the packet through a local approach lookup, where
|
||||
Blackmoor's Favor has no spatial entity and was silently cancelled. Every
|
||||
ordinary Use now sends once and ACE owns any authoritative MoveTo chain.
|
||||
AD-27 remains only for client-side pickup completion. AutoWear applies
|
||||
`CPlayerSystem::AutoWearIsLegal @ 0x0055EF40` through the same double-click and
|
||||
toolbar-hand path, resolves the overlapping worn object from the
|
||||
retail-ordered equipment projection, and emits the exact system line
|
||||
`You must remove your <item> to wear that`. Research:
|
||||
[`../research/2026-07-23-retail-item-use-and-autowear-pseudocode.md`](../research/2026-07-23-retail-item-use-and-autowear-pseudocode.md).
|
||||
The focused 25-test Core and 85-test App sets pass, as do the warning-free
|
||||
Release solution build, 3,476 App tests / 3 skips, and 7,857 complete-solution
|
||||
tests / 5 skips. The carried-use delivery correction adds an end-to-end App
|
||||
pin from Favor activation through wire dispatch and authoritative UseDone busy
|
||||
release. The Release build retains the 17 tracked test-project warnings;
|
||||
3,477 App tests / 3 skips and 7,858 complete-solution tests / 5 skips pass.
|
||||
The user confirmed Blackmoor's Favor now activates correctly.
|
||||
|
||||
Before Assess, the user requested the adjacent retail item-cooldown
|
||||
presentation. `PublicWeenieDesc` now preserves the optional shared cooldown id
|
||||
and duration, assessed property updates reach the same object fields, and
|
||||
`CEnchantmentRegistry::OnCooldown @ 0x005943C0` plus
|
||||
`UIElement_UIItem::UpdateCooldownDisplay @ 0x004E1E20` are ported through the
|
||||
canonical Core registry and one pure display projection. Every retained item
|
||||
list shares one controller and the exact
|
||||
ten DAT-authored radial sprites `0x060067CF..0x060067D8`; items with the same
|
||||
group display the same server-authored cooldown. Research:
|
||||
[`../research/2026-07-23-retail-item-cooldown-pseudocode.md`](../research/2026-07-23-retail-item-cooldown-pseudocode.md).
|
||||
The focused parser/Core/UI/production-DAT tests, warning-free Release solution
|
||||
build, 3,482 App tests / 3 skips, and 7,875 complete-solution tests / 5 skips
|
||||
pass. AP-123 records only the retained toolkit's procedural-child adaptation;
|
||||
the visible assets, ordering, timing, and shared-group behavior are exact.
|
||||
The user accepted the live cooldown presentation 2026-07-23.
|
||||
|
||||
Slice 3 preserves the existing toolbar/keyboard Assess command and ports the
|
||||
retail response owner around it. One shared UI-busy reference covers the
|
||||
latest pending GUID, stale replies are rejected, the accepted reply becomes
|
||||
the current appraisal, and combat-time creature/player refreshes occur every
|
||||
0.75 seconds only while the examination window is visible. Complete
|
||||
`IdentifyObjectResponse` parsing now uses ACE/retail's exact flag values and
|
||||
positional order, includes the three-word HookProfile, and rejects truncated
|
||||
gated payloads rather than continuing from a corrupt cursor. The first live
|
||||
monster gate found that `0x0100` had been mislabeled WeaponProfile; the parser
|
||||
dropped every creature response before response acceptance, leaving the one
|
||||
busy reference held. A literal ACE `0x0100` creature fixture now protects that
|
||||
packet-to-controller path.
|
||||
|
||||
LayoutDesc `0x2100006B`, root `0x100005F2`, supplies the complete 310 x 400
|
||||
floaty chrome, title, item/creature/character subviews, close control, and
|
||||
authored scrollbars. `gmFloatyExaminationUI` is an independent top-level
|
||||
window, so Inventory/Skills/Spellbook no longer replace it or inherit its
|
||||
geometry. The imported multiline inscription field now ports
|
||||
`ItemExamineUI`'s public/hook inscribability, inventory ownership and
|
||||
same-scribe permission rules, placeholder/signature presentation, exact
|
||||
failure notices, focus commit behavior, and CP-1252 `SetInscription (0x00BF)`
|
||||
transaction. Basic item and player reports are live. The creature page now
|
||||
resolves its type through retail EnumMapper `0x2200000E`, preserves the
|
||||
authored Character/Level header, creates the exact nine stat rows from
|
||||
template `0x10000166`, and renders a fixed-heading animated clone through a
|
||||
private viewport using retail's bounding-box camera and distant light.
|
||||
The visual-gate correction also ports the separate `0x10000335`
|
||||
damage/critical/resistance rating list, places authored row chrome behind the
|
||||
animated preview and text in front, adds the balanced row inset, and follows
|
||||
the current selection automatically while the examination window is visible.
|
||||
Item-object preview, specialized item/character detail regions, and exact
|
||||
creature appraisal font-state selection remain the narrowed AP-110 residual.
|
||||
The item report now retains `PublicWeenieDesc` hook identity, applies
|
||||
appraisal-only Value/Burden unknowns, suppresses mounted-hook sentinel
|
||||
capacities, preserves retail line/paragraph boundaries, and selects the
|
||||
authored white/green/red item font-color entries. Research:
|
||||
[`../research/2026-07-23-retail-appraisal-ui-pseudocode.md`](../research/2026-07-23-retail-appraisal-ui-pseudocode.md).
|
||||
The focused parser/router/request/controller/fixture tests pass, as do the
|
||||
Release solution build with 17 pre-existing tracked test warnings, 3,496 App
|
||||
tests / 3 skips, and 7,913 complete-solution tests / 5 skips.
|
||||
|
||||
The creature presentation follow-up adds real-DAT row-template and EnumMapper
|
||||
fixtures plus pure conformance coverage for row order/formatting, enchantment
|
||||
semantics, failed assessments, stable clone identity, live animated mesh
|
||||
updates, hydrated mesh bounds, and retail camera fitting. The warning-free App code,
|
||||
Release solution build, 3,506 App tests / 3 skips, and 7,923 complete-solution
|
||||
tests / 5 skips pass. The connected visual result remains the closeout gate.
|
||||
|
||||
The rating/layering/selection-follow correction adds 18 focused green tests,
|
||||
passes the Release solution build with the 17 warnings already tracked by
|
||||
#228, 3,510 App tests / 3 skips, and 7,927 complete-solution tests / 5 skips.
|
||||
Its corrected connected visual result remains the closeout gate.
|
||||
|
||||
The item-report/layout correction restores the authored 310 x 400 examination
|
||||
size for the connected profile, leaves retail's ordinary resize range intact,
|
||||
keeps row chrome at its LayoutDesc origin while insetting only foreground
|
||||
creature text, and starts generated item prose at the top of its authored
|
||||
scroll surface. `ItemAppraisalTextFormatter` now owns the decomp-ordered item
|
||||
projection outside `GameWindow` and outside the examination controller. It
|
||||
ports common weapon damage ranges/speed/range/ammunition, armor protection
|
||||
bands, defense/caster modifiers, workmanship, ratings, wield/use/activation
|
||||
requirements, item XP/capacity/lock/mana/uses/creator/rare data, cooldown and
|
||||
imbued special properties, and both short spell lists and full DAT spell
|
||||
descriptions. The conformance follow-up replaces public-value fallbacks with
|
||||
retail's appraisal presence semantics, restores hook/capacity/lock behavior,
|
||||
and carries each `AddItemInfo` fragment's paragraph and font-color index into
|
||||
the retained text shaper. AP-110 now names only the remaining specialized,
|
||||
player-dependent, DAT-display-name, creature-font-state, and object-preview gaps.
|
||||
Focused conformance fixtures cover the geometry layering plus melee, launcher,
|
||||
armor, spell, and special-property reports. The Release solution build passes
|
||||
with the 17 warnings already tracked by #228, 3,514 App tests / 3 skips, and
|
||||
7,931 complete-solution tests / 5 skips. The connected visual result remains
|
||||
the historical pre-acceptance checkpoint; the final gate passed 2026-07-24.
|
||||
|
||||
The item-format conformance correction adds literal hook-tail cursor fixtures,
|
||||
wire/session/object-table propagation tests, the exact Black Phyntos Hive
|
||||
report, capacity/page/lock presence cases, structured paragraph checks, and
|
||||
LayoutDesc color-palette/enchantment-style coverage. The isolated Release
|
||||
solution build passes with the same 17 tracked warnings; 3,522 App tests / 3
|
||||
skips and 7,942 complete-solution tests / 5 skips pass while the previously
|
||||
launched client retains the normal Release output lock. The corrected
|
||||
connected item visual was accepted 2026-07-24.
|
||||
|
||||
The exhaustive item-report correction replaces the remaining generic/numeric
|
||||
approximations with the complete named-retail dispatch. Equipment sets use the
|
||||
literal EoR table; ratings, tinkering/salvage averages, coverage, failed weapon
|
||||
unknowns, level restrictions, all three item-XP curves, activation heritage,
|
||||
healer/ordinary boost behavior, rare timers, and magic `~ Name: Description`
|
||||
rows now preserve retail wording and ordering. Description construction ports
|
||||
lifespan prose, workmanship/material/gem decoration and portal/PK restriction
|
||||
bits. `RetailAppraisalNameResolver` follows retail's master
|
||||
`EnumIDMap -> sub-enum 1 DualEnumIDMap` material chain and shares the installed
|
||||
creature mapper for slayers and wield requirements; a production-DAT test pins
|
||||
Ruby and Ghost. AP-110 is narrowed to item preview and the projections that
|
||||
need live player/localization state (effective shield, cooldown remaining, and
|
||||
augmentation-cost `StringInfo`), plus character and creature-font residuals.
|
||||
The focused item formatter suite passes 21/21, App Release passes 3,531 tests /
|
||||
3 skips, and the complete Release solution passes 7,952 tests / 5 skips. The
|
||||
connected item-report comparison passed on 2026-07-24.
|
||||
|
||||
The material-title and section-boundary correction carries
|
||||
`PublicWeenieDesc.MaterialType` from CreateObject through the canonical object
|
||||
table and resolves `ACCWeenieObject::GetObjectName(NAME_APPROPRIATE)` through
|
||||
the installed DAT material map. Examination titles now produce names such as
|
||||
`Reed Shark Hide Steel Toed Boots` without duplicating a material already
|
||||
present in the authored base name. Empty `AddItemInfo` calls are retained as
|
||||
real report fragments, restoring retail's intentional blank rows after
|
||||
workmanship, before armor level, around rating/special-property blocks, and at
|
||||
the later use/item-level boundaries. Focused wire, projection, object-table,
|
||||
title, and boots-layout fixtures protect the full path. With the subsequent
|
||||
right-click and press-time retained-item input ports, the Release solution
|
||||
build and 3,548 App tests / 3 skips plus 7,979 complete-solution tests /
|
||||
5 skips pass.
|
||||
|
||||
The world right-click follow-up ports
|
||||
`UIElement_SmartBoxWrapper::MouseUp @ 0x004E5820` and the
|
||||
`sr_Examine` branch of `RecvNotice_SmartBoxObjectFound @ 0x004E5AD0`.
|
||||
The configurable `SelectRight` binding now completes on release, cancels when
|
||||
pointer travel crosses the retail-observed three-pixel drag threshold, and
|
||||
routes through the existing world picker, lighting pulse, canonical
|
||||
`SelectionState`, and appraisal request owner. Empty world space remains a
|
||||
no-op, right-drag camera orbit does not appraise its release point, and the
|
||||
independent configurable `SelectionExamine` action now reaches the same
|
||||
request/target-mode path.
|
||||
The retained follow-up ports the separate
|
||||
`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50` branch: an occupied
|
||||
backpack, side-bag, loot, paperdoll-slot, or physical toolbar cell now selects
|
||||
its item and enters that same appraisal owner on a completed right-click.
|
||||
Right-button movement cancels the click and can never begin an item drag.
|
||||
The input-latency follow-up ports `UIElement_ListBox::MouseDown @ 0x0046E3A0`
|
||||
and the physical left-click branch of
|
||||
`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50`. Canonical selection
|
||||
and the retained green frame update during left-button down, before the
|
||||
three-pixel drag threshold. Target mode is offered first. Opening, using,
|
||||
equipping, looting, and shortcut activation remain completed-click or
|
||||
double-click actions and are suppressed when target mode consumed the press.
|
||||
Favorite spells now use the parallel non-weenie path:
|
||||
`gmSpellcastingUI::ListenToElementMessage @ 0x004C7AB0` selects the favorite
|
||||
on left press, while the spell branch of
|
||||
`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50` opens the authored
|
||||
SpellPanel locally on right-click. Spell IDs never enter `SelectionState` or
|
||||
the status-bar magnifier path, and no Appraise/busy transaction is invented.
|
||||
The view projects exact spell fields and the current appropriate formula
|
||||
through authored component template `0x1000032E`. Its corrective pass uses
|
||||
each component descriptor's icon DID rather than its inventory WCID, applies
|
||||
the modern scarab/prismatic formula when ACE disables component enforcement
|
||||
(IA-21), and introduces per-window authored-geometry revisions so an obsolete
|
||||
saved examination height resets once without discarding position or future
|
||||
user resizing. The corrected formula/icons/foreground stack and authored
|
||||
extent were accepted on 2026-07-24. The 61-test focused App
|
||||
gate passed before this correction. The corrected focused tests, Release build
|
||||
with #228's 17 tracked test warnings, 3,555 App tests / 3 skips, and 7,986
|
||||
complete-solution tests / 5 skips pass.
|
||||
|
||||
## Slice 1 — spell-bar overflow arrows
|
||||
|
||||
### Retail oracle
|
||||
|
||||
The authored Magic combat layout contains, inside every favorite-tab group:
|
||||
|
||||
- horizontal scrollbar `0x100000B5`, 685×36 at the 800-pixel design width;
|
||||
- decrement button `0x10000071`, 23×36;
|
||||
- increment button `0x10000072`, 23×36;
|
||||
- item list `0x100000B6`, inset by 23 pixels on both sides and 32 pixels high.
|
||||
|
||||
The list references the scrollbar through `UIElement_Scrollable` property
|
||||
`0x71`. The scrollbar references increment/decrement buttons through
|
||||
properties `0x77`/`0x78`, and property `0x79` enables HideDisabled. There is no
|
||||
track or thumb in this specific control: the two authored arrows are the whole
|
||||
visible scrollbar.
|
||||
|
||||
Named retail references and executable pseudocode are recorded in
|
||||
[`../research/2026-07-23-retail-spellbar-overflow-pseudocode.md`](../research/2026-07-23-retail-spellbar-overflow-pseudocode.md).
|
||||
|
||||
### Implementation plan
|
||||
|
||||
1. Generalize `DatWidgetFactory.BuildScrollbar` so horizontal and vertical
|
||||
scrollbars both import the referenced decrement/increment children,
|
||||
including their authored positions, dimensions, Normal/rollover/pressed
|
||||
media, and HideDisabled property.
|
||||
2. Generalize `UiScrollbar` to use distinct authored decrement/increment
|
||||
extents for rendering, hit-testing, track geometry, and dragging. Preserve
|
||||
the existing 16-pixel default for layouts whose button children are absent.
|
||||
3. Reproduce retail disabled presentation: a model without overflow rejects
|
||||
pointer input; when HideDisabled is authored, the scrollbar also draws
|
||||
nothing and does not claim hit tests.
|
||||
4. In `SpellcastingUiController`, bind each group's scrollbar to its
|
||||
`UiItemList.Scroll`, enable horizontal scrolling, and retain one independent
|
||||
pixel offset per favorite tab.
|
||||
5. Match `SpellCastSubMenu::SetSelected`: selecting a spell through keyboard,
|
||||
shortcut, or code scrolls that item into view; passive state refresh does
|
||||
not re-expose it.
|
||||
6. Carry the combat root's mixed-parent raw-edge policies through the complete
|
||||
imported tree and solve for the retail 18-cell favorite viewport; keep that
|
||||
HUD capacity fixed across desktop resizes.
|
||||
7. Pin the importer, arrow hit extents, 32-pixel step, no-overflow behavior,
|
||||
controller binding, and selection exposure with focused App tests.
|
||||
8. Run the App Release suite, solution Release build, and complete Release
|
||||
suite. Then update the roadmap/memory and request the connected visual gate:
|
||||
place more favorites than fit, scroll both directions, change tabs, and
|
||||
verify arrows disappear on a non-overflowing tab.
|
||||
|
||||
### Invariants
|
||||
|
||||
- DAT supplies the controls and their artwork; no new spell-bar texture or
|
||||
overlay is invented.
|
||||
- The scrollbar and list share one `UiScrollable`; there is no second offset.
|
||||
- One arrow press moves one 32-pixel favorite cell, matching
|
||||
`UIElement_ListBox::InqScrollDelta`.
|
||||
- Hidden disabled arrows cannot intercept combat-page dragging or clicks.
|
||||
- Existing stack, combat-power, chat, inventory, spellbook, and external
|
||||
container scrollbars retain their current behavior.
|
||||
- No substantial feature body enters `GameWindow`.
|
||||
|
||||
## Slice 2 — status Use/Assess commands
|
||||
|
||||
### Use-hand implementation
|
||||
|
||||
1. `ItemInteractionPolicy.IsToolbarUseEnabled` is the pure named-retail
|
||||
selection predicate.
|
||||
2. `ItemInteractionController.IsToolbarUseEnabled` adapts the selected live
|
||||
object without triggering a request or consuming the use throttle.
|
||||
3. `ToolbarController` subscribes to canonical `SelectionState.Changed` and
|
||||
selected-object add/update/remove notices, then sets the imported button's
|
||||
normal or ghosted state through `UiButton.Enabled`.
|
||||
4. Enabled clicks retain the one existing activation path:
|
||||
equipment enters `AutoWieldController`, ordinary use enters the normal Use
|
||||
request owner, and targeted items enter `UseItemOnTarget`, which already
|
||||
owns the retail target cursor.
|
||||
|
||||
### Slice 2 closeout
|
||||
|
||||
- The shared item-cooldown visual gate passed.
|
||||
- `gmToolbarUI::ListenToElementMessage @ 0x004BEE90` confirms the existing
|
||||
magnifying-glass path was already correct: assess the selected GUID
|
||||
immediately, otherwise enter one-shot Examine target mode.
|
||||
- Slice 3 owns the response lifetime and retained examination presentation;
|
||||
Slice 2 adds no duplicate command path.
|
||||
|
||||
## Slice 3 — assessment information panel
|
||||
|
||||
### Implemented ownership
|
||||
|
||||
1. `AppraiseInfoParser` owns the complete positional response payload,
|
||||
including HookProfile and strict truncation failure.
|
||||
2. `ItemInteractionController` owns the pending/current GUID pair and the
|
||||
balanced shared busy reference.
|
||||
3. `AppraisalUiController` owns response acceptance, item/creature/character
|
||||
subview selection, report projection, scroll preservation, first-response
|
||||
visibility, and visible-combat refresh.
|
||||
4. `RetailUiRuntime` imports and registers retail's
|
||||
`gmFloatyExaminationUI` as its own top-level window. It deliberately does
|
||||
not enter `RetailPanelUiController`; Inventory, Skills, Spellbook, and
|
||||
effects keep their shared main-panel geometry while Examination coexists.
|
||||
5. Network workers deliver immutable parsed data through the existing
|
||||
session router; retained state changes remain on the update thread.
|
||||
6. `AppraisalUiController` owns inscription presentation and optimistic field
|
||||
state; `WorldSession` owns the exact `0x00BF` GameAction send. Authoritative
|
||||
inventory/appraisal data remains the source on the next response.
|
||||
|
||||
### Connected gate
|
||||
|
||||
- Select an item and click the magnifying glass (or press Assess): a separate
|
||||
floaty window should show its name, available properties, descriptions,
|
||||
inscription/signature, retail chrome, and working scrollbars.
|
||||
- Assess a monster, NPC, and player: the correct creature/character page
|
||||
should appear and the busy cursor should clear.
|
||||
- On a monster, verify the animated creature is centered behind the exact
|
||||
Strength/Endurance/Coordination/Quickness/Focus/Self/Health/Stamina/Mana
|
||||
order, with its creature type and level in the authored header. Assess
|
||||
differently sized monsters to verify the retail bounding-box fit.
|
||||
- In combat, leave a creature assessment open long enough to observe a health
|
||||
refresh; closing the examination window must stop refreshes.
|
||||
- Keep Examination open while opening/moving Inventory or Skills and confirm
|
||||
both windows coexist with independent rectangles.
|
||||
- On the combat spell bar, press and hold a different favorite: its selection
|
||||
and name should update before release. Right-click a favorite: the same
|
||||
examination floaty should show the SpellPanel with name, school, mana,
|
||||
duration, range, description, and component formula. The toolbar magnifier
|
||||
must not treat the selected spell as an object.
|
||||
- Assess an owned inscribable weapon. Edit and clear its inscription by
|
||||
clicking elsewhere, then reassess it. An item authored by another player is
|
||||
read-only and reports the exact retail permission line when clicked.
|
||||
|
||||
## Slice 4 — equipped-child world picking
|
||||
|
||||
**Status:** USER-ACCEPTED 2026-07-29 — the two-client visual gate passed on
|
||||
Coldeve ("child world picking works"). Owner
|
||||
shape per the program table held: pure world-query/picking policy plus
|
||||
presentation anchor. No wire, physics, renderer, or
|
||||
`EquippedChildRenderController` changes. `LiveEntityRuntime` gained scoped
|
||||
`TryGetAttachedProjectedRecord` / `TryGetPickEligibleRecord` predicates;
|
||||
`TryGetInteractionEligibleRecord` and the `_visible` set are untouched, so
|
||||
radar, auto-target, and `CombatAttackTargetSource` remain wielded-item free
|
||||
(regression-asserted). `WorldSelectionQuery` takes the composed child root as
|
||||
an injected `Func<uint, Matrix4x4?>` beside the selection-sphere hook, wired in
|
||||
`LivePresentationComposition` from `EntityEffectPoseRegistry.TryGetRootPose`.
|
||||
The own-wielded `sr_Use` gate (`0x004E5BE9`) ships through the new
|
||||
`IWorldSelectionQuery.IsWieldedByPlayer`. Gates: App tests 3,951/3 skips,
|
||||
complete Release solution 9,783/5 skips, connected world-lifecycle gate
|
||||
`RESULT=PASS`.
|
||||
|
||||
**Correction 2026-07-29 (Opus review finding F1, HIGH).** Making a remote's
|
||||
wielded weapon selectable made the pickup chain reachable end to end for the
|
||||
first time, and acdream had never ported
|
||||
`ItemHolder::AttemptToPlaceInContainer_IsItemLegal @ 0x005870C0`'s arm at
|
||||
`0x005872B7` — `!ACCWeenieObject::IsOwnedByPlayer(item) && item->pwd._location
|
||||
!= 0` — so `SelectionPickUp` on another character's weapon installed a real
|
||||
approach and a wire request the server rejects. That arm now ships, with
|
||||
retail's own notice (`0x007e2228`) and retail's placement ahead of every other
|
||||
`AttemptToPlaceInContainer @ 0x00588140` stage: one local message, no movement,
|
||||
no request. The player's own wielded item is `IsOwnedByPlayer`, so it passes
|
||||
the arm and takes retail's `PositionState.WIELDED` route —
|
||||
`ACCWeenieObject::UIAttemptPutInContainer @ 0x0058D680` records
|
||||
`IR_PUT_IN_CONTAINER`, not `IR_PICK_UP` — dispatching its container transfer
|
||||
immediately with no approach. `TryGetApproach` now refuses attached children
|
||||
outright for the same reason, so no approach can anchor on a wielder's root.
|
||||
Picking, selection, examination, and the marker anchor are unchanged. The
|
||||
slice therefore introduces no divergence, contrary to what `f6db964f`'s message
|
||||
claimed; no register row is owed. Gates: App tests 3,960/3 skips, complete
|
||||
Release solution 9,792/5 skips, connected world-lifecycle gate `RESULT=PASS`.
|
||||
|
||||
### The retail mechanism
|
||||
|
||||
Retail picking is render-coupled, not a scene-graph ray walk. A click arms a
|
||||
per-frame selection cursor (`UIElement_SmartBoxWrapper::MouseDown @
|
||||
0x004E5700` sets a `SearchReason` — sr_Select/sr_Examine/sr_Use/sr_TargetedUse,
|
||||
acclient.h:6789 — then `SmartBox::find_object @ 0x00451C60` sets
|
||||
`Render::set_selection_cursor @ 0x0054B750`). During the frame,
|
||||
`Render::update_viewpoint @ 0x0054CDD0` builds `selection_ray` via
|
||||
`Render::pick_ray @ 0x0054B610`, and EVERY drawn part accumulates hits in
|
||||
`Render::GfxObjUnderSelectionRay @ 0x0054C740`: drawing-sphere test, then
|
||||
per-polygon tests when enabled, keeping the closest — with the polygon winner
|
||||
outranking a sphere-only winner (`GetMouseSelectionObjectID @ 0x0054C950`,
|
||||
read at `SmartBox::DrawNoBlit @ 0x00454C20`).
|
||||
|
||||
**The child-vs-parent answer:** each hit records `part->physobj->id`
|
||||
(`CPhysicsPart::get_physobj_id @ 0x0050D490`), and a part is only a candidate
|
||||
when `part->physobj->id != 0` (`CPhysicsPart::Draw @ 0x0050D7A0`). Equipped
|
||||
children are first-class `CPhysicsObj`s with their own ids and part arrays
|
||||
(`CPhysicsObj::add_child @ 0x0050F870` via `CSetup::GetHoldingLocation @
|
||||
0x005213F0`; `CPhysicsObj::UpdateChild @ 0x00512D50` composes
|
||||
`Frame::combine(parent_part_frame, hold_frame)` into the child's own
|
||||
`m_position` every frame). So a click on a wielded weapon returns THE WEAPON'S
|
||||
GUID — there is no parent redirection in the path, and no ethereal or
|
||||
wielded-specific gate: the only candidacy rule is "drawn part with a nonzero
|
||||
physobj id".
|
||||
|
||||
Post-pick (`RecvNotice_SmartBoxObjectFound @ 0x004E5AD0`): the id must exist in
|
||||
the weenie table; selection is set to the picked id itself
|
||||
(`ACCWeenieObject::SetSelectedObject @ 0x0058C2E0`); the click flash
|
||||
(`CPhysicsObj::SetLighting @ 0x00511A80`) is non-recursive — it lights that
|
||||
object's own part array ONLY, so clicking a weapon flashes the weapon and
|
||||
clicking a creature does not flash its weapon; the vivid brackets
|
||||
(`VividTargetIndicator @ 0x004F5CE0`) derive from the selected object's own
|
||||
selection sphere at its own position, which for a child IS the hand frame.
|
||||
`sr_Use` on an object whose `_wielderID == player_id` is suppressed
|
||||
(0x004E5BE9) while selection still happens; `sr_Examine` examines the child id
|
||||
directly. `PositionState.WIELDED` is distinct from `IN_CONTAINER`
|
||||
(acclient.h:6802), so container suppression never hides a wielded selection.
|
||||
|
||||
### The gap in acdream (the picker is already right)
|
||||
|
||||
Equipped children are already live entities with their own `ServerGuid`
|
||||
(`EquippedChildRenderController.TryRealize`, :448-617) and every draw path
|
||||
already publishes their selection parts under that guid
|
||||
(`RetailSelectionScene.AddVisiblePart`, which only skips `serverGuid == 0`).
|
||||
`RetailWorldPicker.Pick` therefore already returns the weapon as the polygon
|
||||
winner. The failure is entirely downstream: `WorldSelectionQuery.PickAt`
|
||||
(:137-154) requires `TryGetInteractionEligibleRecord`, whose `_visible` set
|
||||
admits `LiveEntityProjectionKind.World` only (`LiveEntityRuntime.cs:1183`
|
||||
excludes Pending/Attached/Hidden by design), so the winning hit is discarded
|
||||
and the click reports nothing. Retail would have succeeded. Marker anchoring
|
||||
has the twin problem: `ResolveVividTargetInfo` (:262-286) gates on the
|
||||
World-only `TryGetSpatiallyProjectedRecord`, and `TryGetSelectionSphere`
|
||||
(:293-318) anchors at `entity.Position/Rotation`, which for an attached child
|
||||
is deliberately the PARENT's root pose (`ApplyParentWorldPose`, :651-658) —
|
||||
brackets at the wielder's feet. The child's true composed root
|
||||
(`pose.RootLocal * parentWorld`, the exact `Frame::combine` equivalent) is
|
||||
already published per frame to `EntityEffectPoseRegistry` (`PublishChildPose`,
|
||||
:632-644; `TryGetRootPose` :195) and is what the vfx anchors already use.
|
||||
|
||||
### Slice plan
|
||||
|
||||
1. **Pick eligibility for attached projections.** Add a scoped
|
||||
`LiveEntityRuntime.TryGetPickEligibleRecord(serverGuid, localEntityId)`
|
||||
accepting `World` (today's semantics) OR `Attached` (with the same
|
||||
`IsSpatiallyProjected` + `WorldEntity.Id == localEntityId` staleness
|
||||
recheck). Consume it in `WorldSelectionQuery.PickAt`, the lighting-pulse
|
||||
identity paths, and `TryGetInteractionTarget`. **Do NOT widen
|
||||
`TryGetInteractionEligibleRecord`/`_visible`** — it feeds radar,
|
||||
auto-target, sticky/MoveTo establishment and `CombatAttackTargetSource`,
|
||||
none of which retail lets wielded items enter (retail's radar has no
|
||||
wielded blips).
|
||||
2. **Marker + sphere anchor.** Branch `ResolveVividTargetInfo` onto the new
|
||||
predicate, and for Attached records transform the Setup selection sphere by
|
||||
`EntityEffectPoseRegistry.TryGetRootPose(localId)` (injected as a
|
||||
`Func<uint, Matrix4x4?>` beside the existing selection-sphere hook) instead
|
||||
of the parent-derived `entity.Position/Rotation`.
|
||||
3. **Own-wielded Use gate.** In the use-immediately path, skip the Activate
|
||||
enqueue when the picked object's `WielderId == playerGuid` (selection and
|
||||
flash still occur) — the 0x004E5BE9 parity. If deferred, it owes an AP row.
|
||||
4. **Files:** `LiveEntityRuntime.cs` (predicate), `WorldSelectionQuery.cs`,
|
||||
`SelectionInteractionController.cs`, plus composition wiring for the
|
||||
root-pose hook. Untouched: `RetailWorldPicker`, `RetailSelectionScene`,
|
||||
`WbDrawDispatcher`, `EquippedChildRenderController`.
|
||||
5. **Conformance tests** (harnesses exist in
|
||||
`tests/AcDream.App.Tests/Interaction/WorldSelectionQueryTests.cs` and
|
||||
`Rendering/RetailSelectionSceneTests.cs`): child part closest → resolves to
|
||||
the CHILD guid; stale/withdrawn child record → null, never the parent;
|
||||
marker uses the pose-registry root, not the parent root; marker suppressed
|
||||
for the player's own wielded child, shown for a remote's; lighting pulse
|
||||
lights the child identity only; double-click Use suppressed on own wielded.
|
||||
6. **Visual gate (user, two-client):** click a remote character's wielded
|
||||
weapon — selection names the weapon, the flash lights only the weapon, the
|
||||
vivid brackets track the weapon through the wielder's animation (hand, not
|
||||
feet), right-click opens Slice 3's examination window on the weapon, radar
|
||||
shows no weapon blip, and double-clicking your OWN weapon does not fire a
|
||||
Use.
|
||||
|
||||
### Notes
|
||||
|
||||
- Slice 3 dependency verified: `SelectionState.Select` stores any nonzero guid
|
||||
and `RequestAppraisal` has no eligibility gate, so the examination window
|
||||
works on a picked child unmodified once the pick resolves.
|
||||
- Divergence register: this slice ADDS no row — it removes an undocumented
|
||||
deviation (Attached exclusion from pick eligibility versus retail's
|
||||
part-id pick).
|
||||
- Existing architectural divergence, unchanged by this slice: retail re-arms
|
||||
the pick every frame for hover/tooltips (`sr_MouseOver`); acdream picks on
|
||||
demand per click against the last published frame, with an identity recheck.
|
||||
|
||||
## Slice 5 — vendor browse lifecycle (contract authored 2026-08-08)
|
||||
|
||||
**Research foundation:**
|
||||
`docs/research/2026-08-08-slice5-vendor-browse-research.md` (all wire,
|
||||
retail-symbol, and seam citations live there — this contract only records
|
||||
DECISIONS and ordered work). Browse only; every buy/sell/accept concern is
|
||||
Slice 6 (see the research doc's §D fence).
|
||||
|
||||
### Decisions on the research doc's eight open questions
|
||||
|
||||
1. `VendorState` lives in `AcDream.Core.Items`, a sibling of
|
||||
`ExternalContainerState`.
|
||||
2. The shared `PublicWeenieDesc`-body parser IS extracted from
|
||||
`CreateObject.TryParse` FIRST, as its own behavior-preserving commit
|
||||
(5.0). Existing CreateObject wire tests must pass unchanged; the
|
||||
extraction adds no parsing behavior.
|
||||
3. `ShopSystem::BuyPrice`/`SellPrice` (0x006B6120/0x006B6180,
|
||||
byte-identical to ACE's `GetBuyCost`/`GetSellCost`) are ported NOW as
|
||||
pure Core functions with golden-value conformance tests — the browse
|
||||
list shows retail-correct prices from day one.
|
||||
4. No request-correlation token in Slice 5: the panel always opens on the
|
||||
browse/Buy tab. Slice 6 adds the sell-initiated correlation.
|
||||
5. `VendorProfile::InqAcceptability` (which player items the vendor would
|
||||
accept) is deferred to Slice 6 with the sell UI it gates.
|
||||
6. Category/type filter tabs are IN SCOPE for retail parity. The
|
||||
implementer's D0 reads `VendorItemsUI::AddTypeFilter` /
|
||||
`ListContainsType` (around 0x004C05C0/0x004C0D90) into a pseudocode
|
||||
note before any UI work; if that read reveals a mechanism too large
|
||||
for this slice, STOP and report (fallback — flat list + register row —
|
||||
requires explicit approval, not implementer discretion).
|
||||
7. The vendor panel's top-level LayoutDesc id is NOT yet known: the UI
|
||||
piece budgets a LayoutImporter discovery pass (the exact process that
|
||||
found the examination window's 0x2100006B), cross-checked by the two
|
||||
known tab-control ids (0x100000B9 Buy / 0x100000BB Sell) resolving
|
||||
under the candidate root.
|
||||
8. AP-110 is narrowed in the SAME COMMIT that lands the panel: "vendor"
|
||||
leaves the absent-panels list; whatever sub-scope remains absent after
|
||||
this slice gets its own precise row.
|
||||
|
||||
### Ordered work (each lands separately, bisectable)
|
||||
|
||||
- **5.0** — extract the shared `PublicWeenieDesc`-body parser
|
||||
(behavior-preserving; wire tests unchanged; no vendor code).
|
||||
- **5.1** — `ApproachVendor` (GameEvent 0x0062) inbound parser:
|
||||
`VendorProfile` + the full-desc item list, against the research doc's
|
||||
byte-verified field table; Core.Net tests with golden byte fixtures.
|
||||
- **5.2** — `VendorState` in Core.Items + the BuyPrice/SellPrice pure
|
||||
port + conformance tests.
|
||||
- **5.3** — Runtime ownership: `RuntimeInventoryState` owns the vendor
|
||||
session per the J4.2 pattern (generation-gated, torn down on
|
||||
reset/portal/logout); the 0x0062 route opens it; close is CLIENT-LOCAL
|
||||
(nothing sent on the wire) via the retail distance-watcher semantics;
|
||||
`ItemInteractionController._activeVendorId` /
|
||||
`ItemInteractionPolicy.ActiveVendorId` finally receive the real id.
|
||||
- **5.4** — the authored vendor panel: layout-id discovery, LayoutDesc
|
||||
import via the Slice-3 examination-window pattern (foreground stacking,
|
||||
authored extent), browse list reusing Slice-1's retained list/scrollbar
|
||||
+ DAT icon resolution, category tabs per the D0 read, prices via 5.2.
|
||||
- **5.5** — register narrowing (decision 8) rides the 5.4 landing commit.
|
||||
|
||||
### Trap list (binding)
|
||||
|
||||
Do not touch: the J5.2 strict use gate's semantics (the vendor open rides
|
||||
the EXISTING use transaction — no second gate, per the J4.5 invariant);
|
||||
`CreateObject.TryParse` behavior (5.0 is extraction only); anything in the
|
||||
Slice 6 fence (no buy/sell wire, no currency mutation, no
|
||||
InqAcceptability). New event handling follows the newest existing
|
||||
GameEvent handler's registration pattern, not a bespoke route.
|
||||
|
||||
### Gates
|
||||
|
||||
Per landing: build + full suite green (clean-room before each landing
|
||||
commit). Slice gate (user, connected, ~3 min): approach a Holtburg
|
||||
vendor, use them, the authored panel opens on the browse tab with
|
||||
retail-correct items/icons/prices; category tabs filter; walking out of
|
||||
range closes the panel by itself; nothing is purchasable anywhere.
|
||||
|
||||
## Slice 6 — vendor transactions, buy arc (contract authored 2026-08-08; user-pulled forward)
|
||||
|
||||
**Research:** `docs/research/2026-08-08-slice6-vendor-transactions-research.md`.
|
||||
User direction: "I cant buy anything... Fix that first." Root cause of all
|
||||
four reported symptoms: `VendorUiController` never touches the shared
|
||||
`SelectionState`/`StackSplitQuantityState` owners every other panel uses.
|
||||
|
||||
### Decisions
|
||||
|
||||
1. **Shop items materialize into `ClientObjectTable`** while the session is
|
||||
open (retail creates real CWeenieObjects from the vendor list — Slice 5
|
||||
research §A.2) and are REMOVED on session close/replace/reset. The
|
||||
implementer verifies retail's removal site (gmVendorUI::CloseVendor
|
||||
family) and mirrors its lifecycle. This dissolves F7c's blocker:
|
||||
`ExamineItemRequested` gets wired in this slice.
|
||||
2. **Vendor selection is the GLOBAL selection**: a new vendor change source
|
||||
on the canonical `SelectionState`; row-click selects through it; the
|
||||
status bar and the existing byte-faithful `StackSplitQuantityState`
|
||||
slider follow automatically (the split-size mask helper from 5.4's F2
|
||||
feeds the vendor-owned seeding exactly as gmToolbarUI does at
|
||||
pc:198635-198790).
|
||||
3. **Buy = retail's Buy button**: immediate single-item purchase
|
||||
(gmVendorUI::BuySingleItem, pc:201661) of the selected item with the
|
||||
slider-chosen quantity for stacks. Outbound `0x005F`: vendorGuid,
|
||||
count, per-item (i32 amount, u32 guid), TRAILING u32
|
||||
alternateCurrencyId — the real client sends it (CM_Vendor::Event_Buy,
|
||||
pc:689288) even though ACE's reader ignores it; we port the real
|
||||
client. The request rides the EXISTING J5.2 one-request-at-a-time
|
||||
reservation and completes on `UseDone` (0x01C7) — already the wired
|
||||
completion signal; no second gate.
|
||||
4. **Reconciliation is the existing inbound machinery**: money property
|
||||
updates, inventory CreateObject, and the ApproachVendor refresh
|
||||
(VendorState.Refreshed) all flow through landed handlers — the slice
|
||||
VERIFIES the loop end-to-end rather than adding an owner.
|
||||
5. **No double-click-to-buy**: retail has no such mechanism (confirmed
|
||||
against the full named table). We match retail. If the user wants it
|
||||
as a deliberate modernization it needs their explicit call + an AP row.
|
||||
6. **Deferred, still AP-161**: the Add button / Buying-tab staging list
|
||||
and everything Sell (0x0060 — researched, next arc).
|
||||
|
||||
### Ordered work (one implementer, bisectable commits)
|
||||
|
||||
- **6.1** shop-item materialization + removal lifecycle + examine wiring.
|
||||
- **6.2** the selection coupling (source, row-click, split seeding) —
|
||||
status bar + slider light up.
|
||||
- **6.3** the 0x005F builder (golden-byte tests incl. the trailing dword),
|
||||
Buy-button wiring, gate/UseDone completion, and the verified
|
||||
reconciliation round-trip. Register: AP-161 narrowed in the landing.
|
||||
|
||||
### Gate (user, connected)
|
||||
|
||||
Select a stacked item → it shows in the status bar with the slider; pick
|
||||
a quantity; Buy → coins drop by the displayed price, the stack lands in
|
||||
the pack, the shop refreshes; a single-item buy works; insufficient funds
|
||||
fails cleanly; the session still closes on walk-away/portal with the
|
||||
materialized items removed.
|
||||
|
||||
## Slice 6b/6c — vendor completion (contract authored 2026-08-08)
|
||||
|
||||
**Research:** `docs/research/2026-08-08-slice6b-vendor-completion-research.md`.
|
||||
Closes the user's seven-finding gate batch (dropdown polish landed at
|
||||
`33b45ee5`). Ordered chunks, one implementer:
|
||||
|
||||
1. **Move-to-use (Q2):** wire the existing client-predicted approach
|
||||
primitive (`PlayerInteractionMovementSink.BeginApproach`,
|
||||
`MovementType.MoveToObject` — today Pickup-only) onto `RequestUse`, so
|
||||
using a vendor (or anything) beyond range walks the player in first,
|
||||
retail's shape. No new movement machinery.
|
||||
2. **Buy staging (Q3):** `AddToBuyList` semantics per the research
|
||||
trace — Add stages the selection + slider quantity into the Buying
|
||||
tab's list (rendered count/price), Buy on that tab sends ONE batched
|
||||
0x005F with every staged entry, the two removal shapes + Clear, and
|
||||
retail's X-close confirm dialog when staging is non-empty (the current
|
||||
unconditional hide stays correct only for empty staging).
|
||||
3. **Selling (Q4):** the Selling tab's list is THE drop target — accept
|
||||
pack-item drops via the `ExternalContainerController` drag-handler
|
||||
pattern, filter through `InqAcceptability` (all four rejection reasons
|
||||
with retail's exact strings), staged sell list, batched 0x0060, and
|
||||
the existing reconciliation machinery.
|
||||
4. **Status bar (Q5) — user evidence is the axiom:** the code-reading
|
||||
says our chain already matches retail, but the user's live session
|
||||
says the stack count/value/split-bar presentation is absent for a
|
||||
vendor selection. Reproduce in a UI-level test FIRST (drive the real
|
||||
SelectedObjectController mount with a vendor selection); fix what the
|
||||
reproduction reveals; if it genuinely cannot reproduce, STOP and
|
||||
report with the test as evidence for a live-probe session.
|
||||
5. **Pack order (Q1):** code-verified correct (wire placement position →
|
||||
front insert). NO change; the gate re-checks it live and #352 gets
|
||||
filed only if it reproduces.
|
||||
|
||||
Register: same-commit rows for any deviation; AP-161 narrows again as
|
||||
staging/sell land (its remaining scope should shrink to nothing or to
|
||||
precisely what stays absent).
|
||||
|
||||
**Gate (user):** click a vendor from afar → walk-in + open; stage two
|
||||
different items with quantities → Buy All → one transaction, coins/items
|
||||
correct; drag a sellable item onto the Selling tab → stages → Sell →
|
||||
coins up, item gone; an InqAcceptability-rejected item shows retail's
|
||||
refusal; X with a staged list → confirm dialog; stacked selection shows
|
||||
count/value/split-bar in the toolbar; bought items land at the front of
|
||||
the pack.
|
||||
|
||||
## PROGRAM CLOSEOUT — 2026-08-08: all six slices COMPLETE, user-accepted
|
||||
|
||||
Slices 5 and 6 closed together after the vendor arc's final gates. The
|
||||
complete retail vendor experience is live and user-verified end to end:
|
||||
walk-to-use from afar (the never-animated-target physics-host resolver +
|
||||
the cylinder-gap range watcher), the authored panel with the scrollable
|
||||
category dropdown (arrow-cap, downward, left-aligned), retail cost
|
||||
sentences with live purse repaint on every money change, per-unit and
|
||||
whole-stack pricing per the split-size mask, the MaxStackSize quantity
|
||||
slider with the right-justified count entry and two-line name wrap in
|
||||
the toolbar, staged buying (accumulate + 5000 cap + shop-row decrement +
|
||||
the four pre-send guards + the batched 0x005F + the X-close confirm),
|
||||
selling (Selling-tab drop target with drag-over auto-switch,
|
||||
InqAcceptability with verbatim rejection strings, BF_RETAINED, batched
|
||||
0x0060), double-click-to-buy (AP-171, user-approved modernization),
|
||||
prepend pack ordering (the cross-queue placement replay), and
|
||||
materialized shop objects with ownership-checked lifecycle + examine.
|
||||
|
||||
Four adversarial Opus reviews found 34 defects before the user saw them;
|
||||
the user's connected gates found eleven more that only live sessions
|
||||
expose; two latent client-wide crashers (#348 cursor-handle exhaustion,
|
||||
#350 render-ledger overflow) were exposed, root-caused, and fixed along
|
||||
the way. Landed across `e45c95b0..af1a1ef9`. Deferred with issues/rows:
|
||||
#352 (range-watcher cylinder unit test), AP-166's pending-sell
|
||||
highlight, AP-167 (SellSingleItem's non-empty-container branch), AP-168's
|
||||
shop-stock half, Buying/Selling staging polish beyond the landed scope.
|
||||
|
||||
This closes the pre-M4 world-interaction completion program.
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
# Modern Runtime Slice B — EnvCell geometry deduplication and full bake
|
||||
|
||||
**Date:** 2026-07-24
|
||||
**Status:** Complete 2026-07-24; full installed-DAT gate passed
|
||||
**Parent:** [`2026-07-24-modern-runtime-architecture.md`](2026-07-24-modern-runtime-architecture.md), Slice B
|
||||
**Prior implementation:** [`../superpowers/plans/2026-07-05-mp1b-pak-and-bake.md`](../superpowers/plans/2026-07-05-mp1b-pak-and-bake.md)
|
||||
|
||||
## Outcome
|
||||
|
||||
Complete the existing MP1b bake without changing runtime behavior. Every
|
||||
EnvCell file ID remains an independently addressable pak key, while cells with
|
||||
the same collision-resistant geometry identity share one serialized blob and one
|
||||
physical byte range. The client does not consume the pak in this slice.
|
||||
|
||||
The full gate must replace the failed 865 GB / 52 minute per-cell bake with a
|
||||
bounded artifact whose size and time scale with unique geometry, not with all
|
||||
729,888 cell instances.
|
||||
|
||||
## Fixed contracts
|
||||
|
||||
1. `DatCollection` remains the only DAT reader.
|
||||
2. `MeshExtractor` remains the only mesh/texture interpreter used by both the
|
||||
live path and the bake.
|
||||
3. Geometry identity is one deterministic hash of the complete typed tuple:
|
||||
`environmentId`, `cellStructure`, surface count, then the ordered surface
|
||||
IDs. App and Bake call the same Core implementation.
|
||||
4. The pure identity helper lives in
|
||||
`AcDream.Core.Rendering.Wb.EnvCellGeometryIdentity`. It uses FNV-1a in a
|
||||
dedicated high-bit namespace and retains bit 33 for compatibility with
|
||||
existing synthetic-geometry diagnostics. WorldBuilder's original
|
||||
`hash = hash * 31 + value` calculation remains as a conformance helper only.
|
||||
5. Every EnvCell TOC key is still
|
||||
`PakKey.Compose(PakAssetType.EnvCellMesh, fileId)`.
|
||||
6. Aliases share `offset`, `length`, and `crc32`. The pak format and reader do
|
||||
not gain an indirection table or a second lookup.
|
||||
7. The shared payload carries the runtime geometry ID in
|
||||
`ObjectMeshData.ObjectId`, matching the current live
|
||||
`PrepareCellStructMeshData(geometryId, ...)` path.
|
||||
8. A geometry-ID collision whose full source tuple differs fails loudly. It
|
||||
may never alias silently.
|
||||
9. Blob and TOC order remain deterministic across worker counts.
|
||||
10. The destination pak is replaced only after a complete temporary artifact
|
||||
opens and validates. Cancellation or failure preserves the prior file and
|
||||
deletes the temporary file.
|
||||
11. The pak itself and raw bake logs are machine artifacts and are not
|
||||
committed. A compact, path-free bake report is committed.
|
||||
|
||||
**Full-DAT correction (2026-07-24):** the first guarded full-catalog run proved
|
||||
that the original fixed contract was impossible. Installed retail DAT cells
|
||||
`0x00030175` (`environment=0x277`, structure `0`, surfaces `0x013B,0x0034`)
|
||||
and `0x01BC0105` (`environment=0x276`, structure `0`, surfaces
|
||||
`0x04FC,0x0034`) both produce WorldBuilder ID `0x00000002020E8C13` despite
|
||||
having visibly different geometry. The difference is algebraic:
|
||||
one environment step contributes `31³`, exactly canceled by the first-surface
|
||||
difference `0x04FC - 0x013B = 31²`. The collision guard stopped the bake before
|
||||
writing an artifact. B1 was therefore corrected at the root: the old polynomial
|
||||
is retained and tested as historical evidence, while runtime and bake now share
|
||||
the stronger namespaced identity. The full-catalog collision gate remains
|
||||
mandatory.
|
||||
|
||||
## Implementation checkpoints
|
||||
|
||||
### B1 — One geometry identity seam
|
||||
|
||||
- Extract one pure identity calculation from the App transaction builder into
|
||||
`AcDream.Core.Rendering.Wb.EnvCellGeometryIdentity`.
|
||||
- Retain the existing App entry points as delegates so production behavior and
|
||||
callers do not change.
|
||||
- Add Core conformance tests for empty surfaces, ordered surfaces, the
|
||||
dedicated namespace, App/Bake shared values, and the installed-DAT collision
|
||||
in the legacy WorldBuilder polynomial.
|
||||
|
||||
**Gate:** existing App geometry-ID tests and new Core tests pass with the exact
|
||||
same values.
|
||||
|
||||
### B2 — Pak blob aliases
|
||||
|
||||
- Make `PakWriter` retain each written key's immutable TOC receipt.
|
||||
- Add `AddAlias(aliasKey, existingKey)`, rejecting a duplicate alias key,
|
||||
missing source key, alias after finish, and accidental self/duplicate use.
|
||||
- Emit a normal TOC row with the alias key and the source receipt's physical
|
||||
range and CRC.
|
||||
- Keep the reader unchanged except for test-only receipt inspection.
|
||||
|
||||
**Gate:** primary and alias deserialize field-for-field equal, have identical
|
||||
offset/length/CRC, use one physical blob, preserve sorted TOC order, and isolate
|
||||
corruption consistently.
|
||||
|
||||
### B3 — Deduplicated bake catalog
|
||||
|
||||
- Enumerate EnvCell file IDs exactly as today.
|
||||
- Read each EnvCell header once and produce:
|
||||
- a sorted `fileId -> geometryId` alias stream;
|
||||
- one full source descriptor per unique geometry ID.
|
||||
- Validate equal geometry IDs against the complete tuple
|
||||
`(environmentId, cellStructure, ordered surfaces)`.
|
||||
- Extract only the unique descriptors through
|
||||
`MeshExtractor.PrepareCellStructMeshData(geometryId, ...)`.
|
||||
- Write the first sorted cell key as the physical blob and every remaining
|
||||
cell key as an alias.
|
||||
- Keep GfxObj, Setup, and side-staged particle-preload assets in the same
|
||||
deterministic writer transaction.
|
||||
- Report enumerated cells, valid aliases, unique geometries, dedup ratio,
|
||||
physical blobs, failures, wall time, output bytes, and peak working set.
|
||||
|
||||
**Gate:** a filtered real-DAT fixture containing duplicate cells produces more
|
||||
EnvCell keys than physical EnvCell blobs, and every key reads the same payload
|
||||
as a live unique-geometry extraction.
|
||||
|
||||
### B4 — Transactional output and compatibility
|
||||
|
||||
- Bake beside the destination under a unique temporary filename.
|
||||
- Pass cancellation through enumeration and parallel extraction.
|
||||
- Open the completed temporary pak and validate its header, TOC count, source
|
||||
DAT iterations, bake-tool version, and every TOC range before replacement.
|
||||
- Atomically replace the destination on the same volume.
|
||||
- Add stale-version, corruption, cancellation, partial-write, existing-target
|
||||
preservation, and temporary-file cleanup tests.
|
||||
|
||||
**Gate:** no failure path damages or replaces a previously valid pak.
|
||||
|
||||
### B5 — Full-scale gate
|
||||
|
||||
- Run Release build and the focused Content/Bake suites.
|
||||
- Run two filtered bakes with different thread counts and require byte-identical
|
||||
output.
|
||||
- Run the complete unfiltered bake against the installed retail DAT files.
|
||||
- Open the result, validate every TOC entry, sample aliases across multiple
|
||||
landblocks, and run the existing live-vs-pak field equivalence suite.
|
||||
- Record counts, dedup ratio, failures, elapsed time, peak working set, and
|
||||
artifact size in `docs/research/`.
|
||||
- Delete the generated pak after validation unless it is intentionally retained
|
||||
outside the repository for the later Slice C cutover.
|
||||
|
||||
**Exit:** the full bake completes in practical time and disk space, all
|
||||
equivalence and determinism gates are green, duplicate EnvCells demonstrably
|
||||
share physical offsets, and no runtime code consumes the pak.
|
||||
|
||||
## Completion record
|
||||
|
||||
All checkpoints B1-B5 are complete. The full installed-DAT bake produced
|
||||
751,141 addressable keys backed by 38,370 physical blobs with zero failures.
|
||||
The 729,888 EnvCell keys resolve to 17,117 unique geometries and 712,771 aliases
|
||||
(42.6×). The validated package is 28,192.4 MiB and publishes atomically in
|
||||
81.4 seconds on the reference machine. A cross-landblock eight-key alias fixture
|
||||
is byte-identical with one versus eight workers. Full evidence:
|
||||
[`../research/2026-07-24-slice-b-full-bake-report.md`](../research/2026-07-24-slice-b-full-bake-report.md).
|
||||
|
||||
No runtime code reads the pak in this slice. Slice C remains blocked by the
|
||||
physical-local Slice A baseline gate. Release build and the complete solution
|
||||
suite pass (8,059 passed, five skipped, zero failed); generated pak artifacts
|
||||
were deleted after validation.
|
||||
|
||||
## Verification
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
dotnet test tests/AcDream.Content.Tests/AcDream.Content.Tests.csproj -c Release
|
||||
dotnet test tests/AcDream.Bake.Tests/AcDream.Bake.Tests.csproj -c Release
|
||||
dotnet build AcDream.slnx -c Release
|
||||
dotnet test AcDream.slnx -c Release
|
||||
```
|
||||
|
||||
Then run the full bake from the Release binary, capture its compact report, and
|
||||
validate the output with `PakReader`. No visual gate is required because this
|
||||
slice changes no runtime path.
|
||||
|
|
@ -1,347 +0,0 @@
|
|||
# Modern Runtime Slice C — prepared-asset cutover
|
||||
|
||||
**Date:** 2026-07-24
|
||||
|
||||
**Status:** complete — automated, connected, performance, and user visual
|
||||
gates passed 2026-07-24
|
||||
|
||||
**Checkpoint ledger:**
|
||||
|
||||
- C1 typed prepared-source boundary — `c42f93b3`
|
||||
- C2 production renderer injection — `f05afc07`
|
||||
- C3 activation probes and lookup precedence — `230a7df4`; automated gate
|
||||
green
|
||||
- C4 prepared-source diagnostics — `b1ad4b7c`
|
||||
- C4 connected equivalence and performance closeout —
|
||||
[`../research/2026-07-24-slice-c-prepared-asset-cutover-report.md`](../research/2026-07-24-slice-c-prepared-asset-cutover-report.md)
|
||||
|
||||
**Parent:** [`2026-07-24-modern-runtime-architecture.md`](2026-07-24-modern-runtime-architecture.md),
|
||||
Slice C / MP1c
|
||||
|
||||
**Authoritative before-state:**
|
||||
[`../research/2026-07-24-slice-a-physical-local-baselines.md`](../research/2026-07-24-slice-a-physical-local-baselines.md)
|
||||
at source and binary commit
|
||||
`49f3a48ee63b82d00731b0f91497930925279c86`.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Production world-mesh streaming reads immutable `ObjectMeshData` from the
|
||||
validated `acdream.pak`; it no longer reconstructs Setup, GfxObj, EnvCell,
|
||||
Surface, texture, and particle dependency graphs from DAT on four background
|
||||
workers during gameplay.
|
||||
|
||||
DAT remains the retail source of truth for simulation, UI, animation, dynamic
|
||||
appearance composition, physics, audio, and effects. The cutover changes when
|
||||
static render payloads are prepared, not their bytes, pixels, ownership, or
|
||||
gameplay meaning.
|
||||
|
||||
The slice is complete only when:
|
||||
|
||||
- the package is a required production input with exact DAT-iteration and bake
|
||||
version validation;
|
||||
- ordinary GfxObj and deduplicated EnvCell requests use typed pak keys;
|
||||
- missing, corrupt, canceled, and successful reads remain distinct;
|
||||
- production streaming has no implicit live-DAT extraction fallback;
|
||||
- Setup activation uses an explicit typed presence check before parsing DAT;
|
||||
- Portal DAT still wins when an ID exists in both Portal and HighRes;
|
||||
- cancellation and unexpected exceptions are never swallowed;
|
||||
- installed-DAT byte/field equivalence and the connected visual/lifetime gates
|
||||
pass;
|
||||
- portal allocation, exception count, p99, and repeated-Caul memory materially
|
||||
improve against the committed physical baseline.
|
||||
|
||||
## 2. Fixed architecture
|
||||
|
||||
### 2.1 Content-layer contract
|
||||
|
||||
Add a BCL/Content-only `IPreparedAssetSource` with a typed request and typed
|
||||
result:
|
||||
|
||||
```csharp
|
||||
enum PreparedAssetReadStatus
|
||||
{
|
||||
Loaded,
|
||||
Missing,
|
||||
Corrupt,
|
||||
}
|
||||
|
||||
readonly record struct PreparedAssetRequest(
|
||||
PakAssetType Type,
|
||||
uint SourceFileId,
|
||||
ulong RuntimeObjectId,
|
||||
PreparedEnvCellSchema? EnvCell);
|
||||
|
||||
readonly record struct PreparedAssetReadResult(
|
||||
PreparedAssetReadStatus Status,
|
||||
ObjectMeshData? Data);
|
||||
|
||||
interface IPreparedAssetSource : IDisposable
|
||||
{
|
||||
PreparedAssetPresence Probe(PakAssetType type, uint sourceFileId);
|
||||
PreparedAssetReadResult Read(
|
||||
in PreparedAssetRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
PreparedAssetSourceStats Stats { get; }
|
||||
}
|
||||
```
|
||||
|
||||
`CancellationToken` cancellation throws `OperationCanceledException`; it is
|
||||
not converted into a missing asset. `Loaded` always carries data and
|
||||
Missing/Corrupt never do.
|
||||
|
||||
The existing `PakKey` already persists the asset-type byte in each TOC key.
|
||||
Therefore Slice C does not invent a second type table or bump the binary
|
||||
format. A TOC-only probe binary-searches the exact
|
||||
`(PakAssetType, SourceFileId)` key without reading, CRC-checking, copying, or
|
||||
deserializing the blob. This is the explicit positive/negative/type metadata.
|
||||
|
||||
`PakPreparedAssetSource` owns one `PakReader`. It:
|
||||
|
||||
- validates format, bake-tool version, and all four DAT iterations at open;
|
||||
- exposes TOC-only presence;
|
||||
- maps PakReader's missing/corrupt/loaded outcomes without ambiguity;
|
||||
- validates the deserialized payload's logical type and runtime identity;
|
||||
- never falls through to DAT extraction.
|
||||
|
||||
An explicit `DatPreparedAssetSource` is retained only for bake/equivalence and
|
||||
UI Studio tooling. It uses `MeshExtractor` and is never selected by
|
||||
`GameWindow`.
|
||||
|
||||
### 2.2 Request identity
|
||||
|
||||
The renderer's internal runtime ID and the package lookup ID are not always
|
||||
the same:
|
||||
|
||||
| Request | Pak key | `ObjectMeshData.ObjectId` |
|
||||
|---|---|---|
|
||||
| ordinary world/live mesh | `GfxObjMesh + GfxObj DID` | GfxObj DID |
|
||||
| Setup tooling request | `SetupMesh + Setup DID` | Setup DID |
|
||||
| EnvCell shell | `EnvCellMesh + source Cell DID` | deduplicated geometry ID |
|
||||
|
||||
`EnvCellShellPlacement.CellId` is therefore carried into
|
||||
`EnvCellMeshPreparationScheduler` and retained beside the existing geometry
|
||||
schema. Cancellation/re-arm reuses that exact descriptor; it never derives a
|
||||
fake DAT ID from the synthetic geometry hash.
|
||||
|
||||
Aliases in the pak make every source Cell DID resolve to the one shared
|
||||
physical geometry blob. Runtime validates that the blob's `ObjectId` equals
|
||||
the scheduler's deduplicated geometry ID before publishing it.
|
||||
|
||||
### 2.3 Ownership and thread model
|
||||
|
||||
- `ContentEffectsAudioComposition` opens DAT first, then the prepared source,
|
||||
and publishes both through typed owners.
|
||||
- `GameWindow` stores only the additional lifetime root and passes the source
|
||||
through `ContentEffectsAudioResult`.
|
||||
- `WorldRenderComposition` injects the source into `WbMeshAdapter`.
|
||||
- `ObjectMeshManager`'s existing four workers read immutable prepared
|
||||
payloads. They retain the current request deduplication, LIFO locality,
|
||||
cancellation, CPU cache, staging high-water, ownership, and retry rules.
|
||||
- Pak reads and deserialization remain worker-only. GL upload remains
|
||||
render-thread-only.
|
||||
- Shutdown disposes `WbMeshAdapter` and joins its workers before unmapping the
|
||||
pak, then disposes DAT. Construction rollback uses the reverse order.
|
||||
- No worker, source, or result stores a window reference or service locator.
|
||||
|
||||
### 2.4 Setup activation and non-allocating DAT lookup
|
||||
|
||||
`WorldEntity.SourceGfxObjOrSetupId` can be either a GfxObj or Setup. The current
|
||||
`SequencerFactory` and `ResolveActivation` try to parse every value as Setup;
|
||||
the physical baseline records 4,659 `ArgumentOutOfRangeException` events on one
|
||||
uncapped route.
|
||||
|
||||
Both paths first probe `SetupMesh + id` in the prepared source:
|
||||
|
||||
- absent: return the existing no-Setup/no-script result without a DAT call;
|
||||
- present: parse the valid Setup through the bounded DAT object cache;
|
||||
- corrupt package entry: renderer load fails loudly; activation does not guess;
|
||||
- cancellation: rethrow;
|
||||
- known corrupt DAT data (`InvalidDataException`, `EndOfStreamException`, and
|
||||
the exact verified DatReaderWriter malformed-record exception): diagnose and
|
||||
return no activation;
|
||||
- unexpected exceptions: diagnose and rethrow.
|
||||
|
||||
The exception list is evidence-driven from installed fixtures and tests; a
|
||||
blanket `catch` is forbidden.
|
||||
|
||||
`IDatReaderWriter.TryResolvePreferred` is added as a non-allocating lookup.
|
||||
The concrete adapter checks Portal first, then HighRes, Language, and Cell.
|
||||
That encodes the existing load-bearing Portal-wins behavior directly.
|
||||
`ResolveId()` remains for compatibility tooling, but production mesh
|
||||
extraction and bounds code no longer use
|
||||
`ResolveId().ToList().OrderByDescending(...)`.
|
||||
|
||||
### 2.5 Texture identity
|
||||
|
||||
Pak payloads contain default-palette texture bytes. Dynamic appearance
|
||||
composition continues through the existing owner-scoped `TextureCache`.
|
||||
Default and palette-overlaid pixels retain distinct cache keys; the prepared
|
||||
source never inserts default bytes into the dynamic composite cache.
|
||||
|
||||
## 3. Checkpoints
|
||||
|
||||
### C1 — typed prepared-source boundary
|
||||
|
||||
Implement:
|
||||
|
||||
- typed request/presence/read-result/stat records;
|
||||
- PakReader TOC-only probe and status-preserving read;
|
||||
- `PakPreparedAssetSource` compatibility validation;
|
||||
- explicit `DatPreparedAssetSource` for tooling;
|
||||
- `RuntimeOptions.PreparedAssetPath`, defaulting to
|
||||
`<DatDir>/acdream.pak`, with `ACDREAM_PAK_PATH` as the sole override;
|
||||
- Content tests for absent/present/corrupt/canceled/type-mismatched reads,
|
||||
alias identity, iteration mismatch, bake-version mismatch, and disposal.
|
||||
|
||||
Gate:
|
||||
|
||||
- Content and Bake tests green;
|
||||
- current pak round-trip/equivalence tests remain green;
|
||||
- no App/runtime behavior changed yet.
|
||||
|
||||
Commit as one boundary unit.
|
||||
|
||||
### C2 — production renderer injection
|
||||
|
||||
Implement:
|
||||
|
||||
- prepared-source acquisition/publication/teardown in composition;
|
||||
- `WorldRenderComposition` and `WbMeshAdapter` injection;
|
||||
- `ObjectMeshManager` worker reads through `IPreparedAssetSource`;
|
||||
- source Cell DID propagation for EnvCell aliases;
|
||||
- exact `TranslucencyKind` persisted per prepared texture batch, removing the
|
||||
render-thread `GfxObjMesh.Build` metadata reconstruction (bake-tool identity
|
||||
advances to version 3; the pak container remains format version 1);
|
||||
- exact terminal-failure and cancellation semantics;
|
||||
- current CPU cache, staging, upload, retry, ownership, and shutdown behavior
|
||||
retained;
|
||||
- UI Studio explicitly opts into the live-DAT tooling source;
|
||||
- remove production worker calls to `MeshExtractor`.
|
||||
|
||||
Focused tests:
|
||||
|
||||
- GfxObj request key and identity;
|
||||
- EnvCell alias key plus geometry identity;
|
||||
- cached/re-armed/canceled generation;
|
||||
- missing and corrupt entries become terminal preparation failures without
|
||||
retry storms;
|
||||
- source disposed only after workers and mesh adapter;
|
||||
- production composition cannot construct without a valid prepared source;
|
||||
- tooling source remains explicit.
|
||||
|
||||
Gate:
|
||||
|
||||
- App Release build/tests green;
|
||||
- source audit finds no production `MeshExtractor.Prepare*` call from the
|
||||
streaming workers;
|
||||
- missing pak and incompatible pak fail at startup with actionable messages.
|
||||
|
||||
Commit as one integration unit.
|
||||
|
||||
### C3 — activation probes and lookup precedence
|
||||
|
||||
Implement:
|
||||
|
||||
- Setup-presence gate shared by `SequencerFactory` and `ResolveActivation`;
|
||||
- specific corrupt-data exception handling and diagnostics;
|
||||
- cancellation and unexpected-exception propagation;
|
||||
- non-allocating Portal-first `TryResolvePreferred`;
|
||||
- remove the old LINQ lookup in `MeshExtractor`, recursive part collection,
|
||||
and remaining bounds helper;
|
||||
- explicit default-palette versus palette-overlay cache-identity tests.
|
||||
|
||||
Focused tests:
|
||||
|
||||
- GfxObj entity never enters Setup parser;
|
||||
- valid Setup parses once then hits bounded cache;
|
||||
- corrupt Setup is diagnosed once;
|
||||
- unexpected exception is visible;
|
||||
- cancellation propagates;
|
||||
- dual-DAT fixture chooses Portal;
|
||||
- missing ID allocates no result collection;
|
||||
- drudge, robed-player, and palette-dyed-armor seam fixtures preserve fields
|
||||
and pixels.
|
||||
|
||||
Gate:
|
||||
|
||||
- no `ResolveActivation` bare catch;
|
||||
- zero `ArgumentOutOfRangeException` Setup-probe storm on connected route;
|
||||
- full Release suite green.
|
||||
|
||||
Commit as one semantic-cleanup unit.
|
||||
|
||||
### C4 — bake, connected equivalence, and performance closeout
|
||||
|
||||
1. Re-bake installed DATs atomically to the production path with the current
|
||||
tool.
|
||||
2. Validate the full artifact, key counts, aliases, CRC contract, iterations,
|
||||
and exact source/binary provenance.
|
||||
3. Run focused byte/field equivalence and seam fixtures.
|
||||
4. Run the capped and uncapped physical nine-stop routes plus pinned dense
|
||||
Arwic with the same reference configuration.
|
||||
5. Compare fixed-camera screenshots under the committed tolerance/mask rule.
|
||||
6. Compare per-portal p99/allocation, process allocation/GC, exception count,
|
||||
cache counters, prepared-source probes/reads/loaded/missing/corrupt totals,
|
||||
and repeated-Caul ownership/memory to Slice A.
|
||||
7. Run Release build and the complete Release suite.
|
||||
8. Update the parent plan, roadmap, WorldBuilder inventory, divergence
|
||||
register if and only if a real deviation changed, and durable memory.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- every route reaches every destination and disconnects gracefully;
|
||||
- no world, animation, particle, effect, texture, mesh, landblock, or GPU
|
||||
ownership regression;
|
||||
- fixed-camera screenshots pass;
|
||||
- no invalid Setup exception storm;
|
||||
- no production DAT mesh extraction;
|
||||
- portal single-frame allocation and p99 materially improve;
|
||||
- process allocation/GC materially improve;
|
||||
- repeated-Caul memory passes the existing assertion without weakening it.
|
||||
|
||||
If pixels or behavior differ, the slice is not complete even when performance
|
||||
improves. If timing improves but heap reservation still fails, keep the failure
|
||||
open and attribute it before considering Slice C closed.
|
||||
|
||||
### C4 result — completed 2026-07-24
|
||||
|
||||
The format-1/bake-tool-3 production package validated with 751,141 keys and
|
||||
the exact installed DAT iterations. Installed-DAT seam/equivalence tests, all
|
||||
three connected physical-display routes, canonical lifetime assertions, and
|
||||
the complete Release suite passed. The user accepted the prepared path's
|
||||
geometry, materials, textures, and world presentation.
|
||||
|
||||
Against the committed Slice A physical baseline:
|
||||
|
||||
- capped CPU p95/p99 improved 15.8%/16.9%, GPU p95/p99 17.4%/20.0%;
|
||||
- uncapped CPU p95/p99 improved 13.8%/17.0%, GPU p95/p99 22.5%/20.6%;
|
||||
- dense-Arwic CPU/GPU p99 improved 12.0%/16.8%;
|
||||
- process allocation fell 54.9% capped, 41.8% uncapped, and 61.9% dense;
|
||||
- GC pause time fell 52.8%, 48.4%, and 54.1%;
|
||||
- the invalid typed-Setup exception count fell to zero in every route;
|
||||
- the largest frame-thread allocation fell from roughly 203 MiB to 46.8 MiB
|
||||
capped and 39.9 MiB uncapped;
|
||||
- both warm repeated-Caul memory comparisons passed the unchanged assertion.
|
||||
|
||||
Windows reports clean memory-mapped package pages in working set, so total
|
||||
working set is higher even though private memory and ownership remain bounded.
|
||||
That is file cache, not retained world state. Exact measurements and the
|
||||
artifact policy are recorded in the
|
||||
[Slice C report](../research/2026-07-24-slice-c-prepared-asset-cutover-report.md).
|
||||
|
||||
## 4. Review checklist per checkpoint
|
||||
|
||||
Because this task is running without delegated reviewers, the primary engineer
|
||||
performs three explicit read-only passes over each complete checkpoint diff:
|
||||
|
||||
1. **Contract/conformance:** request identity, Portal precedence, package
|
||||
compatibility, byte/field/pixel equivalence.
|
||||
2. **Architecture/lifetime:** dependency direction, worker/render-thread
|
||||
ownership, cancellation, construction rollback, shutdown order, no
|
||||
production fallback.
|
||||
3. **Adversarial:** corrupt/truncated package, missing key, stale/canceled
|
||||
request, alias mismatch, callback failure, exception visibility, repeated
|
||||
portal/GUID/landblock churn.
|
||||
|
||||
Every confirmed finding is fixed and the full diff is re-read before the
|
||||
checkpoint commit.
|
||||
|
|
@ -1,300 +0,0 @@
|
|||
# Modern Runtime Slice D — Typed Asset Handles and Unified Residency
|
||||
|
||||
**Status:** complete — D1 through D5 passed 2026-07-24
|
||||
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
|
||||
**Baseline:** Slice C closeout commit `a564c4b7`
|
||||
**Behavior contract:** no visual-quality or retail-behavior change
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Slice D makes runtime memory finite, attributable, and generation-safe without
|
||||
replacing the specialized caches that already work.
|
||||
|
||||
One `ResidencyManager` owns policy, logical asset identity, generations,
|
||||
leases, budgets, and the aggregate diagnostic snapshot. Existing renderer and
|
||||
content owners continue to own their physical storage:
|
||||
|
||||
- `ObjectMeshManager` owns object render data, mesh ranges, texture atlases,
|
||||
prepared CPU mesh entries, and staged uploads.
|
||||
- `GlobalMeshBuffer` owns vertex/index arenas and fence-delayed range reuse.
|
||||
- `CompositeTextureArrayCache` owns pooled composite-array layers and arrays.
|
||||
- `StandaloneBindlessTextureCache` owns standalone particle arrays.
|
||||
- `GpuFrameFlightController` and `GpuRetirementLedger` own fence-delayed
|
||||
physical release.
|
||||
- `RetailAnimationLoader` owns parsed animation residence.
|
||||
- Deferred-alpha producers own their reusable CPU scratch arrays.
|
||||
|
||||
The manager never stores or deletes an OpenGL name. It observes immutable
|
||||
facts, makes bounded policy decisions during the update/resource-maintenance
|
||||
phase, and issues logical trim requests. The physical owner executes any GL
|
||||
release during its existing render-thread maintenance phase.
|
||||
|
||||
## 2. Current-owner inventory
|
||||
|
||||
| Domain | Existing policy | Physical owner | Slice D action |
|
||||
|---|---|---|---|
|
||||
| Object render data | 1 GiB estimated GPU / 50 unowned objects, per-frame bounded reclamation | `ObjectMeshManager` | Preserve; source limits from typed budgets; report logical/non-arena/arena/retiring bytes separately |
|
||||
| Prepared mesh CPU cache | 100 entries / 128 MiB LRU | `CpuMeshUploadCache` | Preserve; source limits from typed budgets; report hits, misses, evictions, count, bytes |
|
||||
| Mesh staging | 256 claims / 128 MiB, generation checked | `MeshUploadStagingQueue` | Preserve; source limits from typed budgets; report queued and claimed bytes separately |
|
||||
| Global mesh arenas | 384 MiB vertex, 128 MiB index, 896 MiB maximum physical overlap; shrink hysteresis | `GlobalMeshBuffer` | Preserve; expose live, free, largest-free, staging, retired, and fragmentation facts |
|
||||
| Composite texture arrays | 64 MiB unowned / 128 MiB physical, throttled logical eviction and fence release | `CompositeTextureArrayCache` | Preserve; source limits from typed budgets; report resident/unowned/retiring/fragmentation |
|
||||
| Standalone particle arrays | 32 MiB / 256 unowned, one eviction per frame | `StandaloneBindlessTextureCache` | Preserve; source limits from typed budgets; report owned/unowned/retiring bytes |
|
||||
| Prepared package | one immutable memory map; payload arrays are charged to CPU cache/staging | `PakPreparedAssetSource` | Report mapped virtual bytes separately; do not mislabel the 27.5 GiB address map as committed residence |
|
||||
| Parsed animations | unbounded dictionary | `RetailAnimationLoader` | Add byte-and-count bounded concurrent LRU; a live sequencer keeps its own reference after cache eviction |
|
||||
| Decoded audio waves | 32 MiB LRU | `DatSoundCache` | Preserve and report; sound-table metadata is finite DAT identity metadata |
|
||||
| Deferred-alpha scratch | grow-only lists/arrays at three submission seams | producer owners | Add capacity-budgeted retention with immediate correctness-preserving growth and policy-driven shrink |
|
||||
| Mesh bounds memo | not present in current source | none | Close stale issue #243; do not add a replacement |
|
||||
|
||||
The process-wide `GpuMemoryTracker` remains an independent physical-allocation
|
||||
cross-check. It is not a policy owner because it intentionally covers shaders,
|
||||
framebuffers, UI textures, and other allocations outside Slice D's cache set.
|
||||
|
||||
## 3. Typed identity
|
||||
|
||||
```csharp
|
||||
readonly record struct AssetHandle<TAsset>(uint Index, ushort Generation);
|
||||
readonly record struct OwnerToken(uint Index, ushort Generation);
|
||||
readonly record struct AssetLease<TAsset>(
|
||||
AssetHandle<TAsset> Handle,
|
||||
OwnerToken Owner);
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
1. Index zero and generation zero are invalid.
|
||||
2. A slot generation increments before an index is reused.
|
||||
3. A stale handle cannot touch, transition, lease, release, or retire its
|
||||
replacement generation.
|
||||
4. Owner tokens are allocated and retired independently from assets. Retiring
|
||||
an owner releases exactly that owner's leases.
|
||||
5. Repeated acquisition of the same `(asset, owner)` is idempotent.
|
||||
6. Callers never derive a handle, owner, or generation through an integer cast.
|
||||
7. Physical cache keys remain domain-specific (`ulong` mesh id,
|
||||
`CompositeTextureKey`, surface DID). An adapter maps those keys to typed
|
||||
handles; the manager does not replace their key or storage types.
|
||||
|
||||
## 4. State machine
|
||||
|
||||
The logical state machine is single-writer. Terminal failure states preserve
|
||||
diagnostic identity until a new generation is requested.
|
||||
|
||||
| Current | Event | Next | Required accounting/action |
|
||||
|---|---|---|---|
|
||||
| `Absent` | request | `Requested` | allocate/current generation; record priority and request frame |
|
||||
| `Requested` | prepare succeeds | `Prepared` | publish actual CPU prepared/decoded bytes |
|
||||
| `Requested` | cancel | `Cancelled` | clear transient bytes; never publish a completion |
|
||||
| `Requested` | missing | `Missing` | retain negative result only |
|
||||
| `Requested` | corrupt | `Corrupt` | retain diagnostic result; do not retry silently |
|
||||
| `Requested` | failure | `Failed` | retain typed failure; do not swallow |
|
||||
| `Prepared` | queue upload | `UploadPending` | move or add staging/GPU-requested charges |
|
||||
| `Prepared` | release with no owners | `Retiring` | enqueue logical eviction |
|
||||
| `UploadPending` | upload succeeds | `Resident` | clear requested/staging charge; publish exact resident charge |
|
||||
| `UploadPending` | retryable rollback completes | `Prepared` | clear GPU-requested charge; retain CPU data |
|
||||
| `UploadPending` | cancel/no owners | `Retiring` | invalidate generation; stale completion is ignored |
|
||||
| `UploadPending` | corrupt/failure | `Corrupt`/`Failed` | clear transient charges; retain diagnostic state |
|
||||
| `Resident` | acquire/touch | `Resident` | update owner set, priority, frame, and rebuild cost |
|
||||
| `Resident` | last owner releases | `Resident` | becomes eviction-eligible; physical cache may retain it |
|
||||
| `Resident` | evict | `Retiring` | invalidate logical availability before physical release |
|
||||
| `Retiring` | fence release completes | `Absent` | clear retiring bytes and recycle slot with next generation |
|
||||
| any non-retiring state | session/world generation invalidates | `Retiring` or terminal cancel | reject later stale work |
|
||||
| terminal failure state | explicit new request | `Requested` on a new generation | prior completion cannot revive the new request |
|
||||
|
||||
Illegal transitions throw in tests and emit a release diagnostic in
|
||||
production composition; they are never silently coerced.
|
||||
|
||||
## 5. Threading and phase ownership
|
||||
|
||||
The graphical client currently advances update and render on one native window
|
||||
thread. Slice D nevertheless fixes the contract at the future host boundary:
|
||||
|
||||
### Shared/worker side
|
||||
|
||||
- Decode and preparation workers may only enqueue immutable
|
||||
`ResidencyObservation` records.
|
||||
- Workers never mutate the manager ledger, budgets, owners, GL resources, or
|
||||
render cache collections.
|
||||
- The observation queue is bounded by active asset generations. Repeated
|
||||
observations for one `(handle, kind)` coalesce last-writer before drain.
|
||||
- Cancellation and corrupt/failure completion carry the exact asset generation.
|
||||
|
||||
### Update/resource-maintenance phase
|
||||
|
||||
- `ResidencyManager` is single-writer.
|
||||
- At most one manager drain and policy pass runs per host tick.
|
||||
- It applies observations, updates the lease/state ledger, captures domain
|
||||
facts, and emits bounded `ResidencyTrimRequest` commands.
|
||||
- Eviction order is: zero owners first, lowest priority first, oldest use
|
||||
generation/frame first, lowest rebuild cost first, then stable handle index.
|
||||
- Manager code never waits on decode, file I/O, a GPU fence, or a lock acquired
|
||||
by the render path.
|
||||
|
||||
### Render-resource phase
|
||||
|
||||
- Specialized owners consume trim requests without calling back into the
|
||||
manager while holding their internal mutation locks.
|
||||
- Logical removal occurs before publication of a physical-release request.
|
||||
- OpenGL non-residency/deletion and arena range return remain render-thread
|
||||
only and fence delayed through the existing retirement owners.
|
||||
- Physical completion is observed on the following manager drain. A stale
|
||||
completion with the wrong handle generation is rejected.
|
||||
|
||||
No render-thread code blocks waiting for the manager, and the manager never
|
||||
sees a raw GL name or bindless handle.
|
||||
|
||||
## 6. Accounting model
|
||||
|
||||
Every domain reports the following independent values:
|
||||
|
||||
- `LogicalBytes`: immutable metadata and logical entry overhead where measured.
|
||||
- `CpuPreparedBytes`: parsed/prepared payload retained for reuse.
|
||||
- `DecodedBytes`: canonical decoded pixels/audio/animation graphs.
|
||||
- `PinnedBytes`: CPU data that cannot currently be evicted due to a live lease.
|
||||
- `StagingBytes`: queued or claimed upload payload.
|
||||
- `GpuRequestedBytes`: physical allocation requested but not yet published.
|
||||
- `GpuResidentBytes`: currently drawable physical allocation.
|
||||
- `RetiringBytes`: logically dead physical allocation awaiting a fence/retry.
|
||||
- `MappedVirtualBytes`: immutable package address space, not committed RAM.
|
||||
- `BudgetBytes`: the policy ceiling for the relevant charge.
|
||||
- `CapacityBytes`, `UsedBytes`, `LargestFreeBytes`: allocator facts used to
|
||||
calculate fragmentation without pretending free capacity is a leak.
|
||||
|
||||
Aggregate totals never add `MappedVirtualBytes` to committed CPU residence and
|
||||
never add an arena's logical allocation bytes to its physical capacity twice.
|
||||
|
||||
## 7. Budgets and defaults
|
||||
|
||||
`RuntimeOptions` owns environment parsing once. `ResidencyBudgetOptions`
|
||||
contains byte/count ceilings with the current production values as defaults:
|
||||
|
||||
- object mesh physical: 1 GiB;
|
||||
- object mesh unowned entries: 50;
|
||||
- prepared mesh CPU: 128 MiB / 100;
|
||||
- staging: 128 MiB / 256;
|
||||
- composite arrays: 128 MiB physical / 64 MiB unowned;
|
||||
- standalone particle arrays: 32 MiB / 256;
|
||||
- decoded animations: 64 MiB / 512;
|
||||
- decoded audio: 32 MiB;
|
||||
- deferred-alpha retained CPU scratch: 16 MiB aggregate with per-owner floors.
|
||||
|
||||
Environment overrides are diagnostic/startup inputs and reject zero, negative,
|
||||
overflowing, or malformed values by falling back to the documented default.
|
||||
The settings quality preset may select a complete budget profile later; Slice D
|
||||
first exposes the typed runtime target so a settings change is atomic rather
|
||||
than mutating individual caches independently. Defaults preserve today's
|
||||
visual radius and cache behavior.
|
||||
|
||||
Multi-session retuning is evidence driven. This slice records actual working
|
||||
sets under normal, dense, and forced-pressure routes before changing production
|
||||
defaults below their current values.
|
||||
|
||||
## 8. Delivery checkpoints
|
||||
|
||||
### D1 — Contract and logical ledger
|
||||
|
||||
- Add typed handles, owner tokens, leases, priorities, states, observations,
|
||||
trim requests, budgets, and aggregate snapshots.
|
||||
- Implement the transition table and generation-safe owner release.
|
||||
- Test stale completions, duplicate acquire, owner reuse, asset-slot reuse,
|
||||
cancellation, corrupt/failure states, and illegal transitions.
|
||||
|
||||
**Complete 2026-07-24.** The typed ledger, generation checks, bounded
|
||||
worker-observation journal, startup budget profile, and transition conformance
|
||||
tests landed without moving physical GL ownership into the policy layer.
|
||||
|
||||
### D2 — Existing-owner adapters
|
||||
|
||||
- Parameterize current mesh, staging, composite, and standalone budgets.
|
||||
- Add exact snapshot facts to each owner.
|
||||
- Register them with one manager without changing their physical storage or
|
||||
fence protocol.
|
||||
- Cross-check manager GPU totals with `GpuMemoryTracker`, allowing explicitly
|
||||
enumerated non-cache resources.
|
||||
|
||||
**Complete 2026-07-24.** Production composition passes one immutable budget
|
||||
profile into the existing mesh and texture owners and registers exact snapshot
|
||||
sources for object geometry/atlases, prepared meshes, staging, the global mesh
|
||||
arena, composite arrays, standalone particle arrays, and the prepared package
|
||||
mapping. Removed object atlases and standalone textures retain retiring-byte
|
||||
ownership until their accepted frame-fence release actually completes.
|
||||
Lifecycle artifacts now publish both cache-attributed GPU bytes and the signed
|
||||
`GpuTrackerMinusResidencyBytes` remainder; that remainder deliberately covers
|
||||
enumerated non-cache allocations such as terrain, shaders, UI textures,
|
||||
framebuffers, and dynamic draw buffers rather than being mislabeled as cache
|
||||
residence.
|
||||
|
||||
### D3 — Missing policies
|
||||
|
||||
- Bound `RetailAnimationLoader` by count and estimated retained bytes.
|
||||
- Bring deferred-alpha retained scratch behind a bounded capacity owner.
|
||||
- Close stale #243 after proving no current bounds cache exists.
|
||||
|
||||
**Complete 2026-07-24.** `RetailAnimationLoader` now uses a concurrent
|
||||
in-flight load gate feeding a byte-and-count bounded LRU. Cache eviction drops
|
||||
only the cache reference, so active sequencers retain valid immutable
|
||||
animations; duplicate DID requests coalesce while unrelated reads remain
|
||||
parallel. The shared retail alpha path now partitions its 16 MiB startup budget
|
||||
between the queue, object dispatcher, and particle renderer. Each physical
|
||||
owner grows immediately when a frame requires it, reports exact retained
|
||||
backing capacity, and shrinks only after repeated severe under-use. This bounds
|
||||
one-frame density spikes without changing draw order, particle content, or
|
||||
visible quality. Issue #240 is closed. Issue #243 is closed as a stale audit
|
||||
finding: no `_boundsCache`, bounds memo, or cited insertion site exists in the
|
||||
current source, so no replacement cache was invented.
|
||||
|
||||
### D4 — Diagnostics and forced pressure
|
||||
|
||||
- Publish aggregate and per-domain budgets/occupancy/fragmentation through the
|
||||
lifecycle diagnostic artifact.
|
||||
- Add deterministic forced-pressure tests that exceed every existing ceiling,
|
||||
drain fence release, and prove convergence.
|
||||
- Assert manager and physical-owner accounting return to zero at teardown.
|
||||
|
||||
**Complete 2026-07-24.** The lifecycle artifact now serializes validated
|
||||
per-domain rows plus aggregate entry/owner, budget, allocator
|
||||
capacity/usage/fragmentation, traffic, committed CPU, mapped-address-space, and
|
||||
physical GPU totals. The final omitted bounded owner, decoded audio, now
|
||||
receives its startup budget through `RuntimeOptions` and reports decoded bytes
|
||||
and hit/miss/eviction traffic beside animations and the render caches. A
|
||||
deterministic all-domain pressure matrix exceeds every reported ceiling and
|
||||
proves zero-charge convergence. The focused physical-owner suites separately
|
||||
exercise prepared-mesh/staging rejection, composite and standalone texture
|
||||
eviction, fence-delayed release, global-arena range retirement, animation/audio
|
||||
LRU pressure, and alpha-scratch convergence. Release build and the complete
|
||||
8,111-test / 5-skip solution gate are green.
|
||||
|
||||
### D5 — Connected gate
|
||||
|
||||
- Run capped, uncapped, and dense connected routes against Slice C.
|
||||
- Run repeated same-location portal loops and compare first/second/third visit.
|
||||
- Require no missing texture/mesh, no stale-generation release, no monotonic
|
||||
owner/resource growth, and graceful shutdown.
|
||||
- Preserve fixed-camera screenshots and obtain the user's visual gate if the
|
||||
connected route changes any visible frame.
|
||||
|
||||
**Complete 2026-07-24.** The exact D4 source/binary commit `1853a57c` passed
|
||||
the capped and uncapped physical-local nine-stop routes and pinned dense Arwic,
|
||||
with zero failures and graceful transport shutdown in all three processes.
|
||||
Cache-attributed residence was non-monotonic: the third Caul checkpoint fell
|
||||
from the second visit's 50.3/355.0 MiB committed-CPU/physical-GPU residence to
|
||||
38.5/335.1 MiB. Parsed animations stopped at 55 entries, alpha scratch stayed
|
||||
below 16 MiB, and staged/retiring bytes were zero at every checkpoint. The
|
||||
harness's fixed-camera captures passed without a visible-frame change. Exact
|
||||
methodology and measurements:
|
||||
[`../research/2026-07-24-slice-d-unified-residency-report.md`](../research/2026-07-24-slice-d-unified-residency-report.md).
|
||||
|
||||
## 9. Acceptance
|
||||
|
||||
Slice D closes only when:
|
||||
|
||||
1. All state/ownership/generation tests pass.
|
||||
2. Forced-pressure tests prove the configured ceilings, not merely a route
|
||||
that happens to stay below them.
|
||||
3. The third same-location visit plateaus within the documented tolerance.
|
||||
4. Repeated portal loops do not grow logical owners, staged work, resident GPU
|
||||
resources, retiring resources, or parsed animation residence.
|
||||
5. The complete Release build/test suite is green.
|
||||
6. The architecture, roadmap, issues, inventory, and durable memory agree with
|
||||
the implementation.
|
||||
7. Connected screenshots retain Slice C's visual output.
|
||||
|
||||
All seven conditions passed 2026-07-24.
|
||||
|
|
@ -1,484 +0,0 @@
|
|||
# Modern Runtime Slice E — Cost-budgeted streaming and retirement
|
||||
|
||||
**Status:** complete — E0/E1/E2/E3/E4/E5/E6 landed and gated 2026-07-24
|
||||
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
|
||||
**Baseline:** Slice D closeout commit `66690805`
|
||||
**Behavior contract:** no visual-quality or retail-behavior reduction
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Slice E removes whole-window portal publication and retirement transactions
|
||||
from a single update frame. It preserves the existing worker, canonical
|
||||
`GpuWorldState`, presentation owners, retry receipts, world-reveal barrier,
|
||||
and renderer resource owners.
|
||||
|
||||
The change is scheduling and lifecycle architecture:
|
||||
|
||||
- one immutable `StreamingWorkBudget` defines real per-frame limits;
|
||||
- one single-writer scheduler admits and advances explicit work stages;
|
||||
- destination-critical work receives reserved capacity across portal frames;
|
||||
- old generations become unavailable and quiescent immediately;
|
||||
- expensive owner/resource teardown resumes from exact cursors;
|
||||
- publication advances through exact owner receipts under time, byte, entity,
|
||||
upload, and retirement-operation limits;
|
||||
- the existing `WorldRevealCoordinator` remains the only reveal predicate.
|
||||
|
||||
No distance, texture, particle, cell, collision, or presentation quality is
|
||||
reduced.
|
||||
|
||||
## 2. Retail boundary
|
||||
|
||||
Retail teardown is synchronous:
|
||||
|
||||
- `CLandBlock::destroy_static_objects @ 0x0052FA50`;
|
||||
- `CLandBlock::Destroy @ 0x0052FAA0`;
|
||||
- `CObjectMaint::DestroyObjects @ 0x00508C30`.
|
||||
|
||||
Retail portal/cell blocking freezes object maintenance, physics, landscape,
|
||||
game time, and ambient sound while continuing scene/UI/event work:
|
||||
`SmartBox::UseTime @ 0x00455410`.
|
||||
|
||||
Acdream's async adaptation may defer physical cleanup, but may not leave a
|
||||
retired owner active. Full translation:
|
||||
[`../research/2026-07-24-retail-streaming-retirement-pseudocode.md`](../research/2026-07-24-retail-streaming-retirement-pseudocode.md).
|
||||
|
||||
## 3. Existing-owner inventory
|
||||
|
||||
| Concern | Current owner | Current behavior | Slice E action |
|
||||
|---|---|---|---|
|
||||
| Worker inbox/outbox | `LandblockStreamer` | unbounded channels; near jobs prioritized | retain worker; expose bounded, allocation-free single-consumer drain and backlog facts |
|
||||
| Completion apply | `StreamingController._deferredApply` | retained `List`; count-budgeted loads; unload and priority ring bypass | replace with explicit generation/priority FIFO stage queues and exact retained-byte accounting |
|
||||
| Full reload | `BeginFullWindowRetirement` | snapshots and begins every resident retirement synchronously | retain stable snapshot/cursor and advance admissions under budget |
|
||||
| Origin recenter | `OriginRecenterRetirement` | has a cursor but drains it to completion in one call | make the cursor the canonical budgeted admission receipt |
|
||||
| Logical/spatial detach | `GpuWorldState.DetachLandblock/DetachNearLayer` | exact immediate bucket withdrawal; captures entity receipt | preserve; add destination-generation quiesce before cursored detaches |
|
||||
| Retirement ledger | `LandblockRetirementCoordinator` | retryable per-stage/per-entity state, but advances a complete ticket on begin/advance | expose one bounded atomic step at a time; stable FIFO |
|
||||
| Retirement effects | `LandblockPresentationRetirementOwner` | lights, translucency, plugin, terrain, physics, cells/buildings | preserve order; each entity/stage consumes measured budget |
|
||||
| Publication ledger | `LandblockPresentationPipeline` | retryable stage booleans, but `Advance` runs the complete transaction | convert booleans/cursors into resumable atomic publication steps |
|
||||
| Render publication | `LandblockRenderPublisher` | terrain/visibility/AABB/buildings/EnvCells whole stages | retain receipts; meter GPU bytes and split iterable registries where needed |
|
||||
| Physics publication | `LandblockPhysicsPublisher` | rebuilds terrain/cells/buildings/statics/reflood in one call | cursor cell/building/static preparation and commit at safe mutation boundaries |
|
||||
| Static presentation | `LandblockStaticPresentationPublisher` | allocates dictionaries/sorts arrays, then loops prior/plugin entities | prepare stable ordered arrays once; cursor cleanup/light/plugin work |
|
||||
| Mesh upload | `ObjectMeshManager`/`WbMeshAdapter` | existing per-frame renderer owner/budgets | report exact upload bytes/operations to the streaming meter; never move GL ownership |
|
||||
| Reveal | `WorldRevealCoordinator` + `WorldRevealReadinessBarrier` | one correct login/portal predicate | add destination generation/readiness stage facts; never duplicate predicate |
|
||||
| Portal wait cue | `PortalTunnelPresentation` | authored tunnel and centered wait notice port exists | hold tunnel past five seconds and publish diagnostic; never early reveal |
|
||||
| Audio | `OpenAlAudioEngine`/`AudioHookSink` | one-shot owner is not retained in source slot | tag 3-D voices by owner; stop old-generation owners at quiesce |
|
||||
|
||||
## 4. Work model
|
||||
|
||||
### 4.1 Immutable budget
|
||||
|
||||
The App-layer type is equivalent to:
|
||||
|
||||
```csharp
|
||||
public readonly record struct StreamingWorkBudget(
|
||||
TimeSpan MaxUpdateTime,
|
||||
int MaxCompletionAdmissions,
|
||||
long MaxAdoptedCpuBytes,
|
||||
int MaxEntityOperations,
|
||||
long MaxGpuUploadBytes,
|
||||
int MaxGlRetireOperations,
|
||||
float DestinationReserveFraction);
|
||||
```
|
||||
|
||||
All fields are positive and validated as one profile. The initial High/default
|
||||
profile is evidence-tuned, not a reduction in quality:
|
||||
|
||||
- 2.0 ms update-thread streaming work;
|
||||
- 64 completion admissions;
|
||||
- 8 MiB retained CPU payload adoption;
|
||||
- 256 entity operations;
|
||||
- 8 MiB requested GPU upload;
|
||||
- 64 logical GL-retirement admissions;
|
||||
- 75% reserved for destination-critical work while a reveal is pending.
|
||||
|
||||
These are initial scheduling ceilings, not content ceilings. Work continues on
|
||||
later frames until exact convergence. Connected E gates may retune the profile
|
||||
as one atomic change.
|
||||
|
||||
The compatibility `MaxCompletionsPerFrame` setting maps to complete Low/Medium/
|
||||
High/Ultra work profiles during migration. It does not remain an independent
|
||||
hidden second throttle.
|
||||
|
||||
### 4.2 Meter
|
||||
|
||||
`StreamingWorkMeter` is stack/frame scoped and single-thread owned. It records:
|
||||
|
||||
- start/deadline timestamp;
|
||||
- admitted results and retained CPU bytes;
|
||||
- entity operations;
|
||||
- GPU bytes requested;
|
||||
- GL retirement operations;
|
||||
- stage completions, yields, overruns, failures, and oldest-work age.
|
||||
|
||||
Reservation is checked before an atomic operation using its conservative known
|
||||
cost. Elapsed time is checked after every atomic operation. A first operation
|
||||
may run when a dimension is otherwise empty so progress cannot deadlock; any
|
||||
single-operation overrun is named in diagnostics and becomes a required split
|
||||
site.
|
||||
|
||||
### 4.3 Cost estimation
|
||||
|
||||
`LandblockStreamResultCost` is computed once from immutable payloads:
|
||||
|
||||
- terrain vertex/index bytes;
|
||||
- entity count;
|
||||
- mesh-reference count;
|
||||
- EnvCell visibility/shell count;
|
||||
- physics cell/building/GfxObj counts;
|
||||
- prepared payload arrays retained by the completion.
|
||||
|
||||
It never uses process working set as a scheduling unit and never counts the
|
||||
memory-mapped package's address space as adopted bytes.
|
||||
|
||||
## 5. Queue and ordering contract
|
||||
|
||||
One scheduler owns queues by `(generation, priority, stage)`.
|
||||
|
||||
Priorities:
|
||||
|
||||
1. destination collision/required Near scene;
|
||||
2. visible Near;
|
||||
3. ordinary Near;
|
||||
4. Far/speculative;
|
||||
5. retirement cleanup after immediate quiesce.
|
||||
|
||||
Rules:
|
||||
|
||||
- FIFO is exact within one generation/priority.
|
||||
- Generation supersession cancels stale unpublished work and releases its
|
||||
retained-byte charge.
|
||||
- Destination reserve applies only while the canonical reveal generation is
|
||||
pending.
|
||||
- Unreserved capacity is work-conserving: any queue may use it.
|
||||
- At least one noncritical/retirement operation is admitted when capacity
|
||||
remains, preventing starvation during a slow destination.
|
||||
- Failed stages retain their exact receipt and queue position.
|
||||
- Callback reentrancy appends work; it never mutates the active cursor.
|
||||
- `_deferredApply`, `MaxDrainIterations`, the eager priority-radius bypass, and
|
||||
count-only `MaxCompletionsPerFrame` execution disappear at cutover.
|
||||
|
||||
## 6. Immediate quiesce versus deferred release
|
||||
|
||||
At a hard destination-generation edge:
|
||||
|
||||
1. The old world generation is marked unavailable to normal world drawing,
|
||||
picking, radar, status targeting, and collision queries.
|
||||
2. Object/static animation, PhysicsScript, particle emission, and ambient/
|
||||
owner-tagged audio for that generation are frozen or silenced.
|
||||
3. The portal tunnel/UI/event path continues, matching retail's
|
||||
`blocking_for_cells` branch.
|
||||
4. Stable resident landblock IDs are captured once.
|
||||
5. Per-landblock spatial detach and owner/resource release advance from cursors.
|
||||
|
||||
Quiesce is idempotent and generation-scoped. It does not destroy live server
|
||||
identity, replay Hidden/UnHide, or clear the replacement generation.
|
||||
|
||||
For ordinary Near→Far demotion outside a hard portal edge, the exact landblock
|
||||
detach remains the immediate visibility/collision boundary; only its expensive
|
||||
owner cleanup is deferred.
|
||||
|
||||
## 7. Publication contract
|
||||
|
||||
One accepted completion progresses through:
|
||||
|
||||
```text
|
||||
Admitted
|
||||
-> PreparedReceipt
|
||||
-> RenderPrefix
|
||||
-> PhysicsBase
|
||||
-> RenderCellsBuildings
|
||||
-> StaticEntityPresentationAndCollision
|
||||
-> SpatialCommit
|
||||
-> RenderPinsAndEnvCellReplay
|
||||
-> LiveProjectionRecovery
|
||||
-> Complete
|
||||
```
|
||||
|
||||
Each transition owns a retry receipt and known cost. Mutation order remains the
|
||||
existing retail-rooted order. A stage cannot publish partial externally visible
|
||||
state unless its receipt describes and resumes that exact partial prefix.
|
||||
|
||||
Destination reveal consumes only `Complete`/renderer-upload facts belonging to
|
||||
the active destination generation. Far-ring work may continue after reveal.
|
||||
|
||||
## 8. Threading
|
||||
|
||||
- Worker threads build immutable `LandblockBuild`/prepared payloads and enqueue
|
||||
immutable completions only.
|
||||
- The update/render thread is the sole scheduler, meter, world-state,
|
||||
publication, and retirement writer.
|
||||
- GL upload/non-residency/deletion remains with renderer resource owners.
|
||||
- No update operation waits for the worker, a GPU fence, file I/O, or a lock
|
||||
held by render work.
|
||||
- Back-pressure never blocks the update thread. Worker-side bounded queues may
|
||||
wait/cancel; update-side admission is non-blocking.
|
||||
|
||||
## 9. Delivery checkpoints
|
||||
|
||||
### E0 — Oracle, inventory, and contract
|
||||
|
||||
- Record named-retail teardown/blocking pseudocode.
|
||||
- Inventory every current publication/retirement owner and bypass.
|
||||
- Define budget dimensions, atomic work, queue ordering, quiesce, retries,
|
||||
threading, diagnostics, and acceptance.
|
||||
|
||||
**Complete 2026-07-24.**
|
||||
|
||||
### E1 — Typed budgets, meter, cost model, and diagnostics
|
||||
|
||||
- Add validated `StreamingWorkBudgetOptions` to `RuntimeOptions`.
|
||||
- Add pure `StreamingWorkMeter` and deterministic injected-clock tests.
|
||||
- Add exact immutable completion-cost estimation.
|
||||
- Publish per-stage backlog, bytes, oldest age, work/yield/overrun facts through
|
||||
lifecycle artifacts.
|
||||
- Preserve current execution while measuring the would-yield decisions.
|
||||
|
||||
**Complete 2026-07-24.** `RuntimeOptions` now owns one validated scheduling
|
||||
profile (time, admissions, retained CPU bytes, entity operations, requested GPU
|
||||
bytes, GL retire operations, and destination reserve). The single-thread meter
|
||||
has a deterministic clock seam, first-operation progress rule, explicit
|
||||
yield/oversize/failure/overrun facts, and no effect on legacy execution at this
|
||||
checkpoint. Immutable completion payloads receive deterministic charges for
|
||||
their exact array data and logical retained entries; this is deliberately not
|
||||
misreported as CLR heap size. Lifecycle JSON now includes last-frame work,
|
||||
deferred retained bytes/age, and pending publication/retirement counts.
|
||||
|
||||
Validation: complete App suite 3,643 passed / 3 skipped; complete solution
|
||||
8,124 passed / 5 skipped; Release solution build green. Existing priority,
|
||||
retry, stale-generation, and deferred-compaction tests remain green.
|
||||
|
||||
### E2 — Explicit admission queues
|
||||
|
||||
- Replace `_deferredApply` with stable generation/priority queues.
|
||||
- Bound result admission by count and CPU bytes.
|
||||
- Remove `MaxDrainIterations`.
|
||||
- Preserve exact retry position and stale-generation rejection.
|
||||
- Keep current publication atomic until E4, but execute only work admitted by
|
||||
the typed meter.
|
||||
|
||||
**Complete 2026-07-24.** Production now consumes the worker channel through
|
||||
one allocation-free `TryPeek`/`TryRead` source, prices the exact immutable
|
||||
result before adoption, and admits it through the typed completion-count and
|
||||
retained-CPU dimensions. Accepted work lives in reusable stable FIFOs for
|
||||
destination, control, unload, Near, and Far classes. FIFO order is exact inside
|
||||
each class, a blocked publication head does not reorder its tail, callback
|
||||
appends cannot invalidate an active receipt, and exact-reference compaction
|
||||
cannot conflate equal records or GUID/landblock reuse.
|
||||
|
||||
`_deferredApply`, `MaxDrainIterations`, direct priority hunting, and the unload
|
||||
budget bypass are gone. Destination and unload work win queue order but consume
|
||||
the same execution meter. One indivisible publication may make progress after
|
||||
admission work, but that privilege is granted only once per frame and every
|
||||
oversize/overrun remains named. Stale generations consume a bounded admission
|
||||
without retaining their payload. The old completion-count quality setting now
|
||||
selects a complete scaled time/count/byte/operation profile rather than acting
|
||||
as a hidden second throttle.
|
||||
|
||||
Validation: 240 focused App streaming tests and 58 focused Core streamer/
|
||||
controller tests passed; the complete App suite passed 3,656 / 3 skipped, the
|
||||
complete solution passed 8,138 / 5 skipped, and the Release solution build was
|
||||
clean. Physical connected and visual gates are intentionally deferred until
|
||||
E6; the active desktop was RDP and is not valid performance evidence.
|
||||
|
||||
### E3 — Quiesce and budgeted retirement
|
||||
|
||||
- Add destination-generation world quiesce.
|
||||
- Tag/stop owner audio at the immediate edge.
|
||||
- Make full-window and recenter retirement admission cursor/time/entity bounded.
|
||||
- Make each retirement ticket advance by bounded atomic entity/stage work.
|
||||
- Prove no old generation ticks, emits, collides, targets, or renders while its
|
||||
retained memory converges.
|
||||
|
||||
**Complete 2026-07-24.** `WorldGenerationQuiescence` now publishes the hard
|
||||
login/portal edge through one generation-scoped availability state owned by
|
||||
`WorldRevealCoordinator`. The edge withdraws world drawing, picking/radar
|
||||
spatial queries, target selection, object/static animation, PhysicsScript time,
|
||||
particle/effect progression, liveness, spatial reconciliation, and owner-tagged
|
||||
3-D audio while network/event/command, portal presentation, UI, streaming, and
|
||||
destination readiness continue. A superseding reveal remains continuously
|
||||
quiescent and only its exact generation can reopen the world; canonical live
|
||||
records and readiness state remain retained, so the mechanism does not
|
||||
synthesize Hidden/UnHide or rebuild server identity.
|
||||
|
||||
Full-window and shared-origin retirement are now retained frame transactions.
|
||||
Generation invalidation, worker-inbox clearing, accepted-payload release,
|
||||
region clearing, stable resident capture, and landblock detachment all resume
|
||||
from exact cursors through the frame's single `StreamingWorkMeter`.
|
||||
`LandblockRetirementCoordinator` preserves stable FIFO order and advances one
|
||||
entity or one owner stage per reservation across scripts, classifications,
|
||||
lighting, translucency, plugin projection, terrain, physics, cell visibility,
|
||||
building registries, and environment cells. Failures retry the exact unfinished
|
||||
entity/stage without replaying committed work. A landblock detach remains one
|
||||
named indivisible spatial boundary; its complete entity count is charged before
|
||||
the operation.
|
||||
|
||||
Validation: 51 focused reveal/quiescence/retirement tests passed; the complete
|
||||
App suite passed 3,666 / 3 skipped; the complete solution passed 8,148 / 5
|
||||
skipped; Release solution build green. The physical connected performance and
|
||||
visual gate remains deferred to E6 because the active desktop is RDP and is not
|
||||
valid GPU/FPS evidence.
|
||||
|
||||
### E4 — Cursor-budgeted publication
|
||||
|
||||
- Move stable ordered arrays/cost facts into retained receipts.
|
||||
- Cursor static cleanup, static light/collision/plugin publication, physics
|
||||
cell/building work, and environment-cell publication at safe boundaries.
|
||||
- Feed GPU requested-byte and retirement-operation facts from renderer owners.
|
||||
- Prove every failure resumes the exact unfinished operation without replay.
|
||||
|
||||
**Complete 2026-07-24.** One accepted completion now remains at the exact queue
|
||||
head while `LandblockPresentationPipeline` advances retained render, physics,
|
||||
and static receipts through the frame's single meter. Stable entity/Gfx/building
|
||||
and prior-owner arrays are captured once. Terrain upload bytes are charged at
|
||||
the render owner; physics cells/buildings, static collision/light/plugin work,
|
||||
prior static teardown, shadow reflood, building-registry construction, and
|
||||
EnvCell shell construction advance one bounded operation at a time. Building
|
||||
and EnvCell replacements are prepared off-side and become visible only when
|
||||
complete. Failures retain the unfinished cursor and never replay a committed
|
||||
prefix. Settings/native callbacks can defer policy changes but cannot drain
|
||||
publication outside `StreamingController.Tick`.
|
||||
|
||||
The final `GpuWorldState.MutationBatch` remains one deliberate observer-atomic
|
||||
operation: bucket mutation, render-id ownership, activation, and the outer
|
||||
visibility notification cannot span frames without exposing a partial spatial
|
||||
identity. It is time-metered and named for E6 overrun evidence; all iterable
|
||||
owner work now occurs before it.
|
||||
|
||||
Validation: focused render/building/publication/controller tests passed; the
|
||||
complete App suite passed 3,669 / 3 skipped; the complete Release solution
|
||||
passed 8,152 / 5 skipped; Release solution build green. Physical connected
|
||||
performance and visual evidence remains an E6 gate.
|
||||
|
||||
### E5 — Destination reservation and reveal generation
|
||||
|
||||
- Replace eager `PriorityRadius` execution with destination reservations.
|
||||
- Join scheduler destination generation to the existing reveal coordinator.
|
||||
- Keep the tunnel animating and surface the retail wait cue/diagnostic beyond
|
||||
five seconds.
|
||||
- Remove the last count-only execution/bypass paths.
|
||||
|
||||
**Complete 2026-07-24.** `WorldRevealCoordinator` now owns one exact
|
||||
generation/cell/radius reservation from login or portal begin through complete
|
||||
or cancel. `StreamingController` no longer exposes a mutable priority
|
||||
landblock/radius pair: accepted work records the reveal generation that
|
||||
classified it, stale completion cannot consume a replacement generation's
|
||||
reserved lane, and stale teardown cannot clear the replacement reservation.
|
||||
The frame meter records destination and non-destination use independently and,
|
||||
while a reveal is active, protects the configured destination share across
|
||||
wall time, completion admissions, adopted CPU bytes, entity operations,
|
||||
requested GPU bytes, and GL-retirement admissions. Destination work still
|
||||
uses the same global ceilings and the existing first-indivisible-operation
|
||||
progress diagnostic; it does not regain a budget bypass.
|
||||
|
||||
The former ten-second forced materialization path is deleted. An incomplete
|
||||
destination remains in the authored portal scene until the canonical render,
|
||||
composite, and collision predicate is true. After five seconds the retained
|
||||
gameplay UI displays retail's centered
|
||||
`"In Portal Space - Please Wait..."` notice and lifecycle telemetry records the
|
||||
cue once; the tunnel continues its DAT animation and repeats the notice on the
|
||||
retail camera-rotation cadence. Login completion now ends its reveal
|
||||
reservation/quiescence at the same auto-entry edge that exposes the ready
|
||||
world.
|
||||
|
||||
Validation: 91 focused reservation/reveal/teleport/UI tests passed; the
|
||||
complete App suite passed 3,674 / 3 skipped; the complete Release solution
|
||||
passed 8,158 / 5 skipped; Release solution build green with zero warnings.
|
||||
The physical-local routes and final performance/resource assertions remain
|
||||
E6, not E5.
|
||||
|
||||
### E6 — Gates and closeout
|
||||
|
||||
- Deterministic pressure, reentrancy, failure, cancellation, stale-generation,
|
||||
GUID reuse, dungeon, outdoor, and session-reset suites.
|
||||
- Release build and full solution tests.
|
||||
- Physical-local capped/uncapped nine-stop and pinned dense-Arwic routes.
|
||||
- Compare portal p99 and maximum single-frame allocation to Slice A and Slice C.
|
||||
- Require zero `viewport-before-ready`, zero stranded old generation, staged
|
||||
upload, collision, effect, audio, or GPU owner.
|
||||
- Fixed-camera screenshots and subjective visual gate only if pixels change.
|
||||
- Update architecture, inventory, issues, roadmap, divergence register, and
|
||||
durable memory.
|
||||
|
||||
**Complete 2026-07-24.** Deterministic budget, pressure, cancellation,
|
||||
stale-generation, retry, callback-reentrancy, GUID-reuse, quiescence, and
|
||||
resource-ownership gates pass. The Release solution builds and the complete
|
||||
suite passes 8,164 tests with 5 intentional skips.
|
||||
|
||||
The exact `91e82c3c6850fcedbf20322b2ecf4fdcd11a2b2a` Release binary passed
|
||||
capped and uncapped nine-stop routes plus pinned dense Arwic on the physical
|
||||
local AMD display. Every destination materialized only after the canonical
|
||||
render/composite/collision predicate, every client exited gracefully, and
|
||||
every canonical checkpoint ended with zero pending publication, retirement,
|
||||
worker/destination/class backlog, staged upload, composite warmup, residency
|
||||
staging/requested-GPU, or retiring bytes.
|
||||
|
||||
The gate found and corrected two connected-only ownership defects. CPU
|
||||
mesh-cache hits now stage only for a live exact owner, preventing evicted work
|
||||
from recreating stale uploads. Loaded spatial residency is now distinct from
|
||||
world availability, allowing quiesced destination live objects to prepare
|
||||
their render projection without becoming drawable, collidable, pickable,
|
||||
targetable, audible, or simulated before reveal.
|
||||
|
||||
Relative to Slice A, capped/uncapped CPU p99 improved 34.7%/30.4% and largest
|
||||
frame allocation fell 84.7%/82.2%. Relative to Slice C, CPU p99 improved
|
||||
21.4%/16.1% and largest frame allocation fell 33.5%/9.7%. Full evidence:
|
||||
[`../research/2026-07-24-slice-e-cost-budgeted-streaming-report.md`](../research/2026-07-24-slice-e-cost-budgeted-streaming-report.md).
|
||||
|
||||
### Post-closeout portal regression correction — 2026-07-25
|
||||
|
||||
The first retained-scene visual pass exposed four scheduling/ownership defects
|
||||
that the original E6 route did not isolate:
|
||||
|
||||
- a 25x25 shared-origin window admitted 625 detach operations over many
|
||||
frames even though no intermediate old spatial subset was observable;
|
||||
- destination publication could sit behind an unrelated retirement receipt,
|
||||
small-mesh upload count, or composite upload count while most byte budget
|
||||
remained unused;
|
||||
- private paperdoll/appraisal meshes borrowed world ownership and the
|
||||
paperdoll discarded its private object during temporary player
|
||||
unavailability;
|
||||
- ACE could retain an expired GUID in `KnownObjects`, omit CreateObject on
|
||||
revisit, and leave doors, signs, portals, and NPCs permanently absent.
|
||||
|
||||
The correction atomically swaps the old spatial generation into exact deferred
|
||||
receipts, advances only the destination's same-key receipt out of order, and
|
||||
orders destination worker/publication work first. A reveal-generation render
|
||||
profile raises only the small-object mesh count from 8 to 64 and composite
|
||||
count from 16 to 64; all 8 MiB byte/array/buffer/mipmap ceilings remain
|
||||
unchanged. Ordinary work still advances when no destination completion exists.
|
||||
Private viewports now own independent mesh leases and the paperdoll survives
|
||||
temporary SmartBox absence. `DormantLiveEntityStore` retains only cold accepted
|
||||
spawn data after active teardown so an ACE revisit can hydrate through the
|
||||
ordinary generation-safe transaction.
|
||||
|
||||
The world-availability edge now ends at retail's portal/world viewport swap;
|
||||
the one-second `WorldFadeIn` protocol tail still sends LoginComplete and closes
|
||||
teleport state later. The user confirmed faster portal transit, continuous
|
||||
paperdoll presentation, and persistent server-spawned objects after repeated
|
||||
portal travel. Full evidence:
|
||||
[`../research/2026-07-25-portal-regression-closeout.md`](../research/2026-07-25-portal-regression-closeout.md).
|
||||
|
||||
## 10. Acceptance
|
||||
|
||||
Slice E closes only when:
|
||||
|
||||
1. No priority or unload path bypasses all work budgets.
|
||||
2. No frame admits another operation after a configured count/byte/entity
|
||||
dimension is exhausted. The documented first-indivisible-operation rule is
|
||||
the sole oversize path and remains named.
|
||||
3. Every wall-time overrun names one indivisible operation and the scheduler
|
||||
admits no later operation after its deadline. Because the meter uses elapsed
|
||||
wall time on a non-real-time OS, thread preemption can make an otherwise
|
||||
bounded atomic operation observe more than 2 ms; the physical gate therefore
|
||||
also requires materially improved route/portal p99 and no repeatable
|
||||
algorithmic tail.
|
||||
4. Old generations become unavailable, tick-frozen, and audio-silent at the
|
||||
hard transition edge.
|
||||
5. Retirement/publication retries resume exact cursors without replay.
|
||||
6. Deferred retained CPU bytes stay within their queue budget.
|
||||
7. Reveal is possible only for the active destination generation and the
|
||||
existing readiness predicate.
|
||||
8. Portal-window p99 and largest frame allocation materially improve over the
|
||||
post-Slice-A physical baseline.
|
||||
9. Complete connected routes end with zero pending publication, retirement,
|
||||
upload, effect, collision, audio, and stale-generation owners.
|
||||
10. Visual output/range/quality and retail gameplay behavior are unchanged.
|
||||
|
|
@ -1,788 +0,0 @@
|
|||
# Modern Runtime Slices F/G — Incremental Render Scene and Delta Submission
|
||||
|
||||
**Date:** 2026-07-24
|
||||
|
||||
**Status:** approved 2026-07-24; Slices F and G complete
|
||||
|
||||
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
|
||||
|
||||
**Baseline:** Slice E closeout commits `91e82c3c` and `e7d9d6fa`
|
||||
|
||||
**Behavior contract:** no change to pixels, view distance, particles, PView
|
||||
visibility, clipping, draw order, selection, lighting, animation, or
|
||||
retail-faithful gameplay
|
||||
|
||||
## 0. Execution ledger
|
||||
|
||||
| Unit | State | Evidence / rollback |
|
||||
|---|---|---|
|
||||
| F0a — partition-input referee | complete | Diagnostic-only `CurrentRenderSceneOracle`; no production draw decision changed. |
|
||||
| F0b — survivor/dispatcher/selection referee | complete | Exact PView routes, dispatcher candidates and final group payloads, material/alpha/clip/light/selection fields, and accepted picking parts. Release gate: 3,690 App tests / 3 skips and 8,174 complete-solution tests / 5 skips. |
|
||||
| F1 — scene types and contained adapter | complete | `Arch 2.1.0` is pinned in App only behind acdream contracts. Five narrow archetypes, generation/incarnation/sequence gates, borrowed-query invalidation, memory accounting, deterministic digesting, and update-thread enforcement pass 11 focused tests. Release gate: 3,701 App tests / 3 skips and 8,185 complete-solution tests / 5 skips. |
|
||||
| F2 — static projection journal | complete | Static and EnvCell-shell projections now append ordered deltas only after the final activation receipt and exact detach receipt. Same-landblock rehydrate reconciles retained/new/omitted identities; stale Far completions are inert. Seven focused lifecycle tests pass inside the 3,708-App / 8,192-solution Release gate. The scene remains unconstructed and non-drawing in production. |
|
||||
| F3 — live/equipped projection | complete | Exact EntityReady/resource teardown, visibility, attachment pose/removal, and active-only final-frame seams now drive generation/incarnation-gated live records. Duplicate CreateObject, pending/loaded rebucket, hidden/appearance, reentrancy, session clear, attachment, and GUID-generation replacement pass 11 focused tests. Production still constructs no render scene. |
|
||||
| F4 — dynamic indices | complete | The contained scene now maintains outdoor/static, per-cell static/dynamic, special dynamic-route, translucent, selectable, light-candidate, and dirty indices incrementally. Final-frame live/equipped synchronization remains active-only, and active animated statics are synchronized from the scheduler's active workset rather than a resident-world scan. The production scene remains unconstructed and non-drawing. |
|
||||
| F5 | complete | Exact `81e2f1a5` passed capped and uncapped nine-stop routes plus uncapped dense Arwic. Across the final checkpoints the referee completed 1,663 + 1,677 + 81 comparisons with zero mismatches, zero pending deltas, zero rejected operations, exact binary/source identity, and graceful shutdown. Evidence: `docs/research/2026-07-24-slice-f5-render-scene-shadow-gate.md`. |
|
||||
| G0 — borrowed frame product | complete | Two reusable arenas publish exact generation/frame-stamped borrowed views for scene candidates, cell ranges, transforms, classification, light sets, selection, PView/clip references, counts, and source digest. Release/reuse invalidates every copied view; abort never publishes; frame N may build while N-1 is borrowed; the warm path allocates zero bytes. Eight focused tests pass inside the 3,743-App / 8,227-solution Release gate. |
|
||||
| G1 — scene-query replacement | complete | Exact `e346f8bb` passed capped and uncapped nine-stop routes plus uncapped dense Arwic: 29,392 + 29,025 + 1,522 same-frame candidate comparisons, zero mismatches, equal counts/digests at every checkpoint, exact binary/source identity, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g1-scene-query-candidate-gate.md`. |
|
||||
| G2 — packed dispatcher input | complete | Exact `f9829d5f` passed capped and uncapped nine-stop routes plus dense Arwic. Candidate order, packed input, classified output, and selection each completed 16,879 + 17,282 + 770 comparisons with zero mismatch; journal/rejection counts were zero and shutdown graceful. Evidence: `docs/research/2026-07-25-slice-g2-packed-dispatcher-gate.md`. |
|
||||
| G3 — retained classification/storage | complete | Exact `6a026c5a` passed capped and uncapped nine-stop routes plus dense Arwic with 17,069 + 16,827 + 757 exact comparisons in every channel, zero mismatch, warm cross-frame reuse, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g3-retained-classification-gate.md`. |
|
||||
| G4 — production cutover | complete | Exact cutover `ef1d263337997bb030eadb7b8e71d73dc659907a` makes the retained frame product the production entity source at the existing five retail PView stages. Exact `03b10183` passed capped/uncapped nine-stop and uncapped dense-Arwic automation with 46,599 total product comparisons and zero mismatch. After the independent portal/private-view/revisit corrections, the user accepted the connected retained-path visual matrix on 2026-07-25: server objects and paperdoll survive travel, distant use works, `/ls` and spell recalls both materialize through the retail purple haze, and neither character pop nor recall tail remains. Evidence: `docs/research/2026-07-25-slice-g4-production-cutover-automated-gate.md` and `docs/research/2026-07-25-portal-regression-closeout.md`. |
|
||||
| G5 — old-path retirement and production closeout | complete | Exact `10ccce3f` removes `InteriorEntityPartition` from normal production: meshes and attached particles consume retained route ranges, while the legacy partition remains only for standalone tests and explicit diagnostic/oracle probes. Exact `14fbe92b` passes the seven-checkpoint lifecycle/reconnect gate after hardening the optional monitor-refresh boundary. The user visual gate, capped/uncapped/dense correctness routes, ordinary 519.7-FPS production profile, 3,826 App tests / 3 skips, and 8,335 complete-solution tests / 5 skips pass. The measured 22.34 KiB/frame remainder is assigned to Slice I1. Evidence: `docs/research/2026-07-25-slice-g5-production-profile.md`. |
|
||||
|
||||
The exact pre-F/G runtime rollback anchor remains `e7d9d6fa`. F0 is
|
||||
non-drawing diagnostic infrastructure and therefore is not a visual-cutover
|
||||
rollback unit. Each later commit that changes the production draw source is
|
||||
listed in section 10 before it is offered for a connected gate.
|
||||
|
||||
## 1. Decision record
|
||||
|
||||
### 1.1 Why this work has a separate approval gate
|
||||
|
||||
Slices A–E optimized measurement, prepared content, residency, streaming, and
|
||||
retirement without replacing the renderer's scene model. Slices F/G resume the
|
||||
MP3 work that the user explicitly deferred on 2026-07-05:
|
||||
|
||||
- Slice F introduces a second, render-only projection in non-drawing shadow
|
||||
mode.
|
||||
- Slice G makes that projection the renderer's production input and removes the
|
||||
old whole-world enumeration/partition path.
|
||||
|
||||
This is a materially larger regression surface than changing an isolated
|
||||
scheduler or cache. A wrong cell bucket, incarnation, transform, material
|
||||
variant, or draw-order classification can produce the historical
|
||||
stairs/doorway/player-vanish bug classes while still compiling and appearing
|
||||
faster. The approval gate therefore remains binding.
|
||||
|
||||
Approval had to be explicit and recorded as:
|
||||
|
||||
> Approved — continue with all slices.
|
||||
|
||||
The user supplied that approval on 2026-07-24 after reviewing the post-E
|
||||
evidence and rewrite risk. Runtime work therefore proceeds from the exact
|
||||
pre-F/G anchor `e7d9d6fa`.
|
||||
|
||||
### 1.2 Post-Slice-E evidence
|
||||
|
||||
Physical-local Release captures on exact binary
|
||||
`91e82c3c6850fcedbf20322b2ecf4fdcd11a2b2a` produced:
|
||||
|
||||
| Capture | CPU p50 / p95 / p99 | GPU p50 / p95 / p99 |
|
||||
|---|---:|---:|
|
||||
| Uncapped nine-stop | 1.216 / 4.216 / 5.450 ms | 0.725 / 2.486 / 2.666 ms |
|
||||
| Dense Arwic uncapped | 2.652 / 4.153 / 5.736 ms | 1.576 / 2.504 / 2.662 ms |
|
||||
|
||||
Dense Arwic exercised 15,530 entities and 111 animated entities. Its CPU
|
||||
median is already below the provisional 3.33 ms / 300 FPS target. Therefore:
|
||||
|
||||
- F/G is not needed to claim that one provisional median target.
|
||||
- CPU p95/p99 still exceed GPU p95/p99, leaving measurable CPU headroom.
|
||||
- The current path still scales with the resident entity population rather
|
||||
than only with scene changes and visible buckets.
|
||||
- The expected win is lower CPU utilization, better dense-scene scaling,
|
||||
lower tail cost, and a clean future snapshot/headless seam.
|
||||
- A dramatic average-FPS increase is possible but is not promised.
|
||||
|
||||
The go/no-go decision is consequently about accepting architectural rewrite
|
||||
risk for throughput headroom and a cleaner long-term runtime boundary, not
|
||||
about repairing an existing visual or stability failure.
|
||||
|
||||
## 2. Current owner map
|
||||
|
||||
| Concern | Current owner | Current cost/contract | F/G treatment |
|
||||
|---|---|---|---|
|
||||
| Logical live identity and accepted state | `LiveEntityRuntime` | sole GUID/incarnation authority | unchanged; emits exact projection facts only |
|
||||
| Spatial landblock membership | `GpuWorldState` | sole loaded/pending bucket authority | unchanged; journals committed render projection deltas |
|
||||
| Static publication/retirement | `LandblockPresentationPipeline` and retirement coordinator | retained exact Slice-E receipts | append render deltas at existing committed receipt boundaries |
|
||||
| Live presentation resources | `EntitySpawnAdapter` | exact per-incarnation mesh/composite ownership | unchanged; appearance facts copied into render projection |
|
||||
| Equipped-child presentation | `EquippedChildRenderController` | final current transform and visibility | copied into equipped projection archetype |
|
||||
| Static animation | `RetailStaticAnimatingObjectScheduler` | final root transform for active animated statics | active-only synchronization into dynamic transform components |
|
||||
| Frame camera/root/building facts | `WorldRenderFrameBuilder` | borrowed one-frame facts | retained |
|
||||
| Retail cell visibility | `PortalVisibilityBuilder` / `RetailPViewRenderer` | retail PView and viewcone oracle | retained without algorithm changes |
|
||||
| Per-frame entity split | `InteriorEntityPartition` | scans every resident `WorldEntity` | replaced by incremental scene indices in G |
|
||||
| Visibility walk/classification | `WbDrawDispatcher` | walks entity/mesh pairs and rebuilds instance groups | consumes packed frame candidates and dirty instance ranges in G |
|
||||
| Alpha order | `RetailAlphaQueue` | shared stable far-to-near ordering | retained |
|
||||
| Selection | retail selection scene/sink | visible part publication and lighting | same accepted parts and transforms from frame view |
|
||||
| Lighting | `LightManager` plus dispatcher selection | per-object retail light set | same algorithm over packed bounds |
|
||||
| GL ownership | `WbMeshAdapter`, texture/residency owners, dispatcher buffers | render-thread only | unchanged |
|
||||
|
||||
`GpuWorldState` remains the canonical spatial projection. The new render scene
|
||||
is a derived, disposable presentation index. It cannot answer gameplay,
|
||||
network, collision, inventory, interaction, radar, target, or persistence
|
||||
questions.
|
||||
|
||||
## 3. Target boundary
|
||||
|
||||
### 3.1 Acdream-owned API
|
||||
|
||||
Arch is an implementation detail in `AcDream.App`. No Arch type crosses an
|
||||
acdream interface, enters Core, or appears in a test fixture outside App.
|
||||
|
||||
The boundary is equivalent to:
|
||||
|
||||
```csharp
|
||||
internal interface IRenderScene
|
||||
{
|
||||
RenderSceneGeneration Generation { get; }
|
||||
RenderProjectionCounts Counts { get; }
|
||||
|
||||
RenderDeltaApplyResult Apply(ReadOnlySpan<RenderProjectionDelta> deltas);
|
||||
void SynchronizeDynamicSources(in DynamicProjectionSyncInput input);
|
||||
RenderSceneDigest BuildDigest(RenderSceneDigestBuffer reuse);
|
||||
RenderSceneQuery OpenQuery();
|
||||
void Clear(RenderSceneGeneration replacementGeneration);
|
||||
}
|
||||
```
|
||||
|
||||
The exact API may split mutation, query, and diagnostics into narrower
|
||||
interfaces, but these rules are fixed:
|
||||
|
||||
- mutation is update-thread only;
|
||||
- draw receives only a borrowed read/query surface;
|
||||
- draw cannot call `LiveEntityRuntime`, `GpuWorldState`, or `WorldEntity`
|
||||
dictionaries;
|
||||
- Arch entities and `.Value` primitives stay inside the scene implementation;
|
||||
- all external identities are acdream-owned wrapper types;
|
||||
- session/world generations are explicit;
|
||||
- stale incarnation deltas are rejected rather than applied to a reused slot.
|
||||
|
||||
### 3.2 Identity
|
||||
|
||||
Use distinct wrapper identities:
|
||||
|
||||
```text
|
||||
RenderProjectionId
|
||||
RenderOwnerIncarnation
|
||||
RenderSceneGeneration
|
||||
RenderSpatialBucket
|
||||
RenderAssetHandle
|
||||
```
|
||||
|
||||
Projection keys are derived presentation identities, not a second gameplay
|
||||
GUID map:
|
||||
|
||||
- static: world generation + canonical landblock + current static entity id;
|
||||
- live root: current local `WorldEntity.Id` + accepted instance sequence;
|
||||
- equipped child: parent local id + attachment slot/child local id +
|
||||
incarnation;
|
||||
- EnvCell: world generation + cell id.
|
||||
|
||||
The scene may keep a source-key-to-projection lookup solely to apply deltas. It
|
||||
may not expose that lookup as object identity to gameplay consumers.
|
||||
|
||||
### 3.3 Component set
|
||||
|
||||
Initial packed components:
|
||||
|
||||
```text
|
||||
RenderTransform
|
||||
PreviousRenderTransform
|
||||
RenderMeshSet
|
||||
RenderMaterialVariant
|
||||
RenderSpatialResidency
|
||||
RenderWorldBounds
|
||||
RenderFlags
|
||||
RenderDegradeState
|
||||
RenderSortKey
|
||||
RenderOwnerIncarnation
|
||||
RenderDirtyMask
|
||||
```
|
||||
|
||||
Archetypes remain narrow:
|
||||
|
||||
- outdoor static;
|
||||
- indoor-cell static;
|
||||
- live/dynamic root;
|
||||
- active animated static;
|
||||
- equipped child;
|
||||
- translucent/light-bearing variants only where query density justifies them.
|
||||
|
||||
Do not attach every component to every projection. Effects, particles, and
|
||||
lights remain in their existing owners during F/G unless a measured query
|
||||
benefit and an exact parity fixture justify a packed reference.
|
||||
|
||||
### 3.4 Delta journal
|
||||
|
||||
One update-thread-owned `RenderProjectionJournal` receives:
|
||||
|
||||
```text
|
||||
Register
|
||||
UpdateTransform
|
||||
UpdateAppearance
|
||||
UpdateFlags
|
||||
Rebucket
|
||||
Unregister
|
||||
ClearGeneration
|
||||
```
|
||||
|
||||
Each delta carries:
|
||||
|
||||
- scene generation;
|
||||
- projection source key;
|
||||
- exact owner incarnation;
|
||||
- monotonically increasing journal sequence;
|
||||
- complete payload for the changed channel.
|
||||
|
||||
Rules:
|
||||
|
||||
- structural order is exact;
|
||||
- updates may coalesce only within one host tick, after the most recent
|
||||
structural edge for the same projection/incarnation;
|
||||
- register/unregister and generation boundaries never coalesce away;
|
||||
- unregister of an old incarnation cannot remove its replacement;
|
||||
- a journal drains fully at the update-to-render boundary;
|
||||
- any carry-over at frame publication is an invariant failure;
|
||||
- callback reentrancy appends after the current sequence and cannot invalidate
|
||||
active iteration;
|
||||
- no bounded queue or worker thread is introduced in F/G—the accepted host
|
||||
thread remains the only writer.
|
||||
|
||||
### 3.5 Static versus dynamic synchronization
|
||||
|
||||
Static projections are change-driven:
|
||||
|
||||
- landblock spatial publication registers them once;
|
||||
- appearance or static-animation membership changes issue explicit deltas;
|
||||
- rebucket/retirement removes them once.
|
||||
|
||||
Dynamic root and equipped-child transforms are synchronized after animation,
|
||||
physics, remote motion, local-player projection, and attachment transforms
|
||||
have reached their existing final frame state. The synchronizer walks only
|
||||
the scene's dynamic/active-animated source registry, compares packed final
|
||||
facts, and emits updates for changed records. It never scans all resident
|
||||
statics.
|
||||
|
||||
This active-only synchronization is deliberate. Retrofitting presentation
|
||||
version counters into every simulation setter during F would broaden the
|
||||
change into gameplay/physics ownership. A later measured slice may replace
|
||||
the comparison with versioned producers without changing `IRenderScene`.
|
||||
|
||||
## 4. Referee before the match
|
||||
|
||||
The comparison oracle lands before Arch or shadow storage.
|
||||
|
||||
`CurrentRenderSceneOracle` captures the current accepted render input after
|
||||
PView construction and before dispatcher classification. Its deterministic
|
||||
digest contains, in stable sort order:
|
||||
|
||||
- source/projection identity and incarnation;
|
||||
- spatial class: outdoor static, cell static, dynamic, equipped;
|
||||
- canonical landblock and full cell;
|
||||
- root transform and world bounds;
|
||||
- mesh ids and part transforms in order;
|
||||
- material/palette/texture replacement identity;
|
||||
- draw/ancestor/hidden/translucency/degrade flags;
|
||||
- active-animation classification;
|
||||
- PView survivor class and clip-route identity;
|
||||
- expected selection part ids;
|
||||
- opaque/alpha classification identity.
|
||||
|
||||
The oracle is validated on the current production path first. It must be:
|
||||
|
||||
- deterministic across repeated unchanged frames;
|
||||
- allocation-free when disabled;
|
||||
- bounded and reusable when enabled;
|
||||
- independent of dictionary enumeration order;
|
||||
- continuously sampled during login, portal transit, reveal, movement,
|
||||
animation, appearance change, and teardown—not only at stable checkpoints.
|
||||
|
||||
The oracle does not decide what retail should draw. It records what the
|
||||
already accepted current path supplies, so F/G cannot silently redefine
|
||||
parity in favor of the new implementation.
|
||||
|
||||
## 5. Slice F — Non-drawing shadow render scene
|
||||
|
||||
### F0 — Current-path oracle
|
||||
|
||||
1. Add acdream-owned digest/value types and reusable buffers.
|
||||
2. Capture the current partition and dispatcher candidate identity without
|
||||
changing draw behavior.
|
||||
3. Add deterministic unit fixtures for static, dynamic, animated, equipped,
|
||||
translucent, hidden, duplicate ids across landblocks, and GUID reuse.
|
||||
4. Extend canonical lifecycle artifacts with digest generation, counts,
|
||||
mismatch count, journal depth, and scene bytes.
|
||||
5. Run the current pipeline alone through the canonical route and establish
|
||||
the referee's own stability.
|
||||
|
||||
**Commit gate:** no Arch dependency; identical screenshots; no material
|
||||
performance regression with comparison disabled.
|
||||
|
||||
### F1 — Scene types and contained Arch adapter
|
||||
|
||||
1. Verify Arch's current license and selected package API; pin one exact
|
||||
version in `AcDream.App` only.
|
||||
2. Add `IRenderScene`, wrapper ids, component structs, query contracts, and
|
||||
`ArchRenderScene`.
|
||||
3. Add an architecture test preventing Arch namespaces outside the allowed App
|
||||
folder and preventing primitive id extraction outside the owner assembly.
|
||||
4. Add memory accounting for chunks, lookup/index storage, journal buffers,
|
||||
and retained synchronization sources.
|
||||
5. Prove create/update/remove/clear and stale-incarnation rejection in pure App
|
||||
tests.
|
||||
|
||||
**Commit gate:** the scene is unbound and non-drawing; full suite green.
|
||||
|
||||
**Landed evidence:** the package/API/license decision is recorded in
|
||||
`docs/research/2026-07-24-arch-render-scene-dependency.md`. Production
|
||||
composition does not construct `ArchRenderScene`; F1 therefore changes no
|
||||
runtime path. Source/reflection architecture tests confine all Arch types and
|
||||
primitive-ID extraction to `AcDream.App/Rendering/Scene/Arch`. Pure App tests
|
||||
cover every structural/channel operation, generation replacement, stale
|
||||
incarnation and sequence rejection, borrowed-query invalidation, deterministic
|
||||
digest order, retained-memory accounting, and cross-thread mutation rejection.
|
||||
The Release gate is 3,701 App tests / 3 skips and 8,185 complete-solution tests
|
||||
/ 5 skips.
|
||||
|
||||
### F2 — Static projection journal
|
||||
|
||||
1. Append Register deltas from the existing final landblock spatial publication
|
||||
receipt.
|
||||
2. Append Unregister deltas from the exact retirement/demotion receipt.
|
||||
3. Preserve owner landblock identity independently of the caller's current
|
||||
draw landblock—the #119 cache lesson.
|
||||
4. Mirror indoor cell statics, outdoor statics, scenery, building shells, and
|
||||
EnvCell projection identity without changing their resource ownership.
|
||||
5. Make same-landblock rehydrate a reconcile operation: retained identities
|
||||
update, omitted identities unregister, and new identities register.
|
||||
6. Clear by exact world generation on login, portal supersession, session
|
||||
reset, and graceful shutdown.
|
||||
|
||||
**Commit gate:** shadow static digest equals the referee over deterministic
|
||||
publication/retirement permutations.
|
||||
|
||||
### F3 — Live and equipped projection journal
|
||||
|
||||
1. Register only after the current live record, materialized projection, and
|
||||
presentation resources have committed.
|
||||
2. Use exact local id + instance sequence; never server GUID alone.
|
||||
3. Journal loaded/pending visibility edges without treating rebucket as
|
||||
logical recreate.
|
||||
4. Mirror appearance, mesh refs, palette/material variants, draw flags,
|
||||
ancestor visibility, full cell, and bounds.
|
||||
5. Mirror equipped children after their current attachment transform update.
|
||||
6. Unregister exactly once on pickup/parenting/withdraw/delete/session reset,
|
||||
rejecting callbacks from displaced generations.
|
||||
|
||||
**Commit gate:** duplicate CreateObject, reentrant callback, pending-to-loaded,
|
||||
loaded-to-loaded, hidden, parent/child, delete/recreate, and GUID-reuse tests
|
||||
have zero leaked or stale projections.
|
||||
|
||||
### F4 — Dynamic synchronization and incremental indices
|
||||
|
||||
1. Synchronize final current-frame transforms for live roots, active animated
|
||||
statics, and equipped children.
|
||||
2. Maintain outdoor-static, per-cell-static, dynamic, translucent, selectable,
|
||||
light-candidate, and dirty-record indices incrementally.
|
||||
3. Preserve `InteriorEntityPartition.IsIndoorCellId` semantics.
|
||||
4. Preserve retail's special dynamic routing:
|
||||
- outdoor dynamics under an interior root enter the outside stage;
|
||||
- exit-portal-straddling dynamics may enter both stages;
|
||||
- out-of-flood indoor dynamics remain members but fail the per-dynamic
|
||||
viewcone cull.
|
||||
5. Assert index counts and membership against the current partition every
|
||||
sampled frame.
|
||||
|
||||
**Commit gate:** no whole-static-world synchronization in a steady frame;
|
||||
unchanged dynamic records emit no update.
|
||||
|
||||
### F5 — Continuous shadow comparison
|
||||
|
||||
1. Apply the journal and build the shadow digest at the update/render boundary.
|
||||
2. Compare continuously at a fixed diagnostic cadence and at every canonical
|
||||
checkpoint.
|
||||
3. Record the first exact mismatch with both projections and source channel;
|
||||
never log an unbounded mismatch storm.
|
||||
4. Run deterministic portal, churn, animation, appearance, hidden, selection,
|
||||
and teardown tests.
|
||||
5. Run capped and uncapped nine-stop routes plus dense Arwic on the physical
|
||||
display.
|
||||
6. Prove:
|
||||
- zero mismatches;
|
||||
- zero stale delta applications;
|
||||
- zero journal carry-over;
|
||||
- zero retained old-generation scene state;
|
||||
- bounded shadow memory;
|
||||
- no pixel changes because the shadow does not draw.
|
||||
|
||||
**Slice F gate:** all evidence above passes before any production consumer is
|
||||
switched. F may temporarily cost memory and a small amount of CPU in diagnostic
|
||||
mode; those costs are reported separately and shadow comparison is disabled by
|
||||
default after its gate.
|
||||
|
||||
#### F5 connected-gate correction record
|
||||
|
||||
The first physical capped nine-stop route on exact commit `056bbd4e` completed
|
||||
the harness and graceful shutdown, but the shadow referee rejected the route at
|
||||
the same-location Sawato revisit. The first mismatch was a missing live root
|
||||
(`sourceChannel=live`, `field=presence`), while journal pending and rejected
|
||||
delta counts remained zero. This proved the problem was source-workset
|
||||
reconciliation rather than a drain failure.
|
||||
|
||||
The derived live journal had been synchronizing only records it already
|
||||
retained. Once a root projection was absent, that loop could not recover it.
|
||||
The correction sources ordinary world roots from the canonical active spatial
|
||||
workset already owned by `LiveEntityRuntime`. Attached children deliberately
|
||||
remain callback-authoritative and are refreshed only while retained, preventing
|
||||
removed equipment from being resurrected.
|
||||
|
||||
The same route also showed that the diagnostic referee was re-hashing immutable
|
||||
selection polygons and vertices every sampled frame. Geometry fingerprints are
|
||||
now cached by GfxObj and mesh identity, and all retained projection collection
|
||||
walks use allocation-free indexed access. Permanent tests cover root recovery,
|
||||
attachment non-resurrection, cached selection geometry, retained transform
|
||||
updates, and active animated-static synchronization. The capped, uncapped, and
|
||||
dense-town routes are all rerun from the corrective commit; no F5 success is
|
||||
claimed from the rejected `056bbd4e` route.
|
||||
|
||||
The next exact capped route on `91463db5` completed all nine stops and shut
|
||||
down gracefully, but F5 again rejected it. A transition frame reached the
|
||||
post-render referee with 5,111 pending deltas because the only ordinary drain
|
||||
was embedded in the spatial reconciler, which is intentionally skipped while
|
||||
the world is quiesced. Later revisit frames also diverged, but the bounded
|
||||
first-mismatch recorder correctly retained the earlier journal-ordering fault.
|
||||
The correction makes scene publication an explicit final update-frame commit
|
||||
phase after streaming, network, teleport, camera, and every conditional
|
||||
spatial reconcile. It runs even while world simulation is quiesced. The former
|
||||
reconciler drain is removed, leaving one ordinary update/render-boundary
|
||||
publication; session reset retains its separate ordered teardown drain. The
|
||||
next capped redrive must prove both zero pending deltas and expose or clear any
|
||||
remaining projection mismatch before uncapped evidence begins.
|
||||
|
||||
The exact `9fab1feb` capped route proved the final commit boundary: no pending
|
||||
or rejected delta mismatch recurred. At Sawato revisit the referee then
|
||||
preserved the remaining fault as
|
||||
`sourceChannel=live id=projection:03000000000F4279 field=presence`.
|
||||
Category counts showed two equipped children missing while every ordinary root
|
||||
and static projection matched. The equipped controller intentionally removes
|
||||
child presentation during portal transit without discarding the accepted
|
||||
parent relation; destination recomposition publishes `ProjectionPoseReady`,
|
||||
not a second logical `EntityReady`. The live journal incorrectly required the
|
||||
child to be already retained before accepting that callback. The correction
|
||||
treats the equipped owner's composed-pose callback as the authoritative
|
||||
attached-child presence/update edge. A permanent test proves a current
|
||||
attachment removed for presentation is registered again on its first
|
||||
destination pose, while passive synchronization still cannot resurrect a
|
||||
removed attachment. The exact corrected commit must repeat the capped route
|
||||
before uncapped and dense-town gates.
|
||||
|
||||
The exact `bb1f4a64` capped route showed that destination-pose recovery covers
|
||||
attachments which are recomposed, but Sawato's retained population has seven
|
||||
children whose accepted relation is not replayed on revisit. The first missing
|
||||
identity changed with that population (`projection:03000000000F4286`);
|
||||
baseline had 43 roots + 8 children and revisit had the same 43 roots but only
|
||||
1 retained child. Existing rendering still presents those children through
|
||||
the rehydrated spatial entity set.
|
||||
|
||||
The lifecycle correction is to distinguish spatial withdrawal from logical
|
||||
unregistration. `ProjectionRemoved` means an equipped child left cell
|
||||
presentation; it now retains the exact scene identity with `Draw` inactive.
|
||||
The existing projection-visibility edge can reactivate that record on
|
||||
same-location rehydration, and a destination pose can still refresh it.
|
||||
`OnResourceUnregister` remains the only logical teardown edge. Tests now pin
|
||||
inactive retention, passive non-reactivation, exact reactivation, duplicate
|
||||
removal, and final logical teardown. Missing-projection diagnostics also carry
|
||||
the expected class, server GUID, local ID, and parent cell for any further
|
||||
failure. Repeat capped F5 on the exact corrective commit before advancing.
|
||||
|
||||
The exact corrective commit `81e2f1a5` passed the complete physical gate:
|
||||
|
||||
- capped nine-stop route `connected-r6-soak-20260724-232827`: 1,663/1,663
|
||||
successful comparisons;
|
||||
- uncapped nine-stop route `connected-r6-soak-20260724-233826`: 1,677/1,677
|
||||
successful comparisons;
|
||||
- uncapped dense-Arwic route `connected-dense-town-20260724-234807`: 81/81
|
||||
successful comparisons.
|
||||
|
||||
Every canonical checkpoint had the shadow enabled, the active projection count
|
||||
equal to the current-path oracle, no first mismatch, no journal carry-over, and
|
||||
no rejected delta. All three processes used a Release binary whose embedded
|
||||
commit exactly matched source and all closed gracefully. This closes Slice F.
|
||||
The shadow scene remains diagnostic-only and is safe to retain if a later
|
||||
production cutover is reverted. The pre-cutover runtime rollback anchor remains
|
||||
`e7d9d6fa`.
|
||||
|
||||
## 6. Slice G — Frame product and production cutover
|
||||
|
||||
### G0 — Borrowed double-buffered frame product
|
||||
|
||||
Add two reusable `RenderFrameArena` instances. Building frame N writes the
|
||||
arena not borrowed by draw N-1 and publishes a generation-stamped
|
||||
`RenderFrameView`.
|
||||
|
||||
The view contains spans/ranges for:
|
||||
|
||||
- visible outdoor static candidates;
|
||||
- visible per-cell static candidates keyed by PView order;
|
||||
- dynamic candidates;
|
||||
- final root/part transforms;
|
||||
- opaque and alpha classification records;
|
||||
- per-object light sets;
|
||||
- selection parts and lighting;
|
||||
- existing PView/clip products by borrowed reference;
|
||||
- diagnostic counts and source digest.
|
||||
|
||||
Rules:
|
||||
|
||||
- no extra frame of latency;
|
||||
- no render thread is introduced;
|
||||
- a view is invalid after its arena is reused;
|
||||
- no consumer retains spans across frame completion;
|
||||
- abort recycles the incomplete arena without publishing it;
|
||||
- world generation and frame sequence must match at draw.
|
||||
|
||||
**Completed 2026-07-24.** `RenderFrameExchange` owns exactly two retained
|
||||
arenas. A published view is accessible only through a validated borrow token;
|
||||
release invalidates every struct copy, and arena reuse advances its epoch.
|
||||
Publication requires a source digest from the same scene generation, while
|
||||
borrowing requires the exact expected generation and frame sequence. The
|
||||
non-published arena alone may be reused, so an aborted build cannot destroy the
|
||||
last completed product. PView/clip products remain borrowed references and all
|
||||
variable-size payloads use retained arrays. The 1,000-frame warm regression
|
||||
allocates zero bytes. The product is deliberately uncomposed until G1 supplies
|
||||
scene-query data; G0 therefore changes no production draw behavior and is not a
|
||||
visual rollback unit.
|
||||
|
||||
### G1 — Replace whole-world partition with scene queries
|
||||
|
||||
1. Query outdoor statics directly.
|
||||
2. Query each PView-visible cell bucket in the exact existing far-to-near order.
|
||||
3. Query the dynamic set once, then apply the existing per-dynamic viewcone and
|
||||
outside-stage/straddle rules.
|
||||
4. Preserve look-in cells as a distinct landscape-stage source.
|
||||
5. Preserve particle-owner filtering against the exact mesh survivor set.
|
||||
6. Compare the new candidate digest with the old partition without drawing
|
||||
twice.
|
||||
|
||||
**Commit gate:** exact candidate parity through indoor/outdoor transitions,
|
||||
door apertures, look-ins, non-flooded rooms, and portal straddles.
|
||||
|
||||
### G2 — Packed dispatcher input
|
||||
|
||||
1. Introduce an acdream-owned `RenderInstanceCandidate` consumed by
|
||||
`WbDrawDispatcher`.
|
||||
2. Copy every current `WorldEntity` fact the dispatcher actually uses:
|
||||
- mesh refs and ordered part transforms;
|
||||
- root transform;
|
||||
- owner landblock/cell;
|
||||
- palette and texture replacement identity;
|
||||
- hidden/draw/ancestor flags;
|
||||
- scale, bounds, source setup/gfx id;
|
||||
- selection owner identity;
|
||||
- translucency/degrade state.
|
||||
3. Preserve cache key and invalidation semantics, especially owner landblock
|
||||
identity and incomplete-mesh retry.
|
||||
4. Keep Wb/texture/selection/light/alpha owners unchanged.
|
||||
5. Add old-input/new-input instance-set comparison at the dispatcher boundary.
|
||||
|
||||
**Completed 2026-07-25.** Exact `f9829d5f` passed capped and uncapped
|
||||
nine-stop routes plus uncapped dense Arwic. Candidate order, packed mesh-part
|
||||
input, classified opaque/alpha output, and selection each completed
|
||||
16,879 + 17,282 + 770 comparisons with zero mismatch. The stronger gate
|
||||
corrected two referee blind spots: route canonicalization had hidden source
|
||||
order, and the no-VAO diagnostic path had hashed stale reusable group lists
|
||||
despite accepting zero instances. `GpuWorldState` now owns explicit traversal
|
||||
slots plus mutable bucket indices; zero-instance fingerprints describe empty
|
||||
accepted output. Evidence:
|
||||
`docs/research/2026-07-25-slice-g2-packed-dispatcher-gate.md`.
|
||||
|
||||
**Commit gate:** identical instance keys, model matrices, material identities,
|
||||
clip slots, light sets, selection parts, alpha records, and draw counts.
|
||||
|
||||
### G3 — Dirty instance ranges and reusable command storage
|
||||
|
||||
1. Retain persistent instance records keyed by projection + mesh part + batch.
|
||||
2. Rebuild classification only for Register, Appearance, Material, Mesh,
|
||||
Degrade, or relevant Flags dirtiness.
|
||||
3. Update transform/light/selection fields through dirty ranges.
|
||||
4. Reuse MDI command, batch, sort, and alpha scratch storage.
|
||||
5. Cache static command templates by scene generation and visible-cell set only
|
||||
after parity proves the simpler indexed path.
|
||||
6. Keep resource upload and GL retirement on the accepted render thread.
|
||||
7. Do not add GPU culling, GPU particles, or render-worker jobs in G.
|
||||
|
||||
**Commit gate:** warm unchanged frames rebuild no static classification and
|
||||
allocate no core scene/dispatcher memory.
|
||||
|
||||
**Completed 2026-07-25.** Exact `6a026c5a` passed capped and uncapped
|
||||
nine-stop routes plus uncapped dense Arwic. Candidate order, packed input,
|
||||
classified output, and selection completed 17,069 + 16,827 + 757 continuous
|
||||
comparisons with zero mismatch. Dense Arwic's final warm frame reused all
|
||||
3,828 unchanged visible classifications, rebuilt zero static projections, and
|
||||
kept 410 active animated classifications on the live path.
|
||||
|
||||
The first development pass exposed a reveal-edge ownership gap: inbound state
|
||||
continues while portal reveal blocks ordinary simulation, but the derived
|
||||
render projection had also been blocked. A narrow hidden-frame projection sync
|
||||
now follows inbound network/command mutation without advancing effects,
|
||||
particles, attachments, lights, animation, or physics. Dirty state is
|
||||
acknowledged only after every packed comparison succeeds. Deterministic tests
|
||||
also pin generation/incarnation replacement, incomplete resources, retirement,
|
||||
and 1,000 warm reuse frames with zero thread allocation. Evidence:
|
||||
`docs/research/2026-07-25-slice-g3-retained-classification-gate.md`.
|
||||
|
||||
### G4 — Cutover
|
||||
|
||||
1. Run old and new input construction in compare-only mode; draw only the old
|
||||
path.
|
||||
2. Fix every mismatch at its source.
|
||||
3. Switch the single production draw input to `RenderFrameView`.
|
||||
4. Run instance-set and fixed-camera image comparisons.
|
||||
5. Obtain the user's connected visual confirmation.
|
||||
6. Delete:
|
||||
- production `InteriorEntityPartition` use;
|
||||
- full `LandblockEntries` walk from normal world draw;
|
||||
- transition flags and dual-input compare code;
|
||||
- obsolete static classification rebuild paths.
|
||||
7. Keep only reusable oracle tooling behind diagnostics/tests.
|
||||
|
||||
There is no permanent runtime fallback. A failed gate reverts the cutover
|
||||
commit; it does not ship a hidden old/new switch.
|
||||
|
||||
**Automated gate completed 2026-07-25.** Exact gated source `03b10183` passed
|
||||
capped and uncapped nine-stop routes plus uncapped dense Arwic. The retained
|
||||
product completed 20,502 + 24,463 + 1,634 continuous comparisons with zero
|
||||
mismatch. Every canonical reveal was ready with zero pending composite warmup,
|
||||
the scene journal was empty, source and binary hashes matched, and every
|
||||
process closed gracefully. The gate corrected whole-retained-world composite
|
||||
warmup discovery by sourcing the exact published 3×3 destination neighborhood;
|
||||
that correction is draw-path independent. Fixed-camera artifacts are captured.
|
||||
The first connected retained-path gate exposed independent portal scheduling,
|
||||
private-viewport ownership, and ACE revisit-liveness defects. Their root-cause
|
||||
correction is recorded in
|
||||
`docs/research/2026-07-25-portal-regression-closeout.md`. The user's subsequent
|
||||
pass occurred while exact cutover `ef1d2633` was temporarily reverted by
|
||||
`2c848d41`, so it proves those corrections but not the retained draw source.
|
||||
`20f9fadb` reapplies the cutover over the accepted corrections. The user
|
||||
accepted that connected retained path on 2026-07-25, including stable objects
|
||||
and paperdoll through travel, distant interaction, and the corrected `/ls` and
|
||||
spell-recall materialization. G4 is complete. G5 removes the old production
|
||||
partition route and runs the production-only matrix.
|
||||
|
||||
### G5 — Physical performance and lifecycle gate
|
||||
|
||||
Run the exact Slice-E reference matrix:
|
||||
|
||||
- capped physical-local nine-stop;
|
||||
- uncapped physical-local nine-stop;
|
||||
- pinned dense-Arwic uncapped with stationary turn;
|
||||
- fresh login, portal/dungeon, same-location revisit, world edge;
|
||||
- appearance, hidden/unhide, equipped-child changes;
|
||||
- graceful logout/reconnect;
|
||||
- rapid generation replacement and GUID reuse.
|
||||
|
||||
Report, without changing thresholds:
|
||||
|
||||
- CPU/GPU p50/p95/p99/p99.9 and maximum;
|
||||
- update, PView, scene query, frame build, dispatcher, alpha, and GL stages;
|
||||
- frame-thread and process allocation;
|
||||
- scene/chunk/index/journal bytes;
|
||||
- managed heap, LOH/POH fragmentation, GC pauses;
|
||||
- tracked CPU/GPU/staging/retiring bytes;
|
||||
- instance/draw/triangle counts;
|
||||
- screenshot and instance-set comparison.
|
||||
|
||||
## 7. Acceptance matrix
|
||||
|
||||
### 7.1 Correctness
|
||||
|
||||
- Exact static/live/equipped projection count and digest.
|
||||
- Exact PView cell order and viewcone behavior.
|
||||
- Exact outside-stage and portal-straddling dynamic behavior.
|
||||
- Exact opaque/clip-map/alpha classification and stable alpha order.
|
||||
- Exact root and part transforms with no one-frame lag.
|
||||
- Exact hidden, NoDraw, ancestor visibility, and translucency behavior.
|
||||
- Exact material/palette/texture replacement identity.
|
||||
- Exact light sets and selection-part placement.
|
||||
- Exact incarnation behavior across stale updates and GUID reuse.
|
||||
- Zero replay of logical create/default script/effect work on rebucket.
|
||||
- Zero draw calls into gameplay/spatial dictionaries.
|
||||
|
||||
### 7.2 Resource and lifecycle
|
||||
|
||||
- Journal empty at every frame publication.
|
||||
- No old world generation in scene after replacement converges.
|
||||
- Scene memory plateaus on third location visit.
|
||||
- No stale Arch entity handle after removal/reuse.
|
||||
- No extra mesh/texture/composite owner.
|
||||
- No unbalanced reference, staged upload, dynamic light, effect, or alpha
|
||||
record.
|
||||
- Graceful shutdown drains scene and GL retirement exactly once.
|
||||
|
||||
### 7.3 Performance
|
||||
|
||||
The program goals remain:
|
||||
|
||||
- dense uncapped p50 at or below 3.33 ms, or a newly attributed blocker;
|
||||
- traversal p99 at or below 16.67 ms;
|
||||
- no post-warm frame above 33.3 ms without an attributed indivisible/OS event;
|
||||
- steady update p95 at or below 2 ms;
|
||||
- core render-scene/dispatcher allocation p50 of zero;
|
||||
- whole-client steady allocation initially at or below 4 KiB/frame;
|
||||
- no known exception-as-control-flow site.
|
||||
|
||||
Because Slice E already satisfies the dense median target, G must additionally
|
||||
show one of:
|
||||
|
||||
- a statistically meaningful CPU p95/p99 reduction;
|
||||
- lower CPU utilization at equal uncapped throughput;
|
||||
- materially improved resident-entity scaling;
|
||||
- or a documented result that the removed scan was no longer dominant and the
|
||||
next measured bottleneck belongs to Slice H.
|
||||
|
||||
No visual-quality reduction is an acceptable way to pass.
|
||||
|
||||
## 8. Commit and review sequence
|
||||
|
||||
Each sub-slice is one or more bisectable commits, but a commit may not mix
|
||||
shadow storage, production cutover, and old-path deletion.
|
||||
|
||||
For every commit:
|
||||
|
||||
1. inspect the complete diff for authority leakage, stale identity, lifecycle
|
||||
asymmetry, and allocations;
|
||||
2. run focused App tests;
|
||||
3. run `dotnet build AcDream.slnx -c Release`;
|
||||
4. run the complete Release solution tests before a sub-slice gate;
|
||||
5. update architecture, roadmap, issues, divergence register, and memory in the
|
||||
same commit when their truth changes;
|
||||
6. introduce no retail-divergence row—F/G is performance-only and any visible
|
||||
difference is a bug.
|
||||
|
||||
No substantial feature body enters `GameWindow`. Composition changes live in
|
||||
`FrameRootComposition`; scene, journal, oracle, synchronization, and frame
|
||||
product owners live under focused `AcDream.App/Rendering` namespaces.
|
||||
|
||||
## 9. Stop/rollback conditions
|
||||
|
||||
Stop the current sub-slice and fix the root cause when:
|
||||
|
||||
- the shadow digest differs;
|
||||
- source identity requires a second authoritative GUID map;
|
||||
- a draw consumer must call back into `LiveEntityRuntime` or `GpuWorldState`;
|
||||
- delta order cannot represent a reentrant lifecycle edge;
|
||||
- the new path needs a timer, suppression flag, or retry workaround;
|
||||
- a screenshot/instance difference cannot be explained;
|
||||
- the frame product adds a frame of latency;
|
||||
- memory fails to plateau;
|
||||
- F/G performance is worse outside explicitly enabled shadow comparison.
|
||||
|
||||
The rollback unit is the failing sub-slice commit. Do not retain a permanent
|
||||
legacy fallback, dual draw, or user-facing renderer switch.
|
||||
|
||||
## 10. Approval and rollback state
|
||||
|
||||
This plan is approved and active.
|
||||
|
||||
- Pre-F/G runtime rollback anchor: `e7d9d6fa`.
|
||||
- Exact first G4 production cutover:
|
||||
`ef1d263337997bb030eadb7b8e71d73dc659907a`.
|
||||
- If tomorrow's visual gate fails, surgically run
|
||||
`git revert ef1d263337997bb030eadb7b8e71d73dc659907a`. This preserves the
|
||||
proven F/G referee, journal, frame-product, and retained-classification
|
||||
commits through G3. Do not reset the branch and do not revert `6a026c5a`.
|
||||
- Every production cutover commit is added here and to
|
||||
`claude-memory/project_render_pipeline_digest.md`.
|
||||
- Shadow/referee commits precede the production switch and need not be reverted
|
||||
for a visual mismatch unless they independently change runtime behavior.
|
||||
- A failed visual gate reverts the exact cutover commit(s); it does not use
|
||||
`git reset`, discard later evidence, or enable a permanent fallback.
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
# Modern runtime Slice H execution plan
|
||||
|
||||
**Status:** COMPLETE (2026-07-25)
|
||||
**Parent plan:** `2026-07-24-modern-runtime-architecture.md`, Slice H
|
||||
**Prerequisite state at execution:** Slice G4 production cutover was
|
||||
automated-gated; its user visual gate later passed on 2026-07-25.
|
||||
|
||||
## 1. Safety boundary
|
||||
|
||||
Slice H removes work which scales with presentation rate. It does not change
|
||||
retail gameplay rules, visible-cell authority, light membership, packet
|
||||
ordering, acknowledgement timing, heartbeat placement, or UI presentation.
|
||||
|
||||
Slice H did not delete or alter the G4 old-route referee while its visual gate
|
||||
was pending. That gate has since passed; G5 owns the final deletion boundary.
|
||||
|
||||
If the G4 visual result is wrong, revert only:
|
||||
|
||||
```text
|
||||
git revert ef1d263337997bb030eadb7b8e71d73dc659907a
|
||||
```
|
||||
|
||||
Do not reset history and do not revert G3. The portal-warmup corrections
|
||||
`129dd77d` and `03b10183` are independent of the retained draw-source cutover.
|
||||
|
||||
## 2. H-a — retained UI, live attachments, diagnostics, and frame scratch
|
||||
|
||||
### H-a1 — cached shaped text — COMPLETE
|
||||
|
||||
1. Add a small retained text-layout cache keyed by source revision/value plus
|
||||
width, padding, color, and font identity.
|
||||
2. Convert appraisal item, inscription, spell, creature, character-info, and
|
||||
active-effect detail providers so expensive report construction and
|
||||
wrapping occur only after source or layout changes.
|
||||
3. Preserve `UiText.LinesProvider` as the rendering seam. Dynamic one-line
|
||||
widgets remain dynamic; this change is limited to report/wrapped content.
|
||||
4. Ensure selection, appraisal response, component count, enchantment,
|
||||
character-property, font, and resize changes invalidate the right cache.
|
||||
5. Add tests proving stable draws return the same line collection while source
|
||||
and geometry changes rebuild it.
|
||||
|
||||
Gate: the existing appraisal/character/effect behavior tests remain unchanged,
|
||||
and repeated stable provider polls allocate no report/wrap objects.
|
||||
|
||||
Landed evidence:
|
||||
[`../research/2026-07-25-slice-h-a1-ui-text-cache.md`](../research/2026-07-25-slice-h-a1-ui-text-cache.md).
|
||||
|
||||
### H-a2 — visible cooldown participants — COMPLETE
|
||||
|
||||
1. Keep the shared retail heartbeat and one cooldown result per represented
|
||||
item.
|
||||
2. Index mounted `UiItemList` participants once at bind time.
|
||||
3. At heartbeat, visit only lists whose complete ancestor chain is visible.
|
||||
Hidden panels keep no stale visual state: their slots query zero while
|
||||
hidden and are refreshed on the first visible heartbeat.
|
||||
4. Keep future-slot support and shared-group behavior.
|
||||
|
||||
Gate: hidden inventory, equipment, external-container, vendor, and spell-bar
|
||||
lists are not scanned; reopening immediately displays the authoritative
|
||||
cooldown step.
|
||||
|
||||
Landed evidence:
|
||||
[`../research/2026-07-25-slice-h-a2-cooldown-scope.md`](../research/2026-07-25-slice-h-a2-cooldown-scope.md).
|
||||
|
||||
### H-a3 — equipped-child transition work — COMPLETE
|
||||
|
||||
1. Preserve both frame ordering points: pre-network presentation and
|
||||
post-network spatial reconciliation have different authority boundaries.
|
||||
2. Split transition retry from pose recomposition.
|
||||
3. Run the complete parent-first pose walk once after animation.
|
||||
4. In the post-network phase, recompose only attachments dirtied by accepted
|
||||
root/relation/appearance changes.
|
||||
5. Replace mutation-safe dictionary `ToArray` snapshots with retained key
|
||||
scratch buffers. Callback reentrancy still observes the same transition
|
||||
generation checks.
|
||||
|
||||
Gate: equip, unparent, reparent, pose loss, rollback, delete, GUID reuse, and
|
||||
subtree-withdrawal suites preserve exact event and resource counts.
|
||||
|
||||
Landed evidence:
|
||||
[`../research/2026-07-25-slice-h-a3-attached-pose-reconcile.md`](../research/2026-07-25-slice-h-a3-attached-pose-reconcile.md).
|
||||
|
||||
### H-a4 — diagnostics and frame scratch — COMPLETE
|
||||
|
||||
1. Reuse one synchronous `RetailPViewFrameInput` owned by
|
||||
`WorldSceneRenderer`; reset every field before each draw.
|
||||
2. Expose stable borrowed landblock entry/bounds views instead of creating
|
||||
iterator state in each frame.
|
||||
3. Consumer-gate optional shadow enumeration. Preserve landblock visibility
|
||||
counting because the normal title and lifecycle checkpoints are active
|
||||
consumers of `lb V/T`; neutralizing it would be a visible/automation
|
||||
behavior change.
|
||||
4. Do not rework `InteriorEntityPartition` while the G4 referee remains.
|
||||
Issue #241 closes with G5 old-route deletion, not by editing the fallback.
|
||||
5. Profile capped and uncapped production paths and retain evidence.
|
||||
|
||||
Gate: identical framebuffer/scene digest, no per-frame PView input allocation,
|
||||
no iterator allocation for stable landblock views, and no shadow traversal
|
||||
with all optional consumers disabled.
|
||||
|
||||
Landed evidence:
|
||||
[`../research/2026-07-25-slice-h-a4-frame-scratch.md`](../research/2026-07-25-slice-h-a4-frame-scratch.md).
|
||||
|
||||
## 3. H-b — exact light top-k — COMPLETE
|
||||
|
||||
Retail anchors:
|
||||
|
||||
- `CEnvCell::add_dynamic_lights` at `0x0052D410`
|
||||
- `Render::insert_light` at `0x0054D1B0`
|
||||
- `Render::add_dynamic_light` at `0x0054D420`
|
||||
- global caps at `0x0081EC94` / `0x0081EC98`
|
||||
|
||||
Execution:
|
||||
|
||||
1. Preserve the resident `_all` registry and existing visible-cell filter.
|
||||
2. Preserve dynamics-first ordering and squared distance from the player.
|
||||
3. Replace only the over-cap complete sort with a bounded exact top-k
|
||||
selection.
|
||||
4. Define tie-breaking from the existing comparator so the selected set and
|
||||
final submission order are identical, including equal-distance fixtures.
|
||||
5. Differential-test randomized candidate sets and the captured 463-light Town
|
||||
Network fixture against the old full-sort oracle.
|
||||
6. Run the fixed-camera screenshot comparison and connected light gate.
|
||||
|
||||
Gate: identical selected IDs and submission order for every fixture, identical
|
||||
Town Network screenshot, reduced overflow CPU/allocation, and AP-85 retired
|
||||
only after the visual evidence passes.
|
||||
|
||||
Landed evidence:
|
||||
[`../research/2026-07-25-slice-h-b-light-top-k-report.md`](../research/2026-07-25-slice-h-b-light-top-k-report.md).
|
||||
The deterministic differential fixtures preserve exact accepted output; the
|
||||
463-light diagnostic microbenchmark reduced selection CPU time by 29.1% with
|
||||
zero warmed allocations. AP-85 remains open because its retail dual-pool
|
||||
behavior is outside this performance-only unit.
|
||||
|
||||
## 4. H-c — ordered, allocation-conscious network I/O
|
||||
|
||||
H-c is executed as three independently reversible units:
|
||||
|
||||
1. **H-c1 — pooled receive owner — COMPLETE.** One cancellable async socket
|
||||
receive, a retained full-size edge buffer, right-sized pooled FIFO
|
||||
datagrams, unchanged blocking handshake cadence, and direct span sends.
|
||||
Evidence:
|
||||
[`../research/2026-07-25-slice-h-c1-pooled-receive-owner.md`](../research/2026-07-25-slice-h-c1-pooled-receive-owner.md).
|
||||
2. **H-c2 — borrowed decode — COMPLETE.** Parse and verify headers, optional
|
||||
fields, and fragments as borrowed views. Copy only multi-fragment state
|
||||
that must survive the current datagram. Evidence:
|
||||
[`../research/2026-07-25-slice-h-c2-borrowed-packet-decode.md`](../research/2026-07-25-slice-h-c2-borrowed-packet-decode.md).
|
||||
3. **H-c3 — direct outbound framing — COMPLETE.** Write packet and fragment
|
||||
framing into caller storage and remove intermediate payload arrays.
|
||||
Evidence:
|
||||
[`../research/2026-07-25-slice-h-c3-direct-outbound-framing.md`](../research/2026-07-25-slice-h-c3-direct-outbound-framing.md).
|
||||
|
||||
Retail/transport invariants:
|
||||
|
||||
- one outstanding receive;
|
||||
- kernel datagram arrival order remains inbound queue order;
|
||||
- one acknowledgement decision per accepted packet;
|
||||
- `LinkStatusHolder::OnHeartbeat` at `0x004113D0` remains at the same
|
||||
last-heard point;
|
||||
- `PumpOnce` handshake pacing remains unchanged;
|
||||
- fragmented message assembly and sequence gates remain authoritative.
|
||||
|
||||
Execution:
|
||||
|
||||
1. Capture allocation and ordering baselines for login, idle, motion, portal,
|
||||
appraisal, container, and combat traffic.
|
||||
2. Introduce a single cancellable socket receive owner using reusable/pool
|
||||
storage.
|
||||
3. Decode packet headers and fragments from spans/borrowed storage.
|
||||
4. Copy only payloads whose lifetime crosses the receive/dispatch boundary;
|
||||
pooled receive memory is never returned while referenced.
|
||||
5. Replace outbound temporary arrays with span/list segment writes where the
|
||||
socket API permits.
|
||||
6. Prove cancellation, remote close, malformed packet, receive fault, fragment
|
||||
reassembly, queue backpressure, and reconnect cleanup.
|
||||
7. Run connected lifecycle, reconnect, portal, interaction, and soak gates.
|
||||
|
||||
Gate: identical decoded event/ack traces, no receive-thread death, no pooled
|
||||
buffer lifetime violation, and materially reduced bytes per packet.
|
||||
|
||||
## 5. Commit and evidence order
|
||||
|
||||
Each numbered unit lands as a bisectable commit after focused tests, App
|
||||
Release, complete Release, and documentation reconciliation:
|
||||
|
||||
1. H-a1 cached shaped text.
|
||||
2. H-a2 cooldown visibility.
|
||||
3. H-a3 attachment retry/recomposition.
|
||||
4. H-a4 diagnostics/frame scratch.
|
||||
5. H-b exact light top-k.
|
||||
6. H-c network receive/decode/send allocations.
|
||||
7. Slice-H connected measurements and closeout.
|
||||
|
||||
Raw captures stay under `.test-out/` or `logs/`; reviewed summaries and compact
|
||||
fixtures are committed under `docs/research/` or the matching test project.
|
||||
|
||||
## 6. Deferred boundaries
|
||||
|
||||
- Physics `Transition` pooling (#237) remains Slice I and requires reset-
|
||||
completeness/identity tests first.
|
||||
- G5 old-route/referee deletion waits for the user's G4 visual acceptance.
|
||||
- Static publication indexing (#242) is not silently folded into H; it is
|
||||
measured after H-a and scheduled only if still material.
|
||||
- Slices I–L start only after Slice H is measured and documented, but no
|
||||
additional authorization is required.
|
||||
|
||||
## 7. Closeout
|
||||
|
||||
All H-a, H-b, and H-c implementation units are complete. The Release build
|
||||
and complete solution pass 8,292 tests with 5 skips. The connected
|
||||
login/portal/dungeon/revisit/graceful-disconnect/fresh-process-reconnect gate
|
||||
passed on commit `41c1a593927fcf71ef83b4825c659a4970e9b858` with all seven
|
||||
checkpoints ready, no invariant or transport failure, and both sessions
|
||||
closing through the normal logout path.
|
||||
|
||||
The connected automation deliberately constructs the retained G4 current-path
|
||||
oracle, so its scene-sized per-frame allocation is comparison-instrumentation
|
||||
cost and not representative of production. A separate normal uncapped Release
|
||||
session, with neither lifecycle automation nor the oracle composed, stabilized
|
||||
at 25.6 KiB/frame and roughly 500 presented frames/second under RDP. Allocation
|
||||
stack attribution identifies the remaining dominant production owners as
|
||||
`Transition` query scratch (Slice I) and the retained current partition/referee
|
||||
path (G5). The H-owned packet decode and recurring outbound framing paths
|
||||
independently allocate zero bytes when warm. The program-wide 4 KiB/frame
|
||||
target therefore remains an end-of-G5/Slice-I acceptance condition rather than
|
||||
being misreported as achieved by H alone.
|
||||
|
||||
Closeout evidence:
|
||||
[`../research/2026-07-25-slice-h-closeout.md`](../research/2026-07-25-slice-h-closeout.md).
|
||||
|
|
@ -1,371 +0,0 @@
|
|||
# Modern runtime Slice I — flat collision assets and zero-allocation physics
|
||||
|
||||
**Status:** I0–I7 COMPLETE — retail oracle, zero-allocation transition
|
||||
scratch, deterministic immutable collision records, complete prepared
|
||||
package, exact flat traversal, dual publication, flat-authoritative production
|
||||
cutover, parsed-graph removal, connected correctness/lifetime acceptance, and
|
||||
documentation closeout
|
||||
**Parent:** `2026-07-24-modern-runtime-architecture.md`, Slice I
|
||||
**Purpose:** remove parsed DAT object graphs and steady collision allocations
|
||||
without changing one retail collision decision, float, comparison, or traversal
|
||||
order.
|
||||
|
||||
## 1. Non-negotiable boundaries
|
||||
|
||||
1. Retail collision math is frozen. This slice changes storage and scratch
|
||||
ownership only.
|
||||
2. Every float read from DAT is copied bit-for-bit. The bake performs no
|
||||
normalization, quantization, coordinate conversion, `double` round-trip, or
|
||||
plane reconstruction.
|
||||
3. Positive/negative child order, polygon order within every leaf, early
|
||||
returns, epsilon comparisons, and contact-selection tie behavior remain
|
||||
exact.
|
||||
4. The current object-graph traversal remains an executable oracle until the
|
||||
flat traversal passes mass differential and connected gates.
|
||||
5. Physics `Transition` reuse lands only after reset-completeness,
|
||||
cross-mover-identity, failure, and reentrancy tests prove that no state can
|
||||
leak between resolves.
|
||||
6. No GL/App dependency enters Core. Flat collision records live in
|
||||
`AcDream.Core`; serialization and package access live in `AcDream.Content`;
|
||||
the bake tool produces them offline.
|
||||
7. The active G4 render cutover is independent. Its exact visual rollback
|
||||
remains:
|
||||
|
||||
```text
|
||||
git revert ef1d263337997bb030eadb7b8e71d73dc659907a
|
||||
```
|
||||
|
||||
## 2. Target data architecture
|
||||
|
||||
The runtime collision representation is immutable, array-backed, and addressed
|
||||
by integer index:
|
||||
|
||||
```text
|
||||
FlatCollisionAsset
|
||||
├── FlatPhysicsBsp
|
||||
│ ├── FlatBspNode[] child indices; -1 = no child
|
||||
│ ├── int[] leaf polygon-index stream
|
||||
│ ├── FlatPolygon[] plane, cull mode, id, vertex range
|
||||
│ └── Vector3[] verbatim polygon vertices
|
||||
├── FlatCellContainmentBsp split planes + positive/negative indices
|
||||
├── FlatSetupCollision cyl/sphere records + verbatim dimensions
|
||||
└── EnvCellTopology portals, visible cells, seen-outside
|
||||
```
|
||||
|
||||
Node order is deterministic pre-order. The flattener records the original
|
||||
positive child before the negative child and retains each source leaf's
|
||||
polygon list order. Traversal follows indices using the same recursive
|
||||
control flow as the current retail port; “flat” does not mean a new collision
|
||||
algorithm.
|
||||
|
||||
Per-EnvCell world transform remains publication state, not baked geometry.
|
||||
CellStruct collision geometry can therefore alias by exact source identity;
|
||||
cell-specific portals, visibility, `seen_outside`, and placement remain a
|
||||
separate small payload/publication record.
|
||||
|
||||
## 3. Execution slices
|
||||
|
||||
### I0 — oracle inventory and fixed evidence
|
||||
|
||||
1. Grep named retail before touching traversal:
|
||||
- `BSPTREE::find_collisions`
|
||||
- `BSPNODE::find_walkable`
|
||||
- `BSPLEAF::find_walkable`
|
||||
- `BSPNODE::sphere_intersects_*`
|
||||
- `BSPNODE::point_inside_cell_bsp`
|
||||
- `CTransition::find_transitional_position`
|
||||
- `CTransition::init_path`
|
||||
- `CPhysicsObj::transition`
|
||||
2. Cross-reference ACE and the existing acdream pseudocode/trajectory notes.
|
||||
3. Write one collision-layout pseudocode note that separates:
|
||||
source storage, traversal order, mutable query scratch, and returned
|
||||
side-effects.
|
||||
4. Inventory every production call into `BSPQuery`, every field read from
|
||||
`PhysicsBSPNode`, `CellBSPNode`, `ResolvedPolygon`, `CellPhysics`,
|
||||
`GfxObjPhysics`, and `SetupPhysics`, and every raw DAT graph retained by
|
||||
`LandblockBuild`/`PhysicsDatBundle`.
|
||||
5. Capture representative installed-DAT fixtures:
|
||||
- outdoor/no-BSP;
|
||||
- Facility Hub and cottage indoor cells;
|
||||
- staircase/ramp and cellar-lip cases;
|
||||
- a multipart door/building shell;
|
||||
- projectile/thin-wall geometry;
|
||||
- cells with asymmetric/null BSP children;
|
||||
- leaves with multiple polygons and equal-distance candidates.
|
||||
6. Record allocation baselines for player, remote, projectile, and camera
|
||||
resolve loops.
|
||||
|
||||
Gate: source inventory is complete, fixtures reproduce through the current
|
||||
graph path, and no behavior code changed.
|
||||
|
||||
### I1 — reusable transition and query scratch
|
||||
|
||||
1. Add explicit `ResetForReuse` methods to `ObjectInfo`, `CollisionInfo`,
|
||||
`SpherePath`, and `Transition`.
|
||||
2. Reset every scalar, nullable, property backing field, diagnostic counter,
|
||||
collision GUID list, sphere element, walkable reference, backup value, and
|
||||
transient flag. Retain only storage identity whose contents are completely
|
||||
reset.
|
||||
3. Give each `PhysicsEngine` one owned transition lease. The normal
|
||||
update-thread path rents, initializes, resolves, snapshots the value-only
|
||||
result, and returns in `finally`.
|
||||
4. Make reentrancy explicit and tested. Never hand one mutable transition to
|
||||
two concurrent/nested resolves and never silently share session scratch.
|
||||
5. Reuse walkable vertex arrays only when their exact logical length matches;
|
||||
allocate only on a true size change. No larger-capacity array may leak stale
|
||||
vertices into polygon math.
|
||||
6. Convert short-lived `BSPQuery.CollisionSphere` reference objects to
|
||||
value/ref scratch while preserving each mutation/copy boundary.
|
||||
7. Add structural reflection tests that poison every resettable member, reset,
|
||||
and compare its complete value graph with a fresh instance while asserting
|
||||
retained buffer identities.
|
||||
8. Differential-run fresh-transition and reused-transition engines across
|
||||
success, collision, failure, placement, grounded, airborne, sliding,
|
||||
step-up/down, two-sphere, projectile, and camera cases.
|
||||
|
||||
Gate: bit-identical `ResolveResult` and body side effects, no state leakage
|
||||
when alternating hostile fixtures between different mover IDs, and zero
|
||||
steady transition/query-scratch allocation. **PASSED 2026-07-25:** the expanded
|
||||
118-test graph oracle, structural poison/reset suite, hostile fresh-vs-reused
|
||||
differential, Core/solution Release gates, and all five measured profiles pass.
|
||||
Issue #237 is closed. Evidence:
|
||||
[`../research/2026-07-25-slice-i1-transition-scratch.md`](../research/2026-07-25-slice-i1-transition-scratch.md).
|
||||
|
||||
### I2 — immutable flat collision schema and flattener
|
||||
|
||||
1. Add Core-only flat record types with explicit index/range contracts.
|
||||
2. Flatten physics and containment BSPs iteratively during preparation while
|
||||
emitting deterministic pre-order node arrays.
|
||||
3. Copy planes, spheres, vertices, setup dimensions, and collision metadata
|
||||
verbatim. Tests compare every float via `SingleToInt32Bits`.
|
||||
4. Resolve each leaf polygon ID to a direct polygon index at preparation time.
|
||||
Missing IDs are corruption, not a runtime fallback.
|
||||
5. Validate:
|
||||
- child indices/ranges;
|
||||
- acyclic/reachable tree shape;
|
||||
- polygon and vertex ranges;
|
||||
- source-vs-flat node, child, polygon, and float identity;
|
||||
- deterministic output independent of dictionary enumeration.
|
||||
6. Keep source graph and flat asset together only in test/shadow fixtures.
|
||||
|
||||
Gate: round-trip structural equality over synthetic edge cases and installed
|
||||
DAT samples; no traversal cutover yet. **PASSED 2026-07-25:** 13 focused
|
||||
structural/installed-DAT tests, the 131-test expanded Slice I oracle, complete
|
||||
Core/solution Release gates, exact float-bit comparisons, and corruption
|
||||
tripwires pass. The graph route remains the only executable traversal.
|
||||
Evidence:
|
||||
[`../research/2026-07-25-slice-i2-flat-collision-schema.md`](../research/2026-07-25-slice-i2-flat-collision-schema.md).
|
||||
|
||||
### I3 — package serialization, bake, and prepared collision source
|
||||
|
||||
1. Append new `PakAssetType` values; never renumber the existing mesh values.
|
||||
2. Bump `CurrentBakeToolVersion` because a complete production package now
|
||||
includes collision assets.
|
||||
3. Add strict little-endian serializers with checked counts/ranges, exact float
|
||||
bits, cancellation, CRC coverage, corruption rejection, and trailing-byte
|
||||
rejection.
|
||||
4. Add a typed `IPreparedCollisionSource` over the existing memory-mapped pak.
|
||||
It shares package/catalog lifetime but does not overload the render-only
|
||||
`ObjectMeshData` API.
|
||||
5. Bake:
|
||||
- GfxObj physics BSP + polygons + visual bounds;
|
||||
- Setup cylinder/sphere/dimension records;
|
||||
- CellStruct physics BSP + containment BSP + physics/portal polygons;
|
||||
- EnvCell topology records.
|
||||
6. Alias only byte-identical immutable collision payloads. Cell-specific
|
||||
topology and world placement cannot be aliased merely because geometry is.
|
||||
7. Extend full-bake determinism, two-thread equivalence, corruption,
|
||||
cancellation, publish-transaction, and catalog-completeness tests.
|
||||
|
||||
Gate: two independent bakes are byte-identical; every collision key required
|
||||
by the canonical route is present; a failed/cancelled bake cannot replace the
|
||||
last good pak. **PASSED 2026-07-25:** two complete 29,908,271,024-byte bakes
|
||||
with different worker counts produced the same SHA-256 and 2,232,170 typed
|
||||
keys with zero failures. Strict serialization/corruption/cancellation gates,
|
||||
representative installed-package reads, Release build, and 8,371 solution
|
||||
tests / 5 skips pass. Production traversal remains graph-only. Evidence:
|
||||
[`../research/2026-07-25-slice-i3-prepared-collision-package.md`](../research/2026-07-25-slice-i3-prepared-collision-package.md).
|
||||
|
||||
### I4 — flat traversal shadow implementation
|
||||
|
||||
1. Port each current entry point line-for-line against integer-index nodes:
|
||||
- point-in-cell;
|
||||
- sphere/cell overlap;
|
||||
- walkable search;
|
||||
- six-path moving collision dispatcher;
|
||||
- static sphere/polygon overlap;
|
||||
- time-of-impact overlap.
|
||||
2. Share only polygon-level math that is storage-independent. Do not
|
||||
“simplify” recursion, branch ordering, or early returns.
|
||||
3. Preserve mutation semantics for valid-position spheres, hit polygon, path,
|
||||
contact plane, slide normal, walk interpolation, and transition state.
|
||||
4. Add a differential harness that executes old and flat queries from cloned
|
||||
transition/body inputs and compares:
|
||||
- bool/enum result;
|
||||
- all output vectors/planes by float bits;
|
||||
- selected polygon ID;
|
||||
- path/collision/object side effects;
|
||||
- cell membership and final `ResolveResult`.
|
||||
5. Sweep a large installed-DAT sample with randomized points, spheres,
|
||||
movement vectors, insert modes, orientations, scales, and boundary values.
|
||||
6. Require explicit fixtures for null children, on-plane equality, radius
|
||||
equality, equal candidate distances, multi-polygon leaf order, and deep
|
||||
trees.
|
||||
|
||||
Gate: zero differential mismatch. Any mismatch blocks cutover; no tolerance
|
||||
band is permitted for deterministic scalar results.
|
||||
|
||||
**PASSED 2026-07-25:** the integer-indexed shadow ports every current
|
||||
containment, overlap, walkable, and six-path moving-collision entry point.
|
||||
The 13-test referee completed 10,000 randomized static/swept comparisons,
|
||||
5,000 walkable comparisons, 7,500 installed-DAT comparisons across three
|
||||
representative cells, all six dispatcher paths, and 1,200 complete resolver
|
||||
frames with exact state and float-bit equality. Ten thousand warmed flat
|
||||
iterations allocated zero managed bytes. Production remains graph-authoritative
|
||||
until I5/I6. Release build and 8,384 solution tests / 5 skips pass. Evidence:
|
||||
[`../research/2026-07-25-slice-i4-flat-bsp-differential.md`](../research/2026-07-25-slice-i4-flat-bsp-differential.md).
|
||||
|
||||
### I5 — dual publication and connected shadow gate
|
||||
|
||||
1. Extend collision preparation so a `LandblockBuild` carries immutable flat
|
||||
assets and small placement/topology records rather than reparsing them on
|
||||
the update thread.
|
||||
2. During the shadow phase, publish both current and flat views under one
|
||||
landblock generation/receipt.
|
||||
3. Keep the current graph path authoritative. Run sampled flat queries from
|
||||
cloned inputs and emit a deterministic mismatch artifact without affecting
|
||||
gameplay.
|
||||
4. Verify load, pending-to-loaded, demotion, rehydrate, same-location revisit,
|
||||
removal, cancellation, and session reset release both views exactly once.
|
||||
5. Run connected login, indoor/outdoor portal churn, doors, ramps, jumping,
|
||||
projectiles, camera collision, and graceful reconnect.
|
||||
|
||||
Gate: zero shadow mismatch and no retained collision owner after landblock or
|
||||
session teardown.
|
||||
|
||||
**Complete 2026-07-25.** Near-tier builds carry one immutable prepared
|
||||
collision closure; streamed and live objects publish graph and flat assets
|
||||
strictly, with no graph-only fallback. Cancellation, publication, demotion,
|
||||
rehydration, revisit, adjacent-landblock isolation, removal, and session
|
||||
teardown gates pass. The complete Release solution passes 8,396 tests / 5
|
||||
skips. The connected lifecycle/reconnect and nine-stop routes sampled 14,064
|
||||
queries with exact state/float-bit equality, zero mismatch, zero flat fault,
|
||||
zero mismatch artifact, equal graph/flat cell residency at every stable
|
||||
checkpoint, and graceful exits. Evidence:
|
||||
[`../research/2026-07-25-slice-i5-dual-collision-shadow.md`](../research/2026-07-25-slice-i5-dual-collision-shadow.md).
|
||||
|
||||
### I6 — production cutover and graph removal
|
||||
|
||||
1. Flip canonical `PhysicsDataCache` records and every `BSPQuery` production
|
||||
call to flat assets in one bisectable commit.
|
||||
2. Keep the graph route as an automated referee for the first cutover gate,
|
||||
with an exact revert command recorded in this plan and project memory.
|
||||
3. After automated and user correctness acceptance, remove:
|
||||
- production `PhysicsBSPTree`/`CellBSPTree` retention;
|
||||
- production polygon dictionaries and vertex DBObj graphs;
|
||||
- raw collision graphs from streaming build/publication payloads;
|
||||
- obsolete graph adapters and referee code.
|
||||
4. Live-DAT tooling may parse source graphs only to produce/compare flat
|
||||
assets; gameplay production never falls back from a missing/corrupt prepared
|
||||
collision asset.
|
||||
|
||||
Gate: trajectories and collision fixtures remain bit-identical, package-only
|
||||
startup succeeds, and retained-memory accounting shows no parsed collision
|
||||
DBObj graph after stable world reveal.
|
||||
|
||||
**Production cutover landed 2026-07-25 at
|
||||
`068a06518dd710ca5e7f166754cbb7789ca1ed0f`.** Gameplay is now
|
||||
flat-authoritative, package publication is strict, and the parsed graph runs
|
||||
only as a sampled referee. The exact rollback for this cutover is:
|
||||
|
||||
```text
|
||||
git revert 068a06518dd710ca5e7f166754cbb7789ca1ed0f
|
||||
```
|
||||
|
||||
Do not revert I3–I5 package/publication work. The pre-visual automated gate
|
||||
passes 8,402 Release tests / 5 skips and a strict dense-Arwic connected run
|
||||
with 46,309/46,309 exact graph-referee matches, zero mismatch/fault/artifact,
|
||||
and graceful shutdown. Production-like uncapped dense Arwic (15,530 entities,
|
||||
no automation oracle) sustained roughly 190–200 FPS with stable CPU p50
|
||||
~5.0 ms / p95 ~5.9–7.3 ms and GPU p95 ~2.1 ms. Evidence:
|
||||
[`../research/2026-07-25-slice-i6-flat-production-cutover.md`](../research/2026-07-25-slice-i6-flat-production-cutover.md).
|
||||
|
||||
The user accepted the connected collision gate on 2026-07-25. Production
|
||||
parsed-graph removal then landed at
|
||||
`82f8d4f82e24ad85042ac3e25c4f6464bebce758`. The production
|
||||
`PhysicsDataCache`, stable `GpuWorldState`, near-build payloads, and live
|
||||
publication path now retain only immutable flat assets. Graph construction
|
||||
survives solely in explicit tests, bake/equivalence tools, and source-oracle
|
||||
constructors. The exact rollback for graph removal is:
|
||||
|
||||
```text
|
||||
git revert 82f8d4f82e24ad85042ac3e25c4f6464bebce758
|
||||
```
|
||||
|
||||
Do not revert I3–I6 unless evidence implicates the prepared package or
|
||||
flat-authoritative traversal itself.
|
||||
|
||||
### I7 — allocation, soak, and documentation closeout
|
||||
|
||||
1. Measure capped and uncapped resolve allocation separately for player,
|
||||
remotes, projectiles, and camera.
|
||||
2. Run the canonical connected lifecycle/reconnect route and the nine-stop R6
|
||||
portal soak.
|
||||
3. Repeat indoor door/ramp/cellar, jump/fall, combat/projectile, and camera-wall
|
||||
gates.
|
||||
4. Under RDP, accept correctness and lifecycle evidence only; defer absolute
|
||||
GPU/frame-time acceptance to a physical-display run.
|
||||
5. Update architecture, WorldBuilder inventory, milestones, roadmap, issue
|
||||
ledger, divergence register, and both physics/render memory digests.
|
||||
|
||||
Final gate:
|
||||
|
||||
- full Release build and solution tests;
|
||||
- zero deterministic old-vs-flat mismatch;
|
||||
- zero steady transition/query-scratch allocation;
|
||||
- no raw collision graph in production stable state;
|
||||
- connected portal/reconnect/interaction routes pass;
|
||||
- exact cutover rollback recorded before user verification.
|
||||
|
||||
**Completed 2026-07-25.** Release build and the complete solution pass
|
||||
8,413 tests / 5 skips. The focused collision/door/doorway/cellar/ramp/jump/
|
||||
projectile/allocation gate passes 154 Core tests plus 47 App camera/projectile
|
||||
tests; player, grounded publication, remote, projectile, and camera resolves
|
||||
remain 0 B/resolve.
|
||||
|
||||
The exact-binary connected lifecycle/reconnect route and canonical nine-stop
|
||||
R6 soak both pass with graceful exits. Every stable checkpoint reports
|
||||
`0/0/0` retained parsed GfxObj/Setup/cell graphs while flat GfxObj/Setup/cell
|
||||
residency remains populated. Movement, jump, combat, projectile, portal,
|
||||
dungeon, revisit, reconnect, publication, retirement, and teardown checks
|
||||
pass. The user accepted collision behavior over RDP. Absolute post-removal
|
||||
GPU/frame-time and ordinary-process memory comparison remains a physical-
|
||||
display measurement, not a Slice-I correctness blocker. Evidence:
|
||||
[`../research/2026-07-25-slice-i7-closeout.md`](../research/2026-07-25-slice-i7-closeout.md).
|
||||
|
||||
**Corrective gate 2026-07-26.** Later connected house/dungeon testing exposed
|
||||
one I7 consumer omission: `CEnvCell::find_transit_cells` still required the
|
||||
deleted parsed portal-polygon dictionary, even though the prepared topology
|
||||
and polygon table retained the exact same plane. The production consumer now
|
||||
uses that direct prepared index. Synthetic graph-free tests and the captured
|
||||
Holtburg cottage camera path prove identical graph/prepared results; no
|
||||
package rebake or parsed-graph restoration is needed. Evidence:
|
||||
[`../research/2026-07-26-prepared-indoor-transit-regression.md`](../research/2026-07-26-prepared-indoor-transit-regression.md).
|
||||
|
||||
## 4. Commit order
|
||||
|
||||
Each unit is independently buildable and reversible:
|
||||
|
||||
1. `docs(physics): pin Slice I collision oracle and fixtures`
|
||||
2. `perf(physics): reuse reset-complete transition scratch`
|
||||
3. `feat(physics): define deterministic flat collision assets`
|
||||
4. `feat(content): bake and read flat collision assets`
|
||||
5. `feat(physics): add differential flat BSP traversal`
|
||||
6. `feat(streaming): shadow-publish flat collision assets`
|
||||
7. `perf(physics): cut production traversal to flat assets`
|
||||
8. `refactor(physics): remove parsed collision graphs`
|
||||
9. `docs(physics): close Slice I evidence and roadmap`
|
||||
|
||||
The exact production-cutover commit and its `git revert <sha>` command are
|
||||
added to this plan and `claude-memory/project_physics_collision_digest.md`
|
||||
immediately when step 7 lands, before any visual/correctness gate.
|
||||
|
|
@ -1,465 +0,0 @@
|
|||
# Modern runtime Slice J — presentation-independent runtime
|
||||
|
||||
**Status:** COMPLETE 2026-07-27
|
||||
**Parent:** `2026-07-24-modern-runtime-architecture.md`, Slice J
|
||||
**Authorization:** the user approved Slices F–L, including the otherwise-frozen
|
||||
Slice J gameplay-owner moves, on 2026-07-24
|
||||
**Purpose:** move the existing authoritative client kernel into a
|
||||
presentation-independent assembly without creating a second world, changing
|
||||
retail behavior, or making the graphical host a special case.
|
||||
|
||||
Slice J is a lifetime-group campaign, not one large move. Each sub-slice keeps
|
||||
the graphical client on the same owner instance, establishes host parity, and
|
||||
deletes the replaced App production path before the next group moves.
|
||||
|
||||
## 1. Fixed invariants
|
||||
|
||||
1. `RuntimeEntityDirectory` is the sole server-GUID/incarnation/local-ID owner.
|
||||
`LiveEntityRuntime` is the exact-key App projection host. There is never a
|
||||
second GUID map or synchronized gameplay world.
|
||||
2. Runtime types do not reference `AcDream.App`, either UI assembly, Silk.NET,
|
||||
OpenAL, Arch, ImGui, a native window, or an OpenGL resource.
|
||||
3. App projection state is keyed by runtime-issued local entity identity plus
|
||||
incarnation. It may not recover or mirror server GUID ownership.
|
||||
4. The accepted update order, packet ordering, timestamp gates, outbound
|
||||
cadence, and retail-shaped gameplay algorithms do not change during the
|
||||
extraction.
|
||||
5. Contracts expose borrowed/immutable views, typed commands, and ordered
|
||||
deltas. They do not expose App render components or deep per-frame object
|
||||
graphs.
|
||||
6. All clocks, queues, random sources, diagnostics identity, plugin behavior,
|
||||
and mutable caches that can differ between sessions are instance-scoped.
|
||||
7. Immutable prepared content may be shared. Session state and mutable query
|
||||
scratch may not.
|
||||
8. Every new owner participates in the structural teardown protocol in parent
|
||||
plan §9.2.1 through acquisition leases and exact acknowledgements.
|
||||
9. A headless construction path is proved after every coherent move. “No
|
||||
window” means no App, Silk, OpenAL, retained UI, particle, or render assembly
|
||||
is loaded—not a hidden graphical process.
|
||||
10. Slice I is closed: production collision is flat-authoritative and retains
|
||||
no parsed collision graph. J1 contracts adapt that final canonical owner
|
||||
shape; they may not reintroduce a graph fallback or mirrored collision
|
||||
authority.
|
||||
|
||||
## 2. Target dependency direction
|
||||
|
||||
```text
|
||||
AcDream.App ───────────────► AcDream.Runtime
|
||||
AcDream.Headless (Slice K) ─► AcDream.Runtime
|
||||
├──► AcDream.Core.Net
|
||||
├──► AcDream.Core
|
||||
├──► AcDream.Content
|
||||
└──► AcDream.Plugin.Abstractions
|
||||
|
||||
AcDream.Runtime -X► AcDream.App / AcDream.UI.* / Silk.NET / OpenAL / Arch
|
||||
```
|
||||
|
||||
Runtime-emitted entity events contain incarnation, transform, flags, and
|
||||
property deltas only. An App-side projection adapter translates them into
|
||||
`IRenderScene` journal operations. Render vocabulary never enters Runtime.
|
||||
|
||||
## 3. Teardown transaction
|
||||
|
||||
The exact order is:
|
||||
|
||||
1. cancel scheduler/session generations and make commands inert;
|
||||
2. detach producers, drain accepted ordered events, and poison queues;
|
||||
3. withdraw and acknowledge graphical presentation;
|
||||
4. retire session residence and GPU-backed owners through existing fences;
|
||||
5. dispose the process-owned immutable `ContentStore` only after every runtime
|
||||
and host has completed.
|
||||
|
||||
Completed stages never replay. Failure retains the exact remaining suffix.
|
||||
Logout, reset, mid-portal disconnect, reconnect replacement, construction
|
||||
rollback, and native close all traverse the same transaction.
|
||||
|
||||
## 4. Execution slices
|
||||
|
||||
### J0 — boundary, plan, and dependency enforcement
|
||||
|
||||
**Completed 2026-07-25.**
|
||||
|
||||
- Add `AcDream.Runtime` and `AcDream.Runtime.Tests`.
|
||||
- Permit references only to Core, Core.Net, Content, and Plugin.Abstractions.
|
||||
- Add App → Runtime as the future host dependency; Runtime never references
|
||||
App.
|
||||
- Add direct-reference, dependency-closure, source-project, and assembly-load
|
||||
guards that fail if a presentation/backend dependency enters Runtime.
|
||||
- Add only an assembly boundary marker; do not introduce a facade
|
||||
`GameRuntime`, mirror state, or move behavior in J0.
|
||||
- Pin this lifetime-group plan and parent-plan teardown protocol.
|
||||
|
||||
Gate: Release build and full tests pass; the Runtime test process loads no App,
|
||||
UI, Silk, OpenAL, Arch, or ImGui assembly; graphical behavior is byte-for-byte
|
||||
unchanged because no runtime path changed.
|
||||
|
||||
Evidence: four focused dependency tests pass, the Release solution builds with
|
||||
zero errors, and the complete Release suite passes 8,406 tests with five
|
||||
pre-existing skips. Exact rollback:
|
||||
|
||||
```text
|
||||
git revert b632672e5ccabfb44c551e08f1c411ab2669c44a
|
||||
```
|
||||
|
||||
### J1 — read, command, event, clock, and lifecycle contracts
|
||||
|
||||
**Completed 2026-07-25 at `854d9e9c`.**
|
||||
|
||||
- Define `IGameRuntimeView`, typed command interfaces, ordered event/delta
|
||||
records, instance clock, lifecycle states, generation tokens, and teardown
|
||||
acknowledgements.
|
||||
- Build App adapters over the current owners first. Adapters borrow; they do
|
||||
not copy mutable collections or own lifetime.
|
||||
- Add a normalized parity trace of accepted inbound events, runtime commands,
|
||||
state revisions, and lifecycle edges.
|
||||
- Prove press-time graphical input reaches the same current owners through the
|
||||
command contract with no extra frame of latency.
|
||||
|
||||
Gate: adapter and direct-host traces are identical over deterministic session,
|
||||
entity, inventory, chat, movement, and portal fixtures; no canonical owner has
|
||||
moved yet.
|
||||
|
||||
Result:
|
||||
|
||||
- `AcDream.Runtime` owns immutable borrowed-view contracts, typed commands,
|
||||
ordered deltas, an instance clock, generation tokens, lifecycle snapshots,
|
||||
and retryable teardown acknowledgements.
|
||||
- Focused App adapters borrow the current canonical owners. They create no
|
||||
second GUID map, state store, command queue, or frame boundary.
|
||||
- Startup plus press-time selection, movement, and combat input now traverse
|
||||
the typed command seam synchronously and still mutate the exact same owners.
|
||||
- Runtime tests pass 13/13, App tests pass 3,838 with three skips, and the
|
||||
complete Release solution passes 8,424 tests with five skips.
|
||||
- The exact-binary connected gate at
|
||||
`logs/connected-world-gate-20260725-190953/report.json` passed six capped
|
||||
world checkpoints plus a fresh uncapped reconnect. Both processes exited
|
||||
gracefully with no failures.
|
||||
|
||||
Exact rollback:
|
||||
|
||||
```text
|
||||
git revert 854d9e9cd13092bd5aaa3cf025d73eeb4600e9f8
|
||||
```
|
||||
|
||||
### J2 — session lifetime and ordered transport group
|
||||
|
||||
**Completed 2026-07-25 at `75930787`.**
|
||||
|
||||
**Detailed execution plan:**
|
||||
[`2026-07-25-modern-runtime-slice-j2.md`](2026-07-25-modern-runtime-slice-j2.md).
|
||||
|
||||
- Remove App presentation dependencies from `LiveSessionController`,
|
||||
`LiveSessionHost`, lifecycle host, event router, and command router.
|
||||
- Move the same owner instances into Runtime with Core.Net transport and
|
||||
instance-scoped clocks/queues.
|
||||
- Preserve bind-before-connect, publish-after-EnterWorld, exact generation
|
||||
rechecks, receive ordering, ack placement, graceful F653/disconnect, and
|
||||
retryable replacement.
|
||||
- Let App provide only endpoint credentials, presentation bindings, and
|
||||
graphical lifecycle acknowledgements.
|
||||
|
||||
Gate: connected login/logout/reconnect and malformed/reentrant lifecycle suites
|
||||
produce the same packet/order trace; no-window construction can connect and
|
||||
disconnect without loading App or a backend.
|
||||
|
||||
Result: Runtime now owns the one `WorldSession` generation, ordered inbound
|
||||
route, connect/enter/tick/replace transaction, and exact retryable teardown
|
||||
acknowledgement. App keeps only connection-option conversion, graphical
|
||||
callbacks, and one borrowed inertable UI command projection. The Release build,
|
||||
79 Runtime tests, 3,776 App tests / 3 skips, 8,428 complete tests / 5 skips,
|
||||
and the exact-binary seven-checkpoint connected gate pass with graceful exits.
|
||||
Evidence:
|
||||
[`../research/2026-07-25-slice-j2-session-lifetime-closeout.md`](../research/2026-07-25-slice-j2-session-lifetime-closeout.md).
|
||||
|
||||
Exact rollback:
|
||||
|
||||
```text
|
||||
git revert 75930787741db40a83eab8663e4464dce8d687ba
|
||||
```
|
||||
|
||||
### J3 — canonical identity, properties, and object-table group
|
||||
|
||||
**Detailed execution plan:**
|
||||
[`2026-07-25-modern-runtime-slice-j3.md`](2026-07-25-modern-runtime-slice-j3.md).
|
||||
|
||||
**Completed 2026-07-26.** Runtime owns accepted entity
|
||||
wire state and the only canonical identity/incarnation/local-ID directory.
|
||||
App's `LiveEntityProjectionStore` and every materialized presentation/spatial
|
||||
workset are keyed by exact `RuntimeEntityKey`; the temporary GUID-shaped
|
||||
compatibility view is deleted. `RuntimeEntityObjectLifetime` now owns the
|
||||
exact entity directory and live `ClientObjectTable`; App, routing, interaction,
|
||||
and UI borrow both. Exact J3.5 binary `ce3ac310` publishes one committed
|
||||
per-generation Runtime entity/object delta stream, issues identity before App
|
||||
hydration, and supplies direct allocation-free views to graphical and
|
||||
no-window hosts. App reconstructs neither entity nor inventory state/events.
|
||||
J3.6 at `119b7c11` makes every canonical commit, callback, teardown receipt,
|
||||
reset, and direct disposal failure-safe and proves the full owner ledger
|
||||
converges to zero. Its 8,484 Release tests / 5 skips, exact seven-checkpoint
|
||||
lifecycle/reconnect gate, and canonical nine-stop route pass. Evidence:
|
||||
[`../research/2026-07-26-slice-j3-6-lifetime-closeout.md`](../research/2026-07-26-slice-j3-6-lifetime-closeout.md).
|
||||
|
||||
- Strip renderer, particle, Wb, and streaming components from the canonical
|
||||
record into an App projection store keyed only by local identity/incarnation.
|
||||
- Move `LiveEntityRuntime`, accepted spawn/state/property timestamps,
|
||||
`ClientObjectTable`, container/inventory indices, vitals, enchantments,
|
||||
spell state, target/combat state, and their exact teardown into Runtime.
|
||||
- Emit ordered create/update/rebucket/hidden/withdraw/delete deltas.
|
||||
- Keep DAT-backed visual hydration, render registrations, effects, lights,
|
||||
paperdoll, selection markers, and radar projections in App.
|
||||
|
||||
Gate: GUID reuse, equal-generation updates, parent/child identity,
|
||||
inventory/container mutations, Hidden, delete/recreate, and portal teardown
|
||||
match current traces; App has no server-GUID ownership.
|
||||
|
||||
### J4 — chat, inventory, magic, and gameplay-state services
|
||||
|
||||
**Completed 2026-07-26.**
|
||||
|
||||
**Detailed execution plan:**
|
||||
[`2026-07-26-modern-runtime-slice-j4.md`](2026-07-26-modern-runtime-slice-j4.md).
|
||||
|
||||
- Move canonical chat history/channel state, inventory transactions, spell and
|
||||
component state, enchantments, vitals, skills/attributes, and gameplay
|
||||
cooldown state by coherent owner groups.
|
||||
- Keep retained panel layout, text shaping, icons, paperdoll rendering, and
|
||||
status-bar presentation in App.
|
||||
- Route plugins and future bots through the same typed command/event surface
|
||||
as graphical UI.
|
||||
|
||||
Gate: deterministic command/response traces and graphical ViewModel revisions
|
||||
match; no Runtime type references UI abstractions.
|
||||
|
||||
J4.1 completed at `c9d25ade`: Runtime now owns the exact chat transcript,
|
||||
reply/retell targets, negotiated Turbine rooms, friends, and squelch state.
|
||||
Graphical UI, devtools, command routing, plugins/current-runtime projections,
|
||||
and no-window consumers borrow that one graph. Its 152 Runtime tests, 3,765
|
||||
App tests / 3 skips, 8,494 complete Release tests / 5 skips, and exact-binary
|
||||
seven-checkpoint lifecycle/reconnect gate pass. J4.2 completed at `011efbea`:
|
||||
Runtime owns external-container, item-mana, shortcut/component, shared-busy,
|
||||
use-reservation, and one-request-at-a-time state while borrowing J3's exact
|
||||
object table. Its 157 Runtime tests, 3,770 App tests / 3 skips, 8,515 complete
|
||||
Release tests / 5 skips, and exact-binary seven-checkpoint
|
||||
lifecycle/reconnect gate pass. J4.3 completed at `d02a12ce`:
|
||||
`RuntimeCharacterState` owns the coupled spellbook and local player sheet;
|
||||
content, routing, retained UI, reset, and shutdown borrow it, and the duplicate
|
||||
desired-component snapshot is gone. Its 162 Runtime tests, 3,772 App tests / 3
|
||||
skips, 8,522 complete Release tests / 5 skips, and exact-binary
|
||||
seven-checkpoint lifecycle/reconnect gate pass. J4.4 completed at `dcb61efb`:
|
||||
Runtime owns character options and server run/jump projections, exposes exact
|
||||
borrowed J4 gameplay views, and routes retained UI plus future no-window hosts
|
||||
through one synchronous generation-gated state-command seam. Its 169 Runtime
|
||||
tests, 3,777 App tests / 3 skips, 8,534 complete Release tests / 5 skips, and
|
||||
exact-binary seven-checkpoint lifecycle/reconnect gate pass. J4.5 completed at
|
||||
`89e6b207`: Runtime owns the one shortcut manager, graphical command effects
|
||||
flow through the exact Runtime inventory/character owners, the item-use UI
|
||||
cannot construct a second transaction gate, and the combined failure-safe
|
||||
ownership ledger converges. Its 175 Runtime tests, 3,780 App tests / 3 skips,
|
||||
8,544 complete Release tests / 5 skips, exact-binary lifecycle/reconnect gate,
|
||||
and canonical nine-stop route pass. Evidence:
|
||||
[`../research/2026-07-26-slice-j4-5-gameplay-state-closeout.md`](../research/2026-07-26-slice-j4-5-gameplay-state-closeout.md).
|
||||
|
||||
Detailed execution begins with a source-ownership inventory. Move one coherent
|
||||
lifetime group at a time in this order:
|
||||
|
||||
1. chat history/channel/command state;
|
||||
2. inventory transaction and gameplay item-use state that is not already
|
||||
canonical in `RuntimeEntityObjectLifetime`;
|
||||
3. learned spells, formulas/components, enchantments, and cast/cooldown state;
|
||||
4. vitals, skills, attributes, advancement, and character-option state;
|
||||
5. plugin/bot borrowed views and typed commands over those same owners;
|
||||
6. delete every replaced App owner/adapter, then run the combined no-window,
|
||||
graphical parity, teardown, Release, lifecycle/reconnect, and nine-stop
|
||||
gates.
|
||||
|
||||
Panel trees, text layout, icons, 3-D paperdolls, status bars, and animation/VFX
|
||||
remain App presentation. The inventory object collection already moved in J3;
|
||||
J4 must borrow it rather than create another inventory model.
|
||||
|
||||
### J5 — movement, physics, interaction, and combat group
|
||||
|
||||
**Completed 2026-07-26.** Detailed execution began with a source/owner inventory
|
||||
and a named-retail ordering matrix. Each coherent move must preserve the
|
||||
accepted connected interaction, facing, projectile, and collision behavior.
|
||||
The executable sub-slice ledger, presentation boundary, acceptance gates, and
|
||||
rollback discipline live in
|
||||
[`2026-07-26-modern-runtime-slice-j5.md`](2026-07-26-modern-runtime-slice-j5.md).
|
||||
|
||||
J5.1 completed at `b298f99f`. `RuntimeActionState` owns the one exact
|
||||
selection, combat, and temporary target-mode graph. Program, plugins, retained
|
||||
UI, session routing, typed commands/views, and shutdown borrow its children;
|
||||
the App interaction-state class and private controller fallback are gone.
|
||||
Its 182 Runtime tests, 3,779 App tests / 3 skips, 8,550 complete Release tests /
|
||||
5 skips, and exact-binary seven-checkpoint lifecycle/reconnect gate pass.
|
||||
J5.2 completed at `f5f7b417`. Runtime now owns the use throttle and exact
|
||||
use/appraisal/pickup transaction identities, typed ordered interaction FIFO,
|
||||
and post-arrival pickup token while borrowing J4's sole busy/request gate.
|
||||
App retains picking, movement/transport, lighting, toasts, drag/drop, and
|
||||
pending-slot presentation. Its 197 Runtime tests, 3,775 App tests / 3 skips,
|
||||
8,561 complete Release tests / 5 skips, and exact-binary seven-checkpoint
|
||||
lifecycle/reconnect gate pass. J5.3 completed at `20df9d15`: Runtime owns the
|
||||
exact attack, target, combat-mode, and spell-cast intent children while App
|
||||
bars, input adapters, content queries, and transport borrow them. Its 223
|
||||
Runtime tests, 3,754 App tests / 3 skips, 8,566 complete Release tests / 5
|
||||
skips, and exact-binary seven-checkpoint lifecycle/reconnect gate pass. J5.4
|
||||
completed at `aa3f4a60`: Runtime owns the exact local movement controller,
|
||||
construction seam, autorun latch, typed view, and outbound MTS/jump/AP cadence;
|
||||
App input and direct commands borrow the same owner. Its 303 Runtime tests,
|
||||
3,716 App tests / 3 skips, 8,575 complete Release tests / 5 skips,
|
||||
exact-binary lifecycle/reconnect gate, and canonical nine-stop movement route
|
||||
pass. J5.5 completed at `7e6033d0`: Runtime owns the per-session engine/cache/
|
||||
scratch/shadow/collision/body/host/remote/workset graph; App publishes
|
||||
prepared collision and projects committed snapshots. Its 314 Runtime tests,
|
||||
3,718 App tests / 3 skips, 8,588 complete Release tests / 5 skips,
|
||||
exact-binary lifecycle/reconnect gate, and canonical nine-stop collision/
|
||||
movement route pass. J5.6 completed at `2aee3356`: Runtime owns the canonical
|
||||
projectile component, exact-key workset, prediction/correction state, and
|
||||
retail projectile simulation; App resolves immutable DAT shape data and
|
||||
projects committed frames only. Its 317 Runtime tests, 3,718 App tests / 3
|
||||
skips, 47 focused Core projectile tests, 8,591 complete Release tests / 5
|
||||
skips, exact arrow/bolt/spell evidence, lifecycle/reconnect gate, and canonical
|
||||
nine-stop route pass. J5.7 completed at `cdee7a4b`: Runtime owns accepted
|
||||
remote-body/vector activation, final simulation retirement, terminal
|
||||
physics/shadow/workset cleanup, and one combined simulation ownership ledger.
|
||||
Its Runtime-only movement/use/combat/cast/projectile host proves no-window
|
||||
parity and terminal convergence after projection failure. Its 323 Runtime
|
||||
tests, 3,717 App tests / 3 skips, 8,596 complete Release tests / 5 skips,
|
||||
exact-binary lifecycle/reconnect gate, and canonical nine-stop route pass.
|
||||
Evidence:
|
||||
[`../research/2026-07-26-slice-j5-1-canonical-action-state.md`](../research/2026-07-26-slice-j5-1-canonical-action-state.md)
|
||||
and
|
||||
[`../research/2026-07-26-slice-j5-2-interaction-transactions.md`](../research/2026-07-26-slice-j5-2-interaction-transactions.md)
|
||||
and
|
||||
[`../research/2026-07-26-slice-j5-3-combat-magic-intent.md`](../research/2026-07-26-slice-j5-3-combat-magic-intent.md)
|
||||
and
|
||||
[`../research/2026-07-26-slice-j5-4-local-movement-ownership.md`](../research/2026-07-26-slice-j5-4-local-movement-ownership.md)
|
||||
and
|
||||
[`../research/2026-07-26-slice-j5-5-physics-remote-ownership.md`](../research/2026-07-26-slice-j5-5-physics-remote-ownership.md)
|
||||
and
|
||||
[`../research/2026-07-26-slice-j5-6-projectile-ownership.md`](../research/2026-07-26-slice-j5-6-projectile-ownership.md)
|
||||
and
|
||||
[`../research/2026-07-26-slice-j5-7-simulation-ownership-closeout.md`](../research/2026-07-26-slice-j5-7-simulation-ownership-closeout.md).
|
||||
|
||||
- Move presentation-free movement interpretation, authoritative/predicted
|
||||
physics state, selection identity, approach/use transactions, combat intent,
|
||||
projectiles, and outbound movement cadence.
|
||||
- Replace input and camera dependencies with typed commands and runtime views.
|
||||
- Keep camera, mouse look, selection markers/highlight, combat/spell bars,
|
||||
animated pose composition, sounds, and particles in App.
|
||||
- Share immutable flat collision content while retaining per-session transition
|
||||
scratch.
|
||||
|
||||
Gate: bit-identical collision/trajectory fixtures, target-facing and
|
||||
auto-approach traces, outbound packet timing, combat/magic commands, and
|
||||
graphical feel gates remain unchanged.
|
||||
|
||||
### J6 — world, portal, environment, and projection handshake
|
||||
|
||||
**J6 complete 2026-07-26.** The instance-scoped Runtime
|
||||
environment owner landed at `902076c0`. The canonical reveal generation and
|
||||
typed readiness owner landed at `a6860d55` plus `acb845d8`; App's duplicate
|
||||
lifecycle owner is deleted and the exact connected lifecycle/reconnect gate
|
||||
passes with zero reveal invariants. Canonical F751/Position correlation and
|
||||
exact generation/sequence/cell placement handshake landed at `6a063a27`; the
|
||||
App transit coordinator and accepted-destination mirror are deleted. Exact
|
||||
typed host acknowledgement and retryable graphical-resource receipts landed at
|
||||
`18d17d8b`; Runtime now retains every outstanding host obligation through
|
||||
failure, re-entrancy, cancellation, supersession, and reset. Its 365 Runtime
|
||||
tests, 3,716 App tests / 3 skips, 8,642-test / 5-skip complete Release suite,
|
||||
and exact-binary lifecycle/reconnect route pass. Evidence:
|
||||
[`../research/2026-07-26-slice-j6-4-host-acknowledgement.md`](../research/2026-07-26-slice-j6-4-host-acknowledgement.md).
|
||||
Detailed owner inventory, retail ordering, host-acknowledgement contract,
|
||||
execution ledger, adversarial matrix, and current plan:
|
||||
[`2026-07-26-modern-runtime-slice-j6.md`](2026-07-26-modern-runtime-slice-j6.md).
|
||||
|
||||
- Move authoritative teleport/session cell identity, world clock, weather
|
||||
state, and reveal-generation state required by gameplay.
|
||||
- Keep streaming publication, terrain/EnvCell rendering, portal tunnel,
|
||||
materialization VFX, sky drawing, audio, and UI in App.
|
||||
- Define the destination-ready/withdrawal acknowledgements between Runtime and
|
||||
the graphical or headless host without making visual readiness gameplay
|
||||
authority.
|
||||
|
||||
Gate: fresh login, repeated recall/portal, world edge, dungeon, reconnect, and
|
||||
mid-portal disconnect traces match; the graphical screenshots remain accepted.
|
||||
|
||||
### J7 — one `GameRuntime` composition root
|
||||
|
||||
J7.1 completed at `96ddd165`; J7.2/J7.3 completed at
|
||||
`ce41efb9e5938f79a7580476b949d172f52a1e69`. Production now owns one
|
||||
`GameRuntime`, App borrows its children, and shutdown is one ordered root
|
||||
transaction. All focused, complete Release, exact-binary lifecycle/reconnect,
|
||||
and canonical nine-stop automated gates pass. The user accepted radar,
|
||||
paperdoll, near/distant Use, combat facing, and both recall paths on
|
||||
2026-07-27; J7 is closed. Detailed evidence:
|
||||
[`2026-07-26-modern-runtime-slice-j7.md`](2026-07-26-modern-runtime-slice-j7.md)
|
||||
and
|
||||
[`../research/2026-07-26-slice-j7-game-runtime-root-cutover.md`](../research/2026-07-26-slice-j7-game-runtime-root-cutover.md).
|
||||
|
||||
- Compose all moved lifetime groups into one instance-scoped `GameRuntime`.
|
||||
- Make `GameWindow`/App own exactly one Runtime instance plus presentation
|
||||
adapters.
|
||||
- Delete temporary direct-owner adapters and every superseded App production
|
||||
path.
|
||||
- Register all Runtime and App projection owners with structural acquisition
|
||||
leases and the exact teardown transaction.
|
||||
|
||||
Gate: graphical connected lifecycle/resource routes pass with no mirrored
|
||||
state, no extra update latency, and no performance regression.
|
||||
|
||||
Exact J7.2/J7.3 rollback:
|
||||
|
||||
```text
|
||||
git revert ce41efb9e5938f79a7580476b949d172f52a1e69
|
||||
```
|
||||
|
||||
### J8 — no-window integration and Slice J closeout
|
||||
|
||||
**Detailed execution plan:**
|
||||
[`2026-07-26-modern-runtime-slice-j8.md`](2026-07-26-modern-runtime-slice-j8.md).
|
||||
Completed at `a9a822f2` after J7's 2026-07-27 physical-display acceptance.
|
||||
|
||||
- Build a transport/content test host that constructs Runtime directly.
|
||||
- Connect, enter world, receive chat/inventory/world updates, move, use,
|
||||
fight/cast through deterministic fixtures, portal, log out, reconnect, and
|
||||
tear down without loading App/Silk/OpenAL/UI.
|
||||
- Add cancellation, failure injection, same-process multiple-instance
|
||||
isolation, credential-safe diagnostics, and zero-presentation-allocation
|
||||
gates.
|
||||
- Update architecture, milestone, roadmap, issue/divergence ledgers, and
|
||||
durable memory.
|
||||
|
||||
Final gate: Release build/full tests, connected graphical route, no-window
|
||||
integration, resource teardown, packet/order/timing parity, and the user visual
|
||||
matrix all pass. Slice K may then build the Linux multi-session host without
|
||||
another gameplay extraction.
|
||||
|
||||
J8 passes 395 Runtime tests, 3,731 App tests / 3 skips, 8,696 complete-solution
|
||||
tests / 5 skips, the exact-binary lifecycle/reconnect route, and the canonical
|
||||
nine-stop route. Graphical and direct hosts share one `GameRuntime`, one
|
||||
retryable canonical-generation reset transaction, and the same gameplay-owner
|
||||
graph. Exact rollback:
|
||||
|
||||
```text
|
||||
git revert a9a822f206cc6021bc099988144488f3bd7c397b
|
||||
```
|
||||
|
||||
## 5. Commit order
|
||||
|
||||
1. `arch(runtime): establish presentation-independent boundary`
|
||||
2. `feat(runtime): define borrowed views commands and ordered events`
|
||||
3. `refactor(runtime): move session lifetime and ordered transport`
|
||||
4. `refactor(runtime): move canonical entity and property ownership`
|
||||
5. `refactor(runtime): move gameplay state services`
|
||||
6. `refactor(runtime): move movement physics interaction and combat`
|
||||
7. `refactor(runtime): separate world authority from presentation`
|
||||
8. `refactor(runtime): compose one graphical game runtime`
|
||||
9. `test(runtime): close no-window parity and teardown`
|
||||
|
||||
Each commit is independently buildable and retains an exact rollback command
|
||||
in this plan before its connected gate.
|
||||
|
||||
J5.7 exact rollback:
|
||||
|
||||
```text
|
||||
git revert cdee7a4b49addb5e1500753f6a885f7c899bd0f0
|
||||
```
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
# Modern runtime Slice J2 — session lifetime and ordered transport
|
||||
|
||||
**Status:** COMPLETE 2026-07-25
|
||||
**Parent:** `2026-07-25-modern-runtime-slice-j.md`, J2
|
||||
**Production base:** `854d9e9cd13092bd5aaa3cf025d73eeb4600e9f8`
|
||||
**Production commit:** `75930787741db40a83eab8663e4464dce8d687ba`
|
||||
**J1 documentation:** `f84624b1e9b83f928debdbe65a5a1576b999ee34`
|
||||
|
||||
## 1. Objective
|
||||
|
||||
Move the one canonical live-session lifetime, transport generation, ordered
|
||||
inbound subscription owner, and route activation/teardown transaction from
|
||||
`AcDream.App` into `AcDream.Runtime`.
|
||||
|
||||
The move is structural. It must preserve the exact current connect, character
|
||||
selection, EnterWorld, packet dispatch, command activation, tick, reconnect,
|
||||
logout, and retryable cleanup order. App remains the graphical host and
|
||||
supplies credentials, retained-UI/domain callbacks, the graphical command
|
||||
projection, and presentation reset acknowledgements.
|
||||
|
||||
## 2. Fixed ownership after J2
|
||||
|
||||
`AcDream.Runtime` owns:
|
||||
|
||||
- the exact `WorldSession` generation and its connect/enter/tick/dispose
|
||||
transaction;
|
||||
- generation validation and live-session lifecycle state;
|
||||
- the serial route lifecycle: attach inbound before Connect, activate outbound
|
||||
only after EnterWorld, make commands inert before detaching inbound;
|
||||
- the retryable teardown suffix and exact acknowledgement stages;
|
||||
- the complete ordered inbound subscription set and all Core/Core.Net event
|
||||
wiring;
|
||||
- connection options with no dependency on App `RuntimeOptions`;
|
||||
- the production endpoint/session operations and deterministic operations
|
||||
seam used by no-window tests.
|
||||
|
||||
`AcDream.App` owns:
|
||||
|
||||
- conversion from `RuntimeOptions` into immutable Runtime connection options;
|
||||
- graphical reset, identity, layout, toolbar, settings, dialog, and
|
||||
presentation callbacks;
|
||||
- the `ICommandBus` projection needed by the current UI until gameplay command
|
||||
groups move in J4/J5;
|
||||
- graphical source adapters for existing App interfaces while those consumer
|
||||
groups await their own J3–J6 migrations.
|
||||
|
||||
The App adapters borrow the Runtime session and command route. They do not
|
||||
dispose the transport, track another generation, retain a second active route,
|
||||
or mirror in-world state.
|
||||
|
||||
## 3. Type and file migration
|
||||
|
||||
### J2.1 — Runtime lifetime contracts
|
||||
|
||||
Add under `src/AcDream.Runtime/Session/`:
|
||||
|
||||
- `LiveSessionConnectOptions`
|
||||
- `RuntimeLiveSessionController`
|
||||
- `RuntimeLiveSessionHost`
|
||||
- `RuntimeLiveSessionBinding`
|
||||
- `IRuntimeLiveSessionLifecycleHost`
|
||||
- `IRuntimeLiveSessionOperations`
|
||||
- `IRuntimeLiveSessionEventRoute`
|
||||
- `IRuntimeLiveSessionCommandRoute`
|
||||
- the Runtime-only lifecycle host and retryable route rollback owner
|
||||
|
||||
The host implements the J1 `IRuntimeSessionCommands` surface directly. Start,
|
||||
Reconnect, and Stop are generation-gated at that owner; the J1 App command
|
||||
adapter no longer translates a second session result or invents teardown
|
||||
stages.
|
||||
|
||||
`RuntimeLiveSessionController` exposes only borrowed state:
|
||||
`CurrentSession`, `IsInWorld`, `Generation`, and `Tick`. It carries no UI
|
||||
command bus and implements no App interface.
|
||||
|
||||
### J2.2 — Ordered inbound route move
|
||||
|
||||
Move the existing `LiveSessionEventRouter` and
|
||||
`LiveSessionSubscriptionSet` into Runtime without changing subscription order:
|
||||
|
||||
1. object/property table wiring;
|
||||
2. combat-state wiring;
|
||||
3. entity and environment messages;
|
||||
4. inventory/item state;
|
||||
5. character/spell state;
|
||||
6. social/chat state.
|
||||
|
||||
The existing accepting gate, reverse-order retryable unsubscribe, construction
|
||||
rollback, and reentrant-cleanup rejection remain line-for-line. The route
|
||||
bindings remain delegates over current owners for J2; their owner groups move
|
||||
in J3/J4.
|
||||
|
||||
### J2.3 — Graphical host adapter
|
||||
|
||||
Replace the App lifetime implementation with focused adapters:
|
||||
|
||||
- a session source bridge for existing App `IsInWorld`, `CurrentSession`,
|
||||
frame-tick, and retained-UI seams;
|
||||
- a graphical route factory that constructs the moved Runtime event router and
|
||||
the current App command projection;
|
||||
- a single active-command projection whose retained UI reference becomes
|
||||
inert before route detachment;
|
||||
- graphical lifecycle callbacks for reset, selection, EnterWorld, connecting,
|
||||
and connected presentation.
|
||||
|
||||
Delete the superseded App controller, lifecycle-host logic, subscription
|
||||
owner, and route rollback implementation in the same commit. Do not keep
|
||||
delegating compatibility copies of lifetime state.
|
||||
|
||||
### J2.4 — Test ownership
|
||||
|
||||
Move controller, host, lifecycle, event-router, and subscription tests into
|
||||
`AcDream.Runtime.Tests`. Preserve every existing call-order, duplicate start,
|
||||
reentrant reconnect/stop/dispose, rollback-failure, stale generation, malformed
|
||||
packet, and retryable teardown case.
|
||||
|
||||
Keep graphical command-projection and App bridge tests in
|
||||
`AcDream.App.Tests`. Add assertions that:
|
||||
|
||||
- the bridge has no generation or in-world backing fields;
|
||||
- the command projection has at most one borrowed active route;
|
||||
- stale command references are inert after replacement;
|
||||
- press-time graphical input still reaches the same owner synchronously.
|
||||
|
||||
Add a Runtime-only construction test that creates the production lifetime
|
||||
graph with deterministic transport operations, enters and exits a session,
|
||||
and verifies that App, UI, Silk.NET, OpenAL, Arch, and ImGui assemblies are
|
||||
not loaded.
|
||||
|
||||
## 4. Exact behavioral order
|
||||
|
||||
Start:
|
||||
|
||||
1. advance generation;
|
||||
2. drain any retired teardown suffix;
|
||||
3. reset the supplied host when required;
|
||||
4. validate enabled state and credentials;
|
||||
5. resolve endpoint and create the one `WorldSession`;
|
||||
6. construct and attach all inbound routes;
|
||||
7. publish connecting state;
|
||||
8. Connect and receive the character list;
|
||||
9. publish connected state;
|
||||
10. select the first available character and publish identity;
|
||||
11. EnterWorld;
|
||||
12. activate the outbound command route;
|
||||
13. mark in-world and publish entered-world presentation.
|
||||
|
||||
Stop/replacement:
|
||||
|
||||
1. advance generation and reject further old-generation commands;
|
||||
2. make the active graphical command route inert;
|
||||
3. detach inbound subscriptions in reverse order;
|
||||
4. dispose `WorldSession`, preserving its graceful F653/disconnect behavior;
|
||||
5. detach the exact host/session pair;
|
||||
6. reset domain and presentation state;
|
||||
7. acknowledge only the completed stages.
|
||||
|
||||
If a stage throws, the exact remaining suffix is retained. A retry never
|
||||
replays a completed stage.
|
||||
|
||||
## 5. Acceptance
|
||||
|
||||
Focused:
|
||||
|
||||
- Runtime lifetime and event-route suites all pass after moving from App;
|
||||
- App graphical bridge and command projection suites pass;
|
||||
- J1 normalized traces are identical before and after the move;
|
||||
- source/dependency guards find no Runtime presentation/backend dependency;
|
||||
- Runtime-only session construction and teardown load no forbidden assembly.
|
||||
|
||||
Complete:
|
||||
|
||||
- `dotnet build AcDream.slnx -c Release`;
|
||||
- full Release solution tests;
|
||||
- connected capped login/six-checkpoint/logout plus fresh uncapped reconnect
|
||||
on the exact binary;
|
||||
- both processes exit gracefully;
|
||||
- no packet/order, reveal, route, teardown, or resource-convergence failure.
|
||||
|
||||
J2 changes no pixels, input feel, or retail algorithm. A separate user visual
|
||||
pause is therefore not required unless the connected gate exposes a
|
||||
presentation symptom.
|
||||
|
||||
## 6. Result and rollback
|
||||
|
||||
All focused and complete gates passed: 79 Runtime tests, 3,776 App tests with
|
||||
three skips, 8,428 complete Release tests with five skips, and the exact-binary
|
||||
seven-checkpoint connected lifecycle/reconnect route. Both processes exited
|
||||
gracefully; every checkpoint had zero render-shadow mismatch and zero pending
|
||||
render delta. Evidence:
|
||||
[`../research/2026-07-25-slice-j2-session-lifetime-closeout.md`](../research/2026-07-25-slice-j2-session-lifetime-closeout.md).
|
||||
|
||||
Exact J2 rollback:
|
||||
|
||||
```text
|
||||
git revert 75930787741db40a83eab8663e4464dce8d687ba
|
||||
```
|
||||
|
||||
J1 remains independently reversible:
|
||||
|
||||
```text
|
||||
git revert 854d9e9cd13092bd5aaa3cf025d73eeb4600e9f8
|
||||
```
|
||||
|
|
@ -1,270 +0,0 @@
|
|||
# Modern runtime J3.4 — canonical object-table ownership
|
||||
|
||||
**Status:** COMPLETE
|
||||
**Parent:** `2026-07-25-modern-runtime-slice-j3.md`
|
||||
**Required production base:** `e937cc36df39cf6ea1eaba2f9c0243d1929da702`
|
||||
**Prior closeout:** `docs/research/2026-07-25-slice-j3-3-exact-projection-store.md`
|
||||
**Production commit:** `5ef8b5371d8990f0380acd939e71cd711289d429`
|
||||
**Closeout:** `docs/research/2026-07-26-slice-j3-4-canonical-object-lifetime.md`
|
||||
|
||||
## Objective
|
||||
|
||||
Make one presentation-independent Runtime lifetime-group root own both:
|
||||
|
||||
- the canonical `RuntimeEntityDirectory`; and
|
||||
- the existing canonical `ClientObjectTable`.
|
||||
|
||||
App, retained UI, interaction controllers, and the graphical projection host
|
||||
must borrow those exact same instances. There must be no copied inventory
|
||||
model, second object table, asynchronous delivery seam, extra input frame, or
|
||||
change to retail event order.
|
||||
|
||||
This slice moves ownership and transaction boundaries, not the
|
||||
`ClientObjectTable` implementation out of `AcDream.Core`. The type is already
|
||||
presentation-free and has extensive Core conformance coverage.
|
||||
|
||||
## Current ownership inventory
|
||||
|
||||
At the required base:
|
||||
|
||||
- `LiveEntityRuntime` constructs its own `RuntimeEntityDirectory`.
|
||||
- `GameWindow` constructs a public readonly `ClientObjectTable Objects`.
|
||||
- App composition records pass that table to retained UI, selection,
|
||||
interaction, magic, combat, paperdoll, appraisal, cooldown, and diagnostics.
|
||||
- Runtime `LiveSessionEventRouter` already applies property, stack, inventory,
|
||||
player-description, container, and appraisal traffic directly to the
|
||||
borrowed table through `ObjectTableWiring`.
|
||||
- App `LiveEntityHydrationController` applies CreateObject to the table only
|
||||
after canonical Runtime create acceptance and revalidates
|
||||
`CreateIntegrationVersion`.
|
||||
- App `LiveEntityDeletionController` applies logical DeleteObject before the
|
||||
exact App projection teardown suffix.
|
||||
- App `LiveSessionResetManifest` clears the object table before clearing live
|
||||
graphical projections.
|
||||
- Optimistic moves, confirmations, rollback, stack updates, container
|
||||
replacement, and every table event are synchronous on the current thread.
|
||||
|
||||
Retail behavior and citations already live in:
|
||||
|
||||
- `src/AcDream.Core/Items/ClientObjectTable.cs`;
|
||||
- `src/AcDream.Core.Net/ObjectTableWiring.cs`;
|
||||
- `docs/research/2026-07-13-retail-give-item-pseudocode.md`;
|
||||
- the named-retail symbols cited beside `ACCObjectMaint::CreateObject`,
|
||||
`ACCObjectMaint::DeleteObject`, and
|
||||
`ACCWeenieObject::ServerSaysMoveItem`.
|
||||
|
||||
J3.4 does not reinterpret those algorithms.
|
||||
|
||||
## Fixed target
|
||||
|
||||
Add a presentation-free Runtime owner equivalent to:
|
||||
|
||||
```csharp
|
||||
public sealed class RuntimeEntityObjectLifetime
|
||||
{
|
||||
public RuntimeEntityDirectory Entities { get; }
|
||||
public ClientObjectTable Objects { get; }
|
||||
}
|
||||
```
|
||||
|
||||
The concrete name may change, but these invariants do not:
|
||||
|
||||
1. The owner constructs each canonical instance exactly once.
|
||||
2. `LiveEntityRuntime` receives `Entities`; it may not construct a directory.
|
||||
3. Runtime session routing receives `Objects` from the same owner.
|
||||
4. App projection, UI, and interaction composition receive borrowed references
|
||||
only.
|
||||
5. Accepted CreateObject/DeleteObject object-table mutation is implemented by
|
||||
the Runtime group and revalidates the exact canonical record/version.
|
||||
6. Reset preserves the current order: object-table clear before graphical live
|
||||
projection teardown, with completed stages never replayed.
|
||||
7. Studio/sample-data object tables remain independent fixture owners and are
|
||||
not part of the live session.
|
||||
|
||||
## Execution
|
||||
|
||||
### 1. Pin parity traces and construction identity
|
||||
|
||||
- Capture normalized traces for:
|
||||
- initial player description and equipment manifest;
|
||||
- item CreateObject add/update;
|
||||
- same-incarnation refresh;
|
||||
- new-generation replacement;
|
||||
- property Int/Int64/String/Float/DataID updates;
|
||||
- stack-size update;
|
||||
- optimistic move, authoritative confirm, and rejection rollback;
|
||||
- wield/unwield;
|
||||
- ViewContents replacement and close;
|
||||
- logical delete, GUID reuse, and session clear.
|
||||
- Add a construction-identity test proving the router, App projection host,
|
||||
retained UI source, and interaction source all receive one table instance.
|
||||
- Preserve current synchronous event order in the golden trace.
|
||||
|
||||
### 2. Introduce the Runtime lifetime-group root
|
||||
|
||||
- Add the owner under `src/AcDream.Runtime/Entities/`.
|
||||
- Construct `RuntimeEntityDirectory` and `ClientObjectTable` there.
|
||||
- Expose borrowed properties only; no collection mirror or event repeater.
|
||||
- Add Runtime-only tests that instantiate it without loading App, UI, Silk,
|
||||
OpenAL, Arch, or ImGui assemblies.
|
||||
- Keep mutable state instance-scoped so two roots can hold conflicting GUIDs
|
||||
and inventory contents without interference.
|
||||
|
||||
### 3. Inject canonical identity into the App projection host
|
||||
|
||||
- Change `LiveEntityRuntime` to require the exact
|
||||
`RuntimeEntityDirectory` instance from the group.
|
||||
- Remove its internal `new RuntimeEntityDirectory(...)`.
|
||||
- Preserve the existing local-ID start override for focused wraparound tests
|
||||
through an explicit fixture/group construction seam, not a second production
|
||||
allocator.
|
||||
- Assert that App cannot replace the directory after projection construction.
|
||||
- Preserve J3.3 `RuntimeEntityKey` identity and teardown behavior unchanged.
|
||||
|
||||
### 4. Replace the `GameWindow` object-table owner
|
||||
|
||||
- Construct the Runtime group before retained UI and live presentation
|
||||
composition.
|
||||
- Delete `GameWindow`'s public readonly `ClientObjectTable Objects` field.
|
||||
- Pass `runtimeGroup.Objects` through the existing typed composition records.
|
||||
- Keep App controllers as direct synchronous borrowers; do not wrap every read
|
||||
in a copied DTO or event bus.
|
||||
- Add source guards rejecting a production App `new ClientObjectTable()` and a
|
||||
second Runtime entity directory. Exempt Studio/sample-data and tests.
|
||||
|
||||
### 5. Move accepted object mutation to the Runtime group
|
||||
|
||||
- Move the generation/version-sensitive
|
||||
`ObjectTableWiring.ApplyEntitySpawn/Delete` transaction boundary out of App.
|
||||
- Runtime must apply a CreateObject only after the canonical directory accepts
|
||||
that exact incarnation and while its create-integration version remains
|
||||
current.
|
||||
- New-generation replacement must remove the old object generation and publish
|
||||
the same add/update/removal sequence as today.
|
||||
- Logical delete must mutate the table before App graphical teardown, exactly
|
||||
as the current `beforeTeardown` callback does.
|
||||
- Pickup/parent leave-world must not delete the object-table entry.
|
||||
- Dormant liveness pruning must retain the existing object-table semantics;
|
||||
only an authoritative logical delete removes the retained object generation.
|
||||
- Keep all callbacks synchronous and revalidate after every callback that can
|
||||
replace, delete, or clear the incarnation.
|
||||
|
||||
### 6. Route session and reset ownership
|
||||
|
||||
- Bind J2 `LiveSessionEventRouter` directly from the Runtime group rather than
|
||||
an App-owned table field.
|
||||
- Keep the current pre-Connect subscription order.
|
||||
- Keep local-player property projection to `LocalPlayerState` and the same
|
||||
table instance until J4 moves the coherent player/gameplay state group.
|
||||
- Replace the App reset field with a Runtime-group object-table reset
|
||||
acknowledgement at the same manifest position.
|
||||
- Failure injection must retain the unfinished reset suffix and never clear a
|
||||
replacement session's table.
|
||||
|
||||
### 7. Delete superseded ownership seams
|
||||
|
||||
- Remove public ownership comments and aliases that describe the table as a
|
||||
GameWindow state owner.
|
||||
- Remove any App construction helper whose only purpose was to allocate the
|
||||
live table.
|
||||
- Keep direct table parameters on UI/controllers for now: they are explicit
|
||||
borrowed dependencies and J4 will move higher-level inventory transactions.
|
||||
- Update architecture, plans, durable memory, and exact rollback in the same
|
||||
closeout commit.
|
||||
|
||||
## Adversarial cases
|
||||
|
||||
- Reentrant `ObjectAdded` deletes or replaces the same GUID.
|
||||
- Reentrant `ObjectMoved` starts another optimistic move.
|
||||
- Delete arrives during CreateObject callbacks.
|
||||
- Same GUID with a new `INSTANCE_TS` while old projection teardown is pending.
|
||||
- Unknown-object authoritative move publishes the same nullable item event.
|
||||
- Equal, stale, and wrapped object/physics generations.
|
||||
- Multiple in-flight optimistic moves followed by confirm/reject in either
|
||||
order.
|
||||
- Container replacement while item events mutate the same container.
|
||||
- Reset during object-table event dispatch.
|
||||
- Failed graphical teardown after canonical object removal.
|
||||
- Two Runtime roots use identical GUIDs with different contents.
|
||||
- No-window root executes the full entity/object lifecycle without loading App
|
||||
or backend assemblies.
|
||||
|
||||
## Mandatory gates
|
||||
|
||||
Run in this order:
|
||||
|
||||
1. Runtime entity/object lifetime tests.
|
||||
2. Core `ClientObjectTable*` and `ObjectTableWiring*` tests.
|
||||
3. Focused App hydration, deletion, reset, inventory, interaction, paperdoll,
|
||||
magic, cooldown, and runtime-adapter tests.
|
||||
4. All Runtime tests.
|
||||
5. All App tests.
|
||||
6. `dotnet build AcDream.slnx -c Release`.
|
||||
7. `dotnet test AcDream.slnx -c Release --no-build`.
|
||||
8. Exact-binary connected lifecycle/reconnect gate.
|
||||
9. At every connected checkpoint: zero render-shadow mismatches, zero pending
|
||||
deltas, graceful process exit, and object/projection teardown convergence.
|
||||
|
||||
A user visual pause is not required unless an automated or connected gate
|
||||
exposes an inventory, paperdoll, selection, or interaction symptom.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- One Runtime lifetime-group root owns the exact entity directory and object
|
||||
table.
|
||||
- `GameWindow` owns neither instance directly.
|
||||
- App and Runtime routing borrow the same object table.
|
||||
- Runtime remains the only GUID/incarnation/local-ID authority.
|
||||
- Create/delete/property/container ordering matches the pinned trace.
|
||||
- Optimistic move/rollback and press-time UI remain synchronous.
|
||||
- No App/UI/backend dependency enters Runtime.
|
||||
- No mirror, queue, frame delay, or steady-state traversal allocation is added.
|
||||
- Runtime-only entity/object lifecycle and multiple-instance isolation pass.
|
||||
- Complete Release and exact connected gates pass on one committed binary.
|
||||
|
||||
## Rollback
|
||||
|
||||
The exact J3.4 rollback is:
|
||||
|
||||
```text
|
||||
git revert 5ef8b5371d8990f0380acd939e71cd711289d429
|
||||
```
|
||||
|
||||
J3.3 remains independently reversible; do not use its rollback for a J3.4
|
||||
failure.
|
||||
|
||||
## Result
|
||||
|
||||
`RuntimeEntityObjectLifetime` now constructs and owns the one
|
||||
`RuntimeEntityDirectory` and one live `ClientObjectTable`. `GameWindow`,
|
||||
session routing, retained UI, interaction, and the graphical projection host
|
||||
borrow those exact instances.
|
||||
|
||||
Accepted CreateObject mutation revalidates the exact canonical record and
|
||||
`CreateIntegrationVersion` before, during, and after synchronous object-table
|
||||
callbacks. Authoritative DeleteObject acceptance uses an exact, single-use
|
||||
Runtime token: the active identity is retired first and retained-object
|
||||
removal then commits before App resource teardown. Dormant/offscreen pruning
|
||||
does not remove retained object information.
|
||||
|
||||
Final gates:
|
||||
|
||||
- Runtime: 118 passed;
|
||||
- App: 3,762 passed / 3 skipped;
|
||||
- complete Release solution: 8,453 passed / 5 skipped;
|
||||
- exact-binary connected lifecycle/reconnect:
|
||||
`logs/connected-world-gate-20260726-055713/report.json`;
|
||||
- two sessions, seven checkpoints, graceful zero-code exits, zero failures,
|
||||
zero render-shadow mismatches, zero pending deltas, and zero pending live
|
||||
teardown/publication/retirement work.
|
||||
|
||||
## Final J3 goal
|
||||
|
||||
J3.4 is not the end of J3. J3.5 must publish one ordered per-session
|
||||
entity/object delta stream and make graphical/direct hosts consume the same
|
||||
committed facts without a second queue. J3.6 then failure-injects and proves
|
||||
the complete no-window entity/object lifecycle, zero leaked records,
|
||||
tombstones, projections, container entries, queued deltas, or subscriptions,
|
||||
plus Release and connected parity. That J3.6 proof is the clean boundary before
|
||||
J4 moves inventory transactions, vitals, enchantments, magic, and other
|
||||
gameplay-state services.
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
# Modern runtime J3.3 — exact-key App projection store
|
||||
|
||||
**Status:** COMPLETE 2026-07-25
|
||||
**Parent:** `2026-07-25-modern-runtime-slice-j3.md`
|
||||
**Required base:** `f46ddb5cdb1e145752bea49aeb1d62bfe71284d3`
|
||||
**Production commits:** `420e5eea70fd2c29cf9c8614e6298a1f84d64045`,
|
||||
then exact-spatial correction
|
||||
`e937cc36df39cf6ea1eaba2f9c0243d1929da702`
|
||||
**Closeout:** `docs/research/2026-07-25-slice-j3-3-exact-projection-store.md`
|
||||
|
||||
## Objective
|
||||
|
||||
Make every materialized App-side live-object owner use the exact
|
||||
`RuntimeEntityKey` (`Runtime local ID + INSTANCE_TS`) issued by
|
||||
`RuntimeEntityDirectory`.
|
||||
|
||||
Runtime remains the only server-GUID authority. App may carry GUID as immutable
|
||||
metadata for diagnostics and outbound commands, but no App dictionary may use
|
||||
GUID to decide which incarnation is current.
|
||||
|
||||
No asynchronous projection queue, extra input frame, renderer behavior change,
|
||||
or local-ID allocation-order change is permitted.
|
||||
|
||||
## Fixed model
|
||||
|
||||
There are three distinct states:
|
||||
|
||||
1. **Canonical-only registration** — Runtime accepted an incarnation, but no
|
||||
graphical host has claimed a local ID. No materialized projection exists.
|
||||
2. **Materialized projection** — Runtime has claimed the local ID and App owns
|
||||
exactly one sidecar under the resulting `RuntimeEntityKey`.
|
||||
3. **Retryable teardown** — Runtime retains the exact canonical tombstone while
|
||||
App retains only the unfinished teardown suffix for that exact key. A
|
||||
registration that failed before local-ID claim has no graphical tombstone.
|
||||
|
||||
A small synchronous construction context may carry a canonical record between
|
||||
registration and materialization. It is not a retained App identity map.
|
||||
|
||||
## Execution
|
||||
|
||||
### 1. Introduce `LiveEntityProjectionStore`
|
||||
|
||||
- Store materialized sidecars in
|
||||
`Dictionary<RuntimeEntityKey, LiveEntityRecord>`.
|
||||
- Resolve GUID only by asking `RuntimeEntityDirectory` for the current
|
||||
canonical record, reading its exact key, then querying the store.
|
||||
- Reject key reuse, local-ID mismatch, incarnation mismatch, and stale
|
||||
canonical records.
|
||||
- Preserve insertion order and allocation-free borrowed enumeration.
|
||||
- Retain teardown progress under the exact key; use canonical-reference storage
|
||||
only for the exceptional pre-materialization registration rollback which has
|
||||
no key.
|
||||
|
||||
### 2. Move sidecar creation to local-ID claim
|
||||
|
||||
- `RegisterLiveEntity` returns canonical registration facts without allocating
|
||||
a graphical sidecar.
|
||||
- `MaterializeLiveEntity` claims the Runtime local ID at the same point as
|
||||
today, creates the exact-key sidecar, then calls the projection factory.
|
||||
- Same-incarnation refresh before materialization mutates only Runtime state.
|
||||
- Same-incarnation refresh after materialization resolves the existing sidecar
|
||||
through its exact key.
|
||||
- Factory, resource-registration, and rollback failures retain exactly the
|
||||
currently unfinished ownership suffix.
|
||||
|
||||
### 3. Re-key every presentation workset
|
||||
|
||||
Convert the following from GUID/local-ID-only maps to exact-key maps:
|
||||
|
||||
- materialized and visible `WorldEntity` projections;
|
||||
- animation and spatial-animation owners;
|
||||
- remote-motion and spatial-remote-motion owners;
|
||||
- projectile and spatial-projectile owners;
|
||||
- ordinary root object workset;
|
||||
- effect profile, visibility publication, hydration, and teardown lookups.
|
||||
|
||||
Local-ID-only iteration views validate the incarnation through the projection
|
||||
store before yielding. No stale entry can match a replacement that reused the
|
||||
same numeric local ID after wraparound.
|
||||
|
||||
### 4. Cut consumers over to borrowed queries
|
||||
|
||||
- Replace direct GUID dictionary access in selection, radar, combat targeting,
|
||||
local-player animation/mode, teleport, physics update, diagnostics, and
|
||||
composition with typed `TryGet...`, exact-key, or borrowed-enumeration
|
||||
methods.
|
||||
- Keep synchronous press-time reads; do not publish a copied frame snapshot.
|
||||
- Preserve server GUID in yielded metadata where outbound commands need it.
|
||||
- Remove `MaterializedWorldEntities`, `WorldEntities`, and component
|
||||
dictionaries once all consumers use the focused queries.
|
||||
|
||||
### 5. Delete the J3.2 compatibility view
|
||||
|
||||
- Delete `ActiveRecordView`.
|
||||
- Delete `_activeRecords` and every App GUID-keyed live-object/component map.
|
||||
- Remove the detached `LiveEntityRecord(EntitySpawn)` fixture constructor;
|
||||
component tests construct exact Runtime identities through a shared fixture.
|
||||
- Add source guards rejecting:
|
||||
- App `Dictionary<uint, LiveEntityRecord>`;
|
||||
- App live-object/component maps keyed by server GUID;
|
||||
- an App reverse GUID/local-ID map;
|
||||
- a projection store key other than `RuntimeEntityKey`.
|
||||
|
||||
### 6. Failure and parity gate
|
||||
|
||||
Exercise:
|
||||
|
||||
- create without materialization;
|
||||
- same-incarnation refresh before and after materialization;
|
||||
- factory throw before sidecar publication;
|
||||
- resource register failure with successful and failed rollback;
|
||||
- loaded/pending rebucket and visibility reentrancy;
|
||||
- delete/recreate with the same GUID;
|
||||
- local-ID wraparound and stale-key rejection;
|
||||
- parent/pickup/Hidden transitions;
|
||||
- session clear during every projection callback;
|
||||
- retryable teardown with a live replacement.
|
||||
|
||||
Run:
|
||||
|
||||
- focused Runtime and App identity/projection tests;
|
||||
- all App tests;
|
||||
- Release solution build;
|
||||
- complete Release tests;
|
||||
- exact-binary connected lifecycle/reconnect;
|
||||
- render-scene identity/digest referee at every checkpoint.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Runtime owns the only GUID/incarnation/local-ID authority.
|
||||
- Every materialized App owner is keyed by exact `RuntimeEntityKey`.
|
||||
- App retains no GUID identity dictionary or reverse local-ID authority.
|
||||
- A stale key cannot query, update, withdraw, or tear down a replacement.
|
||||
- Local-ID allocation order is unchanged.
|
||||
- No new allocation appears in steady-state live-object traversal.
|
||||
- No callback is delayed to another frame.
|
||||
- All automated and connected gates pass on the exact commit.
|
||||
|
||||
## Completion result
|
||||
|
||||
All acceptance conditions pass on final exact binary `e937cc36`.
|
||||
|
||||
- `LiveEntityProjectionStore` is the only materialized App sidecar store and is
|
||||
keyed by exact `RuntimeEntityKey`.
|
||||
- The temporary `ActiveRecordView`, `_activeRecords`, detached App record
|
||||
constructor, and broad materialized/visible `WorldEntity` collection
|
||||
surfaces are deleted. Borrowed exact-key record views remain for hot
|
||||
traversal and tests.
|
||||
- Hydration, animation, effects, lights, equipped children, remote teleport,
|
||||
render journal, liveness, movement observations, teardown, spatial
|
||||
residence, and visibility transitions carry the exact key.
|
||||
- `GpuWorldState` retains the exact key through loaded/pending rebucketing,
|
||||
landblock retirement, origin recentering, quiescence, and reentrant
|
||||
visibility delivery. DAT-static entities remain outside live Runtime
|
||||
identity.
|
||||
- Runtime remains the only current-GUID/incarnation/local-ID authority. App
|
||||
resolves GUID through Runtime and never decides which incarnation is current.
|
||||
- Synchronous callbacks, allocation order, render ordering, and frame timing
|
||||
are unchanged.
|
||||
|
||||
Evidence:
|
||||
|
||||
- 104 focused App ownership/streaming tests passed.
|
||||
- 3,761 App tests passed / 3 skipped.
|
||||
- 8,444 complete Release tests passed / 5 skipped.
|
||||
- Release solution build passed.
|
||||
- `logs/connected-world-gate-20260725-221008/report.json` passed on exact commit
|
||||
`e937cc36`: two sessions, seven checkpoints, two graceful zero-code exits,
|
||||
zero failures, zero render-shadow mismatches, and zero pending deltas.
|
||||
|
||||
Exact rollback, newest commit first:
|
||||
|
||||
```text
|
||||
git revert e937cc36df39cf6ea1eaba2f9c0243d1929da702
|
||||
git revert 420e5eea70fd2c29cf9c8614e6298a1f84d64045
|
||||
```
|
||||
|
||||
J3.4 is the next clean slice. It moves canonical `ClientObjectTable` ownership
|
||||
under the Runtime entity/object lifetime group without moving retained UI,
|
||||
icons, paperdolls, grids, cooldown drawing, or interaction presentation.
|
||||
|
|
@ -1,323 +0,0 @@
|
|||
# Modern runtime Slice J3 — canonical identity, properties, and object table
|
||||
|
||||
**Status:** COMPLETE 2026-07-26
|
||||
**Parent:** `2026-07-25-modern-runtime-slice-j.md`, J3
|
||||
**Production base:** `9496c01b`
|
||||
**J2 production commit:** `75930787741db40a83eab8663e4464dce8d687ba`
|
||||
|
||||
## 0. Current checkpoint
|
||||
|
||||
| Slice | Status | Commit / evidence |
|
||||
|---|---|---|
|
||||
| J3.0 ownership inventory | complete | `b0fecc5f65ed26042c61a1651fb134aeb432c141`; `docs/research/2026-07-25-slice-j3-entity-ownership-inventory.md` |
|
||||
| J3.1 accepted-wire owners | complete | `f7442d13e9ae7d5b077ffecb1f8a10899ad46edd`; 110 Runtime tests and complete Release parity remain green after J3.2 |
|
||||
| J3.2 canonical Runtime entity directory | complete | `f46ddb5cdb1e145752bea49aeb1d62bfe71284d3`; 8,441 Release tests / 5 skips and `logs/connected-world-gate-20260725-200749/report.json` pass |
|
||||
| J3.3 App projection store | complete | `420e5eea70fd2c29cf9c8614e6298a1f84d64045` + `e937cc36df39cf6ea1eaba2f9c0243d1929da702`; 8,444 Release tests / 5 skips and `logs/connected-world-gate-20260725-221008/report.json` pass |
|
||||
| J3.4 canonical object-table ownership | complete | `5ef8b5371d8990f0380acd939e71cd711289d429`; 118 Runtime tests, 3,762 App tests / 3 skips, 8,453 complete Release tests / 5 skips, and `logs/connected-world-gate-20260726-055713/report.json` pass |
|
||||
| J3.5 ordered deltas and borrowed views | complete | `ce3ac310d92722ffb637e81cb1957458874dd220`; 134 Runtime tests, 3,765 App tests / 3 skips, 8,472 complete Release tests / 5 skips, and `logs/connected-world-gate-20260726-064324/report.json` pass |
|
||||
| J3.6 adversarial hardening and closeout | complete | `119b7c115107245546180b2f5cb3cb44c7d5476a`; 146 Runtime tests, 3,765 App tests / 3 skips, 8,484 complete Release tests / 5 skips, exact lifecycle/reconnect gate, and canonical nine-stop route pass |
|
||||
|
||||
J3.5 passes the Release solution build, 134 Runtime tests, 3,765 App tests with
|
||||
three skips, and 8,472 complete tests with five expected skips. Its exact-
|
||||
binary seven-checkpoint connected lifecycle/reconnect route also passes with
|
||||
graceful exits, zero render-shadow mismatches, and zero pending deltas. Runtime
|
||||
now issues canonical identity before graphical hydration, publishes entity and
|
||||
inventory commits through one per-generation sequence, and exposes the same
|
||||
direct borrowed views to graphical and no-window hosts. App no longer
|
||||
reconstructs those views or events. No frame queue, backend dependency, or
|
||||
gameplay behavior change was added. The exact rollback for J3.5 is:
|
||||
|
||||
```text
|
||||
git revert ce3ac310d92722ffb637e81cb1957458874dd220
|
||||
```
|
||||
|
||||
J3.6 closes the group under callback failure, re-entrancy, retained teardown,
|
||||
reset, GUID reuse, and direct disposal. Its complete owner ledger converges to
|
||||
zero and both exact-binary connected routes pass. Evidence:
|
||||
[`../research/2026-07-26-slice-j3-6-lifetime-closeout.md`](../research/2026-07-26-slice-j3-6-lifetime-closeout.md).
|
||||
The exact rollback for J3.6 is:
|
||||
|
||||
```text
|
||||
git revert 119b7c115107245546180b2f5cb3cb44c7d5476a
|
||||
```
|
||||
|
||||
## 1. Objective
|
||||
|
||||
Move the one canonical server-object identity/incarnation owner, accepted
|
||||
CreateObject/property/timestamp state, parent relation state, and
|
||||
`ClientObjectTable` lifetime into `AcDream.Runtime`.
|
||||
|
||||
App becomes a projection host. Its world entity, animation, effect, light,
|
||||
selection, paperdoll, spatial bucket, and hydration state is keyed by a
|
||||
Runtime-issued local identity plus exact incarnation. App must not retain a
|
||||
second server-GUID map or decide whether a packet belongs to the current
|
||||
incarnation.
|
||||
|
||||
This is a structural move. Existing named-retail CreateObject, timestamp,
|
||||
parenting, pickup, Hidden, delete, and generation-replacement behavior remains
|
||||
unchanged. No renderer, particle, streaming, GL, or retained-UI type may enter
|
||||
Runtime.
|
||||
|
||||
## 2. Scope reconciliation
|
||||
|
||||
The umbrella J3 text overlaps later lifetime groups. This detailed plan is the
|
||||
authoritative split:
|
||||
|
||||
- J3 moves raw accepted entity identity, immutable spawn/property snapshots,
|
||||
timestamp gates, parent relations, and the canonical client object/container
|
||||
table.
|
||||
- J4 moves higher-level inventory transactions, vitals, enchantments,
|
||||
spell/component state, cooldown services, chat, and their ViewModel-facing
|
||||
revisions.
|
||||
- J5 moves movement/physics/interaction/combat authority.
|
||||
|
||||
J3 may retain existing Core physics references already stored on the entity
|
||||
record while separating presentation, but it does not redesign or relocate
|
||||
their behavior. That coherent move remains J5.
|
||||
|
||||
## 3. Fixed ownership after J3
|
||||
|
||||
`AcDream.Runtime` owns:
|
||||
|
||||
- server GUID to current incarnation;
|
||||
- exact Runtime local identity allocation and reverse lookup;
|
||||
- accepted `INSTANCE_TS` and every retail packet-channel timestamp;
|
||||
- the latest accepted immutable `WorldSession.EntitySpawn`;
|
||||
- logical create, same-incarnation refresh, pickup, parent, Hidden/state,
|
||||
delete, generation replacement, tombstone, and session-clear state;
|
||||
- unresolved/staged/committed parent relations;
|
||||
- `ClientObjectTable`, containers, placement indices, pending authoritative
|
||||
property state, and generation-aware removal;
|
||||
- ordered immutable entity/object deltas;
|
||||
- retryable logical teardown state and the acknowledgement from an attached
|
||||
projection host.
|
||||
|
||||
`AcDream.App` owns:
|
||||
|
||||
- `WorldEntity` and render-scene projection;
|
||||
- loaded/pending spatial buckets and visibility derived from streaming;
|
||||
- animation, remote-motion presentation, projectile presentation, effects,
|
||||
lights, selection geometry, radar presentation, and private paperdolls;
|
||||
- projection hydration/recovery transaction state;
|
||||
- render/resource registration and its exact retryable teardown suffix.
|
||||
|
||||
App projection keys are:
|
||||
|
||||
```text
|
||||
RuntimeLocalEntityId + INSTANCE_TS
|
||||
```
|
||||
|
||||
Server GUID may be carried as immutable metadata for diagnostics and outbound
|
||||
commands, but it is not an App lookup authority.
|
||||
|
||||
## 4. Runtime contracts
|
||||
|
||||
Introduce or finish contracts equivalent to:
|
||||
|
||||
```csharp
|
||||
public readonly record struct RuntimeEntityKey(
|
||||
uint LocalEntityId,
|
||||
ushort Incarnation);
|
||||
|
||||
public readonly record struct RuntimeEntityDelta(
|
||||
ulong Sequence,
|
||||
RuntimeGenerationToken SessionGeneration,
|
||||
RuntimeEntityKey Entity,
|
||||
RuntimeEntityDeltaKind Kind,
|
||||
RuntimeEntitySnapshot Snapshot);
|
||||
|
||||
public interface IRuntimeEntityProjectionHost
|
||||
{
|
||||
RuntimeProjectionAcknowledgement Register(RuntimeEntitySnapshot entity);
|
||||
RuntimeProjectionAcknowledgement Update(RuntimeEntityDelta delta);
|
||||
RuntimeProjectionAcknowledgement Withdraw(RuntimeEntityKey entity);
|
||||
RuntimeProjectionAcknowledgement TearDown(RuntimeEntityKey entity);
|
||||
}
|
||||
```
|
||||
|
||||
The concrete names may follow existing project vocabulary, but these
|
||||
invariants are mandatory:
|
||||
|
||||
- Runtime issues identity before App creates a projection.
|
||||
- Every App callback carries the exact local identity and incarnation.
|
||||
- Callback acknowledgement is exact and retryable.
|
||||
- Completed teardown work never replays.
|
||||
- App cannot query or mutate Runtime by a stale local identity/incarnation.
|
||||
- Runtime emits deltas only after canonical state commits.
|
||||
- Callback reentrancy may supersede the outer operation; the outer operation
|
||||
must revalidate generation/incarnation/operation version before continuing.
|
||||
|
||||
## 5. Execution slices
|
||||
|
||||
### J3.0 — oracle and ownership inventory
|
||||
|
||||
- Pin the current field/method ownership table for `LiveEntityRecord`,
|
||||
`LiveEntityRuntime`, `InboundPhysicsStateController`,
|
||||
`ParentAttachmentState`, `ClientObjectTable`, `GpuWorldState`, hydration,
|
||||
and teardown owners.
|
||||
- Reuse the existing retail pseudocode and citations; no behavior is being
|
||||
invented. Add research only where a current branch lacks a cited oracle.
|
||||
- Capture normalized existing traces for create, same-generation refresh,
|
||||
parent/unparent, pickup, Hidden, delete/recreate, GUID reuse, container
|
||||
placement, and session clear.
|
||||
- Record baseline render-scene identity/digest at deterministic fixtures.
|
||||
|
||||
### J3.1 — move narrow accepted-wire owners
|
||||
|
||||
- Move `InboundPhysicsStateController`, `ParentAttachmentState`, their result
|
||||
types, and their complete tests into Runtime.
|
||||
- Preserve timestamp cursor/order behavior and parent candidate ordering
|
||||
line-for-line.
|
||||
- App's existing `LiveEntityRuntime` temporarily composes those Runtime owners;
|
||||
no second state is introduced.
|
||||
- Add Runtime dependency/load guards around the moved types.
|
||||
|
||||
Gate: all timestamp, malformed, wraparound, parent-generation, and packet-order
|
||||
tests pass in Runtime; App behavior and complete suite remain unchanged.
|
||||
|
||||
### J3.2 — canonical Runtime entity directory
|
||||
|
||||
- Extract current GUID/incarnation maps, accepted snapshot state, session
|
||||
lifetime version, operation versions, tombstones, delete/generation
|
||||
replacement, and local-ID allocation into one Runtime owner.
|
||||
- Split `LiveEntityRecord` into canonical Runtime state and an App projection
|
||||
sidecar. Canonical fields never reference App interfaces or `WorldEntity`.
|
||||
- Preserve current local-ID allocation order during graphical hydration for
|
||||
parity; Runtime owns the allocator and claim, while a no-window host may
|
||||
claim immediately.
|
||||
- Retain exact reentrant supersession and retryable tombstone behavior.
|
||||
- Keep physics/movement fields structurally in place where required for J5,
|
||||
but remove every renderer/effect/hydration reference from the canonical
|
||||
record.
|
||||
|
||||
Gate: the complete existing `LiveEntityRuntimeTests` identity/timestamp/
|
||||
teardown subset moves to Runtime and produces the same normalized trace.
|
||||
|
||||
### J3.3 — App projection store and cutover
|
||||
|
||||
- Add one App projection store keyed only by Runtime local identity plus
|
||||
incarnation.
|
||||
- Move `WorldEntity`, spatial visibility, hydration/recovery flags, animation,
|
||||
remote-motion presentation, projectile presentation, effect profiles,
|
||||
resource flags, and active presentation worksets into that store.
|
||||
- Convert `GpuWorldState`, hydration, equipped children, render journal,
|
||||
animation/effect/light owners, selection/radar queries, and teardown to the
|
||||
exact projection key.
|
||||
- Replace App's mixed `LiveEntityRuntime` facade with focused Runtime directory
|
||||
and App projection-store dependencies; delete its GUID maps and mixed record.
|
||||
- Preserve current register-once, rebucket-only, withdraw-without-destroy,
|
||||
Hidden, and retryable logical teardown order.
|
||||
|
||||
Gate: projection identity/digest and all existing hydration, rebucket,
|
||||
visibility, effect, animation, projectile, selection, and teardown tests match.
|
||||
Source guards reject any App server-GUID authority map.
|
||||
|
||||
### J3.4 — canonical object-table ownership
|
||||
|
||||
**Detailed execution plan:**
|
||||
[`2026-07-25-modern-runtime-slice-j3-object-table.md`](2026-07-25-modern-runtime-slice-j3-object-table.md).
|
||||
|
||||
- Add a Runtime entity/object lifetime-group root that owns the canonical
|
||||
entity directory and the existing Core `ClientObjectTable`.
|
||||
- Construct it before either graphical UI or live presentation; both borrow
|
||||
the same instances.
|
||||
- Remove the public `ClientObjectTable` owner field from `GameWindow`.
|
||||
- Route J2 inbound object/property/container updates directly to the Runtime
|
||||
group.
|
||||
- Keep UI controllers, item icons, paperdoll, grids, cooldown drawing, and
|
||||
interaction presentation as App borrowers.
|
||||
- Preserve synchronous press-time reads and current event ordering; add no
|
||||
queue or copied inventory model.
|
||||
|
||||
Gate: object add/update/remove, placement, container replacement, optimistic
|
||||
move rollback, stack response, GUID reuse, and session-clear traces match.
|
||||
|
||||
### J3.5 — ordered deltas and borrowed views
|
||||
|
||||
**Detailed execution plan:**
|
||||
[`2026-07-26-modern-runtime-slice-j3-delta-stream.md`](2026-07-26-modern-runtime-slice-j3-delta-stream.md).
|
||||
|
||||
- Publish one monotonic per-session Runtime entity/object delta stream.
|
||||
- Make J1 `IGameRuntimeView.Entities` and inventory snapshots read the Runtime
|
||||
owners directly rather than App adapters.
|
||||
- Feed App projection mutation from the same committed deltas/acknowledgements;
|
||||
do not create a second queue or frame delay.
|
||||
- Remove superseded App entity/object event mirroring and source adapters.
|
||||
- Assert same-thread graphical input and inbound presentation remain
|
||||
synchronous.
|
||||
|
||||
Gate: direct-host and graphical-host normalized traces are identical, including
|
||||
sequence, generation, identity, properties, placement, and teardown edges.
|
||||
|
||||
### J3.6 — hardening and closeout
|
||||
|
||||
- Failure-inject every projection registration/update/withdraw/teardown stage.
|
||||
- Exercise callback reentrancy, duplicate CreateObject, stale/equal/wrapped
|
||||
timestamps, delete/recreate with the same GUID, pending-to-loaded movement,
|
||||
parent events before either object, malformed property packets, and session
|
||||
reset during callbacks.
|
||||
- Prove zero leaked canonical records, tombstones, App projections, resources,
|
||||
container entries, parent candidates, queued deltas, or subscriptions.
|
||||
- Add a Runtime-only entity/object fixture that performs create, property
|
||||
updates, parent/container moves, Hidden, delete/recreate, clear, and teardown
|
||||
without loading presentation/backend assemblies.
|
||||
- Run Release build, focused suites, complete tests, exact-binary connected
|
||||
lifecycle/reconnect, and the canonical world route.
|
||||
- Because J3 changes projection identity plumbing, run the existing automated
|
||||
render digest/referee at every checkpoint. Pause for user visual verification
|
||||
only if the referee or connected client exposes a pixel/interaction symptom.
|
||||
|
||||
## 6. Commit order
|
||||
|
||||
1. `refactor(runtime): move accepted entity wire state`
|
||||
2. `refactor(runtime): own canonical entity identity and incarnations`
|
||||
3. `refactor(app): key live projections by runtime identity`
|
||||
4. `refactor(runtime): own canonical object table`
|
||||
5. `feat(runtime): publish ordered entity and object deltas`
|
||||
6. `test(runtime): close J3 entity ownership parity`
|
||||
|
||||
Each commit is independently buildable and bisectable. The exact rollback for
|
||||
each lands in this document before that commit's connected or visual gate.
|
||||
|
||||
## 7. Acceptance
|
||||
|
||||
J3 is complete only when:
|
||||
|
||||
- Runtime owns the only server-GUID/incarnation map and object table;
|
||||
- App projections are keyed by local identity plus incarnation;
|
||||
- Runtime has no App/UI/backend dependency;
|
||||
- App has no authoritative GUID map or accepted timestamp gate;
|
||||
- no mirrored collection, asynchronous seam, or added input frame exists;
|
||||
- normalized direct/graphical traces match;
|
||||
- deterministic render identity/digests match;
|
||||
- Runtime-only no-window entity/object lifecycle passes;
|
||||
- Release build and complete tests pass;
|
||||
- exact-binary connected login/travel/logout/reconnect passes gracefully;
|
||||
- every teardown owner converges to zero.
|
||||
|
||||
## 8. Current rollback chain
|
||||
|
||||
```text
|
||||
git revert 75930787741db40a83eab8663e4464dce8d687ba
|
||||
git revert 854d9e9cd13092bd5aaa3cf025d73eeb4600e9f8
|
||||
git revert b632672e5ccabfb44c551e08f1c411ab2669c44a
|
||||
```
|
||||
|
||||
These are J2, J1, and J0 respectively. Do not use them for a J3 sub-slice
|
||||
failure; record and revert only the exact failing J3 commit.
|
||||
|
||||
J3 sub-slices are independently reversible:
|
||||
|
||||
```text
|
||||
git revert ce3ac310d92722ffb637e81cb1957458874dd220
|
||||
git revert 5ef8b5371d8990f0380acd939e71cd711289d429
|
||||
git revert e937cc36df39cf6ea1eaba2f9c0243d1929da702
|
||||
git revert 420e5eea70fd2c29cf9c8614e6298a1f84d64045
|
||||
git revert f46ddb5cdb1e145752bea49aeb1d62bfe71284d3
|
||||
git revert f7442d13e9ae7d5b077ffecb1f8a10899ad46edd
|
||||
```
|
||||
|
||||
Revert J3.6 before J3.5. Revert J3.5 before J3.4. Revert J3.4 before J3.3.
|
||||
Revert J3.3's exact-spatial
|
||||
correction before its main projection-store commit. Revert J3.3 before J3.2,
|
||||
and J3.2 before J3.1, when rolling back more than one sub-slice.
|
||||
|
|
@ -1,318 +0,0 @@
|
|||
# Modern runtime J3.5 — committed entity/object deltas and borrowed views
|
||||
|
||||
**Status:** COMPLETE — `ce3ac310d92722ffb637e81cb1957458874dd220`
|
||||
**Parent:** `2026-07-25-modern-runtime-slice-j3.md`
|
||||
**Required production base:** `5ef8b5371d8990f0380acd939e71cd711289d429`
|
||||
**Prior closeout:** `docs/research/2026-07-26-slice-j3-4-canonical-object-lifetime.md`
|
||||
|
||||
## Objective
|
||||
|
||||
Publish one synchronous, monotonic, generation-stamped stream from the
|
||||
canonical Runtime entity/object lifetime and make both graphical and
|
||||
no-window/direct hosts observe those same committed facts.
|
||||
|
||||
`IGameRuntimeView.Entities` and `IGameRuntimeView.Inventory` must read the
|
||||
Runtime-owned `RuntimeEntityDirectory` and `ClientObjectTable` directly.
|
||||
`CurrentGameRuntimeEventAdapter` must stop reconstructing entity changes from
|
||||
App projection visibility and inventory changes from an App-owned event seam.
|
||||
|
||||
This slice adds no later-frame queue, copied world, inventory mirror, input
|
||||
delay, renderer dependency, or gameplay behavior change.
|
||||
|
||||
## Current seam at the production base
|
||||
|
||||
- `RuntimeEntityObjectLifetime` owns the exact directory and object table.
|
||||
- `CurrentGameRuntimeViewAdapter.EntityView` still reads App
|
||||
`LiveEntityRuntime`, so a direct host cannot enumerate canonical entities
|
||||
without constructing the graphical projection host.
|
||||
- Its inventory view reads the right table but asks `LiveEntityRuntime` to
|
||||
recover incarnation metadata.
|
||||
- `CurrentGameRuntimeEventAdapter` owns a separate sequencer and reconstructs:
|
||||
- entity events only from `ProjectionVisibilityChanged`; and
|
||||
- inventory events by subscribing directly to object-table callbacks.
|
||||
- Registered, refreshed, parented, picked-up, state-updated, cellless, and
|
||||
authoritative-delete canonical edges therefore do not have one complete
|
||||
Runtime stream.
|
||||
- Runtime local IDs are still claimed at App materialization rather than at
|
||||
canonical registration, even though direct and graphical hosts require one
|
||||
stable cross-host identity.
|
||||
|
||||
The J1 adapter was intentionally temporary. J3.5 deletes these superseded
|
||||
entity/object adapter responsibilities.
|
||||
|
||||
## Fixed contracts
|
||||
|
||||
### One canonical stream owner
|
||||
|
||||
Add one instance-scoped stream under `AcDream.Runtime.Entities`, owned by
|
||||
`RuntimeEntityObjectLifetime`.
|
||||
|
||||
It exposes a narrow subscription equivalent to:
|
||||
|
||||
```csharp
|
||||
public interface IRuntimeEntityObjectObserver
|
||||
{
|
||||
void OnEntity(in RuntimeEntityDelta delta);
|
||||
void OnInventory(in RuntimeInventoryDelta delta);
|
||||
}
|
||||
|
||||
public interface IRuntimeEntityObjectEventSource
|
||||
{
|
||||
IDisposable Subscribe(IRuntimeEntityObjectObserver observer);
|
||||
}
|
||||
```
|
||||
|
||||
Entity and inventory deltas share one sequence owner. There is no entity queue
|
||||
plus inventory queue and no App repeater. Every stamp carries the exact
|
||||
`RuntimeGenerationToken` and current Runtime frame number.
|
||||
|
||||
Sequence rules:
|
||||
|
||||
- sequence starts at one for each live session generation;
|
||||
- entity and inventory commits consume the same monotonic sequence;
|
||||
- rejected/stale packets consume no sequence;
|
||||
- callback reentrancy appends later sequence values in actual commit order;
|
||||
- session reset publishes its committed withdrawals/removals/clear edge, then
|
||||
the retired generation is sealed;
|
||||
- a replacement generation cannot publish through the retired stream context.
|
||||
|
||||
### Canonical entity identity and snapshot
|
||||
|
||||
Runtime claims a local ID when it creates the canonical active record, before
|
||||
any graphical projection callback. The ID remains stable for that incarnation
|
||||
until canonical teardown; graphical hydration failure must not release a
|
||||
still-active Runtime identity.
|
||||
|
||||
The public entity view is canonical and presentation-free:
|
||||
|
||||
- server GUID;
|
||||
- Runtime local ID;
|
||||
- exact `INSTANCE_TS` incarnation;
|
||||
- full cell;
|
||||
- final physics state;
|
||||
- accepted position/snapshot facts.
|
||||
|
||||
Graphical hydration, loaded/pending visibility, and render-resource state do
|
||||
not become gameplay truth. If retained diagnostic contracts still need
|
||||
attached-host counts, those are supplied by a separate projection
|
||||
acknowledgement/diagnostic seam and are not mixed into canonical entity
|
||||
identity.
|
||||
|
||||
### Commit ordering
|
||||
|
||||
An entity delta is created only after the exact Runtime record mutation
|
||||
commits. The graphical projection host receives that same immutable committed
|
||||
delta synchronously and returns an exact acknowledgement; public observers
|
||||
then observe the same sequence. There is no second conversion step and no
|
||||
frame boundary.
|
||||
|
||||
Required entity edges:
|
||||
|
||||
- `Registered` after active-record/local-ID commit;
|
||||
- `Updated` after same-incarnation CreateObject, ObjDesc, vector, movement,
|
||||
accepted position, parent, or equivalent canonical refresh;
|
||||
- `Rebucketed` after a canonical full-cell change;
|
||||
- `Hidden` after a committed Hidden state transition;
|
||||
- `Withdrawn` after pickup/accepted parent/cellless exit-world while the
|
||||
incarnation remains logically alive;
|
||||
- `Deleted` after authoritative identity retirement and retained-object
|
||||
removal acceptance;
|
||||
- generation replacement orders old `Deleted`/withdrawal before new
|
||||
`Registered`.
|
||||
|
||||
Loaded-to-pending renderer visibility is not a canonical withdrawal and emits
|
||||
no gameplay entity delta.
|
||||
|
||||
Required inventory edges remain the exact object-table callback order:
|
||||
|
||||
- `Added`;
|
||||
- `Updated`;
|
||||
- `Moved`;
|
||||
- `Removed` with exact removed generation;
|
||||
- `Cleared`.
|
||||
|
||||
Unknown-object move notices retain the object ID and placement facts rather
|
||||
than disappearing because no `ClientObject` instance exists.
|
||||
|
||||
### Borrowed views
|
||||
|
||||
The Runtime lifetime group exposes concrete allocation-free
|
||||
`IRuntimeEntityView` and `IRuntimeInventoryView` implementations:
|
||||
|
||||
- entity enumeration walks `RuntimeEntityDirectory.ActiveRecords`;
|
||||
- entity lookup resolves directly through the directory;
|
||||
- inventory enumeration/lookup walks `ClientObjectTable`;
|
||||
- inventory incarnation comes from the exact Runtime directory or the
|
||||
object's retained generation, never an App sidecar;
|
||||
- visitors receive value snapshots and may not retain mutable owners.
|
||||
|
||||
`CurrentGameRuntimeViewAdapter` borrows those view objects. It retains only
|
||||
the still-unmoved J4+ chat/movement/portal/lifecycle composition.
|
||||
|
||||
## Execution
|
||||
|
||||
### 1. Pin the pre-cutover normalized oracle
|
||||
|
||||
Add a focused deterministic fixture covering:
|
||||
|
||||
- create, same-incarnation refresh, and generation replacement;
|
||||
- ObjDesc/property and accepted state/vector/movement/position updates;
|
||||
- parent, pickup, cellless withdrawal, and re-entry;
|
||||
- object add/update/move/remove/clear;
|
||||
- optimistic move, confirm, rejection rollback, and container replacement;
|
||||
- authoritative delete and delete/recreate with the same GUID;
|
||||
- reset and next-generation reuse;
|
||||
- callback reentrancy.
|
||||
|
||||
Record both the canonical final state and exact event order. Preserve existing
|
||||
retail-facing object-table callback order; add missing canonical entity edges
|
||||
without treating projection visibility as gameplay truth.
|
||||
|
||||
### 2. Add the Runtime entity/object stream
|
||||
|
||||
- Add the stream owner and narrow observer/source contracts in Runtime.
|
||||
- Give it one per-generation sequencer and explicit session-generation/frame
|
||||
context.
|
||||
- Use copy-on-write subscriber storage so steady dispatch allocates no observer
|
||||
list and unsubscribe is exact.
|
||||
- Reject duplicate subscriptions, cross-owner subscriptions, stale
|
||||
generations, and dispatch after disposal.
|
||||
- Keep callback execution on the existing update/network-drain thread.
|
||||
- Revalidate generation/incarnation/operation version after any callback that
|
||||
can re-enter Runtime.
|
||||
|
||||
### 3. Move identity allocation to canonical registration
|
||||
|
||||
- Claim the Runtime local ID in the same transaction that installs an active
|
||||
canonical record.
|
||||
- Preserve current allocation range and wrap/exhaustion behavior.
|
||||
- Stop releasing the ID when only App materialization rolls back.
|
||||
- Release it only when the canonical incarnation reaches terminal teardown.
|
||||
- Prove pending/unhydrated and direct-host entities still have exact keys.
|
||||
- Prove GUID reuse gets a new key and stale keys never resolve.
|
||||
|
||||
### 4. Publish canonical entity commits
|
||||
|
||||
- Centralize accepted canonical mutations behind
|
||||
`RuntimeEntityObjectLifetime` transaction methods.
|
||||
- Publish only after state commits and only while the exact operation remains
|
||||
current.
|
||||
- Carry the complete immutable accepted snapshot required by a graphical or
|
||||
direct projection host.
|
||||
- Replace App's projection-visibility-derived entity event reconstruction.
|
||||
- Preserve synchronous graphical projection application and exact retryable
|
||||
teardown acknowledgements.
|
||||
- Do not emit on renderer rebucket, loaded/pending visibility, hydration
|
||||
retries, or other presentation-only changes.
|
||||
|
||||
### 5. Publish exact object-table commits
|
||||
|
||||
- Bind the stream to its owned `ClientObjectTable`.
|
||||
- Convert callbacks to immutable `RuntimeInventoryItemSnapshot` values with
|
||||
generation metadata resolved by Runtime.
|
||||
- Preserve add/update/move/remove/clear order and callback reentrancy.
|
||||
- Keep optimistic movement and rollback synchronous.
|
||||
- Ensure an authoritative delete produces the exact entity/object ordering
|
||||
pinned by J3.4.
|
||||
- Do not add a second retained inventory collection.
|
||||
|
||||
### 6. Replace App entity/object view and event adapters
|
||||
|
||||
- Make `CurrentGameRuntimeViewAdapter.Entities` and `.Inventory` return the
|
||||
Runtime group's borrowed views.
|
||||
- Remove its `LiveEntityRuntime` dependency for canonical entity/inventory
|
||||
reads.
|
||||
- Make `CurrentGameRuntimeEventAdapter` forward the Runtime stream directly.
|
||||
- Remove its `ProjectionVisibilityChanged` and object-table subscriptions.
|
||||
- Retain command, lifecycle, chat, movement, and portal adapter work only until
|
||||
their scheduled J4–J6 owner moves.
|
||||
- Add source guards preventing a new App entity/object mirror, sequencer, or
|
||||
owner-event reconstruction.
|
||||
|
||||
### 7. Direct/graphical parity and teardown
|
||||
|
||||
- Build one no-App Runtime fixture and one graphical-host fixture over the
|
||||
same input script.
|
||||
- Compare normalized generation, sequence, identity, cell/state, properties,
|
||||
placement, and terminal edges exactly.
|
||||
- Verify subscriber removal and Runtime disposal leave zero observers.
|
||||
- Verify reset seals the old generation and a new session begins at sequence
|
||||
one without stale delivery.
|
||||
- Verify graphical callbacks remain same-thread and press-time object reads
|
||||
remain immediate.
|
||||
|
||||
## Adversarial cases
|
||||
|
||||
- Observer adds/removes an observer during dispatch.
|
||||
- Observer creates, deletes, or replaces the same GUID reentrantly.
|
||||
- Observer clears the session during create or object-table callbacks.
|
||||
- Graphical projection callback throws after canonical commit.
|
||||
- Same-incarnation CreateObject arrives while a prior projection teardown is
|
||||
retained.
|
||||
- Authoritative delete races a dormant exact-incarnation record.
|
||||
- Equal, stale, and wrapped timestamps.
|
||||
- Local-ID wrap and namespace exhaustion.
|
||||
- Graphical hydration fails before and after resource registration.
|
||||
- Unknown-object move and removal.
|
||||
- Container replacement mutates contents from inside an event callback.
|
||||
- Multiple Runtime groups use identical GUIDs and session-generation values.
|
||||
- Subscription disposal is replayed and Runtime disposal occurs during a
|
||||
callback.
|
||||
|
||||
## Mandatory gates
|
||||
|
||||
Run in this order:
|
||||
|
||||
1. Runtime stream, borrowed-view, sequence, reentrancy, and identity tests.
|
||||
2. Core `ClientObjectTable*` and `ObjectTableWiring*` tests.
|
||||
3. Focused App Runtime-adapter, hydration, update, rebucket, deletion, reset,
|
||||
inventory, paperdoll, selection, interaction, and cooldown tests.
|
||||
4. Direct-host versus graphical-host normalized parity fixture.
|
||||
5. All Runtime tests.
|
||||
6. All App tests.
|
||||
7. `dotnet build AcDream.slnx -c Release`.
|
||||
8. `dotnet test AcDream.slnx -c Release --no-build`.
|
||||
9. Exact-binary connected lifecycle/reconnect gate.
|
||||
10. At every connected checkpoint: zero render-shadow mismatches, zero pending
|
||||
deltas, graceful process exit, and exact canonical/projection teardown
|
||||
convergence.
|
||||
|
||||
A user visual pause is required only if an automated or connected gate exposes
|
||||
a visible inventory, selection, radar, paperdoll, or world-object symptom.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Runtime owns one per-session ordered entity/object stream.
|
||||
- Entity and inventory deltas share one generation/sequence order.
|
||||
- Runtime views read the directory and object table directly.
|
||||
- Every active canonical entity has a Runtime key before App projection.
|
||||
- App reconstructs neither entity nor inventory events.
|
||||
- Projection visibility is not emitted as canonical gameplay withdrawal.
|
||||
- Graphical and no-window normalized traces are identical.
|
||||
- No copied collection, queue, extra input frame, or backend dependency exists.
|
||||
- Reentrant callbacks cannot resurrect or mutate a superseded incarnation.
|
||||
- Reset/disposal leaves zero observers, queued deltas, and retained stream
|
||||
state.
|
||||
- Complete Release and exact connected gates pass on one committed binary.
|
||||
|
||||
## Rollback
|
||||
|
||||
J3.5 is independently reversible:
|
||||
|
||||
```text
|
||||
git revert ce3ac310d92722ffb637e81cb1957458874dd220
|
||||
```
|
||||
|
||||
Do not revert J3.4 as a shortcut for a J3.5 failure. Automated and connected
|
||||
evidence is recorded in
|
||||
`../research/2026-07-26-slice-j3-5-canonical-delta-stream.md`.
|
||||
|
||||
## Next boundary
|
||||
|
||||
J3.6 executes from
|
||||
[`2026-07-26-modern-runtime-slice-j3-hardening.md`](2026-07-26-modern-runtime-slice-j3-hardening.md).
|
||||
It failure-injects the complete canonical entity/object lifecycle and proves
|
||||
zero records, tombstones, projections, resources, container entries, parent
|
||||
candidates, subscriptions, or pending deltas after reset and disposal. That
|
||||
closeout is required before J4 moves chat, inventory transactions, vitals,
|
||||
enchantments, spells/components, cooldowns, and ViewModel-facing revisions.
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
# Modern runtime J3.6 — adversarial lifetime hardening and closeout
|
||||
|
||||
**Status:** COMPLETE 2026-07-26 at `119b7c115107245546180b2f5cb3cb44c7d5476a`
|
||||
**Parent:** `2026-07-25-modern-runtime-slice-j3.md`
|
||||
**Required production base:** `ce3ac310d92722ffb637e81cb1957458874dd220`
|
||||
**Prior closeout:** `../research/2026-07-26-slice-j3-5-canonical-delta-stream.md`
|
||||
|
||||
## Objective
|
||||
|
||||
Close the canonical entity/object lifetime group under failure, re-entrancy,
|
||||
reset, and disposal before J4 moves another gameplay owner.
|
||||
|
||||
This slice adds no feature, cross-frame/background queue, retry timer,
|
||||
renderer fallback, or gameplay behavior. Its synchronous re-entrant dispatch
|
||||
drain preserves one observer-visible sequence within the committing call. It
|
||||
proves that Runtime's canonical identity/object owner and App's
|
||||
exact-key graphical projection either complete each transaction or retain one
|
||||
explicitly retryable suffix, and that every terminal path converges to zero.
|
||||
|
||||
## Fixed boundary
|
||||
|
||||
- Runtime remains the sole GUID/incarnation/local-ID, accepted-wire,
|
||||
parent-relation, object-table, direct-view, and entity/object-event owner.
|
||||
- App remains an exact-key projection and resource host.
|
||||
- The server remains authoritative.
|
||||
- Existing synchronous retail ordering remains unchanged.
|
||||
- J4 chat, vitals, enchantments, spells/components, cooldowns, and
|
||||
ViewModel-facing revisions do not enter this slice.
|
||||
- A failure test may expose a production ownership defect. Fix the owning
|
||||
transaction at its commit/acknowledgement boundary; do not add a suppression
|
||||
flag, timer, retry loop, or symptom guard.
|
||||
|
||||
## Execution
|
||||
|
||||
### 1. Pin the complete ownership ledger
|
||||
|
||||
Create one assertion helper that reports every J3 owner:
|
||||
|
||||
- active Runtime records;
|
||||
- teardown Runtime records and claimed local IDs;
|
||||
- accepted snapshot/timestamp and parent-relation entries;
|
||||
- active and teardown App projections;
|
||||
- materialized world entities and graphical resource registrations;
|
||||
- spatial loaded/pending projection keys;
|
||||
- animation, remote-motion, projectile, root-object, effect, light, script,
|
||||
and equipped-child bindings;
|
||||
- object-table objects, containers, pending moves, and replacement state;
|
||||
- Runtime stream subscriptions, dispatch failures, and pending work.
|
||||
|
||||
The helper must distinguish an intentionally retained retry receipt from a
|
||||
leak. A stable terminal checkpoint permits neither.
|
||||
|
||||
### 2. Failure-inject canonical registration and hydration
|
||||
|
||||
Exercise failures and re-entry:
|
||||
|
||||
- before and after Runtime active-record/local-ID commit;
|
||||
- same-incarnation refresh;
|
||||
- generation replacement while prior App teardown succeeds or fails;
|
||||
- projection sidecar creation;
|
||||
- world-entity factory;
|
||||
- resource registration before commit and after partial commit;
|
||||
- resource rollback before and after commit;
|
||||
- animation/physics/effect/relationship/ready publication stages;
|
||||
- observer replacement, delete, and session clear during each callback.
|
||||
|
||||
Prove that a still-active Runtime incarnation never loses its key, a failed
|
||||
partial owner remains reachable by one exact teardown receipt, and retry
|
||||
cannot replay create-time resources already committed.
|
||||
|
||||
### 3. Failure-inject accepted updates and placement
|
||||
|
||||
Cover ObjDesc, motion, vector, state, position, parent, pickup, explicit
|
||||
rebucket, withdrawal, and child-NoDraw:
|
||||
|
||||
- stale/equal/new/wrapped timestamps;
|
||||
- loaded-to-loaded, loaded-to-pending, pending-to-loaded, and cellless
|
||||
transitions;
|
||||
- projection callback failure before and after the graphical mutation;
|
||||
- observer delete/recreate or same-record newer update;
|
||||
- parent event before the parent, before the child, and across GUID reuse;
|
||||
- malformed or missing-object updates.
|
||||
|
||||
Assert the shared stream publishes only committed canonical facts, consumes
|
||||
no sequence for rejected packets, never emits renderer visibility as a
|
||||
withdrawal, and never applies a displaced callback to a replacement
|
||||
incarnation.
|
||||
|
||||
### 4. Failure-inject object-table transactions
|
||||
|
||||
Run the exact object-table behavior through the Runtime stream:
|
||||
|
||||
- create/add versus update;
|
||||
- optimistic container move;
|
||||
- confirmation;
|
||||
- rejection rollback;
|
||||
- wield/container replacement;
|
||||
- stack/value/property refresh;
|
||||
- unknown-object move;
|
||||
- authoritative entity delete;
|
||||
- dormant/offscreen delete;
|
||||
- clear and next-generation reuse;
|
||||
- callback mutation of the same object or containing container.
|
||||
|
||||
Verify callback order, exact removed generation, immediate borrowed-view
|
||||
visibility, and no second retained inventory collection.
|
||||
|
||||
### 5. Prove reset, retry, and disposal convergence
|
||||
|
||||
Inject a failure at every App teardown stage, then repeatedly invoke the
|
||||
existing retryable teardown entry until it completes. Exercise:
|
||||
|
||||
- ordinary authoritative delete;
|
||||
- delete/recreate with the same GUID;
|
||||
- session reset during create, update, object callback, and teardown callback;
|
||||
- logout;
|
||||
- transport replacement;
|
||||
- mid-portal disconnect;
|
||||
- Runtime and App disposal;
|
||||
- repeated subscription disposal and disposal from inside dispatch.
|
||||
|
||||
At convergence assert the complete ownership ledger is zero, the retired
|
||||
generation cannot publish, the next generation starts at sequence one, and
|
||||
later object-table mutations cannot reach disposed observers.
|
||||
|
||||
### 6. Runtime-only end-to-end fixture
|
||||
|
||||
Without loading App, UI, Silk.NET, OpenAL, Arch, or ImGui:
|
||||
|
||||
1. construct Runtime;
|
||||
2. bind a generation and frame clock;
|
||||
3. register several conflicting entity/object shapes;
|
||||
4. apply properties, motion/vector/state/position, parent/container moves,
|
||||
Hidden, pickup, delete/recreate, and clear;
|
||||
5. visit direct views after each commit;
|
||||
6. record the exact shared delta stream;
|
||||
7. reset and reconnect;
|
||||
8. dispose.
|
||||
|
||||
Run the same normalized input script through the graphical harness and require
|
||||
exact equality. Assert the Runtime assembly load closure remains backend-free.
|
||||
|
||||
### 7. Closeout gates
|
||||
|
||||
Run, in order:
|
||||
|
||||
1. focused Runtime lifetime/stream failure matrix;
|
||||
2. Core `ClientObjectTable*` and `ObjectTableWiring*`;
|
||||
3. focused App hydration, projection, update, parent, rebucket, delete,
|
||||
session-reset, selection, inventory, and paperdoll suites;
|
||||
4. Runtime-only versus graphical normalized parity;
|
||||
5. all Runtime tests;
|
||||
6. all App tests;
|
||||
7. `dotnet build AcDream.slnx -c Release`;
|
||||
8. `dotnet test AcDream.slnx -c Release --no-build`;
|
||||
9. exact-binary connected lifecycle/reconnect;
|
||||
10. canonical nine-stop connected world route;
|
||||
11. documentation, divergence, dependency, and source-ownership audit.
|
||||
|
||||
A user visual pause is required only if automated or connected evidence shows
|
||||
a visible inventory, selection, radar, paperdoll, portal, or world-object
|
||||
symptom.
|
||||
|
||||
## Acceptance
|
||||
|
||||
J3 is complete only when:
|
||||
|
||||
- every canonical mutation and terminal path has adversarial coverage;
|
||||
- direct and graphical traces remain exact;
|
||||
- rejected/stale work consumes no event sequence;
|
||||
- no callback can mutate a superseded incarnation;
|
||||
- every partial acquisition remains reachable and retryable;
|
||||
- every stable reset/disposal checkpoint has zero records, tombstones, keys,
|
||||
projections, resources, container entries, parent candidates,
|
||||
subscriptions, and pending deltas;
|
||||
- Runtime-only construction and lifecycle load no presentation/backend
|
||||
assembly;
|
||||
- full Release and both exact-binary connected routes pass;
|
||||
- architecture, roadmap, milestones, AGENTS/CLAUDE, durable memory, and exact
|
||||
rollback are synchronized.
|
||||
|
||||
## Rollback
|
||||
|
||||
J3.6 is independently reversible:
|
||||
|
||||
```text
|
||||
git revert 119b7c115107245546180b2f5cb3cb44c7d5476a
|
||||
```
|
||||
|
||||
J3.5 remains independently reversible with
|
||||
`git revert ce3ac310d92722ffb637e81cb1957458874dd220`. Never revert J3.4
|
||||
or J3.3 to mask a later failure.
|
||||
|
||||
## Closeout evidence
|
||||
|
||||
The complete failure matrix, zero-owner ledger, 8,484-test Release result,
|
||||
exact-binary lifecycle/reconnect gate, canonical nine-stop route, divergence
|
||||
audit, and rollback are recorded in
|
||||
`../research/2026-07-26-slice-j3-6-lifetime-closeout.md`.
|
||||
|
||||
## Next boundary
|
||||
|
||||
J3 is closed. Execute J4 from
|
||||
`docs/plans/2026-07-25-modern-runtime-slice-j.md`: move the remaining
|
||||
presentation-independent gameplay-state owners in coherent lifetime groups,
|
||||
with direct/graphical parity and zero-mirror deletion after every group.
|
||||
|
|
@ -1,252 +0,0 @@
|
|||
# Modern runtime Slice J4 — gameplay-state lifetime groups
|
||||
|
||||
**Status:** COMPLETE
|
||||
**Parent:** `2026-07-25-modern-runtime-slice-j.md`, J4
|
||||
**Production base:** `77c013998b902724d66e487589a4ea18538e1ea6`
|
||||
**Authorization:** the user approved Slices F–L, including Slice J gameplay
|
||||
owner moves, on 2026-07-24
|
||||
|
||||
## Objective
|
||||
|
||||
Move the remaining presentation-independent player/gameplay state into
|
||||
`AcDream.Runtime` without creating a second model, changing retail behavior,
|
||||
or making the graphical host authoritative.
|
||||
|
||||
Runtime must construct and own each mutable Core state object. App, retained
|
||||
UI, devtools, plugins, and the live-session router borrow those exact
|
||||
instances. Every moved group retains current synchronous packet/command
|
||||
ordering and its existing retail-derived algorithms.
|
||||
|
||||
## Execution ledger
|
||||
|
||||
| Group | Status | Evidence |
|
||||
|---|---|---|
|
||||
| J4.1 communication/social | complete | `c9d25ade50c0a5c4f7db5b6cca680e01e35cc18e`; 152 Runtime tests, 3,765 App tests / 3 skips, 8,494 complete Release tests / 5 skips, and `logs/connected-world-gate-20260726-075109/report.json` pass |
|
||||
| J4.2 inventory transactions | complete | `011efbeaa72509b35ea7f4a442e50a2377ae1ea4`; 157 Runtime tests, 3,770 App tests / 3 skips, 8,515 complete Release tests / 5 skips, and `logs/connected-world-gate-20260726-082057/report.json` pass |
|
||||
| J4.3 magic/player sheet | complete | `d02a12ceac54d035797b4c58f2fc2f0ccad9a4d6`; 162 Runtime tests, 3,772 App tests / 3 skips, 8,522 complete Release tests / 5 skips, and `logs/connected-world-gate-20260726-083921/report.json` pass |
|
||||
| J4.4 character projections | complete | `dcb61efb5af6c12bd619369e28cf11bacc37bc73`; 169 Runtime tests, 3,777 App tests / 3 skips, 8,534 complete Release tests / 5 skips, and `logs/connected-world-gate-20260726-091340/report.json` pass |
|
||||
| J4.5 combined cleanup | complete | `89e6b207f81946b40c38c8dc6597817ad99fc6e0`; 175 Runtime tests, 3,780 App tests / 3 skips, 8,544 complete Release tests / 5 skips, exact-binary lifecycle/reconnect and nine-stop gates |
|
||||
|
||||
## Current ownership inventory
|
||||
|
||||
The App `GameWindow` currently constructs or roots these
|
||||
presentation-independent owners:
|
||||
|
||||
| Group | Current canonical objects | Current App roots |
|
||||
|---|---|---|
|
||||
| Communication/social | `ChatLog`, `TurbineChatState`, `FriendsState`, `SquelchState`; reply/retell targets currently live in `ChatVM` | public `GameWindow` fields plus `ChatVM` subscription |
|
||||
| Inventory/session transactions | J3's Runtime-owned `ClientObjectTable`; `ExternalContainerState`, `ItemManaState`, shortcut snapshot, desired-component snapshot | `GameWindow`, App snapshot wrappers, and `LiveSessionRuntimeFactory` |
|
||||
| Magic/effects/player sheet | coupled `Spellbook` + `LocalPlayerState`; learned spells, favorites, filters, desired components, active enchantments, cooldown bucket, attributes, skills, vitals, properties, positions | `GameWindow` fields; App `MagicRuntime` and retained controllers borrow them |
|
||||
| Character settings/skill motion projection | character options and run/jump controller inputs | App input/composition state |
|
||||
| Presentation-only state | formatted/layout chat rows, retained panels, text/icons, selected rows, scroll offsets, paperdoll/viewport, effects rendering, cooldown overlays | App/UI; these do **not** move |
|
||||
|
||||
`RuntimeEntityObjectLifetime` already owns the inventory object collection and
|
||||
ordered object deltas. J4 must reuse that owner. It may not introduce a second
|
||||
inventory list or replay object-table events into another collection.
|
||||
|
||||
Selection, approach/use input, movement, combat intent, target state,
|
||||
projectiles, and outbound motion/cast cadence remain J5. World reveal,
|
||||
environment, and portal presentation remain J6.
|
||||
|
||||
## Fixed invariants
|
||||
|
||||
1. One mutable owner instance per state class; adapters and panels borrow.
|
||||
2. Runtime has no App, UI, Silk, OpenAL, Arch, ImGui, or GL dependency.
|
||||
3. No asynchronous App seam, extra frame, polling copy, or reconstructed
|
||||
ViewModel collection.
|
||||
4. Existing `WorldSession`/`GameEventWiring` registration order and
|
||||
generation acceptance gates remain unchanged.
|
||||
5. Session reset preserves the current semantic order. Visible chat history
|
||||
survives character teardown while reply targets, local identity, negotiated
|
||||
rooms, friends, and squelch state reset.
|
||||
6. Immutable DAT-derived catalog data may be installed once. Mutable learned
|
||||
spells, enchantments, filters, favorites, desired components, vitals,
|
||||
skills, and cooldown state are session-owned.
|
||||
7. Every group has a Runtime-only construction/reset/disposal test that loads
|
||||
no presentation/backend assembly.
|
||||
8. The replaced App ownership path is deleted in the same sub-slice.
|
||||
|
||||
## Execution
|
||||
|
||||
### J4.1 — communication and social state
|
||||
|
||||
**Completed 2026-07-26 at `c9d25ade`.**
|
||||
|
||||
- Add a Runtime owner for `ChatLog`, `TurbineChatState`, `FriendsState`,
|
||||
`SquelchState`, and reply/retell command targets.
|
||||
- Move reply/retell target tracking out of `ChatVM`; the VM borrows the same
|
||||
presentation-independent target state.
|
||||
- Make `LiveSessionRuntimeFactory`, command routing, current-runtime views,
|
||||
retained UI, devtools, and plugins borrow the Runtime owner.
|
||||
- Replace `GameWindow` object fields with read-only aliases to that owner.
|
||||
- Preserve transcript lifetime and exact per-session reset semantics.
|
||||
|
||||
Gate: incoming/outgoing tell tracking, room negotiation/cookie wrap, friend and
|
||||
squelch updates, command routing, clear, reset/reconnect, graphical/direct
|
||||
event traces, disposal, and Runtime dependency closure.
|
||||
|
||||
Result: `RuntimeCommunicationState` constructs and owns the exact `ChatLog`,
|
||||
reply/retell targets, Turbine rooms, friends, and squelch database. Session
|
||||
routing, both UI stacks, plugins/current-runtime views, and command routing
|
||||
borrow those instances. The canonical chat stream is synchronous,
|
||||
failure-isolated, and preserves one monotonic sequence under re-entrant
|
||||
callbacks without a frame/background queue. Exact-binary lifecycle/reconnect
|
||||
evidence is recorded in
|
||||
[`../research/2026-07-26-slice-j4-1-communication-state.md`](../research/2026-07-26-slice-j4-1-communication-state.md).
|
||||
|
||||
### J4.2 — inventory transaction state
|
||||
|
||||
**Completed 2026-07-26 at `011efbea`.**
|
||||
|
||||
- Move `ExternalContainerState`, `ItemManaState`, shortcuts, and desired
|
||||
component snapshots into one Runtime owner.
|
||||
- Keep the J3 object table as the only item collection and borrow it directly.
|
||||
- Split presentation/input-only portions from item-use reservations only where
|
||||
the existing class mixes them; do not move retained drag/drop or panel state.
|
||||
- Route inbound object/use/mana/shortcut/component updates and bot/plugin views
|
||||
through the same owner.
|
||||
|
||||
Gate: optimistic move/rollback, external-container open/replace/close,
|
||||
use-completion, mana query, shortcut/component replacement, clear/reconnect,
|
||||
and zero duplicate collection.
|
||||
|
||||
Result: `RuntimeInventoryState` owns external-container, item-mana,
|
||||
shortcut/component, shared-busy, use-reservation, and one-request-at-a-time
|
||||
state while borrowing J3's exact object table. App retains only
|
||||
presentation/input projections. Failure-safe reset and callback delivery
|
||||
prevent a failed observer or dispatch from stranding the canonical busy gate.
|
||||
Exact evidence is recorded in
|
||||
[`../research/2026-07-26-slice-j4-2-inventory-state.md`](../research/2026-07-26-slice-j4-2-inventory-state.md).
|
||||
|
||||
### J4.3 — spell, enchantment, vital, attribute, and skill state
|
||||
|
||||
**Completed 2026-07-26 at `d02a12ce`.**
|
||||
|
||||
- Move construction/lifetime of the coupled `Spellbook` and
|
||||
`LocalPlayerState` into Runtime.
|
||||
- Preserve one-time immutable `SpellTable` installation from App content.
|
||||
- Keep learned spells, favorites, filters, desired components,
|
||||
enchantments/cooldowns, vitals, attributes, skills, properties, and positions
|
||||
on those exact owners.
|
||||
- Retained magic/effects/character panels and App `MagicCatalog` borrow; no
|
||||
DAT/UI type enters Runtime.
|
||||
|
||||
Gate: complete PlayerDescription, incremental updates, spell/enchantment
|
||||
add/remove, favorite/filter/component changes, cooldowns, vital modifiers,
|
||||
clear/reconnect, ViewModel revisions, and graphical/direct trace parity.
|
||||
|
||||
Result: `RuntimeCharacterState` constructs and owns the exact coupled
|
||||
`Spellbook` and `LocalPlayerState`. Content installs immutable DAT metadata
|
||||
through that owner; routing, retained UI, reset, and shutdown borrow it. The
|
||||
interim desired-component mirror was deleted, leaving the spellbook as the
|
||||
only mutable component-count owner. Exact evidence is recorded in
|
||||
[`../research/2026-07-26-slice-j4-3-character-state.md`](../research/2026-07-26-slice-j4-3-character-state.md).
|
||||
|
||||
### J4.4 — character settings and player-state projections
|
||||
|
||||
**Completed 2026-07-26 at `dcb61efb`.**
|
||||
|
||||
- Move remaining presentation-independent character options and skill-derived
|
||||
state needed by commands/bots.
|
||||
- Keep controller/body/camera/input application in App/J5.
|
||||
- Expose borrowed immutable views and synchronous typed commands for all J4
|
||||
groups.
|
||||
|
||||
Gate: character replacement, settings isolation, two Runtime instances in one
|
||||
process, no static mutable session state, and no backend load.
|
||||
|
||||
Result: the Runtime character graph owns both option bitfields and the
|
||||
server-authoritative run/jump projection; the two former App owners are
|
||||
deleted. Exact borrowed inventory/character/social views and synchronous
|
||||
generation-gated state commands are shared by graphical UI and future
|
||||
no-window hosts. Normalized checkpoints now cover the J4 state. Exact evidence
|
||||
is recorded in
|
||||
[`../research/2026-07-26-slice-j4-4-character-projections.md`](../research/2026-07-26-slice-j4-4-character-projections.md).
|
||||
|
||||
### J4.5 — combined cleanup and closeout
|
||||
|
||||
**Completed 2026-07-26 at `89e6b207`.**
|
||||
|
||||
- Delete temporary App owner fields, reset branches, reconstructed runtime
|
||||
views/events, and compatibility adapters replaced by J4.
|
||||
- Prove graphical UI and no-window hosts observe the same owner instances,
|
||||
revisions, command effects, and ordered deltas.
|
||||
- Failure-inject construction, subscription, callback, reset, and disposal for
|
||||
every group; require complete owner-ledger convergence.
|
||||
|
||||
Run:
|
||||
|
||||
1. focused Core and Runtime state tests;
|
||||
2. UI ViewModel/controller tests;
|
||||
3. App composition/session/reset/runtime-adapter tests;
|
||||
4. Runtime dependency/source/assembly-load guards;
|
||||
5. full Runtime and App suites;
|
||||
6. Release build and complete Release suite;
|
||||
7. exact-binary lifecycle/reconnect gate;
|
||||
8. canonical nine-stop route;
|
||||
9. documentation, divergence, dependency, and source-ownership audit.
|
||||
|
||||
Result: the Runtime inventory graph now owns the one retail 18-slot shortcut
|
||||
manager, retained toolbar/spell controllers apply command effects through the
|
||||
canonical Runtime owner, and `ItemInteractionController` can no longer create a
|
||||
second inventory transaction/busy owner. Exact retail local/send ordering is
|
||||
pinned for shortcuts, favorites, filters, and desired components. Graphical
|
||||
and direct no-window command effects match, while a combined failure-safe
|
||||
ledger proves every J4 owner and subscription converges after reset or terminal
|
||||
disposal. Exact evidence is recorded in
|
||||
[`../research/2026-07-26-slice-j4-5-gameplay-state-closeout.md`](../research/2026-07-26-slice-j4-5-gameplay-state-closeout.md).
|
||||
|
||||
## Acceptance
|
||||
|
||||
J4 is complete only when:
|
||||
|
||||
- Runtime constructs and owns every listed presentation-independent object;
|
||||
- App/UI/plugin/bot consumers borrow exact instances;
|
||||
- no App mirror, reconstructed collection, or command target remains;
|
||||
- reset, reconnect, direct disposal, and two-instance isolation pass;
|
||||
- graphical and Runtime-only normalized traces match;
|
||||
- Runtime's assembly closure remains backend-free;
|
||||
- full Release and both exact-binary connected routes pass;
|
||||
- architecture, roadmap, milestones, AGENTS/CLAUDE, durable memory, evidence,
|
||||
and exact rollback are synchronized.
|
||||
|
||||
## Rollback
|
||||
|
||||
Record each J4 production commit before its connected gate. Revert J4 groups in
|
||||
reverse order only. J3.6 remains independently reversible with:
|
||||
|
||||
```text
|
||||
git revert 119b7c115107245546180b2f5cb3cb44c7d5476a
|
||||
```
|
||||
|
||||
Do not revert J3 to mask a J4 owner/reset failure.
|
||||
|
||||
J4.1 is independently reversible with:
|
||||
|
||||
```text
|
||||
git revert c9d25ade50c0a5c4f7db5b6cca680e01e35cc18e
|
||||
```
|
||||
|
||||
J4.2 is independently reversible with:
|
||||
|
||||
```text
|
||||
git revert 011efbeaa72509b35ea7f4a442e50a2377ae1ea4
|
||||
```
|
||||
|
||||
J4.3 is independently reversible with:
|
||||
|
||||
```text
|
||||
git revert d02a12ceac54d035797b4c58f2fc2f0ccad9a4d6
|
||||
```
|
||||
|
||||
J4.4 is independently reversible with:
|
||||
|
||||
```text
|
||||
git revert dcb61efb5af6c12bd619369e28cf11bacc37bc73
|
||||
```
|
||||
|
||||
J4.5 is independently reversible with:
|
||||
|
||||
```text
|
||||
git revert 89e6b207f81946b40c38c8dc6597817ad99fc6e0
|
||||
```
|
||||
|
|
@ -1,534 +0,0 @@
|
|||
# Modern runtime Slice J5 — movement, physics, interaction, and combat
|
||||
|
||||
**Status:** COMPLETE — J5.1–J5.7 closed 2026-07-26
|
||||
**Parent:** `2026-07-25-modern-runtime-slice-j.md`, J5
|
||||
**Production base:** `2c67a2c3`
|
||||
**Authorization:** the user approved Slices F–L, including the Slice J
|
||||
gameplay-owner gates, on 2026-07-24
|
||||
|
||||
## Objective
|
||||
|
||||
Move the complete presentation-independent action and simulation graph into
|
||||
`AcDream.Runtime` while preserving retail behavior and the accepted graphical
|
||||
feel.
|
||||
|
||||
At completion:
|
||||
|
||||
- Runtime owns selection, interaction mode/transactions, combat and spell-cast
|
||||
intent, local/remote movement, outbound cadence, per-session physics, and
|
||||
projectile simulation;
|
||||
- App converts physical input into typed commands and projects committed state
|
||||
into cameras, retained UI, animation, sound, particles, markers, and meshes;
|
||||
- graphical and future no-window hosts borrow the same owners and observe the
|
||||
same ordered state;
|
||||
- no render callback, UI element, or camera object is gameplay authority.
|
||||
|
||||
## Presentation boundary
|
||||
|
||||
### Moves to Runtime
|
||||
|
||||
- selected/previous object identity;
|
||||
- temporary use/examine/use-item-on-target mode;
|
||||
- appraisal/use/pickup/approach transaction identity;
|
||||
- combat mode, attack build/request/repeat intent, target identity;
|
||||
- spell-cast request intent;
|
||||
- `PlayerMovementController` state, movement interpretation, canonical local
|
||||
body, object clock, movement managers, and outbound cadence;
|
||||
- per-session `PhysicsEngine`, transition scratch, shadow/collision gameplay
|
||||
state;
|
||||
- remote canonical body/movement state;
|
||||
- projectile component state and simulation;
|
||||
- typed views, commands, ordered events, reset, disposal, and ownership
|
||||
ledgers for all of the above.
|
||||
|
||||
### Remains in App
|
||||
|
||||
- Silk input sources and configurable key/chord mapping;
|
||||
- camera orbit, mouse cursor capture, and device-delta filtering;
|
||||
- world ray construction/picking;
|
||||
- selection lighting pulse, vivid markers, and selected-object HUD;
|
||||
- combat/spell bars and retained target-mode cursor images;
|
||||
- animated part/root pose composition;
|
||||
- meshes, terrain/EnvCell streaming publication, sounds, particles, lights,
|
||||
translucency, and debug drawing;
|
||||
- UI text, toasts, drag/drop visuals, pending-slot mesh, and appraisals'
|
||||
retained window.
|
||||
|
||||
The camera's instant mouse-look mode still emits typed retail turn/sidestep
|
||||
commands to Runtime. The camera may not write the player heading directly.
|
||||
|
||||
## Fixed invariants
|
||||
|
||||
1. Retail algorithms and branch/order semantics do not change during the move.
|
||||
2. One mutable owner exists for every state class.
|
||||
3. Runtime has no App/UI/backend dependency.
|
||||
4. `RuntimeEntityRecord` remains canonical entity identity and physics
|
||||
authority; App `LiveEntityRecord` remains a projection sidecar.
|
||||
5. `RuntimeInventoryState` remains the only busy/inventory-transaction owner.
|
||||
6. Core flat collision data is immutable/shareable. Each runtime/session owns
|
||||
its `PhysicsEngine`, transition scratch, bodies, shadows, interpolation, and
|
||||
projectile state.
|
||||
7. All mutation stays on the existing update thread. Network workers enqueue
|
||||
immutable messages only.
|
||||
8. App input-to-Runtime commands are synchronous; no extra frame, polling
|
||||
mirror, or background queue is introduced.
|
||||
9. App presentation callbacks may re-enter/delete/replace entities. Every
|
||||
continuation revalidates exact record, incarnation, authority version, and
|
||||
object-clock epoch before commit.
|
||||
10. Reset/disposal executes every suffix after an observer failure and refuses
|
||||
a new session until the complete ownership ledger converges.
|
||||
11. Every sub-slice is independently buildable, tested, connected-gated where
|
||||
behavior can change, documented, committed, and given an exact rollback.
|
||||
|
||||
## Execution ledger
|
||||
|
||||
| Sub-slice | Status | Result/evidence |
|
||||
|---|---|---|
|
||||
| J5.0 owner/retail inventory | complete | `docs/research/2026-07-26-slice-j5-owner-and-retail-order-inventory.md` |
|
||||
| J5.1 canonical action state | complete | `b298f99f913249d299796e6a71f7a9b6b283ac1a`; one Runtime selection/combat/target-mode owner; 182 Runtime tests, 3,779 App tests / 3 skips, 8,550 complete Release tests / 5 skips, exact-binary connected lifecycle/reconnect pass |
|
||||
| J5.2 interaction transactions | complete | `f5f7b4177f449ebcad9724bfea263ef73ab60255`; one Runtime transaction owner; 197 Runtime tests, 3,775 App tests / 3 skips, 8,561 complete Release tests / 5 skips, exact-binary connected lifecycle/reconnect pass |
|
||||
| J5.3 combat and magic intent | complete | `20df9d155db50706a42420d60a9b15861cf4bfe7`; exact Runtime attack/mode/target/cast owners; 223 Runtime tests, 3,754 App tests / 3 skips, 8,566 complete Release tests / 5 skips, exact-binary connected lifecycle/reconnect pass |
|
||||
| J5.4 local movement and outbound cadence | complete | `aa3f4a60f87f2cbebc3d8ecd5e33d779b7fb13c1`; one Runtime movement/controller/cadence owner; 303 Runtime tests, 3,716 App tests / 3 skips, 8,575 complete Release tests / 5 skips, exact-binary lifecycle/reconnect and nine-stop movement gates pass |
|
||||
| J5.5 per-session physics and remote simulation | complete | `7e6033d0adcd0b572c20e89dd275746fb442d52f`; one Runtime engine/cache/scratch/shadow/body/host/remote/workset owner; 314 Runtime tests, 3,718 App tests / 3 skips, 8,588 complete Release tests / 5 skips, exact-binary lifecycle/reconnect and nine-stop collision/movement gates pass |
|
||||
| J5.6 projectile runtime | complete | `2aee33569f0268d7bc0f4f52437dfa0b405432a4`; one Runtime projectile component/workset/simulation/correction owner; 317 Runtime tests, 3,718 App tests / 3 skips, 47 focused Core projectile tests, 8,591 complete Release tests / 5 skips, exact arrow/bolt/spell evidence, lifecycle/reconnect pass, and nine-stop route pass |
|
||||
| J5.7 combined cleanup and closeout | complete | `cdee7a4b49addb5e1500753f6a885f7c899bd0f0`; one combined simulation ledger, Runtime-only fixture parity, exact terminal teardown, 323 Runtime tests, 3,717 App tests / 3 skips, 8,596 complete Release tests / 5 skips, exact-binary lifecycle/reconnect and nine-stop gates |
|
||||
|
||||
## J5.1 — canonical action state
|
||||
|
||||
Create one Runtime owner for:
|
||||
|
||||
- `SelectionState`;
|
||||
- `CombatState`;
|
||||
- presentation-free interaction/target-mode state.
|
||||
|
||||
Actions:
|
||||
|
||||
1. Move interaction-mode types/state from App UI into Runtime gameplay.
|
||||
2. Make Runtime construct all three exact owners.
|
||||
3. Remove Program's selection construction and GameWindow's direct combat
|
||||
construction.
|
||||
4. Make App composition, session routing, retained UI, plugins, and current
|
||||
Runtime commands/views borrow the exact Runtime instances.
|
||||
5. Remove `ItemInteractionController`'s optional private
|
||||
`InteractionState` fallback; production and tests must pass the exact owner.
|
||||
6. Add a combined ownership/revision snapshot and failure-safe reset/disposal.
|
||||
7. Add Runtime views for selection, combat mode/known health, and interaction
|
||||
mode without exposing mutable collections.
|
||||
|
||||
Gate:
|
||||
|
||||
- named-retail selection order and deduplication;
|
||||
- combat Begin/reset defaults;
|
||||
- target-mode enter/acquire/cancel/reset;
|
||||
- UI/plugin observer failure isolation;
|
||||
- two Runtime instances in one process;
|
||||
- exact graphical/direct owner identity;
|
||||
- source guard proving App cannot construct a production owner;
|
||||
- Runtime dependency and assembly-load closure;
|
||||
- focused Runtime/App tests, Release build, full Release suite, connected
|
||||
lifecycle/reconnect route.
|
||||
|
||||
Completed 2026-07-26 at
|
||||
`b298f99f913249d299796e6a71f7a9b6b283ac1a`. `RuntimeActionState` now
|
||||
constructs the exact selection, combat, and temporary interaction-mode
|
||||
children. Program, plugins, retained UI, session routing, typed commands/views,
|
||||
and terminal shutdown borrow those exact instances. The App-owned
|
||||
`InteractionState` and `ItemInteractionController` fallback are deleted.
|
||||
Failure-safe reset/disposal, two-instance isolation, normalized checkpoints,
|
||||
dependency closure, and production source guards pass. The exact-binary
|
||||
connected report is
|
||||
`logs/connected-world-gate-20260726-104445/report.json`: 7/7 ready
|
||||
checkpoints, two graceful code-zero exits, zero failures, and only the 25
|
||||
expected world-edge landblock misses. Evidence:
|
||||
[`../research/2026-07-26-slice-j5-1-canonical-action-state.md`](../research/2026-07-26-slice-j5-1-canonical-action-state.md).
|
||||
|
||||
## J5.2 — interaction transactions
|
||||
|
||||
Split App orchestration into:
|
||||
|
||||
- App `WorldSelectionQuery`/picker/lighting/drag presentation;
|
||||
- Runtime interaction transaction owner;
|
||||
- typed query, movement, inventory-policy, transport, and presentation
|
||||
acknowledgement seams.
|
||||
|
||||
Runtime owns:
|
||||
|
||||
- use throttle and source/target identity;
|
||||
- appraisal request/current identity;
|
||||
- ordinary use reservation transfer to authoritative UseDone;
|
||||
- pickup request and exact post-arrival identity/token;
|
||||
- ordered outbound interaction queue;
|
||||
- entity-hidden/deleted cancellation;
|
||||
- reset/disposal convergence.
|
||||
|
||||
App retains:
|
||||
|
||||
- cursor position and world ray;
|
||||
- lighting pulse;
|
||||
- toasts/text rendering;
|
||||
- retained drag/drop and pending-slot drawing.
|
||||
|
||||
Ordering:
|
||||
|
||||
- ordinary Use sends immediately, then transfers its busy reservation to
|
||||
UseDone;
|
||||
- targeted Use enters mode until a compatible target is acquired;
|
||||
- pickup's same-frame send drains after local movement output and before
|
||||
inbound dispatch;
|
||||
- post-arrival pickup sends once only on natural completion;
|
||||
- stale/replaced object identities never execute queued work.
|
||||
|
||||
Gate:
|
||||
|
||||
- current connected near/far door/NPC/corpse use;
|
||||
- item-on-target, appraisal, pickup, pending-slot accept/failure;
|
||||
- same-frame W-release plus R;
|
||||
- delete/GUID reuse, hidden target, cancellation, transport failure, callback
|
||||
reentrancy, reset/disposal;
|
||||
- normalized graphical/direct ordered traces and no duplicate busy owner.
|
||||
|
||||
Completed 2026-07-26 at
|
||||
`f5f7b4177f449ebcad9724bfea263ef73ab60255`.
|
||||
`RuntimeInteractionTransactionState`, owned beneath `RuntimeActionState`, now
|
||||
owns the retail 200-ms use gate, exact use source/target identity, pending and
|
||||
current appraisal identity, typed ordered interactions, and the exact
|
||||
post-arrival pickup token. It borrows—not duplicates—J4's
|
||||
`InventoryTransactionState`, so ordinary Use transfers one busy reference to
|
||||
authoritative UseDone and pickup retains the same one-request-at-a-time gate.
|
||||
App retains world rays, lighting, movement installation, transport, toasts,
|
||||
drag/drop, and pending-slot presentation. The old App delegate queue and App
|
||||
transaction fields are deleted.
|
||||
|
||||
Identity-bound work is cancelled by exact local incarnation on delete and by
|
||||
server GUID on Hidden. Re-entrant reset, disposal, transport failure, stale
|
||||
approach completion, and GUID reuse cannot resurrect a transaction or strand
|
||||
the busy gate. Focused near/far use, carried-use, appraisal, item-on-target,
|
||||
pickup, pending-slot, hidden/delete, reset, and callback-order tests pass.
|
||||
The exact-binary connected report is
|
||||
`logs/connected-world-gate-20260726-111330/report.json`: 7/7 ready
|
||||
checkpoints, two graceful code-zero exits, zero failures or invariant
|
||||
violations, and only the 25 expected world-edge landblock misses. Evidence:
|
||||
[`../research/2026-07-26-slice-j5-2-interaction-transactions.md`](../research/2026-07-26-slice-j5-2-interaction-transactions.md).
|
||||
|
||||
## J5.3 — combat and magic intent
|
||||
|
||||
Move:
|
||||
|
||||
- `CombatAttackController`'s presentation-free build/request/repeat state;
|
||||
- combat-mode selection/default policy;
|
||||
- target death/auto-target identity transitions;
|
||||
- `SpellCastingController` intent and last-request state.
|
||||
|
||||
Replace `InputAction` dependencies with Runtime-specific typed commands.
|
||||
App maps configured actions and bar/button presses onto those commands and
|
||||
projects Runtime snapshots.
|
||||
|
||||
Keep App-only:
|
||||
|
||||
- combat/spell bar widgets;
|
||||
- camera target tracking;
|
||||
- animation, sound, particles, and response text layout;
|
||||
- DAT catalog/icon/formula presentation.
|
||||
|
||||
Ordering:
|
||||
|
||||
- movement aborts repeat attack at the existing retail input boundary;
|
||||
- targeted attack/cast turns through the shared movement owner before
|
||||
release;
|
||||
- cast stops movement, emits exactly one targeted/untargeted request, then
|
||||
increments the shared busy reference;
|
||||
- server attack/cast completion remains authoritative.
|
||||
|
||||
Gate:
|
||||
|
||||
- power-bar cadence and height selection;
|
||||
- repeat start/abort/movement cancellation;
|
||||
- combat-mode equipment defaults;
|
||||
- 180-degree bow/spell target facing;
|
||||
- hostile-only auto-target after death;
|
||||
- self/untargeted/targeted spell requests, component failure, busy completion;
|
||||
- graphical bar parity and direct typed-command parity.
|
||||
|
||||
**Completed 2026-07-26 at
|
||||
`20df9d155db50706a42420d60a9b15861cf4bfe7`.** `RuntimeActionState`
|
||||
constructs and owns the exact combat-attack, combat-target, combat-mode, and
|
||||
spell-cast intent children. Retained attack/spell bars and graphical input
|
||||
borrow those owners; direct Runtime combat/magic commands drive the same
|
||||
operations and snapshots without a retained UI. App retains world/content
|
||||
queries, transport, bars, animation, sound, particles, and response text.
|
||||
Runtime tests pass 223/223, App tests pass 3,754/3 skips, and the complete
|
||||
Release suite passes 8,566/5 skips. The exact-binary connected report
|
||||
`logs/connected-world-gate-20260726-115752/report.json` passes two sessions,
|
||||
7/7 checkpoints, graceful code-zero exits, and zero failures. Evidence:
|
||||
[`../research/2026-07-26-slice-j5-3-combat-magic-intent.md`](../research/2026-07-26-slice-j5-3-combat-magic-intent.md).
|
||||
|
||||
## J5.4 — local movement and outbound cadence
|
||||
|
||||
Move `PlayerMovementController`, its data types, and
|
||||
`LocalPlayerOutboundController` into Runtime. Replace test-era environment
|
||||
reads with typed construction options and the Runtime character movement-skill
|
||||
projection.
|
||||
|
||||
App provides:
|
||||
|
||||
- current typed movement snapshot from the input dispatcher;
|
||||
- filtered mouse-look turn adjustment;
|
||||
- animation root-motion frame and completion acknowledgements;
|
||||
- optional diagnostic sink.
|
||||
|
||||
Runtime owns:
|
||||
|
||||
- canonical local `PhysicsBody`, `MotionInterpreter`, `MovementManager`,
|
||||
`MoveToManager`, `PositionManager`, and object clock;
|
||||
- local input edge interpretation;
|
||||
- run lock, jump charge/launch, movement/contact state;
|
||||
- target-facing movement;
|
||||
- pre-inbound MTS/jump and post-inbound autonomous-position send cadence;
|
||||
- complete canonical `Position`, contact-plane, timestamps, and sent baselines.
|
||||
|
||||
Gate:
|
||||
|
||||
- bit-exact `RawMotionState`, MTS, jump, and autonomous-position fixtures;
|
||||
- strict cadence/equality boundaries;
|
||||
- complete quaternion/cell-local frame;
|
||||
- short-tap animation-owned start;
|
||||
- run/walk/back/strafe/turn/mouse-look/jump/land;
|
||||
- portal/focus/reset teardown;
|
||||
- target-facing and movement-aborts-repeat ordering;
|
||||
- zero added per-frame allocations after warmup.
|
||||
|
||||
**Completed 2026-07-26 at
|
||||
`aa3f4a60f87f2cbebc3d8ecd5e33d779b7fb13c1`.**
|
||||
`RuntimeLocalPlayerMovementState` constructs and owns the exact
|
||||
`PlayerMovementController`, construction-time motion seam, autorun latch,
|
||||
typed movement view, and terminal ledger. The movement controller and
|
||||
`LocalPlayerOutboundController` now live in `AcDream.Runtime.Gameplay`.
|
||||
Graphical input samples physical state and borrows that owner; direct Runtime
|
||||
commands mutate the same state without an App/input detour. Character
|
||||
run/jump skill projections enter through typed construction options rather
|
||||
than process environment reads. Runtime tests pass 303/303, App tests pass
|
||||
3,716/3 skips, and the complete Release suite passes 8,575/5 skips. The
|
||||
exact-binary connected lifecycle/reconnect report
|
||||
`logs/connected-world-gate-20260726-123425/report.json` passes both sessions,
|
||||
7/7 readiness checkpoints, graceful code-zero exits, and zero failures. The
|
||||
canonical route
|
||||
`logs/connected-r6-soak-20260726-124049.report.json` passes all nine
|
||||
destinations; forward, jump, combat, and authoritative movement-truth
|
||||
exercises pass at Caul, Holtburg, and the Caul return before graceful
|
||||
shutdown. Evidence:
|
||||
[`../research/2026-07-26-slice-j5-4-local-movement-ownership.md`](../research/2026-07-26-slice-j5-4-local-movement-ownership.md).
|
||||
|
||||
## J5.5 — per-session physics and remote simulation
|
||||
|
||||
Add a Runtime physics owner that constructs one `PhysicsEngine` per runtime
|
||||
instance. App streaming publishes immutable prepared collision assets through
|
||||
typed admission/withdrawal acknowledgements.
|
||||
|
||||
Move canonical remote simulation state and component indexes from App sidecars
|
||||
to `RuntimeEntityRecord`/Runtime owners:
|
||||
|
||||
- remote body/interpolation/movement-manager state;
|
||||
- canonical cell/rebucket writes;
|
||||
- physics-host lookup;
|
||||
- shadow/collision gameplay membership;
|
||||
- ordinary-object update workset.
|
||||
|
||||
App consumes committed snapshots/events to:
|
||||
|
||||
- update `WorldEntity`;
|
||||
- rebucket render projections;
|
||||
- publish current root/part/equipped-child poses;
|
||||
- refresh graphical collision/debug views.
|
||||
|
||||
Gate:
|
||||
|
||||
- complete flat collision conformance corpus remains bit-identical;
|
||||
- fresh/reused engine parity and zero-allocation resolve profiles;
|
||||
- player and remote position/cell/orientation parity;
|
||||
- crowd de-overlap, doors, stairs, dungeon/cell seams, login placement;
|
||||
- hidden/pending/rehydration, teleport correction, delete/GUID reuse;
|
||||
- two concurrent Runtime instances prove scratch/shadow isolation.
|
||||
|
||||
**Completed 2026-07-26 at
|
||||
`7e6033d0adcd0b572c20e89dd275746fb442d52f`.**
|
||||
`RuntimeEntityObjectLifetime` now constructs one `RuntimePhysicsState`, which
|
||||
owns the sole production `PhysicsEngine`, data cache/cell graph, transition
|
||||
scratch, shadow registry, collision admissions, canonical bodies and hosts,
|
||||
remote components, ordinary/remote worksets, simulation, and full-cell
|
||||
commits. App publishes prepared collision through generation-scoped receipts,
|
||||
supplies DAT/animation inputs, and projects immutable committed snapshots; it
|
||||
does not construct or run a parallel solver. Runtime tests pass 314/314, App
|
||||
tests pass 3,718/3 skips, and the complete Release suite passes 8,588/5 skips.
|
||||
The exact-binary lifecycle/reconnect report
|
||||
`logs/connected-world-gate-20260726-134107/report.json` passes both sessions,
|
||||
7/7 readiness checkpoints, graceful code-zero exits, and zero failures. The
|
||||
canonical route
|
||||
`logs/connected-r6-soak-20260726-134638.report.json` matches source/binary,
|
||||
passes all nine render/collision-ready destinations and movement exercises,
|
||||
and exits gracefully with code zero and zero failures. Evidence:
|
||||
[`../research/2026-07-26-slice-j5-5-physics-remote-ownership.md`](../research/2026-07-26-slice-j5-5-physics-remote-ownership.md).
|
||||
|
||||
## J5.6 — projectile runtime
|
||||
|
||||
Move projectile component identity, prediction versions, and simulation into
|
||||
Runtime. Store the canonical component on `RuntimeEntityRecord`, not the App
|
||||
sidecar.
|
||||
|
||||
Runtime uses:
|
||||
|
||||
- existing Core `ProjectilePhysicsStepper`;
|
||||
- canonical body/cell/state/timestamp authority;
|
||||
- strict prepared collision shapes;
|
||||
- existing split Begin/Complete prediction with exact-version revalidation.
|
||||
|
||||
App acknowledges successful commits by:
|
||||
|
||||
- updating/rebucketing the render projection;
|
||||
- synchronizing graphical shadow representation;
|
||||
- publishing effect root/pose.
|
||||
|
||||
App does not decide collision outcome, impact, damage, effect, or deletion.
|
||||
|
||||
Gate:
|
||||
|
||||
- existing clamp/gravity/AlignPath/angular/collision/thin-wall fixtures;
|
||||
- designated-target exclusion semantics;
|
||||
- loaded/pending/cross-landblock/correction/delete/GUID-reuse;
|
||||
- 96-owner lifetime/effect stress;
|
||||
- no duplicated mesh, effect, shadow, body, or script owner;
|
||||
- arrow, bolt, and representative spell projectile connected route.
|
||||
|
||||
## J5.7 — combined cleanup and closeout
|
||||
|
||||
Delete:
|
||||
|
||||
- temporary App owner fields and construction fallbacks;
|
||||
- App simulation component dictionaries;
|
||||
- current-runtime adapters that reconstruct moved snapshots;
|
||||
- reset/disposal branches replaced by Runtime;
|
||||
- compatibility aliases after all consumers borrow Runtime.
|
||||
|
||||
Add:
|
||||
|
||||
- one J5 ownership ledger;
|
||||
- failure injection at every construction, subscription, command, projection
|
||||
acknowledgement, reset, and disposal boundary;
|
||||
- graphical/direct normalized traces;
|
||||
- Runtime-only movement/use/combat/cast/projectile fixture host;
|
||||
- source/dependency guards against App/backend leakage and duplicate owners.
|
||||
|
||||
Run:
|
||||
|
||||
1. focused Core physics/selection/combat tests;
|
||||
2. Runtime state/command/event/teardown tests;
|
||||
3. App input/interaction/combat/magic/physics/projectile tests;
|
||||
4. Release build and complete Release suite;
|
||||
5. exact-binary lifecycle/reconnect route;
|
||||
6. canonical nine-stop movement/combat/jump route;
|
||||
7. connected interaction, casting, projectile, portal, and reconnect route;
|
||||
8. user graphical feel gate for movement, facing, use, combat/cast bars, and
|
||||
visible missiles;
|
||||
9. architecture, divergence, issue, roadmap, AGENTS/CLAUDE, and durable-memory
|
||||
reconciliation.
|
||||
|
||||
**Completed 2026-07-26 at
|
||||
`cdee7a4b49addb5e1500753f6a885f7c899bd0f0`.**
|
||||
Runtime now owns accepted CreateObject vector installation, remote-motion
|
||||
construction/activation, canonical vector commits, final simulation-component
|
||||
retirement, terminal physics/shadow/workset cleanup, and one combined
|
||||
entity/object/physics/gameplay ownership ledger. App compatibility views and
|
||||
the duplicate remote-body initializer are deleted; graphical checkpoints
|
||||
borrow direct Runtime views. A Runtime-only fixture drives movement, use,
|
||||
combat, casting, and projectile simulation without loading App or a
|
||||
presentation/backend assembly, and injected projection failure still converges
|
||||
the complete ledger.
|
||||
|
||||
Runtime tests pass 323/323, App tests pass 3,717/3 skips, the complete Release
|
||||
suite passes 8,596/5 skips, and the Release solution builds with zero errors.
|
||||
The exact-binary lifecycle/reconnect report
|
||||
`logs/connected-world-gate-20260726-160825/report.json` passes all seven
|
||||
checkpoints and both graceful code-zero exits. The canonical report
|
||||
`logs/connected-r6-soak-20260726-161406.report.json` matches embedded
|
||||
binary/source SHA, passes all nine destinations and movement/combat/jump
|
||||
exercises, converges every streaming backlog to zero, and exits gracefully
|
||||
with zero failures. Evidence:
|
||||
[`../research/2026-07-26-slice-j5-7-simulation-ownership-closeout.md`](../research/2026-07-26-slice-j5-7-simulation-ownership-closeout.md).
|
||||
|
||||
## Commit and rollback discipline
|
||||
|
||||
Each sub-slice lands as one independently reversible production commit followed
|
||||
by a documentation/evidence checkpoint where needed.
|
||||
|
||||
Planned production commit sequence:
|
||||
|
||||
1. `refactor(runtime): own canonical action state`
|
||||
(`b298f99f913249d299796e6a71f7a9b6b283ac1a`)
|
||||
2. `refactor(runtime): own interaction transactions`
|
||||
3. `refactor(runtime): own combat and magic intent`
|
||||
4. `refactor(runtime): own local movement and outbound cadence`
|
||||
5. `refactor(runtime): own per-session physics simulation`
|
||||
6. `refactor(runtime): own projectile simulation`
|
||||
7. `refactor(runtime): close simulation ownership`
|
||||
|
||||
Record each full SHA and exact `git revert <sha>` command in this file before
|
||||
running its connected gate. Revert J5 groups in reverse order. Do not revert
|
||||
J4 or the flat-collision Slice I cutover to mask a J5 ownership failure.
|
||||
|
||||
J5.1 rollback:
|
||||
|
||||
```text
|
||||
git revert b298f99f913249d299796e6a71f7a9b6b283ac1a
|
||||
```
|
||||
|
||||
J5.2 rollback:
|
||||
|
||||
```text
|
||||
git revert f5f7b4177f449ebcad9724bfea263ef73ab60255
|
||||
```
|
||||
|
||||
J5.3 rollback:
|
||||
|
||||
```text
|
||||
git revert 20df9d155db50706a42420d60a9b15861cf4bfe7
|
||||
```
|
||||
|
||||
J5.4 rollback:
|
||||
|
||||
```text
|
||||
git revert aa3f4a60f87f2cbebc3d8ecd5e33d779b7fb13c1
|
||||
```
|
||||
|
||||
J5.5 rollback:
|
||||
|
||||
```text
|
||||
git revert 7e6033d0adcd0b572c20e89dd275746fb442d52f
|
||||
```
|
||||
|
||||
J5.6 rollback:
|
||||
|
||||
```text
|
||||
git revert 2aee33569f0268d7bc0f4f52437dfa0b405432a4
|
||||
```
|
||||
|
||||
J5.7 rollback:
|
||||
|
||||
```text
|
||||
git revert cdee7a4b49addb5e1500753f6a885f7c899bd0f0
|
||||
```
|
||||
|
||||
## Acceptance
|
||||
|
||||
J5 is complete only when:
|
||||
|
||||
- every listed presentation-free owner is constructed by Runtime;
|
||||
- App owns presentation only and borrows exact Runtime state;
|
||||
- graphical and no-window commands produce the same ordered state and packets;
|
||||
- collision and trajectory fixtures remain bit-identical;
|
||||
- reset, reconnect, same-process multiple-instance, GUID reuse, and failure
|
||||
injection converge every owner;
|
||||
- no extra frame or per-frame allocation regression exists;
|
||||
- complete Release and connected gates pass;
|
||||
- the user accepts graphical movement, facing, interaction, combat/casting,
|
||||
and projectile behavior;
|
||||
- architecture, roadmap, milestones, issues/divergences, AGENTS/CLAUDE,
|
||||
evidence, memory, and rollback instructions are synchronized.
|
||||
|
|
@ -1,407 +0,0 @@
|
|||
# Modern runtime Slice J6 — world, transit, and host handshake
|
||||
|
||||
**Status:** J6 COMPLETE
|
||||
**Parent:** `2026-07-25-modern-runtime-slice-j.md`, J6
|
||||
**Authorization:** the user approved Slices F–L, including gameplay-owner
|
||||
moves, on 2026-07-24
|
||||
**Purpose:** move the one authoritative world-environment and local-transit
|
||||
lifetime into `AcDream.Runtime` while preserving the accepted retail portal
|
||||
presentation and leaving graphical readiness, streaming, sky drawing, VFX,
|
||||
audio, and UI in `AcDream.App`.
|
||||
|
||||
## 1. Retail ordering oracle
|
||||
|
||||
The load and placement boundary is fixed by the named retail client:
|
||||
|
||||
- `SmartBox::TeleportPlayer @ 0x00453910`
|
||||
- `SmartBox::UseTime @ 0x00455410`
|
||||
- `gmSmartBoxUI::EndTeleportAnimation @ 0x004D65A0`
|
||||
- `CPhysicsObj::enter_world @ 0x00516170`
|
||||
- `SkyDesc::CalcPresentDayGroup @ 0x00500E10`
|
||||
- `CPlayerSystem::Handle_Admin__Environs @ 0x0055DE20`
|
||||
|
||||
The consolidated pseudocode is already captured in:
|
||||
|
||||
- `docs/research/2026-07-15-retail-portal-space-pseudocode.md`
|
||||
- `docs/research/2026-07-16-portal-completion-pseudocode.md`
|
||||
- `docs/research/2026-07-24-retail-streaming-retirement-pseudocode.md`
|
||||
- `docs/research/2026-04-23-daygroup-selection.md`
|
||||
|
||||
The load-bearing order is:
|
||||
|
||||
```text
|
||||
fresh F751 notification
|
||||
establish one teleport generation
|
||||
matching accepted Position
|
||||
establish one exact destination
|
||||
CellManager finishes destination load
|
||||
host acknowledges exact generation + destination readiness
|
||||
SmartBox::TeleportPlayer / CPhysicsObj::enter_world
|
||||
commit position and destination cell
|
||||
resume destination simulation
|
||||
gmSmartBoxUI::EndTeleportAnimation
|
||||
continue private tunnel presentation
|
||||
TunnelFadeOut -> WorldFadeIn
|
||||
reveal normal world viewport
|
||||
WorldFadeIn tail ends
|
||||
send LoginComplete and complete the generation
|
||||
```
|
||||
|
||||
The graphical host may delay the readiness acknowledgement until render,
|
||||
composite-texture, and collision publication have converged. It does not own
|
||||
the teleport generation, destination identity, materialization state, or
|
||||
simulation availability. A headless host supplies the same acknowledgement
|
||||
contract using its own required domains.
|
||||
|
||||
## 2. Baseline owner inventory (before J6)
|
||||
|
||||
### 2.1 Authoritative state that J6 removed from App
|
||||
|
||||
| Baseline App owner | State | Final J6 owner |
|
||||
|---|---|---|
|
||||
| `WorldEnvironmentController` | server-synced world clock, selected day group, weather, AdminEnvirons override, debug overrides | `RuntimeWorldEnvironmentState` |
|
||||
| static `DerethDateTime.OriginOffsetTicks` | mutable Region calendar origin shared by every process session | instance `DerethCalendar` owned by each environment |
|
||||
| `TeleportTransitCoordinator<T>` | F751 freshness, Position correlation, accepted-destination history | `RuntimeWorldTransitState` |
|
||||
| `WorldRevealLifecycleTelemetry` | reveal generation, destination, readiness, materialized/completed/cancelled/world-visible state | `RuntimeWorldTransitState` |
|
||||
| `WorldGenerationAvailabilityState` | whether gameplay/world simulation may advance | borrowed projection of `RuntimeWorldTransitState` |
|
||||
| `CurrentGameRuntimeViewAdapter.PortalView` | reconstructs Runtime portal state from App lifecycle state | direct borrowed Runtime view |
|
||||
|
||||
### 2.2 App state that must remain App
|
||||
|
||||
| Owner | Reason |
|
||||
|---|---|
|
||||
| `WorldRevealReadinessBarrier` | joins renderer, texture-upload, and collision-publication facts |
|
||||
| `StreamingController` / destination reservation | graphical streaming and publication |
|
||||
| `WorldRevealRenderResourceScheduler` | GPU upload priority |
|
||||
| `PortalTunnelPresentation`, `TeleportAnimSequencer`, `TeleportViewPlaneController` | private graphical presentation |
|
||||
| `WorldGenerationQuiescence` effects | clears graphical selection and suspends/resumes world audio |
|
||||
| `LoadedSkyDesc` / `DayGroupData` projection | raw DAT-backed sky presentation and renderer input |
|
||||
| terrain, EnvCell, sky, VFX, audio, UI owners | presentation |
|
||||
|
||||
### 2.3 State that already has the correct Runtime owner
|
||||
|
||||
- Current player full-cell and position: `RuntimeLocalPlayerMovementState`.
|
||||
- Accepted entity position/timestamp state: `RuntimeEntityDirectory`.
|
||||
- Canonical entity identity and incarnation: `RuntimeEntityObjectLifetime`.
|
||||
- Physics placement/cell membership: `RuntimePhysicsState`.
|
||||
- Network session generation and ordered transport: `LiveSessionController`.
|
||||
|
||||
J6 must borrow these owners. It may not add a second current-cell field, entity
|
||||
map, movement body, session generation, or packet queue.
|
||||
|
||||
## 3. Target Runtime contracts
|
||||
|
||||
### 3.1 Environment
|
||||
|
||||
```csharp
|
||||
public sealed class RuntimeWorldEnvironmentState
|
||||
{
|
||||
WorldTimeService WorldTime { get; }
|
||||
WeatherSystem Weather { get; }
|
||||
RuntimeWorldEnvironmentSnapshot Snapshot { get; }
|
||||
|
||||
void Initialize(RuntimeWorldEnvironmentDefinition definition);
|
||||
void SynchronizeFromServer(double ticks);
|
||||
RuntimeEnvironmentEffect ApplyAdminEnvirons(uint changeType);
|
||||
void RefreshDayGroup();
|
||||
void ResetSession();
|
||||
}
|
||||
```
|
||||
|
||||
The immutable definition contains only presentation-independent values:
|
||||
calendar origin, sky tick rate, and ordered day-group descriptors with the
|
||||
Core `SkyStateProvider`. Runtime owns the selected index and weather state.
|
||||
App retains the raw `LoadedSkyDesc` and resolves Runtime's active index to the
|
||||
matching `DayGroupData` for drawing.
|
||||
|
||||
`WorldTimeService` receives an instance `DerethCalendar` and an injectable
|
||||
`TimeProvider`; production no longer mutates process-global calendar origin or
|
||||
reads wall time through a hidden static dependency.
|
||||
|
||||
### 3.2 Transit/reveal
|
||||
|
||||
```csharp
|
||||
public readonly record struct RuntimeTeleportDestination(...);
|
||||
|
||||
public readonly record struct RuntimeDestinationReadiness(
|
||||
long Generation,
|
||||
uint DestinationCell,
|
||||
bool IsUnhydratable,
|
||||
bool RenderReady,
|
||||
bool CompositeReady,
|
||||
bool CollisionReady);
|
||||
|
||||
public sealed class RuntimeWorldTransitState : IRuntimePortalView
|
||||
{
|
||||
bool QueueTeleportStart(ushort sequence);
|
||||
RuntimeDestinationOfferResult OfferDestination(...);
|
||||
bool ActivatePending(out RuntimeTeleportDestination destination);
|
||||
|
||||
long BeginReveal(RuntimePortalKind kind, uint destinationCell);
|
||||
bool AcknowledgeDestinationReadiness(in RuntimeDestinationReadiness ack);
|
||||
bool AcknowledgeMaterialized(long generation, uint destinationCell);
|
||||
bool AcknowledgeWorldViewportVisible(long generation);
|
||||
bool Complete(long generation);
|
||||
bool Cancel(long generation);
|
||||
void ResetSession();
|
||||
}
|
||||
```
|
||||
|
||||
All mutators validate the exact active generation and destination. A stale
|
||||
acknowledgement cannot open a newer destination. Readiness is an immutable
|
||||
host acknowledgement; Runtime owns the resulting lifecycle state.
|
||||
|
||||
The snapshot separately exposes:
|
||||
|
||||
- destination readiness;
|
||||
- simulation availability;
|
||||
- materialization;
|
||||
- normal-world viewport observation;
|
||||
- completion/cancellation.
|
||||
|
||||
This preserves retail's two release edges:
|
||||
|
||||
1. materialization resumes destination simulation;
|
||||
2. the later tunnel/world swap releases graphical destination reservations.
|
||||
|
||||
### 3.3 App adapters
|
||||
|
||||
`WorldRevealCoordinator` becomes a graphical host adapter:
|
||||
|
||||
- it asks Runtime to begin the generation;
|
||||
- begins exact-generation streaming and render-resource reservations;
|
||||
- evaluates App readiness and acknowledges the exact generation/cell;
|
||||
- projects Runtime simulation-availability edges to selection/audio;
|
||||
- releases graphical reservations only at the viewport edge;
|
||||
- owns no lifecycle snapshot or generation counter.
|
||||
|
||||
`LocalPlayerTeleportController` remains the portal presentation orchestrator.
|
||||
It borrows `RuntimeWorldTransitState` for F751/Position correlation and
|
||||
lifecycle facts, while retaining only translated render-space placement,
|
||||
camera/view-plane state, tunnel animation, and App callback reentrancy guards.
|
||||
|
||||
## 4. Execution ledger
|
||||
|
||||
### J6.0 — owner proof and detailed plan
|
||||
|
||||
- Re-read the named retail functions and existing consolidated pseudocode.
|
||||
- Inventory every producer, consumer, reset edge, and checkpoint.
|
||||
- Pin the target contracts, no-mirror rules, sub-slices, gates, and rollback
|
||||
discipline in this document.
|
||||
|
||||
Gate: documentation/source audit proves every mutable field has exactly one
|
||||
planned owner and no accepted presentation edge changes.
|
||||
|
||||
### J6.1 — instance-scoped environment owner
|
||||
|
||||
**Complete 2026-07-26 at `902076c0`.**
|
||||
|
||||
- Add `DerethCalendar` and make `WorldTimeService` consume its exact instance.
|
||||
- Add `RuntimeWorldEnvironmentState` and immutable environment definitions.
|
||||
- Reduce App's `WorldEnvironmentController` to DAT conversion plus
|
||||
`IWorldSceneSkyStateSource` projection.
|
||||
- Route TimeSync, AdminEnvirons, diagnostics, render weather, and sky lookup
|
||||
through the one Runtime state.
|
||||
- Delete production mutation of `DerethDateTime.OriginOffsetTicks`.
|
||||
- Add two-instance isolation, day-group equality, TimeSync, debug override,
|
||||
AdminEnvirons, reset, and teardown tests.
|
||||
|
||||
Gate: Core/Runtime/App focused tests, Release build, full tests, exact
|
||||
day-group fixtures, and no Runtime dependency violation.
|
||||
|
||||
Result: Runtime now owns the one instance-scoped calendar, synchronized world
|
||||
clock, weather state, selected day group, AdminEnvirons state, and debug
|
||||
overrides. App only translates immutable DAT sky definitions and projects the
|
||||
selected Runtime state into the renderer. The 332 Runtime tests, 3,718 App
|
||||
tests / 3 skips, 8,611 complete Release tests / 5 skips, and exact-binary
|
||||
connected lifecycle/reconnect route pass. Both connected clients used
|
||||
`902076c0a42079596a8a273e9be402776cabf4b0`, completed all seven checkpoints,
|
||||
and exited gracefully with code zero. Evidence:
|
||||
[`../research/2026-07-26-slice-j6-1-environment-ownership.md`](../research/2026-07-26-slice-j6-1-environment-ownership.md).
|
||||
|
||||
### J6.2 — canonical reveal generation and typed readiness
|
||||
|
||||
**Complete 2026-07-26 at `a6860d55` plus connected correction
|
||||
`acb845d8`.**
|
||||
|
||||
- Add `RuntimeWorldTransitState`, immutable readiness acknowledgement, and
|
||||
direct `IRuntimePortalView`.
|
||||
- Move lifecycle telemetry and generation state from App to Runtime.
|
||||
- Make `WorldRevealCoordinator` a strict App host adapter.
|
||||
- Replace App's mutable availability state with a read-only Runtime projection;
|
||||
retain selection/audio side effects as exact edge observers.
|
||||
- Reject stale generation, wrong destination, reordered, duplicate, and
|
||||
post-cancel acknowledgements.
|
||||
- Preserve unhydratable destination behavior and the five-second retail wait
|
||||
cue without a timeout or forced reveal.
|
||||
|
||||
Gate: login, portal, supersession, stale acknowledgement, callback failure,
|
||||
session reset, and exact two-release-edge fixtures pass.
|
||||
|
||||
Result: Runtime owns the sole monotonic reveal generation, destination,
|
||||
typed readiness latch, materialization/simulation edge, viewport observation,
|
||||
completion, cancellation, wait cue, and portal materialization count. App's
|
||||
former lifecycle owner is deleted; `WorldRevealCoordinator` is a graphical
|
||||
host adapter and `WorldGenerationAvailabilityState` is a read-only Runtime
|
||||
projection. The first connected run correctly exposed that post-acceptance
|
||||
render-readiness samples may regress while the accepted atomic edge must
|
||||
remain latched; `acb845d8` ignores those later samples without mutating
|
||||
Runtime truth or reporting false invariant failures.
|
||||
|
||||
Runtime tests pass 349/349, App tests pass 3,716/3 skips, and the complete
|
||||
Release suite passes 8,626/5 skips. Exact-binary report
|
||||
`logs/connected-world-gate-20260726-171724/report.json` records six capped
|
||||
checkpoints and one uncapped fresh-process reconnect, graceful code-zero exits,
|
||||
zero failures, zero reveal invariant failures, and only the expected
|
||||
world-edge warning. Evidence:
|
||||
[`../research/2026-07-26-slice-j6-2-reveal-ownership.md`](../research/2026-07-26-slice-j6-2-reveal-ownership.md).
|
||||
|
||||
### J6.3 — canonical F751/Position transit and placement handshake
|
||||
|
||||
**Complete 2026-07-26 at `6a063a27`.**
|
||||
|
||||
- Move `TeleportTransitCoordinator` state into the Runtime transit owner.
|
||||
- Convert accepted network destination data once into
|
||||
`RuntimeTeleportDestination`.
|
||||
- Keep render-space translation/recenter and camera placement in App.
|
||||
- Validate placement and materialization against the exact Runtime generation,
|
||||
sequence, and destination cell.
|
||||
- Derive current session cell from the existing Runtime movement/entity owner;
|
||||
add no parallel current-cell field.
|
||||
- Reset/cancel transit through Runtime's structural teardown.
|
||||
- Delete the App transit coordinator and App portal-view reconstruction.
|
||||
|
||||
Gate: Position-before-F751, F751-before-Position, duplicate/stale/wrapped
|
||||
sequence, superseding teleport, same-landblock, cross-landblock, dungeon,
|
||||
mid-portal reset, and reconnect traces match.
|
||||
|
||||
`RuntimeWorldTransitState` now owns wrap-safe F751 history, pending/active
|
||||
teleport sequence, both accepted packet orders, the exact destination, and
|
||||
generation/sequence/cell placement and materialization validation. The former
|
||||
App transit coordinator and accepted-destination mirror are deleted; App
|
||||
retains only graphical portal and placement work.
|
||||
|
||||
Runtime tests pass 360/360, the focused App transit/reveal suite passes 34/34,
|
||||
App tests pass 3,710/3 skips, and the complete Release suite passes 8,631/5
|
||||
skips. Exact-binary report
|
||||
`logs/connected-world-gate-20260726-175333/report.json` records six capped
|
||||
checkpoints and one uncapped fresh-process reconnect, graceful code-zero exits,
|
||||
zero failures, zero reveal invariant failures, and only the expected world-edge
|
||||
warning. Evidence:
|
||||
[`../research/2026-07-26-slice-j6-3-teleport-correlation.md`](../research/2026-07-26-slice-j6-3-teleport-correlation.md).
|
||||
|
||||
### J6.4 — projection acknowledgement and owner cleanup
|
||||
|
||||
**Complete 2026-07-26 at `18d17d8b`.**
|
||||
|
||||
- Make App presentation registration/withdrawal use typed generation-scoped
|
||||
acknowledgements.
|
||||
- Ensure simulation release, viewport reveal, reservation release, and final
|
||||
completion each occur once and retain retryable suffixes after a callback
|
||||
failure.
|
||||
- Remove superseded App lifecycle/generation state and compatibility types.
|
||||
- Extend normalized checkpoints and ownership ledgers with environment,
|
||||
transit, readiness, and pending-host-ack counts.
|
||||
- Add direct/no-window host fixtures proving Runtime can complete login,
|
||||
portal, cancellation, and reset without loading App or a backend.
|
||||
|
||||
Gate: source/dependency guards find no App authority or Runtime presentation
|
||||
dependency; every failure-injection ledger converges to zero.
|
||||
|
||||
Result: Runtime owns an exact host-projection token and typed remaining-stage
|
||||
ledger for each reveal generation/destination. App retains only graphical
|
||||
resource receipts attached to that token. Registration, simulation release,
|
||||
reservation release, terminal completion, cancellation, supersession, and
|
||||
reset preserve an exact retryable suffix through callback failure and
|
||||
re-entrancy. Normalized checkpoints expose environment and transit ownership,
|
||||
including pending host acknowledgements. Direct Runtime fixtures complete
|
||||
login, portal, cancellation, and reset without loading App or a backend.
|
||||
|
||||
### J6.5 — connected and documentation closeout
|
||||
|
||||
**Complete 2026-07-26.**
|
||||
|
||||
- Run focused Core, Runtime, and App projects.
|
||||
- Run `dotnet build AcDream.slnx -c Release`.
|
||||
- Run the complete Release test suite.
|
||||
- Rebuild the exact binary.
|
||||
- Run fresh login, repeated recall/portal, world edge, dungeon, graceful
|
||||
disconnect, reconnect, and mid-portal disconnect routes.
|
||||
- Compare the accepted portal screenshots and presentation trace.
|
||||
- Update architecture, code structure, milestone, roadmap, parent plan,
|
||||
AGENTS/CLAUDE, divergence ledger, and durable memory.
|
||||
|
||||
Runtime tests pass 365/365, App tests pass 3,716/3 skips, and the complete
|
||||
Release suite passes 8,642/5 skips. Exact-binary report
|
||||
`logs/connected-world-gate-20260726-182805/report.json` records six capped
|
||||
checkpoints and one uncapped fresh-process reconnect, graceful code-zero exits,
|
||||
zero failures, and zero environment/transit/host ownership debt at every
|
||||
stable checkpoint. Evidence:
|
||||
[`../research/2026-07-26-slice-j6-4-host-acknowledgement.md`](../research/2026-07-26-slice-j6-4-host-acknowledgement.md).
|
||||
|
||||
J6 is complete only when automated and connected evidence proves:
|
||||
|
||||
- one Runtime environment owner;
|
||||
- one Runtime transit/reveal owner;
|
||||
- no stale destination acknowledgement can reveal a different generation;
|
||||
- no App portal-state reconstruction remains;
|
||||
- no portal timing, paperdoll, object-lifetime, VFX-tail, or world-void
|
||||
regression;
|
||||
- teardown and reconnect converge with no pending host acknowledgement.
|
||||
|
||||
## 5. Mandatory adversarial matrix
|
||||
|
||||
- two Runtime instances with different Region origins and server clocks;
|
||||
- TimeSync while a debug override is active;
|
||||
- day/year rollover and day-group reselection;
|
||||
- every AdminEnvirons fog and sound value, plus unknown values;
|
||||
- Position before F751 and F751 before Position;
|
||||
- duplicate, old, equal, and `ushort`-wrapped teleport sequences;
|
||||
- readiness for the wrong generation or destination;
|
||||
- ready then superseded, materialized then cancelled, viewport ack before
|
||||
readiness, and duplicate completion;
|
||||
- destination recenter still pending;
|
||||
- indoor render ready without EnvCell collision;
|
||||
- outdoor collision ready without the full render neighborhood;
|
||||
- unhydratable authoritative destination;
|
||||
- callback reentrancy at begin, readiness, placement, viewport, and complete;
|
||||
- disconnect during every portal animation state;
|
||||
- same-process disconnect/reconnect and two simultaneous Runtime instances;
|
||||
- graphical host withdrawal failure followed by retry;
|
||||
- zero App/UI/Silk/OpenAL/Arch assemblies in direct Runtime tests.
|
||||
|
||||
## 6. Commit and rollback discipline
|
||||
|
||||
Each coherent production sub-slice lands separately after its focused,
|
||||
Release, full-suite, dependency, and teardown gates. The exact production
|
||||
commit and matching `git revert <sha>` are recorded here, in the parent plan,
|
||||
the closeout evidence, architecture, and durable memory before moving on.
|
||||
|
||||
J6.1 exact rollback:
|
||||
|
||||
```text
|
||||
git revert 902076c0a42079596a8a273e9be402776cabf4b0
|
||||
```
|
||||
|
||||
J6.2 exact rollback, newest first:
|
||||
|
||||
```text
|
||||
git revert acb845d812574206da1693fb379fec8a6f175fcb
|
||||
git revert a6860d5563ae845a2cbb916abacf2b4aee515cdb
|
||||
```
|
||||
|
||||
J6.3 exact rollback:
|
||||
|
||||
```text
|
||||
git revert 6a063a27d4c805cd166d5a487afc290c34c400fd
|
||||
```
|
||||
|
||||
J6.4 exact rollback:
|
||||
|
||||
```text
|
||||
git revert 18d17d8bb17ed9ff3ed01da8273e2948abe6398b
|
||||
```
|
||||
|
||||
Documentation-only research/plan commits are not behavior rollback points.
|
||||
No workaround, timer, forced-ready path, duplicated owner, or alternate
|
||||
headless behavior is permitted.
|
||||