volleyballsimtypes 0.0.509 → 0.0.510

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.
@@ -35,13 +35,13 @@ function computeMatchRallyAggregates(input) {
35
35
  });
36
36
  return { rallyMetrics, breakdown };
37
37
  }
38
- // A block scores a point in exactly ONE place in the sim (reception-simulator: a dig of a block fails
39
- // and the block landed in-court, i.e. target >= 7). Each such rally is ONE block point regardless of how
40
- // many blockers were on it; the box score records it as one `block_assists` per blocker (or one
41
- // `block_solo`), which is why summing solo+assists over-counts block points. We rebuild the true count
42
- // from the rallies. blockSolo / blockAssists must reconstruct box.block.solo and box.block.assists
43
- // exactly (the correctness gate). `teamOf(playerId)` returns a player's team id (or null); the scoring
44
- // team is the blockers' team.
38
+ // A block scores a point when the block lands in-court (target >= 7): the positional engine ends the rally
39
+ // on that BLOCK event; the zone engine ends it on the failed dig (RECEPTION) right after the BLOCK. Each
40
+ // such rally is ONE block point regardless of how many blockers were on it; the box score records it as one
41
+ // `block_assists` per blocker (or one `block_solo`), which is why summing solo+assists over-counts block
42
+ // points. We rebuild the true count from the rallies. blockSolo / blockAssists must reconstruct box.block.solo
43
+ // and box.block.assists exactly (the correctness gate). `teamOf(playerId)` returns a player's team id (or
44
+ // null); the scoring team is the blockers' team.
45
45
  function tallyBlockPoints(rallies, teamOf) {
46
46
  const out = new Map();
47
47
  const tallyFor = (team) => {
@@ -56,17 +56,22 @@ function tallyBlockPoints(rallies, teamOf) {
56
56
  // Ignore substitutions / libero replacements (eventType < SERVE) when locating the terminal action.
57
57
  const inPlay = rally.events.filter(e => e.eventType >= event_1.EventTypeEnum.SERVE);
58
58
  const last = inPlay[inPlay.length - 1];
59
- const prev = inPlay[inPlay.length - 2];
60
- if (last == null || prev == null)
59
+ if (last == null)
61
60
  continue;
62
- // Block point signature: the rally ends on a failed RECEPTION (dig) whose previous event is a BLOCK
63
- // that landed in-court (target >= 7). target < 7 is a deflection -> the ATTACKER's kill, not a block.
64
- if (last.eventType !== event_1.EventTypeEnum.RECEPTION || last.failure <= 0)
65
- continue;
66
- if (prev.eventType !== event_1.EventTypeEnum.BLOCK)
67
- continue;
68
- const block = prev;
69
- if (block.target < 7)
61
+ // A block point lands in-court (target >= 7); a deflection / tool-out (target < 7) is the ATTACKER's kill.
62
+ // Two engines end a block point in different shapes, both counted here:
63
+ // - positional: the rally ENDS on the BLOCK itself (the stuff kills the ball outright);
64
+ // - zone: the rally ends on a failed RECEPTION (dig) whose previous event is the BLOCK.
65
+ let block;
66
+ if (last.eventType === event_1.EventTypeEnum.BLOCK) {
67
+ block = last;
68
+ }
69
+ else if (last.eventType === event_1.EventTypeEnum.RECEPTION && last.failure > 0) {
70
+ const prev = inPlay[inPlay.length - 2];
71
+ if (prev != null && prev.eventType === event_1.EventTypeEnum.BLOCK)
72
+ block = prev;
73
+ }
74
+ if (block == null || block.target < 7)
70
75
  continue;
71
76
  const team = teamOf(block.blockers[0] ?? block.playerId);
72
77
  if (team == null)
@@ -30,6 +30,14 @@ const teamOf = (id) => id.startsWith('h') ? 'HOME' : id.startsWith('a') ? 'AWAY'
30
30
  const t = (0, point_breakdown_1.tallyBlockPoints)([rally([blockEvent('h1', 8, ['h1', 'h2']), receptionEvent('a3', 0), spikeEvent('a3')])], teamOf);
31
31
  (0, globals_1.expect)(t.size).toBe(0);
32
32
  });
33
+ (0, globals_1.it)('counts a POSITIONAL stuff that ENDS on the BLOCK (no trailing dig)', () => {
34
+ const t = (0, point_breakdown_1.tallyBlockPoints)([rally([spikeEvent('a1'), blockEvent('h1', 9, ['h1', 'h2'])])], teamOf);
35
+ (0, globals_1.expect)(t.get('HOME')).toEqual({ blockPoints: 1, blockSolo: 0, blockAssists: 2 });
36
+ });
37
+ (0, globals_1.it)('does NOT count a terminal touch / tool-out block (target < 7)', () => {
38
+ const t = (0, point_breakdown_1.tallyBlockPoints)([rally([spikeEvent('a1'), blockEvent('h1', 0, ['h1'])])], teamOf);
39
+ (0, globals_1.expect)(t.size).toBe(0);
40
+ });
33
41
  (0, globals_1.it)('reconstructs box block.solo and block.assists across a mix (incl. a triple block)', () => {
34
42
  const t = (0, point_breakdown_1.tallyBlockPoints)([
35
43
  rally([blockEvent('h1', 8, ['h1', 'h2']), receptionEvent('a3', 1)]), // double: +2 assists
@@ -30,13 +30,13 @@ export function computeMatchRallyAggregates(input) {
30
30
  });
31
31
  return { rallyMetrics, breakdown };
32
32
  }
33
- // A block scores a point in exactly ONE place in the sim (reception-simulator: a dig of a block fails
34
- // and the block landed in-court, i.e. target >= 7). Each such rally is ONE block point regardless of how
35
- // many blockers were on it; the box score records it as one `block_assists` per blocker (or one
36
- // `block_solo`), which is why summing solo+assists over-counts block points. We rebuild the true count
37
- // from the rallies. blockSolo / blockAssists must reconstruct box.block.solo and box.block.assists
38
- // exactly (the correctness gate). `teamOf(playerId)` returns a player's team id (or null); the scoring
39
- // team is the blockers' team.
33
+ // A block scores a point when the block lands in-court (target >= 7): the positional engine ends the rally
34
+ // on that BLOCK event; the zone engine ends it on the failed dig (RECEPTION) right after the BLOCK. Each
35
+ // such rally is ONE block point regardless of how many blockers were on it; the box score records it as one
36
+ // `block_assists` per blocker (or one `block_solo`), which is why summing solo+assists over-counts block
37
+ // points. We rebuild the true count from the rallies. blockSolo / blockAssists must reconstruct box.block.solo
38
+ // and box.block.assists exactly (the correctness gate). `teamOf(playerId)` returns a player's team id (or
39
+ // null); the scoring team is the blockers' team.
40
40
  export function tallyBlockPoints(rallies, teamOf) {
41
41
  const out = new Map();
42
42
  const tallyFor = (team) => {
@@ -51,17 +51,22 @@ export function tallyBlockPoints(rallies, teamOf) {
51
51
  // Ignore substitutions / libero replacements (eventType < SERVE) when locating the terminal action.
52
52
  const inPlay = rally.events.filter(e => e.eventType >= EventTypeEnum.SERVE);
53
53
  const last = inPlay[inPlay.length - 1];
54
- const prev = inPlay[inPlay.length - 2];
55
- if (last == null || prev == null)
54
+ if (last == null)
56
55
  continue;
57
- // Block point signature: the rally ends on a failed RECEPTION (dig) whose previous event is a BLOCK
58
- // that landed in-court (target >= 7). target < 7 is a deflection -> the ATTACKER's kill, not a block.
59
- if (last.eventType !== EventTypeEnum.RECEPTION || last.failure <= 0)
60
- continue;
61
- if (prev.eventType !== EventTypeEnum.BLOCK)
62
- continue;
63
- const block = prev;
64
- if (block.target < 7)
56
+ // A block point lands in-court (target >= 7); a deflection / tool-out (target < 7) is the ATTACKER's kill.
57
+ // Two engines end a block point in different shapes, both counted here:
58
+ // - positional: the rally ENDS on the BLOCK itself (the stuff kills the ball outright);
59
+ // - zone: the rally ends on a failed RECEPTION (dig) whose previous event is the BLOCK.
60
+ let block;
61
+ if (last.eventType === EventTypeEnum.BLOCK) {
62
+ block = last;
63
+ }
64
+ else if (last.eventType === EventTypeEnum.RECEPTION && last.failure > 0) {
65
+ const prev = inPlay[inPlay.length - 2];
66
+ if (prev != null && prev.eventType === EventTypeEnum.BLOCK)
67
+ block = prev;
68
+ }
69
+ if (block == null || block.target < 7)
65
70
  continue;
66
71
  const team = teamOf(block.blockers[0] ?? block.playerId);
67
72
  if (team == null)
@@ -28,6 +28,14 @@ describe('tallyBlockPoints', () => {
28
28
  const t = tallyBlockPoints([rally([blockEvent('h1', 8, ['h1', 'h2']), receptionEvent('a3', 0), spikeEvent('a3')])], teamOf);
29
29
  expect(t.size).toBe(0);
30
30
  });
31
+ it('counts a POSITIONAL stuff that ENDS on the BLOCK (no trailing dig)', () => {
32
+ const t = tallyBlockPoints([rally([spikeEvent('a1'), blockEvent('h1', 9, ['h1', 'h2'])])], teamOf);
33
+ expect(t.get('HOME')).toEqual({ blockPoints: 1, blockSolo: 0, blockAssists: 2 });
34
+ });
35
+ it('does NOT count a terminal touch / tool-out block (target < 7)', () => {
36
+ const t = tallyBlockPoints([rally([spikeEvent('a1'), blockEvent('h1', 0, ['h1'])])], teamOf);
37
+ expect(t.size).toBe(0);
38
+ });
31
39
  it('reconstructs box block.solo and block.assists across a mix (incl. a triple block)', () => {
32
40
  const t = tallyBlockPoints([
33
41
  rally([blockEvent('h1', 8, ['h1', 'h2']), receptionEvent('a3', 1)]), // double: +2 assists
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volleyballsimtypes",
3
- "version": "0.0.509",
3
+ "version": "0.0.510",
4
4
  "description": "vbsim types",
5
5
  "main": "./dist/cjs/src/index.js",
6
6
  "module": "./dist/esm/src/index.js",