Browse Source

Add the authored scene event key section to the SDK pack

A scene resource references its event graph; the graph holds the gates the server
publishes with set_scene_events, each carrying an event key and its ordinal in the
scene's progression. The pack now carries one row per gate, ordered by scene slot
then gate, with the resource and graph tags the row was read from.

The catalog exposes the section and a per-slot range, and relation validation
refuses a pack whose keys do not belong to a type-43 slot with exactly one
resource of the stated tag. Format version 39.
Fozkais 1 week ago
parent
commit
bfbdb42875

+ 25 - 0
Sunrise/src/state/activity_sdk/activity_sdk_catalog.cpp

@@ -307,6 +307,10 @@ std::span<const format::ActorSequenceBinding> Catalog::actor_sequence_bindings()
     return rows<format::ActorSequenceBinding>(
     return rows<format::ActorSequenceBinding>(
         header_, view_, format::SectionIndex::actorSequenceBindings);
         header_, view_, format::SectionIndex::actorSequenceBindings);
 }
 }
+std::span<const format::AuthoredSceneEventKey> Catalog::authored_scene_event_keys() const noexcept {
+    return rows<format::AuthoredSceneEventKey>(
+        header_, view_, format::SectionIndex::authoredSceneEventKeys);
+}
 
 
 std::span<const format::ActorStateName> Catalog::actor_state_names() const noexcept {
 std::span<const format::ActorStateName> Catalog::actor_state_names() const noexcept {
     return rows<format::ActorStateName>(header_, view_, format::SectionIndex::actorStateNames);
     return rows<format::ActorStateName>(header_, view_, format::SectionIndex::actorStateNames);
@@ -789,6 +793,27 @@ slot_authored_scene_resources(const Catalog& catalog, const format::Slot& slot)
                           static_cast<std::size_t>(last - first));
                           static_cast<std::size_t>(last - first));
 }
 }
 
 
+/** Relies on scene-slot ordering to return one contiguous zero-copy key range. */
+std::span<const format::AuthoredSceneEventKey>
+slot_authored_scene_event_keys(const Catalog& catalog, const format::Slot& slot) noexcept {
+    const auto slots = catalog.slots();
+    if (!owns(slots, slot)) {
+        return {};
+    }
+    const auto values = catalog.authored_scene_event_keys();
+    const std::uint32_t slotIndex = static_cast<std::uint32_t>(&slot - slots.data());
+    const auto first =
+        std::lower_bound(values.begin(), values.end(), slotIndex, [](const auto& row, auto index) {
+            return row.sceneSlotIndex < index;
+        });
+    const auto last =
+        std::upper_bound(first, values.end(), slotIndex, [](auto index, const auto& row) {
+            return index < row.sceneSlotIndex;
+        });
+    return values.subspan(static_cast<std::size_t>(first - values.begin()),
+                          static_cast<std::size_t>(last - first));
+}
+
 /** Relies on scene-slot ordering to return one contiguous zero-copy edge range. */
 /** Relies on scene-slot ordering to return one contiguous zero-copy edge range. */
 std::span<const format::AuthoredSceneSquadEdge>
 std::span<const format::AuthoredSceneSquadEdge>
 slot_authored_scene_squad_edges(const Catalog& catalog, const format::Slot& slot) noexcept {
 slot_authored_scene_squad_edges(const Catalog& catalog, const format::Slot& slot) noexcept {

+ 1 - 0
Sunrise/src/state/activity_sdk/activity_sdk_loader.cpp

@@ -66,6 +66,7 @@ constexpr std::array<std::uint32_t, format::kSectionCount> kExpectedStrides{
     sizeof(format::CombatObjectiveGroup),
     sizeof(format::CombatObjectiveGroup),
     sizeof(format::ActorAbility),
     sizeof(format::ActorAbility),
     sizeof(format::ActorAbilityTarget),
     sizeof(format::ActorAbilityTarget),
+    sizeof(format::AuthoredSceneEventKey),
 };
 };
 
 
 /** Checks all section bounds before any typed row pointer is formed. */
 /** Checks all section bounds before any typed row pointer is formed. */

+ 43 - 7
Sunrise/src/state/activity_sdk/activity_sdk_relation_validation.cpp

@@ -21,6 +21,40 @@ constexpr std::uint32_t kAbilityAuthSchema = 0x80807DA1U;
 constexpr std::uint32_t kAbilityTargetSlotType = 58U;
 constexpr std::uint32_t kAbilityTargetSlotType = 58U;
 constexpr std::uint32_t kAbilityTargetComponentClass = 0x80807D9BU;
 constexpr std::uint32_t kAbilityTargetComponentClass = 0x80807D9BU;
 
 
+/**
+ * @return True when every scene event key belongs to a resourced type-43 slot, names that
+ *         slot's resource, and the rows come in slot then gate order without repeats.
+ */
+[[nodiscard]] bool authored_scene_event_keys(const Catalog& catalog) noexcept {
+    const auto keys = catalog.authored_scene_event_keys();
+    const auto slots = catalog.slots();
+    for (std::size_t index = 0; index < keys.size(); ++index) {
+        const format::AuthoredSceneEventKey& row = keys[index];
+        if (row.sceneSlotIndex >= slots.size() || row.flags != format::kAuthoredSceneEventKeyExact
+            || row.reserved != 0 || row.key == 0 || row.key == format::kAbsentIndex
+            || row.graphTag == 0 || row.graphTag == format::kAbsentIndex) {
+            return false;
+        }
+        const format::Slot& slot = slots[row.sceneSlotIndex];
+        if (slot.slotType != format::kAuthoredSceneSlotType) {
+            return false;
+        }
+        const auto resources = slot_authored_scene_resources(catalog, slot);
+        if (resources.size() != 1 || resources.front().resourceTag != row.resourceTag) {
+            return false;
+        }
+        if (index != 0) {
+            const format::AuthoredSceneEventKey& previous = keys[index - 1];
+            if (previous.sceneSlotIndex > row.sceneSlotIndex
+                || (previous.sceneSlotIndex == row.sceneSlotIndex
+                    && previous.gateOffset >= row.gateOffset)) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
 /** @return True when every task target names slots and objectives the catalog holds. */
 /** @return True when every task target names slots and objectives the catalog holds. */
 [[nodiscard]] bool task_targets(const Catalog& catalog) noexcept {
 [[nodiscard]] bool task_targets(const Catalog& catalog) noexcept {
     const auto targets = catalog.task_targets();
     const auto targets = catalog.task_targets();
@@ -582,13 +616,15 @@ bool relations(const Catalog& catalog) {
         bool (*run)(const Catalog&) noexcept;
         bool (*run)(const Catalog&) noexcept;
     };
     };
     // Every relation check a catalog must pass, named so a refusal reports which one failed.
     // Every relation check a catalog must pass, named so a refusal reports which one failed.
-    static constexpr std::array<Check, 7> kChecks{{{"task_targets", &task_targets},
-                                                   {"authored_text", &authored_text},
-                                                   {"behavior_edges", &behavior_edges},
-                                                   {"actor_semantics", &actor_semantics},
-                                                   {"runtime_semantics", &runtime_semantics},
-                                                   {"sobject_semantics", &sobject_semantics},
-                                                   {"entity_types", &entity_types}}};
+    static constexpr std::array<Check, 8> kChecks{
+        {{"authored_scene_event_keys", &authored_scene_event_keys},
+         {"task_targets", &task_targets},
+         {"authored_text", &authored_text},
+         {"behavior_edges", &behavior_edges},
+         {"actor_semantics", &actor_semantics},
+         {"runtime_semantics", &runtime_semantics},
+         {"sobject_semantics", &sobject_semantics},
+         {"entity_types", &entity_types}}};
     for (const Check& check : kChecks) {
     for (const Check& check : kChecks) {
         if (!check.run(catalog)) {
         if (!check.run(catalog)) {
             remember(check.name);
             remember(check.name);

+ 40 - 3
Sunrise/src/state/activity_sdk/format.h

@@ -12,7 +12,7 @@ inline constexpr std::array<char, 8> kMagic{'S', 'R', 'S', 'D', 'K', 'P', '0', '
 /** Runtime-pack schema version accepted by this reader. */
 /** Runtime-pack schema version accepted by this reader. */
 inline constexpr std::uint32_t kVersion = 41;
 inline constexpr std::uint32_t kVersion = 41;
 /** The ABI contains only activity identity, topology, placement, and panel metadata. */
 /** The ABI contains only activity identity, topology, placement, and panel metadata. */
-inline constexpr std::uint32_t kSectionCount = 50;
+inline constexpr std::uint32_t kSectionCount = 51;
 #if defined(SUNRISE_ACTIVITY_SDK_TESTING)
 #if defined(SUNRISE_ACTIVITY_SDK_TESTING)
 /** The runtime accepts only the checked generated SDK build. Each pin is a SHA-256 digest. */
 /** The runtime accepts only the checked generated SDK build. Each pin is a SHA-256 digest. */
 inline constexpr std::array<std::byte, 32> kExpectedSdkBuildDigest{
 inline constexpr std::array<std::byte, 32> kExpectedSdkBuildDigest{
@@ -273,6 +273,25 @@ inline constexpr std::uint32_t kAuthoredSceneSenseSchema = 0x8080626AU;
 inline constexpr std::uint32_t kAuthoredSceneAuthSchema = 0x8080626BU;
 inline constexpr std::uint32_t kAuthoredSceneAuthSchema = 0x8080626BU;
 inline constexpr std::uint32_t kAuthoredSceneResourceRelativeOffset = 0x60U;
 inline constexpr std::uint32_t kAuthoredSceneResourceRelativeOffset = 0x60U;
 inline constexpr std::uint32_t kAuthoredSceneResourceClass = 0x80809C0FU;
 inline constexpr std::uint32_t kAuthoredSceneResourceClass = 0x80809C0FU;
+/**
+ * A scene resource references its event graph at a fixed field. In the graph, the gates form
+ * one typed package array: a marker, a 64-bit count, the element class, then the elements. A
+ * gate element opens with the graph's own tag and its class, and carries the event key the
+ * server publishes and the gate's ordinal in the scene's progression (-1 for the opening gate
+ * every scene shares).
+ */
+inline constexpr std::uint32_t kAuthoredSceneGraphRelativeOffset = 0xC0U;
+inline constexpr std::uint32_t kPackageArrayMarker = 0x80809FBDU;
+inline constexpr std::uint32_t kAuthoredSceneGateArrayClass = 0x8080638AU;
+inline constexpr std::uint32_t kAuthoredSceneGateClass = 0x8080637DU;
+inline constexpr std::uint32_t kAuthoredSceneGateSize = 0x60U;
+inline constexpr std::uint32_t kAuthoredSceneGateClassOffset = 0x4U;
+inline constexpr std::uint32_t kAuthoredSceneGateKeyOffset = 0x10U;
+inline constexpr std::uint32_t kAuthoredSceneGateOrdinalOffset = 0x58U;
+/** More gates than any installed scene declares; a larger count is a misread graph. */
+inline constexpr std::uint32_t kAuthoredSceneGateCapacity = 64U;
+inline constexpr std::uint32_t kAuthoredSceneEventKeyExact = 0x1U;
+inline constexpr std::uint32_t kAuthoredSceneEventKeyFlagMask = kAuthoredSceneEventKeyExact;
 /** Scene-to-squad rows retain one exact same-object package reference. */
 /** Scene-to-squad rows retain one exact same-object package reference. */
 inline constexpr std::uint32_t kAuthoredSceneSquadSameObjectExact = 0x1U;
 inline constexpr std::uint32_t kAuthoredSceneSquadSameObjectExact = 0x1U;
 /** A type-42 performance sensor names the same-object squad it drives at descriptor +0x58. */
 /** A type-42 performance sensor names the same-object squad it drives at descriptor +0x58. */
@@ -387,7 +406,7 @@ inline constexpr std::int64_t kAbsentSignedValue = (-0x7FFFFFFFFFFFFFFFLL - 1);
 
 
 /** Fixed packed byte sizes make producer and consumer ABI drift fail at compile time. */
 /** Fixed packed byte sizes make producer and consumer ABI drift fail at compile time. */
 inline constexpr std::size_t kSectionSize = 16;
 inline constexpr std::size_t kSectionSize = 16;
-inline constexpr std::size_t kHeaderSize = 960;
+inline constexpr std::size_t kHeaderSize = 976;
 inline constexpr std::size_t kStringRefSize = 8;
 inline constexpr std::size_t kStringRefSize = 8;
 inline constexpr std::size_t kRangeSize = 8;
 inline constexpr std::size_t kRangeSize = 8;
 inline constexpr std::size_t kActivitySize = 124;
 inline constexpr std::size_t kActivitySize = 124;
@@ -411,6 +430,7 @@ inline constexpr std::size_t kSquadSize = 52;
 inline constexpr std::size_t kSquadMemberSize = 44;
 inline constexpr std::size_t kSquadMemberSize = 44;
 inline constexpr std::size_t kSquadAnchorSize = 48;
 inline constexpr std::size_t kSquadAnchorSize = 48;
 inline constexpr std::size_t kAuthoredSceneResourceSize = 40;
 inline constexpr std::size_t kAuthoredSceneResourceSize = 40;
+inline constexpr std::size_t kAuthoredSceneEventKeySize = 40;
 inline constexpr std::size_t kAuthoredSceneSquadEdgeSize = 40;
 inline constexpr std::size_t kAuthoredSceneSquadEdgeSize = 40;
 inline constexpr std::size_t kTaskTargetSize = 44;
 inline constexpr std::size_t kTaskTargetSize = 44;
 inline constexpr std::size_t kDialogueCueTextSize = 36;
 inline constexpr std::size_t kDialogueCueTextSize = 36;
@@ -727,9 +747,11 @@ enum class SectionIndex : std::uint32_t {
     combatObjectiveGroups,
     combatObjectiveGroups,
     actorAbilities,
     actorAbilities,
     actorAbilityTargets,
     actorAbilityTargets,
+    authoredSceneEventKeys,
 };
 };
 
 
-static_assert(static_cast<std::uint32_t>(SectionIndex::actorAbilityTargets) + 1 == kSectionCount);
+static_assert(static_cast<std::uint32_t>(SectionIndex::authoredSceneEventKeys) + 1
+              == kSectionCount);
 
 
 /** Exact activity-name/root join result retained for every activity row. */
 /** Exact activity-name/root join result retained for every activity row. */
 enum class ActivityJoinStatus : std::uint32_t {
 enum class ActivityJoinStatus : std::uint32_t {
@@ -1146,6 +1168,20 @@ struct AuthoredSceneResource final {
     std::uint32_t reserved{};
     std::uint32_t reserved{};
 };
 };
 
 
+/** One event gate of a type-43 scene's graph, in the scene's gate order. */
+struct AuthoredSceneEventKey final {
+    StringRef id{};
+    std::uint32_t sceneSlotIndex{};
+    std::uint32_t resourceTag{};
+    std::uint32_t graphTag{};
+    /** Offset of the gate element inside the graph, the row's provenance. */
+    std::uint32_t gateOffset{};
+    std::int32_t ordinal{};
+    std::uint32_t key{};
+    std::uint32_t flags{};
+    std::uint32_t reserved{};
+};
+
 /** One type-43 slot retains an exact same-object reference to one type-1 squad slot. */
 /** One type-43 slot retains an exact same-object reference to one type-1 squad slot. */
 struct AuthoredSceneSquadEdge final {
 struct AuthoredSceneSquadEdge final {
     StringRef id{};
     StringRef id{};
@@ -1528,6 +1564,7 @@ static_assert(sizeof(Squad) == kSquadSize);
 static_assert(sizeof(SquadMember) == kSquadMemberSize);
 static_assert(sizeof(SquadMember) == kSquadMemberSize);
 static_assert(sizeof(SquadAnchor) == kSquadAnchorSize);
 static_assert(sizeof(SquadAnchor) == kSquadAnchorSize);
 static_assert(sizeof(AuthoredSceneResource) == kAuthoredSceneResourceSize);
 static_assert(sizeof(AuthoredSceneResource) == kAuthoredSceneResourceSize);
+static_assert(sizeof(AuthoredSceneEventKey) == kAuthoredSceneEventKeySize);
 static_assert(sizeof(AuthoredSceneSquadEdge) == kAuthoredSceneSquadEdgeSize);
 static_assert(sizeof(AuthoredSceneSquadEdge) == kAuthoredSceneSquadEdgeSize);
 static_assert(sizeof(TaskTarget) == kTaskTargetSize);
 static_assert(sizeof(TaskTarget) == kTaskTargetSize);
 static_assert(sizeof(DialogueCueText) == kDialogueCueTextSize);
 static_assert(sizeof(DialogueCueText) == kDialogueCueTextSize);

+ 2 - 0
Sunrise/src/state/activity_sdk/generation/activity_sdk_pack_writer.cpp

@@ -71,6 +71,7 @@ constexpr std::array<std::uint32_t, format::kSectionCount> kSectionStrides{
     sizeof(format::CombatObjectiveGroup),
     sizeof(format::CombatObjectiveGroup),
     sizeof(format::ActorAbility),
     sizeof(format::ActorAbility),
     sizeof(format::ActorAbilityTarget),
     sizeof(format::ActorAbilityTarget),
+    sizeof(format::AuthoredSceneEventKey),
 };
 };
 /** Writer-owned siblings use the cleanup service's final.process.thread.sequence.tmp shape. */
 /** Writer-owned siblings use the cleanup service's final.process.thread.sequence.tmp shape. */
 constexpr std::wstring_view kTemporarySuffix = L".%08lX.%08lX.%08lX.tmp";
 constexpr std::wstring_view kTemporarySuffix = L".%08lX.%08lX.%08lX.tmp";
@@ -182,6 +183,7 @@ raw_sections(const Tables& tables) noexcept {
         rows(tables.combatObjectiveGroups),
         rows(tables.combatObjectiveGroups),
         rows(tables.actorAbilities),
         rows(tables.actorAbilities),
         rows(tables.actorAbilityTargets),
         rows(tables.actorAbilityTargets),
+        rows(tables.authoredSceneEventKeys),
     };
     };
 }
 }
 
 

+ 1 - 0
Sunrise/src/state/activity_sdk/generation/pack_writer.h

@@ -72,6 +72,7 @@ struct Tables final {
     std::span<const format::CombatObjectiveGroup> combatObjectiveGroups{};
     std::span<const format::CombatObjectiveGroup> combatObjectiveGroups{};
     std::span<const format::ActorAbility> actorAbilities{};
     std::span<const format::ActorAbility> actorAbilities{};
     std::span<const format::ActorAbilityTarget> actorAbilityTargets{};
     std::span<const format::ActorAbilityTarget> actorAbilityTargets{};
+    std::span<const format::AuthoredSceneEventKey> authoredSceneEventKeys{};
 };
 };
 
 
 /** Canonical pack build and publication outcomes. */
 /** Canonical pack build and publication outcomes. */

+ 5 - 0
Sunrise/src/state/activity_sdk/runtime.h

@@ -248,6 +248,8 @@ public:
     actor_sequence_entries() const noexcept;
     actor_sequence_entries() const noexcept;
     [[nodiscard]] std::span<const format::ActorSequenceBinding>
     [[nodiscard]] std::span<const format::ActorSequenceBinding>
     actor_sequence_bindings() const noexcept;
     actor_sequence_bindings() const noexcept;
+    [[nodiscard]] std::span<const format::AuthoredSceneEventKey>
+    authored_scene_event_keys() const noexcept;
 
 
 private:
 private:
     friend bool load(void* module,
     friend bool load(void* module,
@@ -509,6 +511,9 @@ slot_authored_scene_resources(const Catalog& catalog, const format::Slot& slot)
 /** Returns all exact same-object squad edges owned by one type-43 slot. */
 /** Returns all exact same-object squad edges owned by one type-43 slot. */
 [[nodiscard]] std::span<const format::AuthoredSceneSquadEdge>
 [[nodiscard]] std::span<const format::AuthoredSceneSquadEdge>
 slot_authored_scene_squad_edges(const Catalog& catalog, const format::Slot& slot) noexcept;
 slot_authored_scene_squad_edges(const Catalog& catalog, const format::Slot& slot) noexcept;
+/** Returns the event keys of one type-43 slot's graph, in gate order. */
+[[nodiscard]] std::span<const format::AuthoredSceneEventKey>
+slot_authored_scene_event_keys(const Catalog& catalog, const format::Slot& slot) noexcept;
 /** Resolves one validated scene edge to its exact type-1 target slot. */
 /** Resolves one validated scene edge to its exact type-1 target slot. */
 [[nodiscard]] const format::Slot*
 [[nodiscard]] const format::Slot*
 authored_scene_linked_squad_slot(const Catalog& catalog,
 authored_scene_linked_squad_slot(const Catalog& catalog,