The path tracer that imitated LU Toolbox's Remove Hidden Faces took 17 to 100
times as long as the renders from 42 directions around the model, too heavy to
use. It goes with its settings (hsr_method, hsr_samples, hsr_bounces,
hsr_sample_spacing, hsr_min_points), its side by side tracing for GPUs and its
tests. The renders (UgcRender::VisibleFromAround) remove hidden faces as before
that method existed; their size is hsr_resolution (1024), and the memory
estimate counts their buffers again.
The hidden-face method is no longer a processing option: the dashboard's picker,
/reprocessproperty and --make-model take only the ray backend (the occlusion's)
and denoising. Options stored before (made_options, process_options,
ugc_process_runs, --make-model arguments) that name toolbox or fast still
parse; the word is skipped.
Check: the settings page has no hsr_method or path settings and has
hsr_resolution; /reprocessproperty embree toolbox still works (toolbox
ignored); models made with the defaults keep their hashes (UGC tests).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Staff can make models again with other processing options than the UGC
settings' (ray backend, hidden-face method, denoising) and every make records
what made it, for comparing the options.
- migration mysql 99 / sqlite 82 (ugc_process_options): ugc.process_options
(picked for the next make, cleared once made), ugc.made_options (what made
the current files) and ugc_process_runs (every successful make: options,
wall and CPU time, hidden faces', occlusion's and icon's time, bricks,
triangles before and after)
- IUgc: ResetUgcModelProcessing and ResetPropertyUgcModelProcessing take the
options; PendingModel carries them; RecordUgcModelRun, GetUgcRunSummaries;
list entries have madeOptions and processOptions
- the UGC server applies a job's options over its settings and records the run
- /api/ugc/reprocess takes options ("embree fast oidn"); /api/ugc/options
lists the choices, the settings' defaults and averages per combination
- /reprocessproperty [builtin|embree|hiprt] [toolbox|fast] [off|oidn], any
order, all optional
Check: run the migration on MySQL and SQLite; /reprocessproperty embree fast
on a property, then the models' made_options and ugc_process_runs rows;
/api/ugc/options.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item::RemoveFromInventory ended with `delete this`, so every caller that still used the item afterwards (SetCount(0)
returning into its caller, loops that remove several items, proxies purged while their parent is handled) touched
freed memory. The inventory now takes the removed item and frees it at the inventory component's next update, or
with the inventory.
Check in game: sell, drop, delete, trade, mail and use up stacks (including the last of a stack); unequip and
remove an item set piece and a proxy-bearing item (rocket, modular car); donate items; nothing crashes and the
inventory shows the right counts after relogging.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client's TacArcBehavior::DoHit (0x00fb10c0) writes the closest max-targets ids from a set, so ascending and
each once, then the action data per id in that same order; DoUnserializeBS (0x00fb26a0) reads them the same way and
skips empty ids. The server handled the targets in list order and skipped ids whose object it could not find
without reading their action data, so every later target read the wrong bits (several pirates under a Doom Slicer).
Handle now reads the action for every listed id in ascending order, and the server's own casts write ids and actions
in ascending order after picking the closest targets.
TacArcBehavior::Cast (0x00fb2d10): a picked target that passes the filter gets the action with no TacArc data (the
server's own cast now calculates that action instead of handling it); otherwise the target is dropped, and an arc
measured from the target's position writes nothing.
Check in game: Doom Slicer and multi-target katanas on groups of pirates/admirals damage each of them; apes still
take damage during their stun; enemies with arc attacks (apes, Maelstrom horsemen) still hit players.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client's AreaOfEffectBehavior::Cast (0x004ec590) writes every target id, then runs and writes the action once
per unique target in ascending id order. The server ran it for every listed target in list order, so a target
listed twice (the caster with a magnet, Everlasting items' refill) was handled twice, and later targets' data was
read against the wrong target. Handle and the server's own Calculate now go through the unique ids in ascending
order. Check: Thumpin' Bass / Flowin' MC refill once; Shinobi charge with a magnet gives imagination once; area
attacks on several enemies still hit each of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When staff approve or reject a character name while the player is online, the player gets a private announcement
popup ("Name approved" / "Name not approved") as well as the chat line. Players who are offline still aren't told
at their next login. Check: approve and reject a pending name while its player is online.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Venture vision from an equipped item was sent while the character loaded, before the client's UI existed, so after a
login the minimap stayed empty until the item was equipped again (a world transfer keeps the UI, so it worked
there). The player's active venture vision effects are sent again once the player has loaded. Check: wear the
Venture Vision helmet, log out and back in: the minimap shows the icons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Members' clients read the chat text up to a terminating zero, as the client itself sends it; /g sent the text
without one, so the client read past it and showed extra characters ("test0>"). Check: guild chat shows exactly
what was typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The world side of guilds (docs/Guilds.md):
- TMP_GUILD_CREATE, which the client's guild create box sends to its world, goes to the chat server as GUILD_CREATE.
- The character component reads the character's guild from the database when it loads (instead of the old unused "gn"
and "gid" charxml attributes) and takes GUILD_GET_STATUS from the chat server; a change is serialized once (the
client redraws the name billboard every time it reads a guild name). A name waiting for moderation isn't shown.
- Slash commands (the client has none for guilds): /g and /guild (guild chat; the client's guild tab sends /g, which
goes through the chat filter and mute like zone chat, then to the chat server as channel 10), /guildcreate (opens the
create box with DisplayGuildCreateBox), /gkick, /grank <name> <officer|veteran|recruit>, /gleader and
/gdisband confirm.
- The Guild Master script (LOT 3001, L_GUILD_CREATE.lua: using it opens the create box). Live never placed it; GMs
can spawn it.
Check in game (two accounts, a client with a FeatureGating row "guilds", 1, 0, 0 in its cdclient.fdb): /guildcreate,
make a guild (the name shows under yours, the guild button appears on the status bar); invite the second character
from the guild window, accept, both lists show both; guild chat tab; /grank, /gkick, /gleader; leave from the window;
log the second character out and in (guildmate logged off/in lines).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Connects GuildManager to the chat server (ChatGuilds): the client's GUILD_INVITE, GUILD_INVITE_RESPONSE, GUILD_LEAVE
and GUILD_GET_ALL (routed through its world), the worlds' GUILD_CREATE, GUILD_KICK, GUILD_SET_RANK and GUILD_DISBAND,
guild chat (GENERAL_CHAT_MESSAGE in channel 10, sent to the online members as channel 10 private chat, which the
client's guild tab shows, and logged as "guild"), guildmates told when a member logs in, out or changes worlds, and a
member's pending invite dropped when they log off. Client packets go through the member's world as WorldRoutePacket;
GUILD_GET_STATUS goes to the world itself.
Guild names: the chat filter's deny list refuses a name (dChatFilter::HasDenyList, since without a deny list the deny
check refuses everything); a name the allow list doesn't cover waits for moderation, as pet names do.
The dashboard's new player action GUILD_CHANGED goes from master to the chat server only (answered 1 when chat is
connected), which catches online members up. chatconfig.ini: guild_max_members (100) and guild_invite_timeout (600
seconds).
Check in game: nothing yet on its own (the world side makes guilds reachable). Server log: the chat server starts and
logs no unhandled guild packets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guild packets of the 1.10.64 client (docs/Guilds.md) as Serialize/Deserialize structs, laid out the way the client's
packet handlers read them: GUILD_CREATE_RESPONSE, GUILD_INVITE, GUILD_INVITE_INITIAL_RESPONSE, _FINAL_RESPONSE,
_CONFIRM, GUILD_ADD_PLAYER, GUILD_REMOVE_PLAYER, GUILD_LOGIN_LOGOUT and GUILD_DATA (ClientPackets), the world packet
TMP_GUILD_CREATE the create box sends, the chat packets the client sends through its world (GUILD_INVITE,
GUILD_INVITE_RESPONSE, GUILD_LEAVE, GUILD_GET_ALL), DLU's world <-> chat packets (GUILD_CREATE, GUILD_KICK,
GUILD_GET_STATUS as the chat server's guild update to a world, and GUILD_SET_RANK and GUILD_DISBAND appended to
MessageType::Chat after CREATE_TEAM) and the game message DisplayGuildCreateBox (626). Fixed-size names always keep a
NUL, since the client reads them as C strings. Enums eGuildCreateResponse, eGuildInviteResponse,
eGuildInviteFinalResponse, eGuildRank and eGuildLeaveReason. Nothing sends them yet.
Check: GuildPacketsTests compares every packet with the bytes at the client's offsets. Nothing to check in game.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live wrote the item component's UGC info on every construction of an
object with an item component (99 of 99 constructions of LOT 14535, a
possessable with item, skill and controllable physics and no Objects row:
ug_id 0, moderation status NoStatus, no description). DLU wrote a 0 bit.
The item component is read after the skill and base-combat-AI components
(ComponentOrderVector::Initialize 0x0101f8e0; live constructions of LOT 14535
have Skill then Item). DLU wrote it before the inventory, which did not show while
it was a single 0 bit next to the skill's 0 bit, but would misalign every
component after it now that it writes more. It now comes after
BASE_COMBAT_AI. The ReplicaComponentOrderTests oracle moves the item the same
way (intended change).
Test: ReplicaConstructionTest.ItemConstructionWritesEmptyUgcInfo,
ReplicaComponentOrderTest.EveryListedComponent (oracle updated).
Check in game: with a second player watching, use a possessable / mount /
vehicle item (e.g. a racing car in the race lobby); it appears, moves and
is left normally for both. Rocket launches and property models look as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client reads an object's factions only from the DestructibleComponent
factionList (LWODestroyableComponent::LoadDataFromTemplate 0x00c9f900: one
atol per comma-separated token, -1 kept; the faction column is not read),
and live replicated that list: [-1] on 12,829 constructions (vendors,
quickbuilds, bouncers). DLU added the faction column and dropped -1,
so those objects were sent with no factions, and the four rows with faction 6
but factionList "-1" were sent as [6].
-1 has no Factions row, so it adds no friends or enemies.
Test: ReplicaConstructionTest.TemplateFactionMinusOneIsReplicated.
Check in game: vendors, quickbuilds, bouncers and NPCs still can't be
attacked; enemies still fight players and pets; smashables still smash.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live's simple-physics constructions (40,786 in the captures):
- Motion type Fixed (5) for objects without a motionType (38,970). DLU read
the level's motionType and, when there was none, sent 0, a value the
client's motion-type enum does not have.
- Keyframed (4) for every moving platform (1,059 of 1,059) and for models
without behaviors; Dynamic (1) for models with behaviors (655 of 666 model
constructions follow this; DLU only switched it when a behavior was added
or removed).
- No velocity for fixed objects (all 38,970); keyframed and dynamic objects
have one. DLU wrote a velocity in every construction.
A level-set motionType still wins.
Test: ReplicaConstructionTest.SimplePhysicsConstructionLikeLive (values of a
captured smashable, LOT 12266); the existing SimplePhysicsTest bytes are
unchanged.
Check in game: moving platforms (AG, GF, NS, NT), spinners and other
simple-physics objects move and collide as before; smashables and static
objects stay put; on a property, placed models with and without behaviors
sit and run as before, including after picking them up and placing again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live wrote the controllable-physics cheat block (gravity scale, speed
multiplier) in a construction only when one of them differed from 1: 216 of
20,617 constructions had it (players at run speed 1.05 / 1.5 / 2.0 / 0.5,
enemies with gravity 0), never 1 and 1. DLU wrote it in every construction.
Serializations are unchanged.
Test: ReplicaConstructionTest.ControllablePhysicsConstructionWritesCheatsOnlyWhenChanged.
Check in game: with a second player watching, use a speed boost (or /setspeed)
and zone in while boosted; the other player sees you move at the right
speed. Enemies with gravity 0 (e.g. Sentinel Turret 6254) behave as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Equipped items in the inventory component construction, as live wrote
them (49,534 equipped items in the captures):
- is_bound is the item's bound state (false for 2,990 items that bind
neither way); DLU always wrote true. NPC items are bound when the item
binds on pickup or on equip (2,640 of 2,640).
- inventory_type is the item's inventory, left out for ITEMS: TempEquip for
temporary equips and proxies (3,899), Model for models (232). DLU never
wrote it. DLU keeps player proxies in ITEM_SETS; they are sent as
TEMP_ITEMS, like live.
- An NPC item's slot is its slot in the inventory it would be in: models
count from 0 in MODELS and proxies from 0 in TEMP_ITEMS, so a proxy no
longer takes the next ITEMS slot (1,475 of 1,491 NPC constructions match;
DLU's numbering matched 1,352).
- equipped_model_transforms is an empty list on every construction (11,214
of 11,214); DLU wrote none.
Test: InventoryConstructionTest.NpcItemsLikeLive (NPCs 7426 and 13790 from
the captures), InventoryConstructionTest.PlayerItemsLikeLive.
Check in game: NPCs with gear (Numb Chuck in FV, the Ninjago ninjas, faction
vendors) show all their gear; your own and other players' equipped items,
proxies (ninja hoods, capes) and temporary equips (Maelstrom vacuum, quest
items) show normally after zoning and after equipping/unequipping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live wrote all three optional blocks of the character component in every
player construction (9,726 LOT 1 constructions: gm_pvp_info with is_gm
false and gm_level 0 for civilians, current_activity Some(None) on 9,620,
social_info always Some with guild 0 and an empty name). DLU wrote each one
only after something set its dirty flag, so civilians got none of them.
Serializations are unchanged (still only when dirty).
Writing the social block every time exposed two bugs in it: the guild ID
was never initialized (now 0), and the guild name was written with
sizeof(wchar_t) (4 on Linux) bits per character, reading past the end of
the UTF-16 string; it is now written as 16-bit characters.
Test: ReplicaConstructionTest.CharacterConstructionAlwaysWritesGmActivityAndSocialBlocks.
Check in game: log in as a civilian and as a GM, with a second player
watching; both see each other normally (name tags, GM tag/level for the
GM, no PvP flag), and starting a quickbuild or pet taming still shows the
activity to the other player.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent the milliseconds since the object was created on the server in
every construction header (nonzero in 142,258 of 145,677 captured
constructions; the same object constructed again later carries a larger
value by the time elapsed; the local player's own construction on load is
0). DLU always wrote 0. The client stores it in the object's config
(LWOGameObject::time_since_created_on_server).
Entities now remember when they were created (steady clock) and write
their age.
Test: ReplicaConstructionTest.TimeSinceCreatedOnServerIsTheObjectsAge.
Check in game: zone in and walk around several worlds; objects, NPCs,
enemies and other players appear and animate as before (no objects
popping, stuck or misplaced animations on platforms/spinners).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DownloadPropertyData sent the same zonePosition (548, 406, 178) and a
maxBuildHeight of 128 for every property. Both now come from the
property's PropertyTemplate row (zoneX/zoneY/zoneZ, maxBuildHeight), the
same row the template ID, vendor map and spawn name already come from.
A map with no row still sends the old values.
Live rows: Avant Gardens small (1150) keeps 548/406/178 and 128; Avant
Gardens medium (1151) is 428/413/82 with a build height of 256; the
Nimbus Station (1250, 1251), Gnarled Forest (1350) and Forbidden Valley
(1450) properties get their own positions with 128.
Check in game: on the medium Avant Gardens property (1151) models can be
placed higher than before (up to 256), and on the small one (1150) the
limit is unchanged; on the other worlds' properties the property screens
(plaque, news/claim UI) still show and building works as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent the launchpad's FireEventClientSide("RocketEquipped") to every
client in the zone: 1,196 of 1,265 captured were other players'
launches. On each client it starts that player's launch
(LWORocketLaunchComponentCommon::msgFireEventClientSide sends
BeginLaunch to the sender), so others see the rocket take off; DLU sent
it only to the launcher. param1 is the property clone for property
launches (7 of 7 equal the clone in the following TransferToZone) and
param2 the picked world of a LUP entrance (0-3, one pad only).
The capture notes read the RocketEquipped and ChangeObjectWorldState
seen before LEVEL_LOAD_COMPLETE as the arriving player's own rocket:
all 21 are other players launching (their sender is never the loading
player), so arrival itself is left as it is.
Check: with two players at a launchpad, launch one: the other sees the
rocket launch animation; launch to a property and to a LUP world and
land in the right place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent, right before TRANSFER_TO_WORLD on every rocket launch (131
captured): NotifyClientFlagChange(32, true), TransferToZone
(check_transfer_allowed, the target map, the landing spawn point, the
clone only for properties, no position or rotation),
TransferToZoneCheckedIM (no queue) and NotifyClientFlagChange(32,
false). DLU went straight to TRANSFER_TO_WORLD.
In the client TransferToZone runs the civilian INVALIDMAPTRANSFERLIST
check, and TransferToZoneCheckedIM pauses the player's controls, starts
the target zone's loading screen and leaves the gameplay state
(LWOCharacterComponent, cases TransferToZone and
TransferToZoneCheckedIM). They are sent from the transfer callback, so
a failed zone request never leaves the client paused. Instance exits
through a message box used TransferToLastNonInstance in live instead
and are not changed here.
Check: launch rockets to Nimbus Station, Gnarled Forest and your
property: controls lock and the destination's loading screen shows as
the rocket leaves, and you land at the usual spawn point.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live pushed a mail NotificationResponse (NewMail, the unread count)
right after the respawn checkpoint in loads where the player had
unread mail, without the client asking (e.g. a new character's first
load: ServerDoneLoadingAllObjects, PlayerReachedRespawnCheckpoint,
NotifyMissionTask, MAIL). DLU had that call commented out, so the
mailbox icon only lit up once the client asked or new mail arrived.
It is sent only when there is unread mail (inferred: the 223 captured
loads without one are loads without unread mail; only 3 of 226 loads
carry it).
Check: send a character mail, log it out and back in without opening
the mailbox: the new-mail indicator shows right after loading; a
character with no unread mail shows none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent PlayerReachedRespawnCheckpoint right after
ServerDoneLoadingAllObjects on 143 of 157 captured loads, with a
rotation: the spawn point the player arrived at, or a checkpoint they
had reached before. DLU sent it before constructing the zone's objects,
only when a checkpoint was saved for the zone, and always unrotated.
It now goes right after ServerDoneLoadingAllObjects on every load: the
saved checkpoint when there is one (DLU saves no rotation for it, so
that one stays unrotated), otherwise the position and facing the
player loaded in at. Inferred: that the loaded-in spot matches live's
spawn point choice (live's is the spawn point object, a few tenths of
a unit from the player's start).
Check: log in fresh to a zone, die before touching any checkpoint and
respawn: you come back where you arrived, facing the same way; reach
a checkpoint, change zones and come back, die: you respawn at that
checkpoint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live answered PlayerLoaded with PlayerReady to the player and then a
second PlayerReady to the zone control object (0x3FFFFFFFFFFE), both
to the loading client: 235 of 237 captured loads carry exactly that
pair. DLU sent only the first. The zone control object's client-side
zone scripts are what can react to it (inferred: the client's
component handlers only act on the player's own PlayerReady).
Check: load into Avant Gardens, Nimbus Station, a property and an
instance (a survival or dragon battle): the zone plays its intro,
music and UI as before and nothing is stuck waiting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent the player ModifyPlayerZoneStatistic(set false, value 1, the
current map) for "QuickBuildsCompleted" (227) after a quickbuild's
EnableRebuild and for "AchievementsCompleted" (186) at the end of an
achievement's completion. DLU counted both server side (they are saved
and shown at the next load) but never told the client, so the zone
summary only caught up after a zone change. The client counts
enemies, coins and bricks itself and sends those to the server.
Check: complete a quickbuild and an achievement, then open the zone
summary (passport / map zone stats) without changing zones: both
counts went up by one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent SetStunned to every dead object with a combat AI in the
packet right after its Die: 7,098 of 7,105 such deaths (7,424 decoded
stuns, all the same: no originator, push, can't attack, move or turn,
ignore immunity). Objects without combat AI (smashables, 9,393 deaths)
never got one. DLU sent none, so a dying enemy could keep turning or
start an attack while its death plays.
Check: kill enemies mid-attack and while they chase you; they stop
moving, turning and attacking the moment they die, and the death and
loot still play normally.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent duration 0.0 in all 744 successful EnableRebuild (cancels
carry the time spent building, which DLU already matches); DLU sent
the reset time.
Tests get EntityManager::_addEntity/_removeEntity to make an entity
built outside CreateEntity findable, and helpers that read game
messages back out of captured packets.
Check: finish a quickbuild; the build completes, the celebrate
animation plays and the built object stays up for its usual time
before resetting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live DropClientLoot (12,501 decoded): use_position is set only when a
player is the source (1,432, all activity rewards such as the dragon,
Frakjaw and BONS chests); drops from enemies, smashables and chests
leave it clear, so the client spawns the loot at the source object as
it sees it and falls back to spawn_position only if that object is
gone (LWODestroyableComponent::GetLootSpawnPos). Coins land where they
spawn (final = spawn in all 3,158 coin drops); items land 10 units
away (median 9.998, uniform direction) at spawn height. DLU always set
use_position and scattered everything 4.2 units away.
Inferred: the 10-unit rule for player-sourced items (live spread there
ranges 0-58 units, source unknown).
Check: kill enemies and smash crates: coins pop out of the object,
items fly about 10 units away; open an activity chest (e.g. after a
dragon or Frakjaw) and the loot still appears around you.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live filled SetCurrency's source fields per source type (16,816 decoded
live SetCurrency): pickups carry the position the client picked the
coins up at (the one in its PickupCurrency) and no source; mission,
achievement and activity rewards name the player (object and LOT 1);
vendor buys, sells and buybacks name the vendor and its LOT; trades
carry the trade ID; death, mail and everything else name nothing. No
live SetCurrency sets loot_type. DLU wrote a present source LOT of 0,
no object and a zero position for all of them.
The position is the one the client uses: for pickups it projects it to
the screen as the start of the coin counter animation
(LWOCharacterComponent::SetCurrency), so it started from the world
origin before. PickupCurrency now reads that position (the client
always sends it). The trade ID is an 8-byte object ID in the client;
DLU declared it 4 bytes, which only never broke because it was never
set.
Check: pick up coins and watch the coin counter animate from where the
coins were; buy and sell at a vendor, finish a mission and trade coins
with another player, and the coin total updates each time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live filled Die's direction_relative_angle_xz, _angle_y and _force on
11,606 of 19,428 deaths. The values are the dir_angle_xz, dir_angle_y
(degrees, sent as radians) and dir_force parameters of the BasicAttack
that dealt the killing blow: per killing skill the triple is constant
(skill 1210: -25 degrees, force 12 on 540 of 549 kills; skill 10:
force 7 on 1,830 of 1,835), and deaths with no killer (5,948) never
have one. BasicAttack is the only behavior with these parameters apart
from one Grab. The client passes them to PerformSmashableDeath, which
throws the smashable's pieces that way; DLU sent 0.
The radians are rounded the way live did (-25 is 0xbedf66f3, 20 is
0x3eb2b8c3). Not covered: use_caster_velocity (one behavior, racing).
Check: smash crates and enemies with different weapons; the pieces fly
off to the side/up per weapon instead of straight out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent client_death=false on every Die, players included (19,428 in
the live captures, 452 of them player deaths); DLU sent true for
players. The client only uses the flag to match a death it already
predicted locally (LWODestroyableComponent::msgDie): with it set, a
Die that arrives while a post-death respawn is pending is dropped.
Check: die to an enemy, to falling and to a quickbuild trap; the death
animation, coin loss and respawn prompt all still appear, and other
players see you die.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sends used_mouse false, no caster latency, no clicked position and
a zero originator rotation (present, all four components 0) in every
echo: 78,841 of 78,841 in the live captures, player and server casts
alike. DLU forwarded the casting client's values and wrote the caster's
facing for server casts. The client copies the rotation into its local
StartSkill (LWOSkillComponent::msgEchoStartSkill), which is why a real
rotation made power-up pickups jitter; the per-call override that
worked around that is no longer needed and is removed.
Check: with two players, attack, use a thrown or ranged item and pick
up power-ups; the other player sees the same animations and the
caster no longer snaps or jitters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EventGating (eventName, date_start, date_end; 8 rows) is only in the
CDClient for the server: the 1.10.64 client has no string for it. The
dates are Unix times (UTC), inclusive: pirateDay 2011-09-19 00:00 to
2011-09-20 23:59:59 (Talk Like a Pirate Day), buildNexusTower 2010-03-09
to 2011-03-14, buildNexusTowerEnd, frostburgh2010_notused and test rows.
The live Lua asked for an event with GetHolidayEvent{eventToCheck =
name}.isValid; the only callers are the Crux Prime random spawners
(L_BASE_RANDOM_SPAWNER.lua, L_AM_ZONE_SERVER.lua), whose only event is
pirateDay. DLU's BaseRandomServer::CheckEvents was a TODO.
- CDEventGatingTable loads the table; HolidayEvents::IsActive(name) is
true while the dates include now, or when event_1..event_8 names the
event (every live date is in 2010/2011, so this is how a server turns
one on; the same settings already switch gatingOnFeature objects).
- BaseRandomServer::CheckEvents: during pirateDay the str and zip areas
use the event loads from the Lua (80%: 5 pirates on each of type1-3,
20%: 5 admirals each; multipliers secA 1, secB 1, secC 1.2 for str).
Inferred: every spawner's Lua put the whole {str, zip} table in its
zones, which its getRandomLoad walks with ipairs and so finds no load;
DLU uses each table for the area it names, as L_AM_ZONE_SERVER.lua's
layout intends.
- sharedconfig.ini says event_N also takes EventGating names.
Check in game: with event_2=pirateDay in sharedconfig.ini, the Crux Prime
areas filled by the str and zip random spawners (spawner networks
em_str_* and em_zip_*) spawn only Maelstrom pirates and admirals; the
other two areas and a server without the setting spawn the usual mix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CollectibleComponent (79 rows: id, requirement_mission) is only in the
CDClient for the server: the 1.10.64 client has no string for it. DLU
never read it.
What the column holds (1.10.64 CDClient, joined with ComponentsRegistry,
Missions and MissionTasks): for 63 rows it is the mission the collectible
belongs to. Mostly that is the achievement whose collection task
(taskType 3) targets the collectible's LOT (the flags, imagination bricks,
Johnny Thunder collectibles). For a few it is a mission to accept from an
NPC that does not collect the item itself: the four Ninjago dragon relics
(16483-16485) are collected by the hidden achievements 2064-2067 but
their requirement is 2040 (accepted from LOT 13789, "complete 2064-2067").
Other values: -1 and 66666666 (no such mission) on test rows.
So a collectible whose requirement_mission is a mission to accept
(Missions.isMission) now only counts (HasBeenCollected progresses
nothing) while the player has that mission accepted and not handed in
(ACTIVE, READY_TO_COMPLETE or their repeat states). Achievements, missing
missions and rows without one are unchanged. Without this, a player who
had not accepted 2040 could collect the relics early and have 2040
complete as soon as it was accepted. The gate itself is inferred from the
data: the captures show collections but not the live server's check.
The collectible's object report shows the requirement mission.
Check in game: in Ninjago Monastery, before accepting the dragon relic
mission (2040), touch a dragon relic: it does not count; accept 2040 and
collect them: each counts and 2040 completes after the fourth. Flags,
imagination bricks and Johnny Thunder collectibles still count as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client reads <pet a> as its active pet's database ID with
GetInt64Attribute (LWOPetControlComponent::LoadFromSaveData 0x00c18120);
DLU never wrote it. Live wrote a="0" on every character (226 live
charxmls, 45 with pets): the charxml is only read when the player loads,
before any pet is out, and a pet summoned afterwards registers itself
(RegisterPetDBID). So a is written as 0.
The pet taming type p@t stays 0 as DLU already wrote it: it is an int
(IntAttribute), the client sets it to 0 for every pet it is given
(AddPetToPlayer in LWOPetControlComponent::HandleMessage 0x00d0fe00,
which ignores the message's elemental type) and every live pet had t="0".
Commented so it isn't "fixed" later.
Check in game: with a tamed pet, summon it, change zones and log out and
back in: the pet menu and pet naming work as before and no pet is shown
as out until you summon one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client reads the cooldowns still running from the charxml on load
(LWOSkillComponent::LoadFromSaveData 0x00c2a9c0): <skil sc="..."> split
on ';' and ':' (the delimiter string at 0x015b1714 is L";:") into a
cooldown group and the seconds left, each put in its cooldown map under
{hasCooldownGroup = 1, group}, the key it checks when the player casts a
skill of that group (0x00c11400). Live wrote <skil/> with nothing running
and e.g. <skil sc="17:15.6958;"/> otherwise (226 live charxmls; groups
8, 17 and 78 seen, the times below the groups' SkillBehavior cooldowns).
The claude-client-re-docs page described sc as "id;time" pairs; the
live saves and the delimiters show "group:time;".
DLU wrote no <skil>, so relogging or changing zones cleared every
cooldown (e.g. the 90 s imagination and faction skills).
- A player's successful cast (CastPlayerSkill) starts its
SkillBehavior.cooldowngroup's cooldown (groups 0 and up, cooldown > 0),
counted down in Update.
- Saved as live wrote it; loaded back when the player's SkillComponent
is created, so a cooldown survives several zone changes. Old saves
without <skil> load with none running.
Check in game: use a skill with a long cooldown (e.g. a faction kit's
special skill, a consumable with a cooldown), change zones or log out and
back in right away: its cooldown is still shown and counts down from
where it was (minus the loading time on the server).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOCharacterComponent sends ModifyGhostingDistance (1485: an optional
fDistanceScalar, default 1) on load. All 239 live packets were
the single byte 00 (default scale), so there is nothing for the server to
change; it was logged as an unknown game message. It is now read and
ignored.
Check in game: nothing changes; the world server log no longer has
"Received Unknown GM ... 1485" on each load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client's rail activator sends RequestRailActivatorState (1479, no
payload) when it is added to the world (LWORailActivatorComponent::
SendMessage 0x00c00da0) and live answered with
NotifyRailActivatorStateChange (1478, bActive) to that client; the client
stores rail_activator_active and updates the rail's pick type (usable or
not). Live: 152 requests, 413 answers, every one bActive = true. DLU
dropped the request and never answered.
RailActivatorComponent keeps the level key rail_activator_active
(default true; every rail in the live levels sets it to 1) and the
request is answered with it.
Check in game: in Ninjago Monastery and Nimbus Station, rails (spinjitzu
posts, the Nexus Tower rails) show as usable and work as before when
first loading the world and after zone changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The property editor sends ResyncEquipment (1238, no payload)
after the player picks up or sets down a model they carry: 59 live
packets, every one right after PlaceModelResponse. Live answered with no
game message, only replica serializations (packet 0x1b), i.e. the
player's equipment sent again. DLU dropped it.
The handler marks the inventory's equipped items dirty and serializes the
player, so everyone gets the equipment again. That live re-sent the
inventory part of the replica is inferred: the captures show a
serialization but it was not decoded.
Check in game: on your property, in build mode, pick up a model from the
world and place it again a few times, then leave build mode: your hat,
shirt, weapon etc. are all shown on you (and to a second player
watching), none invisible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a client gets EchoStartSkill (or SyncSkill) from a caster it sees
as dead, aimed at another object, it logs "msgEchoStartSkill
msgCasterDead" and sends CasterDead (120: optional i64Caster, optional
uiSkillHandle) through that target
(LWOSkillComponent::msgEchoStartSkill 0x00d5dc90). 303 live packets, the
target nearly always the attacked player, the caster a spawned enemy;
the next messages were mostly Die and SetStunned for the player. DLU
dropped it.
The server now ends that skill on the caster: its behaviors with that
handle are dropped (end entries run, pending timers and syncs discarded,
its projectiles removed), so a hit an enemy scheduled before dying does
not land later. It only does so when the server also sees the caster as
dead, so a client can't cancel a living enemy's attack. What live did
with the message is inferred from the name and when it was sent.
Check in game: kill an enemy in the middle of a slow or charged attack
(e.g. a Maelstrom horseman or a spider queen add): its attack does not
hit after it has died, and other enemies' attacks still hit normally.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client sends SetLastCustomBuild (890, a u32-length wide string;
other references) with its rocket build, e.g. "1:14454;1:4714;1:4715;", when it
assembles the rocket the player carries: after EquipInventory at a
launchpad and on load when landing (202 live packets). It stores it as
lastUsedRocketInfo (LWOCharacterComponent::SendMessage 0x00d34330) and
reads it back from char@lcbp on load (0x00ca4910). Live saved lcbp in the
same format on every character.
DLU dropped the message and only set lcbp when the server equipped the
rocket. The handler now keeps the client's build as the rocket config
(saved as lcbp). It does not change whether the player lands on the next
load: DLU uses an empty config to skip the landing after non-rocket
transfers, and that still happens when the server clears it.
Check in game: build a new rocket in the rocket builder, launch from a
launchpad and land: the rocket shown on landing and the builder's default
next time are the new build. Teleport to another world without a rocket
(e.g. from a property): no landing animation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client sends SetTooltipFlag (469: bFlag, then the tooltip,
2 live packets, both tooltip 24) when a tooltip has been shown, and
follows it with SetFlag for the same ID. DLU dropped it and never wrote
char@ttip, so the client's tooltip bits reset on every load.
- SetTooltipFlag message struct and handler: the bit is set or cleared on
the CharacterComponent exactly as the client does it
(LWOCharacterComponent::SendMessage 0x00d34330): tooltips above 127 are
ignored, set = 1 << tooltip, clear = mask ~1 << tooltip (which also
clears the lower bits), shifts of 64 or more give 0.
- Saved as char@ttip, a 64-bit value (the client reads it with
GetLongLongValue in LoadFromSaveData 0x00ca4910). Live wrote it on
every character (226 live charxmls: "0" or "16777216" = tooltip 24).
Old saves without it load with 0.
Check in game: on a new character, trigger a first-time tooltip (e.g.
the one about an item or a new area), change zones and log out and back
in: the character loads normally and the same tooltip does not come back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client sends SetMissionTypeState (851) for the journal's mission
types and subtypes (Missions.defined_type / defined_subtype) at load and
after mission updates: 650 live packets, every one with the optional
state left at NEW, the subtype written before the type. DLU
dropped it and never wrote the states, so the client lost them on every
load.
- SetMissionTypeState message struct and handler: the state is stored on
the player's MissionComponent (byte-sized as the client keeps it,
LWOMissionComponent::msgSetMissionTypeState 0x00c90d00).
- Saved as live wrote it (226 live charxmls): after <cur>,
<ts><type v="Build"><st sub="" val="1"/></type>...</ts>, <ts/> when
empty. Read back per <type>; old saves without <ts> load with none.
The client's reader (0x00d171c0) reads v from <ts> rather than from
each <type>, so it files every state under one type; the server keeps
the types apart.
- Mission::SetMissionTypeState (on accept) records NEW for the mission's
type instead of doing nothing.
Check in game: accept missions of a few kinds (a location mission, a
battle achievement), open the passport/journal, then log out and back in
and change zones: the journal tabs keep their "new" markers as before,
nothing errors, and the character still loads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live, on every equip and unequip (captures: 22,422 ChangeObjectWorldState,
4,269 equip effects, 245 UncastSkill):
- ChangeObjectWorldState on the item to everyone: ATTACHED when equipped,
INVENTORY when unequipped, proxies (sub-items) included.
- The equip-<slot> / unequip-<slot> effect on the wearer to everyone
(effect -1, priority 1.07), the slot picked from the item's equip location
as the client's ItemComponent does (0x00cdafb0): special_l left,
special_r right, hair head, clavicle, chest, legs; none for others (e.g. a
rocket's Extra_1); the item's equipEffects with "equip"/"unequip" when it
has one; no equip effect for noEquipAnimation items, no unequip effect for
proxies.
- Order when an item replaces another: the new item's state and effect,
then the old item's, then the old item's proxies taken away
(RemoveItemFromInventory, UnEquipInventory with ignoreCooldown,
ChangeObjectWorldState INVENTORY, to the player), then UncastSkill for
the old item's equip skills (castOnType 1, not its proxies'), then its
skill removed and the new one added, then the new item's proxies.
- An equipped item taken out of the inventory is followed by
UnEquipInventory (ignoreCooldown) to the player.
DLU sent none of these (only the rocket's ChangeObjectWorldState, which
now comes from equipping it, before RocketEquipped as live).
Check in game (with a second player watching): equip and unequip a hat, a
sword, a shield, a shirt, pants and a backpack; both players see the equip
animation/effect. Put a ninja hood on, then another hat over it: the hood's
back piece disappears and its passive effects end (no lingering aura or
stat). Launch a rocket: it is still shown during the launch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ChangeObjectWorldState's state is optional on the wire (a flag, then the
state when it isn't INWORLD): live sent 80 80 00 00 00 for ATTACHED. DLU
wrote the bare u32, so the client read the rocket's ATTACHED as INWORLD.
UnEquipInventory gets its optional replacementObjectID (the client always
sends the flag; the server-sent copies in the captures have it too), so it
can be sent server -> client.
New UncastSkill (1206, server -> client: the client ends its running
instance of the skill, LWOSkillComponent::msgUncastSkill 0x00bd86d0).
Tests: all three against live capture bytes.
Check in game: launch a rocket; the rocket is shown in the player's hands
during the launch animation (for the launcher and players nearby).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RocketsUsed: live counted the rocket when the client asked to go
(FireEventServerSide "ZonePlayer"), right before TransferToZone (captures:
94 of 117 directly before it). DLU counted it when the launch animation
started, so a launch that never went counted too.
QuickBuildsCompleted: live sent it after RebuildNotifyState(Completed) and
the completion effect (507), before EnableRebuild (218 of 227 samples); DLU
counted it before the notify state.
Check in game: launch a rocket to another world; Rockets Used goes up by 1
as the screen fades out. Finish a quick build: Quick Builds Completed goes
up by 1 as the build finishes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent TotalArmorRepaired / TotalImaginationRestored with what a repair
or restore applied, 0 included (1,050 and 2,148 zeros in the captures: a
power-up picked up at full), and never a TotalDamageHealed or
TotalDamageTaken of 0. DLU counted every change of the value instead, so
setting stats up (loading, respawning, level changes) counted as healing
and a heal at full health counted as 0 damage taken.
Now Heal, Repair and Imagine count what they applied; health lost and
imagination spent still count wherever they change.
Check in game: pick up an armor or imagination power-up when full; the
passport's Armor Repaired / Imagination Restored stay the same. Take damage
and heal; Damage Taken and Damage Healed go up by what the bar shows.
Respawn: Damage Healed doesn't jump.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The passport totals for coins, bricks and enemies came from the client's
ModifyPlayerZoneStatistic, which live's client sent for its per-zone counts;
live's server counted the totals itself and told the client
(UpdatePlayerStatistic). Now:
- CurrencyCollected (the amount gained) right after every SetCurrency that
raised the coins (pickups, missions, achievements, selling, activities);
none for losses. Captures: 15,815 pickups + mission/achievement/vendor/
activity gains, all with the stat; no loss had one.
- BricksCollected (the count) after every add to the bricks inventory the
client is told about (captures: 999 of 1,010, pickups and relocations).
- The kill counts go to the killer right after Die, before the loot:
EnemiesSmashed when the DestructibleComponent's isnpc is set, else
SmashablesSmashed for a smashable (captures: 2,766 NPC kills and 538
smashables; faction or AI don't decide it, e.g. the Banana Cluster has AI
and counted as a smashable). Neither while racing. Before, every kill was
a SmashablesSmashed.
- ModifyPlayerZoneStatistic from the client only updates the zone counts,
so nothing is counted twice.
Check in game: pick up coins, sell an item, finish a mission, pick up
bricks, smash a stromling and a crate; each passport number goes up by the
right amount at once and stays the same after relogging (no double counts).
The per-zone statistics still go up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent UpdatePlayerStatistic (1481) server -> client for every statistic
the server counted (59,933 messages in the captures, none client -> server);
DLU only counted them, so the passport stayed stale until the next login.
CharacterComponent::UpdatePlayerStatistic now tells the player's client what
it counts (not what a client reported), with the amount left out when it is
1, byte for byte as the live samples.
MetersTraveled: whole meters now count (each position update's distance was
cut to a whole number before, so short steps never counted) and go to the
client once 25 have gathered (live's values were nearly all 25-33 while
moving) and the rest, 0 included, when the player is taken down on leaving
the world (live sent one more after TRANSFER_TO_WORLD). DistanceDriven goes
out every 10 seconds of racing (inferred: live sent it every few hundred
packets, about 1300 units at race speed).
Check in game: open the passport's statistics page, walk around, collect a
power-up, complete a mission; the numbers go up without relogging. Walk
25+ meters; Meters Traveled matches what the passport shows after
relogging. Change worlds and relog: the meters were kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ghosting_scenes defaults to 1 in the worldconfig.ini template, the settings
catalog and when the key is missing. Zones without a terrain scene map keep
distance ghosting; ghosting_scenes=0 turns it off everywhere.
Check in game: with a fresh worldconfig.ini the world server log says
"Scene ghosting is on" in Avant Gardens; objects appear by scene (the whole
scene you are in and its neighbours) instead of within 100-150 units. In a
zone without a scene map (e.g. a property) the log says it is off and
ghosting is by distance.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>