Skip to content

Fix quest progression, scene dialogue, and object lifecycle synchronization - #890

Closed
sfedev wants to merge 8 commits into
tiltedphoques:devfrom
sfedev:feat/quest-object-sync
Closed

sfedev wants to merge 8 commits into
tiltedphoques:devfrom
sfedev:feat/quest-object-sync

Conversation

@sfedev

@sfedev sfedev commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Prevent repeated remote quest execution, redundant scene dialogue broadcasts,
and stale door/container bindings during cooperative play.

Changes:

  • Suppress duplicate quest starts/stops and use the game's IsStageDone
    function to avoid re-running completed quest stages.
  • Prevent party members from stopping other players' quests by default,
    configurable through Gameplay:bPartyMembersCanStopQuests.
  • Filter scene dialogue and subtitle broadcasts while preserving dialogue
    addressed to the local player.
  • Refresh and deduplicate object bindings, clear them on disconnect, and
    notify all clients before retiring server object entities.
  • Add regression tests for message encoding, client object bindings, and
    server cell retirement.
  • Fix launcher compilation with MSVC 14.51.

Validation:

  • Client, server, server runner, and launcher build and link successfully.
  • TPTests: 63 assertions across 9 test cases passed.
  • TPServerTests: 28 assertions across 5 test cases passed.
  • Two-client smoke testing covered connections, chests/doors, and dungeon
    transitions on the integrated gameplay build.

Compatible client/server builds are required for the new server opcode.
Full story and long-scene testing remain pending. The follower transition
issue is deferred.

sfedev and others added 8 commits September 10, 2026 09:56
…h MSVC 14.51

VS 18 (MSVC 14.51) rejects `constinit const uint8_t* pBasePtr =
reinterpret_cast<const uint8_t*>(&__ImageBase)` with C2127: a
reinterpret_cast is not a constant expression, even of an address constant.
VS 2022, which CI uses, accepted it.

Removing `constinit` keeps the same object code: the initializer is still an
ADDR64 relocation to __ImageBase and no dynamic initializer is emitted
(verified with dumpbin on the .obj by Codex's review). kpImageEnd keeps its
CRT initializer as before.

Co-authored-by: Codex <codex@openai.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Object (door/container) replication bindings on the client are now owned by
ObjectSystem: one binding per local reference, re-registration updates the
existing entity and drops duplicates or stale collisions, and disconnecting
clears object bindings while keeping actor entities. The server sends the new
NotifyRemoveObjects (kNotifyRemoveObjects, appended before kServerOpcodeMax)
to every player before destroying object entities, so clients can drop the
bindings instead of keeping dead server ids. Removal uses the full 32-bit
EnTT id including generation bits.

Adds Catch2 tests for the message encoding and for the binding lifecycle on
a real EnTT registry.

Imported unchanged from Codex's staged index in
C:\Git\TiltedEvolution-codex-objects (build/full-verification/
codex-object-lifecycle.patch, 12 files). Authored by Codex.

Co-authored-by: Codex <codex@openai.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Raise logging level and fix logs of quest progression.

Previously, multi-execution of quest stage entry was suppressed. But multi quest starts weren't. This is a problem when a quest start has side effects that advance quest stage; as the party members started their quests, the stage-rewind would reflect back to the leader, allowing quest stage side effects to happen again.

Also added fix to debugger to force setStage. This is needed so leader can set the stage to the stage they are at, and not have it suppressed so it is sent to party.

Regularized quest logging so you can import into a spreadsheet, break fields on colon and comma (:,)
TESQuest::IsStageDone walked `stages` as a GameList<Stage>, but the field is
really the pair of (executed stages, waiting stages) list heads. That walk
only ever saw the first executed stage, so every other completed stage was
reported as "not done" and ScriptSetStage let a reflected or late remote
update re-run the fragments of a stage this client had already played
(restarting scenes, duplicating quest side effects). Ask Papyrus instead.

PR tiltedphoques#848 fixed the same bug with the native TESQuest::IsStageDone; this uses
the Papyrus binding so no address-library id is involved.

Co-authored-by: Richard Fortier <rfortier@ourchateau.net>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A quest normally ends on every client through the synced stage that
completes it, so a member's stop event is redundant in the common case. When
it isn't, it is because the member's local scripts decided the quest failed
(e.g. misjudging the brawl outcome in "Trouble in Whiterun"), and forwarding
it stopped the leader's quest and corrupted the party's progress.

Keep updating the member's own quest log, but only broadcast stops from the
leader unless `Gameplay:bPartyMembersCanStopQuests` is enabled. Doing this
on the server (rather than in the client as PR tiltedphoques#846 proposed) keeps member
quest progression intact and makes the behaviour a server setting.

Co-authored-by: liangchen <141402668+otsffs@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Scenes run on every client that has the owning quest at the right stage, so
each client already plays the scene's lines locally, including those of
actors it doesn't own. Broadcasting the owner's copy made receivers call
StopCurrentDialogue() on their own actor mid-scene, ending the dialogue
action early, letting their scene phase race ahead of the owner's and
sometimes hanging the scene for everyone.

Skip DialogueRequest/SubtitleRequest for actors with the scene bool bit set,
except when the actor is in dialogue with the local player: those lines are
not played anywhere else and still have to be synced.

Reduced from PR tiltedphoques#854, which also handles member-to-NPC dialogue and remote
line stomping; this keeps only the sender-side scene filter.

Co-authored-by: Richard Fortier <rfortier@ourchateau.net>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Extract cell retirement into ObjectLifecycle so production cleanup can be
exercised with an EnTT registry and a synchronous notification callback.
Preserve the existing current-cell retention policy and broadcast removals
before entity identifiers can be reused.

Add the optional TPServerTests target and regression coverage for occupied
cells, complete cell identifiers, notification ordering, repeated cleanup,
and entity generation bits.

Co-authored-by: Codex <codex@openai.com>
@miredirex

Copy link
Copy Markdown
Collaborator

Look, I see that you're trying to help improve the mod, but you really should've told your agent to not look at any open PRs so that it can work toward its own independent (and unbiased) solution to fix certain issues with questing. Your PR largely bases on #848 and it wouldn't be fair if we merged yours instead

How well is this tested? Did you test this yourself in-game? What quests have been tested?

I don't mind AI assisted PRs at all (especially when strong models are used), but please start with simpler tasks; you can browse issues or ask the dev team in the discord

Join our discord because all collaboration happens there @sfedev

@sfedev

sfedev commented Sep 10, 2026 via email

Copy link
Copy Markdown
Contributor Author

@miredirex

miredirex commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

@sfedev Yes, object lifecycle fixes should be a separate PR (I'm assuming they are not related to questing whatsoever), so you can start with that in the meanwhile. Make sure to provide a brief description what it solves and what didn't work before.

@sfedev

sfedev commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Closing this combined PR in favor of #891 (#891), which contains only the object lifecycle fixes and their tests. The overlapping quest and scene changes will be left to the existing contributors' PRs. Thanks for the guidance.

@sfedev sfedev closed this Sep 10, 2026
@MostExcellent

Copy link
Copy Markdown
Contributor

For what it's worth, with games, a lot of stuff just isn't practical to unit test, and you must test in game.
Closest thing to unit tests are scripted types of interactions/sequences, but that does not exist here, re: skyrim it probably only exists internally at Bethesda, if at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants