tods-competition-factory 7.2.0 → 7.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/assemblies/generators/mocks/processLeagueProfiles.mjs +2 -1
  3. package/dist/esm/assemblies/governors/scoreGovernor/index.mjs +2 -1
  4. package/dist/esm/assemblies/governors/scoreGovernor/query.mjs +2 -1
  5. package/dist/esm/constants/matchUpStatusConstants.mjs +1 -1
  6. package/dist/esm/fixtures/policies/POLICY_PROGRESSION_DEFAULT.mjs +1 -1
  7. package/dist/esm/mutate/drawDefinitions/matchUpGovernor/{matchUpStatusCodes.mjs → recordSourceSideProvenance.mjs} +3 -14
  8. package/dist/esm/mutate/drawDefinitions/matchUpGovernor/removeDoubleExit.mjs +10 -9
  9. package/dist/esm/mutate/drawDefinitions/positionGovernor/doubleExitAdvancement.mjs +259 -61
  10. package/dist/esm/mutate/matchUps/drawPositions/assignDrawPositionBye.mjs +7 -11
  11. package/dist/esm/mutate/matchUps/drawPositions/directParticipants.mjs +1 -0
  12. package/dist/esm/mutate/matchUps/drawPositions/drawPositionPlacement.mjs +26 -25
  13. package/dist/esm/mutate/matchUps/drawPositions/positionClear.mjs +27 -4
  14. package/dist/esm/mutate/matchUps/drawPositions/progressExitStatus.mjs +20 -13
  15. package/dist/esm/mutate/matchUps/drawPositions/propagateUnfillableLoserBye.mjs +22 -8
  16. package/dist/esm/mutate/matchUps/drawPositions/propagatesByeOnDoubleExit.mjs +5 -0
  17. package/dist/esm/mutate/matchUps/drawPositions/releaseAdvancedDrawPosition.mjs +22 -1
  18. package/dist/esm/mutate/matchUps/drawPositions/removeDirectedParticipants.mjs +5 -4
  19. package/dist/esm/mutate/matchUps/drawPositions/removeSubsequentRoundsParticipant.mjs +4 -3
  20. package/dist/esm/mutate/matchUps/matchUpStatus/applyWithdrawnExits.mjs +1 -0
  21. package/dist/esm/mutate/matchUps/matchUpStatus/reconcileDecider.mjs +101 -0
  22. package/dist/esm/mutate/matchUps/matchUpStatus/reconcileStaleExitOrigins.mjs +65 -0
  23. package/dist/esm/mutate/matchUps/matchUpStatus/setMatchUpStatus.mjs +50 -16
  24. package/dist/esm/mutate/matchUps/matchUpStatus/sideExitProvenance.mjs +94 -45
  25. package/dist/esm/mutate/matchUps/matchUpStatus/sideStatusCodes.mjs +36 -0
  26. package/dist/esm/mutate/matchUps/score/modifyMatchUpScore.mjs +16 -0
  27. package/dist/esm/package.json.mjs +1 -1
  28. package/dist/esm/query/drawDefinition/getStructureInconsistencies.mjs +67 -6
  29. package/dist/esm/query/matchUp/analyzeMatchUp.mjs +29 -2
  30. package/dist/esm/query/matchUp/checkSetIsComplete.mjs +2 -1
  31. package/dist/esm/query/matchUp/getComplement.mjs +19 -1
  32. package/dist/esm/query/matchUp/retainScoreForFormat.mjs +57 -0
  33. package/dist/esm/query/matchUps/getDrawPositionSides.mjs +11 -1
  34. package/dist/esm/validators/validateMatchUpScore.mjs +9 -4
  35. package/dist/tods-competition-factory.d.cts +58 -22
  36. package/dist/tods-competition-factory.d.mts +58 -22
  37. package/dist/tods-competition-factory.d.ts +58 -22
  38. package/dist/tods-competition-factory.development.cjs.js +1310 -614
  39. package/dist/tods-competition-factory.development.cjs.js.map +1 -1
  40. package/dist/tods-competition-factory.production.cjs.min.js +1 -1
  41. package/dist/tods-competition-factory.production.cjs.min.js.map +1 -1
  42. package/package.json +28 -26
package/README.md CHANGED
@@ -142,7 +142,7 @@ Full documentation with interactive examples: **[courthive.github.io/competition
142
142
 
143
143
  ## Testing
144
144
 
145
- **13,000+ tests** covering draw generation and exit propagation, scheduling, scoring, participants and entries, team competition, publishing and embargo, ranking points and ratings, officiating and sanctioning.
145
+ **13,700+ tests** covering draw generation and exit propagation, scheduling, scoring, participants and entries, team competition, publishing and embargo, ranking points and ratings, officiating and sanctioning.
146
146
 
147
147
  Beyond conventional unit and integration tests, the suite includes relational property suites (do/undo, idempotence, monotonicity), agreement oracles, and an at-scale randomized sweep with delta-debugging for exit propagation — added because full branch coverage of a propagation guard proved compatible with hundreds of wrong answers.
148
148
 
@@ -6,13 +6,14 @@ import { isNumeric } from '../../../tools/math.mjs';
6
6
  import { ANY, MALE, OTHER, MIXED, FEMALE } from '../../../constants/genderConstants.mjs';
7
7
  import tieFormatDefaults from '../templates/tieFormatDefaults.mjs';
8
8
  import { AD_HOC, DOUBLE_ROUND_ROBIN, MAIN } from '../../../constants/drawDefinitionConstants.mjs';
9
+ import '../../../helpers/keyValueScore/constants.mjs';
9
10
  import '../../../mutate/matchUps/schedule/byeScheduling.mjs';
10
11
  import { TEAM } from '../../../constants/participantConstants.mjs';
11
12
  import { DIRECT_ACCEPTANCE } from '../../../constants/entryStatusValues.mjs';
12
13
  import { UUID } from '../../../tools/UUID.mjs';
13
- import '../../../helpers/keyValueScore/constants.mjs';
14
14
  import { COMPETITOR } from '../../../constants/participantRoleValues.mjs';
15
15
  import '../../../types/tournamentTypes.mjs';
16
+ import '../../../mutate/matchUps/matchUpStatus/sideStatusCodes.mjs';
16
17
  import '../../../query/drawDefinition/predictDrawCompetitiveBands.mjs';
17
18
  import { generateDrawDefinition } from '../drawDefinitions/generateDrawDefinition/generateDrawDefinition.mjs';
18
19
  import { addParticipants } from '../../../mutate/participants/addParticipants.mjs';
@@ -23,9 +23,9 @@ export { deduceMatchUpFormat } from '../../../query/scoring/deduceMatchUpFormat.
23
23
  export { generateScoreString } from '../../generators/matchUps/generateScoreString.mjs';
24
24
  export { generateTieMatchUpScore } from '../../generators/tieMatchUpScore/generateTieMatchUpScore.mjs';
25
25
  export { getEpisodes } from '../../../query/scoring/getEpisodes.mjs';
26
+ export { getMaxSetScore, getSetComplement, getTiebreakComplement } from '../../../query/matchUp/getComplement.mjs';
26
27
  export { getScore } from '../../../query/scoring/getScore.mjs';
27
28
  export { getScoreboard } from '../../../query/scoring/getScoreboard.mjs';
28
- export { getSetComplement, getTiebreakComplement } from '../../../query/matchUp/getComplement.mjs';
29
29
  export { getWinner } from '../../../query/scoring/getWinner.mjs';
30
30
  export { inferServeSide } from '../../../mutate/scoring/serveSideCalculator.mjs';
31
31
  export { isComplete } from '../../../query/scoring/isComplete.mjs';
@@ -34,6 +34,7 @@ export { keyValueScore } from '../../../helpers/keyValueScore/keyValueScore.mjs'
34
34
  export { parse as parseMatchUpFormat } from '../../../helpers/matchUpFormatCode/parse.mjs';
35
35
  export { parseScoreString } from '../../../tools/parseScoreString.mjs';
36
36
  export { resolvePointValue } from '../../../mutate/scoring/resolvePointValue.mjs';
37
+ export { retainScoreForFormat } from '../../../query/matchUp/retainScoreForFormat.mjs';
37
38
  export { reverseScore } from '../../generators/score/reverseScore.mjs';
38
39
  export { stringify as stringifyMatchUpFormat } from '../../../helpers/matchUpFormatCode/stringify.mjs';
39
40
  export { toStatObjects } from '../../../query/scoring/statistics/toStatObjects.mjs';
@@ -2,7 +2,8 @@ export { validateMatchUpScore, validateSetScore } from '../../../validators/vali
2
2
  export { isValidMatchUpFormat } from '../../../validators/isValidMatchUpFormat.mjs';
3
3
  export { validateTieFormat } from '../../../validators/validateTieFormat.mjs';
4
4
  export { validateScore } from '../../../validators/validateScore.mjs';
5
- export { getSetComplement, getTiebreakComplement } from '../../../query/matchUp/getComplement.mjs';
5
+ export { getMaxSetScore, getSetComplement, getTiebreakComplement } from '../../../query/matchUp/getComplement.mjs';
6
+ export { retainScoreForFormat } from '../../../query/matchUp/retainScoreForFormat.mjs';
6
7
  export { checkScoreHasValue } from '../../../query/matchUp/checkScoreHasValue.mjs';
7
8
  export { checkSetIsComplete } from '../../../query/matchUp/checkSetIsComplete.mjs';
8
9
  export { analyzeScore } from '../../../query/matchUp/analyzeScore.mjs';
@@ -1,4 +1,4 @@
1
- import { CANCELLED, ABANDONED, COMPLETED, DEAD_RUBBER, DEFAULTED, DOUBLE_WALKOVER, DOUBLE_DEFAULT, RETIRED, WALKOVER, TO_BE_PLAYED, SUSPENDED, NOT_PLAYED, INCOMPLETE, IN_PROGRESS, CHALLENGED, BYE, AWAITING_RESULT } from './matchUpStatusValues.mjs';
1
+ import { ABANDONED, AWAITING_RESULT, CANCELLED, CHALLENGED, DEAD_RUBBER, IN_PROGRESS, INCOMPLETE, NOT_PLAYED, SUSPENDED, TO_BE_PLAYED, COMPLETED, DEFAULTED, DOUBLE_WALKOVER, DOUBLE_DEFAULT, RETIRED, WALKOVER, BYE } from './matchUpStatusValues.mjs';
2
2
 
3
3
  const recoveryTimeRequiredMatchUpStatuses = [
4
4
  AWAITING_RESULT,
@@ -2,7 +2,7 @@ import { POLICY_TYPE_PROGRESSION } from '../../constants/policyConstants.mjs';
2
2
 
3
3
  const POLICY_PROGRESSION_DEFAULT = {
4
4
  [POLICY_TYPE_PROGRESSION]: {
5
- doubleExitPropagateBye: false,
5
+ doubleExitPropagateBye: true,
6
6
  autoPlaceQualifiers: false,
7
7
  autoReplaceQualifiers: false,
8
8
  autoRemoveQualifiers: false,
@@ -1,9 +1,9 @@
1
- import { producedExitStatus, mergeSideExitProvenance } from '../../matchUps/matchUpStatus/sideExitProvenance.mjs';
1
+ import { mergeSideExitProvenance, producedExitStatus } from '../../matchUps/matchUpStatus/sideExitProvenance.mjs';
2
2
  import { getPairedPreviousMatchUp } from '../../../query/matchUps/getPairedPreviousMatchup.mjs';
3
3
  import { definedAttributes } from '../../../tools/definedAttributes.mjs';
4
4
  import { TO_BE_PLAYED } from '../../../constants/matchUpStatusValues.mjs';
5
5
 
6
- function updateMatchUpStatusCodes({ inContextDrawMatchUps, sourceMatchUpStatus, sourceMatchUpId, matchUpsMap, matchUp, }) {
6
+ function recordSourceSideProvenance({ inContextDrawMatchUps, sourceMatchUpStatus, sourceMatchUpId, matchUpsMap, matchUp, }) {
7
7
  const sourceMatchUp = inContextDrawMatchUps.find((matchUp) => matchUp.matchUpId === sourceMatchUpId);
8
8
  const { pairedPreviousMatchUp } = getPairedPreviousMatchUp({
9
9
  structureId: sourceMatchUp?.structureId,
@@ -18,17 +18,6 @@ function updateMatchUpStatusCodes({ inContextDrawMatchUps, sourceMatchUpStatus,
18
18
  (sourceMatchUp?.roundPosition < pairedMatchUp?.roundPosition && 1) || 2
19
19
  :
20
20
  (sourceMatchUp.structureId === pairedMatchUp?.structureId && 2) || 1;
21
- matchUp.matchUpStatusCodes = (matchUp.matchUpStatusCodes ?? []).map((code) => {
22
- const value = typeof code === 'object' && code !== null ? code : { code: code ?? undefined };
23
- if (value.sideNumber === sourceSideNumber) {
24
- return {
25
- ...value,
26
- matchUpStatus: producedExitStatus(sourceMatchUpStatus),
27
- previousMatchUpStatus: sourceMatchUpStatus,
28
- };
29
- }
30
- return value;
31
- });
32
21
  if (sourceMatchUpStatus && sourceMatchUpStatus !== TO_BE_PLAYED) {
33
22
  mergeSideExitProvenance({
34
23
  matchUp,
@@ -44,4 +33,4 @@ function updateMatchUpStatusCodes({ inContextDrawMatchUps, sourceMatchUpStatus,
44
33
  }
45
34
  }
46
35
 
47
- export { updateMatchUpStatusCodes };
36
+ export { recordSourceSideProvenance };
@@ -1,5 +1,6 @@
1
- import { withdrawByeClaim, withdrawByeClaimsFrom, byeClaimSurvives, getNativeSideExitProvenance, projectExitStatusCodes, setSideExitProvenance, retainForeignProvenance, deriveExitStateFromProvenance } from '../../matchUps/matchUpStatus/sideExitProvenance.mjs';
1
+ import { withdrawByeClaim, withdrawByeClaimsFrom, byeClaimSurvives, getSideExitProvenance, retainPolicyCodes, setSideExitProvenance, retainForeignProvenance, deriveExitStateFromProvenance } from '../../matchUps/matchUpStatus/sideExitProvenance.mjs';
2
2
  import { removeDirectedBye, removeDirectedWinner } from '../../matchUps/drawPositions/removeDirectedParticipants.mjs';
3
+ import { propagatesByeOnDoubleExit } from '../../matchUps/drawPositions/propagatesByeOnDoubleExit.mjs';
3
4
  import { getPairedPreviousMatchUp } from '../../../query/matchUps/getPairedPreviousMatchup.mjs';
4
5
  import { modifyMatchUpScore } from '../../matchUps/score/modifyMatchUpScore.mjs';
5
6
  import { decorateResult } from '../../../functions/global/decorateResult.mjs';
@@ -98,7 +99,7 @@ function removeDoubleExit(params) {
98
99
  if (anotherClaimSurvives) {
99
100
  return decorateResult({ result: { ...SUCCESS }, stack });
100
101
  }
101
- if (appliedPolicies?.progression?.doubleExitPropagateBye || byePropagatedToLoserMatchUp) {
102
+ if (propagatesByeOnDoubleExit(appliedPolicies) || byePropagatedToLoserMatchUp) {
102
103
  removeDirectedBye({
103
104
  drawPosition: loserTargetDrawPosition,
104
105
  targetLink: loserTargetLink,
@@ -133,7 +134,7 @@ function removeDoubleExit(params) {
133
134
  return decorateResult({ result: { ...SUCCESS }, stack });
134
135
  }
135
136
  function carriesWithdrawnOrigin(matchUp, withdrawnSourceIds) {
136
- const provenance = getNativeSideExitProvenance({ matchUp });
137
+ const provenance = getSideExitProvenance({ matchUp });
137
138
  if (!provenance)
138
139
  return false;
139
140
  return [1, 2].some((sideNumber) => {
@@ -170,7 +171,7 @@ function withdrawExitFromByeChain({ inContextDrawMatchUps, withdrawnSourceIds, d
170
171
  keyColors,
171
172
  });
172
173
  const result = modifyMatchUpScore({
173
- matchUpStatusCodes: unwound.provenance ? projectExitStatusCodes(unwound.provenance) : [],
174
+ matchUpStatusCodes: retainPolicyCodes(noContextTargetMatchUp),
174
175
  removeWinningSide: unwound.winningSide === undefined,
175
176
  matchUpStatus: unwound.matchUpStatus,
176
177
  matchUpId: fromMatchUp.matchUpId,
@@ -308,7 +309,7 @@ function conditionallyRemoveDrawPosition(params) {
308
309
  if (result.error)
309
310
  return decorateResult({ result, stack });
310
311
  const carriesSomethingOfThisCascade = carriesWithdrawnOrigin(noContextTargetMatchUp, withdrawnSourceIds);
311
- const holdsProvenance = !!getNativeSideExitProvenance({ matchUp: noContextTargetMatchUp });
312
+ const holdsProvenance = !!getSideExitProvenance({ matchUp: noContextTargetMatchUp });
312
313
  if (holdsProvenance && !carriesSomethingOfThisCascade) {
313
314
  pushGlobalLog({
314
315
  method: stack,
@@ -329,7 +330,7 @@ function conditionallyRemoveDrawPosition(params) {
329
330
  const removeScore = !pairedPreviousDoubleExit;
330
331
  result = modifyMatchUpScore({
331
332
  ...params,
332
- matchUpStatusCodes: unwound.provenance ? projectExitStatusCodes(unwound.provenance) : [],
333
+ matchUpStatusCodes: retainPolicyCodes(noContextTargetMatchUp),
333
334
  removeWinningSide: unwound.winningSide === undefined,
334
335
  matchUpId: targetMatchUp.matchUpId,
335
336
  matchUp: noContextTargetMatchUp,
@@ -384,10 +385,10 @@ function getUnwoundState({ pairedPreviousDoubleExit, noContextTargetMatchUp, wit
384
385
  if (noContextTargetMatchUp.matchUpStatus === BYE) {
385
386
  return {
386
387
  matchUpStatus: BYE,
387
- provenance: retainForeignProvenance(getNativeSideExitProvenance({ matchUp: noContextTargetMatchUp }), withdrawnSourceIds),
388
+ provenance: retainForeignProvenance(getSideExitProvenance({ matchUp: noContextTargetMatchUp }), withdrawnSourceIds),
388
389
  };
389
390
  }
390
- const retained = retainForeignProvenance(getNativeSideExitProvenance({ matchUp: noContextTargetMatchUp }), withdrawnSourceIds);
391
+ const retained = retainForeignProvenance(getSideExitProvenance({ matchUp: noContextTargetMatchUp }), withdrawnSourceIds);
391
392
  if (pairedPreviousDoubleExit) {
392
393
  return {
393
394
  matchUpStatus: [DOUBLE_DEFAULT, DEFAULTED].includes(noContextTargetMatchUp?.matchUpStatus) ? DEFAULTED : WALKOVER,
@@ -397,7 +398,7 @@ function getUnwoundState({ pairedPreviousDoubleExit, noContextTargetMatchUp, wit
397
398
  if (targetDrawPositionIsBye({ drawDefinition, noContextTargetMatchUp, targetMatchUp })) {
398
399
  return {
399
400
  matchUpStatus: BYE,
400
- provenance: retainForeignProvenance(getNativeSideExitProvenance({ matchUp: noContextTargetMatchUp }), withdrawnSourceIds),
401
+ provenance: retainForeignProvenance(getSideExitProvenance({ matchUp: noContextTargetMatchUp }), withdrawnSourceIds),
401
402
  };
402
403
  }
403
404
  if (isDoubleExit(noContextTargetMatchUp?.matchUpStatus)) {
@@ -1,20 +1,23 @@
1
- import { propagateUnfillableLoserBye } from '../../matchUps/drawPositions/propagateUnfillableLoserBye.mjs';
2
- import { assignDrawPositionBye, advanceDrawPosition } from '../../matchUps/drawPositions/assignDrawPositionBye.mjs';
1
+ import { advanceDrawPosition, assignDrawPositionBye } from '../../matchUps/drawPositions/assignDrawPositionBye.mjs';
3
2
  import { getPairedPreviousMatchUpIsDoubleExit } from '../../../query/matchUps/getPairedPreviousMatchUpIsDoubleExit.mjs';
4
- import { assignMatchUpDrawPosition, directWinner } from '../../matchUps/drawPositions/drawPositionPlacement.mjs';
3
+ import { propagateUnfillableLoserBye } from '../../matchUps/drawPositions/propagateUnfillableLoserBye.mjs';
4
+ import { releaseAdvancedDrawPosition } from '../../matchUps/drawPositions/releaseAdvancedDrawPosition.mjs';
5
+ import { directWinner, assignMatchUpDrawPosition } from '../../matchUps/drawPositions/drawPositionPlacement.mjs';
6
+ import { propagatesByeOnDoubleExit } from '../../matchUps/drawPositions/propagatesByeOnDoubleExit.mjs';
5
7
  import { getExitWinningSide } from '../matchUpGovernor/getExitWinningSide.mjs';
8
+ import { getAllStructureMatchUps } from '../../../query/matchUps/getAllStructureMatchUps.mjs';
6
9
  import { modifyMatchUpScore } from '../../matchUps/score/modifyMatchUpScore.mjs';
7
10
  import { getPositionAssignments } from '../../../query/drawDefinition/positionsGetter.mjs';
11
+ import { isFedLoserEligible } from '../../../query/matchUp/isFedLoserEligible.mjs';
12
+ import { isExit, isAnyExit, isDoubleExit } from '../../../validators/isExit.mjs';
8
13
  import { getAllDrawMatchUps } from '../../../query/matchUps/drawMatchUps.mjs';
9
14
  import { decorateResult } from '../../../functions/global/decorateResult.mjs';
10
15
  import { positionTargets } from '../../../query/matchUp/positionTargets.mjs';
16
+ import { getMatchUpsMap } from '../../../query/matchUps/getMatchUpsMap.mjs';
11
17
  import { pushGlobalLog } from '../../../functions/global/globalLog.mjs';
12
- import { isExit, isDoubleExit, isAnyExit } from '../../../validators/isExit.mjs';
13
- import { getAllStructureMatchUps } from '../../../query/matchUps/getAllStructureMatchUps.mjs';
14
- import { isFedLoserEligible } from '../../../query/matchUp/isFedLoserEligible.mjs';
15
18
  import { findStructure } from '../../../acquire/findStructure.mjs';
16
19
  import { overlap } from '../../../tools/arrays.mjs';
17
- import { producedExitStatus, recordByeClaim, buildCarriedExitProvenance, getSideExitProvenance, projectExitStatusCodes, mergeSideExitProvenance, collapseDoubleExitStatus, buildSideExitProvenance } from '../../matchUps/matchUpStatus/sideExitProvenance.mjs';
20
+ import { getExitSides, getSideExitProvenance, buildCarriedExitProvenance, retainPolicyCodes, mergeSideExitProvenance, producedExitStatus, recordByeClaim, collapseDoubleExitStatus, buildSideExitProvenance, deriveExitStateFromProvenance, deriveStatusCodes } from '../../matchUps/matchUpStatus/sideExitProvenance.mjs';
18
21
  import { MISSING_STRUCTURE, MISSING_MATCHUP, DRAW_POSITION_ASSIGNED } from '../../../constants/errorConditionConstants.mjs';
19
22
  import { CONTAINER, FIRST_MATCHUP } from '../../../constants/drawDefinitionConstants.mjs';
20
23
  import { BYE } from '../../../constants/matchUpStatusValues.mjs';
@@ -51,7 +54,7 @@ function doubleExitAdvancement(params) {
51
54
  winnerStatus: winnerMatchUp?.matchUpStatus,
52
55
  winnerDP: JSON.stringify(winnerMatchUp?.drawPositions),
53
56
  });
54
- const loserTargetStillOpen = !!(appliedPolicies?.progression?.doubleExitPropagateBye &&
57
+ const loserTargetStillOpen = !!(propagatesByeOnDoubleExit(appliedPolicies) &&
55
58
  loserMatchUp?.matchUpStatus === BYE &&
56
59
  loserTargetDrawPosition !== undefined &&
57
60
  !getPositionAssignments({
@@ -135,19 +138,21 @@ function doubleExitAdvancement(params) {
135
138
  });
136
139
  if (result.error)
137
140
  return decorateResult({ result, stack });
138
- const refreshed = getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? [];
139
- const carried = carryExitOnward({
140
- fromMatchUp: refreshed.find((m) => m.matchUpId === loserMatchUp.matchUpId) ?? loserMatchUp,
141
- EXIT: producedExitStatus(params.matchUpStatus),
142
- originMatchUpId: sourceMatchUp?.matchUpId,
143
- inContextDrawMatchUps: refreshed,
144
- drawDefinition,
145
- matchUpsMap,
146
- params,
147
- stack,
148
- });
149
- if (carried.error)
150
- return decorateResult({ result: carried, stack });
141
+ if (result.stamped) {
142
+ const refreshed = getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? [];
143
+ const carried = carryExitOnward({
144
+ fromMatchUp: refreshed.find((m) => m.matchUpId === loserMatchUp.matchUpId) ?? loserMatchUp,
145
+ EXIT: producedExitStatus(params.matchUpStatus),
146
+ originMatchUpId: sourceMatchUp?.matchUpId,
147
+ inContextDrawMatchUps: refreshed,
148
+ drawDefinition,
149
+ matchUpsMap,
150
+ params,
151
+ stack,
152
+ });
153
+ if (carried.error)
154
+ return decorateResult({ result: carried, stack });
155
+ }
151
156
  }
152
157
  if (winnerMatchUp) {
153
158
  logAdvancement(stack, {
@@ -169,7 +174,7 @@ function doubleExitAdvancement(params) {
169
174
  }
170
175
  function handleLoserMatchUp({ loserMatchUpIsEmptyExit, loserMatchUpIsDoubleExit, loserTargetDrawPosition, appliedPolicies, tournamentRecord, drawDefinition, sourceMatchUp, loserMatchUp, targetLinks, matchUpsMap, params, event, stack, }) {
171
176
  const { loserTargetLink } = targetLinks;
172
- const propagateBye = appliedPolicies?.progression?.doubleExitPropagateBye;
177
+ const propagateBye = propagatesByeOnDoubleExit(appliedPolicies);
173
178
  const targetFedIn = loserMatchUp.hasFedDrawPosition;
174
179
  if (propagateBye || targetFedIn) {
175
180
  logAdvancement(stack, {
@@ -230,12 +235,12 @@ function stampExitOnByeHeldLoserTarget({ loserTargetDrawPosition, drawDefinition
230
235
  const { positionAssignments } = getPositionAssignments({ structureId: loserMatchUp.structureId, drawDefinition });
231
236
  const targetAssignment = positionAssignments?.find((assignment) => assignment.drawPosition === loserTargetDrawPosition);
232
237
  if (targetAssignment?.bye || targetAssignment?.participantId)
233
- return { ...SUCCESS };
238
+ return { ...SUCCESS, stamped: false };
234
239
  const drawPositions = loserMatchUp.drawPositions ?? [];
235
240
  const positionIndex = drawPositions.indexOf(loserTargetDrawPosition);
236
241
  const exitingSideNumber = loserMatchUp.feedRound ? 1 : positionIndex + 1;
237
242
  if (positionIndex === -1 || (exitingSideNumber !== 1 && exitingSideNumber !== 2)) {
238
- return { ...SUCCESS };
243
+ return { ...SUCCESS, stamped: false };
239
244
  }
240
245
  const noContextLoserMatchUp = matchUpsMap.drawMatchUps.find((matchUp) => matchUp.matchUpId === loserMatchUp.matchUpId);
241
246
  if (!noContextLoserMatchUp)
@@ -258,7 +263,7 @@ function stampExitOnByeHeldLoserTarget({ loserTargetDrawPosition, drawDefinition
258
263
  exitingSideNumber,
259
264
  });
260
265
  const result = modifyMatchUpScore({
261
- matchUpStatusCodes: projectExitStatusCodes(provenance),
266
+ matchUpStatusCodes: retainPolicyCodes(noContextLoserMatchUp),
262
267
  appliedPolicies: params.appliedPolicies,
263
268
  matchUpId: loserMatchUp.matchUpId,
264
269
  matchUp: noContextLoserMatchUp,
@@ -269,7 +274,7 @@ function stampExitOnByeHeldLoserTarget({ loserTargetDrawPosition, drawDefinition
269
274
  if (result.error)
270
275
  return result;
271
276
  mergeSideExitProvenance({ matchUp: noContextLoserMatchUp, provenance });
272
- return { ...SUCCESS };
277
+ return { ...SUCCESS, stamped: true };
273
278
  }
274
279
  function handleEmptyExitLoser({ loserTargetDrawPosition, drawDefinition, sourceMatchUp, loserMatchUp, matchUpsMap, params, stack, }) {
275
280
  const DOUBLE_EXIT = collapseDoubleExitStatus([params.matchUpStatus, loserMatchUp?.matchUpStatus]);
@@ -293,7 +298,7 @@ function handleEmptyExitLoser({ loserTargetDrawPosition, drawDefinition, sourceM
293
298
  ...getSideExitProvenance({ matchUp: noContextLoserMatchUp }),
294
299
  ...arrivingProvenance,
295
300
  };
296
- const matchUpStatusCodes = projectExitStatusCodes(provenance);
301
+ const matchUpStatusCodes = retainPolicyCodes(noContextLoserMatchUp);
297
302
  const result = modifyMatchUpScore({
298
303
  ...params,
299
304
  matchUp: noContextLoserMatchUp,
@@ -305,37 +310,48 @@ function handleEmptyExitLoser({ loserTargetDrawPosition, drawDefinition, sourceM
305
310
  if (result.error)
306
311
  return result;
307
312
  mergeSideExitProvenance({ matchUp: noContextLoserMatchUp, provenance });
308
- const refreshed = getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? [];
309
- const convergedTargets = positionTargets({
310
- matchUpId: loserMatchUp.matchUpId,
311
- inContextDrawMatchUps: refreshed,
313
+ const onward = advanceConvergedWinner({
314
+ convergedMatchUp: loserMatchUp,
312
315
  drawDefinition,
316
+ matchUpsMap,
317
+ DOUBLE_EXIT,
318
+ params,
319
+ stack,
313
320
  });
314
- const convergedWinnerMatchUp = convergedTargets?.targetMatchUps?.winnerMatchUp;
315
- if (convergedWinnerMatchUp) {
316
- logAdvancement(stack, {
317
- color: 'cyan',
318
- decision: 'CONVERGED_advance_its_own_winner',
319
- from: loserMatchUp.matchUpId,
320
- to: convergedWinnerMatchUp.matchUpId,
321
- });
322
- const onward = conditionallyAdvanceDrawPosition({
323
- ...params,
324
- inContextDrawMatchUps: refreshed,
325
- matchUpId: convergedWinnerMatchUp.matchUpId,
326
- targetMatchUp: convergedWinnerMatchUp,
327
- sourceMatchUp: inContextLoserMatchUp(refreshed, loserMatchUp.matchUpId) ?? loserMatchUp,
328
- matchUpStatus: DOUBLE_EXIT,
329
- drawDefinition,
330
- matchUpsMap,
331
- });
332
- if (onward?.error)
333
- return onward;
334
- }
321
+ if (onward?.error)
322
+ return onward;
335
323
  return result;
336
324
  }
337
325
  return { ...SUCCESS };
338
326
  }
327
+ function advanceConvergedWinner({ convergedMatchUp, drawDefinition, matchUpsMap, DOUBLE_EXIT, params, stack }) {
328
+ const refreshed = getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? [];
329
+ const convergedTargets = positionTargets({
330
+ matchUpId: convergedMatchUp.matchUpId,
331
+ inContextDrawMatchUps: refreshed,
332
+ drawDefinition,
333
+ });
334
+ const convergedWinnerMatchUp = convergedTargets?.targetMatchUps?.winnerMatchUp;
335
+ if (!convergedWinnerMatchUp)
336
+ return { ...SUCCESS };
337
+ logAdvancement(stack, {
338
+ color: 'cyan',
339
+ decision: 'CONVERGED_advance_its_own_winner',
340
+ from: convergedMatchUp.matchUpId,
341
+ to: convergedWinnerMatchUp.matchUpId,
342
+ });
343
+ return conditionallyAdvanceDrawPosition({
344
+ ...params,
345
+ walkoverWinningSide: undefined,
346
+ inContextDrawMatchUps: refreshed,
347
+ matchUpId: convergedWinnerMatchUp.matchUpId,
348
+ targetMatchUp: convergedWinnerMatchUp,
349
+ sourceMatchUp: inContextLoserMatchUp(refreshed, convergedMatchUp.matchUpId) ?? convergedMatchUp,
350
+ matchUpStatus: DOUBLE_EXIT,
351
+ drawDefinition,
352
+ matchUpsMap,
353
+ });
354
+ }
339
355
  function inContextLoserMatchUp(inContextDrawMatchUps, matchUpId) {
340
356
  return inContextDrawMatchUps.find((candidate) => candidate.matchUpId === matchUpId);
341
357
  }
@@ -369,6 +385,20 @@ function conditionallyAdvanceDrawPosition(params) {
369
385
  EXIT,
370
386
  });
371
387
  if (sameStructure && overlap(sourceDrawPositions, targetMatchUpDrawPositions)) {
388
+ if (isDoubleExit(params.matchUpStatus)) {
389
+ for (const drawPosition of targetMatchUpDrawPositions.filter((position) => sourceDrawPositions.includes(position))) {
390
+ releaseAdvancedDrawPosition({
391
+ fromRoundNumber: targetMatchUp.roundNumber,
392
+ structureId: targetMatchUp.structureId,
393
+ withdrawingExit: true,
394
+ event: params.event,
395
+ tournamentRecord,
396
+ drawDefinition,
397
+ drawPosition,
398
+ matchUpsMap,
399
+ });
400
+ }
401
+ }
372
402
  targetMatchUpDrawPositions = targetMatchUpDrawPositions.filter((drawPosition) => !sourceDrawPositions.includes(drawPosition));
373
403
  }
374
404
  if (sameStructure && targetMatchUpDrawPositions.length > 1)
@@ -455,11 +485,7 @@ function conditionallyAdvanceDrawPosition(params) {
455
485
  sourceMatchUpStatus,
456
486
  sourceSideNumber,
457
487
  });
458
- const provenance = {
459
- ...getSideExitProvenance({ matchUp: noContextTargetMatchUp }),
460
- ...newProvenance,
461
- };
462
- const matchUpStatusCodes = newProvenance ? projectExitStatusCodes(provenance) : [];
488
+ const matchUpStatusCodes = retainPolicyCodes(noContextTargetMatchUp);
463
489
  logAdvancement(stack, {
464
490
  color: 'brightgreen',
465
491
  keyColors: { matchUpStatus: 'brightcyan', winningSide: 'brightyellow' },
@@ -482,9 +508,27 @@ function conditionallyAdvanceDrawPosition(params) {
482
508
  if (result.error)
483
509
  return decorateResult({ result, stack });
484
510
  mergeSideExitProvenance({ matchUp: noContextTargetMatchUp, provenance: newProvenance });
511
+ const merged = getSideExitProvenance({ matchUp: noContextTargetMatchUp });
512
+ const bothDelivered = [1, 2].every((sideNumber) => isDoubleExit(merged?.[sideNumber]?.previousMatchUpStatus));
513
+ const convergedAfterMerge = bothDelivered && !targetHoldsBye;
514
+ {
515
+ const derived = bothDelivered ? deriveExitStateFromProvenance(merged) : undefined;
516
+ if (derived && !targetHoldsBye && derived.matchUpStatus !== noContextTargetMatchUp.matchUpStatus) {
517
+ const reconcile = modifyMatchUpScore({
518
+ ...params,
519
+ removeWinningSide: derived.winningSide === undefined,
520
+ winningSide: derived.winningSide,
521
+ matchUp: noContextTargetMatchUp,
522
+ matchUpStatus: derived.matchUpStatus,
523
+ matchUpStatusCodes: deriveStatusCodes(noContextTargetMatchUp)});
524
+ if (reconcile.error)
525
+ return decorateResult({ result: reconcile, stack });
526
+ }
527
+ }
485
528
  return advanceFromTarget({
486
529
  pairedPreviousMatchUpIsDoubleExit,
487
530
  targetMatchUpDrawPositions,
531
+ convergedAfterMerge,
488
532
  noContextTargetMatchUp,
489
533
  inContextDrawMatchUps,
490
534
  walkoverWinningSide,
@@ -529,7 +573,17 @@ function inferSourceSideNumber({ inContextPairedPreviousMatchUp, pairedPreviousM
529
573
  });
530
574
  return sourceSideNumber;
531
575
  }
532
- function advanceFromTarget({ pairedPreviousMatchUpIsDoubleExit, targetMatchUpDrawPositions, noContextTargetMatchUp, inContextDrawMatchUps, walkoverWinningSide, nextWinnerMatchUp, drawDefinition, existingExit, matchUpStatus, targetMatchUp, matchUpsMap, targetData, DOUBLE_EXIT, structure, params, stack, EXIT, }) {
576
+ function advanceFromTarget({ pairedPreviousMatchUpIsDoubleExit, targetMatchUpDrawPositions, convergedAfterMerge, noContextTargetMatchUp, inContextDrawMatchUps, walkoverWinningSide, nextWinnerMatchUp, drawDefinition, existingExit, matchUpStatus, targetMatchUp, matchUpsMap, targetData, DOUBLE_EXIT, structure, params, stack, EXIT, }) {
577
+ if (convergedAfterMerge && !existingExit) {
578
+ return advanceConvergedWinner({
579
+ convergedMatchUp: targetMatchUp,
580
+ drawDefinition,
581
+ matchUpsMap,
582
+ DOUBLE_EXIT,
583
+ params,
584
+ stack,
585
+ });
586
+ }
533
587
  if (existingExit) {
534
588
  logAdvancement(stack, {
535
589
  color: 'brightred',
@@ -656,7 +710,7 @@ function advanceByeAdvancedDrawPosition({ nextWinnerMatchUpDrawPositions, nextWi
656
710
  }),
657
711
  };
658
712
  const result = modifyMatchUpScore({
659
- matchUpStatusCodes: projectExitStatusCodes(provenance),
713
+ matchUpStatusCodes: retainPolicyCodes(noContextNextWinnerMatchUp),
660
714
  appliedPolicies: params.appliedPolicies,
661
715
  matchUpId: noContextNextWinnerMatchUp.matchUpId,
662
716
  matchUp: noContextNextWinnerMatchUp,
@@ -815,7 +869,7 @@ function carryExitOnward({ inContextDrawMatchUps, drawDefinition, matchUpsMap, o
815
869
  matchUpStatus: holdsBye ? BYE : EXIT,
816
870
  });
817
871
  const result = modifyMatchUpScore({
818
- matchUpStatusCodes: projectExitStatusCodes(provenance),
872
+ matchUpStatusCodes: retainPolicyCodes(noContextNextWinnerMatchUp),
819
873
  matchUpId: noContextNextWinnerMatchUp.matchUpId,
820
874
  appliedPolicies: params.appliedPolicies,
821
875
  matchUp: noContextNextWinnerMatchUp,
@@ -927,5 +981,149 @@ function advanceByeToLoserMatchUp(params) {
927
981
  event,
928
982
  });
929
983
  }
984
+ function settleHeldExits({ tournamentRecord, appliedPolicies, drawDefinition, event }) {
985
+ const stack = 'settleHeldExits';
986
+ if (!drawDefinition || propagatesByeOnDoubleExit(appliedPolicies))
987
+ return { ...SUCCESS };
988
+ const matchUpsMap = getMatchUpsMap({ drawDefinition });
989
+ const carried = new Set();
990
+ for (let pass = 0; pass < 16; pass++) {
991
+ const inContextDrawMatchUps = getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? [];
992
+ let held;
993
+ for (const matchUp of inContextDrawMatchUps) {
994
+ if (carried.has(matchUp.matchUpId))
995
+ continue;
996
+ held = getHeldExit({ inContextDrawMatchUps, drawDefinition, matchUpsMap, matchUp });
997
+ if (held)
998
+ break;
999
+ }
1000
+ if (!held)
1001
+ break;
1002
+ carried.add(held.holder.matchUpId);
1003
+ const params = {
1004
+ matchUpStatus: held.origin.previousMatchUpStatus ?? held.origin.matchUpStatus,
1005
+ tournamentRecord,
1006
+ appliedPolicies,
1007
+ event,
1008
+ };
1009
+ const result = carryExitOnward({
1010
+ originMatchUpId: held.origin.sourceMatchUpId,
1011
+ EXIT: held.origin.matchUpStatus,
1012
+ fromMatchUp: held.holder,
1013
+ inContextDrawMatchUps,
1014
+ drawDefinition,
1015
+ matchUpsMap,
1016
+ params,
1017
+ stack,
1018
+ });
1019
+ if (result?.error)
1020
+ return decorateResult({ result, stack });
1021
+ }
1022
+ const crossed = crossLinksThroughByes({ tournamentRecord, drawDefinition, matchUpsMap, event, stack });
1023
+ if (crossed.error)
1024
+ return decorateResult({ result: crossed, stack });
1025
+ return { ...SUCCESS };
1026
+ }
1027
+ function crossLinksThroughByes({ tournamentRecord, drawDefinition, matchUpsMap, event, stack }) {
1028
+ for (let pass = 0; pass < 16; pass++) {
1029
+ const inContextDrawMatchUps = getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? [];
1030
+ const crossing = inContextDrawMatchUps
1031
+ .map((matchUp) => getByeCrossing({ inContextDrawMatchUps, drawDefinition, matchUp }))
1032
+ .find(Boolean);
1033
+ if (!crossing)
1034
+ break;
1035
+ logAdvancement(stack, {
1036
+ color: 'cyan',
1037
+ decision: 'BYE_advancement_crosses_winner_link',
1038
+ fromMatchUpId: crossing.matchUp.matchUpId,
1039
+ winnerMatchUpId: crossing.winnerMatchUp.matchUpId,
1040
+ });
1041
+ const result = directWinner({
1042
+ winnerMatchUpDrawPositionIndex: crossing.winnerMatchUpDrawPositionIndex,
1043
+ winningDrawPosition: crossing.drawPosition,
1044
+ winnerTargetLink: crossing.winnerTargetLink,
1045
+ sourceMatchUpId: crossing.matchUp.matchUpId,
1046
+ winnerMatchUp: crossing.winnerMatchUp,
1047
+ projectedWinningSide: undefined,
1048
+ sourceMatchUpStatus: undefined,
1049
+ dualMatchUp: undefined,
1050
+ inContextDrawMatchUps,
1051
+ tournamentRecord,
1052
+ drawDefinition,
1053
+ matchUpsMap,
1054
+ event,
1055
+ });
1056
+ if (result.error)
1057
+ return result;
1058
+ const after = getAllDrawMatchUps({ inContext: true, drawDefinition, matchUpsMap })?.matchUps ?? [];
1059
+ const target = after.find((matchUp) => matchUp.matchUpId === crossing.winnerMatchUp.matchUpId);
1060
+ if (!target?.sides?.some((side) => side.participantId === crossing.participantId))
1061
+ break;
1062
+ }
1063
+ return { ...SUCCESS };
1064
+ }
1065
+ function getByeCrossing({ inContextDrawMatchUps, drawDefinition, matchUp }) {
1066
+ if (matchUp.collectionId || !matchUp.winnerMatchUpId)
1067
+ return undefined;
1068
+ const occupants = (matchUp.sides ?? []).filter((side) => side.participantId && !side.bye);
1069
+ if (occupants.length !== 1 || !matchUpHoldsBye({ drawDefinition, matchUp }))
1070
+ return undefined;
1071
+ const { targetMatchUps, targetLinks } = positionTargets({
1072
+ matchUpId: matchUp.matchUpId,
1073
+ inContextDrawMatchUps,
1074
+ drawDefinition,
1075
+ });
1076
+ const winnerMatchUp = targetMatchUps?.winnerMatchUp;
1077
+ const winnerTargetLink = targetLinks?.winnerTargetLink;
1078
+ if (!winnerMatchUp || !winnerTargetLink || winnerMatchUp.structureId === matchUp.structureId)
1079
+ return undefined;
1080
+ if (winnerMatchUp.winningSide)
1081
+ return undefined;
1082
+ const [{ participantId, drawPosition }] = occupants;
1083
+ if (winnerMatchUp.sides?.some((side) => side.participantId === participantId))
1084
+ return undefined;
1085
+ return {
1086
+ winnerMatchUpDrawPositionIndex: targetMatchUps.winnerMatchUpDrawPositionIndex,
1087
+ winnerTargetLink,
1088
+ winnerMatchUp,
1089
+ participantId,
1090
+ drawPosition,
1091
+ matchUp,
1092
+ };
1093
+ }
1094
+ function getHeldExit({ inContextDrawMatchUps, drawDefinition, matchUpsMap, matchUp }) {
1095
+ if (matchUp.collectionId || matchUp.winningSide || !matchUp.winnerMatchUpId)
1096
+ return undefined;
1097
+ if (matchUp.sides?.some((side) => side.participantId))
1098
+ return undefined;
1099
+ if (!matchUpHoldsBye({ drawDefinition, matchUp }))
1100
+ return undefined;
1101
+ const stored = matchUpsMap.drawMatchUps.find((candidate) => candidate.matchUpId === matchUp.matchUpId);
1102
+ const [exitSideNumber] = getExitSides({ matchUp: stored });
1103
+ const origin = exitSideNumber ? getSideExitProvenance({ matchUp: stored })?.[exitSideNumber] : undefined;
1104
+ if (!origin?.sourceMatchUpId || !isExit(origin.matchUpStatus))
1105
+ return undefined;
1106
+ const target = inContextDrawMatchUps.find((candidate) => candidate.matchUpId === matchUp.winnerMatchUpId);
1107
+ if (!target || target.winningSide)
1108
+ return undefined;
1109
+ const arrivalSideNumber = getExitArrivalSideNumber({
1110
+ nextWinnerMatchUp: target,
1111
+ sourceMatchUp: matchUp,
1112
+ inContextDrawMatchUps,
1113
+ });
1114
+ if (!arrivalSideNumber)
1115
+ return undefined;
1116
+ if (target.sides?.some((side) => side.sideNumber === arrivalSideNumber && side.participantId))
1117
+ return undefined;
1118
+ const storedTarget = matchUpsMap.drawMatchUps.find((candidate) => candidate.matchUpId === target.matchUpId);
1119
+ const targetProvenance = getSideExitProvenance({ matchUp: storedTarget });
1120
+ if (targetProvenance?.[arrivalSideNumber])
1121
+ return undefined;
1122
+ const opponentSide = target.sides?.find((side) => side.sideNumber === 3 - arrivalSideNumber);
1123
+ const settledNow = !!opponentSide?.participantId || !!opponentSide?.bye;
1124
+ if (!settledNow)
1125
+ return undefined;
1126
+ return { holder: matchUp, origin };
1127
+ }
930
1128
 
931
- export { doubleExitAdvancement };
1129
+ export { doubleExitAdvancement, settleHeldExits };