Fix restored bot passengers and add capital Cataclysm portals

This commit is contained in:
Nico committed 2026-10-29 23:07:10 +01:00
1 parent 19075b6c75
commit bc8f171055
23 files changed
+1184 -24

No files matched your search

+213
View File
@@ -1,3 +1,216 @@
# Commit checkpoint: transport resumption, deck return and capital portals
User requested a local commit of all completed changes since19075b6c75. Includes
stopped-only recorded deck stand fallback, adoption of restored native ship
passengers and safe recorded exit, capital Cataclysm/Tol Barad/Vashjir graph
bindings and scoped spell repairs (migration07), plus tests/docs/evidence.
Transport final build and six tests passed; portal native tests verified all12
transfers and both-faction Vashjir graph denial. Temporary profiles/tele fixtures
removed, generated profiles/schedules restored. Final server362937 is operational;
no lifecycle operation or push as part of committing. Earlier uncommitted/pending
notes below are historical; consult git log for the resulting commit.
# Latest: capital Cataclysm/Tol portals COMPLETE, all12 native transfers verified
Applied SQL07 graphbindings forSW/Org Hyjal,Deepholm,Uldum,TwilightHighlands,
TolBarad,Vashjir (6/capital). Sources factionACL1/2, duplicateOrgTol219035 skipped.
Nine existing templates keptdata6=0 and worked. OrgTol206595 failednativeclick
atdata6=0 twice, nowvisitorcast1 and verifiednative88341arrival732. Vash90244/
90245 previouslyempty; native DBteleporteffects160117/118 + existingSmugglersScar
fixedtarget(-4570.29,3478.06,-101.809), visitorcast207690/691. Explicitfixedcave
landing, NOT retailquestprogress destinations. Other targets/conditions unchanged.
All12 nativeclicks/expectedarrivalcoordinates verified using35Karggan/36Edwin85.
Finalresults/evidence doc/clientless/checkpoints/2026-10-29-capital-cata-portals.
Area5146/children policies byteunchanged. Nativebothbotquery target5057 mask0
allowed0, both hardgraphtravelattempts rejected destinationdenied. Nodes/edges
remainpresent/enabled. Migrationreplay preservedbindings/effects/targets. Three
existing portal/ACK/area-node regressions passed; no newC++ changes/buildneeded.
Temp profileskarggan.lua/edwin.lua removed after verifyingexacttestcontents;
generatedprofiles+daily schedulesrestored. Bothtestbotsreturnedowncapitals;
12vcore_cata_probe_*gametelefixturesdeleted,cache reloaded,count0. No human edits.
EarlierHyjalbaselinefalsefailurewasreadingtooearly; finalsaveproveditworked,
userinformed andblanketcasterchange undone. AfterfixfirstTolretestattempt before
Edwinonline was rejected; real afteronline nativeclickARRIVED andfinalJSONcorrect.
Final server362937 undergdb/tmuxvcore-worldserver, console/tmp/vcore-cata-portals-
final-start-20261029.log. Freshready11s/process+8085/8087 verified. Known old
OutdoorPvPshutdowntracespreserved. No commit/push. Prior uncommitteddeckreturn/
resumedpassengerWIP preserved. SQL07intent-to-add becausecustomSQLignored.
See capital-cataclysm-portals.md. Taskcomplete; report andwaituserfeedback.
# Active task: capital Cataclysm/Tol Barad portals incl blocked Vashjir
User asks graph entries + real functionality checks for bothcapitals inclVashjir,
keeping global area ban. Native imports done12 unique spawns (OrgTol duplicate
219035 intentionally skipped). SQL07 prepared/applied, scoped data6visitorcast
for206595 (OrgTol FAILED live twice atdata6=0),207690/207691 (Vash emptyserver
spells). Nine other templates keep originaldata6=0 and native targets/conditions.
Vash90244/90245 hadNOeffects/targets; added DBnative teleport effects160117/118
and fixed existing gameteleSmugglersScar landing(-4570.29,3478.06,-101.809),
NOT claimed retailquest-dependent destination logic. GraphSQL seedonlypreserves
manual positions/enabledflags. SourceACL Alliance/Horde; area catalog unchanged.
Temp tests use35KargganAlliance85 and36EdwinHorde85, nohumans. Temporary specific
profiles karggan.lua/edwin.lua under sourceadventure/profiles copied fromgenerated
DB thenbehavior.enabled/daily_schedule=false. ORIGINAL backups /tmp/vcore-cata-
portals/*-profile-original.lua; files DID NOT EXIST before task. MUST REMOVEONLY
these2 profiles aftertests; luareload+scheduleon torestoregeneratedbehavior.
12 temporary game_tele namesvcore_cata_probe_<spawn> created; deleteonlythatprefix
and reloadgame_tele aftertests. No testfixtureleftinrepo/DB onfinish.
Probe script/tmp/vcore-cata-portals/probe.py completed12tests;11successful.
Failure OrgTolspawn211264 remainedsource after30s/retry; nowchangeddata6=1 and
issued shutdown10 to361432 (console/tmp/vcore-cata-portals-start-20261029.log)
for cache reload. Needwaitfullexit+gdbtrace thenstartsameinstalledbinaryfreshgdb
console/tmp/vcore-cata-portals-final-start-20261029.log,verifyready/ports. Retest
211264 withEdwin36 viaGMtelefixture+nativeclientlessportalobjectuse; pollsaveall/
DBuntiltargetmap732(-603.724,1387.62,22.0498). Original probes JSONsaved beforefix
/tmp/vcore-cata-portals/probes-before-tol-fix.json. Update finalprobes.json entry.
Other11 includingBOTHVashlandings passednativeclick->expectedposition. Native
35 areaqueriesbothVashtargets returnedfactionMask0/allowed0 area5057; real hard
travel_to_node35 targetVash rejected detail destinationdenied. Need repeat36
areaqueries whennotintransfer. Verifypolicy before.tsv byteunchanged.
Corrected earlier misleadingHyjalbaseline: firstDBread tooearly, finalsave proved
native0 worked; user informed, blanketcasterchange undone before tests. Do NOT
claim all portals broken. Otherexistinguncommitteddeckreturn/resumedpassengerWIP
preserved. No newC++ source changes inthisportal task; DBreloadrequiredrestarts.
Next finishOrgTolretest,cleanupfixtures/profiles, tests/docs/evidence,report.
# Latest: resumed-passenger build INSTALLED, server READY; Shanron on normal plan
User explicitly released recording hold and requested install/restart. Old349322
fully exited after known OutdoorPvP shutdown destructor crash (oldconsole trace
retained). Installed successful prepared build using existing CMake:
/tmp/vcore-transport-resume-install.log. Fresh gdb/tmux vcore-worldserver
PID356681, console/tmp/vcore-transport-resume-start-20261029.log. NewServer.log
freshready12s, both8085/8087 listening, process persists. No source/config/DB
changes for deployment; prior uncommitted deck-return+passenger-resume WIP remains.
Requested native clientless login140 to check Shanron without manual position/
profile/dayplan override. Core LoadFromDB reported transportGUID2 unavailable,
teleported him to homebind before controller startup; same warning for26.
Thus no restored attachment existed to exercise new ResumePassenger for140.
Confirmed140 online/profileloaded, normal restintent poi_guard_111 arrived and
rest_sleep/rest_look micros. No TRANSPORT_RESUME live event observed yet; do not
claim restored-passenger/exit behavior live-proven from this deployment. Six
focused tests and complete build previously passed. Core inability to restore
certain ship bindings remains a separate observed limitation. User was told
Shanron resumed ordinary schedule via core homebind fallback, not new deck exit.
No push/commit requested. Server operational. Recording hold is now released.
# Latest: orphan-passenger fix READY; USER HOLD NO INSTALL/RESTART
Final complete build passed/tmp/vcore-transport-resume-final-build.log (source
TransportMgr object rebuilt after final edits, link completed). Six targeted
tests pass/tmp/vcore-resumed-passenger-tests.json. Latest additional detached
land-tail ownership assertion passes in test_transport log. Native attachment
adoption + next permitted recorded halt + ordinary exit implemented; current
installed server349322/executable21:00:04 unchanged. No runtime console commands,
DB/profile/dayplan changes, installation or restart in this task. Existing
uncommitted stopped-deck-return fallback work retained. New fix is source/build
only and NOT live-verified. See resumed-transport-passengers.md.
USER IS RECORDING ROUTES. Notify ready and WAIT for explicit hold release before
install/restart. No pending builds/test processes. No commit/push requested.
# Latest: resumed-passenger fix prepared; USER RECORDING, NO RESTART/INSTALL
User explicitly requests fix for Shanron's orphaned native ship attachment but
NO restart while recording more routes. Added TransportMgr::ResumePassenger
and Resumed flag; Lua Adventure controller invokes before actions, after
observation/maintenance. Preserve normal active rides; require actual known
recorded vehicle+managed alive noncombat passenger, no taxi/pending teleport,
allowed stop. Lazy ensure both stop and graph data load. Reconstruct only Riding
state, never reboard/teleport/change schedule. At each Riding update choose actual
currently stopped permitted recorded dock; no guessed prior destination. Reuse
normal deck return/budget/recorded exit incl native map-transfer ACK; keep control
through detached land tail until arrived. No exiting at newly forbidden stops.
Unrecorded/forbidden services untouched. No static tram DB persistence repair.
Six tests passed/tmp/vcore-resumed-passenger-tests.json; extended production
transport harness includes restart adoption, preserved normal rides, complete
native-modeled exits, denied stops, unknown/unmanaged, landtail and cross-map.
Final build running/tmp/vcore-transport-resume-final-build.log. Source latest
changes after initialbuild must be included; verify final completion.
Current server349322 remains unchanged, NO console/lifecycle/DB/profile changes.
Doc resumed-transport-passengers.md. Previous uncommitted deck-return fallback
work preserved. Next finish final build/checks, tell user ready; WAIT for user
recording hold release before any install/restart. Live behavior unverified.
# Latest diagnosis: Shanron140 attached to ship but no resumed ride controller
User observes Shanron aboard ship toSW. Native console transport status140:
`No transport ride state for this character.` actioninfo140 Idle/empty. Character
DB online map0(-8644.53,1335.31,6.12629),transguid2(The Bravery). Currentserver
349322. Both previous and current boot logged generated140 startup but no native
boarding/travel progression; normal core restores MO_TRANSPORT attachment from
saved character, CPC `_rides` exists only in memory and Start refuses already
attached players. Thus restored ship passenger has no ride driver to initiate
recorded exit/deck fallback. Distinct from120Brynna's repeated return issue.
No code/DB/lifecycle/relocation changes for Shanron investigation; existing
uncommitted stopped-deck fallback changes preserved. Needed follow-up: adopt
already attached managed passengers after login/restart using matching recorded
service; disembark only at an actual safe recorded dock, then resume normal plan.
Do not simply detach/teleport a passenger in transit. Existing120fallback still
requires a tracked ride to call DeckMgr::Update and cannot solve orphaned ride.
# Latest: stopped deck-return fallback INSTALLED, server READY
User permits transfer to recorded onboard stand after return failure only while
vehicle stopped. Implemented checked direct native spline launch + persistent
return failure state and safe native passenger relocation fallback. No detach,
no transportGUID changes, exact recorded local anchor, same transport/map,
alive/no combat/no pending teleport, valid coordinates, stopped+exitBudget1s
reserve independently rechecked. Ordinary recorded exit still follows.
Five tests PASS: deck control (including failed start/return, motion and short
halt rejection), actual fallback helper with doubles, real meshes, generic
transport, grid loading. Build/tmp/vcore-deck-return-build.log PASS. Installed
/tmp/vcore-deck-return-install.log after341932 fully exited (old known OutdoorPvP
shutdown fault trace retained). Fresh gdb/tmux PID349322, console
/tmp/vcore-deck-return-start-20261029.log, freshready13s,8085/8087 listeners,
process verified. Installed binary contains DECK_RETURN_FALLBACK string.
No native fallback event seen yet; no live/visual acceptance claim. Natural bot
loader active; no manual forced action, DB relocation or profile/plan override.
Doc deck-return-fallback.md. All these changes UNCOMMITTED after19075b6c75.
Previous SESSION diagnostic edit is intentional. Report completion; await user.
# Latest: stopped deck-return fallback implemented, build passed
User explicitly permits transfer back to recorded onboard stand after return
navigation fails, only while vehicle stopped. Added Passenger.Returning/
ReturnFailed, checked immediate native spline launch, failed/timeout/interrupted
return handling; helper rechecks actual same passenger/map, stopped and exit
reserve+1s, life/combat/teleport, valid local/world coordinates. Native local
position+TransportBase passenger relocation+broadcast; attachment never removed.
Ordinary recorded exit remains unchanged. Tests controller/native helper/real
mesh passed, generic transport/grid tests running. Build
/tmp/vcore-deck-return-build.log complete PASS. Not yet installed. Currentserver
341932 remains running. Prior SESSION diagnostic change is expected WIP after
commit19075b6c75. New doc deck-return-fallback.md. Next: finish test checks,
graceful shutdown/install/restart undergdb, freshready+ports, observe boarding/
returns/exits; do not claim actual fallback occurred until logged.
# Latest: Brynna120 stuck aboard tram; diagnosed repeated deck return
User observes Brynna in wagon. Branch19075b6c75 clean at investigation start.
Native status120 Riding SW->IF spawn18803 point4 elapsed238825, abort0, action
Moving. Prior rescueSW completed20:45:28, normal boarding20:46:42/Riding20:46:43.
Deck outing target(2.419,-3.118,-10.445). At destination20:47:55 and20:47:58
repeated DECK_WALK return target(-0.112,-0.187,-10.345),length3.87. Next dock
20:50:17..22 dozens of identical return launches per second, NO Leaving state.
This confirms recurring deck-return problem, not lost attachment after restart.
DeckMgr::Launch reports success unconditionally via MotionMaster LaunchMoveSpline
(void), Update uses IsSplineEnabled and retries when actual local target not
reached. Exact cause (launch failure, motion slot priority, competing stop) not
proven yet. Unit UpdateSplinePosition does update transport-local position.
Server.log has _checkPathLengths validation errors for76/110, NOT120; do not
attribute those to Brynna without evidence. No code or DB/teleport/lifecycle
changes in this diagnosis. Server341932 remains running. Ordinary rescue refuses
attached passengers by design; do not detach/teleport moving wagon passenger.
Next fix should validate actual deck spline start/completion, avoid repeated
return issuance, reserve enough dock time, retain safe passenger attachment.
# Commit checkpoint: group follow, grounded portal and explicit tram rescue
User requested a local commit of the current completed changes. Includes group
@@ -0,0 +1,64 @@
# Capital Cataclysm portals and Tol Barad
World migration `2026_10_29_07_capital_cataclysm_portals.sql` adds twelve native
GameObject portal graph bindings, six for each capital. Source faction ACL is
Alliance1 or Horde2; destination area rules retain precedence. Imports and SQL
seed only: existing enabled flags and manually adjusted positions survive.
| Destination | Stormwind spawn | Orgrimmar spawn |
| --- | --- | --- |
| Hyjal | 200624 | 219028 |
| Deepholm | 200625 | 211258 |
| Uldum | 200626 | 211259 |
| Twilight Highlands | 200627 | 211260 |
| Tol Barad Peninsula | 220365 | 211264 |
| Vashj'ir | 220366 | 211257 |
Orgrimmar Tol Barad duplicate219035 is coincident with211264 and deliberately
has no second graph edge. Its shared template receives the same repair.
Nine existing portal templates worked in native tests without caster changes.
Orgrimmar Tol Barad206595 accepted clicks but failed to transfer Edwin85 in two
attempts; changing only this template to native visitor-cast mode made its
88341 teleport work. Native target coordinates and conditions remain unchanged.
An early Hyjal failure inference was corrected: first save/read was too early,
final save showed successful arrival. No blanket caster change was retained.
Vashj'ir server-only spells90244/90245 had neither effects nor target positions.
The migration supplies direct native teleport effects160117/160118 and visitor
casting for207690/207691. The fixed landing comes from the existing project
SmugglersScar game_tele1514 at map0(-4570.29,3478.06,-101.809). This is a deliberate
fixed cave destination, not reconstructed retail quest-progress destination
selection. No quest completion is granted or visibility condition bypassed.
Vashj'ir and children remain closed by unchanged area5146 faction mask0. Both
source and destination nodes and the portal edges exist/enabled. Native area
queries for Alliance35 and Horde36 resolve the landing to5057 with inherited
factionMask0 and allowed0. Actual hard travel_to_node attempts are rejected with
`destination node denied by area/node catalog` for both factions. Diagnostic
native-click tests intentionally exercise the underlying portal, outside
ordinary autonomous planning; the ban was never disabled.
## Verification and cleanup
Twelve native CMSG_GAMEOBJ_USE clicks with level85 Karggan35 and Edwin36 reached
their expected spell_target_position destinations. Full position snapshots are
in checkpoints/2026-10-29-capital-cata-portals/probes.json. Before-Tol-fix output
is retained separately. An initial post-restart retest attempted a click before
Edwin finished login and was rejected; the successful retest waited for online
state and native interaction-range validation. Do not count that login race as
a portal failure or an unverified success.
Migration replay preserved binding/effect/target results. Existing native portal
use, transfer/ACK and area-node regressions passed. No new C++ changes/build were
required; two restarts loaded server spell/template DB changes. Final server
PID362937 under gdb/tmux vcore-worldserver, console
/tmp/vcore-cata-portals-final-start-20261029.log; freshready11s,8085/8087 verified.
Known OutdoorPvP shutdown fault traces retained in earlier consoles.
The two temporary specific profiles were removed and generated profiles plus
daily schedules restored. Testbots returned to their own capitals. Twelve
vcore_cata_probe_* game_tele fixtures were deleted and cache reloaded (count0).
No human character modified. Prior uncommitted transport fixes preserved.
Native transfers are verified; final client-side visual acceptance remains with
the user. No commit or push requested for this task.
@@ -0,0 +1,13 @@
gameobject_spawn_id gameobject_entry spell_id id enabled from_node_key to_node_key allowed_factions
200624 207692 94565 1798 1 portal_go_200624_source portal_go_200624_target 1
200625 207693 84515 1799 1 portal_go_200625_source portal_go_200625_target 1
200626 207695 94564 1800 1 portal_go_200626_source portal_go_200626_target 1
200627 207694 85545 1801 1 portal_go_200627_source portal_go_200627_target 1
211257 207690 90244 1808 1 portal_go_211257_source portal_go_211257_target 2
211258 207689 84513 1803 1 portal_go_211258_source portal_go_211258_target 2
211259 207687 94564 1804 1 portal_go_211259_source portal_go_211259_target 2
211260 207686 82621 1805 1 portal_go_211260_source portal_go_211260_target 2
211264 206595 88341 1806 1 portal_go_211264_source portal_go_211264_target 2
219028 207688 94565 1807 1 portal_go_219028_source portal_go_219028_target 2
220365 206594 88339 1802 1 portal_go_220365_source portal_go_220365_target 1
220366 207691 90245 1809 1 portal_go_220366_source portal_go_220366_target 1
1 gameobject_spawn_id gameobject_entry spell_id id enabled from_node_key to_node_key allowed_factions
2 200624 207692 94565 1798 1 portal_go_200624_source portal_go_200624_target 1
3 200625 207693 84515 1799 1 portal_go_200625_source portal_go_200625_target 1
4 200626 207695 94564 1800 1 portal_go_200626_source portal_go_200626_target 1
5 200627 207694 85545 1801 1 portal_go_200627_source portal_go_200627_target 1
6 211257 207690 90244 1808 1 portal_go_211257_source portal_go_211257_target 2
7 211258 207689 84513 1803 1 portal_go_211258_source portal_go_211258_target 2
8 211259 207687 94564 1804 1 portal_go_211259_source portal_go_211259_target 2
9 211260 207686 82621 1805 1 portal_go_211260_source portal_go_211260_target 2
10 211264 206595 88341 1806 1 portal_go_211264_source portal_go_211264_target 2
11 219028 207688 94565 1807 1 portal_go_219028_source portal_go_219028_target 2
12 220365 206594 88339 1802 1 portal_go_220365_source portal_go_220365_target 1
13 220366 207691 90245 1809 1 portal_go_220366_source portal_go_220366_target 1
@@ -0,0 +1,20 @@
Native portal click sent: bot=35 spawn=200624 entry=207692. Check teleport completion and cast diagnostics.
Native portal click sent: bot=35 spawn=200625 entry=207693. Check teleport completion and cast diagnostics.
[?2004hTC>Native portal click sent: bot=35 spawn=200626 entry=207695. Check teleport completion and cast diagnostics.
Native portal click sent: bot=35 spawn=200627 entry=207694. Check teleport completion and cast diagnostics.
[?2004hTC>Native portal click sent: bot=35 spawn=220365 entry=206594. Check teleport completion and cast diagnostics.
Native portal click sent: bot=35 spawn=220366 entry=207691. Check teleport completion and cast diagnostics.
Native portal click sent: bot=36 spawn=219028 entry=207688. Check teleport completion and cast diagnostics.
Native portal click sent: bot=36 spawn=211258 entry=207689. Check teleport completion and cast diagnostics.
Native portal click sent: bot=36 spawn=211259 entry=207687. Check teleport completion and cast diagnostics.
[?2004hTC>Native portal click sent: bot=36 spawn=211260 entry=207686. Check teleport completion and cast diagnostics.
TC> Area access GUID 35: revision=1 map=0 area=5057 factionMask=0 minLevel=80 maxLevel=82 gap=5 knownLevel=1 preferFlight=0 nodeMask=-1 allowed=0
TC> Area access GUID 35: revision=1 map=0 area=5057 factionMask=0 minLevel=80 maxLevel=82 gap=5 knownLevel=1 preferFlight=0 nodeMask=-1 allowed=0
Native portal click sent: bot=36 spawn=211264 entry=206595. Check teleport completion and cast diagnostics.
Native portal click sent: bot=36 spawn=211257 entry=207690. Check teleport completion and cast diagnostics.
TC> Action GUID 35: state=Failed action='travel_to_node' target='' targetGuid=0 entry=0 detail='destination node denied by area/node catalog'
[?2004hTC>Native portal click sent: bot=36 spawn=211264 entry=206595. Check teleport completion and cast diagnostics.
[?2004hTC>Native portal click sent: bot=36 spawn=211264 entry=206595. Check teleport completion and cast diagnostics.
Area access GUID 36: revision=1 map=0 area=5057 factionMask=0 minLevel=80 maxLevel=82 gap=5 knownLevel=1 preferFlight=0 nodeMask=3 allowed=0
TC> Area access GUID 36: revision=1 map=0 area=5057 factionMask=0 minLevel=80 maxLevel=82 gap=5 knownLevel=1 preferFlight=0 nodeMask=3 allowed=0
Action GUID 36: state=Failed action='travel_to_node' target='' targetGuid=0 entry=0 detail='destination node denied by area/node catalog'
@@ -0,0 +1,218 @@
[
{
"spawn": 200624,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
1.0,
5534.08,
-3624.69,
1567.04
],
"expected": [
1.0,
5534.08,
-3624.69,
1567.04
]
},
{
"spawn": 200625,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
646.0,
974.921,
566.768,
-47.5163
],
"expected": [
646.0,
974.921,
566.768,
-47.5163
]
},
{
"spawn": 200626,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
1.0,
-9443.31,
-958.365,
111.012
],
"expected": [
1.0,
-9443.31,
-958.365,
111.012
]
},
{
"spawn": 200627,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
0.0,
-4890.07,
-6626.74,
10.2846
],
"expected": [
0.0,
-4890.07,
-6626.74,
10.2846
]
},
{
"spawn": 220365,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
732.0,
-369.208,
1058.73,
21.7719
],
"expected": [
732.0,
-369.208,
1058.73,
21.7719
]
},
{
"spawn": 220366,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
0.0,
-4570.29,
3478.06,
-101.809
],
"expected": [
0.0,
-4570.29,
3478.06,
-101.809
]
},
{
"spawn": 219028,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
1.0,
5534.08,
-3624.69,
1567.04
],
"expected": [
1.0,
5534.08,
-3624.69,
1567.04
]
},
{
"spawn": 211258,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
646.0,
995.808,
471.879,
-48.1172
],
"expected": [
646.0,
995.808,
471.879,
-48.1172
]
},
{
"spawn": 211259,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
1.0,
-9443.31,
-958.365,
111.012
],
"expected": [
1.0,
-9443.31,
-958.365,
111.012
]
},
{
"spawn": 211260,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
0.0,
-4032.79,
-6318.53,
38.6975
],
"expected": [
0.0,
-4032.79,
-6318.53,
38.6975
]
},
{
"spawn": 211264,
"bot": 36,
"at_source": true,
"success": false,
"actual": [
1.0,
2031.22,
-4330.87,
95.771
],
"expected": [
732.0,
-603.724,
1387.62,
22.0498
]
},
{
"spawn": 211257,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
0.0,
-4570.29,
3478.06,
-101.809
],
"expected": [
0.0,
-4570.29,
3478.06,
-101.809
]
}
]
@@ -0,0 +1,219 @@
[
{
"spawn": 200624,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
1.0,
5534.08,
-3624.69,
1567.04
],
"expected": [
1.0,
5534.08,
-3624.69,
1567.04
]
},
{
"spawn": 200625,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
646.0,
974.921,
566.768,
-47.5163
],
"expected": [
646.0,
974.921,
566.768,
-47.5163
]
},
{
"spawn": 200626,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
1.0,
-9443.31,
-958.365,
111.012
],
"expected": [
1.0,
-9443.31,
-958.365,
111.012
]
},
{
"spawn": 200627,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
0.0,
-4890.07,
-6626.74,
10.2846
],
"expected": [
0.0,
-4890.07,
-6626.74,
10.2846
]
},
{
"spawn": 220365,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
732.0,
-369.208,
1058.73,
21.7719
],
"expected": [
732.0,
-369.208,
1058.73,
21.7719
]
},
{
"spawn": 220366,
"bot": 35,
"at_source": true,
"success": true,
"actual": [
0.0,
-4570.29,
3478.06,
-101.809
],
"expected": [
0.0,
-4570.29,
3478.06,
-101.809
]
},
{
"spawn": 219028,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
1.0,
5534.08,
-3624.69,
1567.04
],
"expected": [
1.0,
5534.08,
-3624.69,
1567.04
]
},
{
"spawn": 211258,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
646.0,
995.808,
471.879,
-48.1172
],
"expected": [
646.0,
995.808,
471.879,
-48.1172
]
},
{
"spawn": 211259,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
1.0,
-9443.31,
-958.365,
111.012
],
"expected": [
1.0,
-9443.31,
-958.365,
111.012
]
},
{
"spawn": 211260,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
0.0,
-4032.79,
-6318.53,
38.6975
],
"expected": [
0.0,
-4032.79,
-6318.53,
38.6975
]
},
{
"spawn": 211264,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
732.0,
-603.724,
1387.62,
22.0498
],
"expected": [
732.0,
-603.724,
1387.62,
22.0498
],
"retest": "native click after confirmed online state; visitor cast=1"
},
{
"spawn": 211257,
"bot": 36,
"at_source": true,
"success": true,
"actual": [
0.0,
-4570.29,
3478.06,
-101.809
],
"expected": [
0.0,
-4570.29,
3478.06,
-101.809
]
}
]
@@ -0,0 +1,19 @@
Id Effect EffectAmplitude EffectAura EffectAuraPeriod EffectBasePoints EffectBonusCoefficient EffectChainAmplitude EffectChainTargets EffectDieSides EffectItemType EffectMechanic EffectMiscValue EffectMiscValueB EffectPointsPerResource EffectRadiusIndex EffectRadiusMaxIndex EffectRealPointsPerLevel EffectSpellClassMaskA EffectSpellClassMaskB EffectSpellClassMaskC EffectTriggerSpell EffectImplicitTargetA EffectImplicitTargetB SpellID EffectIndex Comment
160117 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 25 17 90244 0 Orgrimmar Vashjir portal: fixed SmugglersScar landing
160118 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 25 17 90245 0 Stormwind Vashjir portal: fixed SmugglersScar landing
ID EffectIndex MapID PositionX PositionY PositionZ Orientation VerifiedBuild
90244 0 0 -4570.29 3478.06 -101.809 5.62628 0
90245 0 0 -4570.29 3478.06 -101.809 5.62628 0
entry data6
206594 0
206595 1
207686 0
207687 0
207688 0
207689 0
207690 1
207691 1
207692 0
207693 0
207694 0
207695 0
1 Id Effect EffectAmplitude EffectAura EffectAuraPeriod EffectBasePoints EffectBonusCoefficient EffectChainAmplitude EffectChainTargets EffectDieSides EffectItemType EffectMechanic EffectMiscValue EffectMiscValueB EffectPointsPerResource EffectRadiusIndex EffectRadiusMaxIndex EffectRealPointsPerLevel EffectSpellClassMaskA EffectSpellClassMaskB EffectSpellClassMaskC EffectTriggerSpell EffectImplicitTargetA EffectImplicitTargetB SpellID EffectIndex Comment
2 160117 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 25 17 90244 0 Orgrimmar Vashjir portal: fixed SmugglersScar landing
3 160118 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 25 17 90245 0 Stormwind Vashjir portal: fixed SmugglersScar landing
4 ID EffectIndex MapID PositionX PositionY PositionZ Orientation VerifiedBuild
5 90244 0 0 -4570.29 3478.06 -101.809 5.62628 0
6 90245 0 0 -4570.29 3478.06 -101.809 5.62628 0
7 entry data6
8 206594 0
9 206595 1
10 207686 0
11 207687 0
12 207688 0
13 207689 0
14 207690 1
15 207691 1
16 207692 0
17 207693 0
18 207694 0
19 207695 0
@@ -0,0 +1,13 @@
entry type data0 data6
206594 22 88339 0
206595 22 88341 0
207686 22 82621 0
207687 22 94564 0
207688 22 94565 0
207689 22 84513 0
207690 22 90244 0
207691 22 90245 0
207692 22 94565 0
207693 22 84515 0
207694 22 85545 0
207695 22 94564 0
1 entry type data0 data6
2 206594 22 88339 0
3 206595 22 88341 0
4 207686 22 82621 0
5 207687 22 94564 0
6 207688 22 94565 0
7 207689 22 84513 0
8 207690 22 90244 0
9 207691 22 90245 0
10 207692 22 94565 0
11 207693 22 84515 0
12 207694 22 85545 0
13 207695 22 94564 0
@@ -0,0 +1 @@
Production area destination excludes outside and unreachable graph nodes.
@@ -0,0 +1 @@
Portal near/far ACK and denied same-map transfer regressions passed.
@@ -0,0 +1,3 @@
Interactive portal native click, near/far/global ACK, denial, timeout and target checks passed.
Native portal destination discovery and unsupported/ambiguous spell rejection passed.
Production clientless ACK completion sequence increments only after completed near/far transfers.
@@ -0,0 +1,5 @@
area_id map_id parent_area_id display_name allowed_factions min_level max_level max_level_gap prefer_flight notes
4815 0 5146 Tang'tharwald NULL 80 82 NULL NULL
5144 0 5146 Schimmernde Weiten NULL 80 82 NULL NULL
5145 0 5146 Abyssische Tiefen NULL 80 82 NULL NULL
5146 0 0 Vashj'ir 0 NULL NULL NULL NULL Vashjir closed to all AdventureBots, including child areas
1 area_id map_id parent_area_id display_name allowed_factions min_level max_level max_level_gap prefer_flight notes
2 4815 0 5146 Tang'tharwald NULL 80 82 NULL NULL
3 5144 0 5146 Schimmernde Weiten NULL 80 82 NULL NULL
4 5145 0 5146 Abyssische Tiefen NULL 80 82 NULL NULL
5 5146 0 0 Vashj'ir 0 NULL NULL NULL NULL Vashjir closed to all AdventureBots, including child areas
+41
View File
@@ -0,0 +1,41 @@
# Stopped-vehicle fallback to the recorded deck stand
User authorized transferring a bot to the recorded onboard stand when its return
navigation fails, exclusively while the vehicle is stopped. Implemented in
ClientlessTransportDeckMgr for its supported Bravery/tram deck meshes.
Bord movement now starts with checked native MoveSplineInit::Launch rather than
a void MotionMaster wrapper. Failed launch is not reported as a successful walk.
The controller distinguishes return walks from initial deck outings. A failed
path/launch, stopped-short return, timeout or departure-interrupted return sets
a persistent return-failure flag. An insufficient walk budget defers the attempt
and allows the same safe fallback once a sufficient stopped window exists.
At an arrival dock, a failed return can relocate only to the exact recorded local
stand. The helper independently rechecks stopped state, same map, actual same
transport attachment, alive/no combat/no pending teleport, finite bounded local
coordinates, valid transformed world coordinates and remaining exit budget+1s.
It stops the old spline, updates transport-local coordinates, calls the native
passenger relocation and broadcasts movement state. It never removes/adds a
passenger or changes vehicle/GUID. The normal reverse recorded exit then runs,
subject to the existing AtDock recheck. No relocation during vehicle motion.
Tests: production controller and actual relocation helper against deterministic
doubles; real WMO/M2 deck mesh tests; generic transport and grid-loading tests.
Compile log /tmp/vcore-deck-return-build.log. Initial gameplay root cause behind
Brynna's repeated return launches was not fully instrumented; checked launch and
bounded fallback address this failure without claiming static passenger
persistence across restart was repaired. Runtime deployment results appended
below after installation.
## Installed state
Successful build and five focused tests. Installed after complete process exit;
startup PID349322 undergdb in vcore-worldserver reached ready in13s and listens
on8085/8087. Install log /tmp/vcore-deck-return-install.log; new console
/tmp/vcore-deck-return-start-20261029.log. Prior known OutdoorPvP shutdown fault
trace retained in the previous console. No native fallback occurrence observed
yet and no manually forced trip/profile/position/plan change for this task.
Native helper/control test logs /tmp/vcore-deck-return-helper-test.log and
-control-test.log; real meshes/transport/grid logs share that prefix. Visual
acceptance and actual fallback use remain unverified.
@@ -0,0 +1,62 @@
# Resume restored clientless transport passengers
Shanron140 retained native Bravery spawn2 attachment after restart but had no
CPC ride state. The AdventureBot Lua controller now invokes ResumePassenger
before supply/combat/intent actions while a restored exit is owned. Observation
and population maintenance still run first. Active ordinary executor rides are
never replaced. Dead/combat/pending teleport/taxi and unmanaged players are not
adopted. Actual native attachment is required; no passenger is invented.
For an orphaned passenger, ensure stop and graph data are loaded; match a recorded
stop to the actual native transport object/pointer, spawn and entry; enforce
area/node/faction/level access. Seed a Resumed Riding record without reboarding,
relocating, clearing schedules or changing attachments. Unrecorded/forbidden
services are untouched. Failed ride records with an attached passenger can also
be recovered this way.
While Riding, choose the first actual permitted recorded dock the vehicle stops
at (deterministic node tie-break for coincident stops). Do not infer the original
journey destination or force a detour back to the seed dock. Native AtDock checks
map, position, heading and stopped state. Reuse deck return, stop-time budgets
and the ordinary reversed recorded exit. No exit at subsequently denied stops.
Native cross-map worldport ACK and transport copies retain the same service.
The resumed controller keeps ownership through the land portion after passenger
detachment, releasing it on Arrived/Failed so normal day planning can continue.
Static tram passenger persistence itself is unchanged: this requires an actual
restored/attached passenger, not merely a saved position in the tram tunnel.
Six focused tests passed: real transport methods now cover orphan adoption,
no duplicate boarding, normal ride preservation, unmanaged/unknown/denied cases,
first actual permitted dock, real exit completion, land-tail ownership and
cross-map restored ships. Existing deck controller/native fallback/real mesh,
transport grid and group travel tests also pass. Results
/tmp/vcore-resumed-passenger-tests.json; final build log
/tmp/vcore-transport-resume-final-build.log. Gameplay remains unverified until a
later install/restart.
USER HOLD: the user is recording more routes. Do not install or restart now.
Current running server349322 and its installed executable remain untouched;
changes are source/build only. Existing uncommitted deck-return fallback work
is preserved. No DB/profile/plan/action changes are required for this fix.
Final complete build succeeded (/tmp/vcore-transport-resume-final-build.log).
Current server process349322 and installed executable timestamp21:00:04 were
verified unchanged. Six test results remain successful, including the added
land-tail check. Ready for later installation; user recording hold remains in
force. No pending build processes or native gameplay verification.
## Deployment after recording hold release
User authorized installation/restart. Installed prepared build successfully,
log /tmp/vcore-transport-resume-install.log. Fresh PID356681 undergdb/tmux,
console /tmp/vcore-transport-resume-start-20261029.log. Freshready12s, process
persistence and8085/8087 verified. Known old OutdoorPvP shutdown destructor
fault captured before full old process exit. Source/config/DB untouched by deploy.
Native login140 requested to inspect Shanron. Core transport restoration failed
for savedGUID2 and moved him to homebind before controller activation. He then
arrived at restlocation poi_guard_111 and executed rest micros. No manual position,
profile or dayplan override. The new resumed-attachment branch could not be live
validated on him because actual attachment was absent; automated tests remain
the verification of that branch. Core missing-transport restoration is separate.
Earlier no-install notes above record the completed hold, not a current ban.
@@ -0,0 +1,54 @@
-- Empty Vashjir spells and the live-failing Orgrimmar Tol Barad portal
-- cast as their visitor. Other casting modes/conditions/destinations stay.
UPDATE gameobject_template SET data6=1
WHERE entry IN(207690,207691,206595) AND type=22 AND data2=0 AND data6=0;
-- Two pre-existing server-only Vashj'ir spells had NO effects or destinations.
-- Supply a direct native teleport to the project's existing SmugglersScar
-- game_tele landing (existing named cave destination). This is an explicit fixed landing,
-- not a reconstruction of quest-dependent retail destinations.
INSERT IGNORE INTO spelleffect_dbc
(Id,Effect,EffectImplicitTargetA,EffectImplicitTargetB,SpellID,EffectIndex,Comment)
SELECT 160117,5,25,17,90244,0,'Orgrimmar Vashjir portal: fixed SmugglersScar landing'
WHERE NOT EXISTS(SELECT 1 FROM spelleffect_dbc WHERE SpellID=90244 AND EffectIndex=0);
INSERT IGNORE INTO spelleffect_dbc
(Id,Effect,EffectImplicitTargetA,EffectImplicitTargetB,SpellID,EffectIndex,Comment)
SELECT 160118,5,25,17,90245,0,'Stormwind Vashjir portal: fixed SmugglersScar landing'
WHERE NOT EXISTS(SELECT 1 FROM spelleffect_dbc WHERE SpellID=90245 AND EffectIndex=0);
INSERT IGNORE INTO spell_target_position
(ID,EffectIndex,MapID,PositionX,PositionY,PositionZ,Orientation,VerifiedBuild)
SELECT s.spell,0,t.map,t.position_x,t.position_y,t.position_z,t.orientation,0
FROM (SELECT 90244 AS spell UNION ALL SELECT 90245) s
JOIN game_tele t ON t.name='SmugglersScar';
-- Source faction permission is independent of destination region permission.
-- In particular Vashjir stays forbidden by the unchanged area catalog.
INSERT INTO vcore_clientless_node_access(node_key,allowed_factions,notes)
SELECT CONCAT('portal_go_',g.guid,'_source'),IF(g.map=0,1,2),'Capital Cataclysm portal; destination area policy still applies'
FROM gameobject g
WHERE g.guid IN(200624,200625,200626,200627,220365,220366,211257,211258,211259,211260,211264,219028)
ON DUPLICATE KEY UPDATE allowed_factions=VALUES(allowed_factions),notes=VALUES(notes);
-- Persist the same seed-only native bindings produced by portal object import.
-- Existing enabled flags and manually adjusted graph positions are preserved.
INSERT IGNORE INTO vcore_clientless_world_point
(point_key,category,map,position_x,position_y,position_z,orientation,enabled,comment)
SELECT CONCAT('portal_go_',g.guid,'_source'),'travel_portal',g.map,g.position_x,g.position_y,g.position_z,g.orientation,1,'Capital Cataclysm native portal source'
FROM gameobject g WHERE g.guid IN(200624,200625,200626,200627,220365,220366,211257,211258,211259,211260,211264,219028);
INSERT IGNORE INTO vcore_clientless_world_point
(point_key,category,map,position_x,position_y,position_z,orientation,enabled,comment)
SELECT CONCAT('portal_go_',g.guid,'_target'),'travel_portal',p.MapID,p.PositionX,p.PositionY,p.PositionZ,p.Orientation,1,'Capital Cataclysm native portal target'
FROM gameobject g JOIN gameobject_template t ON t.entry=g.id JOIN spell_target_position p ON p.ID=t.data0 AND p.EffectIndex=0
WHERE g.guid IN(200624,200625,200626,200627,220365,220366,211257,211258,211259,211260,211264,219028);
INSERT IGNORE INTO vcore_clientless_travel_node(node_key,node_type,world_point_key,enabled,comment)
SELECT p.point_key,'portal',p.point_key,1,'Capital Cataclysm native portal'
FROM vcore_clientless_world_point p
WHERE p.point_key REGEXP '^portal_go_(200624|200625|200626|200627|220365|220366|211257|211258|211259|211260|211264|219028)_(source|target)$';
INSERT IGNORE INTO vcore_clientless_travel_edge(from_node_key,to_node_key,edge_type,cost,bidirectional,enabled,comment)
SELECT CONCAT('portal_go_',g.guid,'_source'),CONCAT('portal_go_',g.guid,'_target'),'portal_use',1,0,1,'Capital Cataclysm native portal click'
FROM gameobject g WHERE g.guid IN(200624,200625,200626,200627,220365,220366,211257,211258,211259,211260,211264,219028);
INSERT IGNORE INTO vcore_clientless_portal_use(edge_id,gameobject_spawn_id,gameobject_entry,spell_id)
SELECT e.id,g.guid,g.id,t.data0
FROM gameobject g JOIN gameobject_template t ON t.entry=g.id
JOIN vcore_clientless_travel_edge e ON e.from_node_key=CONCAT('portal_go_',g.guid,'_source') AND e.to_node_key=CONCAT('portal_go_',g.guid,'_target') AND e.edge_type='portal_use'
WHERE g.guid IN(200624,200625,200626,200627,220365,220366,211257,211258,211259,211260,211264,219028);
@@ -4,6 +4,7 @@
#include "ClientlessPlayerMgr.h"
#include "ClientlessActionExecutorMgr.h"
#include "ClientlessTravelMgr.h"
#include "ClientlessTransportMgr.h"
#include "AdventureBehaviorMgr.h"
#include "AdventureCombatMgr.h"
#include "AdventureInteractionMgr.h"
@@ -235,6 +236,9 @@ public:
sClientlessLuaEngine->UpdateWorldPresence(player,sAdventureBehaviorMgr->InteractionIntent(player));
sAdventureGroupTravelMgr->Observe(player,diff);
sAdventurePopulationMgr->Maintain(player,diff);
// Restart restores native ship attachment, but CPC ride execution is
// transient. Own its safe recorded exit before starting another action.
if(sClientlessTransportMgr->ResumePassenger(player))return;
if(sAdventureGroupTravelMgr->Airborne(player)){sAdventureGroupTravelMgr->Follow(player);return;}
if(sAdventureSupplyMgr->Update(player,diff,sAdventureCombatMgr->Busy(player)))return;
if(!sAdventureCombatMgr->Update(player,diff) && !sAdventureInteractionMgr->Update(player,diff))
@@ -10,6 +10,9 @@
#include "MoveSplineInit.h"
#include "Chat.h"
#include "Log.h"
#include "VehicleDefines.h"
#include "WorldPacket.h"
#include "Opcodes.h"
#include <algorithm>
#include <cmath>
#include <unordered_map>
@@ -22,7 +25,7 @@ std::unordered_map<uint32,Model> Models;
struct Passenger
{
ObjectGuid Transport;
bool Tried=false,Moving=false,Reserved=false;
bool Tried=false,Moving=false,Reserved=false,Returning=false,ReturnFailed=false;
Point Target{};
time_t Deadline=0;
};
@@ -61,18 +64,46 @@ Model* Get(uint32 entry)
TC_LOG_INFO("server.adventurebot.navigation","DECK_MESH entry=%u polygons=%u error='%s'",entry,model.Mesh.Polygons(),model.Error.c_str());
return &model;
}
void Launch(Player* player,Passenger& state,std::vector<Point> const& path)
bool Launch(Player* player,Passenger& state,std::vector<Point> const& path)
{
Movement::PointsArray points;
// Already in transport coordinates; never transform these a second time.
for(auto const& p:path)points.emplace_back(p[0],p[1],p[2]);
Movement::MoveSplineInit init(player);init.DisableTransportPathTransformations();
init.MovebyPath(points);init.SetWalk(true);init.SetVelocity(2.0f);
player->GetMotionMaster()->LaunchMoveSpline(std::move(init));
if(init.Launch()<=0)
{state.Moving=false;return false;}
state.Moving=true;state.Target=path.back();
state.Deadline=GameTime::GetGameTime()+time_t(std::ceil(Length(path)/2.0f))+5;
TC_LOG_INFO("server.adventurebot.navigation","DECK_WALK guid=%u points=%u length=%.2f target=(%.3f %.3f %.3f)",
player->GetGUID().GetCounter(),uint32(path.size()),Length(path),state.Target[0],state.Target[1],state.Target[2]);
return true;
}
bool ReturnToRecordedStand(Player* player,GameObject* object,Position const& anchor,uint32 exitBudget)
{
TransportDockState dock;
auto transport=object->ToTransportBase();
if(!transport || player->GetTransport()!=transport || !player->IsAlive() || player->IsInCombat() ||
player->IsBeingTeleported() || player->GetMap()!=object->GetMap() ||
!object->GetTransportDockState(dock) || !dock.Stopped || uint64(dock.RemainingPause)<uint64(exitBudget)+1000)return false;
float x=anchor.GetPositionX(),y=anchor.GetPositionY(),z=anchor.GetPositionZ(),o=anchor.GetOrientation();
if(!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) ||
std::fabs(x)>100 || std::fabs(y)>100 || std::fabs(z)>100)return false;
transport->CalculatePassengerPosition(x,y,z,&o);
if(!Trinity::IsValidMapCoord(x,y,z,o))return false;
player->StopMoving();
// Preserve the real passenger attachment and its transport GUID. Change
// only its local position, then use native passenger relocation/broadcast.
player->m_movementInfo.transport.pos.Relocate(anchor);
transport->UpdatePassengerPosition(object->GetMap(),player,x,y,z,o,false);
player->m_movementInfo.guid=player->GetGUID();
player->m_movementInfo.pos.Relocate(player->GetPosition());
player->m_movementInfo.time=GameTime::GetGameTimeMS();
WorldPacket update(SMSG_MOVE_UPDATE);player->WriteMovementInfo(update);
player->SendMessageToSet(&update,true);
TC_LOG_WARN("server.adventurebot.navigation","DECK_RETURN_FALLBACK guid=%u entry=%u local=(%.3f %.3f %.3f)",
player->GetGUID().GetCounter(),object->GetEntry(),anchor.GetPositionX(),anchor.GetPositionY(),anchor.GetPositionZ());
return true;
}
}
void ClientlessTransportDeckMgr::Clear(uint32 guid){Passengers.erase(guid);}
@@ -87,28 +118,45 @@ bool ClientlessTransportDeckMgr::Update(Player* player,GameObject* object,Positi
Point current=Local(player);
if(!stopped || !player->IsAlive() || player->IsInCombat())
{
if(state.Moving){player->StopMoving();state.Moving=false;state.Reserved=false;}
if(state.Moving){player->StopMoving();state.ReturnFailed|=state.Returning;state.Moving=false;state.Reserved=false;}
return false;
}
if(state.Moving)
{
// Every active walk retains a reserve before departure, even after a lag spike.
if(dock.RemainingPause<1500 || GameTime::GetGameTime()>state.Deadline)
{player->StopMoving();state.Moving=false;state.Reserved=false;return false;}
if(player->IsSplineEnabled())return false;
{
player->StopMoving();state.Moving=false;state.Reserved=false;
state.ReturnFailed|=state.Returning;
if(!state.Returning)return false;
}
else if(player->IsSplineEnabled())return false;
state.Moving=false;
if(Distance(current,state.Target)>0.8f){state.Reserved=false;return false;}
if(Distance(current,state.Target)>0.8f)
{state.Reserved=false;state.ReturnFailed|=state.Returning;if(!state.Returning)return false;}
}
Point anchor{exitAnchor.GetPositionX(),exitAnchor.GetPositionY(),exitAnchor.GetPositionZ()};
if(arriving)
{
state.Reserved=false;
if(Distance(current,anchor)<=0.65f)return dock.RemainingPause>=exitBudget;
if(Distance(current,anchor)<=0.65f){state.ReturnFailed=false;return dock.RemainingPause>=exitBudget;}
if(state.ReturnFailed)
{
if(!ReturnToRecordedStand(player,object,exitAnchor,exitBudget))return false;
state.Moving=state.Returning=state.ReturnFailed=false;return true;
}
std::vector<Point> path;
if(!model->Mesh.Path(current,anchor,path))return false; // remain attached, never cut across the deck
if(!model->Mesh.Path(current,anchor,path) || path.size()<2)
{
state.ReturnFailed=true;
if(!ReturnToRecordedStand(player,object,exitAnchor,exitBudget))return false;
state.ReturnFailed=false;return true;
}
uint64 budget=uint64(std::ceil(Length(path)/2.0f)*1000)+exitBudget+2000;
if(path.size()<2 || dock.RemainingPause<budget)return false;
Launch(player,state,path);return false;
if(dock.RemainingPause<budget){state.ReturnFailed=true;return false;}
state.Returning=true;
if(!Launch(player,state,path))state.ReturnFailed=true;
return false;
}
if(state.Tried)return false;
state.Tried=true;
@@ -141,7 +189,7 @@ bool ClientlessTransportDeckMgr::Update(Player* player,GameObject* object,Positi
if(clearance>=1.5f && score>bestScore){bestScore=score;best=std::move(path);}
}
if(!best.empty())
{Launch(player,state,best);state.Reserved=true;}
{state.Returning=false;if(Launch(player,state,best))state.Reserved=true;}
return false;
}
@@ -415,6 +415,56 @@ bool ClientlessTransportMgr::Start(Player* player, std::string const& from, std:
return true;
}
bool ClientlessTransportMgr::ResumePassenger(Player* player)
{
if(!player || !player->IsInWorld() || !sClientlessPlayerMgr->IsManaged(player->GetGUID().GetCounter()) ||
!player->IsAlive() || player->IsInCombat() || player->IsBeingTeleported() || player->IsInFlight())return false;
uint32 guid=player->GetGUID().GetCounter();auto existing=_rides.find(guid);
if(existing!=_rides.end() && existing->second.Status.Phase!=ClientlessTransportPhase::Arrived &&
existing->second.Status.Phase!=ClientlessTransportPhase::Failed)
return existing->second.Resumed; // never replace a normal executor-owned ride
if(!player->GetTransport() || !EnsureLoaded() || !sClientlessTravelGraphMgr->EnsureLoaded())return false;
ClientlessTransportStop const* source=nullptr;
for(auto const& entry:_stops)
{
auto const& stop=entry.second;
auto object=FindObject(player,stop);
auto node=sClientlessTravelGraphMgr->FindNode(stop.Node);
if(!object || object->ToTransportBase()!=player->GetTransport() || !node ||
!sAdventureAreaMgr->AllowsNode(player,*node))continue;
if(!source || stop.Node<source->Node)source=&stop;
}
if(!source)return false; // unknown vehicles and forbidden stops retain their owner
Ride ride;ride.From=ride.To=*source;ride.Resumed=true;
ride.Status.Phase=ClientlessTransportPhase::Riding;
ride.Status.Detail="restored passenger; waiting for a permitted recorded dock";
ClientlessTransportDeckMgr::Clear(guid);
_rides[guid]=std::move(ride);
TC_LOG_INFO("server.adventurebot.navigation","TRANSPORT_RESUME guid=%u spawn=%u entry=%u",guid,source->Spawn,source->Entry);
return true;
}
bool ClientlessTransportMgr::SelectResumedDock(Player* player,GameObject* object,Ride& ride)
{
if(!ride.Resumed || !object || player->GetTransport()!=object->ToTransportBase())return false;
ClientlessTransportStop const* chosen=nullptr;
for(auto const& entry:_stops)
{
auto const& stop=entry.second;
auto node=sClientlessTravelGraphMgr->FindNode(stop.Node);
if(!SameService(ride.From,stop) || !node || !sAdventureAreaMgr->AllowsNode(player,*node) ||
!AtDock(player,object,stop,0))continue;
if(!chosen || stop.Node<chosen->Node)chosen=&stop;
}
if(!chosen)return false;
if(ride.From.Node!=chosen->Node || ride.To.Node!=chosen->Node)
{
TC_LOG_INFO("server.adventurebot.navigation","TRANSPORT_RESUME_DOCK guid=%u node='%s'",player->GetGUID().GetCounter(),chosen->Node.c_str());
ride.From=ride.To=*chosen;
}
return true;
}
ClientlessTransportStatus const* ClientlessTransportMgr::GetStatus(uint32 guid) const
{
auto itr = _rides.find(guid);
@@ -524,9 +574,10 @@ void ClientlessTransportMgr::UpdateRide(Player* player, Ride& ride, uint32 diff)
}
fail("transport passenger identity changed; attachment retained");return;
}
bool permittedArrival=!ride.Resumed || SelectResumedDock(player,object,ride);
ClientlessTransportStop const* arrival = nullptr;
if (AtDock(player, object, ride.To, 0)) arrival = &ride.To;
else if (ride.Abort && AtDock(player, object, ride.From, 0)) arrival = &ride.From;
if (permittedArrival && AtDock(player, object, ride.To, 0)) arrival = &ride.To;
else if (!ride.Resumed && ride.Abort && AtDock(player, object, ride.From, 0)) arrival = &ride.From;
auto const& exit = arrival ? *arrival : ride.From;
uint32 exitBudget=uint32(PathLength(exit)/std::max(1.0f,player->GetSpeed(MOVE_RUN))*1000)+uint32(exit.Points.size())*100+1500;
bool ready=ClientlessTransportDeckMgr::Update(player,object,exit.Points.back().Pos,arrival!=nullptr,exitBudget);
@@ -54,6 +54,8 @@ public:
bool ArrivalNode(Player* player, std::string const& from, std::string& to);
ClientlessTransportStop const* GetStop(std::string const& node);
bool Start(Player* player, std::string const& from, std::string const& to, std::string& error);
// Returns true while a restored passenger is owned by its resumed exit.
bool ResumePassenger(Player* player);
ClientlessTransportStatus const* GetStatus(uint32 guid) const;
void Cancel(uint32 guid);
void Update(uint32 diff);
@@ -81,6 +83,7 @@ private:
uint32 PointTimeout = 0;
bool Moving = false;
bool Abort = false;
bool Resumed = false;
uint32 RecoveryMs = 0, Recoveries = 0;
bool RecoveryMoving = false;
uint8 WaitStage=0; // 0 node, 1 walking out, 2 waiting, 3 returning
@@ -92,6 +95,7 @@ private:
bool Save(Player* player, std::string const& stand, std::string& error);
void UpdateRide(Player* player, Ride& ride, uint32 diff);
void RecoverPassenger(Player* player, Ride& ride, uint32 diff);
bool SelectResumedDock(Player* player, GameObject* object, Ride& ride);
void SetPhase(Player* player, Ride& ride, ClientlessTransportPhase phase, char const* detail);
GameObject* FindObject(Player* player, ClientlessTransportStop const& stop) const;
bool AtDock(Player* player, GameObject* object, ClientlessTransportStop const& stop, uint32 requiredPause) const;
+40 -3
View File
@@ -97,9 +97,10 @@ public:
float GetSpeed(int)const{return 7;}void StopMoving(){}bool IsSplineEnabled()const{return false;}
Motion* GetMotionMaster(){return &motion;}Session* GetSession(){return &session;}
};
struct Node{} node;
struct Graph{Node* FindNode(std::string const&){return &node;}}graph;auto sClientlessTravelGraphMgr=&graph;
struct AreaAccess{bool allowed=true;bool Applies(Player*){return true;}bool AllowsNode(Player*,Node const&){return allowed;}}areas;auto sAdventureAreaMgr=&areas;
struct Node{std::string Key;} node;
struct Graph{unsigned loads=0;bool EnsureLoaded(){++loads;return true;}Node* FindNode(std::string const&key){node.Key=key;return &node;}}graph;auto sClientlessTravelGraphMgr=&graph;
struct Core{bool managed=true;bool IsManaged(unsigned){return managed;}}clientless;auto sClientlessPlayerMgr=&clientless;
struct AreaAccess{std::string denied;bool allowed=true;bool Applies(Player*){return true;}bool AllowsNode(Player*,Node const&n){return allowed&&n.Key!=denied;}}areas;auto sAdventureAreaMgr=&areas;
namespace ClientlessTransportDeckMgr {bool ready=true;int calls=0;void Clear(uint32){} bool Update(Player*,GameObject*,Position const&,bool,uint32){++calls;return ready;}}
namespace ClientlessPositioning{bool available=false;bool Nearby(Player*p,Position const&,float,Position&spot,std::vector<Position>&path){if(!available)return false;spot=*p;spot.x+=2;path={*p,spot};return true;}}
void TransportBase::CalculatePassengerPosition(float&x,float&y,float&z,float*o)const{CalculatePassengerPosition(x,y,z,o,go->x,go->y,go->z,go->o);}
@@ -130,6 +131,7 @@ helpers = '\n'.join(function(source, marker) for marker in [
' float Distance(', ' bool SameService(', ' bool LoadNode(', ' Position WorldPoint(', ' float PathLength('
])
methods = '\n'.join(function(source, marker) for marker in [
'bool ClientlessTransportMgr::ResumePassenger(', 'bool ClientlessTransportMgr::SelectResumedDock(',
'bool ClientlessTransportMgr::HasConnection(', 'bool ClientlessTransportMgr::AtDock(',
'bool ClientlessTransportMgr::Sample(', 'bool ClientlessTransportMgr::Start(',
'void ClientlessTransportMgr::SetPhase(', 'void ClientlessTransportMgr::Cancel(',
@@ -243,6 +245,41 @@ int main(){
ClientlessPositioning::available=false;
Player denied;denied.object=&dock;areas.allowed=false;
assert(!mgr.Start(&denied,"A","B",error));areas.allowed=true;
// Restored attachment without transient CPC state is adopted, never reboarded.
ClientlessTransportMgr resumed;resumed._stops["A"]=stop("A",5,0);resumed._stops["B"]=stop("B",100,104);
Player orphan;GameObject vessel;vessel.x=50;vessel.state.Stopped=false;orphan.object=&vessel;orphan.x=51;
vessel.base.AddPassenger(&orphan);orphan.m_movementInfo.transport.pos.x=1;
assert(resumed.ResumePassenger(&orphan));auto& adopted=resumed._rides.at(26);
assert(adopted.Resumed&&adopted.Status.Phase==Phase::Riding&&orphan.teleports==0&&graph.loads>0);
run(resumed,orphan,adopted);assert(orphan.transport==&vessel.base&&adopted.Status.Phase==Phase::Riding);
// B is the first actual permitted stop, even though seed A was selected.
vessel.x=100;vessel.state.Stopped=true;orphan.x=101;
resumed.UpdateRide(&orphan,adopted,100);assert(adopted.To.Node=="B");
assert(resumed.ResumePassenger(&orphan));run(resumed,orphan,adopted);
assert(adopted.Status.Phase==Phase::Arrived&&!orphan.transport&&orphan.x==104);
assert(!resumed.ResumePassenger(&orphan));
// After detachment the remaining land transition still owns the bot until done.
adopted.Status.Phase=Phase::Leaving;assert(resumed.ResumePassenger(&orphan));
adopted.Status.Phase=Phase::Arrived;assert(!resumed.ResumePassenger(&orphan));
// Existing normal rides are never replaced by passenger adoption.
vessel.x=5;orphan.x=0;assert(resumed.Start(&orphan,"A","B",error));run(resumed,orphan,resumed._rides.at(26));
assert(!resumed.ResumePassenger(&orphan)&&!resumed._rides.at(26).Resumed);
// Unknown vehicles, unmanaged players and entirely forbidden services are untouched.
resumed._rides.clear();clientless.managed=false;assert(!resumed.ResumePassenger(&orphan));clientless.managed=true;
vessel.spawn=99;assert(!resumed.ResumePassenger(&orphan));vessel.spawn=1;
areas.allowed=false;assert(!resumed.ResumePassenger(&orphan));areas.allowed=true;
// An adopted passenger must not leave at a subsequently forbidden dock.
vessel.x=50;orphan.x=51;assert(resumed.ResumePassenger(&orphan));areas.denied="B";
vessel.x=100;orphan.x=101;run(resumed,orphan,resumed._rides.at(26));assert(orphan.transport);
areas.denied.clear();run(resumed,orphan,resumed._rides.at(26));assert(!orphan.transport&&orphan.x==104);
// Native cross-map ship restoration keeps the same service after worldport ACK.
resumed._rides.clear();resumed._stops["A"].Map=1;
GameObject copy1;copy1.map=1;copy1.x=50;copy1.state.Stopped=false;
orphan.map=1;orphan.object=&copy1;copy1.base.AddPassenger(&orphan);orphan.x=51;
assert(resumed.ResumePassenger(&orphan));run(resumed,orphan,resumed._rides.at(26));assert(orphan.transport);
copy1.base.RemovePassenger(&orphan);orphan.map=0;orphan.object=&vessel;vessel.x=100;
vessel.base.AddPassenger(&orphan);orphan.x=101;orphan.m_movementInfo.transport.pos.x=1;
run(resumed,orphan,resumed._rides.at(26));assert(!orphan.transport&&orphan.map==0&&orphan.x==104);
std::cout<<"Transport dock, frame, recorder, cross-map copy and ride regressions passed\n";
}
'''
@@ -25,7 +25,7 @@ struct Player{Point current{0,0,0};unsigned id=1;bool spline=false,alive=true,co
Guid GetGUID(){return {id};}bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsSplineEnabled(){return spline;}
void StopMoving(){++stops;spline=false;}void* GetTransport(){return transport;}void GetPlayerListInGrid(std::list<Player*>&,int){} void GetCreatureListWithEntryInGrid(std::list<Creature*>&,int,int){}};
struct Creature:Player{};
struct Passenger{unsigned Transport=0;bool Tried=false,Moving=false,Reserved=false;Point Target{};time_t Deadline=0;};
struct Passenger{unsigned Transport=0;bool Tried=false,Moving=false,Reserved=false,Returning=false,ReturnFailed=false;Point Target{};time_t Deadline=0;};
std::unordered_map<unsigned,Passenger> Passengers;
float Distance(Point const&a,Point const&b){return std::sqrt(std::pow(a[0]-b[0],2)+std::pow(a[1]-b[1],2)+std::pow(a[2]-b[2],2));}
Point Local(Player*p){return p->current;}float Length(std::vector<Point>const&path){float n=0;for(unsigned i=1;i<path.size();++i)n+=Distance(path[i-1],path[i]);return n;}
@@ -33,8 +33,12 @@ struct Mesh{bool blocked=false;unsigned Polygons(){return 50;}bool Path(Point co
struct Model{Mesh Mesh;}model;
Model* Get(unsigned entry){return entry==176310?&model:nullptr;}
namespace GameTime{time_t now=100;time_t GetGameTime(){return now;}}
int launches=0;
void Launch(Player*p,Passenger&s,std::vector<Point>const&path){++launches;p->spline=true;s.Moving=true;s.Target=path.back();s.Deadline=GameTime::now+20;}
int launches=0;bool launchOK=true;
bool Launch(Player*p,Passenger&s,std::vector<Point>const&path){++launches;if(!launchOK)return false;p->spline=true;s.Moving=true;s.Target=path.back();s.Deadline=GameTime::now+20;return true;}
unsigned fallback=0;
bool ReturnToRecordedStand(Player*p,GameObject*go,Position const&a,unsigned budget){
if(!go->dock.Stopped||go->dock.RemainingPause<budget+1000||!p->alive||p->combat||p->transport!=go)return false;
++fallback;p->current={a.x,a.y,a.z};return true;}
namespace ClientlessTransportDeckMgr{bool Update(Player*,GameObject*,Position const&,bool,uint32);}
'''+s[a:b]+r'''
int main(){Player p;Position anchor;
@@ -50,12 +54,20 @@ int main(){Player p;Position anchor;
// Arriving at the next dock starts a real return, provided it fits the halt.
p.current={4,0,0};object.dock.Stopped=true;object.dock.RemainingPause=6000;
assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==1);
object.dock.RemainingPause=60000;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==2);
Passengers[1].ReturnFailed=false;object.dock.RemainingPause=60000;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==2);
assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)); // still moving
p.spline=false;p.current={0,0,0};assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));
// Interrupted arrival never claims the exit anchor was reached.
p.current={4,0,0};model.Mesh.blocked=true;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==2);
model.Mesh.blocked=false;p.combat=true;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));p.combat=false;
// A failed arrival path may transfer to the exact recorded local anchor, preserving attachment.
p.current={4,0,0};model.Mesh.blocked=true;assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==2&&fallback==1&&p.transport==&object);
// Repeated interrupted return is bounded and uses the fallback at a stopped dock.
model.Mesh.blocked=false;p.current={4,0,0};Passengers[1].Moving=Passengers[1].Returning=true;
Passengers[1].Target={0,0,0};Passengers[1].Deadline=200;p.spline=false;
assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)&&fallback==2);
object.dock.Stopped=false;p.current={4,0,0};Passengers[1].ReturnFailed=true;
assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)&&fallback==2);
object.dock.Stopped=true;object.dock.RemainingPause=5500;
assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)&&fallback==2);
object.dock.RemainingPause=60000;p.combat=true;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));p.combat=false;
Passengers.clear();p.current={0,0,0};p.spline=false;launches=0;
ClientlessTransportDeckMgr::Update(&p,&object,anchor,false,5000);Point first=Passengers[1].Target;
Player other;other.id=2;ClientlessTransportDeckMgr::Update(&other,&object,anchor,false,5000);
@@ -63,6 +75,10 @@ int main(){Player p;Position anchor;
// Near departure, a running walk is stopped on the deck.
object.dock.RemainingPause=1000;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,false,5000));assert(!p.spline);
object.entry=999;assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));
Passengers.clear();object.entry=176310;object.dock.RemainingPause=60000;
p.current={4,0,0};p.spline=false;launchOK=false;auto before=fallback;
assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)&&Passengers[1].ReturnFailed);
assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)&&fallback==before+1);
std::cout<<"Production deck controller: halt budgets, departure interruption, return-before-exit, blocked paths and reservations passed\n";
}
'''
@@ -0,0 +1,54 @@
"""Run the native recorded-stand fallback with deterministic passenger relocation."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
s=(root/'src/server/game/World/ClientlessTransportDeckMgr.cpp').read_text();a=s.index('bool ReturnToRecordedStand(');b=s.index('\n}\nvoid ClientlessTransportDeckMgr::Clear',a)
code=r'''
#include <cassert>
#include <cmath>
using uint32=unsigned;using uint64=unsigned long long;
#define TC_LOG_WARN(...) ((void)0)
constexpr int SMSG_MOVE_UPDATE=1;
struct Position{float x=0,y=0,z=0,o=0;float GetPositionX()const{return x;}float GetPositionY()const{return y;}float GetPositionZ()const{return z;}float GetOrientation()const{return o;}void Relocate(Position const&p){*this=p;}};
struct Guid{unsigned GetCounter(){return 120;}};
struct WorldPacket{explicit WorldPacket(int){}};
namespace GameTime{unsigned GetGameTimeMS(){return 123;}}
namespace Trinity{bool IsValidMapCoord(float x,float y,float z,float o){return std::isfinite(x)&&std::isfinite(y)&&std::isfinite(z)&&std::isfinite(o)&&std::fabs(x)<17000;}}
struct Player;struct Transport{
unsigned relocations=0;void CalculatePassengerPosition(float&x,float&y,float&z,float*){x+=100;y+=200;z+=300;}
void UpdatePassengerPosition(int*,Player*,float,float,float,float,bool);
}transport;
int map;
struct Player:Position{bool alive=true,combat=false,teleport=false;int*world=&map;Transport*attached=&transport;unsigned stops=0,updates=0;
struct{Guid guid;Position pos;unsigned time=0;struct{Position pos;unsigned guid=42;}transport;}m_movementInfo;
bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleport;}
Transport*GetTransport(){return attached;}int*GetMap(){return world;}Guid GetGUID(){return {};}
void StopMoving(){++stops;}Position GetPosition(){return *this;}
void WriteMovementInfo(WorldPacket&){assert(m_movementInfo.transport.guid==42);}
void SendMessageToSet(WorldPacket*,bool){++updates;}
};
void Transport::UpdatePassengerPosition(int*,Player*p,float x,float y,float z,float o,bool){++relocations;p->x=x;p->y=y;p->z=z;p->o=o;}
struct TransportDockState{bool Stopped=true;unsigned RemainingPause=10000;};
struct GameObject{TransportDockState dock;bool valid=true;Transport*ToTransportBase(){return valid?&transport:nullptr;}int*GetMap(){return &map;}bool GetTransportDockState(TransportDockState&d){d=dock;return true;}unsigned GetEntry(){return 176081;}}object;
'''+s[a:b]+r'''
int main(){
Player p;Position anchor{2,-3,-10,0};
assert(ReturnToRecordedStand(&p,&object,anchor,5000));
assert(p.attached==&transport&&transport.relocations==1&&p.updates==1&&p.stops==1);
assert(p.m_movementInfo.transport.pos.x==2&&p.m_movementInfo.transport.pos.z==-10);
assert(p.x==102&&p.y==197&&p.z==290&&p.m_movementInfo.pos.x==102);
object.dock.Stopped=false;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));object.dock.Stopped=true;
object.dock.RemainingPause=5999;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));object.dock.RemainingPause=6000;
p.combat=true;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));p.combat=false;
p.alive=false;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));p.alive=true;
p.teleport=true;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));p.teleport=false;
p.attached=nullptr;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));p.attached=&transport;
anchor.x=101;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));anchor.x=NAN;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));
assert(transport.relocations==1&&p.updates==1); // rejected operations never move or detach
}
'''
with tempfile.TemporaryDirectory()as d:
p=Path(d);(p/'test.cpp').write_text(code)
subprocess.run(['c++','-std=c++17',str(p/'test.cpp'),'-o',str(p/'test')],check=True)
subprocess.run([str(p/'test')],check=True)
print('Native deck fallback: stopped-only, sufficient exit reserve, exact local anchor, preserved attachment, world transform and movement broadcast passed')