@woosh/meep-engine 3.10.0 → 3.11.0

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 (142) hide show
  1. package/README.md +168 -152
  2. package/package.json +1 -1
  3. package/src/REVIEW_2026_08_06.md +610 -610
  4. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  5. package/src/core/geom/3d/shape/ConvexHullShape3D.js +28 -4
  6. package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts +26 -0
  7. package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts.map +1 -1
  8. package/src/engine/graphics/render/frame_graph/FrameGraph.js +47 -0
  9. package/src/engine/physics/ecs/PhysicsSystem.d.ts +0 -3
  10. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  11. package/src/engine/physics/ecs/PhysicsSystem.js +0 -3
  12. package/src/shade/device/ShadeGPUCommandContext.d.ts +21 -0
  13. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  14. package/src/shade/device/ShadeGPUCommandContext.js +940 -749
  15. package/src/shade/device/timing/GPUTimerArray.d.ts +22 -4
  16. package/src/shade/device/timing/GPUTimerArray.d.ts.map +1 -1
  17. package/src/shade/device/timing/GPUTimerArray.js +88 -14
  18. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +349 -98
  19. package/src/shade/device/timing/profile/GPUFrameRecorder.d.ts +93 -0
  20. package/src/shade/device/timing/profile/GPUFrameRecorder.d.ts.map +1 -0
  21. package/src/shade/device/timing/profile/GPUFrameRecorder.js +297 -0
  22. package/src/shade/device/timing/profile/GPUProfileCapture.d.ts +62 -0
  23. package/src/shade/device/timing/profile/GPUProfileCapture.d.ts.map +1 -0
  24. package/src/shade/device/timing/profile/GPUProfileCapture.js +77 -0
  25. package/src/shade/device/timing/profile/GPUProfileFrame.d.ts +73 -0
  26. package/src/shade/device/timing/profile/GPUProfileFrame.d.ts.map +1 -0
  27. package/src/shade/device/timing/profile/GPUProfileFrame.js +88 -0
  28. package/src/shade/device/timing/profile/GPUProfileLevel.d.ts +20 -0
  29. package/src/shade/device/timing/profile/GPUProfileLevel.d.ts.map +1 -0
  30. package/src/shade/device/timing/profile/GPUProfileLevel.js +50 -0
  31. package/src/shade/device/timing/profile/GPUProfileMeta.d.ts +63 -0
  32. package/src/shade/device/timing/profile/GPUProfileMeta.d.ts.map +1 -0
  33. package/src/shade/device/timing/profile/GPUProfileMeta.js +73 -0
  34. package/src/shade/device/timing/profile/GPUProfileSession.d.ts +173 -0
  35. package/src/shade/device/timing/profile/GPUProfileSession.d.ts.map +1 -0
  36. package/src/shade/device/timing/profile/GPUProfileSession.js +470 -0
  37. package/src/shade/device/timing/profile/GPUProfileSpan.d.ts +60 -0
  38. package/src/shade/device/timing/profile/GPUProfileSpan.d.ts.map +1 -0
  39. package/src/shade/device/timing/profile/GPUProfileSpan.js +65 -0
  40. package/src/shade/device/timing/profile/GPUProfileSpanKind.d.ts +19 -0
  41. package/src/shade/device/timing/profile/GPUProfileSpanKind.d.ts.map +1 -0
  42. package/src/shade/device/timing/profile/GPUProfileSpanKind.js +25 -0
  43. package/src/shade/device/timing/profile/GPUProfileTopology.d.ts +159 -0
  44. package/src/shade/device/timing/profile/GPUProfileTopology.d.ts.map +1 -0
  45. package/src/shade/device/timing/profile/GPUProfileTopology.js +202 -0
  46. package/src/shade/device/timing/profile/GPUProfileWork.d.ts +90 -0
  47. package/src/shade/device/timing/profile/GPUProfileWork.d.ts.map +1 -0
  48. package/src/shade/device/timing/profile/GPUProfileWork.js +115 -0
  49. package/src/shade/device/timing/profile/SGPTDefect.d.ts +43 -0
  50. package/src/shade/device/timing/profile/SGPTDefect.d.ts.map +1 -0
  51. package/src/shade/device/timing/profile/SGPTDefect.js +65 -0
  52. package/src/shade/device/timing/profile/SGPTHeader.d.ts +38 -0
  53. package/src/shade/device/timing/profile/SGPTHeader.d.ts.map +1 -0
  54. package/src/shade/device/timing/profile/SGPTHeader.js +46 -0
  55. package/src/shade/device/timing/profile/SGPTRecordRef.d.ts +37 -0
  56. package/src/shade/device/timing/profile/SGPTRecordRef.d.ts.map +1 -0
  57. package/src/shade/device/timing/profile/SGPTRecordRef.js +45 -0
  58. package/src/shade/device/timing/profile/SGPTWriteStream.d.ts +103 -0
  59. package/src/shade/device/timing/profile/SGPTWriteStream.d.ts.map +1 -0
  60. package/src/shade/device/timing/profile/SGPTWriteStream.js +287 -0
  61. package/src/shade/device/timing/profile/SGPT_DEFECT.d.ts +19 -0
  62. package/src/shade/device/timing/profile/SGPT_DEFECT.d.ts.map +1 -0
  63. package/src/shade/device/timing/profile/SGPT_DEFECT.js +26 -0
  64. package/src/shade/device/timing/profile/SGPT_FLAG.d.ts +8 -0
  65. package/src/shade/device/timing/profile/SGPT_FLAG.d.ts.map +1 -0
  66. package/src/shade/device/timing/profile/SGPT_FLAG.js +15 -0
  67. package/src/shade/device/timing/profile/SGPT_FORMAT.md +345 -0
  68. package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.d.ts +10 -0
  69. package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.d.ts.map +1 -0
  70. package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.js +9 -0
  71. package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.d.ts +17 -0
  72. package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.d.ts.map +1 -0
  73. package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.js +16 -0
  74. package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.d.ts +16 -0
  75. package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.d.ts.map +1 -0
  76. package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.js +15 -0
  77. package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.d.ts +15 -0
  78. package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.d.ts.map +1 -0
  79. package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.js +29 -0
  80. package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.d.ts +7 -0
  81. package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.d.ts.map +1 -0
  82. package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.js +6 -0
  83. package/src/shade/device/timing/profile/SGPT_MAGIC.d.ts +7 -0
  84. package/src/shade/device/timing/profile/SGPT_MAGIC.d.ts.map +1 -0
  85. package/src/shade/device/timing/profile/SGPT_MAGIC.js +6 -0
  86. package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.d.ts +16 -0
  87. package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.d.ts.map +1 -0
  88. package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.js +15 -0
  89. package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.d.ts +7 -0
  90. package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.d.ts.map +1 -0
  91. package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.js +6 -0
  92. package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.d.ts +13 -0
  93. package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.d.ts.map +1 -0
  94. package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.js +23 -0
  95. package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.d.ts +12 -0
  96. package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.d.ts.map +1 -0
  97. package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.js +11 -0
  98. package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.d.ts +18 -0
  99. package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.d.ts.map +1 -0
  100. package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.js +46 -0
  101. package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts +20 -0
  102. package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts.map +1 -0
  103. package/src/shade/device/timing/profile/frame_graph_extract_topology.js +174 -0
  104. package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts +21 -0
  105. package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts.map +1 -0
  106. package/src/shade/device/timing/profile/make_profiling_pass_encoder.js +114 -0
  107. package/src/shade/device/timing/profile/parse_workgroup_size.d.ts +20 -0
  108. package/src/shade/device/timing/profile/parse_workgroup_size.d.ts.map +1 -0
  109. package/src/shade/device/timing/profile/parse_workgroup_size.js +43 -0
  110. package/src/shade/device/timing/profile/sgpt_fourcc.d.ts +18 -0
  111. package/src/shade/device/timing/profile/sgpt_fourcc.d.ts.map +1 -0
  112. package/src/shade/device/timing/profile/sgpt_fourcc.js +34 -0
  113. package/src/shade/device/timing/profile/sgpt_frame_codec.d.ts +42 -0
  114. package/src/shade/device/timing/profile/sgpt_frame_codec.d.ts.map +1 -0
  115. package/src/shade/device/timing/profile/sgpt_frame_codec.js +172 -0
  116. package/src/shade/device/timing/profile/sgpt_meta_codec.d.ts +20 -0
  117. package/src/shade/device/timing/profile/sgpt_meta_codec.d.ts.map +1 -0
  118. package/src/shade/device/timing/profile/sgpt_meta_codec.js +65 -0
  119. package/src/shade/device/timing/profile/sgpt_read_capture.d.ts +19 -0
  120. package/src/shade/device/timing/profile/sgpt_read_capture.d.ts.map +1 -0
  121. package/src/shade/device/timing/profile/sgpt_read_capture.js +134 -0
  122. package/src/shade/device/timing/profile/sgpt_read_header.d.ts +22 -0
  123. package/src/shade/device/timing/profile/sgpt_read_header.d.ts.map +1 -0
  124. package/src/shade/device/timing/profile/sgpt_read_header.js +94 -0
  125. package/src/shade/device/timing/profile/sgpt_read_records.d.ts +27 -0
  126. package/src/shade/device/timing/profile/sgpt_read_records.d.ts.map +1 -0
  127. package/src/shade/device/timing/profile/sgpt_read_records.js +153 -0
  128. package/src/shade/device/timing/profile/sgpt_topology_codec.d.ts +54 -0
  129. package/src/shade/device/timing/profile/sgpt_topology_codec.d.ts.map +1 -0
  130. package/src/shade/device/timing/profile/sgpt_topology_codec.js +329 -0
  131. package/src/shade/device/timing/profile/sgpt_write_header.d.ts +22 -0
  132. package/src/shade/device/timing/profile/sgpt_write_header.d.ts.map +1 -0
  133. package/src/shade/device/timing/profile/sgpt_write_header.js +47 -0
  134. package/src/shade/device/timing/profile/sgpt_write_record.d.ts +23 -0
  135. package/src/shade/device/timing/profile/sgpt_write_record.d.ts.map +1 -0
  136. package/src/shade/device/timing/profile/sgpt_write_record.js +62 -0
  137. package/src/shade/renderer/Renderer.d.ts +21 -0
  138. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  139. package/src/shade/renderer/Renderer.js +2513 -2455
  140. package/src/engine/save/GameStateLoader.d.ts +0 -49
  141. package/src/engine/save/GameStateLoader.d.ts.map +0 -1
  142. package/src/engine/save/GameStateLoader.js +0 -168
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The decoded 32-byte head of an `.sgpt` stream.
3
+ *
4
+ * @author Alex Goldring
5
+ * @copyright Company Named Limited (c) 2026
6
+ */
7
+ export class SGPTHeader {
8
+ /**
9
+ * @type {number}
10
+ */
11
+ format_version: number;
12
+ /**
13
+ * @type {number}
14
+ */
15
+ min_reader_version: number;
16
+ /**
17
+ * {@link SGPT_FLAG} bitmask.
18
+ * @type {number}
19
+ */
20
+ flags: number;
21
+ /**
22
+ * Absolute offset of the directory record, or 0 when the session never stopped.
23
+ * @type {number}
24
+ */
25
+ directory_offset: number;
26
+ /**
27
+ * @type {number}
28
+ */
29
+ directory_byte_length: number;
30
+ /**
31
+ * Whether the writer reached `stop()`. A capture without this is readable and honest; it is
32
+ * simply shorter than the session that produced it.
33
+ *
34
+ * @returns {boolean}
35
+ */
36
+ get is_closed(): boolean;
37
+ }
38
+ //# sourceMappingURL=SGPTHeader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SGPTHeader.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/SGPTHeader.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACI;;OAEG;IACH,gBAFU,MAAM,CAEG;IAEnB;;OAEG;IACH,oBAFU,MAAM,CAEO;IAEvB;;;OAGG;IACH,OAFU,MAAM,CAEN;IAEV;;;OAGG;IACH,kBAFU,MAAM,CAEK;IAErB;;OAEG;IACH,uBAFU,MAAM,CAEU;IAE1B;;;;;OAKG;IACH,yBAEC;CACJ"}
@@ -0,0 +1,46 @@
1
+ import { SGPT_FLAG } from "./SGPT_FLAG.js";
2
+
3
+ /**
4
+ * The decoded 32-byte head of an `.sgpt` stream.
5
+ *
6
+ * @author Alex Goldring
7
+ * @copyright Company Named Limited (c) 2026
8
+ */
9
+ export class SGPTHeader {
10
+ /**
11
+ * @type {number}
12
+ */
13
+ format_version = 0;
14
+
15
+ /**
16
+ * @type {number}
17
+ */
18
+ min_reader_version = 0;
19
+
20
+ /**
21
+ * {@link SGPT_FLAG} bitmask.
22
+ * @type {number}
23
+ */
24
+ flags = 0;
25
+
26
+ /**
27
+ * Absolute offset of the directory record, or 0 when the session never stopped.
28
+ * @type {number}
29
+ */
30
+ directory_offset = 0;
31
+
32
+ /**
33
+ * @type {number}
34
+ */
35
+ directory_byte_length = 0;
36
+
37
+ /**
38
+ * Whether the writer reached `stop()`. A capture without this is readable and honest; it is
39
+ * simply shorter than the session that produced it.
40
+ *
41
+ * @returns {boolean}
42
+ */
43
+ get is_closed() {
44
+ return (this.flags & SGPT_FLAG.CLOSED) !== 0;
45
+ }
46
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Where one record sits in the stream, and what it claims to be. Produced by
3
+ * {@link sgpt_read_records}; the payload is left where it lies and decoded on demand.
4
+ *
5
+ * @author Alex Goldring
6
+ * @copyright Company Named Limited (c) 2026
7
+ */
8
+ export class SGPTRecordRef {
9
+ /**
10
+ * @param {number} type
11
+ * @param {number} offset
12
+ * @param {number} payload_offset
13
+ * @param {number} payload_byte_length
14
+ */
15
+ constructor(type: number, offset: number, payload_offset: number, payload_byte_length: number);
16
+ /**
17
+ * One of {@link SGPT_RECORD_TYPE}. Not validated against that enum — an unrecognised type is a
18
+ * record a newer writer produced, and skipping it by length is the whole point of the framing.
19
+ * @type {number}
20
+ */
21
+ type: number;
22
+ /**
23
+ * Offset of the record's framing.
24
+ * @type {number}
25
+ */
26
+ offset: number;
27
+ /**
28
+ * Offset of the first payload byte.
29
+ * @type {number}
30
+ */
31
+ payload_offset: number;
32
+ /**
33
+ * @type {number}
34
+ */
35
+ payload_byte_length: number;
36
+ }
37
+ //# sourceMappingURL=SGPTRecordRef.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SGPTRecordRef.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/SGPTRecordRef.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH;IAyBI;;;;;OAKG;IACH,kBALW,MAAM,UACN,MAAM,kBACN,MAAM,uBACN,MAAM,EAOhB;IAnCD;;;;OAIG;IACH,MAFU,MAAM,CAEP;IAET;;;OAGG;IACH,QAFU,MAAM,CAEL;IAEX;;;OAGG;IACH,gBAFU,MAAM,CAEG;IAEnB;;OAEG;IACH,qBAFU,MAAM,CAEQ;CAc3B"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Where one record sits in the stream, and what it claims to be. Produced by
3
+ * {@link sgpt_read_records}; the payload is left where it lies and decoded on demand.
4
+ *
5
+ * @author Alex Goldring
6
+ * @copyright Company Named Limited (c) 2026
7
+ */
8
+ export class SGPTRecordRef {
9
+ /**
10
+ * One of {@link SGPT_RECORD_TYPE}. Not validated against that enum — an unrecognised type is a
11
+ * record a newer writer produced, and skipping it by length is the whole point of the framing.
12
+ * @type {number}
13
+ */
14
+ type = 0;
15
+
16
+ /**
17
+ * Offset of the record's framing.
18
+ * @type {number}
19
+ */
20
+ offset = 0;
21
+
22
+ /**
23
+ * Offset of the first payload byte.
24
+ * @type {number}
25
+ */
26
+ payload_offset = 0;
27
+
28
+ /**
29
+ * @type {number}
30
+ */
31
+ payload_byte_length = 0;
32
+
33
+ /**
34
+ * @param {number} type
35
+ * @param {number} offset
36
+ * @param {number} payload_offset
37
+ * @param {number} payload_byte_length
38
+ */
39
+ constructor(type, offset, payload_offset, payload_byte_length) {
40
+ this.type = type;
41
+ this.offset = offset;
42
+ this.payload_offset = payload_offset;
43
+ this.payload_byte_length = payload_byte_length;
44
+ }
45
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * A forward-only writer for an `.sgpt` stream, plus the symbol interning that goes with it.
3
+ *
4
+ * Open-ended by construction. There is no cap and no ring: everything written is kept until
5
+ * {@link finish}, because the point of a capture is the whole history, and a ring buffer's
6
+ * complexity buys nothing for the way these are actually used.
7
+ *
8
+ * **Nothing here depends on the stream being finished.** Records are self-framed and checksummed,
9
+ * so a reader can recover the entire contents by scanning. The directory {@link finish} appends is
10
+ * an index over that, never a prerequisite for it — which is what makes a capture that ended when
11
+ * the tab did still a capture.
12
+ *
13
+ * @author Alex Goldring
14
+ * @copyright Company Named Limited (c) 2026
15
+ */
16
+ export class SGPTWriteStream {
17
+ /**
18
+ * Fires after each frame is committed, with the byte total so far.
19
+ *
20
+ * A session is uncapped, so this is how an application shows what it is accumulating rather
21
+ * than finding out when the tab dies.
22
+ *
23
+ * @readonly
24
+ * @type {Signal<number>}
25
+ */
26
+ readonly onBytesWritten: any;
27
+ /**
28
+ * Bytes committed so far, header included.
29
+ *
30
+ * @returns {number}
31
+ */
32
+ get bytes_written(): number;
33
+ /**
34
+ * How many records have been committed.
35
+ *
36
+ * @returns {number}
37
+ */
38
+ get record_count(): number;
39
+ /**
40
+ * Index of `value` in the capture's symbol space, interning it if it is new.
41
+ *
42
+ * Strings are written once and referenced by index thereafter, which is what keeps a repeating
43
+ * pass name from costing its length every frame.
44
+ *
45
+ * @param {string} value
46
+ * @returns {number}
47
+ */
48
+ intern(value: string): number;
49
+ /**
50
+ * Append one record, flushing any symbols it interned first.
51
+ *
52
+ * @template T
53
+ * @param {number} type one of {@link SGPT_RECORD_TYPE}
54
+ * @param {function(BinaryBuffer, T): void} write_payload
55
+ * @param {T} [data]
56
+ * @returns {void}
57
+ */
58
+ write_record<T>(type: number, write_payload: (arg0: BinaryBuffer, arg1: T) => void, data?: T): void;
59
+ /**
60
+ * Reserve headroom for the frame about to be recorded.
61
+ *
62
+ * Call at a frame boundary, never inside one. {@link BinaryBuffer} grows by reallocating and
63
+ * copying, and while that costs little — we are measuring GPU time, and the worst of it is a
64
+ * cache eviction that makes the next upload marginally slower — there is no reason for it to
65
+ * land in the middle of the interval whose timings we are taking. One reservation per frame
66
+ * moves it to the boundary, where it is invisible.
67
+ *
68
+ * @returns {void}
69
+ */
70
+ reserve_frame(): void;
71
+ /**
72
+ * Announce the current byte total. Called once per frame, after the frame's records land.
73
+ *
74
+ * @returns {void}
75
+ */
76
+ notify_frame_committed(): void;
77
+ /**
78
+ * The stream as it stands, without ending it.
79
+ *
80
+ * Carries no directory and no CLOSED flag, which is not a defect — it is the same shape a
81
+ * capture has when the tab is killed part-way through, and it reads back exactly the same way.
82
+ * That is what makes this useful for more than tests: a long session can persist a snapshot
83
+ * periodically and lose at most the frames since the last one, rather than everything.
84
+ *
85
+ * Pending symbols are flushed first, so a snapshot always resolves every name it references.
86
+ *
87
+ * @returns {ArrayBuffer}
88
+ */
89
+ snapshot(): ArrayBuffer;
90
+ /**
91
+ * Append the directory, mark the header closed, and hand back the bytes.
92
+ *
93
+ * The two header patches are the only backward writes in the whole format. Both are in fields
94
+ * the header checksum deliberately does not cover, so patching them cannot invalidate it — and
95
+ * a capture that never gets here is not damaged, it simply has neither.
96
+ *
97
+ * @returns {ArrayBuffer}
98
+ */
99
+ finish(): ArrayBuffer;
100
+ #private;
101
+ }
102
+ import { BinaryBuffer } from "../../../../core/binary/BinaryBuffer.js";
103
+ //# sourceMappingURL=SGPTWriteStream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SGPTWriteStream.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/SGPTWriteStream.js"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;GAcG;AACH;IAoCI;;;;;;;;OAQG;IACH,6BAA8B;IAM9B;;;;OAIG;IACH,4BAEC;IAED;;;;OAIG;IACH,2BAEC;IAED;;;;;;;;OAQG;IACH,cAHW,MAAM,GACJ,MAAM,CAiBlB;IAkDD;;;;;;;;OAQG;IACH,sBALW,MAAM,wBACG,YAAY,cAAM,IAAI,aAE7B,IAAI,CAUhB;IAED;;;;;;;;;;OAUG;IACH,iBAFa,IAAI,CAMhB;IAED;;;;OAIG;IACH,0BAFa,IAAI,CAMhB;IAED;;;;;;;;;;;OAWG;IACH,YAFa,WAAW,CAQvB;IAED;;;;;;;;OAQG;IACH,UAFa,WAAW,CA2CvB;;CACJ;6BA7R4B,yCAAyC"}
@@ -0,0 +1,287 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { BinaryBuffer } from "../../../../core/binary/BinaryBuffer.js";
3
+ import { Signal } from "../../../../core/events/signal/Signal.js";
4
+ import { SGPT_FLAG } from "./SGPT_FLAG.js";
5
+ import { SGPT_FRAME_SLACK } from "./SGPT_FRAME_SLACK.js";
6
+ import { SGPT_HEADER_OFFSET } from "./SGPT_HEADER_OFFSET.js";
7
+ import { SGPT_RECORD_TYPE } from "./SGPT_RECORD_TYPE.js";
8
+ import { sgpt_write_header } from "./sgpt_write_header.js";
9
+ import { sgpt_write_record } from "./sgpt_write_record.js";
10
+
11
+ /**
12
+ * A forward-only writer for an `.sgpt` stream, plus the symbol interning that goes with it.
13
+ *
14
+ * Open-ended by construction. There is no cap and no ring: everything written is kept until
15
+ * {@link finish}, because the point of a capture is the whole history, and a ring buffer's
16
+ * complexity buys nothing for the way these are actually used.
17
+ *
18
+ * **Nothing here depends on the stream being finished.** Records are self-framed and checksummed,
19
+ * so a reader can recover the entire contents by scanning. The directory {@link finish} appends is
20
+ * an index over that, never a prerequisite for it — which is what makes a capture that ended when
21
+ * the tab did still a capture.
22
+ *
23
+ * @author Alex Goldring
24
+ * @copyright Company Named Limited (c) 2026
25
+ */
26
+ export class SGPTWriteStream {
27
+
28
+ /**
29
+ * @type {BinaryBuffer}
30
+ */
31
+ #buffer = new BinaryBuffer();
32
+
33
+ /**
34
+ * Strings already written into some {@link SGPT_RECORD_TYPE.SYMS} block, and the index each was
35
+ * given.
36
+ *
37
+ * @type {Map<string, number>}
38
+ */
39
+ #symbols = new Map();
40
+
41
+ /**
42
+ * Interned this frame but not yet written out. Flushed as one `SYMS` record immediately before
43
+ * the record that references them, so that record never refers forward to something a
44
+ * truncated file does not contain.
45
+ *
46
+ * @type {string[]}
47
+ */
48
+ #pending_symbols = [];
49
+
50
+ /**
51
+ * `{type, offset, byte_length}` per record, for the directory.
52
+ *
53
+ * @type {{type: number, offset: number, byte_length: number}[]}
54
+ */
55
+ #index = [];
56
+
57
+ /**
58
+ * @type {boolean}
59
+ */
60
+ #finished = false;
61
+
62
+ /**
63
+ * Fires after each frame is committed, with the byte total so far.
64
+ *
65
+ * A session is uncapped, so this is how an application shows what it is accumulating rather
66
+ * than finding out when the tab dies.
67
+ *
68
+ * @readonly
69
+ * @type {Signal<number>}
70
+ */
71
+ onBytesWritten = new Signal();
72
+
73
+ constructor() {
74
+ sgpt_write_header(this.#buffer);
75
+ }
76
+
77
+ /**
78
+ * Bytes committed so far, header included.
79
+ *
80
+ * @returns {number}
81
+ */
82
+ get bytes_written() {
83
+ return this.#buffer.position;
84
+ }
85
+
86
+ /**
87
+ * How many records have been committed.
88
+ *
89
+ * @returns {number}
90
+ */
91
+ get record_count() {
92
+ return this.#index.length;
93
+ }
94
+
95
+ /**
96
+ * Index of `value` in the capture's symbol space, interning it if it is new.
97
+ *
98
+ * Strings are written once and referenced by index thereafter, which is what keeps a repeating
99
+ * pass name from costing its length every frame.
100
+ *
101
+ * @param {string} value
102
+ * @returns {number}
103
+ */
104
+ intern(value) {
105
+ assert.isString(value, 'value');
106
+
107
+ const existing = this.#symbols.get(value);
108
+
109
+ if (existing !== undefined) {
110
+ return existing;
111
+ }
112
+
113
+ const id = this.#symbols.size;
114
+
115
+ this.#symbols.set(value, id);
116
+ this.#pending_symbols.push(value);
117
+
118
+ return id;
119
+ }
120
+
121
+ /**
122
+ * Emit any symbols interned since the last flush, as one `SYMS` record.
123
+ *
124
+ * Called for you by {@link write_record}. Symbols travel *ahead of* the record that needs them
125
+ * rather than being gathered into one table at the end: a tail table makes a truncated capture
126
+ * resolve no names at all, which is precisely the capture of the crash somebody was trying to
127
+ * record.
128
+ *
129
+ * @returns {void}
130
+ */
131
+ #flush_symbols() {
132
+ const pending = this.#pending_symbols;
133
+
134
+ if (pending.length === 0) {
135
+ return;
136
+ }
137
+
138
+ const first_index = this.#symbols.size - pending.length;
139
+
140
+ this.#commit(SGPT_RECORD_TYPE.SYMS, (buffer) => {
141
+ buffer.writeUintVar(first_index);
142
+ buffer.writeUintVar(pending.length);
143
+
144
+ for (const value of pending) {
145
+ buffer.writeUTF8String(value);
146
+ }
147
+ });
148
+
149
+ pending.length = 0;
150
+ }
151
+
152
+ /**
153
+ * @template T
154
+ * @param {number} type
155
+ * @param {function(BinaryBuffer, T): void} write_payload
156
+ * @param {T} [data]
157
+ * @returns {void}
158
+ */
159
+ #commit(type, write_payload, data) {
160
+ const buffer = this.#buffer;
161
+
162
+ const offset = buffer.position;
163
+
164
+ const byte_length = sgpt_write_record(buffer, type, data, write_payload);
165
+
166
+ this.#index.push({ type, offset, byte_length });
167
+ }
168
+
169
+ /**
170
+ * Append one record, flushing any symbols it interned first.
171
+ *
172
+ * @template T
173
+ * @param {number} type one of {@link SGPT_RECORD_TYPE}
174
+ * @param {function(BinaryBuffer, T): void} write_payload
175
+ * @param {T} [data]
176
+ * @returns {void}
177
+ */
178
+ write_record(type, write_payload, data) {
179
+ assert.notOk(this.#finished, 'stream already finished');
180
+ assert.isInteger(type, 'type');
181
+ assert.isFunction(write_payload, 'write_payload');
182
+
183
+ this.#flush_symbols();
184
+
185
+ this.#commit(type, write_payload, data);
186
+ }
187
+
188
+ /**
189
+ * Reserve headroom for the frame about to be recorded.
190
+ *
191
+ * Call at a frame boundary, never inside one. {@link BinaryBuffer} grows by reallocating and
192
+ * copying, and while that costs little — we are measuring GPU time, and the worst of it is a
193
+ * cache eviction that makes the next upload marginally slower — there is no reason for it to
194
+ * land in the middle of the interval whose timings we are taking. One reservation per frame
195
+ * moves it to the boundary, where it is invisible.
196
+ *
197
+ * @returns {void}
198
+ */
199
+ reserve_frame() {
200
+ assert.notOk(this.#finished, 'stream already finished');
201
+
202
+ this.#buffer.ensureCapacity(this.#buffer.position + SGPT_FRAME_SLACK);
203
+ }
204
+
205
+ /**
206
+ * Announce the current byte total. Called once per frame, after the frame's records land.
207
+ *
208
+ * @returns {void}
209
+ */
210
+ notify_frame_committed() {
211
+ if (this.onBytesWritten.hasHandlers()) {
212
+ this.onBytesWritten.send1(this.#buffer.position);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * The stream as it stands, without ending it.
218
+ *
219
+ * Carries no directory and no CLOSED flag, which is not a defect — it is the same shape a
220
+ * capture has when the tab is killed part-way through, and it reads back exactly the same way.
221
+ * That is what makes this useful for more than tests: a long session can persist a snapshot
222
+ * periodically and lose at most the frames since the last one, rather than everything.
223
+ *
224
+ * Pending symbols are flushed first, so a snapshot always resolves every name it references.
225
+ *
226
+ * @returns {ArrayBuffer}
227
+ */
228
+ snapshot() {
229
+ assert.notOk(this.#finished, 'stream already finished');
230
+
231
+ this.#flush_symbols();
232
+
233
+ return this.#buffer.raw_bytes.slice(0, this.#buffer.position).buffer;
234
+ }
235
+
236
+ /**
237
+ * Append the directory, mark the header closed, and hand back the bytes.
238
+ *
239
+ * The two header patches are the only backward writes in the whole format. Both are in fields
240
+ * the header checksum deliberately does not cover, so patching them cannot invalidate it — and
241
+ * a capture that never gets here is not damaged, it simply has neither.
242
+ *
243
+ * @returns {ArrayBuffer}
244
+ */
245
+ finish() {
246
+ assert.notOk(this.#finished, 'stream already finished');
247
+
248
+ this.#flush_symbols();
249
+
250
+ const buffer = this.#buffer;
251
+
252
+ const directory_offset = buffer.position;
253
+
254
+ const index = this.#index;
255
+
256
+ this.#commit(SGPT_RECORD_TYPE.DIRE, (out) => {
257
+ out.writeUintVar(index.length);
258
+
259
+ for (const entry of index) {
260
+ out.writeUint32(entry.type);
261
+ // u64 rather than the compact uintVar: `writeUintVar` refuses anything above
262
+ // 2^31-1, and an assertion at `finish` would destroy the very capture it was asked
263
+ // to hand over. Sixteen bytes a record is nothing against that.
264
+ out.writeUint64(BigInt(entry.offset));
265
+ out.writeUint32(entry.byte_length);
266
+ }
267
+ });
268
+
269
+ const end = buffer.position;
270
+
271
+ buffer.position = SGPT_HEADER_OFFSET.FLAGS;
272
+ const flags = buffer.readUint32();
273
+
274
+ buffer.position = SGPT_HEADER_OFFSET.FLAGS;
275
+ buffer.writeUint32(flags | SGPT_FLAG.CLOSED);
276
+
277
+ buffer.position = SGPT_HEADER_OFFSET.DIRECTORY_OFFSET;
278
+ buffer.writeUint64(BigInt(directory_offset));
279
+ buffer.writeUint64(BigInt(end - directory_offset));
280
+
281
+ buffer.position = end;
282
+
283
+ this.#finished = true;
284
+
285
+ return buffer.raw_bytes.slice(0, end).buffer;
286
+ }
287
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Which invariant of an `.sgpt` stream was violated. The `detail` string carries the specifics.
3
+ *
4
+ * Mirrors the {@link VGEO_DEFECT } convention: a defect is reported as data rather than thrown as a
5
+ * bare `Error`, because a profile capture is frequently damaged in ways worth *reporting to the
6
+ * reader* rather than refusing outright — a session killed mid-record is the normal outcome of
7
+ * capturing a crash.
8
+ */
9
+ export type SGPT_DEFECT = number;
10
+ export namespace SGPT_DEFECT {
11
+ let HEADER: number;
12
+ let CHECKSUM: number;
13
+ let FRAMING: number;
14
+ let RESYNC: number;
15
+ let PAYLOAD: number;
16
+ let DIRECTORY: number;
17
+ let TRUNCATED: number;
18
+ }
19
+ //# sourceMappingURL=SGPT_DEFECT.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SGPT_DEFECT.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/SGPT_DEFECT.js"],"names":[],"mappings":";;;;;;;;0BAQU,MAAM"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Which invariant of an `.sgpt` stream was violated. The `detail` string carries the specifics.
3
+ *
4
+ * Mirrors the {@link VGEO_DEFECT} convention: a defect is reported as data rather than thrown as a
5
+ * bare `Error`, because a profile capture is frequently damaged in ways worth *reporting to the
6
+ * reader* rather than refusing outright — a session killed mid-record is the normal outcome of
7
+ * capturing a crash.
8
+ *
9
+ * @enum {number}
10
+ */
11
+ export const SGPT_DEFECT = {
12
+ /** Not an `.sgpt` file, or a header too short or too new to read. */
13
+ HEADER: 0,
14
+ /** A checksum did not match the bytes it covers. */
15
+ CHECKSUM: 1,
16
+ /** A record's framing is self-inconsistent — length runs past the end of the data. */
17
+ FRAMING: 2,
18
+ /** Bytes between records that are not a record. Recovered by scanning; reported, not fatal. */
19
+ RESYNC: 3,
20
+ /** A record's payload does not decode as its type says it should. */
21
+ PAYLOAD: 4,
22
+ /** The directory disagrees with the records actually present. */
23
+ DIRECTORY: 5,
24
+ /** The stream ends inside a record. Expected for a capture that never stopped. */
25
+ TRUNCATED: 6
26
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Header flag bits.
3
+ */
4
+ export type SGPT_FLAG = number;
5
+ export namespace SGPT_FLAG {
6
+ let CLOSED: number;
7
+ }
8
+ //# sourceMappingURL=SGPT_FLAG.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SGPT_FLAG.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/SGPT_FLAG.js"],"names":[],"mappings":";;;wBAGU,MAAM"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Header flag bits.
3
+ *
4
+ * @enum {number}
5
+ */
6
+ export const SGPT_FLAG = {
7
+ /**
8
+ * The writer reached {@link GPUProfileSession.stop} and the file is complete as intended.
9
+ *
10
+ * Its absence is not an error — see {@link SGPT_HEADER_OFFSET.DIRECTORY_OFFSET}. It is how a
11
+ * reader knows to say "this capture was cut short" rather than silently presenting a partial
12
+ * session as a whole one.
13
+ */
14
+ CLOSED: 1 << 0
15
+ };