Add SCUM log sessions and trajectory projections

This commit is contained in:
npc0-hue
2026-08-27 12:34:07 +08:00
parent 0940780058
commit 316efbe780
38 changed files with 1549 additions and 84 deletions
+93 -9
View File
@@ -44,6 +44,18 @@ type GameClientBridgeQueryTemplateDeclaration struct {
MaxRows int
TimeoutSeconds int
PollIntervalSeconds int
Projections []GameClientBridgeQueryProjectionDeclaration
}
type GameClientBridgeQueryProjectionDeclaration struct {
Collection string
RowPath string
MatchField string
MatchValue string
UpsertKeys []string
FieldMappings map[string]string
FixedValues map[string]string
ObservedAtField string
}
type GameClientBridgeLogProjectionStepDeclaration struct {
@@ -54,6 +66,7 @@ type GameClientBridgeLogProjectionTargetDeclaration struct {
Collection string
UpsertKeys []string
CaptureMappings map[string]string
HashMappings map[string]string
FixedValues map[string]string
ObservedAtField string
}
@@ -64,6 +77,30 @@ type GameClientBridgeLogProjectionPresenceDeclaration struct {
ActivityTarget *GameClientBridgeLogProjectionTargetDeclaration
}
type GameClientBridgeLifecycleProjectionDeclaration struct {
Key string
Capabilities []string
ProcessStates []string
Target GameClientBridgeBulkProjectionTargetDeclaration
}
type GameClientBridgeBulkProjectionTargetDeclaration struct {
Collection string
MatchField string
MatchValue string
FixedValues map[string]string
ObservedAtField string
ActivityTarget *GameClientBridgeBulkActivityTargetDeclaration
}
type GameClientBridgeBulkActivityTargetDeclaration struct {
Collection string
UpsertKeys []string
RowMappings map[string]string
FixedValues map[string]string
ObservedAtField string
}
type GameClientBridgeLogProjectionDeclaration struct {
Key string
StreamKeys []string
@@ -115,15 +152,16 @@ type GameClientBridgeCompanionDeclaration struct {
}
type GameClientBridgeManifest struct {
Commands []GameClientBridgeCommandDeclaration
Snapshots []GameClientBridgeSnapshotDeclaration
QueryTemplates []GameClientBridgeQueryTemplateDeclaration
LogProjections []GameClientBridgeLogProjectionDeclaration
DataPacks []GameClientBridgeDataPackDeclaration
Retention GameClientBridgeRetention
Pages []GameClientBridgePageContract
Features []GameClientBridgeFeatureDeclaration
Companion GameClientBridgeCompanionDeclaration
Commands []GameClientBridgeCommandDeclaration
Snapshots []GameClientBridgeSnapshotDeclaration
QueryTemplates []GameClientBridgeQueryTemplateDeclaration
LogProjections []GameClientBridgeLogProjectionDeclaration
LifecycleProjections []GameClientBridgeLifecycleProjectionDeclaration
DataPacks []GameClientBridgeDataPackDeclaration
Retention GameClientBridgeRetention
Pages []GameClientBridgePageContract
Features []GameClientBridgeFeatureDeclaration
Companion GameClientBridgeCompanionDeclaration
}
type GameClientBridgeResultStatus string
@@ -394,10 +432,17 @@ func CopyGameClientBridgeManifest(value GameClientBridgeManifest) GameClientBrid
value.Commands = append([]GameClientBridgeCommandDeclaration(nil), value.Commands...)
value.Snapshots = append([]GameClientBridgeSnapshotDeclaration(nil), value.Snapshots...)
value.QueryTemplates = append([]GameClientBridgeQueryTemplateDeclaration(nil), value.QueryTemplates...)
for index := range value.QueryTemplates {
value.QueryTemplates[index] = CopyGameClientBridgeQueryTemplateDeclaration(value.QueryTemplates[index])
}
value.LogProjections = append([]GameClientBridgeLogProjectionDeclaration(nil), value.LogProjections...)
for index := range value.LogProjections {
value.LogProjections[index] = CopyGameClientBridgeLogProjectionDeclaration(value.LogProjections[index])
}
value.LifecycleProjections = append([]GameClientBridgeLifecycleProjectionDeclaration(nil), value.LifecycleProjections...)
for index := range value.LifecycleProjections {
value.LifecycleProjections[index] = CopyGameClientBridgeLifecycleProjectionDeclaration(value.LifecycleProjections[index])
}
value.DataPacks = append([]GameClientBridgeDataPackDeclaration(nil), value.DataPacks...)
for index := range value.DataPacks {
value.DataPacks[index].LogParserRefs = CopyStringSlice(value.DataPacks[index].LogParserRefs)
@@ -419,6 +464,44 @@ func CopyGameClientBridgeManifest(value GameClientBridgeManifest) GameClientBrid
return value
}
func CopyGameClientBridgeQueryTemplateDeclaration(value GameClientBridgeQueryTemplateDeclaration) GameClientBridgeQueryTemplateDeclaration {
value.Projections = append([]GameClientBridgeQueryProjectionDeclaration(nil), value.Projections...)
for index := range value.Projections {
value.Projections[index] = CopyGameClientBridgeQueryProjectionDeclaration(value.Projections[index])
}
return value
}
func CopyGameClientBridgeQueryProjectionDeclaration(value GameClientBridgeQueryProjectionDeclaration) GameClientBridgeQueryProjectionDeclaration {
value.UpsertKeys = CopyStringSlice(value.UpsertKeys)
value.FieldMappings = CopyStringMap(value.FieldMappings)
value.FixedValues = CopyStringMap(value.FixedValues)
return value
}
func CopyGameClientBridgeLifecycleProjectionDeclaration(value GameClientBridgeLifecycleProjectionDeclaration) GameClientBridgeLifecycleProjectionDeclaration {
value.Capabilities = CopyStringSlice(value.Capabilities)
value.ProcessStates = CopyStringSlice(value.ProcessStates)
value.Target = CopyGameClientBridgeBulkProjectionTargetDeclaration(value.Target)
return value
}
func CopyGameClientBridgeBulkProjectionTargetDeclaration(value GameClientBridgeBulkProjectionTargetDeclaration) GameClientBridgeBulkProjectionTargetDeclaration {
value.FixedValues = CopyStringMap(value.FixedValues)
if value.ActivityTarget != nil {
activity := CopyGameClientBridgeBulkActivityTargetDeclaration(*value.ActivityTarget)
value.ActivityTarget = &activity
}
return value
}
func CopyGameClientBridgeBulkActivityTargetDeclaration(value GameClientBridgeBulkActivityTargetDeclaration) GameClientBridgeBulkActivityTargetDeclaration {
value.UpsertKeys = CopyStringSlice(value.UpsertKeys)
value.RowMappings = CopyStringMap(value.RowMappings)
value.FixedValues = CopyStringMap(value.FixedValues)
return value
}
func CopyGameClientBridgeLogProjectionDeclaration(value GameClientBridgeLogProjectionDeclaration) GameClientBridgeLogProjectionDeclaration {
value.StreamKeys = CopyStringSlice(value.StreamKeys)
value.Steps = append([]GameClientBridgeLogProjectionStepDeclaration(nil), value.Steps...)
@@ -438,6 +521,7 @@ func CopyGameClientBridgeLogProjectionDeclaration(value GameClientBridgeLogProje
func CopyGameClientBridgeLogProjectionTargetDeclaration(value GameClientBridgeLogProjectionTargetDeclaration) GameClientBridgeLogProjectionTargetDeclaration {
value.UpsertKeys = CopyStringSlice(value.UpsertKeys)
value.CaptureMappings = CopyStringMap(value.CaptureMappings)
value.HashMappings = CopyStringMap(value.HashMappings)
value.FixedValues = CopyStringMap(value.FixedValues)
return value
}
+10 -5
View File
@@ -4,24 +4,29 @@ import "testing"
func TestCopyGameClientBridgeDeclarationsCopiesQueryTemplateSlices(t *testing.T) {
manifest := GameClientBridgeManifest{
QueryTemplates: []GameClientBridgeQueryTemplateDeclaration{{Key: "player.lookup", PollIntervalSeconds: 3, SQLRef: "sql/player-lookup.sql"}},
QueryTemplates: []GameClientBridgeQueryTemplateDeclaration{{Key: "player.lookup", PollIntervalSeconds: 3, SQLRef: "sql/player-lookup.sql", Projections: []GameClientBridgeQueryProjectionDeclaration{{Collection: "users", RowPath: "rows", MatchField: "kind", MatchValue: "player", UpsertKeys: []string{"steamId"}, FieldMappings: map[string]string{"steamId": "steamId"}, FixedValues: map[string]string{"source": "sqlite"}, ObservedAtField: "sampledAt"}}}},
LogProjections: []GameClientBridgeLogProjectionDeclaration{{
Key: "player.login", StreamKeys: []string{"process.stdout"}, Steps: []GameClientBridgeLogProjectionStepDeclaration{{Pattern: `Player (?<slot>\d+)`}}, CorrelationFields: []string{"slot"}, MaxInterveningLines: 8,
Target: GameClientBridgeLogProjectionTargetDeclaration{Collection: "users", UpsertKeys: []string{"steamId"}, CaptureMappings: map[string]string{"steamId": "steamId"}, FixedValues: map[string]string{"source": "stdout"}, ObservedAtField: "lastLoginAt"},
Target: GameClientBridgeLogProjectionTargetDeclaration{Collection: "users", UpsertKeys: []string{"steamId"}, CaptureMappings: map[string]string{"steamId": "steamId"}, HashMappings: map[string]string{"networkCorrelation": "ip"}, FixedValues: map[string]string{"source": "stdout"}, ObservedAtField: "lastLoginAt"},
Presence: &GameClientBridgeLogProjectionPresenceDeclaration{TimestampField: "lastLoginAt", ActiveWindowSeconds: 600, ActivityTarget: &GameClientBridgeLogProjectionTargetDeclaration{Collection: "activity", UpsertKeys: []string{"steamId"}, CaptureMappings: map[string]string{"steamId": "steamId"}}},
}},
DataPacks: []GameClientBridgeDataPackDeclaration{{Key: "db-v1", LogParserRefs: []string{"logs.json"}, ConfigMapRefs: []string{"config.json"}, DataRefs: []string{"data.json"}}},
Pages: []GameClientBridgePageContract{{PageKey: "players", QueryTemplateKeys: []string{"player.lookup"}}},
LifecycleProjections: []GameClientBridgeLifecycleProjectionDeclaration{{Key: "server.stop", Capabilities: []string{"process.stop"}, ProcessStates: []string{"stopped"}, Target: GameClientBridgeBulkProjectionTargetDeclaration{Collection: "users", MatchField: "online", MatchValue: "true", FixedValues: map[string]string{"online": "false"}, ActivityTarget: &GameClientBridgeBulkActivityTargetDeclaration{Collection: "activity", UpsertKeys: []string{"steamId"}, RowMappings: map[string]string{"steamId": "steamId"}, FixedValues: map[string]string{"eventType": "logout"}}}}},
DataPacks: []GameClientBridgeDataPackDeclaration{{Key: "db-v1", LogParserRefs: []string{"logs.json"}, ConfigMapRefs: []string{"config.json"}, DataRefs: []string{"data.json"}}},
Pages: []GameClientBridgePageContract{{PageKey: "players", QueryTemplateKeys: []string{"player.lookup"}}},
}
manifestCopy := CopyGameClientBridgeManifest(manifest)
manifestCopy.QueryTemplates[0].Key = "mutated"
manifestCopy.QueryTemplates[0].Projections[0].FieldMappings["steamId"] = "mutated"
manifestCopy.LogProjections[0].StreamKeys[0] = "mutated"
manifestCopy.LogProjections[0].Target.CaptureMappings["steamId"] = "mutated"
manifestCopy.LogProjections[0].Target.HashMappings["networkCorrelation"] = "mutated"
manifestCopy.LogProjections[0].Presence.ActivityTarget.CaptureMappings["steamId"] = "mutated"
manifestCopy.LifecycleProjections[0].Capabilities[0] = "mutated"
manifestCopy.LifecycleProjections[0].Target.ActivityTarget.RowMappings["steamId"] = "mutated"
manifestCopy.DataPacks[0].LogParserRefs[0] = "mutated"
manifestCopy.DataPacks[0].DataRefs[0] = "mutated"
manifestCopy.Pages[0].QueryTemplateKeys[0] = "mutated"
if manifest.QueryTemplates[0].Key != "player.lookup" || manifest.QueryTemplates[0].SQLRef != "sql/player-lookup.sql" || manifest.LogProjections[0].StreamKeys[0] != "process.stdout" || manifest.LogProjections[0].Target.CaptureMappings["steamId"] != "steamId" || manifest.LogProjections[0].Presence.ActivityTarget.CaptureMappings["steamId"] != "steamId" || manifest.DataPacks[0].LogParserRefs[0] != "logs.json" || manifest.DataPacks[0].DataRefs[0] != "data.json" || manifest.Pages[0].QueryTemplateKeys[0] != "player.lookup" {
if manifest.QueryTemplates[0].Key != "player.lookup" || manifest.QueryTemplates[0].SQLRef != "sql/player-lookup.sql" || manifest.QueryTemplates[0].Projections[0].FieldMappings["steamId"] != "steamId" || manifest.LogProjections[0].StreamKeys[0] != "process.stdout" || manifest.LogProjections[0].Target.CaptureMappings["steamId"] != "steamId" || manifest.LogProjections[0].Target.HashMappings["networkCorrelation"] != "ip" || manifest.LogProjections[0].Presence.ActivityTarget.CaptureMappings["steamId"] != "steamId" || manifest.LifecycleProjections[0].Capabilities[0] != "process.stop" || manifest.LifecycleProjections[0].Target.ActivityTarget.RowMappings["steamId"] != "steamId" || manifest.DataPacks[0].LogParserRefs[0] != "logs.json" || manifest.DataPacks[0].DataRefs[0] != "data.json" || manifest.Pages[0].QueryTemplateKeys[0] != "player.lookup" {
t.Fatalf("manifest copy aliases query template declarations: source=%#v copy=%#v", manifest, manifestCopy)
}