package combatturns import ( "sort" "gitea.maximumdirect.net/eric/notarius/internal/core/source" "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd" ) func canonicalizeResponse(response *extractionResponse, doc *source.SourceDocument) { if response == nil { return } for index := range response.CombatTurns { canonicalizeCombatTurn(&response.CombatTurns[index]) } sort.SliceStable(response.CombatTurns, func(i, j int) bool { left, leftOK := earliestSourcePosition(doc, response.CombatTurns[i]) right, rightOK := earliestSourcePosition(doc, response.CombatTurns[j]) if leftOK != rightOK { return leftOK } if !leftOK { return false } return left < right }) } func canonicalizeCombatTurn(turn *combatTurnResponse) { if turn == nil { return } sort.SliceStable(turn.SourceRefs, func(i, j int) bool { left := turn.SourceRefs[i] right := turn.SourceRefs[j] if unitSortValue(left.StartUnitID) != unitSortValue(right.StartUnitID) { return unitSortValue(left.StartUnitID) < unitSortValue(right.StartUnitID) } return unitSortValue(left.EndUnitID) < unitSortValue(right.EndUnitID) }) turn.SourceRefs = dedupeSourceRefs(turn.SourceRefs) } func dedupeSourceRefs(refs []combatSourceRefResponse) []combatSourceRefResponse { if len(refs) < 2 { return refs } out := refs[:0] var previous combatSourceRefResponse for index, ref := range refs { if index > 0 && sameSourceRef(previous, ref) { continue } out = append(out, ref) previous = ref } return out } func sameSourceRef(left combatSourceRefResponse, right combatSourceRefResponse) bool { return left == right } func earliestSourcePosition(doc *source.SourceDocument, turn combatTurnResponse) (int, bool) { if doc == nil { return 0, false } earliest := 0 found := false for _, ref := range turn.SourceRefs { candidate := source.SourceRef{SourceID: doc.ID, StartUnitID: ref.StartUnitID, EndUnitID: ref.EndUnitID} if err := source.ValidateRef(doc, candidate); err != nil { continue } index, ok := source.UnitIndex(doc, candidate.StartUnitID) if !ok || (found && index >= earliest) { continue } earliest = index found = true } return earliest, found } func unitSortValue(value int) int { if value <= 0 { return int(^uint(0) >> 1) } return value } func canonicalCombatTurnList(response extractionResponse, sourceID string) dnd.CombatTurnList { turns := make([]dnd.CombatTurn, len(response.CombatTurns)) for index, turn := range response.CombatTurns { turns[index] = dnd.CombatTurn{ Actor: turn.Actor, TurnKind: dnd.CombatTurnKind(turn.TurnKind), SourceRefs: canonicalSourceRefs(turn.SourceRefs, sourceID), } } if response.CombatTurns == nil { turns = nil } return dnd.CombatTurnList{CombatTurns: turns} } func canonicalSourceRefs(refs []combatSourceRefResponse, sourceID string) []source.SourceRef { if refs == nil { return nil } out := make([]source.SourceRef, len(refs)) for index, ref := range refs { out[index] = source.SourceRef{SourceID: sourceID, StartUnitID: ref.StartUnitID, EndUnitID: ref.EndUnitID} } return out }