Browse Source

port dawn activities and fix stuff

stan 2 days ago
parent
commit
ff0bdee7e7

+ 1 - 0
Sunrise/Sunrise.vcxproj

@@ -1483,6 +1483,7 @@
     <ClInclude Include="src\state\content_manifest\scanner\package\content_manifest_package_name.h" />
     <ClInclude Include="src\state\content_manifest\content_manifest_row_validation.h" />
     <ClInclude Include="src\state\activity_sdk\format.h" />
+    <ClInclude Include="src\state\activity_sdk\squad_profiles.h" />
     <ClInclude Include="src\state\activity_sdk\identity.h" />
     <ClInclude Include="src\state\activity_sdk\generation\definition.h" />
     <ClInclude Include="src\state\activity_sdk\generation\internal.h" />

+ 2 - 9
Sunrise/src/client/content/activity/activity_sdk_actor_rsat_validation.cpp

@@ -7,6 +7,7 @@
 #include <string_view>
 #include <vector>
 
+#include "../../../state/activity_sdk/squad_profiles.h"
 #include "activity_sdk_actor_rsat_inventory_internal.h"
 #include "activity_sdk_actor_sequences.h"
 
@@ -52,15 +53,7 @@ namespace {
 
 /** Field-5 lanes 1..4 are bias-one values with widths 2, 3, 2, and 3. */
 [[nodiscard]] bool valid_authored_spawn_profile(const ActorClass& actor) noexcept {
-    // Logical maxima of the four lanes after the bias-one decode.
-    constexpr std::array<std::int8_t, 4> kMaximumLogical{2, 6, 2, 6};
-    for (std::size_t index = 0; index < actor.authoredSpawnProfile.size(); ++index) {
-        if (actor.authoredSpawnProfile[index] < 0
-            || actor.authoredSpawnProfile[index] > kMaximumLogical[index]) {
-            return false;
-        }
-    }
-    return true;
+    return state::activity_sdk::valid_spawn_profile(actor.authoredSpawnProfile);
 }
 
 /** Checks an exact stored self-relative header and data pair. */

+ 4 - 3
Sunrise/src/client/content/activity/activity_sdk_native_pack_pipeline.cpp

@@ -138,8 +138,8 @@ void report(ProgressProbe probe, void* context, Phase phase) noexcept {
 
 /** Resolves one exact actor definition tag against the validated sorted actor section. */
 [[nodiscard]] bool
-resolve_actor(void* opaque, std::uint32_t definitionTag, std::uint32_t& output) noexcept {
-    output = format::kAbsentIndex;
+resolve_actor(void* opaque, std::uint32_t definitionTag, squads::ResolvedActor& output) noexcept {
+    output = {};
     if (opaque == nullptr) {
         return false;
     }
@@ -154,7 +154,8 @@ resolve_actor(void* opaque, std::uint32_t definitionTag, std::uint32_t& output)
         || static_cast<std::size_t>(found - actors.begin()) >= format::kAbsentIndex) {
         return false;
     }
-    output = static_cast<std::uint32_t>(found - actors.begin());
+    output.actorClassIndex = static_cast<std::uint32_t>(found - actors.begin());
+    output.authoredSpawnProfile = found->authoredSpawnProfile;
     return true;
 }
 

+ 1 - 0
Sunrise/src/client/content/activity/activity_sdk_pack_composer_rows.cpp

@@ -593,6 +593,7 @@ translate_native_rows(const Inputs& inputs, const detail::StringResolver& linker
         target.flags = input.flags;
         target.candidateCounts = input.candidateCounts;
         target.defaultCount = input.defaultCount;
+        target.authoredSpawnProfile = input.authoredSpawnProfile;
         if (!link_string(linker, input.id, target.id)) {
             return false;
         }

+ 9 - 2
Sunrise/src/client/content/activity/activity_sdk_squad_inventory.h

@@ -22,10 +22,16 @@ using TagReader = bool (*)(void* context,
                            std::vector<std::byte>& bytes,
                            std::uint32_t& classId) noexcept;
 
-/** The actor inventory may close one exact definition tag to its final row index. */
+/** One validated actor definition supplies its identity and native spawn profile. */
+struct ResolvedActor final {
+    std::uint32_t actorClassIndex{format::kAbsentIndex};
+    std::array<std::int8_t, 4> authoredSpawnProfile{};
+};
+
+/** The actor inventory closes one exact definition tag to its validated row. */
 using ActorResolver = bool (*)(void* context,
                                std::uint32_t definitionTag,
-                               std::uint32_t& actorClassIndex) noexcept;
+                               ResolvedActor& output) noexcept;
 
 enum class CandidateState : std::uint8_t {
     nullPlacement,
@@ -214,6 +220,7 @@ struct SquadMember final {
     std::uint32_t flags{};
     std::array<std::uint16_t, format::kSquadCandidateCountLaneCount> candidateCounts{};
     std::int32_t defaultCount{-1};
+    std::array<std::int8_t, 4> authoredSpawnProfile{};
     ActorLink actorLink{ActorLink::absent};
 };
 

+ 31 - 5
Sunrise/src/client/content/activity/activity_sdk_squad_inventory_link_support.cpp

@@ -10,6 +10,7 @@
 
 #include "../../../middleware/content/packages/tables/authored_squad_reader.h"
 #include "../../../middleware/content/packages/tables/scenario_reader.h"
+#include "../../../state/activity_sdk/squad_profiles.h"
 #include "activity_sdk_squad_inventory_internal.h"
 
 namespace sunrise::client::content::activity::sdk_generation::squad_inventory::detail {
@@ -377,6 +378,34 @@ template <typename... Values>
     if (noNull) {
         output.flags |= format::kSquadMemberNoNullCandidates;
     }
+    ResolvedActor onlyActor{};
+    std::array<std::int8_t, 4> commonProfile{};
+    bool profileExact = allActorsEligible && actorResolver != nullptr;
+    bool firstProfile = true;
+    for (const auto tag : actorTags) {
+        ResolvedActor resolved{};
+        if (actorResolver == nullptr || !actorResolver(actorContext, tag, resolved)
+            || resolved.actorClassIndex == format::kAbsentIndex) {
+            profileExact = false;
+            continue;
+        }
+        if (actorTags.size() == 1) {
+            onlyActor = resolved;
+        }
+        if (!state::activity_sdk::valid_spawn_profile(resolved.authoredSpawnProfile)) {
+            profileExact = false;
+            continue;
+        }
+        if (!firstProfile && commonProfile != resolved.authoredSpawnProfile) {
+            profileExact = false;
+        }
+        commonProfile = resolved.authoredSpawnProfile;
+        firstProfile = false;
+    }
+    if (profileExact && !firstProfile) {
+        output.authoredSpawnProfile = commonProfile;
+        output.flags |= format::kSquadMemberSpawnProfileExact;
+    }
     if (allActorsEligible && actorTags.size() == 1) {
         output.actorDefinitionTag = *actorTags.begin();
         if (!format_text(output.actorDefinitionId,
@@ -384,11 +413,8 @@ template <typename... Values>
                          static_cast<unsigned>(output.actorDefinitionTag))) {
             return false;
         }
-        std::uint32_t actorIndex = format::kAbsentIndex;
-        if (actorResolver != nullptr
-            && actorResolver(actorContext, output.actorDefinitionTag, actorIndex)
-            && actorIndex != format::kAbsentIndex) {
-            output.actorClassIndex = actorIndex;
+        if (onlyActor.actorClassIndex != format::kAbsentIndex) {
+            output.actorClassIndex = onlyActor.actorClassIndex;
             output.actorLink = ActorLink::exactReciprocal;
             output.flags |= format::kSquadMemberActorClassExact;
         } else {

+ 3 - 24
Sunrise/src/server/activity/activity_sdk_squad_runtime.cpp

@@ -7,6 +7,7 @@
 
 #include "../../middleware/content/packages/tables/region_reader.h"
 #include "../../state/activity/runtime.h"
+#include "../../state/activity_sdk/squad_profiles.h"
 #include "../../state/build_data/runtime.h"
 #include "../bap/runtime.h"
 #include "host_runtime.h"
@@ -95,30 +96,8 @@ struct PreparedSquad final {
                                     const format::Squad& squad,
                                     std::span<const std::int32_t> requestedCounts,
                                     std::array<std::int8_t, 4>& output) noexcept {
-    output = {};
-    const auto members = sdk::squad_members(catalog, squad);
-    const auto actors = catalog.actor_classes();
-    bool found = false;
-    // A named type-2 member needs its parent's authored profile but zero loose actors.
-    const bool loose = std::any_of(
-        requestedCounts.begin(), requestedCounts.end(), [](auto count) { return count > 0; });
-    for (std::size_t index = 0; index < members.size(); ++index) {
-        if (loose && requestedCounts[index] <= 0) {
-            continue;
-        }
-        const format::SquadMember& member = members[index];
-        if ((member.flags & format::kSquadMemberActorClassExact) == 0
-            || member.actorClassIndex >= actors.size()) {
-            return false;
-        }
-        const auto& candidate = actors[member.actorClassIndex].authoredSpawnProfile;
-        if (found && candidate != output) {
-            return false;
-        }
-        output = candidate;
-        found = true;
-    }
-    return found;
+    return sdk::squad_spawn_profile(
+        sdk::squad_members(catalog, squad), catalog.actor_classes(), requestedCounts, output);
 }
 /** Checks the generated source slot before it can select a wire roster target. */
 [[nodiscard]] bool valid_generated_slot(const sdk::Catalog& catalog,

+ 19 - 1
Sunrise/src/state/activity_sdk/activity_sdk_relation_validation.cpp

@@ -6,11 +6,28 @@
 
 #include "actor_sequences.h"
 #include "internal.h"
+#include "squad_profiles.h"
 #include "validation_internal.h"
 
 namespace sunrise::state::activity_sdk::validation {
 namespace {
 
+/** @return True when every member certificate belongs to its declared squad lane. */
+[[nodiscard]] bool squad_profiles(const Catalog& catalog) noexcept {
+    const auto squads = catalog.squads();
+    const auto members = catalog.squad_members();
+    for (std::size_t index = 0; index < members.size(); ++index) {
+        const auto& member = members[index];
+        if (member.squadIndex >= squads.size()
+            || member.memberOrdinal >= squads[member.squadIndex].members.count
+            || squads[member.squadIndex].members.first + member.memberOrdinal != index
+            || !valid_member_spawn_profile(member, catalog.actor_classes())) {
+            return false;
+        }
+    }
+    return true;
+}
+
 // An empty authored name uses the FNV-1 basis.
 constexpr std::uint32_t kAbsentDefinitionHash = 0x811C9DC5U;
 // Ability owners and targets use these exact package slot classes and schemas.
@@ -582,7 +599,8 @@ bool relations(const Catalog& catalog) {
         bool (*run)(const Catalog&) noexcept;
     };
     // 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},
+    static constexpr std::array<Check, 8> kChecks{{{"squad_profiles", &squad_profiles},
+                                                   {"task_targets", &task_targets},
                                                    {"authored_text", &authored_text},
                                                    {"behavior_edges", &behavior_edges},
                                                    {"actor_semantics", &actor_semantics},

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

@@ -10,7 +10,7 @@ namespace sunrise::state::activity_sdk::format {
 /** Eight-byte identity at the start of every runtime SDK pack. */
 inline constexpr std::array<char, 8> kMagic{'S', 'R', 'S', 'D', 'K', 'P', '0', '1'};
 /** Runtime-pack schema version accepted by this reader. */
-inline constexpr std::uint32_t kVersion = 40;
+inline constexpr std::uint32_t kVersion = 41;
 /** The ABI contains only activity identity, topology, placement, and panel metadata. */
 inline constexpr std::uint32_t kSectionCount = 50;
 #if defined(SUNRISE_ACTIVITY_SDK_TESTING)
@@ -245,7 +245,9 @@ inline constexpr std::uint32_t kSquadMemberActorClassExact = 0x1U;
 inline constexpr std::uint32_t kSquadMemberCandidateCountsComplete = 0x2U;
 inline constexpr std::uint32_t kSquadMemberCandidateCountsInvariant = 0x4U;
 inline constexpr std::uint32_t kSquadMemberNoNullCandidates = 0x8U;
-inline constexpr std::uint32_t kSquadMemberFlagMask = 0xFU;
+/** Every authored candidate resolves to the same four-byte spawn profile. */
+inline constexpr std::uint32_t kSquadMemberSpawnProfileExact = 0x10U;
+inline constexpr std::uint32_t kSquadMemberFlagMask = 0x1FU;
 inline constexpr std::uint32_t kSquadMemberInvariantReadyMask =
     kSquadMemberCandidateCountsComplete | kSquadMemberCandidateCountsInvariant
     | kSquadMemberNoNullCandidates;
@@ -388,7 +390,7 @@ inline constexpr std::size_t kRsatDescriptorSize = 92;
 inline constexpr std::size_t kRsatSchemaSize = 64;
 inline constexpr std::size_t kRsatFieldSize = 40;
 inline constexpr std::size_t kSquadSize = 52;
-inline constexpr std::size_t kSquadMemberSize = 44;
+inline constexpr std::size_t kSquadMemberSize = 48;
 inline constexpr std::size_t kSquadAnchorSize = 48;
 inline constexpr std::size_t kAuthoredSceneResourceSize = 40;
 inline constexpr std::size_t kAuthoredSceneSquadEdgeSize = 40;
@@ -611,6 +613,7 @@ inline constexpr std::size_t kSquadMemberActorClassIndex = 20;
 inline constexpr std::size_t kSquadMemberFlags = 24;
 inline constexpr std::size_t kSquadMemberCandidateCounts = 28;
 inline constexpr std::size_t kSquadMemberDefaultCount = 40;
+inline constexpr std::size_t kSquadMemberAuthoredSpawnProfile = 44;
 inline constexpr std::size_t kSquadAnchorId = 0;
 inline constexpr std::size_t kSquadAnchorSquadIndex = 8;
 inline constexpr std::size_t kSquadAnchorPointOrdinal = 12;
@@ -1099,6 +1102,7 @@ struct SquadMember final {
     std::uint32_t flags{};
     std::array<std::uint16_t, kSquadCandidateCountLaneCount> candidateCounts{};
     std::int32_t defaultCount{-1};
+    std::array<std::int8_t, 4> authoredSpawnProfile{};
 };
 
 /** One exact authored anchor retains its placed-entry identity and raw position bits. */
@@ -1781,6 +1785,8 @@ static_assert(offsetof(SquadMember, actorClassIndex) == offset::kSquadMemberActo
 static_assert(offsetof(SquadMember, flags) == offset::kSquadMemberFlags);
 static_assert(offsetof(SquadMember, candidateCounts) == offset::kSquadMemberCandidateCounts);
 static_assert(offsetof(SquadMember, defaultCount) == offset::kSquadMemberDefaultCount);
+static_assert(offsetof(SquadMember, authoredSpawnProfile)
+              == offset::kSquadMemberAuthoredSpawnProfile);
 static_assert(offsetof(SquadAnchor, id) == offset::kSquadAnchorId);
 static_assert(offsetof(SquadAnchor, squadIndex) == offset::kSquadAnchorSquadIndex);
 static_assert(offsetof(SquadAnchor, pointOrdinal) == offset::kSquadAnchorPointOrdinal);

+ 1 - 1
Sunrise/src/state/activity_sdk/identity.h

@@ -40,7 +40,7 @@ derive(const Digest& sourceFingerprint, const Digest& payloadSha256, Expected& o
     }
     // Domain tags separate the two derived digests; the build tag changes with the pack layout.
     constexpr auto kLogicalDomain = std::to_array("sunrise-activity-sdk-logical-ir-v1");
-    constexpr auto kBuildDomain = std::to_array("sunrise-activity-sdk-build-v13");
+    constexpr auto kBuildDomain = std::to_array("sunrise-activity-sdk-build-v14");
     const auto logicalDomain = std::as_bytes(std::span(kLogicalDomain));
     const auto buildDomain = std::as_bytes(std::span(kBuildDomain));
     Digest logical{};

+ 103 - 0
Sunrise/src/state/activity_sdk/squad_profiles.h

@@ -0,0 +1,103 @@
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <span>
+
+#include "format.h"
+
+namespace sunrise::state::activity_sdk {
+
+/** Field-5 lanes use bias-one widths 2, 3, 2 and 3; authored profiles are nonnegative. */
+[[nodiscard]] inline bool valid_spawn_profile(const std::array<std::int8_t, 4>& profile) noexcept {
+    // Bias-one wire widths bound each logical profile lane.
+    constexpr std::array<std::int8_t, 4> kMaximumLogical{2, 6, 2, 6};
+    for (std::size_t index = 0; index < profile.size(); ++index) {
+        if (profile[index] < 0 || profile[index] > kMaximumLogical[index]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
+ * A profile certificate never supplies or replaces an actor identity.
+ * @param member Generated member and its all-candidate certificate.
+ * @param actors Exact actor definitions retained by the same catalog.
+ * @return False for invalid certificates or conflicting single-class evidence.
+ */
+[[nodiscard]] inline bool
+valid_member_spawn_profile(const format::SquadMember& member,
+                           std::span<const format::ActorClass> actors) noexcept {
+    if ((member.flags & ~format::kSquadMemberFlagMask) != 0) {
+        return false;
+    }
+    const bool exactActor = (member.flags & format::kSquadMemberActorClassExact) != 0;
+    if (exactActor ? member.actorClassIndex >= actors.size()
+                   : member.actorClassIndex != format::kAbsentIndex) {
+        return false;
+    }
+    if ((member.flags & format::kSquadMemberSpawnProfileExact) == 0) {
+        return member.authoredSpawnProfile == std::array<std::int8_t, 4>{};
+    }
+    // Certification requires every candidate to exist and be resolved.
+    constexpr auto kCandidateProof =
+        format::kSquadMemberCandidateCountsComplete | format::kSquadMemberNoNullCandidates;
+    return (member.flags & kCandidateProof) == kCandidateProof
+           && std::any_of(member.candidateCounts.begin(),
+                          member.candidateCounts.end(),
+                          [](auto count) { return count != 0; })
+           && valid_spawn_profile(member.authoredSpawnProfile)
+           && (!exactActor
+               || member.authoredSpawnProfile
+                      == actors[member.actorClassIndex].authoredSpawnProfile);
+}
+
+/**
+ * A zero-count retirement retains the same profile proof as its source.
+ * @param members Authored member lanes in their source order.
+ * @param actors Exact catalog actor definitions for single-class members.
+ * @param requestedCounts Requested count for each member lane.
+ * @param output Receives the common profile; cleared on failure.
+ * @return True only when every selected member has the same proved profile.
+ */
+[[nodiscard]] inline bool squad_spawn_profile(std::span<const format::SquadMember> members,
+                                              std::span<const format::ActorClass> actors,
+                                              std::span<const std::int32_t> requestedCounts,
+                                              std::array<std::int8_t, 4>& output) noexcept {
+    output = {};
+    if (members.empty() || members.size() != requestedCounts.size()
+        || std::any_of(
+            requestedCounts.begin(), requestedCounts.end(), [](auto count) { return count < 0; })) {
+        return false;
+    }
+    const bool loose = std::any_of(
+        requestedCounts.begin(), requestedCounts.end(), [](auto count) { return count > 0; });
+    bool found = false;
+    std::array<std::int8_t, 4> common{};
+    for (std::size_t index = 0; index < members.size(); ++index) {
+        if (loose && requestedCounts[index] == 0) {
+            continue;
+        }
+        const auto& member = members[index];
+        if (!valid_member_spawn_profile(member, actors)) {
+            return false;
+        }
+        const bool certified = (member.flags & format::kSquadMemberSpawnProfileExact) != 0;
+        if (!certified && (member.flags & format::kSquadMemberActorClassExact) == 0) {
+            return false;
+        }
+        const auto& profile = certified ? member.authoredSpawnProfile
+                                        : actors[member.actorClassIndex].authoredSpawnProfile;
+        if (!valid_spawn_profile(profile) || (found && profile != common)) {
+            return false;
+        }
+        common = profile;
+        found = true;
+    }
+    output = common;
+    return found;
+}
+
+} // namespace sunrise::state::activity_sdk