Port retail portal viewport projection and reveal behavior, preserve outbound combat style, drive remote and local grounded movement from authored CSequence root frames, and reuse the local prepared pose so animation hooks advance once. User-verified portal, observer movement, combat stance, and short-tap locomotion gates. Release build passed with 5,767 tests and five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
3.1 KiB
Retail combat-style ownership in outbound movement
Symptom
The local acdream player remains visually in combat while moving, but a retail observer sees the player leave the combat stance and locomote in NonCombat.
Named-retail oracle
CommandInterpreter::SendMovementEvent @ 0x006B4680 does not construct a new
axis-only motion object. It obtains the player's canonical raw state and passes
that exact object to the packet constructor:
player = CommandInterpreter.player
raw = player.InqRawMotionState()
if player != null and smartbox != null and raw != null and autonomy_level != 0:
packet = MoveToStatePack(
raw,
player.position,
player is Contact+OnWalkable,
player.minterp.standing_longjump,
player movement timestamps)
SendMoveToStateEvent(packet)
CPhysicsObj::InqRawMotionState @ 0x0050FDE0 returns
MovementManager::InqRawMotionState; stance and movement axes therefore have
one canonical owner.
RawMotionState::Pack @ 0x0051ED10 compares current_style with the retail
default MotionStance_NonCombat (0x8000003D):
flags.bit1 = current_style != NonCombat
write flags
if flags.bit0: write current_holdkey
if flags.bit1: write current_style as uint32
if flags.bit2: write forward_command
... write the remaining axes and action queue ...
RawMotionState::UnPack @ 0x0051EFC0 restores NonCombat when bit 1 is absent.
Omitting the bit is therefore an explicit NonCombat value, not “preserve the
receiver's previous stance.”
Reference cross-checks
- ACE
Network/Motion/RawMotionState.csreadsRawMotionFlags.CurrentStyle(0x2) as a 32-bit stance. - ACE
Network/Motion/MovementData.cscopies that field into the interpreted state relayed to observers only when the flag is present. - acclientlib
UtilityBelt.Common/DataTypes.csindependently documents that an absent rawCurrentStyledefaults to0x3DNonCombat.
Thus an axis-only acdream packet necessarily makes ACE/retail reconstruct NonCombat movement.
acdream root cause and port
PlayerMovementController already owns retail's canonical
MotionInterpreter.RawState, and inbound server stance changes correctly
update RawState.CurrentStyle. However, MovementResult omitted that field and
LocalPlayerOutboundController.BuildRawMotionState rebuilt only the hold key
and three axes. Its new RawMotionState retained the constructor default
NonCombat, so RawMotionStatePacker correctly omitted the CurrentStyle bit.
Port the missing canonical field through the existing immutable frame result:
MovementResult.CurrentStyle = MotionInterpreter.RawState.CurrentStyle
BuildRawMotionState(result):
raw.CurrentStyle = result.CurrentStyle
raw.CurrentHoldKey = result current hold level
raw.forward/side/turn = result axes
return raw
This preserves the existing update-thread ownership and packet scheduler. It
does not infer a stance from CombatMode or equipped items; the raw motion
state remains the oracle, exactly as retail. The still-unwired raw action queue
is separate and remains tracked by divergence TS-24.