# C4 route 2 — ForcePosition: connected visual gate (2026-08-03) The user-facing acceptance test for route 2. Route 2 is code-complete and suite-green before this runs; this document is the hand-off. ## Why the obvious recipe does NOT work The route-2 contract's acceptance line says *"ACE `@teleport`-style displacement"*. That is wrong about which route it exercises, and following it would have produced a false pass. Verified in ACE this session: `PositionPack`'s constructor (`references/ACE/Source/ACE.Server/Network/Structure/PositionPack.cs:36-57`) advances `ObjectTeleport` when `adminMove == true` (lines 49-52) and only ever *reads* `ObjectForcePosition` (line 54). Every admin move command — `@teleto`, `@teletome`, `@teleloc`, `@movetome` — routes through `Player_Location.cs:654 Teleport()` / `SendUpdatePosition(true)` and therefore advances **TELEPORT_TS, not FORCE_POSITION_TS**. Those commands exercise **route 3**, not route 2. `SequenceType.ObjectForcePosition` is advanced at exactly **two** places in the whole ACE tree: 1. `references/ACE/Source/ACE.Server/WorldObjects/Player.cs:1148` — the PK Lite entry collision bump. 2. `references/ACE/Source/ACE.Server/WorldObjects/Player_Tick.cs:488` — the anti-cheat z-position rubber-band. (2) requires the server to believe you are fly-hacking — same landblock, a claimed Z more than 10 units above your last ground contact, more than a second after your last jump, Jump skill under 1000, and still flagged airborne. It is not reachable by legitimate play and there is no command path into it. Do not build the gate on it. So (1) is the gate. ## The only reliable lever is deferred — what that means The reachable trigger is the PK Lite entry-collision bump, which requires retail's `@pklite`. That is a **client** command, not a server one — ACE has no `pklite` text-command handler, so typing `@pklite` into chat today forwards as inert text. Retail's client turns it into a game action instead: - `ClientCommunicationSystem::DoPKLite` @`0x0057A490` (`acclient_2013_pseudo_c.txt:390106`) — rejects with error `0x507` when `ACCWeenieObject::IsPlayerKiller` @`0x0058C910` is true (PK bit `0x20` OR PKLite bit `0x2000000`), otherwise calls - `CM_Character::Event_EnterPKLite` @`0x006A13F0` (line 680071) — a 12-byte parameterless game action, opcode `0x28F`, no payload. acdream does not implement it, and **the user deferred implementing it (2026-08-03).** Scoping is preserved in this session's research so it can be picked up cheaply: ~40 lines of production code across `ClientCommandId`, `RetailClientCommandCatalog`, `ClientCommandRequests.BuildParameterless`, `WorldSession`, `ClientCommandController`, `LiveSessionCommandRouter`, and `LiveSessionRuntimeFactory` — every pattern already exists, including `WeenieError 0x0507`. **Consequence, stated plainly: route 2's own behaviour is NOT visually verifiable in this campaign.** Nothing a user can do makes ACE emit a ForcePosition. Route 2's acceptance therefore rests on its automated Runtime / App / Headless tests and the complete Release suite. The connected pass below is a **regression check on the blast radius**, not acceptance of the new route. Do not record it as the latter. ## What the connected pass actually covers Launch acdream in **Release** with `ACDREAM_RETAIL_UI=1` against the local ACE at `127.0.0.1:9000`. Route 2 deletes `PlayerMovementController.BlipPosition` and `HeadlessSessionWorldProjection.BlipLocalPlayer`, removes App's generic-tail double-write for the local player, and re-routes the local player's accepted placement through the canonical Runtime SetPosition transaction. So the things to confirm are that ordinary play is untouched: - Ordinary running, turning, walk/run toggle. No tethering, no rubber-band, no drift against the server. - A jump and a landing. - Walking through a doorway into an interior and back out. - A portal recall, and a portal into a dungeon. - Two-client observation: `+Acdream` seen from the retail client moves smoothly and lands where acdream shows it. Anything wrong there is route 2's fault even though route 2 did not intend to touch it. ## Optional cheap shot (may not fire) The second ACE call site is the anti-cheat z-position rubber-band (`Player_Tick.cs:459-490`). It needs: same landblock as your last ground contact, a claimed Z more than 10 units above it, more than a second since your last jump, Jump skill under 1000, and the server still flagging you airborne. There is no command path into it and normal play cannot satisfy it (falling makes the Z delta negative, and walking up terrain keeps refreshing the ground position), but a `@teleloc` straight up ~15 units within the same landblock is a two-minute experiment with a definitive tell: ACE's console logs `z-pos hacking detected for +Acdream` at `Player_Tick.cs:486` immediately before it force-bumps you. If that line appears, you have a genuine ForcePosition and the checks below apply. If it does not, the route is untested by observation — which is the expected outcome. **If a ForcePosition does occur, must be true:** - You end up at the corrected position and stay there. No visible double-apply, no snap-then-yank-back over one or two frames. - **Your facing does not change.** Retail's force branch replaces the destination heading with your current heading before placing (`HandleReceivedPosition` @`0x00453FD0`, `Frame::set_heading` at `0x00454068`). - Exactly **one** outbound `AutonomousPosition` for the correction. **The two named behaviour changes:** 1. **The ack now fires after the canonical commit, not before it.** Previously the client told ACE "got it, I'm here" before deciding where "here" was. Symptom of a regression: ACE re-sending corrections, or a visible fight between client and server position after the bump. 2. **The ForcePosition route no longer re-arms the constraint leash.** Retail's force branch returns at `0x0045409D`, ahead of all three `ConstrainTo` call sites (`0x00454272`, `0x0045418A`, `0x004541EC`); our old `BlipPosition` re-armed the leash citing a branch it was not on. #167 was a leash bug, so this is the change most worth your eyes if you get a bump. Symptom of a problem: after the bump, movement feels tethered, rubber-bands back toward the pre-bump spot, or the leash trips on ordinary running shortly after. ## Known, deliberately unchanged Retail's `SmartBox::PlayerPositionUpdated` (@`0x00453870`) gates its `set_viewer` / `LScape::update_viewpoint` re-seat on `distance >= GetAutonomyBlipDistance` (`0x004538C0-0x004538E2`). We publish the render root unconditionally. Not changed in this slice; recorded here so the next reader does not rediscover it as a bug.