File size: 5,309 Bytes
4302cd7
 
 
 
74f4fb6
4302cd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74f4fb6
 
4302cd7
 
 
 
 
74f4fb6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
af73265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4302cd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { describe, it, expect } from 'vitest';
import {
  resolveHoverTelemetry,
  formatActivationValue,
  formatHoverTokenLabel,
  estimateMonospaceChars,
  activationDisplayBudget,
  MAX_ACTIVATION_DECIMALS,
} from '../src/ui/hoverTelemetry.js';

describe('resolveHoverTelemetry', () => {
  it('returns null for empty hover', () => {
    expect(resolveHoverTelemetry(null)).toBeNull();
    expect(resolveHoverTelemetry(undefined)).toBeNull();
  });

  it('reads activation from pointsData[index] (POINTS mesh)', () => {
    const data = {
      index: 1,
      point: { x: 10, y: 2, z: 0 },
      userData: {
        pointsData: [
          { activation: 0.01, meta: { type: 'compare', token: 'cat', dim: 0, val: 0.01 } },
          { activation: -0.0421, meta: { type: 'compare', token: 'cat', dim: 1, val: -0.0421 } },
        ],
      },
    };
    const t = resolveHoverTelemetry(data);
    expect(t.activation).toBe(-0.0421);
    expect(t.token).toBe('cat');
    expect(t.tokenLabel).toBe('cat');
    expect(t.groupLabel).toBeNull();
    expect(t.type).toBe('COMPARE');
    expect(t.dim).toBe(1);
    expect(t.point).toEqual({ x: 10, y: 2, z: 0 });
  });

  it('formats grouped token as group/token without spaces', () => {
    expect(formatHoverTokenLabel('car', 'vehicles')).toBe('vehicles/car');
    expect(formatHoverTokenLabel('car', null)).toBe('car');
    expect(formatHoverTokenLabel('car', '')).toBe('car');

    const t = resolveHoverTelemetry({
      index: 0,
      userData: {
        pointsData: [{
          activation: 0.2,
          groupId: 'vehicles',
          groupLabel: 'vehicles',
          meta: {
            type: 'compare',
            token: 'car',
            dim: 0,
            val: 0.2,
            groupId: 'vehicles',
            groupLabel: 'vehicles',
          },
        }],
      },
    });
    expect(t.token).toBe('car');
    expect(t.groupLabel).toBe('vehicles');
    expect(t.tokenLabel).toBe('vehicles/car');
  });

  it('maps ribbon actIdx through sourceDims when present', () => {
    const data = {
      index: 4,
      face: { a: 6, b: 7, c: 8 },
      userData: {
        kind: 'wideRibbon',
        activations: [0.1, 0.2, 0.3, 0.4],
        sourceDims: [10, 20, 30, 40],
        token: 'dog',
        type: 'compare',
      },
    };
    const t = resolveHoverTelemetry(data);
    expect(t.activation).toBe(0.4);
    expect(t.dim).toBe(40);
  });

  it('does not fall back to 0 when pointsData has a real value (regression)', () => {
    // Legacy HUD read userData.val / data.activation — both missing on Points meshes
    const data = {
      index: 0,
      point: { x: 1, y: 0, z: 0 },
      userData: {
        pointsData: [{ activation: 1.23e-7, meta: { dim: 0, val: 1.23e-7, type: 'res' } }],
      },
    };
    expect(resolveHoverTelemetry(data).activation).toBe(1.23e-7);
  });

  it('reads activations[] on wideRibbon via face vertex index', () => {
    const data = {
      index: 4,
      face: { a: 6, b: 7, c: 8 },
      userData: {
        kind: 'wideRibbon',
        activations: [0.1, 0.2, 0.3, 0.4, 0.5],
        token: 'dog',
        type: 'compare',
      },
    };
    const t = resolveHoverTelemetry(data);
    expect(t.activation).toBe(0.4); // floor(6/2) = 3
    expect(t.token).toBe('dog');
  });

  it('falls back to DIM #n when no token meta', () => {
    const t = resolveHoverTelemetry({
      index: 12,
      userData: {
        pointsData: [{ activation: 0.5, meta: { dim: 12, val: 0.5 } }],
      },
    });
    expect(t.token).toBe('DIM #12');
  });
});

describe('formatActivationValue', () => {
  it('caps decimals at 32', () => {
    expect(MAX_ACTIVATION_DECIMALS).toBe(32);
    const tiny = 1e-30;
    const s = formatActivationValue(tiny, { maxDecimals: 32, maxChars: 40 });
    expect(s.includes('e') || s.length <= 40).toBe(true);
    // Must not be the old HUD "0.0000"
    expect(s).not.toBe('0.0000');
    expect(Number(s)).not.toBe(0);
  });

  it('shows small magnitudes that toFixed(4) would hide', () => {
    const s = formatActivationValue(0.00001234, { maxDecimals: 32, maxChars: 20 });
    expect(s).not.toMatch(/^0\.0+$/);
    expect(Math.abs(Number(s) - 0.00001234)).toBeLessThan(1e-8);
  });

  it('shrinks to fit maxChars without overflowing', () => {
    const s = formatActivationValue(0.123456789012345, { maxDecimals: 32, maxChars: 8 });
    expect(s.length).toBeLessThanOrEqual(8);
    expect(s).not.toBe('0');
  });

  it('uses scientific when fixed form cannot fit', () => {
    const s = formatActivationValue(1.23456789e-20, { maxDecimals: 32, maxChars: 10 });
    expect(s.length).toBeLessThanOrEqual(10);
    expect(s).toMatch(/e/i);
  });

  it('formats zero as 0', () => {
    expect(formatActivationValue(0, { maxChars: 10 })).toBe('0');
  });
});

describe('activationDisplayBudget', () => {
  it('estimates monospace capacity', () => {
    expect(estimateMonospaceChars(120, 12)).toBeGreaterThan(10);
  });

  it('switches to compact ACT: label when slot is narrow (mobile)', () => {
    const wide = activationDisplayBudget(400, 'ACTIVATION: ', 12);
    expect(wide.compactLabel).toBe(false);

    const narrow = activationDisplayBudget(90, 'ACTIVATION: ', 11);
    expect(narrow.compactLabel).toBe(true);
    expect(narrow.maxChars).toBeGreaterThanOrEqual(4);
  });
});