Skip to content
This repository was archived by the owner on Sep 20, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
648 changes: 648 additions & 0 deletions docs/drafts/2026-08-04-periodic-effect-attribution-goal.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,52 @@ internal static string Group(
return string.Join(" · ", parts);
}

internal static string PeriodicImpact(
CombatImpactGroup group,
bool chinese,
string? damageMarker = null,
string? shieldMarker = null
)
{
var impact = group.PeriodicImpact;
if (impact == null)
return string.Empty;

var parts = new List<string>();
if (impact.HealthAmount > 0)
{
var amount = Integer(impact.HealthAmount);
var isRegen =
group.Kind == CombatImpactKind.AttributeChange
&& group.NativeAttributeKey == "RegenApplyAmount";
parts.Add(
isRegen
? chinese
? $"治疗 {amount}"
: $"{amount} healed"
: string.IsNullOrWhiteSpace(damageMarker)
? chinese
? $"伤害 {amount}"
: $"{amount} dmg"
: $"{amount} {damageMarker}"
);
}

if (impact.ShieldAmount > 0)
{
var amount = Integer(impact.ShieldAmount);
parts.Add(
string.IsNullOrWhiteSpace(shieldMarker)
? chinese
? $"耗盾 {amount}"
: $"{amount} shield consumed"
: $"{amount} {shieldMarker}"
);
}

return string.Join(" · ", parts);
}

internal static string Target(CombatImpactGroup group, CombatImpactTarget target, bool chinese)
{
var count = $"×{target.Count}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,29 @@ internal enum CombatImpactAuthoritativeBasis
ApplicationCount,
}

internal enum CombatImpactPeriodicKind
{
Burn,
Poison,
Regen,
}

internal enum CombatImpactPeriodicProof
{
Exact,
Constrained,
Proportional,
}

internal readonly record struct PeriodicImpactKey(string SourceId, CombatImpactPeriodicKind Kind);

internal sealed record CombatImpactPeriodicImpact(
int HealthAmount,
int ShieldAmount,
CombatImpactPeriodicProof Proof,
string ModelVersion
);

internal sealed record CombatImpactEntity(
string Id,
string Name,
Expand Down Expand Up @@ -182,6 +205,8 @@ IReadOnlyList<CombatImpactTarget> Targets

internal IReadOnlyList<CombatImpactTriggerSource> TriggerSources { get; init; } = [];

internal CombatImpactPeriodicImpact? PeriodicImpact { get; init; }

internal bool HasDivergentTargetCoverage =>
UnresolvedTargetCount > 0
|| AuthoritativeMetric is { Basis: CombatImpactAuthoritativeBasis.TotalAmount } total
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,69 @@ IReadOnlyDictionary<string, CombatImpactEntity> entities
var report = CombatImpactAggregator.Aggregate(
new CombatImpactProjectionInput(entities, events, useCounts, authoritative)
);
report = AttachPeriodicImpacts(
report,
PeriodicEffectAttribution.Project(simulation, entities)
);
return AttachTriggerSources(report, triggerOccurrences, entities);
}

private static CombatImpactReport AttachPeriodicImpacts(
CombatImpactReport report,
IReadOnlyDictionary<PeriodicImpactKey, CombatImpactPeriodicImpact> impacts
)
{
if (impacts.Count == 0)
return report;

return report with
{
Sources = report
.Sources.Select(source =>
source with
{
Groups = source
.Groups.Select(group =>
{
var kind = PeriodicKind(group);
return
kind.HasValue
&& impacts.TryGetValue(
new PeriodicImpactKey(source.Entity.Id, kind.Value),
out var impact
)
? group with
{
PeriodicImpact = impact,
}
: group;
})
.ToArray(),
}
)
.ToArray(),
};
}

private static CombatImpactPeriodicKind? PeriodicKind(CombatImpactGroup group) =>
group.Surface != CombatImpactEventSurface.AppliedEffect
? null
: group.Kind switch
{
CombatImpactKind.Burn
when group.NativeAttributeKey
== CombatImpactAggregator.NativeKey(CombatImpactKind.Burn) =>
CombatImpactPeriodicKind.Burn,
CombatImpactKind.Poison
when group.NativeAttributeKey
== CombatImpactAggregator.NativeKey(CombatImpactKind.Poison) =>
CombatImpactPeriodicKind.Poison,
CombatImpactKind.AttributeChange
when group.NativeAttributeKey == "RegenApplyAmount" =>
CombatImpactPeriodicKind.Regen,
_ => null,
};

private static CombatImpactReport AttachTriggerSources(
CombatImpactReport report,
IReadOnlyCollection<TriggerOccurrence> occurrences,
Expand Down
Loading