Browse Source

Read every participant of an authored scene when linking its squads

A type-43 config lists its participants as a table: a count, then one
self-relative pointer per participant to a block whose class precedes its
payload. The generator read a single block at the fixed position it has
when the table holds exactly one entry, so a scene with two participants
or more yielded no squad edge at all: on the installed content only 682 of
2231 edges were extracted, and none for Homecoming's scenes.

The generator now walks the table and emits one edge per squad block. An
edge's identity and natural key name the squad slot, since a scene may
carry several; validation bounds a scene edge's reference by the table's
position instead of a fixed offset, and leaves performance edges as they
were.
Fozkais 1 week ago
parent
commit
033284c609

+ 3 - 2
Sunrise/src/client/content/activity/activity_sdk_authored_scene_internal.h

@@ -92,7 +92,7 @@ is_performance_descriptor(const topology::Snapshot& topology,
 
 
 /** Sort key matching the final section-22 pack order. */
 /** Sort key matching the final section-22 pack order. */
 [[nodiscard]] inline auto edge_natural(const SquadEdge& row) noexcept {
 [[nodiscard]] inline auto edge_natural(const SquadEdge& row) noexcept {
-    return std::tie(row.sceneSlotIndex, row.configTag, row.descriptorOffset);
+    return std::tie(row.sceneSlotIndex, row.configTag, row.descriptorOffset, row.squadSlotIndex);
 }
 }
 
 
 /** Sort key matching the final task-target pack order. */
 /** Sort key matching the final task-target pack order. */
@@ -176,9 +176,10 @@ schema_index(const topology::Snapshot& topology, const Facts& facts, SchemaIndex
                                const squad::DescriptorFact& descriptor,
                                const squad::DescriptorFact& descriptor,
                                Text& output) noexcept;
                                Text& output) noexcept;
 
 
-/** Formats one scene-to-squad edge ID from its exact descriptor tuple. */
+/** Formats one scene-to-squad edge ID from its descriptor tuple and the squad slot it names. */
 [[nodiscard]] bool edge_id(const topology::Snapshot& topology,
 [[nodiscard]] bool edge_id(const topology::Snapshot& topology,
                            const squad::DescriptorFact& descriptor,
                            const squad::DescriptorFact& descriptor,
+                           std::uint32_t squadSlotRow,
                            Text& output) noexcept;
                            Text& output) noexcept;
 
 
 /** Formats one task-to-objective target ID from its exact descriptor tuple. */
 /** Formats one task-to-objective target ID from its exact descriptor tuple. */

+ 88 - 59
Sunrise/src/client/content/activity/activity_sdk_authored_scene_inventory.cpp

@@ -32,23 +32,27 @@ bool resource_id(const topology::Snapshot& topology,
                        static_cast<unsigned>(slot.slotType));
                        static_cast<unsigned>(slot.slotType));
 }
 }
 
 
-/** Formats one scene-to-squad edge ID from its exact descriptor tuple. */
+/** Formats one scene-to-squad edge ID from its descriptor tuple and the squad slot it names. */
 bool edge_id(const topology::Snapshot& topology,
 bool edge_id(const topology::Snapshot& topology,
              const squad::DescriptorFact& descriptor,
              const squad::DescriptorFact& descriptor,
+             std::uint32_t squadSlotRow,
              Text& output) noexcept {
              Text& output) noexcept {
     if (descriptor.objectIndex >= topology.objects.size()
     if (descriptor.objectIndex >= topology.objects.size()
-        || descriptor.slotIndex >= topology.slots.size()) {
+        || descriptor.slotIndex >= topology.slots.size() || squadSlotRow >= topology.slots.size()) {
         return false;
         return false;
     }
     }
     const topology::Object& object = topology.objects[descriptor.objectIndex];
     const topology::Object& object = topology.objects[descriptor.objectIndex];
     const topology::Slot& slot = topology.slots[descriptor.slotIndex];
     const topology::Slot& slot = topology.slots[descriptor.slotIndex];
+    const topology::Slot& squad = topology.slots[squadSlotRow];
     return format_text(output,
     return format_text(output,
-                       "authored-scene-squad-edge/%08x/%08x/%08x/%04x/%04x",
+                       "authored-scene-squad-edge/%08x/%08x/%08x/%04x/%04x/%04x/%04x",
                        static_cast<unsigned>(descriptor.configTag),
                        static_cast<unsigned>(descriptor.configTag),
                        static_cast<unsigned>(object.objectTag),
                        static_cast<unsigned>(object.objectTag),
                        static_cast<unsigned>(descriptor.descriptorOffset),
                        static_cast<unsigned>(descriptor.descriptorOffset),
                        static_cast<unsigned>(slot.slotIndex),
                        static_cast<unsigned>(slot.slotIndex),
-                       static_cast<unsigned>(slot.slotType));
+                       static_cast<unsigned>(slot.slotType),
+                       static_cast<unsigned>(squad.slotIndex),
+                       static_cast<unsigned>(squad.slotType));
 }
 }
 
 
 /** Formats one task-to-objective target ID from its exact descriptor tuple. */
 /** Formats one task-to-objective target ID from its exact descriptor tuple. */
@@ -459,7 +463,7 @@ bool build(const topology::Snapshot& topology,
                     continue;
                     continue;
                 }
                 }
                 SquadEdge row{};
                 SquadEdge row{};
-                if (!edge_id(topology, descriptor, row.id)) {
+                if (!edge_id(topology, descriptor, linkedSlot, row.id)) {
                     log_performance_edge(descriptor, "identity");
                     log_performance_edge(descriptor, "identity");
                     continue;
                     continue;
                 }
                 }
@@ -499,8 +503,7 @@ bool build(const topology::Snapshot& topology,
                 continue;
                 continue;
             }
             }
             if (resourceTag == 0 || resourceTag == format::kAbsentIndex) {
             if (resourceTag == 0 || resourceTag == format::kAbsentIndex) {
-                log_scene_resource(
-                    descriptor, resourceTag, "unresourced", core::log::Level::debug);
+                log_scene_resource(descriptor, resourceTag, "unresourced", core::log::Level::debug);
                 pending.unresourcedSlots.push_back(descriptor.slotIndex);
                 pending.unresourcedSlots.push_back(descriptor.slotIndex);
             } else {
             } else {
                 const PackageRow* resourcePackage = nullptr;
                 const PackageRow* resourcePackage = nullptr;
@@ -525,61 +528,87 @@ bool build(const topology::Snapshot& topology,
                 }
                 }
             }
             }
 
 
-            const std::size_t blockClassField =
-                static_cast<std::size_t>(descriptor.descriptorOffset)
-                + format::kAuthoredSceneSquadBlockClassRelativeOffset;
-            std::uint32_t blockClass = 0;
-            if (!read_value(blob, blockClassField, blockClass)) {
-                continue;
-            }
-            if (blockClass != format::kAuthoredSceneSquadBlockClass) {
-                continue;
-            }
-            const std::size_t referenceField = static_cast<std::size_t>(descriptor.descriptorOffset)
-                                               + format::kAuthoredSceneSquadReferenceRelativeOffset;
-            std::uint32_t targetObjectKey = 0;
-            std::uint16_t targetSlotType = 0;
-            std::uint16_t targetSlotIndex = 0;
-            if (!read_value(blob, referenceField, targetObjectKey)
-                || !read_value(blob, referenceField + kTargetSlotTypeRelativeOffset, targetSlotType)
-                || !read_value(
-                    blob, referenceField + kTargetSlotIndexRelativeOffset, targetSlotIndex)) {
-                continue;
-            }
-            if (targetSlotType != format::kSquadSlotType) {
+            // Every squad in the participant table is an edge; the other participant kinds
+            // (point sets, objects) are not.
+            const std::size_t descriptorOffset = descriptor.descriptorOffset;
+            std::uint64_t participantCount = 0;
+            std::uint32_t tableClass = 0;
+            if (!read_value(blob,
+                            descriptorOffset + format::kAuthoredSceneParticipantCountRelativeOffset,
+                            participantCount)
+                || !read_value(blob,
+                               descriptorOffset
+                                   + format::kAuthoredSceneParticipantTableClassRelativeOffset,
+                               tableClass)
+                || tableClass != format::kAuthoredSceneParticipantTableClass
+                || participantCount > format::kAuthoredSceneParticipantCapacity) {
                 continue;
                 continue;
             }
             }
-            if (descriptor.objectIndex >= topology.objects.size()
-                || targetObjectKey != topology.objects[descriptor.objectIndex].objectKey) {
-                continue;
-            }
-            std::uint32_t linkedSlot = format::kAbsentIndex;
-            if (!same_object_slot(
-                    topology, descriptor, targetSlotType, targetSlotIndex, linkedSlot)) {
-                return false;
-            }
-            if (linkedSlot == format::kAbsentIndex
-                || !slot_shape(topology,
-                               schemas,
-                               linkedSlot,
-                               format::kSquadSlotType,
-                               format::kSquadComponentClass,
-                               format::kSquadSenseSchema,
-                               format::kSquadAuthSchema)) {
-                continue;
-            }
-            SquadEdge row{};
-            if (!edge_id(topology, descriptor, row.id)) {
-                continue;
+            for (std::uint64_t entry = 0; entry < participantCount; ++entry) {
+                const std::size_t pointerField =
+                    descriptorOffset + format::kAuthoredSceneParticipantTableRelativeOffset
+                    + static_cast<std::size_t>(entry)
+                          * format::kAuthoredSceneParticipantPointerSize;
+                std::uint64_t pointer = 0;
+                if (!read_value(blob, pointerField, pointer)
+                    || pointer > blob.size() - pointerField) {
+                    break;
+                }
+                const std::size_t payload = pointerField + static_cast<std::size_t>(pointer);
+                std::uint32_t blockClass = 0;
+                if (payload < format::kAuthoredSceneParticipantClassSize
+                    || !read_value(
+                        blob, payload - format::kAuthoredSceneParticipantClassSize, blockClass)) {
+                    break;
+                }
+                if (blockClass != format::kAuthoredSceneSquadBlockClass) {
+                    continue;
+                }
+                const std::size_t referenceField =
+                    payload + format::kAuthoredSceneSquadPayloadReferenceOffset;
+                std::uint32_t targetObjectKey = 0;
+                std::uint16_t targetSlotType = 0;
+                std::uint16_t targetSlotIndex = 0;
+                if (!read_value(blob, referenceField, targetObjectKey)
+                    || !read_value(
+                        blob, referenceField + kTargetSlotTypeRelativeOffset, targetSlotType)
+                    || !read_value(
+                        blob, referenceField + kTargetSlotIndexRelativeOffset, targetSlotIndex)) {
+                    break;
+                }
+                if (targetSlotType != format::kSquadSlotType
+                    || descriptor.objectIndex >= topology.objects.size()
+                    || targetObjectKey != topology.objects[descriptor.objectIndex].objectKey) {
+                    continue;
+                }
+                std::uint32_t linkedSlot = format::kAbsentIndex;
+                if (!same_object_slot(
+                        topology, descriptor, targetSlotType, targetSlotIndex, linkedSlot)) {
+                    return false;
+                }
+                if (linkedSlot == format::kAbsentIndex
+                    || !slot_shape(topology,
+                                   schemas,
+                                   linkedSlot,
+                                   format::kSquadSlotType,
+                                   format::kSquadComponentClass,
+                                   format::kSquadSenseSchema,
+                                   format::kSquadAuthSchema)) {
+                    continue;
+                }
+                SquadEdge row{};
+                if (!edge_id(topology, descriptor, linkedSlot, row.id)) {
+                    continue;
+                }
+                row.sceneSlotIndex = descriptor.slotIndex;
+                row.squadSlotIndex = linkedSlot;
+                row.configTag = descriptor.configTag;
+                row.descriptorOffset = descriptor.descriptorOffset;
+                row.referenceFieldOffset = static_cast<std::uint32_t>(referenceField);
+                row.targetObjectKey = targetObjectKey;
+                row.flags = format::kAuthoredSceneSquadSameObjectExact;
+                pending.squadEdges.push_back(row);
             }
             }
-            row.sceneSlotIndex = descriptor.slotIndex;
-            row.squadSlotIndex = linkedSlot;
-            row.configTag = descriptor.configTag;
-            row.descriptorOffset = descriptor.descriptorOffset;
-            row.referenceFieldOffset = static_cast<std::uint32_t>(referenceField);
-            row.targetObjectKey = targetObjectKey;
-            row.flags = format::kAuthoredSceneSquadSameObjectExact;
-            pending.squadEdges.push_back(row);
         }
         }
         // A sensor with no descriptor fact logs nothing above, so count both sides here.
         // A sensor with no descriptor fact logs nothing above, so count both sides here.
         {
         {

+ 12 - 7
Sunrise/src/client/content/activity/activity_sdk_authored_scene_validation.cpp

@@ -87,7 +87,7 @@ constexpr std::size_t kHashCombineConstant = 0x9E3779B9U;
                 || descriptor.objectIndex >= topology.objects.size()
                 || descriptor.objectIndex >= topology.objects.size()
                 || descriptor.slotIndex >= topology.slots.size()
                 || descriptor.slotIndex >= topology.slots.size()
                 || descriptor.descriptorOffset
                 || descriptor.descriptorOffset
-                       > format::kAbsentIndex - format::kAuthoredSceneSquadReferenceRelativeOffset
+                       > format::kAbsentIndex - format::kAuthoredSceneParticipantTableRelativeOffset
                 || !descriptor_id(topology, descriptor, expected) || descriptor.id != expected
                 || !descriptor_id(topology, descriptor, expected) || descriptor.id != expected
                 || !ids.emplace(descriptor.id).second) {
                 || !ids.emplace(descriptor.id).second) {
                 return false;
                 return false;
@@ -232,11 +232,16 @@ bool validate(const topology::Snapshot& topology,
         const squad::DescriptorFact* descriptor =
         const squad::DescriptorFact* descriptor =
             find_descriptor(descriptors, row.sceneSlotIndex, row.configTag, row.descriptorOffset);
             find_descriptor(descriptors, row.sceneSlotIndex, row.configTag, row.descriptorOffset);
         Text expectedId{};
         Text expectedId{};
-        // A row is a type-43 scene edge or a type-42 performance edge; the flag says which.
+        // A row is a type-43 scene edge or a type-42 performance edge; the flag says which. A
+        // performance names its squad at a fixed field; a scene names it in a block its
+        // participant table points at, so only the table's position bounds that field here.
         const bool performance = row.flags == format::kAuthoredSceneSquadPerformanceTargetExact;
         const bool performance = row.flags == format::kAuthoredSceneSquadPerformanceTargetExact;
         const std::uint32_t referenceOffset =
         const std::uint32_t referenceOffset =
             performance ? format::kPerformanceSquadReferenceRelativeOffset
             performance ? format::kPerformanceSquadReferenceRelativeOffset
-                        : format::kAuthoredSceneSquadReferenceRelativeOffset;
+                        : format::kAuthoredSceneParticipantTableRelativeOffset;
+        const bool referencePlaced =
+            performance ? row.referenceFieldOffset == row.descriptorOffset + referenceOffset
+                        : row.referenceFieldOffset > row.descriptorOffset + referenceOffset;
         const bool sourceShape = performance ? slot_shape(topology,
         const bool sourceShape = performance ? slot_shape(topology,
                                                           schemas,
                                                           schemas,
                                                           row.sceneSlotIndex,
                                                           row.sceneSlotIndex,
@@ -256,8 +261,9 @@ bool validate(const topology::Snapshot& topology,
             || topology.slots[row.squadSlotIndex].objectIndex != descriptor->objectIndex
             || topology.slots[row.squadSlotIndex].objectIndex != descriptor->objectIndex
             || !(performance ? is_performance_descriptor(topology, *descriptor)
             || !(performance ? is_performance_descriptor(topology, *descriptor)
                              : is_scene_descriptor(topology, *descriptor))
                              : is_scene_descriptor(topology, *descriptor))
-            || !edge_id(topology, *descriptor, expectedId) || expectedId.value != row.id.value
-            || expectedId.length != row.id.length || !sourceShape
+            || !edge_id(topology, *descriptor, row.squadSlotIndex, expectedId)
+            || expectedId.value != row.id.value || expectedId.length != row.id.length
+            || !sourceShape
             || !slot_shape(topology,
             || !slot_shape(topology,
                            schemas,
                            schemas,
                            row.squadSlotIndex,
                            row.squadSlotIndex,
@@ -265,8 +271,7 @@ bool validate(const topology::Snapshot& topology,
                            format::kSquadComponentClass,
                            format::kSquadComponentClass,
                            format::kSquadSenseSchema,
                            format::kSquadSenseSchema,
                            format::kSquadAuthSchema)
                            format::kSquadAuthSchema)
-            || row.descriptorOffset > format::kAbsentIndex - referenceOffset
-            || row.referenceFieldOffset != row.descriptorOffset + referenceOffset
+            || row.descriptorOffset > format::kAbsentIndex - referenceOffset || !referencePlaced
             || row.targetObjectKey != topology.objects[descriptor->objectIndex].objectKey
             || row.targetObjectKey != topology.objects[descriptor->objectIndex].objectKey
             || (row.flags != format::kAuthoredSceneSquadSameObjectExact && !performance)
             || (row.flags != format::kAuthoredSceneSquadSameObjectExact && !performance)
             || row.reserved != 0
             || row.reserved != 0

+ 3 - 4
Sunrise/src/client/content/activity/activity_sdk_native_pack_internal.h

@@ -65,9 +65,8 @@ struct PackageContext final {
                                               authored_scene::Snapshot& authored);
                                               authored_scene::Snapshot& authored);
 
 
 /** Marks the SDK slot row of every type-43 scene whose descriptor names no resource. */
 /** Marks the SDK slot row of every type-43 scene whose descriptor names no resource. */
-[[nodiscard]] bool
-attach_unresourced_scenes(const topology_inventory::Snapshot& topology,
-                          const authored_scene::Snapshot& scenes,
-                          topology_enrichment::Snapshot& enrichment) noexcept;
+[[nodiscard]] bool attach_unresourced_scenes(const topology_inventory::Snapshot& topology,
+                                             const authored_scene::Snapshot& scenes,
+                                             topology_enrichment::Snapshot& enrichment) noexcept;
 
 
 } // namespace sunrise::client::content::activity::sdk_generation::native_pack_pipeline
 } // namespace sunrise::client::content::activity::sdk_generation::native_pack_pipeline

+ 15 - 2
Sunrise/src/state/activity_sdk/format.h

@@ -300,9 +300,22 @@ inline constexpr std::uint32_t kActorSequenceLocalDefinitionClass = 0x8080815FU;
 inline constexpr std::uint32_t kActorSequenceGlobalArrayOffset = 152;
 inline constexpr std::uint32_t kActorSequenceGlobalArrayOffset = 152;
 inline constexpr std::uint32_t kActorSequenceLocalArrayOffset = 16;
 inline constexpr std::uint32_t kActorSequenceLocalArrayOffset = 16;
 inline constexpr std::uint32_t kActorSequenceOwnerSourceClass = 0x808082ECU;
 inline constexpr std::uint32_t kActorSequenceOwnerSourceClass = 0x808082ECU;
-inline constexpr std::uint32_t kAuthoredSceneSquadBlockClassRelativeOffset = 0xA4U;
+/**
+ * A type-43 config lists its participants as a table: a 64-bit count, the table class, then one
+ * 64-bit pointer per participant, each relative to its own field. A pointer lands on a block's
+ * payload; the block's class sits in the four bytes before it. Only squad blocks become edges.
+ */
+inline constexpr std::uint32_t kAuthoredSceneParticipantCountRelativeOffset = 0x88U;
+inline constexpr std::uint32_t kAuthoredSceneParticipantTableClassRelativeOffset = 0x90U;
+inline constexpr std::uint32_t kAuthoredSceneParticipantTableRelativeOffset = 0x98U;
+inline constexpr std::uint32_t kAuthoredSceneParticipantTableClass = 0x80806268U;
+inline constexpr std::uint32_t kAuthoredSceneParticipantPointerSize = 8U;
+inline constexpr std::uint32_t kAuthoredSceneParticipantClassSize = 4U;
+/** More participants than any installed scene declares; a larger count is a misread config. */
+inline constexpr std::uint32_t kAuthoredSceneParticipantCapacity = 64U;
 inline constexpr std::uint32_t kAuthoredSceneSquadBlockClass = 0x80806262U;
 inline constexpr std::uint32_t kAuthoredSceneSquadBlockClass = 0x80806262U;
-inline constexpr std::uint32_t kAuthoredSceneSquadReferenceRelativeOffset = 0xB0U;
+/** Squad reference inside a squad block's payload: object key, then slot type and index. */
+inline constexpr std::uint32_t kAuthoredSceneSquadPayloadReferenceOffset = 0x8U;
 /** Exact type-38 task edge to the authored type-3 objective component it mutates. */
 /** Exact type-38 task edge to the authored type-3 objective component it mutates. */
 inline constexpr std::uint32_t kTaskSlotType = 38U;
 inline constexpr std::uint32_t kTaskSlotType = 38U;
 inline constexpr std::uint32_t kTaskComponentClass = 0x80807D87U;
 inline constexpr std::uint32_t kTaskComponentClass = 0x80807D87U;