diff --git a/docs/superpowers/plans/2026-05-13-phase-b4c-plan.md b/docs/superpowers/plans/2026-05-13-phase-b4c-plan.md
new file mode 100644
index 0000000..0e945d1
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-13-phase-b4c-plan.md
@@ -0,0 +1,444 @@
+# Phase B.4c — Door Swing Animation Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make Holtburg's inn doors visibly swing open / closed when the player Uses them. Closes #58 and completes the M1 demo target *"open the inn door"* with full visual feedback.
+
+**Architecture:** One block edit in `GameWindow.cs` adds a Door-specific spawn-time branch alongside the existing creature gate at line 2692. Detect Door entities by `spawn.Name == "Door"`. For each, build the same `AnimationSequencer` as creatures (load `MotionTable` from dats, construct sequencer) and immediately seed it with the `Off` cycle (closed) or `On` cycle (already open) based on the spawn's `PhysicsState` ETHEREAL bit. The existing `OnLiveMotionUpdated` handler then routes naturally — no downstream changes. Adds one diagnostic line in the UM handler for greppable verification. Spec: [`docs/superpowers/specs/2026-05-13-phase-b4c-design.md`](../specs/2026-05-13-phase-b4c-design.md).
+
+**Tech Stack:** C# .NET 10 · existing `AnimationSequencer` + per-frame tick + WB renderer · no new dependencies.
+
+---
+
+## File map
+
+| File | Op | Why |
+|---|---|---|
+| `src/AcDream.App/Rendering/GameWindow.cs` | Modify | Add `IsDoorSpawn` static helper + Door registration branch (after the existing `idleCycle` gate at line 2692) + `[door-cycle]` diagnostic in `OnLiveMotionUpdated`. |
+
+No new files. No unit tests added — GameWindow integration code is runtime-verified per the project's existing precedent (B.4b's switch cases, L.2g's MotionUpdated routing).
+
+---
+
+## Task 1 — Add `IsDoorSpawn` helper + Door registration branch
+
+**Files:**
+- Modify: `src/AcDream.App/Rendering/GameWindow.cs`
+
+This task adds two things in one commit: the static helper that detects Door entities, and the new `else if` branch in the live-spawn handler that registers them with a seeded `AnimationSequencer`.
+
+- [ ] **Step 1: Add the `IsDoorSpawn` helper**
+
+Insert this static helper immediately above the live-spawn handler. Use the `Edit` tool with this exact anchor:
+
+`old_string`:
+```
+ private void OnLiveEntitySpawnedLocked(AcDream.Core.Net.WorldSession.EntitySpawn spawn)
+```
+
+`new_string`:
+```
+ ///
+ /// Phase B.4c — door detection by server-sent name. Doors fail the
+ /// generic multi-frame-idle gate at line 2692 (no idle cycle), so we
+ /// register them via a sibling branch with a state-seeded sequencer.
+ ///
+ private static bool IsDoorSpawn(AcDream.Core.Net.WorldSession.EntitySpawn spawn)
+ => spawn.Name == "Door";
+
+ private void OnLiveEntitySpawnedLocked(AcDream.Core.Net.WorldSession.EntitySpawn spawn)
+```
+
+- [ ] **Step 2: Add the Door registration branch**
+
+Insert the new `else if` branch immediately after the existing `idleCycle` gate's closing brace (around line 2800). The anchor is the comment line that follows the gate. Use the `Edit` tool:
+
+`old_string`:
+```
+ }
+
+ // Dump a summary periodically so we can see drop breakdowns without
+ // waiting for a graceful shutdown.
+ if (_liveSpawnReceived % 20 == 0)
+```
+
+`new_string`:
+```
+ }
+ else if (IsDoorSpawn(spawn) && _animLoader is not null)
+ {
+ // Phase B.4c — Door swing animation. Doors fail the
+ // multi-frame-idle gate above (no idle cycle) but DO have a
+ // MotionTable with On/Off cycles that ACE drives via
+ // UpdateMotion. Register with a seeded sequencer so the
+ // per-frame tick has frames to advance from frame 1 (without
+ // the seed, Sequencer.Advance(dt) returns no frames and the
+ // MeshRefs rebuild at line 7691 collapses the door to origin).
+ //
+ // Initial cycle mirrors ACE's Door.cs:43
+ // (CurrentMotionState = motionClosed): Off when the door is
+ // closed at spawn, On when the spawn PhysicsState carries the
+ // ETHEREAL bit (door was already open in ACE's DB).
+ uint mtableId = spawn.MotionTableId ?? (uint)setup.DefaultMotionTable;
+ if (mtableId != 0)
+ {
+ var mtable = _dats.Get(mtableId);
+ if (mtable is not null)
+ {
+ var sequencer = new AcDream.Core.Physics.AnimationSequencer(setup, mtable, _animLoader);
+
+ const uint NonCombatStance = 0x80000001u;
+ const uint MotionOn = 0x4000000Bu; // ACE MotionCommand.On (door open)
+ const uint MotionOff = 0x4000000Cu; // ACE MotionCommand.Off (door closed)
+ const uint EtherealPs = 0x4u;
+ uint spawnState = spawn.PhysicsState ?? 0u;
+ uint initialCycle = (spawnState & EtherealPs) != 0 ? MotionOn : MotionOff;
+ if (sequencer.HasCycle(NonCombatStance, initialCycle))
+ sequencer.SetCycle(NonCombatStance, initialCycle);
+
+ var template = new (uint, IReadOnlyDictionary?)[meshRefs.Count];
+ for (int i = 0; i < meshRefs.Count; i++)
+ template[i] = (meshRefs[i].GfxObjId, meshRefs[i].SurfaceOverrides);
+
+ _animatedEntities[entity.Id] = new AnimatedEntity
+ {
+ Entity = entity,
+ Setup = setup,
+ Animation = null, // sequencer-driven; tick reads sequencer state
+ LowFrame = 0,
+ HighFrame = 0,
+ Framerate = 0f,
+ Scale = scale,
+ PartTemplate = template,
+ CurrFrame = 0,
+ Sequencer = sequencer,
+ };
+
+ if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabled)
+ Console.WriteLine(System.FormattableString.Invariant(
+ $"[door-anim] registered guid=0x{spawn.Guid:X8} entityId=0x{entity.Id:X8} mtable=0x{mtableId:X8} initialCycle=0x{initialCycle:X8}"));
+ }
+ }
+ }
+
+ // Dump a summary periodically so we can see drop breakdowns without
+ // waiting for a graceful shutdown.
+ if (_liveSpawnReceived % 20 == 0)
+```
+
+- [ ] **Step 3: Build green**
+
+Run: `dotnet build src/AcDream.App/AcDream.App.csproj -c Debug`
+
+Expected: build succeeds, 0 errors. Any new warnings should be tied to the additions only.
+
+If a name like `meshRefs`, `entity`, `setup`, or `scale` doesn't resolve in scope at the insertion point, STOP and report — these are variables that exist in the surrounding scope at line 2800 of `OnLiveEntitySpawnedLocked` (verified during spec authoring at line 2700-2784 reads). They should be in scope; if Edit landed in the wrong place, fix the anchor first.
+
+- [ ] **Step 4: Tests green**
+
+Run: `dotnet test`
+
+Expected: **1046 pass / 8 pre-existing-baseline fail** (same as main HEAD `3e08e10`). Any regression here means the new branch is touching unrelated code paths — investigate.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/AcDream.App/Rendering/GameWindow.cs
+git commit -m "$(cat <<'EOF'
+feat(B.4c): door spawn-time AnimationSequencer with state-seeded initial cycle
+
+Adds IsDoorSpawn helper and a sibling branch to the live-spawn
+handler's animation registration gate. Detects entities where
+spawn.Name == "Door" and registers them in _animatedEntities with an
+AnimationSequencer seeded from the spawn PhysicsState's ETHEREAL bit
+(Off cycle if closed, On if already open).
+
+Mirrors ACE Door.cs:43 (CurrentMotionState = motionClosed) so the
+sequencer always has frames for the per-frame tick to advance from
+the first render. Without the seed, Advance(dt) returns no frames and
+the MeshRefs rebuild at line 7691 collapses the door to origin.
+
+No changes to OnLiveMotionUpdated, AnimationSequencer, EntitySpawnAdapter,
+or the per-frame tick. The tick's sequencer branch at line 7497 reads
+ae.Sequencer.Advance(dt) and never touches ae.Animation in this path
+(only the legacy slerp else branch at line 7644+ does).
+
+[door-anim] registered diagnostic gated on ACDREAM_PROBE_BUILDING.
+
+Co-Authored-By: Claude Opus 4.7 (1M context)
+EOF
+)"
+```
+
+---
+
+## Task 2 — Add `[door-cycle]` UM dispatch diagnostic
+
+**Files:**
+- Modify: `src/AcDream.App/Rendering/GameWindow.cs`
+
+Adds one diagnostic line in `OnLiveMotionUpdated` that fires whenever an `UpdateMotion` arrives for an entity named "Door". Greppable trail for verification of the open/close cycle dispatch.
+
+- [ ] **Step 1: Locate the diagnostic insertion point**
+
+Run (Grep tool):
+```
+pattern: ACDREAM_DUMP_MOTION.*== "1"
+path: src/AcDream.App/Rendering/GameWindow.cs
+output: content with -n
+```
+
+Expected: one match around line 3075 in the `OnLiveMotionUpdated` body. The `[door-cycle]` diagnostic goes immediately after this `ACDREAM_DUMP_MOTION` block so both diagnostics are grouped.
+
+- [ ] **Step 2: Add the `[door-cycle]` diagnostic**
+
+Use the `Edit` tool. The anchor is the closing brace + blank line + the next code section ("Wire server-echoed RunRate") which follows the `ACDREAM_DUMP_MOTION` block at line 3075-3087:
+
+`old_string`:
+```
+ $"UM guid=0x{update.Guid:X8} mt=0x{update.MotionState.MovementType:X2} stance=0x{stance:X4} cmd={cmdStr} spd={spd:F2} " +
+ $"| seq now style=0x{seqStyle:X8} motion=0x{seqMotion:X8}");
+ }
+
+ // Wire server-echoed RunRate first — used for the player's own
+```
+
+`new_string`:
+```
+ $"UM guid=0x{update.Guid:X8} mt=0x{update.MotionState.MovementType:X2} stance=0x{stance:X4} cmd={cmdStr} spd={spd:F2} " +
+ $"| seq now style=0x{seqStyle:X8} motion=0x{seqMotion:X8}");
+ }
+
+ // Phase B.4c — durable per-Door UM dispatch trail for visual-test grep.
+ if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabled
+ && _liveEntityInfoByGuid.TryGetValue(update.Guid, out var doorInfo)
+ && doorInfo.Name == "Door")
+ {
+ Console.WriteLine(System.FormattableString.Invariant(
+ $"[door-cycle] guid=0x{update.Guid:X8} stance=0x{update.MotionState.Stance:X4} cmd=0x{(update.MotionState.ForwardCommand ?? 0u):X4}"));
+ }
+
+ // Wire server-echoed RunRate first — used for the player's own
+```
+
+- [ ] **Step 3: Build green**
+
+Run: `dotnet build src/AcDream.App/AcDream.App.csproj -c Debug`
+
+Expected: build succeeds, 0 errors.
+
+If the name `_liveEntityInfoByGuid` doesn't resolve, STOP and report. It exists in `GameWindow.cs` (verified during spec authoring; used elsewhere in `DescribeLiveEntity` around line 8758 of the B.4b-shipped tree).
+
+If `doorInfo.Name` doesn't resolve, the field on the live-entity info struct may be named differently (e.g. `EntityName`). Use Grep to find the existing usage pattern and adjust.
+
+- [ ] **Step 4: Tests green**
+
+Run: `dotnet test`
+
+Expected: same **1046 pass / 8 pre-existing-baseline fail** from Task 1.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/AcDream.App/Rendering/GameWindow.cs
+git commit -m "$(cat <<'EOF'
+feat(B.4c): [door-cycle] diagnostic in OnLiveMotionUpdated
+
+Logs one line per UpdateMotion arriving for an entity named "Door"
+when ACDREAM_PROBE_BUILDING=1. Greppable trail for the B.4c visual
+test: confirms the dispatcher hit the sequencer for door open / close.
+
+Durable subsystem-named tag per the Opus reviewer's B.4b feedback
+([B.4c] would rot after phase archival; [door-cycle] survives).
+
+Co-Authored-By: Claude Opus 4.7 (1M context)
+EOF
+)"
+```
+
+---
+
+## Task 3 — Visual verification at Holtburg inn doorway
+
+**This task is performed by the user.** The implementing agent kicks off the launch in background; the user observes the running client and reports the result.
+
+- [ ] **Step 1: Kill any stale client + wait for ACE session cleanup**
+
+Run via PowerShell:
+```powershell
+Get-Process -Name AcDream.App -ErrorAction SilentlyContinue | Stop-Process -Force
+Start-Sleep -Seconds 20
+```
+
+Per CLAUDE.md "Logout-before-reconnect": ACE keeps the last session alive briefly after disconnect. 20s is the empirical minimum from B.4b's debug session.
+
+- [ ] **Step 2: Launch the client with probes enabled**
+
+Run via Bash tool with `run_in_background: true`:
+```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_DEVTOOLS = "1"
+$env:ACDREAM_PROBE_BUILDING = "1"
+$env:ACDREAM_DUMP_MOTION = "1"
+dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 |
+ Tee-Object -FilePath "launch-b4c.log"
+```
+
+- [ ] **Step 3: User performs the scenario**
+
+In the running client:
+1. Wait ~8s for the player to spawn at Holtburg.
+2. Walk to the inn doorway (south building, north-facing door).
+3. Observe: door visually closed.
+4. Double-left-click the door.
+5. **Observe: door visibly swings open over a fraction of a second.**
+6. Walk forward through the open doorway.
+7. Wait ~30s in the inn.
+8. **Observe: door visibly swings closed.**
+9. Bump the closed door — confirm it blocks again.
+10. Close the client window.
+
+- [ ] **Step 4: Grep the log**
+
+Run via PowerShell:
+```powershell
+Select-String -Path launch-b4c.log -Pattern "door-anim|door-cycle|UM guid=.*Door|setstate.*0x7A9B4015"
+```
+
+Expected matches (in approximate order):
+- `[door-anim] registered guid=0x... mtable=0x... initialCycle=0x4000000C` (one per closed door at world load)
+- (user double-clicks)
+- `UM guid=0x7A9B4015 mt=... stance=0x0001 cmd=0x000B ...` (existing UM dump for the open motion)
+- `[door-cycle] guid=0x7A9B4015 stance=0x0001 cmd=0x000B` (NEW; cmd=On)
+- `[setstate] guid=0x7A9B4015 entityId=0x000F4245 state=0x0001000C` (L.2g chain)
+- ~30s gap
+- `UM guid=0x7A9B4015 mt=... stance=0x0001 cmd=0x000C ...`
+- `[door-cycle] guid=0x7A9B4015 stance=0x0001 cmd=0x000C` (NEW; cmd=Off)
+- `[setstate] guid=0x7A9B4015 entityId=0x000F4245 state=0x00010008`
+
+- [ ] **Step 5: Decide on follow-up based on observed behavior**
+
+- **Animation plays + door rests at open pose for ~30s + animation plays again on close + rests at closed pose**: success. Proceed to Task 4.
+- **Animation plays as a loop instead of one-shot** (door spins continuously): pivot to Approach C from the spec (bespoke `DoorAnimationState` outside the sequencer). Out of B.4c scope; revise the spec and file a slice 2.
+- **No animation, but log shows the dispatch fired**: motion-table cycle resolution issue. Inspect `mtable.Cycles[(0x80000001 << 16) | 0x4000000B]` to see if the cycle is present. May need a different cycle key form.
+- **`[door-anim] registered` never logs**: spawn-time branch isn't firing. Check `spawn.Name` actual value (might be localized or padded). Add a one-line `Console.WriteLine` of `spawn.Name` in the live-spawn handler to surface it, then revise `IsDoorSpawn` accordingly.
+
+---
+
+## Task 4 — Ship handoff + close #58 + roadmap/CLAUDE/memory updates
+
+**Files (in-repo):**
+- Create: `docs/research/2026-05-13-b4c-shipped-handoff.md`
+- Modify: `docs/ISSUES.md` (close #58)
+- Modify: `docs/plans/2026-04-11-roadmap.md` (add B.4c row to shipped table)
+- Modify: `CLAUDE.md` ("Currently in Phase L.2" paragraph + Next phase candidates)
+
+**File (outside repo):**
+- Modify: `C:\Users\erikn\.claude\projects\C--Users-erikn-source-repos-acdream\memory\project_interaction_pipeline.md`
+
+- [ ] **Step 1: Write the ship-handoff doc**
+
+Create `docs/research/2026-05-13-b4c-shipped-handoff.md`. Model after `docs/research/2026-05-13-b4b-shipped-handoff.md` for structure: TL;DR / What shipped (commit table) / End-to-end flow with actual observed evidence / Open notes / Reproducibility / Worktree state.
+
+Required content:
+- TL;DR: B.4c shipped, M1 demo target "open the inn door" now has full visual feedback. ~50 LOC, 2 implementation commits + 1 docs commit.
+- What shipped table (2 implementation commits from Tasks 1+2)
+- Actual observed `[door-anim]` and `[door-cycle]` log lines from Task 3 step 4
+- Worktree branch: `claude/phase-b4c-door-anim`, 4 commits ahead of `3e08e10` (the B.4b merge)
+
+- [ ] **Step 2: Move #58 from Active to Recently Closed in `docs/ISSUES.md`**
+
+Edit `docs/ISSUES.md`:
+- Cut the `## #58 — Door swing animation` block from "Active issues".
+- Paste under "Recently closed" with header changed to `## #58 — [DONE 2026-05-13] Door swing animation: ...`.
+- Add `**Status:** DONE` and `**Closed:** 2026-05-13` lines.
+- Add a one-paragraph closure summary describing the fix: Door-specific spawn-time branch + state-seeded SetCycle + UM diagnostic. Cite this PR's merge commit + the handoff doc.
+
+- [ ] **Step 3: Update the roadmap's shipped table**
+
+Edit `docs/plans/2026-04-11-roadmap.md`. Add a new row to the "shipped" table:
+
+```
+| 2026-05-13 | Phase B.4c — Door swing animation | | Closes #58. Door-specific spawn-time AnimationSequencer registration with state-seeded initial cycle. M1 demo target "open the inn door" now has full visual feedback. |
+```
+
+(Read the table first to match its column structure exactly — the B.4b row uses `Phase | What landed | Verification`; match that.)
+
+- [ ] **Step 4: Update `CLAUDE.md` "Currently in Phase L.2" paragraph + Next phase candidates**
+
+Edit `CLAUDE.md`:
+- Update "Currently in Phase L.2" paragraph to reflect B.4c shipped + visual-verified 2026-05-13.
+- Remove `#58 — Door swing animation` from the "Next phase candidates" list.
+- Elevate the next candidate (currently #2 in the list: "Triage the chronic open-issue list") to #1, OR pick a different next-phase based on M1 critical-path-ness. The natural next step per CLAUDE.md's "work-order autonomy" rule is whichever progresses M1's remaining demo targets ("click an NPC", "pick up an item") — file a one-line note that these are the M1-critical-path follow-ups even though they aren't pre-specced phases.
+
+- [ ] **Step 5: Update the memory file** (outside the repo, NOT git-tracked)
+
+Edit `C:\Users\erikn\.claude\projects\C--Users-erikn-source-repos-acdream\memory\project_interaction_pipeline.md`:
+- Append a "B.4c shipped 2026-05-13" entry to the components table:
+ - `Door swing animation` — exists now (`GameWindow.cs IsDoorSpawn + sibling spawn-time branch`)
+ - `[door-anim]` / `[door-cycle]` diagnostics — gated on `ACDREAM_PROBE_BUILDING`
+- Note: animation routing is door-specific, not general non-creature support yet (chests/levers/traps still drop through the gate).
+
+- [ ] **Step 6: Commit the in-repo docs**
+
+```bash
+git add docs/research/2026-05-13-b4c-shipped-handoff.md docs/ISSUES.md docs/plans/2026-04-11-roadmap.md CLAUDE.md
+git commit -m "$(cat <<'EOF'
+docs(B.4c): ship handoff + close #58 + roadmap/CLAUDE update
+
+Phase B.4c shipped end-to-end 2026-05-13. Holtburg inn doorway
+double-click verified: door visually swings open, player walks
+through, door visually swings closed ~30s later.
+
+2 implementation commits:
+- B.4c Task 1: door spawn-time AnimationSequencer with state-seeded cycle
+- B.4c Task 2: [door-cycle] diagnostic in OnLiveMotionUpdated
+
+Closes #58. Memory file project_interaction_pipeline.md updated
+outside the repo.
+
+Co-Authored-By: Claude Opus 4.7 (1M context)
+EOF
+)"
+```
+
+(The memory file lives outside the repo — update it but don't include it in this commit.)
+
+- [ ] **Step 7: Hand off to merge (controller does final review + merge)**
+
+After this commit, hand off to the controller. The controller will:
+1. Run the final whole-branch code review (Opus per CLAUDE.md "load-bearing quality review of a phase boundary").
+2. Merge `claude/phase-b4c-door-anim` → `main` with `--no-ff`.
+3. Verify tests on merged main.
+4. Remove the worktree (best-effort; submodules may block per the B.4b finishing experience).
+
+---
+
+## Self-review against the spec
+
+| Spec section | Plan task(s) | Coverage |
+|---|---|---|
+| §Architecture: sibling branch after creature gate at line 2692 | Task 1 step 2 | covered |
+| §Architecture: state-seeded initial cycle from spawn.PhysicsState | Task 1 step 2 | covered |
+| §Components: `IsDoorSpawn(spawn) => spawn.Name == "Door"` | Task 1 step 1 | covered |
+| §Components: Door registration body (sequencer build + SetCycle + AnimatedEntity register) | Task 1 step 2 | covered |
+| §Components: `[door-anim] registered` diagnostic on spawn | Task 1 step 2 | covered (inline in registration body) |
+| §Components: `[door-cycle]` diagnostic in OnLiveMotionUpdated | Task 2 step 2 | covered |
+| §Data flow: spawn → seeded cycle → UM dispatch → state flip → animation | Tasks 1+2 + L.2g (existing) | covered (L.2g pipeline is the upstream dependency) |
+| §Error handling: door has no MotionTable | Task 1 step 2 (`if (mtableId != 0)` + inner `if (mtable is not null)`) | covered |
+| §Error handling: MotionTable lacks On/Off cycle | Task 1 step 2 (`if (sequencer.HasCycle(...))` gate around SetCycle) | covered |
+| §Error handling: `_animLoader` null | Task 1 step 2 (outer `&& _animLoader is not null`) | covered |
+| §Error handling: spawn.Name != "Door" | (no code change — silent fallback, acceptable per spec) | covered by omission |
+| §Testing: runtime visual verification at Holtburg | Task 3 | covered |
+| §Testing: log grep | Task 3 step 4 | covered |
+| §Acceptance: build + tests green | Tasks 1+2 steps 3-4 | covered |
+| §Acceptance: ISSUES.md #58 → Recently closed | Task 4 step 2 | covered |
+| §Acceptance: roadmap + CLAUDE.md update | Task 4 steps 3-4 | covered |
+| §Non-goals: sound effects, dust, lighting, collision rotation, generalized non-creature support | (none — explicitly deferred) | covered by omission |
+
+No placeholders. No "TBD." Every code step shows the actual code; every command step shows the exact command and expected output. Type names (`AnimationSequencer`, `AnimatedEntity`, `MotionTable`, `EntitySpawn`) match across tasks. Diagnostic tags (`[door-anim]`, `[door-cycle]`) consistent throughout.