Commit Graph

96 Commits

Author SHA1 Message Date
Aaron Kimbrell
8a1d278265 fix: the zone keeps its paths after the merge, and the tests follow the merged readers
- Zone::LoadZoneIntoMemory takes the paths the zone file reader read (lost in the merge of the raw terrain loading)
- the terrain reader is main's unchanged: files from before version 32 keep their colour and texture maps as the file
  has them, and the dashboard's terrain tests expect that (bounds by the span between the first and last point too)
- the model component test counts the model block only: the item block in front of it is the ItemComponent's
- scene file tests: a file from before chunks keeps only its header, editor settings are a count of colours, and the
  tests that log have a logger
- the settings catalog lists export_terrain_to_obj

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 12:14:23 -06:00
Aaron Kimbrell
b466c08ee2 Merge origin/main into experimental: behaviors, raw terrain scene lookups, property model fixes, player count
Main's model behaviors (every rotation, DoDamage, OnTimer, OnStartup, MoveBackToStart, moves along the model's
local axes), raw terrain reading with scene lookups and scene tools, property model and brick by brick fixes and the
chat server's player count fix, merged onto this branch's structures:

- game messages stay structs; the angular velocity messages join them and the handler sets a message's target
- strips own their speed again and the block names, moves and rotations stay in the shared action table
  (PropertyBehaviorActions, which the dashboard's behavior player reads; it now lists rotations too)
- the zone keeps reading its file through ZoneFile and takes the boundaries from it, then loads the raw terrain as main
  does; the old dNavigation terrain reader is gone
- the show all response stays a struct and logs a player count mismatch as main does
- the property management component tells models who sent a chat message

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 10:57:45 -06:00
Aaron Kimbrell
0ff5d2bf87 fix: property model and brick by brick fixes (#2041)
* test: the server mock keeps a copy of the last bitstream sent

The bitstreams sent are usually on the sender's stack, so the pointer it kept was dangling once Send returned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: PlaceModelResponse writes the model's rotation

The client's PlaceModelResponse::Deserialize (0x00dc0170) reads the rotation as an optional w, x, y, z quaternion, but the server wrote the 4-byte response after the rotation flag, so with any rotation other than identity the client ran out of data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: naming a model no longer renames the property

The model info window sends UpdatePropertyOrModelForFilterCheck with isProperty false (client 0x00cc42e0); only the property editor sends it true (0x00c2e710). A model's name and description aren't stored yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep a model item's blueprint across saves

A brick built model item's config has blueprintid (lowercase), which the saved item XML didn't keep (only blueprintID), so a picked up model lost its blueprint on the next load and could not be placed again. It is now saved as bp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: write model behaviors only for property and inventory models

The client makes the mutable model component, which reads the behaviors and editing info, only when the object's config has propertyObjectID or inInventory (ObjectLoader2::LoadModelBehaviorsComponent); every other model gets the plain one, which reads only the model block. Writing the behaviors for every model made the client read them as the next component's data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the model vault uses the models' inventory groups, as the client

LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) and msgUpdateInventoryGroupContents (0x00ce1f50) look the inventory type up in m_VaultToPlayerInventory first, which maps VAULT_MODELS to MODELS. The server refused group updates for the model vault.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:34:31 -05:00
Aaron Kimbrell
c42a7ba387 fix(missions): a mission is only turned in when its rewards fit, as the client
LWOMissionComponent::MissionWindowOK (0x00d501f0) turns a mission in only
when CanReceiveAllRewards (msgCanReceiveAllRewards 0x00d17e20,
sub_00d17750) passes for the reward the player picked: the reward items
(the repeat ones once completed, each at least 1) must fit after the items
the tasks take (a gather task's target unless it keeps them, one of a
delivery task's target) with the first completion's bag rewards added.
The fit is LWOInventoryComponent_Common::CheckCanAddItems (0x00d53de0):
turned in items leave first (an item of 1 settles the whole count), a
unique item fails while one is held outside the buyback, then each item
tops up stacks of its LOT (stack size 0 is no limit, LookupItemInfo
0x00c1c190) and takes free slots.

MissionDialogueOK now refuses a turn-in that does not pass, which the
client never sends. The client's max currency part is not checked: the
server writes no cm, so the client has no limit either.
InventoryComponent::GetDefaultInventorySize is split out of GetInventory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:48:44 -05:00
Aaron Kimbrell
2fe489d518 fix(missions): accepting a repeat waits whole minutes of cooldown, as the client
The client's cooldown (LWOMissionComponent sub_00cd7770) counts whole
minutes since the last completion and has none when Missions.cooldownTime
is 0 or below. Accepting used seconds and treated only -1 as no cooldown,
so a cooldown of 0 still refused in the second of completion and other
negative values refused for a while. CanAccept now uses the same check as
the offers (MissionComponent::IsCooldownRunning).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:48:44 -05:00
Aaron Kimbrell
c1079fc20c fix(missions): prerequisites read as the client reads them
The client checks Missions.prereqMissionID in
LWOMissionComponent::getMissionState (0x00cd7400): text without any of
!&^;,|(): is one mission ID (met when 0 or ever completed); anything else
goes through its expression reader (sub_00a32980), left to right with no
precedence, & , ; and, | or, ^ xor, ! ~ not, ( ) groups, and/or skipping
the rest once the result is known. Each operand (sub_00c90a80): "id" is
met once the mission was ever completed; "id:state" when the client's
state mask of that mission equals state.

DLU read the text right to left ignoring groups, so
"(509|229)&(812|813|814|815)" (mission 781) was met by 509 alone; a plain
ID needed the mission's state to be 8, so a repeatable being repeated
blocked the missions after it (1870 after 1869); and id:state compared the
mission's own state. GetClientMissionState moves to MissionPrerequisites
so operands can ask for the mask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:48:44 -05:00
Aaron Kimbrell
529bacd077 feat(missions): keep the viewed flag of missions in progress, as the client
The client keeps a viewed flag on each mission in progress, sets it with
SetMissionViewed (LWOMissionComponent::msgSetMissionViewed 0x00bec7a0, only
for missions in progress) and reads it as v on the mission's <cur> entry
(LWOMissionComponent::LoadFromSaveData 0x00d171c0, a number or "true", false
when missing). The server now handles SetMissionViewed, saves v="1" for a
viewed mission in progress and loads it back; saves without v load as not
viewed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:48:43 -05:00
Aaron Kimbrell
02c96b08bd fix(inventory): nothing is used while stunned from using items, as the client
LWOInventoryComponent_Common::msgUseNonEquipmentItem (0x00cdeba0) asks
GetStunned first and uses nothing when canUseItem is false
(LWOControllablePhysComponent::msgGetStunned 0x00be0df0: stunned from
using items, or dead). The server now counts the use-item stun of every
SetStunned it sends for an entity as msgSetStunned (0x00bf1d10) does
(push/pop, never below 0, characters only, unless immune and not
bIgnoreImmunity), and checks it with IsDead before using an item. The
server never checked either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
a538f32382 fix(inventory): the charxml's items element has nn="1", as live
Live charxmls carried <items nn="1"> (225 of 226 captured).
LWOInventoryComponent_Common::LoadFromSaveData (0x00d1ec40) reads it and
then does not add the inventory component's template items to the loaded
character. No effect on players today (the player LOT's inventory
component has no items); the server never wrote it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
0b5a79cf96 fix(inventory): inventory groups keep their locked flag, as the client
LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) stores
the ADD's locked flag in the group; locked groups don't count toward the
50 user groups (msgGetInventoryGroupCount 0x00bde4b0 counts only unlocked
ones) and msgUpdateInventoryGroupContents (0x00ce1f50) applies the
1000-LOT cap only to unlocked groups. LoadFromSaveData (0x00d1ec40) reads
it from the grp element's u attribute. The server dropped the flag and
always saved u="0"; saves without u load as unlocked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
841004e6a4 fix(inventory): an inventory group holds at most 1000 LOTs, as the client
LWOInventoryComponent_Common::msgUpdateInventoryGroupContents (0x00ce1f50)
ignores an ADD to an unlocked group that already holds 1000 LOTs. The
server had no cap, so a modified client could grow the charxml without
limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
042594e3dd fix(inventory): UpdateInventoryGroupContents CLEAR empties the group, as the client
LWOInventoryComponent_Common::msgUpdateInventoryGroupContents (0x00ce1f50)
takes ADD, REMOVE and CLEAR; CLEAR removes every LOT from the group. The
server logged CLEAR as an invalid action and kept the LOTs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
a3d1b6a8b8 fix(inventory): the model vault uses the models' inventory groups, as the client
LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) and
msgUpdateInventoryGroupContents (0x00ce1f50) look the inventory type up in
m_VaultToPlayerInventory first, which Initialize (0x00d55a60) fills with
VAULT_MODELS -> MODELS. The server refused group updates for the model
vault.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
9afe8ccedb fix(inventory): inventory group names are cut to 64 characters, as the client
LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) resizes
the group name to 64 characters on ADD and MODIFY. The server kept names
of any length in the charxml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
0bc1a88e75 fix(inventory): adding an inventory group that exists does nothing, as the client
LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) ignores
an ADD whose group id is already in the inventory. The server added a
second group with the same id, saved to the charxml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
ba6228ef09 fix(inventory): MoveItemInInventory moves only where the item belongs, as the client
LWOInventoryComponent_Common::msgMoveItemInInventory (0x00d20590) looks the
item up in the inventory the message names and moves it only to the
inventory its LOT belongs in (GetInventoryTypeForLOT 0x00c3c4f0), or for
the vaults (VAULT_ITEMS, VAULT_MODELS) the LOT's vault type
(GetValueTypeForLOT 0x00c3c4a0). An item in the target slot swaps into the
moved item's old slot and inventory, and must belong there. The server
moved an item into any inventory destInvType named and left the swapped
item in the destination. Live clients never set destInvType (0 of 61 in
the captures).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
7366799152 fix(inventory): deleting skips equipped items, as the client
LWOInventoryComponent_Common::DoRemoveItemFromInventory (0x00d1fb50)
passes over equipped items when the loot source is LOOT_SOURCE_DELETION.
The server deleted the item while it was still worn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
9497d20027 fix(inventory): deleting more than the stack holds does nothing, as the client
LWOInventoryComponent_Common::msgRemoveItemFromInventory (0x00d203b0)
counts the item (GetInvItemCount) and does nothing when it holds fewer
than iStackCount; a request for an item with no count takes the whole
stack. The server cut the request down to the stack and deleted it, and
deleted nothing for a count of 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
5bf637b02e fix(inventory): the ridden object and the mount item can't be deleted, as the client
LWOInventoryComponent_Common::CanRemoveFromInventory (0x00ce0d20) refuses
the possessed object (GetPossessedObject) and the mount item
(GetMountInventoryID) before the operator bypass and the deletion
restrictions. The server let a player delete the mount they were riding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:55:06 -05:00
Aaron Kimbrell
66dca36b63 fix(vendor): dropping the oldest buyback item moves the rest up, as live
Live's 28th sale to a full buyback removed the oldest item, then sent
MoveItemInInventory (inventory 11, VENDOR_BUYBACK) for each of the 26 items
left, to slots 0 to 25 in order, before the sold item was added after them.
The server left the hole, so the sold item took the dropped item's slot
and the buyback page was no longer in sale order. The buyback inventory is
not saved and nothing changes hands; only slots move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:51:25 -05:00
Aaron Kimbrell
1ac1248161 fix(skill): cooldowns start as the client starts them
LWOSkillComponent::msgStartSkill (0x00d45cb0) starts no cooldown for cast
types 1 and 2, and inserts the cooldown (std::map insert), so a cooldown
group already running keeps its time left. The server started one for
every cast type and extended a running group to the longer cooldown.
Live client casts used cast types 0, 3 and 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:15:53 -05:00
Aaron Kimbrell
24932b1282 fix(skill): a dead caster's sync ends the behavior without running it, as the client
LWOSkillComponent::MsgSyncSkill (0x00d454b0) checks whether the caster is
dead before running a synced behavior; if so it only removes the behavior
handle from the skill's pending ones. The server ran the sync anyway, so a
player's delayed hit still landed after they died.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:15:53 -05:00
Aaron Kimbrell
c9929f1c1e fix(skill): a start with a skill handle still in progress is dropped, as the client
LWOSkillComponent::msgStartSkill (0x00d45cb0) logs and ignores a StartSkill
whose skill handle is still waiting on behavior syncs. The server ran it a
second time: the hits, the imagination cost and the mission progress twice.
Live captures hold 22 byte-identical StartSkill resends with the same
handle a few packets apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:15:53 -05:00
Aaron Kimbrell
4fbebf7135 fix(inventory): an opened package reports success in UseItemResult, as live
Item::UseNonEquip declared a second `success` inside the package branch, so
the UseItemResult it sent (and its log line) was always false, even when the
package opened and its contents were given. The client
(LWOInventoryComponent_Client::sub_00c1bb20 (0x00c1bb20)) plays the item's
audioEventUse on true and a failure sound on false; live sent true for 183
of the 187 UseItemResult in the captures.

Test: UseItemResultTests.AnOpenedPackageReportsSuccess.

Check in game: opening a package (e.g. a starter pack or a reward bag) plays
its open sound rather than the failure sound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:47:24 -05:00
Aaron Kimbrell
04e0c13678 fix(mission offer): offer the first mission of the giver's list, as the client lists them
LWOMissionOfferComponent builds a player's missions for a giver
(0x00c605f0, one entry 0x00bebf30) in priority order: 0 a mission to turn
in here, 5 one in progress, 10 a new one, 15 a repeat. A ready mission is
only listed where MissionNPCComponent.acceptsMission is set, a new one
only where offersMission is set and its cooldown has run out, a mission in
progress by every giver that has it. A random mission (0x00c5ffd0) lists
one of its pool that the giver has: one to turn in first, then one in
progress, else a random one. Missions are kept by mission ID; equal
priorities keep that order.

DLU sent an OfferMission for every mission that qualified, including one
ready to turn in at a giver that does not accept it. In the live captures
every use got one mission's OfferMission pair. A use now offers the head
of the list, and a pick from the multi interaction menu the picked one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:43:08 -05:00
Aaron Kimbrell
0253fea006 feat(destroyable): enemy preconditions from the level, as the client
An object's EnemyPrecondition (level config) decides who it is an enemy of:
whoever asks and doesn't meet the preconditions, whatever the factions
(LWODestroyableComponent::msgIsEnemy 0x00c9e930; ',' ';' ':' join groups
that must all pass, '|' alternatives). Forbidden Valley Siege's objects use
it today; any level object can.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:31:35 -05:00
Aaron Kimbrell
4b84677cba fix(destroyable): resurrect only a dead player that isn't already resurrecting
RequestResurrect resurrected whoever sent it, so a living player could reset
their health and imagination, and repeated requests stacked respawns. The
client's destroyable resurrects only when smashed and not resurrecting
(LWODestroyableComponent::SendMessage 0x00d29e40); live, all 17 requests in
the captures came after the player's Die and before its Resurrect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:17:28 -05:00
Aaron Kimbrell
cf1e8a54c6 fix(destroyable): damage reductions add up, as the client
The damage reduction behavior set the reduction to its amount and back to 0
when it ended, so a second reduction replaced the first and ending either
removed both. The client's behavior sends AddDamageReduction
(DamageReductionBehavior::Cast) and the destroyable adds it to the current
reduction; removing takes off that amount, never below 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:11:20 -05:00
Aaron Kimbrell
6f4fd4054a feat(entity): the object's interaction distance, preconditions and world state, as the client
LWOGameObject loads these itself: interactionDistance from its Objects row
(16 without one) and the level's interaction_distance over it,
CheckPrecondition and objectWorldState from the level config, before its
components load theirs. The quickbuild, the pet and the race instancer read
them from the object; the race instancer now uses the Objects row's
distance like the client does, instead of a fixed 16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:44:53 -05:00
Aaron Kimbrell
6627b022d4 feat(component): the BBB component only where the client has one
The client makes a BBB component (76) for a character (DoObjectPreLoad, LOT 1)
and for registry entries (none in the CDClient), and reads it last
(LWOBBBComponent::Deserialize: a metadata-source-item bit and the item).
DLU wrote a placeholder 0 bit at the end of every construction; now only a
character's BBBComponent writes it. Every other object sends one bit less,
which the client never read (live non-player constructions decode without
it; the zone control object stays byte for byte equal to live).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:18:52 -05:00
Aaron Kimbrell
bff3fe6363 refactor(component): moving platform subcomponents like the client's
PlatformSubComponent (LWOPlatform: the state between waypoints and its
serialization) is the base of MoverSubComponent (4), SimpleMoverSubComponent
(5) and the new RotatorSubComponent (6), as LWOPlatformMover,
LWOPlatformSimpleMover and LWOPlatformRotator are built on LWOPlatform. The
platform keeps one subcomponent per type the client makes
(LWOMovingPlatformComponent::SetupPlatform: one per platformIs* setting that
is true) and sends them all; the first is the one the server runs. Same bytes
for every platform with one subcomponent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:59:45 -05:00
Aaron Kimbrell
2d7b5cca13 refactor(spawner): the spawner object's SpawnerComponent owns its spawner
The zone manager keeps an index of spawners by object ID; the spawner now
belongs to the SpawnerComponent of its LOT 176 object and goes with it (the
component tells the zone manager to stop indexing it). Spawning, timers and
the update order are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:31:18 -05:00
Aaron Kimbrell
7a0dba22a5 refactor(component): use is a RequestUse message handler
Components no longer have a virtual OnUse; each component that reacts to a
use registers a RequestUse handler, like the client's components. The entity
runs the INTERACT trigger and the script, then delivers a RequestUseEvent
(carrying the user) to the handlers. The mission choice of a multi-interact
use goes only to the mission offer, as before. Switches keep a direct
EnterIfResponding for touches and pets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 07:55:55 -05:00
Aaron Kimbrell
50bfa623ca refactor(component): the client's component lifecycle
Components get the client's per-component functions: LoadDataFromTemplate,
PrepareConfigData, LoadConfigData, LoadFromSaveData, Startup, Run,
OnEnterScope, OnLeaveScope, Preshutdown and Shutdown, called by the entity in
the client's order. Update is renamed Run, LoadFromXml LoadFromSaveData and
UpdateXml WriteSaveData. Nothing sent changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 07:45:16 -05:00
Aaron Kimbrell
cc337a298d fix(entity): models get their item component, as the client makes it
The client makes the registry's item component for models too (only a pet takes it away); DLU skipped it and the model component wrote the item block itself, for the 30 model LOTs without an item row as well, and with the object id as ug_id. Live (Raven Bluff property capture): the item block carries the userModelID for brick built models (LOT 14) and 0 for reward models (LOT 8069). 1,455 LOTs gain the item component; nothing else changes (LoaderSnapshotTest).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 06:27:36 -05:00
Aaron Kimbrell
6ff42bf312 fix(moving platform): a mover started at a waypoint waits there first (issue 964)
LWOPlatform::ResumeTravelling: not travelling at a waypoint it waits the waypoint's wait; the AG monument elevator rose at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 04:55:03 -05:00
Aaron Kimbrell
ed62a5850f test: a mover's construction writes its path bit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:23:25 -05:00
Aaron Kimbrell
8444effa81 test: SavingTest fails instead of crashing when its XML isn't next to it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:23:25 -05:00
Aaron Kimbrell
83f321bec6 feat(moving platform): the server runs movers as live did and answers resyncs with their state
The client's LWOPlatform logic (ArrivedAtWaypoint 0x00c86c30, ProcessStateChange 0x00cce380, GetNextWaypoint, the waypoint speeds) runs on the server: stopped at a waypoint (25 at an end) for its wait, then travelling (2), serialized on arrival and departure as in the NT mover captures; constructions carry the path and the mover state; RequestPlatformResync gets the current state, percent and position. The LOT 9483 timers are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:36:00 -05:00
Aaron Kimbrell
5fbd915a54 test(moving platform): subcomponent choice, a live simple mover construction, trip timing and rotaters
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:59:55 -05:00
Aaron Kimbrell
b5891cd61a test(capture): property model tests read with the merged decoder
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:32:00 -05:00
Aaron Kimbrell
6afcfed7ff Merge exp/property-replay: property models, info and behaviours in capture replays
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:54:20 -05:00
Aaron Kimbrell
045999a252 feat(capture): a property's models, data and events from a capture
CaptureProperty::Build reads, per world of a capture, the property it was:
each model (constructed with a model component, or a brick built model) with
its LOT, object, spawner, blueprint and behavior count and where it stood per
time span, from its constructions, serializations and destruction; the
DownloadPropertyData of that map and the GetModelsOnProperty counts; and the
events: placed (a PlaceModelResponse with the model made at that position,
before or after it), moved, removed (with the DeleteModelFromClient reason),
editing, and behavior messages.

CaptureTool property <bundle> --cdserver prints it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:40:09 -05:00
Aaron Kimbrell
e043a8f6b5 fix(capture): read compressed configs and placed models in replica packets
A compressed construction config is a u32 uncompressed size, a u32 compressed
size and the zlib bytes: the reader took the first size for the second and lost
its place, so live model constructions never read. It now inflates and reads
the entries.

A model's item component isn't made (Entity::Initialize): its model component
writes the item info. The layout left the registry's item component in, so a
premade model's components read one block twice and didn't match. Live writes
the same bits from its item component, so both now read exactly.

Tested with a live placed model's construction and the server's own model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:40:03 -05:00
Aaron Kimbrell
c01828a3bb Merge exp/decoder-live-check: replica decoders checked on live captures, moving platform/pet/model wire fixes 2026-09-30 08:40:02 -05:00
Aaron Kimbrell
ff4b4c7385 fix(capture): replica components read as the client reads them, checked on live captures
Every live capture decoded: 83 of 60,659 constructions and 5 of 609,919
serializations now don't read exactly (was about 1 in 10 constructions, and
the decode aborted on text that isn't UTF-8).

- trigger component (header trigger bit): a bit and the trigger ID, last
- BBB component on objects listing component 107 (characters)
- local space info, buff immunities, phantom physics distance, skills in
  progress, a choice build's setting
- moving platform: path when dirty, then subcomponents while a 1 bit comes
  before one (mover and simple mover)
- pet: names under the dirty bit, on updates too; no item or model component
- models: the plain model block, or the mutable one (behaviors, editing
  info) when the config has propertyObjectID or inInventory; the UGC block
  is the item's
- zone file variants: markedAsPhantom (phantom physics) and renderDisabled
  (no render data)
- compressed LDF: u32 uncompressed and compressed sizes, inflated and shown
  as entries
- narrow text that isn't UTF-8 is shown byte by byte, and the capture tool
  prints invalid text with replacements instead of aborting

Tests use live byte samples for each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:39:33 -05:00
Aaron Kimbrell
ebc2fd3abf fix(pet): write the names bit on updates too
The client reads the moderation and names bit whenever the pet component is
dirty, on updates as well as on construction (LWOPetComponent::Deserialize),
and live wrote a 0 there on updates. DLU wrote it only on construction, so
on an update the client took the next component's first bit (a combat AI's
dirty bit) for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:39:23 -05:00
Aaron Kimbrell
3956df8877 fix(moving platform): end the subcomponent list with a 0 bit
The client reads a moving platform's subcomponents while a 1 bit comes
before one (LWOMovingPlatformComponent::Deserialize), as live wrote them.
DLU wrote the mover without the closing 0 bit, so the client took the next
component's first bit for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:39:23 -05:00
Aaron Kimbrell
f6659d35f9 test(ai): pets at pet blockers, carver_only walls and hedge sizes
A pet chasing across a "PR - Pet Blocker" stops in front of it; a
carver_only navmesh carver added without an object stops an enemy and goes
away with the physics world; carver_only alone blocks nothing; a Robot City
hedge's blocker has the size and offset of its collision shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:49:19 -05:00
Aaron Kimbrell
e77535984d test(loot): item source keys and the quickbuild item cost's source
Covers the key names and name value types, a pickup from an object, from the
player and from an object that is gone, metrics after an item's config (and
not kept on the item), a removal's loot source, and a quickbuild taking its
item cost with itself as the source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:46:13 -05:00