Use Mission authoring and operations for the migration workflow. Mission content is authored in C# and deployed by EF migrations. No pack envelope, release manifest or publish command is required.
| Contract | Source |
|---|---|
| Shared scene and experience definitions | MissionSceneDefinition.cs |
| Internal scene serialization | MissionContentCodec.cs |
| Migration helpers | MissionDataMigration.cs |
| Consolidated World seed | WorldContentDataV1.cs |
| Fixed Bootcamp migration data | BootcampMissionDataV1.cs |
| Normalized row fields | src\Rasa.DBL\Structures\World\Mission*Entry.cs |
| Objective trigger shapes | MissionObjectiveRuntimeAnalyzer.cs, MissionProgressRuleAuthoring.cs |
| Scene observations, intents and bindings | SceneContracts.cs |
| Script discovery and sequences | SceneScriptRegistry.cs |
| Predicates and handlers | MissionRequirements.cs |
| Authoritative fact providers | MissionRequirementFactsAdapter.cs |
| Typed native dialogue | src\Rasa.Missions\Definitions\MissionDialogueTopicDefinition.cs, src\Rasa.Game\Missions\Protocol\MissionConversationProjection.cs |
| Assignment-owned quest items | src\Rasa.Missions\Definitions\MissionItemBinding.cs, src\Rasa.Game\Missions\Persistence\MissionItemPlanner.cs |
| Repeat policies and attempt identity | src\Rasa.Missions\Runtime\MissionRepeatPolicy.cs, src\Rasa.Missions\Runtime\MissionLog.cs |
| Radio channels and source authoring | src\Rasa.Missions\Definitions\MissionChannels.cs, src\Rasa.DBL\Structures\World\MissionChannelPolicyEntry.cs |
| Durable offer authority | src\Rasa.Game\Missions\MissionOfferAuthority.cs, src\Rasa.DBL\Repositories\Char\MissionOffer\MissionOfferRepository.cs |
| Explicit party sharing | src\Rasa.Game\Missions\MissionSharing.cs, src\Rasa.Game\Managers\PartyManager.cs |
Normalized rows repeat MissionId and ContentRevision. Revisions are nonempty
strings of at most 32 characters. Objective, transition, reward and scenario
cross-links stay within that mission/revision unless a field explicitly names
another mission.
MissionContentDefinitionEntry.Enabled controls runtime selection. Keep
unreconstructed/example data disabled and enable at most one revision per
mission. A migration can update an enabled definition; there is no separate
release-immutability rule.
Legacy npc_mission fallback rows remain inactive until a complete enabled
definition is installed by migrations. That inactivity diagnostic takes
precedence over reward-shape diagnostics for those rows; an unsupported legacy
reward must not bypass the operational-content gate.
Requirement on normalized rows is MissionContentRequirement.Required or
Optional, describing validation criticality. It is not a player-eligibility
predicate. AbandonmentPolicy is Allowed or Prohibited.
GiverId and ReceiverId identify creature definitions. Conversation triggers
use NPC package IDs; public actor reservations use static spawn IDs.
Keep those namespaces distinct.
Shareable opts into explicit party sharing, subject to the supported scene
capability and live source checks below. The native projection uses that
validated capability, not the row boolean alone. RadioCompleteable on old
normalized rows is retained for migration compatibility, but is not admission
authority or a completion switch. Mission.RadioCompletable is derived from
the validated explicit completion channel. Supply real native mission,
objective and text IDs and source evidence. A nonzero confidence value does
not verify the source.
Mission.AcceptanceChannel and CompletionChannel independently use Npc=1,
Radio=2 or Mixed=3. mission_channel_policy(mission_id, content_revision)
stores both and the JSON array of MissionOfferSourceDefinition values.
No row means NPC-only. A side that permits NPC interaction requires its NPC ID;
radio-only sides support actual SQL NULL. MissionRuntime indexes only
present, channel-authorized giver/receiver IDs. All definition copies preserve
channels, sources, repeat policy, dialogue, items and requirements.
| Source field | Meaning |
|---|---|
Kind |
ServerEvent or Scene; live party authority is not an authored radio source |
Key |
Authored event key or scene script key, 1-96 characters |
MapContextId |
Optional nonzero map restriction |
OwnedPrivateMap |
Require a private map owned by the recipient |
Requirement |
Additional shared requirement evaluated against durable facts |
MissionOfferSourceIdentity adds InstanceId, source Generation,
AssignmentId and AssignmentGeneration. Server events use a stable event ID
(the key by default), generation zero and no assignment. Scene intents capture
the originating run ID, scene generation and exact participant assignment;
these are not the shared actor's execution-root identity. A mission source's
release must match its current operational definition. The active
starting-experience fact used by Initiation is durable-only, not inferred from
the absence of a completion flag. Its ReadState scalar query bypasses EF's
tracked pre-write value at the final source check. Authored map restrictions
must reference an installed World map.
MissionApplication.Offers.TryOffer is the trusted server-event entry point.
PlanOffer is its enlisted scene adapter seam. Both return publication only
after committing Char state. OfferRadioMissionIntent is a CharacterIntent,
so its offer, source scene advancement and receipt share that transaction.
It does not change forwarded world-effect provenance or operation retirement.
Char stores one row per (character_id, mission_id) with an independent offer
ID, target revision, recipient account/entity identity, connection session,
player-location epoch, map epoch, source identity, creation/expiry times,
predecessor assignment/history identity, state and concurrency version.
Consumption records the new exact assignment ID/generation. A pending offer
is valid for five UTC minutes, with expiry excluded. Creation timestamps
are canonical microseconds for MySQL datetime(6) compatibility; final time
checks use the current UTC clock.
There are at most 30 stored transient slots per recipient through this API. Issuance prunes expired, invalid-session and terminal rows other than the slot being reused. Consumed/cancelled rows are not permanent mission history. Identical creation preserves the offer ID/expiry and sends no second dialog. A different live source cannot overwrite an existing valid slot. Session-invalid rows fail closed immediately; their physical cancellation or collection can wait until another authority operation. An old connection cannot cancel a newer connection's pending offer.
Radio acceptance revalidates the pending offer, predecessor, source, ownership, requirements, repeat entitlement, duplicate assignment and journal capacity. It consumes the offer and creates objectives and acceptance item plans in the same common transaction used by NPC acceptance. Opening NPC/object dialogue does not replace pending authority. NPC acceptance cancels any outstanding radio or party offer for that mission.
Radio turn-in requires an explicit radio completion channel, not an NPC
conversation or an unconsumed acceptance offer. Admission captures one current
MissionLog identity and rechecks it in the shared reward transaction. Normal
objective, requirement, selection, inventory, repeat-window and per-assignment
receipt checks still apply. Legacy pending Success can be settled, not
reaccepted. Every non-null rating is rejected.
The common acceptance and reward planners treat requirements as
preconditions, not predicates that must remain true after their own writes.
Before writing, they capture durable typed inputs, including legacy admission
prerequisites and the selected radio source's requirement. Final validation
compares those inputs with the expected new Active assignment, or the
Completed journal/history and the reward planner's exact resulting level.
The captures belong to the existing unit-of-work transaction. Common reward,
progress/action and scene planners project their explicit writes in execution
order, including cross-mission failures and history, flag writes/removal,
entitlement and qualification changes. An optional objective failure does not
project a mission failure or new history. Earlier lifetime success/reward
memberships are retained when a later attempt fails.
Level-derived custom facts use the shared reward planner's planned level. Qualification-derived completion uses the captured starting-experience state and the planned flag value, not a reread of mutated persistence. A stored zero flag remains distinct from an absent flag, and only the last ordered write is expected. Thus a turn-in requiring flag 92 equal to 1 can intentionally set it to 2 through another mission's completion action; a later write to 3 still rejects. An unaccepted-only requirement can admit its own assignment, and an eligible level-9 turn-in can award the XP that reaches level 10.
Only planners executing in the captured transaction contribute expectations. Queued scenario work executed after commit belongs to its own transaction; queueing it does not authorize its future writes in the current transaction. Fresh final facts must match the captured inputs plus those explicit outputs. Unexpected changes still reject and roll back the whole transaction.
Receipt/time writes remain in FinalizePersistence, before inventory guards.
Requirement postconditions, offer expiry/session/source identity and radio
reward/window checks run in the final read-only ValidateCommitBoundary phase.
Nothing persists after those guards. Source identity checks remain independent
of the captured source precondition.
Native completion has no assignment token: byte-identical old input after a
fresh eligible/current attempt cannot be distinguished from current input.
No native nonce or NPC placeholder is added.
MissionApplication.Sharing.TryShare(sender, missionId) captures the sender's
current active assignment, then releases the sender's mutation lock before
calling the existing Offers.TryOffer for each eligible recipient. Each
recipient's transaction commits independently before notification. This is
another source for the same durable authority, not another offer or reward
engine. Sharing does not require a radio acceptance channel.
Both players must be living, registered world actors in the same live party, on the same map instance and within 20 units, including the exact boundary, at issuance and acceptance. Distance uses finite three-dimensional world positions only after actor, cell and instance resolution. Equal coordinates on different instances do not establish proximity. Durable character/account ownership is checked separately from party membership.
Party.LifetimeId distinguishes recycled numeric party IDs.
PartyMember.MembershipId invalidates departures, rejoining, map departure and
character/entity replacement. TryGetLiveMembership checks the current
account-to-character/entity mapping. The nullable party_source JSON column
on character_mission_offer stores the immutable MissionPartyOfferSource:
party lifetime, both membership identities, sender character/account/entity,
and sender connection, player-location and map epochs. Recipient identity
continues to use MissionSessionIdentity. Actor IDs remain unsigned 64-bit
values; the native dialog identifies the sender's actor, never its account.
SourceKind.Party uses the sender's exact assignment ID/generation and the
target revision. Without a scene, its instance identity is the party lifetime
and its generation is zero. For a public encounter it instead pins the
existing run ID/generation. A participant may offer its own assignment to
another member, but the run's initiator identity stays separate.
The same five-minute expiry, one slot per recipient/mission, 30-slot cap, predecessor checks and duplicate-notification suppression apply. Repeating an identical valid offer neither renews expiry nor sends another dialog. Departure, character/session/map changes, abandonment/replacement, expiry or invalid current range make acceptance fail. Physical cancellation/pruning may wait until another authority operation. Declining can simply dismiss the UI; no decline RPC or automatic acceptance is introduced.
AssignSharedMission(sourcePlayerEntityId, missionId) must match a current
pending party offer. Radio callbacks cannot consume party authority, nor can
shared callbacks consume radio authority. The common acceptance planner
rechecks prerequisites, repeat policy, journal capacity and unsettled rewards,
creates fresh objectives and independent acceptance items, and consumes the
offer in one transaction. It retains the transaction-scoped ordered requirement
outputs and enlisted shadow inventory. Journal capacity is checked again at
the read-only commit boundary without counting the new slot twice.
The native callback has no offer token: byte-identical delayed input after a
fresh currently valid offer cannot be distinguished from current input.
Captured stale authority is still rejected; no extra native field is invented.
Public joining requires PublicEncounterBinding.AllowPartyJoin = true.
TryGetJoinRun checks the real reserved actor, lease, map, running scene,
initiator assignment and source participant. AttachSharedAssignment adds
the recipient's exact assignment membership in the acceptance transaction.
It creates no reservation, scene or actor and dispatches no second scene-start
event. Its final guard checks the persisted membership and run generation.
Only the initiator/script controls world work. Joined assignments cannot start a scene or queue its sequences. Their personal dialogue can still update their own objectives and flags. Retiring a participant deactivates only that assignment's membership and expires its deliveries; initiator abandonment retains the existing authored run cancellation. Execution-root/source-run provenance, per-operation cancellations and reconstruction retries are unchanged.
Shared scenes with assignment-owned deadlines, or objective transitions using
StartScenario/ActivateSpawnGroup, must be nonshareable: those paths require
an independent scene controller. Content loading rejects incompatible
shareable declarations, and native status projection hides unsupported sharing.
The same capability check rejects required Personal scenario-event
transitions because their signals only progress the initiator's assignment.
Another objective's group policy does not make that personal path shareable.
It also traces required-progress dependencies backward through the executable
reveal/activate graph shared with content cycle validation, plus
ObjectiveStateReached prerequisites. This includes optional predecessors and
transitive chains in normalized, explicit-transition and legacy definitions.
Unlock edges only introduce dependencies for initially Inactive or
NotAssigned targets; already active objectives can progress independently.
All authored dependency branches count: an alternate progress or dialogue
branch does not prove an owner-only branch safe. This conservative check does
not simulate execution or prove branch equivalence.
Optional personal signals outside those dependencies and independent personal
dialogue/actions remain allowed, including personal actions that activate
required objectives. Authored NearbyParty/EncounterParticipants signals
retain future-only delivery. Validation never rewrites a credit policy or
grants participants world control.
Joining never copies checkpoint progress, flags, choices or rewards.
GroupCreditService freezes only future eligible kill/scenario deliveries.
Encounter kill credit uses the same exact participant lookup as scene signals.
Selection matches participant assignment ID and generation to each candidate
mission before evaluating its objective policies. Unmatched membership cannot
authorize encounter-only credit; unrelated Personal/NearbyParty assignments
still use their own eligibility rules. Freezing rechecks the captured assignment.
Previously frozen deliveries retain their original assignment ID/generation;
they cannot move to a repeated or late-joining assignment.
Mission.RepeatPolicy is an immutable MissionRepeatPolicy. Missing metadata
uses Once; all mission copy paths preserve it alongside dialogue, items and
requirements. World stores the optional policy in
mission_repeat_policy(mission_id, content_revision), not as new reflected
columns on historical normalized content entries.
| Kind/value | Timing fields | Admission after a successful attempt |
|---|---|---|
Once / 0 |
Both null | Never, including after journal clear |
Immediate / 1 |
Both null | After the prior assignment is terminal and rewarded |
Cooldown / 2 |
Positive CooldownSeconds; null reset |
At or after the last committed reward time plus the duration |
Daily / 3 |
ResetSecondUtc in 0..86399; null cooldown |
When the last reward predates the current UTC window |
The UTC clock is injected through MissionApplication. A Daily window starts
at today's UTC midnight plus ResetSecondUtc; subtract one day if that instant
is still in the future. Midnight, host-local time and DST are not implicit
reset policies. Rewards are stamped after deferred inventory preparation and
before all final validation. A read-only commit-boundary check runs after every
participant has validated, including inventory plans enlisted by progress in
another mission. If reset crosses during those writes or database reads, the
transaction rolls back and the same valid conversation can retry.
No success packet is sent for that rollback.
There is one current journal row per character/mission, including pending
Success. Acceptance rechecks durable eligibility and capacity inside its
transaction, archives/removes an eligible terminal row, retires its old work,
and creates fresh objectives and a new assignment ID. New attempt generations
advance from prior stored generations. MissionLog carries the exact ID,
generation and stored content revision during creation and hydration.
legacy/unversioned remain compatible stored revisions, not permission to
substitute another attempt.
The committed native projection sends MissionCleared before MissionGained
when replacing a terminal journal. Once keeps its prior failure-dismissal
behavior, including Bootcamp's authored failure/reset flow; automatic terminal
replacement is an opt-in repeat behavior.
Transaction participants run Prepare, the initial flush,
FinalizePersistence, another flush if needed, all Validate guards, and
finally all read-only ValidateCommitBoundary checks before commit.
TransactionValidation.BeforeValidation stamps rewards during finalization;
TransactionValidation.AtCommitBoundary verifies the Daily window after all
final database validation. Neither validation phase may write receipts or
timestamps after the shared inventory plan has validated.
Forwarded shared-actor effects retain their experience-root execution identity
and separately store SourceRunId, SourceGeneration, SourceAssignmentId
and SourceAssignmentGeneration. Replacement cancels only that attempt's
forwarded effects. Post-commit route retirement matches the execution run,
generation and operation key, without resetting the root or removing its actors.
Removing a retired route's movement anchors the actor only while its controller
still runs that scripted movement. A retained movement reference alone does not
authorize replacing a newer attack or follow command.
Follow and attack controls also retain their current operation key, so retiring
an old attempt does not override a newer root command on the same actor.
Replay, reconstruction, acknowledgement and route callbacks check the durable
source identity; final persistence checks also guard callback/acknowledgement
writes. A cleared Once success can retain its authored experience state through
its exact terminal history row. Missing or changed source identities do not
authorize a new attempt's work.
Private instance keys can change when an instance is recreated. The current
private-map owner and durable source identity authorize replay, not equality
between historical instance keys; public map-key checks remain in force.
If reconstruction applies a durable Running or Applied effect but its
acknowledgement fails, the failure is logged and its owned route/control is
stopped. An ordinary retry tick can replay the operation. The retry retains the
exact execution and source identities, status, version, payload and concrete reconstructed intent, including pose
recovery instead of route movement. It rechecks current source ownership and
discards changed or superseded work, including newer route/follow/attack commands
on the same actor. It does not reconstruct the whole experience or replay
unrelated applied effects. Detachment or retirement clears the run's retry state;
fresh attachment still uses guarded reconstruction.
character_mission_history has one row per assignment ID. It records
AssignmentGeneration, terminal Outcome, CompletedAtUtc, Rewarded,
RewardedAtUtc and optional RewardWindowStartUtc. Outcomes use mission-state
values: Success = 1 is pending reward, Failed = 2, NotAssigned = 3 records
abandonment, and Completed = 4 is rewarded. Settling a pending reward updates
that assignment's row; a later failed attempt does not overwrite earlier
success.
Use MissionRuntimeRepository.EverSucceeded for lifetime completion,
LatestTerminal for the most recent outcome, LastRewardedAtUtc for timing,
and WasRewarded(assignmentId) for duplicate reward reconciliation.
HasHistory retains its success-only compatibility meaning; it does not mean
"any failure exists" and is not repeat admission policy. History now returns
multiple rows per mission: never call ToDictionary(missionId) on it.
For explicit non-success state requirements, a present journal takes precedence
over terminal history. Only lifetime success/reward prerequisites can override a
nonmatching current journal. Committed abandonment immediately projects
NotAssigned into runtime history without clearing lifetime success or the last
reward timestamp.
Reward receipts use (assignment_id, assignment_generation, "mission-reward").
Legacy generation-zero receipts remain valid evidence for that exact
assignment. A unique database index on
(character_id, mission_id, reward_window_start_utc) prevents competing Daily
claims. Other outcomes use null windows and do not collide on that guard.
Failure and abandonment consume neither cooldown nor Daily eligibility.
Unsettled Success, including preserved legacy history, blocks a new attempt.
ConsolidatedCharacterSchema creates per-assignment history, reward timestamps
and forwarding provenance directly. It does not import intermediate branch
journals, reconstruct missing attempts or infer prior reward claims. The
consolidated branch history requires fresh databases.
Bootcamp remains Once, private and unshareable. Persistent flags and
starting-experience state are separate from attempt history. Party acceptance
reuses these repeat and history rules; no Wilderness content is enabled.
The shared mission reward planner checks currency, experience and crossed
clone-credit milestones before provider writes. Milestones at levels 5, 15 and
30 each add one clone credit when crossed. Arithmetic overflow is a gameplay
rejection and rolls back the whole reward. An OverflowException originating
from a provider operation is not gameplay arithmetic and retains its identity;
do not catch it around the complete persistence call.
A publication failure after commit does not undo history, items or rewards. Reconnection restores committed state through the normal journal and inventory readers, and the exact assignment's receipt prevents another grant.
Use one executable trigger shape per transition. Mixing conversation, progress, area, objective-state and timer trigger shapes in one transition is rejected.
MissionTriggerKind |
Required binding |
|---|---|
Conversation |
NpcPackageId, PlayerFlagId |
ProgressEvent |
EventKind and event-specific parameters below |
AreaEntered |
AreaId |
ObjectiveState |
RelatedObjectiveId, RelatedState |
TimerElapsed |
Positive DurationSeconds |
Progress event values come from MissionProgressEventKind, not client opcodes:
| Value | Event | Parameter meaning |
|---|---|---|
| 0 | WaypointAcquired |
SubjectId is a waypoint ID |
| 1 | LogosAcquired |
SubjectId is a Logos ID |
| 2 | CreatureKilled |
SubjectId is a creature ID |
| 3 | MissionCompleted |
SubjectId is the mission ID |
| 4, 5 | ItemAcquired, ItemConsumed |
SubjectId is an item class, not template ID |
| 6 | InteractionUsed |
SubjectId is the interacted entity class |
| 7 | AreaEntered |
Prefer the typed area trigger |
| 8 | ItemEquipped |
SubjectId is class, or template with SourceSpawnResolved = true |
| 9 | AbilityHit |
SubjectId is action ID; CounterId is target creature ID |
| 10 | ScenarioEvent |
SubjectId is signal event ID; CounterId is scenario/sequence ID |
| 11 | DeadlineElapsed |
Prefer TimerElapsed; runtime supplies mission/objective scope |
| 12 | ObjectiveStateReached |
Prefer the typed objective-state trigger |
| 13 | ObjectHit |
SubjectId is entity class; CounterId is action ID |
For item counters, set InitialValue and TargetValue, omit CounterId, and
provide the native counter-text binding. Generic counters require all three.
Targets must exceed initial values. Do not combine counter ranges with
SourceSpawnResolved.
Multiple progress triggers form an all-distinct-subject set only for waypoints
or Logos; they are not a general AND expression. Counter text uses
ClientCounter0TextId, ClientCounter1TextId, ClientCounter2TextId.
Objective states are NotAssigned = 0 (revealed), Incomplete = 1 (active),
Completed = 2, Failed = 3, Inactive = 4 (hidden). Only Inactive is omitted
from client projections. Hidden objectives remain in definitions and storage.
MissionActionKind |
Binding |
|---|---|
RevealObjective |
TargetObjectiveId |
ActivateObjective |
TargetObjectiveId, ObjectiveState |
CompleteObjective |
Current transition's TargetObjectiveId, ObjectiveState |
GrantReward |
RewardId, the turn-in package rather than an immediate grant |
StartScenario |
ScenarioId, also declared in the typed scene |
ActivateSpawnGroup |
SpawnGroupId; bind Names["spawn-group-<id>"] to its sequence |
ShowIndicator |
IndicatorId |
SetPlayerFlag |
PlayerFlagId, PlayerFlagValue |
ShowAmbientConversation |
NpcPackageId is the greeting text ID for this historical shape |
IssueMissionItem (10) |
ItemIntentJson containing an IssueMissionItemIntent |
ConsumeMissionItem (11) |
ItemIntentJson containing a ConsumeMissionItemIntent |
RemoveMissionItems (12) |
ItemIntentJson containing a RemoveMissionItemsIntent |
Reward rows distinguish fixed and selectable items. At most one turn-in reward ID is selected per mission; selectable rewards require a native selection. Scene reward grants cannot contain selectable alternatives.
Use quest-item operations for temporary items issued by an assignment. Permanent items still belong in normal reward packages. A matching template alone never establishes assignment ownership.
MissionSceneDefinition.Items supplies immutable MissionItemBinding values:
| Field | Meaning |
|---|---|
ItemKey |
Mission-local key, at most 64 characters |
ItemTemplateId |
Existing World item template |
Scope |
AssignmentIssued or an explicit CharacterOwned cost |
MaximumQuantity |
Positive upper bound on an operation and on the currently held assignment quantity |
Completion, Failure, Abandonment |
Explicit Remove or Retain dispositions |
All binding fields, including terminal dispositions, are required when reading
the internal serialized document. Character-owned bindings must use Retain
for every terminal disposition: cleanup is not permission to spend ordinary
inventory. Prefer Remove for temporary assignment items. Retain keeps their
provenance and restrictions; it does not convert them to ordinary rewards.
After an assignment row is cleared, retained ownership still exists and requires
an explicit migration or character deletion to retire.
Automatic replacement of a terminal journal for a new attempt instead retires
all of that old assignment's issued stacks, even Retain bindings. Normal
rewards and character-owned costs are unaffected; old operation receipts cannot
issue or spend the replacement attempt's items.
The character intents are:
new IssueMissionItemIntent("recover-scanner", missionId, "scanner", templateId, 1);
new ConsumeMissionItemIntent("use-scanner", missionId, "scanner", 1,
MissionItemScope.AssignmentIssued);
new RemoveMissionItemsIntent("retire-scanner", missionId, "scanner");The issuing template, consuming scope and quantity must match the binding.
CharacterOwned consumption selects only unbound personal inventory; it never
spends another assignment's stack. An issue cannot use that scope. Removal
operates only on the exact assignment's concrete items for the named key.
For an ordinary objective transition, author the corresponding action kind and
serialize its intent into MissionActionEntry.ItemIntentJson with
MissionContentCodec.Options. Leave the other action parameter columns null.
Game executes the item plan in the same transaction as the selected objective
transition. Do not replace this with a queued scene grant after progression:
inventory capacity or a stale stack must reject the objective transition too.
NPC completion, typed native choices and object/corpse Continue share this path.
Flag actions and item actions on that transition are atomic together. An item
flush failure leaves neither the branch flag nor the issue receipt behind, and
the same still-valid conversation can retry. Successful native flag snapshots
and inventory publication occur only after commit.
For a scripted sequence, place these intents in SceneSequenceDefinition.Character.
Only mission-owned scenes with a matching assignment participant may use them,
not private experience scenes. The adapter checks the assignment ID and
assignment generation separately from the scene generation.
Acceptance issuance is explicit:
scene.Items = new()
{
new MissionItemBinding("scanner", templateId, MissionItemScope.AssignmentIssued, 1,
MissionItemCleanupDisposition.Remove, MissionItemCleanupDisposition.Remove,
MissionItemCleanupDisposition.Remove)
};
scene.AcceptanceItems = new()
{
new IssueMissionItemIntent("accept-scanner", missionId, "scanner", templateId, 1)
};Items and AcceptanceItems are optional and omitted from serialized content
when absent. All Mission copy paths preserve them and existing dialogue and
requirement metadata. A running deadline does not imply issuance. Reconnect
restores durable provenance and never reissues a consumed item.
character_mission_item identifies the character, mission, assignment, item
key, concrete durable item and owned quantity, and records its generation.
Each protected stack belongs wholly to one assignment/key. The ledger and
character_mission_item_receipt survive removal of the active
character_mission row; they cascade only with character deletion. Receipts are
keyed by character, assignment and operation key and retain the generation and
typed payload. An exact replay is a no-op; reusing a key with different content
or generation is rejected. Use stable, distinct operation keys for distinct
assignment operations.
Quest items, normal rewards, loot and explicit consumption share one transaction-local shadow inventory. Original ownership/slot/count checks run before planning and flushing; final inventory and interaction checks run after writes and before commit. Runtime state and packets receive the final inventory once. An issue followed by full consumption in one transaction creates no visible intermediate item. Rollback releases staged entities and leaves inventory and receipts unchanged. A still-valid dialogue can retry a failed write.
Each validation phase batch-reads persisted items and provenance, then checks indexed results. The final phase reads again after writes, including removed item IDs and ownership outside the current character. It also checks for orphaned assignment rows; earlier reads are not reused as commit validation.
Assignment items cannot be sold, traded, auctioned, banked, deposited in clan storage, equipped outside personal inventory, destroyed or merged through ordinary mutation paths. All personal-category moves and swaps are transactional and update both items' runtime slot metadata after commit. Normal rewards and loot cannot merge into protected stacks. Server handlers and durable inventory/item repositories enforce these rules; they do not depend on UI flags or the presence of a runtime marker alone.
Reloads and ordinary ability costs skip assignment-owned stacks even when a matching unbound stack comes later in the inventory. A missing runtime marker can be restored from validated durable ownership after commit; a conflicting marker or ownership change during the transaction still rejects. Clone-credit redemption rejects assignment ownership and commits the ordinary item cost and credit increment together before publishing either change.
ConsolidatedWorldSchema creates the final action payload schema.
SeedWorldContent installs the fixed BootcampMissionItemsV7 data through
WorldContentDataV1. Schema and data are separate paired migrations, so all
constraints exist before seeding. ItemIntentJson is fluent-mapped: adding a
reflected ColumnAttribute would change fixed preloader row widths.
For mission 1995, objective-3 Continue issues one template 11519; its
existing objective activation starts the 600-second deadline in that same
transaction. The planting transition consumes that assignment's item before
queuing the existing fuse. Timeout and abandonment remove only that attempt's
item. Mission 2005 explicitly issues one new item on acceptance.
ConsolidatedCharacterSchema installs the item ledger, receipts and quarantine
table. No migration adopts legacy bombs or reconstructs their receipts.
Runtime item operations and cleanup reject quarantined assignments; unrelated
items must not be adopted or deleted to resolve ambiguity.
Apply the paired World and Char migrations to fresh databases before admitting players. The five existing Bootcamp missions remain the only enabled production definitions.
MissionInteractionPolicy in Game is the admission boundary for NPC and authored
object conversations. NpcManager.RequestNpcConverse opens a
MissionConversationSession on the client; acceptance, objective completion,
choice, turn-in and legacy reward callbacks require a matching topic from that session.
Calling a mutation method does not open a conversation.
The session captures the character and target references, entity IDs, runtime map and epoch, NPC package, source owner/scene generation or public lease, and the topics actually sent. Each topic pins the loaded content revision and, for an existing mission, its durable assignment ID, generation and stored revision. Legacy/unversioned assignments retain their existing compatibility behavior; their exact stored revision is still captured and checked. Opening does not migrate or replace an assignment. Repeat offers capture the terminal assignment being replaced and the latest history identity. An offer opened with no assignment cannot be replayed after another attempt was accepted, settled and cleared. A stale runtime log cannot authorize a callback against a newer durable assignment. A genuine NPC/object reopen hydrates changed attempt identities from a consistent durable snapshot before classifying topics; the mutation callback itself never performs this rebinding. Other guards, including coherent inventory and currency state, remain required.
Mission-scoped source scenes must independently match the current operational
definition's revision. The legacy/unversioned compatibility applies to the
stored assignment, not to the scene release. Changing either compatible stored
revision to the other while dialogue is open still invalidates that dialogue.
Admission requires a living registered character with no pending logout, removal or transfer. Ordinary NPCs must be living and interactable. An enabled authored object needs NPC augmentation; a corpse's dead-body appearance does not make that object ineligible. Targets must be registered in the same runtime map, with eligible ownership and current scene/lease state. Shared public NPCs remain shared.
The server uses finite, inclusive 5-metre origin distance in three dimensions,
not the generic usable-object 20-metre limit. Native
shared/gameconstants.pyo defines MAX_CONVERSATION_RANGE = 5, and
Actor.IsInConversationRange passes it to body.InRadiusOf. Server entities do
not expose the native body bounds, so body-distance acceptance can differ at
model edges. Native body-boundary and line-of-sight behavior remain separate
client acceptance checks. There is no authoritative server LOS implementation;
navmesh reachability is not used as a substitute.
Callbacks revalidate the session, source and assignment inside their existing
write transaction before applying mission changes. Their terminal
ValidateCommitBoundary check runs after every participant's validation reads
and any required fact reads, including deferred inventory validation. NPC
acceptance and rewards use the same ordering. Ownership and scene/lease reads
are followed by another live session/target check; a reader cannot invalidate a
target that was only checked earlier in the transaction. Becoming eligible after
opening does not add a topic. Abandoning and accepting again does not authorize
an old objective or reward callback. Native requests carry no nonce: identical
input after a legitimately reopened, current conversation cannot be identified
as an old packet.
Transitions without a DeadlineElapsed rule do not read deadline state merely
to detect a timer notification. This does not remove the independent dialogue,
planner or final persisted assignment/source guards. The simple non-deadline
lifecycle regression measures eight SELECTs: three character ownership reads,
four assignment reads and one objective/counter aggregate, with one save and
unchanged ordered post-commit packets. It is not a universal budget for item,
requirement or scene-bearing transitions.
A successful mutation consumes the session. A failed durable write can retry the same still-valid session. Opening another conversation, character replacement, transfer/logout/disconnect, map detachment, or target removal invalidates it. Accepted movement outside range also invalidates it; returning does not restore it.
MissionConversationTopicKind distinguishes acceptance, objective completion,
mission completion, legacy reward, objective choice, mission reminder and
ambient objective topics. Reminders and ambient topics cannot authorize mutation.
Older Success saves may use the legacy
reward callback against the offered completion presentation for that same
assignment. Trusted typed scene transitions keep their scene authorization and
do not require player dialogue.
MissionSceneDefinition.Dialogue is optional typed metadata. Each
MissionDialogueTopicDefinition supplies a progression ObjectiveId,
NpcPackageId, PlayerFlagId and Kind. DialogObjectiveId, when present,
overrides only the native text/callback objective ID. The flag ID selects native
text; it does not instruct the server to write a character flag.
MissionDialogueKind |
Native Converse payload |
Transition binding |
|---|---|---|
Completion |
ObjectiveComplete: list of mission/objective/flag triples |
One TransitionId |
Reminder |
MissionReminder: list of mission IDs |
None; read-only |
Ambient |
ObjectiveAmbient: list of mission/objective/flag triples |
None; read-only |
Choice |
ObjectiveChoice: list of mission/objective/flag triples |
Choices, mapping native indices to transition IDs |
Typed topics replace the default completion presentation for the same
progression objective/package/flag binding. Other conversation bindings remain
unchanged. Without metadata, normalized Conversation triggers retain their
completion behavior; the existing objective Reminder discriminator projects
as ambient objective dialogue.
Completion and choice bindings must reference existing executable conversation transitions on their own objective and NPC package/flag. Each selected transition must leave the objective completed or failed; a null target state means completed. Event/counter triggers cannot be repurposed as choice transitions. Missing or ambiguous transitions, duplicate native topics, unknown kinds, missing NPC packages and unbound scene sequences fail authoring validation.
The native window has callbacks 1, 2 and 3, not zero-based indices. Author all three mappings before exposing a choice topic. Sparse maps are rejected: the packet contains no option labels or list of available indices, so the server cannot hide a missing mapping from the native window.
For isolated tests, mission 339, objective 8, package 586, flag 1 has
reviewed native body text 4318 and choice texts 4319/4320/4321. Given authored
transitions 101/102/103, its metadata is:
scene.Dialogue = new()
{
new(8, 586, 1, MissionDialogueKind.Choice,
choices: new Dictionary<int, uint>
{
[1] = 101, [2] = 102, [3] = 103
})
};Those transition IDs are server-authored mappings, not text IDs. Do not enable
mission 339 in production or invent translations from this example. The five
Bootcamp missions remain the only enabled production content.
PerformNPCChoice (497) accepts exactly
(ulong npcEntityId, uint missionId, uint objectiveId, uint playerFlagId, int choiceIdx).
It uses the same session and transactional transition planner as Continue.
Objective changes, flag writes and StartScenario/ActivateSpawnGroup inputs persist together before
success publication. Scene inputs use the existing durable inbox and grant
receipts. GrantReward actions still declare a turn-in package; immediate scene
grants use GrantRewardIntent.
An opened choice captures its immutable mapping as well as the P2 source and
assignment identity. A successful choice consumes the session and sends an
EndConversation marker. Repeated or competing callbacks cannot select another
branch; a rolled-back write may retry the same still-valid session.
ConversePacket validates every entry and nested payload before writing any
bytes. ImportantGreering (the existing enum spelling) is a scalar greeting ID,
EndConversation carries a boolean presence marker, and ForcedByScript is a
native boolean controlling the client's range-based auto-close behavior.
Bind MissionRequirement expressions to MissionSceneDefinition.Requirement,
ObjectiveRequirements[objectiveId], or TurnInRequirement.
| C# type | Arguments |
|---|---|
AllRequirements, AnyRequirement |
List of expressions |
NotRequirement |
One expression |
LevelRequirement |
Minimum level |
MissionStateRequirement |
Mission ID, optional state, optional accepted flag |
FlagRequirement |
Server-owned flag ID and value |
CustomRequirement |
Registered pure handler key |
var requirement = new AllRequirements(new MissionRequirement[]
{
new LevelRequirement(4),
new NotRequirement(new CustomRequirement("character.starting-experience-completed"))
});MissionStateRequirement.Accepted = true requires journal presence; otherwise
history can satisfy it. With no explicit state, completion accepts Success or
Completed. Successful history is lifetime evidence: an active or failed repeat
does not mask it. Explicit Completed also checks rewarded history, while
failure outcomes use the current/latest state. Accepted = true never uses
archived history. These are mission states, not objective states.
Current custom keys are example.even-level,
account.starting-experience-entitlement, and
character.starting-experience-completed. Handlers declare their required
boolean facts; Game must provide them for both runtime queries and durable
transactions. Unknown handlers/facts fail explicitly. Nesting is bounded to 32.
character.starting-experience-active is also available for durable radio-source
requirements; it is not a runtime-cache predicate. Both active and completed
starting-experience facts use the fresh ReadState scalar query during durable
validation rather than EF's previously tracked state.
The Char database owns character_flag(character_id, flag_id, value), keyed by
(character_id, flag_id) with cascading deletion when the character is deleted.
IDs and values are unsigned 32-bit integers. A missing row means unset;
an explicit zero is a stored value and does not match an unset flag.
Flags belong to the character, not its character-selection slot or account.
Any subsystem can use ICharUnitOfWork.CharacterFlags:
| Operation | Meaning |
|---|---|
Get(characterId) |
Read all flags as an ID/value dictionary |
GetValue(characterId, flagId) |
Read a nullable value; null means unset |
HasValue(characterId, flagId, value = 1) |
Match a stored value exactly |
Set(characterId, flagId, value) |
Insert or update one flag |
Remove(characterId, flagId) |
Unset one flag |
Add(entry) |
Insert a new row, rejecting a duplicate key |
Mission SetPlayerFlag actions write in the same transaction as their objective
transition. NPC dialogue, ordinary progress, failure and abandonment follow the
same rule. SetCharacterFlagIntent(operationKey, flagId, value) provides typed
scene writes; a null value removes the flag. Composed scene plans publish the
last flag snapshot in transaction order, not an earlier child plan's snapshot.
Runtime flags change only after commit and are loaded during character selection.
The Manifestation.PlayerFlags dictionary is a cache, not a persistence API.
Transactional prerequisites and FlagRequirement checks use the database.
Composed mission state follows the same final-state rule. Transaction finalization freezes each touched assignment's full objective states, both counter sets, mission state and completable state from the tracked aggregates after all character intents have been planned. The scene converges that state before publishing flags or child packets. Child progress/failure plans do not reapply intermediate state, while their packet order is retained. Full snapshots also preserve committed successor counters when a competing client was stale; neither a post-commit database reread nor reconnect is needed for immediate turn-in. Publication still checks the exact assignment, generation and revision.
Normal mission flag IDs use 1..2147483647. IDs 2147483648..4294967295 are
reserved for named server state declared in CharacterFlagIds; mission
SetPlayerFlag actions cannot write that range. BootcampComplete is
0x80000001 with value 1. Bootcamp completion/skip and requirement facts now
use that flag instead of character_qualification.
The old qualification enum and SetQualificationIntent remain only to decode
frozen content and historical scene intents; their adapter writes the new flag.
There is no current qualification entity, repository or table. Mission history,
reward receipts and character_starting_experience remain separate records.
PlayerFlags (opcode 710) takes one collection of flag IDs, not a bitmask.
The native Recv_PlayerFlags(playerFlagIds) replaces its collection;
HasPlayerFlag(id) tests membership. PlayerFlagsPacket writes
tuple(1) -> list(count) -> uint IDs and requires an explicit collection.
An empty list clears native membership.
Game's CharacterFlagProjection selects IDs accepted by
CharacterFlagIds.IsMissionFlag(id) whose stored values are nonzero, in ascending
ID order. It sends neither numeric values nor reserved server IDs such as
BootcampComplete. Stored zero and unset both project to absence without
changing their distinct database meanings. Any nonzero value grants native
membership, while server-side requirements still compare the numeric value.
Owner introduction and map reintroduction use the committed
Manifestation.PlayerFlags cache. Introductions to another player contain an
empty list, never the introduced character's private flags.
NPC objectives, ordinary progress, failure/abandonment, scene intents and starting-experience updates converge the cache after their transaction commits. Changed native membership produces a full owner-only replacement snapshot; nonzero-to-nonzero changes do not resend an identical set. A composite plan applies its final flag snapshot once, and child plans cannot restore earlier writes. NPC flags converge before follow-up scenarios can commit newer changes.
If publication fails, the owning connection retains a dirty snapshot. The map tick retries the latest committed cache; a successful owner introduction also satisfies the pending publication. Recovery does not reread database flags or replay rewards. Pending state is bound to the connection, manifestation and character ID, and is not sent while disconnected or transferring. A fresh login reloads committed flags through normal character selection.
MissionSceneDefinition contains Script, StateVersion, role-keyed Actors,
named Routes, numeric Sequences, named sequence entry points, eligibility
requirements, Credit, optional Audio, and an optional PublicEncounter.
Optional Dialogue supplies the native topic and branch bindings described above.
Scripts receive only SceneContext and SceneObservation, not clients or EF
contexts. Decisions contain typed intents, signals, timers and JSON checkpoints.
Internal database/checkpoint JSON does not introduce a file-based publish step.
MissionAudioDefinition is available to any mission, including main-world
quests and data-only missions. Author it on the mission's scene definition
(a metadata-only scene may omit Script):
OfferAudioSetId: native briefing narration in the existing offer tuple.Events: optionalMissionAudioEvent.AcceptedandCompletedvoice cues.Announcements: greeting-text ID to audio-set ID, paired with authored ambient mission announcements.
Use verified client audio-set IDs, not sound-file IDs. Omit audio metadata for
a silent mission; zero or invalid bindings are rejected. Do not bind the same
briefing to both offer and acceptance unless a repeated readout is intended.
The Game adapter uses the native offer slot or existing voice-over RPC
(PlayTutorialAudio, a historical wire name that is not restricted to tutorial
zones). There are no Bootcamp-ID checks in that playback module.
Scene scripts can also use the existing PresentationIntent with
PresentationKind.Audio for explicitly timed cues.
SceneActorKind |
TemplateId means |
|---|---|
PublicSpawn |
Exact existing static spawn-pool ID |
Creature |
Creature definition ID |
Object, PracticeTarget |
Entity class ID |
Role dictionary keys must equal SceneActorDefinition.Role. Created actors need
ScenePosition(X, Y, Z), with Y as height. Other fields include Orientation,
InitiallyInteractable, InitialObjectState, WindupMilliseconds, loot
bindings and mission/group/spawn attribution.
An object actor can have an optional SceneObjectConversation binding:
MissionId and ObjectiveId identify the progression target;
NpcPackageId, DialogObjectiveId and PlayerFlagId select an existing native
objective dialogue. Its entity class must already support the client's NPC
augmentation. The adapter publishes NPC metadata and opens Converse on that
same object, without adding a creature or sending unsupported Usable packets.
Kind defaults to Completion; other kinds require an exactly matching typed
mission dialogue topic. In particular, Kind = Choice uses that topic's authored
index-to-transition map rather than a separate object choice system.
The client must open the dialog before Continue, remain near the same object
in the same instance, and retain the same mission assignment. Completion
uses the existing transactional progress planner, restricted to the bound
objective. Opening a dialog never grants inventory or advances progress.
SharedKey is for private experience-owned actors. It does not make personal
copies of main-world NPCs. Keep the role consistent between mission and
experience definitions.
SceneRoute contains ordered SceneWaypoint records, speed in metres/second
and optional ResumeAtDestination. Waypoint orientation is radians; pauses are
milliseconds. Scripted movement needs a complete loaded-navmesh route.
RunRouteIntent.ResumeAfterCombat lets a combat-capable actor pause its route
while fighting and resume it afterward. Removing that actor cancels its route.
Optional MissionSceneDefinition.DefeatSequences maps actor roles to sequence
IDs. A confirmed defeat persists the actor outcome and queues that sequence in
one transaction, even when no player receives kill rewards. The normal durable
scene inbox delivers it once and retries failed scene writes. Scripts can track
the defeated roles in their checkpoint to gate a finite encounter; losing a
route or despawning an actor is not a confirmed defeat.
| World intent | Purpose |
|---|---|
EnsureActorIntent |
Ensure the authored actor is present |
RemoveActorIntent |
Remove/release its role |
SetInteractionIntent |
Set enabled/object-state properties |
RunRouteIntent |
Run an authored route |
AttackActorIntent |
Engage a living hostile actor in the same map; omitted TargetRole means the run's owner |
FollowActorIntent |
Follow a character; ID zero means owner |
PresentationIntent |
Tutorial, audio or greeting using an existing ID |
TransferIntent |
Transfer to a map and authored position |
RestoreActorPoseIntent |
Recover a static actor in its owning private map |
TransitionObjectStateIntent |
Send the native usable transition to an object state, with an optional windup in milliseconds |
Character intents are GrantRewardIntent, GrantAbilityIntent,
SetCharacterFlagIntent, SetEntitlementIntent, ObjectiveIntent, and
MissionDeadlineIntent. SetQualificationIntent is retained for historical
content compatibility. Game applies them through its transaction adapters.
Operation keys are nonempty, at most 96 characters and unique across a scene's
authored sequences. Timers have names of at most 64 characters, a target sequence
and WallClock or ActiveScene policy. One decision permits at most 64 world
intents, character intents and signals combined. A checkpoint is a JSON object
limited to 16,384 UTF-8 bytes.
SceneActorDefinition.GameplayPolicy is an optional ActorGameplayPolicy for
Creature and PublicSpawn roles, in public or private scenes. It is invalid
on objects and practice targets. Absence is serialized as no field, including
with the default serializer used by older data helpers.
Resolution uses the current role policy on the exact actor first, then the existing private-experience policy where applicable, then ordinary behavior. Role policies are complete overrides, not field-by-field merges.
| Field | Default and contract |
|---|---|
Invulnerable |
false; blocks normal direct and missile damage and kill handling |
DefenseRadius |
0; finite and nonnegative, in metres; positive values enable defense |
DefenseTargetTag |
Absent; required when defense is enabled |
Tags |
Empty list; nonempty, unique, case-sensitive tokens without whitespace |
RewardScenarioKills |
false; explicit kill-reward eligibility for this role, including leased static actors |
TrackParticipation |
false; tracks eligible damage participation for normal defender final-blow credit |
Loot |
Absent; optional AuthoredLootProfile, used by the existing corpse-loot path when the kill is eligible |
| Actor/policy source | Reward behavior |
|---|---|
| Ordinary static actor, no role policy | Normal rewards, including the existing private-map behavior |
| Scene-created actor, no role or applicable experience policy | No kill rewards |
| Explicit role policy | Its RewardScenarioKills, even when false or when the actor is a leased static spawn |
| Private scene-created actor with no role policy and an experience policy | The experience policy's reward setting |
Enabling rewards does not override faction or killer eligibility. Eligible
kills use existing XP, adrenaline, harvest and corpse-loot logic. Group mission
credit still follows MissionCreditPolicy; XP and corpse ownership are not
redistributed to the party.
AuthoredLootProfile snapshots its LootDrop list. A drop has a nonzero
TemplateId, ChancePercent in 0-100, and inclusive positive Minimum and
Maximum quantities (Maximum < int.MaxValue). Catalog loading rejects
missing item-template/class references and quantities exceeding the class's
stack size. Scene bindings, experience catalogs and public lease bindings
copy tag lists into immutable policy snapshots.
Definitions using the same private SharedKey must agree on kind, template and
policy, including whether the policy is absent. Tag order is not significant.
A shared key may identify only one role in each scene.
Game installs the snapshot after the owning transaction commits, before actor
publication. The binding checks the actor object, template, spawn ownership, run/role,
generation and map epoch. Death captures the policy needed for that kill's
loot, then clears the live override. Removal, reset/return and lease release
discard old overrides; a replacement or later lease cannot inherit them.
Wait/Continue reconnects retain the same public actors and policy snapshots.
Private reconstruction reapplies its role binding; releasing one reference to
a private shared actor preserves the policy of a remaining scene reference.
Public scene termination removes live actors and their spawn pools, but keeps
already-earned corpse loot on the normal corpse timer. This does not retain
the dead actor's role policy or make it available to a later lease. Explicit
actor-removal intents and private-map cleanup retain their existing behavior.
PublicEncounterBinding identifies mission, spawn, role, script and owner-loss
policy (Reset, Wait, Continue). Main-world NPCs remain public; admission
reserves the existing spawn. Deliberate initiator abandonment cancels its run.
AllowPartyJoin explicitly permits later shared assignments to join that run;
it defaults to false and is independent of IncludeEligibleParty, which
captures already-assigned eligible members at reservation time. Neither option
automatically accepts a mission for anyone.
MissionCreditPolicy supports Personal, NearbyParty and
EncounterParticipants. Shared modes require a positive finite radius and
matching active objectives; participant mode also requires participation.
Only eligible creature-kill/scenario-event objectives share credit, not personal
dialogue, inventory actions, acceptance or reward choices.
MissionExperienceDefinition has a key, revision, map context, private-per-character
flag, scene, mission triggers and actor policies. Triggers respond to Accepted,
Rewarded, Completeable or Departing for a mission and name an experience
sequence. Completeable is also derived from current active assignments during
reconnect; Departing is emitted by the authorized starting-experience boarding
flow. Each trigger must reference a loaded mission and a declared sequence.
Experience-level ActorPolicies remain a private-map fallback; public roles
use the actor metadata above rather than installing a map/template override.
The fixed C# Bootcamp example is
BootcampMissionDataV1.Experience.cs.
The composable helper
BootcampFinaleDataV2.cs
supplies finale presentation and experience bindings.
BootcampAudioAndCreditsV3.cs
supplies native audio bindings and the configured credit rewards.
BootcampExtractionDataV5.cs
authors a finite assault, allied defenders and defeat-sequence bindings.
SeedWorldContent applies these fixed helpers in dependency order.
The mission-local extraction script keeps its additive checkpoint compatible
with earlier sequence-only saves; the generic mission manager has no
Bootcamp-specific enemy counters or spawn coordinates.
All helpers take a MigrationBuilder. Provider migration classes can share
these C# operations:
| Method | Operation |
|---|---|
EnableMission |
Set the selected definition's enabled flag |
InsertScene, UpdateScene |
Write a typed scene for a mission/revision |
InsertExperience, UpdateExperience |
Write a typed private experience and enabled flag |
Use ordinary EF data operations for normalized rows. Keep migration-owned data fixed, add new migrations for changes, and scaffold provider schema changes with EF rather than hand-editing generated snapshots.