@woosh/meep-engine 3.0.2 → 3.2.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.
- package/LICENSE +188 -188
- package/README.md +152 -145
- package/editor/Editor.js +598 -598
- package/editor/EditorCameraNavigation.js +205 -205
- package/editor/EditorKeyMap.js +182 -182
- package/editor/SelectionVisualizer.js +127 -127
- package/editor/actions/concrete/ActionInvalidateSampler.js +27 -27
- package/editor/actions/concrete/PatchTerrainHeightAction.js +85 -85
- package/editor/camera/camera_framing.js +104 -104
- package/editor/clipboard.js +184 -184
- package/editor/ecs/component/editors/ObservedIntegerEditor.js +22 -22
- package/editor/ecs/component/editors/ObservedStringEditor.js +34 -34
- package/editor/ecs/component/editors/common/two_way_sync.js +50 -50
- package/editor/ecs/component/editors/ecs/GridObstacleEditor.js +19 -19
- package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +35 -35
- package/editor/ecs/component/editors/ecs/ParameterTrackEditor.js +17 -17
- package/editor/ecs/component/editors/ecs/SGMeshEditor.js +24 -24
- package/editor/ecs/component/editors/ecs/ShadedGeometryEditor.js +33 -33
- package/editor/ecs/component/editors/ecs/Trail3DEditor.js +45 -45
- package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.js +19 -19
- package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.js +72 -72
- package/editor/ecs/component/editors/shade/MeshletGeometryEditor.js +30 -30
- package/editor/ecs/component/editors/shade/ShadeImageEditor.js +70 -70
- package/editor/ecs/component/editors/shade/ShadeMaterialEditor.js +34 -34
- package/editor/ecs/component/editors/shade/ShadeTextureEditor.js +46 -46
- package/editor/ecs/component/editors/shade/StandardShadeMaterialEditor.js +24 -24
- package/editor/ecs/component/prototypeObjectEditor.js +265 -265
- package/editor/ecs/component/registerBasicTypeEditors.js +100 -100
- package/editor/ecs/component/registerEngineComponentEditors.js +95 -95
- package/editor/entity_world_bounds.js +27 -27
- package/editor/particles/lut_ops.js +148 -148
- package/editor/particles/particleEditEvents.js +12 -12
- package/editor/particles/rebuildParticleEmitter.js +31 -31
- package/editor/persistence/EditorLayoutStore.js +71 -71
- package/editor/persistence/SceneMetadata.js +52 -52
- package/editor/persistence/SceneSlotManager.js +144 -144
- package/editor/persistence/buildNewSceneDataset.js +67 -67
- package/editor/persistence/files.js +68 -68
- package/editor/persistence/scene_import.js +143 -143
- package/editor/persistence/scene_serialization.js +79 -79
- package/editor/persistence/string_buffer_codec.js +41 -41
- package/editor/process/GridDisplayProcess.js +125 -125
- package/editor/process/SymbolicDisplayProcess.js +106 -108
- package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
- package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
- package/editor/process/symbolic/PathSymbolicDisplay.js +118 -118
- package/editor/process/symbolic/PositionedMarkerSymbolicDisplay.js +79 -79
- package/editor/process/symbolic/SoundEmitterSymbolicDisplay.js +57 -57
- package/editor/process/symbolic/SymbolicDisplay.js +62 -62
- package/editor/process/symbolic/gizmo_draw_wire_box_oriented.js +85 -85
- package/editor/process/symbolic/gizmo_draw_wire_circle.js +53 -53
- package/editor/prototypeEditorShell.js +156 -156
- package/editor/selection/editor_pick.js +130 -130
- package/editor/selection/screen_rect_frustum.js +119 -119
- package/editor/style/controls.scss +168 -168
- package/editor/style/inspector.scss +223 -223
- package/editor/templates/entity_templates.js +167 -167
- package/editor/tools/GridPaintTool.js +273 -273
- package/editor/tools/SelectionTool.js +307 -307
- package/editor/tools/TransformToolV2.js +378 -378
- package/editor/tools/engine/ToolEngine.js +188 -188
- package/editor/tools/paint/TerrainTexturePaintTool.js +250 -250
- package/editor/tools/v2/BlenderCameraOrientationGizmo.js +501 -501
- package/editor/tools/v2/TransformControls.js +971 -971
- package/editor/tools/v2/handle/gizmo_drag_plane.js +141 -141
- package/editor/tools/v2/handle/gizmo_draw.js +399 -399
- package/editor/tools/v2/handle/gizmo_handles.js +364 -364
- package/editor/tools/v2/handle/gizmo_ray_shapes.js +239 -239
- package/editor/view/ecs/HierarchicalEntityListView.js +339 -339
- package/editor/view/ecs/components/DatGuiController.js +2 -2
- package/editor/view/ecs/components/common/AutoCanvasView.js +100 -100
- package/editor/view/ecs/components/common/TextController.js +59 -59
- package/editor/view/library/MeshLibraryView.js +175 -175
- package/editor/view/library/mesh_library_drag.js +63 -63
- package/editor/view/library/model_thumbnail_source.js +86 -86
- package/editor/view/makeEntityDecorators.js +125 -125
- package/editor/view/node-graph/NodeGraphSelection.js +89 -89
- package/editor/view/node-graph/NodeView.js +211 -211
- package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -36
- package/editor/view/node-graph/actions/NodesMoveAction.js +41 -41
- package/editor/view/node-graph/connection_wire_geometry.js +107 -107
- package/editor/view/particles/ColorLutGradientView.js +291 -291
- package/editor/view/particles/ScalarLutCurveView.js +279 -279
- package/editor/view/prepareMeshLibrary.js +188 -188
- package/editor/view/shell/SceneTreePanelView.js +119 -119
- package/editor/view/v2/SplitView.js +2 -2
- package/package.json +94 -94
- package/samples/generation/generators/interactive/mir_generator_place_buff_objects.js +362 -362
- package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +738 -738
- package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +79 -79
- package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +93 -93
- package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +80 -80
- package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +164 -164
- package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +69 -69
- package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +102 -102
- package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +129 -129
- package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +72 -72
- package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +74 -74
- package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +169 -169
- package/src/DUPLICATION_AUDIT_2026_08_07.md +254 -254
- package/src/DUPLICATION_AUDIT_REMEDIATION.md +250 -250
- package/src/REVIEW_2026_08_06/adv-core.md +511 -511
- package/src/REVIEW_2026_08_06/adv-gfx.md +480 -480
- package/src/REVIEW_2026_08_06/adv-view.md +446 -446
- package/src/REVIEW_2026_08_06/api-core.md +296 -296
- package/src/REVIEW_2026_08_06/api-engine.md +195 -195
- package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -507
- package/src/REVIEW_2026_08_06/core-binary.md +348 -348
- package/src/REVIEW_2026_08_06/core-collection.md +376 -376
- package/src/REVIEW_2026_08_06/core-math.md +221 -221
- package/src/REVIEW_2026_08_06/core-process.md +266 -266
- package/src/REVIEW_2026_08_06/core-science.md +223 -223
- package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -279
- package/src/REVIEW_2026_08_06/engine-animation.md +167 -167
- package/src/REVIEW_2026_08_06/engine-asset.md +166 -166
- package/src/REVIEW_2026_08_06/engine-ecs.md +920 -920
- package/src/REVIEW_2026_08_06/engine-input.md +197 -197
- package/src/REVIEW_2026_08_06/engine-misc.md +248 -248
- package/src/REVIEW_2026_08_06/engine-terrain.md +52 -52
- package/src/REVIEW_2026_08_06/generation.md +176 -176
- package/src/REVIEW_2026_08_06/geom-2d.md +434 -434
- package/src/REVIEW_2026_08_06/geom-3d.md +117 -117
- package/src/REVIEW_2026_08_06/gfx-gi.md +143 -143
- package/src/REVIEW_2026_08_06/gfx-render.md +740 -740
- package/src/REVIEW_2026_08_06/gfx-texture.md +690 -690
- package/src/REVIEW_2026_08_06/prior-criticals.md +49 -49
- package/src/REVIEW_2026_08_06/prior-highs.md +138 -138
- package/src/REVIEW_2026_08_06/test-gaps.md +364 -364
- package/src/REVIEW_2026_08_06/view.md +667 -667
- package/src/REVIEW_2026_08_06.md +610 -610
- package/src/avif/codec/dec/avif_dec.cpp +233 -233
- package/src/avif/codec/dec/avif_dec.js +16 -16
- package/src/core/binary/BitImage2.js +246 -246
- package/src/core/binary/align_32.js +21 -21
- package/src/core/binary/data_view/DataType2DataViewReaders.js +23 -23
- package/src/core/binary/data_view/DataType2DataViewWriters.js +25 -25
- package/src/core/binary/hash/XXH128Hash.js +55 -55
- package/src/core/binary/hash/XXH3_SECRET.js +25 -25
- package/src/core/binary/hash/xxh3_128.js +341 -341
- package/src/core/binary/hash/xxh3_64.js +241 -241
- package/src/core/binary/hash/xxh3_common.js +476 -476
- package/src/core/binary/lz4/LZ4_MAX_INPUT_SIZE.js +9 -9
- package/src/core/binary/lz4/lz4_compress_block.js +385 -385
- package/src/core/binary/lz4/lz4_compress_bound.js +25 -25
- package/src/core/binary/lz4/lz4_decompress_block.js +139 -139
- package/src/core/binary/lz4/lz4_decompress_bound.js +30 -30
- package/src/core/binary/meshopt/MESHOPT_GROUP_SIZE.js +18 -18
- package/src/core/binary/meshopt/MeshoptCompressionFilter.js +30 -30
- package/src/core/binary/meshopt/MeshoptCompressionMode.js +21 -21
- package/src/core/binary/meshopt/__meshopt_test_streams.js +237 -237
- package/src/core/binary/meshopt/meshopt_decode.js +73 -73
- package/src/core/binary/meshopt/meshopt_decode_byte_group.js +67 -67
- package/src/core/binary/meshopt/meshopt_decode_data_block.js +59 -59
- package/src/core/binary/meshopt/meshopt_decode_filter_color.js +69 -69
- package/src/core/binary/meshopt/meshopt_decode_filter_exponential.js +43 -43
- package/src/core/binary/meshopt/meshopt_decode_filter_octahedral.js +60 -60
- package/src/core/binary/meshopt/meshopt_decode_filter_quaternion.js +71 -71
- package/src/core/binary/meshopt/meshopt_decode_index_buffer.js +294 -294
- package/src/core/binary/meshopt/meshopt_decode_index_sequence.js +115 -115
- package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.js +381 -381
- package/src/core/binary/meshopt/meshopt_read_uint_var.js +59 -59
- package/src/core/binary/reinterpret_float32_as_uint32.js +13 -13
- package/src/core/binary/reinterpret_uint32_as_float32.js +14 -14
- package/src/core/binary/type/DataType2TypedArrayConstructorMapping.js +35 -35
- package/src/core/bvh2/bvh3/ebvh_geometry_query_any_triangle_ray.js +149 -149
- package/src/core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js +96 -96
- package/src/core/bvh2/bvh3/query/bvh_query_depth_range_in_frustum.js +101 -101
- package/src/core/bvh2/bvh3/query/bvh_query_leaves_generic.js +77 -77
- package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray.js +90 -90
- package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray_segment.js +94 -94
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_generic.js +86 -86
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_capsule.js +101 -101
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_sphere.js +92 -92
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray.js +96 -96
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray_segment.js +100 -100
- package/src/core/bvh2/visual/BVHGeometry.js +131 -131
- package/src/core/bvh8/BVH8.js +436 -436
- package/src/core/bvh8/aabb3_round_to_float32.js +22 -22
- package/src/core/bvh8/build/BVH8Converter.js +588 -588
- package/src/core/bvh8/build/TriangleCluster.js +122 -122
- package/src/core/bvh8/build/aabb3_compute_merge_cost.js +28 -28
- package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +17 -17
- package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -303
- package/src/core/bvh8/build/bvh8_from_proxy.js +256 -256
- package/src/core/bvh8/build/byte.js +9 -9
- package/src/core/bvh8/build/encode_bounds_e.js +11 -11
- package/src/core/bvh8/bvh8_convert_to_dot.js +132 -132
- package/src/core/bvh8/bvh8_count_primitives.js +98 -98
- package/src/core/bvh8/bvh8_geometry_validate.js +138 -138
- package/src/core/bvh8/bvh8_geometry_validate_indirect.js +166 -166
- package/src/core/bvh8/bvh8_get_node_bounds.js +34 -34
- package/src/core/bvh8/bvh8_get_node_child_bounds.js +52 -52
- package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -18
- package/src/core/bvh8/bvh8_node_count_triangles.js +27 -27
- package/src/core/bvh8/bvh8_quality.js +72 -72
- package/src/core/bvh8/bvh8_validate_structure.js +87 -87
- package/src/core/clipboard/obtainClipBoard.js +29 -29
- package/src/core/clipboard/safeClipboardReadText.js +55 -55
- package/src/core/clipboard/safeClipboardWriteText.js +23 -23
- package/src/core/collection/array/typed/typed_array_value_denormalize.js +52 -52
- package/src/core/collection/array/typed/typed_array_value_normalize.js +51 -51
- package/src/core/collection/union-find/union_find.js +79 -79
- package/src/core/color/YCoCg/rgb_to_ycocg.js +20 -20
- package/src/core/color/YCoCg/ycocg_to_rgb.js +17 -17
- package/src/core/color/colormap/MAGMA_LUT.js +26 -26
- package/src/core/color/illuminant/D65_TRISTIMULUS_XYZ.js +19 -19
- package/src/core/color/lab/lab_apply_hunt_adjustment.js +27 -27
- package/src/core/color/lab/lab_distance_hyab.js +23 -23
- package/src/core/color/lab/xyz_to_lab.js +62 -62
- package/src/core/color/operations/color_darken.js +24 -24
- package/src/core/color/operations/color_desaturate.js +24 -24
- package/src/core/color/operations/color_lighten.js +24 -24
- package/src/core/color/operations/color_saturate.js +24 -24
- package/src/core/color/operations/color_scale_okhsv_channel.js +45 -45
- package/src/core/color/tonemap/tonemap_aces.js +41 -41
- package/src/core/color/ycxcz/xyz_to_ycxcz.js +28 -28
- package/src/core/color/ycxcz/ycxcz_to_xyz.js +19 -19
- package/src/core/dom/isImageBitmap.js +12 -12
- package/src/core/function/frameThrottle.js +23 -23
- package/src/core/geom/2d/aabb/aabb2_array_combine.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_compute_area.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_compute_overlap.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_from_v2_array.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_intersects_point.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_overlap_exists.js +7 -7
- package/src/core/geom/2d/circle/circle_compute_circle_intersection.js +7 -7
- package/src/core/geom/2d/circle/circle_compute_circle_penetration.js +7 -7
- package/src/core/geom/2d/circle/circle_intersects_point.js +7 -7
- package/src/core/geom/2d/compute_polygon_area_2d.js +7 -7
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +7 -7
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +7 -7
- package/src/core/geom/2d/convex-hull/orientation3_array.js +7 -7
- package/src/core/geom/2d/intersect_ray_2d.js +7 -7
- package/src/core/geom/2d/line/line2_distance_to_point_sqr.js +7 -7
- package/src/core/geom/2d/line/line2_segment_intersection_fraction_2d.js +44 -44
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_array_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_array_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_line_segment_intersection_exists_2d.js +7 -7
- package/src/core/geom/2d/line/segment2_intersects_segment2_2d.js +25 -25
- package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.js +46 -46
- package/src/core/geom/2d/line/segment2_segment_intersection_fractions.js +52 -52
- package/src/core/geom/2d/oriented-box/min_area_rect_2d.js +69 -69
- package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -433
- package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.js +51 -51
- package/src/core/geom/2d/polygon/polygon2_clip_halfplane.js +50 -50
- package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -84
- package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -52
- package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -86
- package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -1280
- package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -77
- package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +39 -39
- package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +101 -101
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +7 -7
- package/src/core/geom/2d/rectangle_to_aabb.js +7 -7
- package/src/core/geom/2d/spline_bezier2_2d.js +7 -7
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +99 -99
- package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -35
- package/src/core/geom/2d/triangle2d_compute_area.js +7 -7
- package/src/core/geom/2d/triangle2d_contains_point.js +7 -7
- package/src/core/geom/2d/triangle2d_get_barycentric.js +7 -7
- package/src/core/geom/2d/v2_morton_encode.js +23 -23
- package/src/core/geom/3d/aabb/aabb3_array_intersects_frustum_degree.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_box_surface_area_2.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_build_frustum.js +5 -5
- package/src/core/geom/3d/aabb/aabb3_closest_point_on_surface.js +57 -57
- package/src/core/geom/3d/aabb/aabb3_combined_surface_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_compute_half_surface_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_compute_projected_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_compute_surface_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_estimate_projected_area2.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_from_threejs_geometry.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_from_triangle_group.js +46 -46
- package/src/core/geom/3d/aabb/aabb3_intersects_frustum_degree.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_ray_segment_entry_distance.js +95 -95
- package/src/core/geom/3d/aabb/aabb3_raycast.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +93 -93
- package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.js +82 -82
- package/src/core/geom/3d/aabb/compute_aabb_from_points.js +7 -7
- package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +7 -7
- package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +486 -486
- package/src/core/geom/3d/atlas/BFF_PLAN.md +174 -174
- package/src/core/geom/3d/atlas/Chart.js +77 -77
- package/src/core/geom/3d/atlas/REVIEW_02.md +392 -392
- package/src/core/geom/3d/atlas/atlas_bench_lib.js +315 -315
- package/src/core/geom/3d/atlas/atlas_chart_type.js +16 -16
- package/src/core/geom/3d/atlas/atlas_compute_charts.js +188 -188
- package/src/core/geom/3d/atlas/atlas_extract_chart.js +107 -107
- package/src/core/geom/3d/atlas/atlas_generate.js +50 -50
- package/src/core/geom/3d/atlas/atlas_test_fixtures.js +266 -266
- package/src/core/geom/3d/atlas/atlas_write_chart_uvs.js +36 -36
- package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.js +62 -62
- package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.js +33 -33
- package/src/core/geom/3d/atlas/data/atlas_build_face_areas.js +50 -50
- package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js +39 -39
- package/src/core/geom/3d/atlas/io/bt_loop_uv.js +35 -35
- package/src/core/geom/3d/atlas/pack/ChartPlacement.js +46 -46
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +269 -269
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +209 -209
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.js +38 -38
- package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.js +86 -86
- package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.js +183 -183
- package/src/core/geom/3d/atlas/param/fit_plane_basis.js +66 -66
- package/src/core/geom/3d/atlas/segment/RegionGraph.js +40 -40
- package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.js +136 -136
- package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.js +30 -30
- package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.js +77 -77
- package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +116 -116
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +271 -271
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +191 -191
- package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -35
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.js +67 -67
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.js +137 -137
- package/src/core/geom/3d/compute_circle_bounding_box.js +7 -7
- package/src/core/geom/3d/cone/computeConeBoundingBox.js +7 -7
- package/src/core/geom/3d/cone/computeConePlaneSide.js +7 -7
- package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +7 -7
- package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts +38 -0
- package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/cylinder/cylinder3_raycast.js +105 -0
- package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -18
- package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -24
- package/src/core/geom/3d/frustum/clipping_volume_matrix4_project.js +7 -7
- package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +7 -7
- package/src/core/geom/3d/frustum/frustum3_compute_closest_point.js +117 -117
- package/src/core/geom/3d/frustum/frustum_matrix4_project.js +7 -7
- package/src/core/geom/3d/frustum/hexahedron_from_aabb.js +7 -7
- package/src/core/geom/3d/frustum/read_frustum_corner.js +17 -17
- package/src/core/geom/3d/frustum/read_three_planes_to_array.js +7 -7
- package/src/core/geom/3d/frustum/slice_frustum_linear_to_points.js +7 -7
- package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +146 -146
- package/src/core/geom/3d/gjk/gjk.js +430 -430
- package/src/core/geom/3d/gjk/gjk_epa_penetration.js +520 -520
- package/src/core/geom/3d/gjk/minkowski_support.js +71 -71
- package/src/core/geom/3d/gjk/mpr.js +368 -368
- package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +7 -7
- package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.js +7 -7
- package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.js +64 -64
- package/src/core/geom/3d/line/line3_compute_segment_closest_point_to_aabb3_t.js +153 -153
- package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.js +7 -7
- package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js +7 -7
- package/src/core/geom/3d/line/line3_compute_segment_segment_closest_points.js +88 -88
- package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.js +71 -71
- package/src/core/geom/3d/line/segment3_point_distance_to_segments.js +25 -25
- package/src/core/geom/3d/mat4/allocate_m4.js +7 -7
- package/src/core/geom/3d/mat4/apply_mat4_transform_to_direction_v3_array.js +7 -7
- package/src/core/geom/3d/mat4/apply_mat4_transform_to_v3_array.js +7 -7
- package/src/core/geom/3d/mat4/compose_matrix4_array.js +7 -7
- package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +7 -7
- package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +7 -7
- package/src/core/geom/3d/mat4/m4_compose.js +22 -22
- package/src/core/geom/3d/mat4/m4_compute_normal_matrix3.js +7 -7
- package/src/core/geom/3d/mat4/m4_decompose.js +64 -64
- package/src/core/geom/3d/mat4/m4_decompose_array.js +157 -157
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +32 -32
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.js +67 -67
- package/src/core/geom/3d/mat4/m4_linear_matrix3.js +29 -29
- package/src/core/geom/3d/mat4/m4_look_at.js +96 -96
- package/src/core/geom/3d/mat4/m4_make_rotation_x.js +47 -47
- package/src/core/geom/3d/mat4/m4_make_rotation_y.js +47 -47
- package/src/core/geom/3d/mat4/m4_make_rotation_z.js +47 -47
- package/src/core/geom/3d/mat4/m4_normal_matrix3.js +39 -39
- package/src/core/geom/3d/mat4/m4_perspective.js +66 -66
- package/src/core/geom/3d/mat4/m4_transpose.js +48 -48
- package/src/core/geom/3d/morton/mortonEncode_LUT.js +7 -7
- package/src/core/geom/3d/morton/mortonEncode_magicbits.js +7 -7
- package/src/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -5
- package/src/core/geom/3d/normal/hemioct/encode_unit_to_hemioct.js +28 -28
- package/src/core/geom/3d/normal/lambert_azimuth/decode_azimuth_to_unit.js +30 -30
- package/src/core/geom/3d/normal/lambert_azimuth/encode_unit_to_azimuth.js +19 -19
- package/src/core/geom/3d/normal/spherical/decode_spheremap_to_unit.js +38 -38
- package/src/core/geom/3d/normal/spherical/encode_unit_to_spheremap.js +30 -30
- package/src/core/geom/3d/plane/is_point_within_planes.js +7 -7
- package/src/core/geom/3d/plane/orient3d_fast_point.js +47 -47
- package/src/core/geom/3d/plane/plane3_compute_convex_3_plane_intersection.js +7 -7
- package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +7 -7
- package/src/core/geom/3d/plane/plane3_intersect_plane.js +7 -7
- package/src/core/geom/3d/plane/plane3_three_compute_convex_3_plane_intersection.js +7 -7
- package/src/core/geom/3d/point/point3_raycast.d.ts +33 -0
- package/src/core/geom/3d/point/point3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/point/point3_raycast.js +47 -0
- package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -60
- package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -94
- package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts +40 -0
- package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.js +94 -0
- package/src/core/geom/3d/quadric/QUADRIC3_SIZE.js +14 -14
- package/src/core/geom/3d/quadric/Quadric3.js +282 -282
- package/src/core/geom/3d/quadric/quadric3_add.js +33 -33
- package/src/core/geom/3d/quadric/quadric3_clear.js +25 -25
- package/src/core/geom/3d/quadric/quadric3_copy.js +27 -27
- package/src/core/geom/3d/quadric/quadric3_distance.js +36 -36
- package/src/core/geom/3d/quadric/quadric3_evaluate.js +25 -25
- package/src/core/geom/3d/quadric/quadric3_optimize.js +45 -45
- package/src/core/geom/3d/quadric/quadric3_scale.js +32 -32
- package/src/core/geom/3d/quadric/quadric3_set_from_plane_through_point.js +30 -30
- package/src/core/geom/3d/quadric/quadric3_set_from_vector4.js +35 -35
- package/src/core/geom/3d/quadric/quadric3_sub.js +36 -36
- package/src/core/geom/3d/quadric/quadric3_to_tensor_m3.js +28 -28
- package/src/core/geom/3d/quadric/quadric3_to_tensor_m3_inverse.js +56 -56
- package/src/core/geom/3d/quadric/quadric3_to_vector3.js +19 -19
- package/src/core/geom/3d/quaternion/quat3_createFromAxisAngle.js +7 -7
- package/src/core/geom/3d/quaternion/quat3_integrate.js +62 -62
- package/src/core/geom/3d/quaternion/quat3_multiply.js +31 -31
- package/src/core/geom/3d/quaternion/quat3_nlerp.js +72 -72
- package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -69
- package/src/core/geom/3d/quaternion/quat_decode_from_uint32.js +7 -7
- package/src/core/geom/3d/quaternion/quat_encode_to_uint32.js +7 -7
- package/src/core/geom/3d/ray/ray3_array_compose.js +7 -7
- package/src/core/geom/3d/ray/ray3_compute_nearest_point_to_point.js +7 -7
- package/src/core/geom/3d/ray/ray3_distance_to_point.js +7 -7
- package/src/core/geom/3d/ray/ray3_shift_origin_along_direction.js +7 -7
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +17 -0
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.js +43 -0
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts +1 -0
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CylinderShape3D.js +21 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts +7 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PointShape3D.js +88 -63
- package/src/core/geom/3d/shape/PosedShape3D.d.ts +6 -0
- package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PosedShape3D.js +27 -0
- package/src/core/geom/3d/shape/TransformedShape3D.d.ts +10 -0
- package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/TransformedShape3D.js +13 -0
- package/src/core/geom/3d/shape/Triangle3D.d.ts +1 -0
- package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/Triangle3D.js +26 -0
- package/src/core/geom/3d/shape/UnionShape3D.d.ts +6 -0
- package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnionShape3D.js +7 -0
- package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -45
- package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -44
- package/src/core/geom/3d/shape/shape3_mesh_from_geometry.js +64 -64
- package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +7 -7
- package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.js +51 -51
- package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +7 -7
- package/src/core/geom/3d/sphere/compute_bounding_sphere_of_2_spheres.js +7 -7
- package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js +53 -53
- package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js +30 -30
- package/src/core/geom/3d/sphere/harmonics/sh3_rotate_bl.js +7 -7
- package/src/core/geom/3d/sphere/harmonics/sh_compute_K.js +7 -7
- package/src/core/geom/3d/sphere/sphere_array_intersects_point.js +7 -7
- package/src/core/geom/3d/sphere/sphere_array_intersects_ray.js +7 -7
- package/src/core/geom/3d/sphere/sphere_intersects_point.js +7 -7
- package/src/core/geom/3d/sphere/sphere_matrix4_transform.js +5 -5
- package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +7 -7
- package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +7 -7
- package/src/core/geom/3d/sphere/spherical_from_cartesian.js +7 -7
- package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +174 -174
- package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +7 -7
- package/src/core/geom/3d/tetrahedra/compute_tetrahedron_volume.js +7 -7
- package/src/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +7 -7
- package/src/core/geom/3d/tetrahedra/graphics/build_tetrahedral_mesh_geometry.js +39 -39
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -48
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +173 -173
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_compute_tet_quality.js +22 -22
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -146
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -232
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -255
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +387 -387
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +140 -140
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +7 -7
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +97 -97
- package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -66
- package/src/core/geom/3d/tetrahedra/triangle/triangle_decode_depth.js +59 -59
- package/src/core/geom/3d/tetrahedra/triangle/triangle_rasterized_depth_sample_index.js +54 -54
- package/src/core/geom/3d/tetrahedra/validate_neighbour.js +70 -70
- package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +314 -314
- package/src/core/geom/3d/topology/struct/binary/BinaryDataLayer.js +364 -364
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +567 -567
- package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +1085 -1085
- package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.js +73 -73
- package/src/core/geom/3d/topology/struct/binary/io/bt_loop_kill.js +35 -35
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_append.js +119 -119
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +84 -84
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +172 -172
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -56
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +227 -227
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +161 -161
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +490 -490
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +120 -120
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js +120 -120
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -52
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +115 -115
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_flip.js +142 -142
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -21
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +92 -92
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_split.js +179 -179
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js +28 -28
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_kill_only_edge.js +8 -8
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -83
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +89 -89
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_kill.js +25 -25
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.js +127 -127
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -268
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_kill_only_face.js +8 -8
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_mesh_kill_degenerate_faces.js +72 -72
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_kill_only_vert.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_relative_merge_distance.js +48 -48
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_split_pinched_vertices.js +256 -256
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_vertex_merge_distance.js +77 -77
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_kill.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.js +148 -148
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_kill_only.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_collect_boundary_segments.js +29 -29
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_breaks_link_condition.js +107 -107
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_is_legal.js +41 -41
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_would_fold.js +120 -120
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -42
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_is_boundary.js +20 -20
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_read_diamond.js +96 -96
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_compute_newell_normal.js +72 -72
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_attached_faces.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces.js +39 -39
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces_weighted.js +93 -93
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_neighbour_faces.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_shared_loop.js +48 -48
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -45
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.js +66 -66
- package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -44
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +32 -32
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -53
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +703 -703
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +181 -181
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.js +133 -133
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +82 -82
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -599
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -108
- package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricEdge.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricGeometry.js +7 -7
- package/src/core/geom/3d/triangle/compute_triangle_area_3d.js +7 -7
- package/src/core/geom/3d/triangle/compute_triangle_normal.js +7 -7
- package/src/core/geom/3d/triangle/rayTriangleIntersection.js +7 -7
- package/src/core/geom/3d/triangle/tri3_closest_point.js +65 -65
- package/src/core/geom/3d/triangle/tri3_closest_point_barycentric.js +171 -171
- package/src/core/geom/3d/triangle/tri3_flatten_isometric.js +79 -79
- package/src/core/geom/3d/triangle/tri3_ray_intersection.js +160 -160
- package/src/core/geom/3d/triangle/tri3_ray_intersection_barycentric.js +96 -96
- package/src/core/geom/3d/triangle/tri3_raycast.d.ts +42 -0
- package/src/core/geom/3d/triangle/tri3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/triangle/tri3_raycast.js +94 -0
- package/src/core/geom/3d/triangle/triangle3_incenter.js +7 -7
- package/src/core/geom/3d/triangle/triangle_compute_plane_side.js +7 -7
- package/src/core/geom/3d/triangle/triangle_compute_signed_volume.js +7 -7
- package/src/core/geom/3d/triangle/triangle_intersects_clipping_volume.js +7 -7
- package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +7 -7
- package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +7 -7
- package/src/core/geom/3d/util/make_justified_point_grid.js +7 -7
- package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +381 -381
- package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +684 -684
- package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -554
- package/src/core/geom/mat3/m3_cm_from_translation.js +7 -7
- package/src/core/geom/mat3/m3_cm_invert.js +7 -7
- package/src/core/geom/mat3/m3_make_rotation.js +5 -5
- package/src/core/geom/mat3/m3_make_rotation_TBN.js +31 -31
- package/src/core/geom/mat3/m3_multiply_vec3.js +7 -7
- package/src/core/geom/packing/computeBoundingSphereOfSpheres.js +7 -7
- package/src/core/geom/packing/max-rect/cost/costByBestShortSide.js +7 -7
- package/src/core/geom/packing/max-rect/cost/costByRemainingArea.js +7 -7
- package/src/core/geom/packing/max-rect/cutArea.js +7 -7
- package/src/core/geom/packing/max-rect/findBestContainer.js +7 -7
- package/src/core/geom/packing/max-rect/packMaxRectangles.js +7 -7
- package/src/core/geom/packing/max-rect/packOneBox.js +7 -7
- package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +7 -7
- package/src/core/geom/packing/max-rect/removeRedundantBoxesArray.js +7 -7
- package/src/core/geom/random/randomPointInBox.js +7 -7
- package/src/core/geom/random/randomPointInCircle.js +7 -7
- package/src/core/geom/random/randomPointInPoint.js +7 -7
- package/src/core/geom/random/randomPointInSphere.js +7 -7
- package/src/core/geom/random/randomPointOnBox.js +7 -7
- package/src/core/geom/random/randomPointOnSphere.js +7 -7
- package/src/core/geom/random/random_orthonormal_basis.js +31 -31
- package/src/core/geom/vec/vector_axpy.js +15 -15
- package/src/core/geom/vec/vector_axpy_offset.js +20 -20
- package/src/core/geom/vec/vector_copy.js +7 -7
- package/src/core/geom/vec/vector_dot_offset.js +25 -25
- package/src/core/geom/vec/vector_scale_array.js +7 -7
- package/src/core/geom/vec2/v2_rotate.js +21 -21
- package/src/core/geom/vec2/v2_rotate_cos_sin.js +21 -21
- package/src/core/geom/vec3/v3_angle_atan2_between.js +7 -7
- package/src/core/geom/vec3/v3_angle_between.js +7 -7
- package/src/core/geom/vec3/v3_angle_cos_between.js +7 -7
- package/src/core/geom/vec3/v3_array_add_in_place.js +7 -7
- package/src/core/geom/vec3/v3_array_centroid.js +7 -7
- package/src/core/geom/vec3/v3_array_copy.js +7 -7
- package/src/core/geom/vec3/v3_array_displace_in_direction.js +7 -7
- package/src/core/geom/vec3/v3_array_equals.js +7 -7
- package/src/core/geom/vec3/v3_array_immediate_add.js +7 -7
- package/src/core/geom/vec3/v3_array_matrix4_rotate.js +7 -7
- package/src/core/geom/vec3/v3_array_normalize.js +7 -7
- package/src/core/geom/vec3/v3_array_scale.js +7 -7
- package/src/core/geom/vec3/v3_compute_interior_angle.js +7 -7
- package/src/core/geom/vec3/v3_cotangent.js +33 -33
- package/src/core/geom/vec3/v3_dot_array_array.js +7 -7
- package/src/core/geom/vec3/v3_matrix3_multiply.js +30 -30
- package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -49
- package/src/core/geom/vec3/v3_matrix4_rotate.js +7 -7
- package/src/core/geom/vec3/v3_multiply.js +23 -23
- package/src/core/geom/vec3/v3_multiply_scalar.js +7 -7
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +49 -49
- package/src/core/geom/vec3/v3_quat3_apply.js +7 -7
- package/src/core/geom/vec3/v3_quat3_apply_inverse.js +7 -7
- package/src/core/geom/vec3/v3_quaternion_apply.js +39 -39
- package/src/core/geom/vec3/v3_quaternion_apply_inverse.js +41 -41
- package/src/core/geom/vec3/v3_reflect.js +24 -24
- package/src/core/geom/vec3/v3_scale_array.js +18 -18
- package/src/core/geom/vec3/v3_triple_cross.js +45 -45
- package/src/core/geom/vec3/v3_triple_cross_product.js +7 -7
- package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -21
- package/src/core/geom/vec4/v4_multiply_mat4.js +7 -7
- package/src/core/graph/csr/csr_graph_build_distance_field.js +112 -112
- package/src/core/graph/csr/csr_graph_find_edge.js +25 -25
- package/src/core/graph/csr/csr_graph_find_shortest_path.js +105 -105
- package/src/core/graph/csr/csr_graph_from_undirected_edge_list.js +94 -94
- package/src/core/graph/graph_k_means_cluster.js +250 -250
- package/src/core/graph/layout/CircleLayout.js +1 -1
- package/src/core/graph/layout/box/BoxLayouter.js +1 -1
- package/src/core/graph/layout/graph_peel_topological_layer.js +45 -45
- package/src/core/graph/metis/BinaryTopologyFaceGraph.js +49 -49
- package/src/core/graph/metis/bt_mesh_build_face_graph.js +111 -111
- package/src/core/graph/metis/cluster_mesh_metis.js +137 -137
- package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -100
- package/src/core/graph/metis/native/bisection/compute_2way_params.js +86 -86
- package/src/core/graph/metis/native/bisection/fm_2way.js +297 -297
- package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -137
- package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -119
- package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -94
- package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -158
- package/src/core/graph/metis/native/coarsen/match_shem.js +175 -175
- package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -122
- package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -170
- package/src/core/graph/metis/native/metis_partition_kway.js +126 -126
- package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -53
- package/src/core/graph/metis/native/refine/fm_kway.js +487 -487
- package/src/core/graph/metis/native/refine/project_kway.js +43 -43
- package/src/core/graph/metis/native/refine/refine_kway.js +43 -43
- package/src/core/lang/reactive/pegjs/parser.js +3458 -3458
- package/src/core/math/complex/complex_horner_eval.js +31 -31
- package/src/core/math/computeWholeDivisorLow.js +33 -33
- package/src/core/math/f32_next_down.js +24 -24
- package/src/core/math/f32_next_up.js +27 -27
- package/src/core/math/hash/hash_mix2.js +22 -22
- package/src/core/math/hash/hash_mix3.js +19 -19
- package/src/core/math/linalg/cg/cg_solve.js +106 -106
- package/src/core/math/linalg/cg/cg_solve_normal_equations.js +59 -59
- package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.js +43 -43
- package/src/core/math/linalg/eigen/matrix_householder_in_place.js +122 -122
- package/src/core/math/linalg/eigen/matrix_qr_in_place.js +190 -190
- package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +117 -117
- package/src/core/math/linalg/givens/givens_apply_rows.js +28 -28
- package/src/core/math/linalg/givens/givens_apply_rows_offset.js +30 -30
- package/src/core/math/linalg/givens/givens_rotation_coefficients.js +39 -39
- package/src/core/math/linalg/lu_factor_linear_system.js +108 -108
- package/src/core/math/linalg/lu_solve_linear_system.js +53 -53
- package/src/core/math/linalg/polynomial_add_into.js +29 -29
- package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +159 -159
- package/src/core/math/linalg/polynomial_cubic_derivative_eval.js +16 -16
- package/src/core/math/linalg/polynomial_cubic_horner_eval.js +19 -19
- package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.js +14 -14
- package/src/core/math/linalg/polynomial_multiply.js +41 -41
- package/src/core/math/linalg/polynomial_real_roots_in_interval.js +211 -211
- package/src/core/math/linalg/polynomial_root_bound_cauchy.js +30 -30
- package/src/core/math/linalg/polynomial_scale_into.js +17 -17
- package/src/core/math/linalg/polynomial_sub_into.js +29 -29
- package/src/core/math/linalg/solve_linear_system_GEPP_2x2.js +96 -96
- package/src/core/math/linalg/sor_optimal_omega.js +42 -42
- package/src/core/math/linalg/sparse/SparseMatrixCSR.js +88 -88
- package/src/core/math/linalg/sparse/spm_matvec.js +22 -22
- package/src/core/math/linalg/sparse/spm_matvec_transpose.js +29 -29
- package/src/core/math/lookup/LUT_HEATMAP.js +30 -30
- package/src/core/math/lookup/ParameterLookupTable.js +495 -495
- package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -6
- package/src/core/math/noise/curl_noise_3d.js +18 -18
- package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -28
- package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -48
- package/src/core/math/physics/kinematics/computeInterceptPoint.js +79 -79
- package/src/core/math/random/randomSeed.js +16 -16
- package/src/core/math/random/random_pick_weighted_index.js +26 -26
- package/src/core/math/round_half_away_from_zero.js +13 -13
- package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -107
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +125 -125
- package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +50 -50
- package/src/core/math/spline/spline3_hermite_reverse.js +49 -49
- package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -37
- package/src/core/math/statistics/hammersley_sequence.js +53 -53
- package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
- package/src/core/path/convertPathToURL.js +107 -107
- package/src/core/process/delay.js +16 -16
- package/src/core/science/units/MEASUREMENT_UNITS_SCIENTIFIC.js +87 -87
- package/src/core/science/units/MEASUREMENT_UNITS_SI.js +17 -17
- package/src/core/science/units/NamedUnit.js +48 -48
- package/src/core/science/units/UNIT_DIMENSION_MAPPING.js +29 -29
- package/src/core/science/units/UnitDimension.js +28 -28
- package/src/core/science/units/format_quantity.js +74 -74
- package/src/core/science/units/magnitude_prefix.js +127 -127
- package/src/core/science/units/si/AMPERE.js +13 -13
- package/src/core/science/units/si/CANDELA.js +13 -13
- package/src/core/science/units/si/DIMENSIONLESS.js +14 -14
- package/src/core/science/units/si/KELVIN.js +13 -13
- package/src/core/science/units/si/KILOGRAM.js +13 -13
- package/src/core/science/units/si/METER.js +13 -13
- package/src/core/science/units/si/MOLE.js +13 -13
- package/src/core/science/units/si/SECOND.js +13 -13
- package/src/core/science/units/si/derived/BECQUEREL.js +16 -16
- package/src/core/science/units/si/derived/COULOMB.js +13 -13
- package/src/core/science/units/si/derived/FARAD.js +13 -13
- package/src/core/science/units/si/derived/GRAY.js +16 -16
- package/src/core/science/units/si/derived/HENRY.js +13 -13
- package/src/core/science/units/si/derived/HERTZ.js +16 -16
- package/src/core/science/units/si/derived/JOULE.js +13 -13
- package/src/core/science/units/si/derived/KATAL.js +13 -13
- package/src/core/science/units/si/derived/LUMEN.js +14 -14
- package/src/core/science/units/si/derived/LUX.js +13 -13
- package/src/core/science/units/si/derived/NEWTON.js +13 -13
- package/src/core/science/units/si/derived/OHM.js +13 -13
- package/src/core/science/units/si/derived/PASCAL.js +13 -13
- package/src/core/science/units/si/derived/SIEMENS.js +13 -13
- package/src/core/science/units/si/derived/SIEVERT.js +16 -16
- package/src/core/science/units/si/derived/TESLA.js +13 -13
- package/src/core/science/units/si/derived/VOLT.js +13 -13
- package/src/core/science/units/si/derived/WATT.js +13 -13
- package/src/core/science/units/si/derived/WEBER.js +13 -13
- package/src/core/science/units/unit_matrix_to_string.js +403 -403
- package/src/engine/Engine.js +726 -726
- package/src/engine/EngineHarness.js +590 -590
- package/src/engine/REVIEW_2026_07_03.md +211 -211
- package/src/engine/animation/AnimatedActions.js +81 -81
- package/src/engine/animation/AnimationUtils.js +252 -252
- package/src/engine/animation/async/TimeSeries.js +300 -300
- package/src/engine/animation/curve/animation_curve_binary_codec.js +60 -60
- package/src/engine/animation/curve/draw/canvas_point_to_curve.js +22 -22
- package/src/engine/animation/curve/draw/curve_point_to_canvas.js +38 -38
- package/src/engine/animation/curve/draw/position_canvas_to_curve.js +22 -22
- package/src/engine/animation/curve/draw/position_curve_to_canvas.js +22 -22
- package/src/engine/asset/GameAssetType.d.ts +0 -1
- package/src/engine/asset/GameAssetType.js +0 -1
- package/src/engine/asset/load_model_scene_bundle.js +27 -27
- package/src/engine/asset/loaders/font/FontAsset.js +21 -21
- package/src/engine/asset/loaders/font/FontAssetLoader.js +20 -20
- package/src/engine/asset/loaders/gltf_test_fixtures.js +358 -358
- package/src/engine/asset/loaders/image/ImageBitmapAssetLoader.js +52 -52
- package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +94 -94
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -59
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -55
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -74
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -34
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -46
- package/src/engine/control/first-person/DESIGN.md +640 -640
- package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -563
- package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -134
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2045 -2045
- package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -30
- package/src/engine/control/first-person/math/Spring.js +71 -71
- package/src/engine/control/first-person/math/computeJumpFromApex.js +23 -23
- package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -41
- package/src/engine/control/first-person/math/computeMassRatios.js +44 -44
- package/src/engine/control/first-person/math/criticallyDampedSpring.js +34 -34
- package/src/engine/control/first-person/math/dampedSpringStep.js +72 -72
- package/src/engine/control/first-person/math/jumpDynamics.js +108 -108
- package/src/engine/control/first-person/math/stepTowards.js +20 -20
- package/src/engine/control/first-person/pose/FirstPersonPose.js +137 -137
- package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -27
- package/src/engine/control/first-person/test/buildTestPlayer.js +36 -36
- package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +531 -531
- package/src/engine/ecs/EntityManager.d.ts +7 -3
- package/src/engine/ecs/EntityManager.d.ts.map +1 -1
- package/src/engine/ecs/EntityManager.js +10 -5
- package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
- package/src/engine/ecs/async/SystemWorkerHost.js +329 -329
- package/src/engine/ecs/async/SystemWorkerLoopback.js +143 -143
- package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +246 -246
- package/src/engine/ecs/async/WorkerSystem.js +702 -702
- package/src/engine/ecs/async/WorkerSystemProtocol.js +150 -150
- package/src/engine/ecs/fow/FogOfWar.js +707 -707
- package/src/engine/ecs/fow/FogOfWarEdgeMode.js +40 -40
- package/src/engine/ecs/fow/fog_of_war_mask_world_bounds.js +24 -24
- package/src/engine/ecs/fow/fog_of_war_uv_transform.js +30 -30
- package/src/engine/ecs/gui/hud/testHudEdgeStick.js +132 -134
- package/src/engine/ecs/hierarchy/EntityChildIndex.js +140 -140
- package/src/engine/ecs/ik/IKProblem.js +42 -42
- package/src/engine/ecs/ik/InverseKinematicsSystem.js +193 -193
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +134 -134
- package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +97 -97
- package/src/engine/ecs/ik/ik_bone_chain.js +85 -85
- package/src/engine/ecs/ik/ik_probe_terrain_contact.js +119 -119
- package/src/engine/ecs/parent/EntityNode.d.ts +1 -1
- package/src/engine/ecs/parent/EntityNode.js +2 -2
- package/src/engine/ecs/parent/ParentEntitySystem.js +255 -255
- package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
- package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
- package/src/engine/ecs/speaker/VoiceSystem.js +13 -7
- package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +269 -269
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js +315 -315
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js +331 -331
- package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +199 -199
- package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js +57 -57
- package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js +56 -56
- package/src/engine/ecs/storage/binary/object-v2/README.md +110 -110
- package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.js +48 -48
- package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js +44 -44
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +265 -265
- package/src/engine/ecs/terrain/TerrainClouds.js +97 -97
- package/src/engine/ecs/terrain/ecs/StubTerrainBuildWorker.js +65 -65
- package/src/engine/ecs/terrain/ecs/Terrain.d.ts +4 -0
- package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
- package/src/engine/ecs/terrain/ecs/TerrainSystem.js +209 -209
- package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +316 -316
- package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +416 -416
- package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +525 -525
- package/src/engine/ecs/terrain/overlay/TerrainOverlay.js +302 -302
- package/src/engine/ecs/terrain/tiles/TerrainTile.js +561 -561
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +896 -896
- package/src/engine/ecs/terrain/tiles/tile_geometry_to_meshlets.js +36 -36
- package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
- package/src/engine/ecs/transform/Transform64.js +814 -814
- package/src/engine/ecs/transform/t64_copy_from_transform.js +35 -35
- package/src/engine/ecs/transform/t64_equals_transform.js +55 -55
- package/src/engine/ecs/transform/t64_look_rotation.js +36 -36
- package/src/engine/ecs/transform/t64_transform_direction.js +25 -25
- package/src/engine/ecs/transform/transform_copy_from_t64.js +28 -28
- package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +345 -345
- package/src/engine/ecs/transform-attachment/transform_attachment_find_descendant_by_name.js +65 -65
- package/src/engine/ecs/transform-attachment/transform_attachment_parent_of.js +24 -24
- package/src/engine/ecs/util/hideEntityGracefully.js +208 -208
- package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -446
- package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js +14 -14
- package/src/engine/graphics/FrameThrottle.js +2 -2
- package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +207 -207
- package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.js +151 -151
- package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -22
- package/src/engine/graphics/ecs/camera/Camera.js +165 -165
- package/src/engine/graphics/ecs/camera/camera_active_frustum.js +25 -25
- package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
- package/src/engine/graphics/ecs/camera/camera_traverse_active.js +18 -18
- package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +170 -170
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -68
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +296 -296
- package/src/engine/graphics/ecs/decal/v2/Decal.js +220 -220
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +56 -56
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_0_1.js +38 -38
- package/src/engine/graphics/ecs/light/shadow/compute_view_frustum_aabb_in_space.js +62 -62
- package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.js +77 -77
- package/src/engine/graphics/ecs/make_bvh_depth_range_computer.js +33 -33
- package/src/engine/graphics/ecs/mesh-v2/DeferredBoundsQueue.js +88 -88
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
- package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +303 -303
- package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +1 -1
- package/src/engine/graphics/ecs/mesh-v2/aggregate/serialization/SGMeshSerializationAdapter.js +39 -39
- package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +36 -36
- package/src/engine/graphics/ecs/path/tube/build/GeometryStream.js +94 -94
- package/src/engine/graphics/ecs/path/tube/build/build_stream_catmullrom.js +310 -310
- package/src/engine/graphics/ecs/path/tube/build/build_stream_linear.js +73 -73
- package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +199 -199
- package/src/engine/graphics/ecs/path/tube/build/makeTubeStream.js +217 -217
- package/src/engine/graphics/ecs/path/tube/build/make_cap.js +340 -340
- package/src/engine/graphics/ecs/path/tube/build/make_ring_faces.js +40 -40
- package/src/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +153 -153
- package/src/engine/graphics/ecs/render_layers_compute_depth_range.js +64 -64
- package/src/engine/graphics/ecs/trail3d/Trail3D.js +483 -483
- package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +26 -26
- package/src/engine/graphics/ecs/trail3d/advance_trail.js +42 -42
- package/src/engine/graphics/ecs/trail3d/make_gradient_stroke.js +58 -58
- package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +90 -90
- package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +75 -75
- package/src/engine/graphics/ecs/trail3d/trail_head_position.js +19 -19
- package/src/engine/graphics/ecs/water/Water.js +104 -104
- package/src/engine/graphics/geometry/AttributeData.js +84 -84
- package/src/engine/graphics/geometry/buffered/geometry_compute_vertex_normals_indexed.js +49 -49
- package/src/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +288 -288
- package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.js +87 -87
- package/src/engine/graphics/geometry/buffered/read_triangle_vertices.js +102 -102
- package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_buffered_geometry.js +52 -52
- package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_indexed_geometry.js +20 -20
- package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_unindexed_geometry.js +19 -19
- package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.js +135 -135
- package/src/engine/graphics/impostors/voxel/README.md +149 -149
- package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.js +141 -141
- package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.js +20 -20
- package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts +1 -2
- package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.js +45 -45
- package/src/engine/graphics/particles/particular/engine/ParticularEngine.js +229 -229
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +2 -10
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1149 -1149
- package/src/engine/graphics/particles/particular/engine/emitter/ParticlePool.js +472 -472
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -2
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -2
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.js +3 -5
- package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +135 -135
- package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.js +168 -168
- package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.js +18 -18
- package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -2
- package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +219 -219
- package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
- package/src/engine/graphics/render/frame_graph/FrameGraphScope.js +72 -72
- package/src/engine/graphics/render/frame_graph/FrameGraphState.js +39 -39
- package/src/engine/graphics/render/frame_graph/ResourceEntry.js +96 -96
- package/src/engine/graphics/render/gizmo/GizmoShapeBuffer.js +105 -105
- package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.js +162 -162
- package/src/engine/graphics/render/layers/RenderLayer.js +95 -95
- package/src/engine/graphics/sh3/build_injected_shader.js +56 -56
- package/src/engine/graphics/sh3/grid_dilate_average.js +175 -175
- package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.js +506 -506
- package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.js +161 -161
- package/src/engine/graphics/sh3/path_tracer/PathTracedRenderer.js +205 -205
- package/src/engine/graphics/sh3/path_tracer/PathTracedScene.js +698 -698
- package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +490 -490
- package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -2
- package/src/engine/graphics/sh3/path_tracer/make_sky_hosek.js +45 -45
- package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.js +73 -73
- package/src/engine/graphics/sh3/path_tracer/ray_hit_apply_transform.js +32 -32
- package/src/engine/graphics/sh3/path_tracer/texture/apply_texture_clamping_to_coordinate.js +28 -28
- package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +142 -142
- package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -70
- package/src/engine/graphics/sh3/sky/hosek/sky_hosek_compute_irradiance_by_direction.js +388 -388
- package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.js +277 -277
- package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.js +57 -57
- package/src/engine/graphics/shadows/vsm.glsl.js +43 -43
- package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -81
- package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -15
- package/src/engine/graphics/texture/channel_count_to_format_prefix.js +26 -26
- package/src/engine/graphics/texture/isImageBitmap.js +2 -2
- package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -189
- package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -51
- package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
- package/src/engine/graphics/texture/sampler/Sampler2DArray.js +172 -172
- package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +466 -466
- package/src/engine/graphics/texture/sampler/compare/flip/FlipParameters.js +18 -18
- package/src/engine/graphics/texture/sampler/compare/flip/FlipPooling.js +33 -33
- package/src/engine/graphics/texture/sampler/compare/flip/flip_build_feature_kernel.js +147 -147
- package/src/engine/graphics/texture/sampler/compare/flip/flip_build_spatial_kernel.js +177 -177
- package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_color_difference.js +104 -104
- package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_feature_difference.js +76 -76
- package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_pixels_per_degree.js +34 -34
- package/src/engine/graphics/texture/sampler/compare/flip/flip_constants.js +77 -77
- package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_feature.js +48 -48
- package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_spatial.js +86 -86
- package/src/engine/graphics/texture/sampler/compare/flip/flip_pool.js +71 -71
- package/src/engine/graphics/texture/sampler/compare/flip/flip_srgb_to_ycxcz.js +55 -55
- package/src/engine/graphics/texture/sampler/compare/flip/sampler2d_flip_ldr.js +80 -80
- package/src/engine/graphics/texture/sampler/filter/SeparableKernel.js +82 -82
- package/src/engine/graphics/texture/sampler/filter/sampler2d_convolve_separable.js +122 -122
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +148 -148
- package/src/engine/graphics/texture/sampler/sampler2d_channel_apply_lut.js +82 -82
- package/src/engine/graphics/texture/sampler/sampler2d_combine.js +67 -67
- package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -54
- package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -63
- package/src/engine/graphics/texture/sampler/sampler2d_srgb_to_linear.js +33 -33
- package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -68
- package/src/engine/graphics/texture/shade_image_preview.js +103 -103
- package/src/engine/graphics/trail/TrailSpawnMode.js +20 -20
- package/src/engine/graphics/trail/tube/TubeX.js +573 -573
- package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +58 -58
- package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +34 -34
- package/src/engine/graphics/trail/tube/tube_attributes_spec.js +69 -69
- package/src/engine/graphics/trail/tube/tube_frame.js +83 -83
- package/src/engine/graphics3/AnimationGraphSystem3.js +329 -329
- package/src/engine/graphics3/AnimationSystem3.js +352 -352
- package/src/engine/graphics3/CameraSystem3.js +170 -170
- package/src/engine/graphics3/DecalSystem3.js +441 -441
- package/src/engine/graphics3/FogOfWarSystem3.js +265 -265
- package/src/engine/graphics3/GraphicsEngine3.js +776 -776
- package/src/engine/graphics3/HighlightSystem3.js +241 -241
- package/src/engine/graphics3/LightSystem3.js +211 -211
- package/src/engine/graphics3/MeshSystem3.js +646 -646
- package/src/engine/graphics3/ParticleEmitterSystem3.js +289 -289
- package/src/engine/graphics3/PathDisplaySystem3.js +309 -309
- package/src/engine/graphics3/PickingQuery.js +48 -48
- package/src/engine/graphics3/PickingResult.js +47 -47
- package/src/engine/graphics3/PickingSystem3.js +273 -273
- package/src/engine/graphics3/ShadeCameraAdapter.js +55 -55
- package/src/engine/graphics3/ShadedGeometrySystem3.js +169 -169
- package/src/engine/graphics3/TerrainSystem3.js +566 -566
- package/src/engine/graphics3/TooltipComponentSystem3.js +328 -328
- package/src/engine/graphics3/Trail3DSystem3.js +221 -221
- package/src/engine/graphics3/WaterSystem3.js +293 -293
- package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
- package/src/engine/graphics3/animation/wrap_clip_time.js +23 -23
- package/src/engine/graphics3/animation/write_graph_playback.js +48 -48
- package/src/engine/graphics3/camera_sync_from_transform.js +29 -29
- package/src/engine/graphics3/debug/gizmo_view_from_camera.js +48 -48
- package/src/engine/graphics3/decal/DECAL_CLUSTER.js +61 -61
- package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +117 -117
- package/src/engine/graphics3/decal/DecalAtlas.js +194 -194
- package/src/engine/graphics3/decal/DecalAtlasFill.js +32 -32
- package/src/engine/graphics3/decal/GPUDecalRenderer.js +360 -360
- package/src/engine/graphics3/decal/chunk_decal_intersects_frustum.js +59 -59
- package/src/engine/graphics3/decal/chunk_decal_sampling_info.js +57 -57
- package/src/engine/graphics3/decal/chunk_decal_surface_frame.js +54 -54
- package/src/engine/graphics3/decal/decal_atlas_border.js +69 -69
- package/src/engine/graphics3/decal/decal_gpu_records.js +120 -120
- package/src/engine/graphics3/decal/decal_material_samplers.js +109 -109
- package/src/engine/graphics3/decal/graph_build_decal_clusters.js +212 -212
- package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +207 -207
- package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +108 -108
- package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +151 -151
- package/src/engine/graphics3/highlight/HIGHLIGHT_OUTLINE.js +23 -23
- package/src/engine/graphics3/highlight/chunk_highlight_lookup.js +53 -53
- package/src/engine/graphics3/highlight/pack_highlight_color.js +44 -44
- package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
- package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
- package/src/engine/graphics3/load_cube_environment.js +68 -68
- package/src/engine/graphics3/make_default_environment.js +107 -107
- package/src/engine/graphics3/particles/particle_gpu_records.js +221 -221
- package/src/engine/graphics3/path/build_tube_path_streams.js +98 -98
- package/src/engine/graphics3/path/tube_stream_write_geometry.js +90 -90
- package/src/engine/graphics3/preview/ModelPreviewFraming.js +35 -35
- package/src/engine/graphics3/preview/make_model_preview_scene.js +154 -154
- package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
- package/src/engine/graphics3/preview/model_preview_framing.js +70 -70
- package/src/engine/graphics3/shade_bundle_bounds.js +55 -55
- package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
- package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
- package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.js +481 -481
- package/src/engine/graphics3/terrain/chunk_terrain_grid_overlay.js +61 -61
- package/src/engine/graphics3/terrain/chunk_terrain_splat_mix.js +66 -66
- package/src/engine/graphics3/terrain/pack_terrain_row_table.js +53 -53
- package/src/engine/graphics3/terrain/shader_terrain_splat.js +215 -215
- package/src/engine/graphics3/trail/trail_tube_write_geometry.js +158 -158
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts +2 -2
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
- package/src/engine/input/GAMEPAD_API_PROPOSAL.md +717 -717
- package/src/engine/input/analog/apply_radial_dead_zone.js +38 -38
- package/src/engine/input/devices/GamepadDevice.js +466 -466
- package/src/engine/input/devices/ManualInputDevice.js +152 -152
- package/src/engine/input/devices/PointerCoordinates.js +26 -26
- package/src/engine/input/devices/events/GamepadEvents.js +10 -10
- package/src/engine/input/devices/gamepad/GamepadAxes.js +19 -19
- package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -29
- package/src/engine/input/devices/gamepad/GamepadCoordinates.js +17 -17
- package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -323
- package/src/engine/input/devices/gamepad/GamepadHandle.js +286 -286
- package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -160
- package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +31 -31
- package/src/engine/input/devices/gamepad/GamepadStick.js +169 -169
- package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -119
- package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -250
- package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -144
- package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -61
- package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -30
- package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -2124
- package/src/engine/input/devices/mouse/MouseButtons.js +22 -22
- package/src/engine/input/ecs/components/Input.js +118 -118
- package/src/engine/input/ecs/components/InputController.js +78 -78
- package/src/engine/input/ecs/ism/ISM_DESIGN.md +1523 -1523
- package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +627 -627
- package/src/engine/input/ecs/ism/InputActionEvent.js +40 -40
- package/src/engine/input/ecs/ism/InputBinding.js +71 -71
- package/src/engine/input/ecs/ism/InputCoordinateKind.js +25 -25
- package/src/engine/input/ecs/ism/InputCoordinateSource.js +86 -86
- package/src/engine/input/ecs/ism/InputDirection.js +33 -33
- package/src/engine/input/ecs/ism/InputMapProgram.js +236 -236
- package/src/engine/input/ecs/ism/InputMapSystem.js +976 -976
- package/src/engine/input/ecs/ism/InputOcclusion.js +31 -31
- package/src/engine/input/ecs/ism/InputSource.js +67 -67
- package/src/engine/input/ecs/ism/InputSourceTable.js +107 -107
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +93 -93
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannelType.js +11 -11
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +153 -153
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.js +160 -160
- package/src/engine/input/ecs/ism/device/GAMEPAD_INPUT_DEVICE_ID.js +9 -9
- package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.js +88 -88
- package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +72 -72
- package/src/engine/input/ecs/ism/device/KEYBOARD_INPUT_DEVICE_ID.js +9 -9
- package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.js +63 -63
- package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.js +55 -55
- package/src/engine/input/ecs/ism/device/POINTER_INPUT_DEVICE_ID.js +9 -9
- package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.js +160 -160
- package/src/engine/input/ecs/ism/map/InputBinding.js +88 -88
- package/src/engine/input/ecs/ism/map/InputCoordinateBinding.js +102 -102
- package/src/engine/input/ecs/ism/map/InputLayer.js +220 -220
- package/src/engine/input/ecs/ism/map/InputMap.js +331 -331
- package/src/engine/input/ecs/ism/prototypeInputMap.js +309 -309
- package/src/engine/input/ecs/ism/trigger/InputTrigger.js +148 -148
- package/src/engine/input/ecs/ism/trigger/InputTriggerChord.js +173 -173
- package/src/engine/input/ecs/ism/trigger/InputTriggerGamepadButton.js +37 -37
- package/src/engine/input/ecs/ism/trigger/InputTriggerHold.js +176 -176
- package/src/engine/input/ecs/ism/trigger/InputTriggerKey.js +39 -39
- package/src/engine/input/ecs/ism/trigger/InputTriggerMouseButton.js +38 -38
- package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.js +120 -120
- package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.js +315 -315
- package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +105 -105
- package/src/engine/input/ecs/ism/trigger/InputTriggerType.js +17 -17
- package/src/engine/input/ecs/systems/InputControllerSystem.js +253 -253
- package/src/engine/input/ecs/systems/InputSystem.js +162 -162
- package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.js +25 -25
- package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.js +26 -26
- package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.js +55 -55
- package/src/engine/interpolation/BinaryInterpolationAdapter.js +56 -56
- package/src/engine/interpolation/Interpoland.js +49 -49
- package/src/engine/interpolation/Interpolated.js +149 -149
- package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -61
- package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -57
- package/src/engine/interpolation/pose_interpoland.js +27 -27
- package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
- package/src/engine/navigation/mesh/NavigationMesh.js +145 -145
- package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +661 -0
- package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +229 -229
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +628 -520
- package/src/engine/network/CONGESTION_CONTROL.md +87 -87
- package/src/engine/network/README.md +52 -52
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +43 -43
- package/src/engine/network/adapters/TransformInterpolationAdapter.js +84 -84
- package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -87
- package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -46
- package/src/engine/network/convertPathToURL.js +2 -2
- package/src/engine/network/core/MalformedPacketError.js +26 -26
- package/src/engine/network/core/quantize/quantize_float.js +73 -73
- package/src/engine/network/core/quantize/quantize_position.js +65 -65
- package/src/engine/network/core/sequence/ack_bitfield.js +77 -77
- package/src/engine/network/core/sequence/seq16.js +69 -69
- package/src/engine/network/core/sequence/seq32.js +73 -73
- package/src/engine/network/ecs/NetworkSystem.js +84 -84
- package/src/engine/network/ecs/components/NetworkIdentity.js +73 -73
- package/src/engine/network/ecs/owner_authorization.js +55 -55
- package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -64
- package/src/engine/network/replication/ScopeFilter.js +71 -71
- package/src/engine/network/sim/ActionLog.js +152 -152
- package/src/engine/network/sim/SimAction.js +273 -273
- package/src/engine/network/sim/Snapshotter.js +222 -222
- package/src/engine/network/state/FrameBufferRing.js +168 -168
- package/src/engine/network/state/InputRing.js +134 -134
- package/src/engine/network/state/PriorityAccumulator.js +180 -180
- package/src/engine/network/time/TimeDilation.js +62 -62
- package/src/engine/network/transport/Channel.js +336 -336
- package/src/engine/network/transport/Transport.js +203 -203
- package/src/engine/network/transport/adapters/NodeUDPTransport.js +186 -186
- package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +105 -105
- package/src/engine/network/transport/fragments/fragment_send.js +147 -147
- package/src/engine/network/transport/fragments/packet_size.js +101 -101
- package/src/engine/network/xhr.js +23 -23
- package/src/engine/physics/BULLET_REVIEW.md +945 -945
- package/src/engine/physics/CANNON_REVIEW.md +1300 -1300
- package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -208
- package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -364
- package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -287
- package/src/engine/physics/JOLT_REVIEW.md +913 -913
- package/src/engine/physics/PLAN.md +1120 -1094
- package/src/engine/physics/RAPIER_REVIEW.md +934 -934
- package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -642
- package/src/engine/physics/REVIEW_002.md +151 -151
- package/src/engine/physics/REVIEW_003.md +166 -166
- package/src/engine/physics/broadphase/compute_fat_world_aabb.js +97 -97
- package/src/engine/physics/constraint/DofMode.js +35 -35
- package/src/engine/physics/constraint/solve_constraints.js +1 -1
- package/src/engine/physics/contact/combine_material.js +35 -35
- package/src/engine/physics/ecs/BodyKind.js +29 -29
- package/src/engine/physics/ecs/Collider.js +177 -177
- package/src/engine/physics/ecs/ColliderFlags.js +15 -15
- package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -37
- package/src/engine/physics/ecs/Joint.js +475 -475
- package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -72
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +3 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +7 -2
- package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -37
- package/src/engine/physics/ecs/SleepState.js +12 -12
- package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -76
- package/src/engine/physics/fluid/FluidField.js +867 -867
- package/src/engine/physics/fluid/FluidSimulator.js +832 -832
- package/src/engine/physics/fluid/REVIEW_02_PLAN.md +243 -243
- package/src/engine/physics/fluid/SliceVisualiser.js +190 -190
- package/src/engine/physics/fluid/ecs/FluidComponent.js +239 -239
- package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -89
- package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -97
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -348
- package/src/engine/physics/fluid/ecs/FluidSystem.js +142 -142
- package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -123
- package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -395
- package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -12
- package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -32
- package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -47
- package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -85
- package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -16
- package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -111
- package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -18
- package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -81
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +202 -202
- package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +257 -257
- package/src/engine/physics/fluid/effector/WakeFluidEffector.js +405 -405
- package/src/engine/physics/fluid/prototype.js +126 -126
- package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +143 -143
- package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +162 -162
- package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +107 -107
- package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -131
- package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +90 -90
- package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -38
- package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -77
- package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -113
- package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -92
- package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -107
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +135 -135
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +511 -511
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -108
- package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +317 -317
- package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -236
- package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -217
- package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +174 -174
- package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -95
- package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -77
- package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -131
- package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -104
- package/src/engine/physics/inertia/world_inverse_inertia.js +160 -160
- package/src/engine/physics/integration/integrate_position.js +97 -97
- package/src/engine/physics/narrowphase/box_box_manifold.js +681 -681
- package/src/engine/physics/narrowphase/box_triangle_contact.js +906 -906
- package/src/engine/physics/narrowphase/capsule_triangle_contact.js +395 -395
- package/src/engine/physics/narrowphase/compute_penetration.js +369 -369
- package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -55
- package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -37
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
- package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1738
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -74
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +12 -11
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_hit.js +228 -36
- package/src/engine/physics/narrowphase/sphere_sphere_contact.js +52 -52
- package/src/engine/physics/narrowphase/sphere_triangle_contact.js +138 -138
- package/src/engine/physics/persistence/solver_caches.js +309 -309
- package/src/engine/physics/queries/overlap_shape.js +187 -187
- package/src/engine/physics/queries/raycast.d.ts +7 -4
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +11 -5
- package/src/engine/physics/queries/shape_cast.js +417 -417
- package/src/engine/physics/solver/apply_impulse_to_body.js +86 -86
- package/src/engine/physics/solver/friction_cone.js +37 -37
- package/src/engine/physics/solver/solve_contacts.js +1 -1
- package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
- package/src/engine/platform/WebEnginePlatform.js +1 -2
- package/src/engine/scene/SerializedScene.d.ts +2 -2
- package/src/engine/scene/SerializedScene.d.ts.map +1 -1
- package/src/engine/scene/transitionToScene.js +121 -121
- package/src/engine/simulation/Ticker.d.ts.map +1 -1
- package/src/engine/simulation/Ticker.js +33 -0
- package/src/engine/sound/ecs/audio/LiveEmitterSet.js +345 -345
- package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -140
- package/src/engine/sound/simulation/AcousticSimulator.js +646 -646
- package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +432 -432
- package/src/engine/sound/simulation/PROBE_PLACEMENT.md +250 -250
- package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +322 -322
- package/src/engine/sound/simulation/README.md +277 -277
- package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +261 -261
- package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +121 -121
- package/src/engine/sound/simulation/configureAcousticSimulation.js +48 -48
- package/src/engine/sound/simulation/core/AcousticMedium.js +113 -113
- package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.js +27 -27
- package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +312 -312
- package/src/engine/sound/simulation/core/AcousticRegionIndex.js +159 -159
- package/src/engine/sound/simulation/core/AcousticSolution.js +90 -90
- package/src/engine/sound/simulation/core/AcousticSourceState.js +109 -109
- package/src/engine/sound/simulation/core/AcousticVolumeRegion.js +73 -73
- package/src/engine/sound/simulation/core/OcclusionSolver.js +112 -112
- package/src/engine/sound/simulation/core/RayHit.js +50 -50
- package/src/engine/sound/simulation/core/VolumeField.js +149 -149
- package/src/engine/sound/simulation/definition/AcousticDirectivity.js +114 -114
- package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.js +30 -30
- package/src/engine/sound/simulation/definition/AcousticMaterial.js +122 -122
- package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.js +32 -32
- package/src/engine/sound/simulation/ecs/AcousticBody.js +48 -48
- package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.js +34 -34
- package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.js +64 -64
- package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +75 -75
- package/src/engine/sound/simulation/ecs/AcousticVolume.js +74 -74
- package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.js +37 -37
- package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.js +66 -66
- package/src/engine/sound/simulation/probe/AcousticProbeField.js +578 -578
- package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.js +60 -60
- package/src/engine/sound/simulation/probe/ProbeFieldPathingSolution.js +64 -64
- package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +132 -132
- package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +103 -103
- package/src/engine/sound/simulation/probe/acoustic_trace_energy.js +136 -136
- package/src/engine/sound/simulation/probe/bakeProbeField.js +169 -169
- package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +110 -110
- package/src/engine/sound/simulation/probe/bakeReverbBands.js +110 -110
- package/src/engine/sound/simulation/probe/bakeVisibility.js +148 -148
- package/src/engine/sound/simulation/probe/echogram_band_rt60.js +83 -83
- package/src/engine/sound/simulation/probe/probe_delaunay_edges.js +215 -215
- package/src/engine/sound/simulation/probe/probe_densify_portals.js +140 -140
- package/src/engine/sound/simulation/probe/probe_detect_portals.js +196 -196
- package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -38
- package/src/engine/sound/simulation/probe/probe_edge_visible.js +41 -41
- package/src/engine/sound/simulation/probe/probe_place_sdf_cover.js +82 -82
- package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +181 -181
- package/src/engine/sound/simulation/probe/probe_sdf_grid.js +171 -171
- package/src/engine/sound/simulation/probe/probe_visibility_augment.js +146 -146
- package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +167 -167
- package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +354 -354
- package/src/engine/sound/simulation/render/ProbeReverbRenderer.js +231 -231
- package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +105 -105
- package/src/engine/sound/simulation/render/reverbImpulseResponse.js +66 -66
- package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -93
- package/src/engine/sound/simulation/serialization/populateAcousticSerializationRegistry.js +25 -25
- package/src/engine/sound/sopra/README.md +643 -643
- package/src/engine/sound/sopra/SopraEngine.js +432 -432
- package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -71
- package/src/engine/sound/sopra/asset/BufferProvider.js +29 -29
- package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -58
- package/src/engine/sound/sopra/definition/BusDefinition.js +142 -142
- package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -54
- package/src/engine/sound/sopra/definition/DuckingRule.js +106 -106
- package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -31
- package/src/engine/sound/sopra/definition/EventDescription.js +262 -262
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +55 -55
- package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -83
- package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -39
- package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -117
- package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -31
- package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -18
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -109
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +185 -185
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +59 -59
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +128 -128
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +226 -226
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -54
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -191
- package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -39
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +79 -79
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -42
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -77
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -27
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +5 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +125 -125
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -41
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -24
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -120
- package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -31
- package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -128
- package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -29
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +113 -113
- package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -25
- package/src/engine/sound/sopra/runtime/BusGraph.js +231 -231
- package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -98
- package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -68
- package/src/engine/sound/sopra/runtime/Voice.js +145 -145
- package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -136
- package/src/engine/sound/sopra/runtime/VoicePool.js +17 -17
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -42
- package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -99
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -49
- package/src/engine/sound/sopra/util/MockAudioContext.js +224 -224
- package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -60
- package/src/engine/ui/DraggableAspect.js +115 -115
- package/src/engine/ui/apply_pointer_tilt_3d.js +50 -50
- package/src/generation/COORDINATES.md +54 -54
- package/src/generation/GridGenerationResource.js +24 -24
- package/src/generation/GridTaskGroup.js +214 -214
- package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -628
- package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -79
- package/src/generation/automata/CellularAutomata.js +22 -22
- package/src/generation/compute_writes_union.js +25 -25
- package/src/generation/filtering/CellFilter.js +117 -117
- package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -37
- package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -43
- package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -29
- package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -36
- package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -42
- package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -18
- package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -47
- package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -54
- package/src/generation/grid/GridData.js +233 -233
- package/src/generation/grid/coords/grid_to_texel.js +10 -10
- package/src/generation/grid/coords/texel_to_grid.js +10 -10
- package/src/generation/grid/generation/GridTaskApplyActionToCells.js +90 -90
- package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +388 -388
- package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +127 -127
- package/src/generation/grid/generation/GridTaskSequence.js +58 -58
- package/src/generation/grid/generation/NoopGridTaskGenerator.js +12 -12
- package/src/generation/grid/generation/discrete/GridTaskActionRuleSet.js +48 -48
- package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +146 -146
- package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +540 -540
- package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +222 -222
- package/src/generation/grid/generation/grid/GridTaskAddNodesFixed.js +44 -44
- package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +614 -614
- package/src/generation/markers/GridCellActionPlaceMarker.js +244 -244
- package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +86 -86
- package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +266 -266
- package/src/generation/markers/emitter/MarkerNodeEmitterGridCellAction.js +49 -49
- package/src/generation/placement/GridCellActionTransformNearbyMarkers.js +120 -120
- package/src/generation/placement/action/GridCellAction.js +45 -45
- package/src/generation/placement/action/GridCellActionPlaceTags.js +140 -140
- package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +89 -89
- package/src/generation/placement/action/random/CellActionSelectRandom.js +69 -69
- package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +91 -91
- package/src/generation/placement/action/util/GridCellActionSequence.js +61 -61
- package/src/generation/placement/action/util/GridCellDisplacedAction.js +61 -61
- package/src/generation/rules/cell/CellMatcherRemovalPreservesConnectivity.js +183 -183
- package/src/generation/theme/ThemeEngine.js +666 -666
- package/src/shade/RENDERER_CONTRACT.md +568 -0
- package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +267 -267
- package/src/shade/device/ShadeDeviceFailure.js +104 -104
- package/src/shade/device/ShadeDeviceFailureReason.js +41 -41
- package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +255 -255
- package/src/shade/device/mock/REVIEW_2026_08_22.md +527 -527
- package/src/shade/device/mock/SoftwareGPUBindGroup.js +23 -23
- package/src/shade/device/mock/SoftwareGPUBindGroupLayout.js +22 -22
- package/src/shade/device/mock/SoftwareGPUError.js +63 -63
- package/src/shade/device/mock/SoftwareGPUPipeline.js +75 -75
- package/src/shade/device/mock/SoftwareGPUQuerySet.js +113 -113
- package/src/shade/device/mock/SoftwareGPUSampler.js +30 -30
- package/src/shade/device/mock/SoftwareGPUShaderModule.js +32 -32
- package/src/shade/device/mock/SoftwareGPUTextureView.js +22 -22
- package/src/shade/device/mock/SoftwareGPUUncapturedErrorEvent.js +23 -23
- package/src/shade/device/mock/normalize_dynamic_offsets.js +26 -26
- package/src/shade/device/mock/texture_copy.js +287 -287
- package/src/shade/device/timing/GPUTimer.js +285 -285
- package/src/shade/device/timing/GPUTimerArray.js +205 -205
- package/src/shade/playground/add_random_lights.js +49 -49
- package/src/shade/playground/add_random_meshes.js +83 -83
- package/src/shade/playground/animation_prototype.js +508 -508
- package/src/shade/playground/animation_prototype_skinning.js +647 -647
- package/src/shade/playground/animation_prototype_stress.js +765 -765
- package/src/shade/playground/enable_picker.js +59 -59
- package/src/shade/playground/gltf_skinned_prototype.js +560 -560
- package/src/shade/playground/gltf_skinned_stress_prototype.js +387 -387
- package/src/shade/playground/gltf_viewer.js +282 -282
- package/src/shade/playground/load_default_environment.js +19 -19
- package/src/shade/playground/main.js +2727 -2727
- package/src/shade/playground/make_animated_instances.js +117 -117
- package/src/shade/playground/prototype_virtual_texture.js +274 -274
- package/src/shade/playground/prototype_volumetrics_01.js +206 -206
- package/src/shade/playground/sample.js +234 -234
- package/src/shade/playground/sample_load_gltf.js +102 -102
- package/src/shade/playground/spread_node3d_grid.js +19 -19
- package/src/shade/renderer/Renderer.js +2436 -2436
- package/src/shade/renderer/ShadeIndirectLightingMode.js +14 -14
- package/src/shade/renderer/animation/ShadeAnimationChannel.js +414 -414
- package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +531 -531
- package/src/shade/renderer/animation/pose/PosePlayback.js +55 -55
- package/src/shade/renderer/animation/pose/pose_evaluate_local.js +179 -179
- package/src/shade/renderer/animation/pose/pose_evaluate_world.js +76 -76
- package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +1024 -1024
- package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +79 -79
- package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +56 -56
- package/src/shade/renderer/binding/BindGroupManager.js +282 -282
- package/src/shade/renderer/binding/adapt_cache_capacity.js +25 -25
- package/src/shade/renderer/buffer/GPUBufferAllocator.js +116 -116
- package/src/shade/renderer/buffer/buffer_pool_ops.js +123 -123
- package/src/shade/renderer/buffer/table/GPU_DATABASE_PAGE_SIZE.js +9 -9
- package/src/shade/renderer/buffer/table/GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js +8 -8
- package/src/shade/renderer/buffer/table/PageBufferPool.js +71 -71
- package/src/shade/renderer/buffer/table/PageSlotAllocator.js +95 -95
- package/src/shade/renderer/camera/Camera.js +281 -281
- package/src/shade/renderer/camera/GPUCameraContext.js +233 -233
- package/src/shade/renderer/camera/orbital/README.md +84 -84
- package/src/shade/renderer/deferred/shader_composite_volumetrics.js +75 -75
- package/src/shade/renderer/dynamic/DYNAMIC_MESH_VERTEX.js +25 -25
- package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +342 -342
- package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
- package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +170 -170
- package/src/shade/renderer/extension/FrameContext.js +263 -263
- package/src/shade/renderer/extension/FramePhase.js +104 -104
- package/src/shade/renderer/extension/FrameRecord.js +126 -126
- package/src/shade/renderer/extension/GBufferTextures.js +83 -83
- package/src/shade/renderer/extension/PresentTarget.js +34 -34
- package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
- package/src/shade/renderer/extension/RenderExtension.js +69 -69
- package/src/shade/renderer/extension/RenderExtensionRegistry.js +319 -319
- package/src/shade/renderer/extension/SceneColor.js +40 -40
- package/src/shade/renderer/extension/ViewTextures.js +78 -78
- package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +182 -182
- package/src/shade/renderer/geometry/Geometry.js +608 -608
- package/src/shade/renderer/geometry/geometry_build_from_meshlet_geometry.js +342 -342
- package/src/shade/renderer/geometry/meshlet_geometry_transfer.js +41 -41
- package/src/shade/renderer/geometry/primitives/make_box_geometry.js +153 -153
- package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +240 -240
- package/src/shade/renderer/geometry/primitives/make_plane_geometry.js +145 -145
- package/src/shade/renderer/geometry/primitives/make_polyhedron_geometry.js +327 -327
- package/src/shade/renderer/geometry/primitives/make_torus_geometry.js +107 -107
- package/src/shade/renderer/geometry/primitives/make_torus_knot_geometry.js +165 -165
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +607 -607
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +66 -66
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildResult.js +32 -32
- package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.js +103 -103
- package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.js +25 -25
- package/src/shade/renderer/geometry/virtual/build/VGeoLevel.js +33 -33
- package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.js +62 -62
- package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.js +49 -49
- package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.js +20 -20
- package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.js +214 -214
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfByteSource.js +122 -122
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfCollectReport.js +29 -29
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfDocument.js +210 -210
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometry.js +48 -48
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometryInstance.js +25 -25
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometrySelection.js +28 -28
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_collect_geometries.js +296 -296
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +166 -166
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_read_geometry.js +199 -199
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +173 -173
- package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.js +28 -28
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +200 -200
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.js +1786 -1786
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.js +157 -157
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.js +26 -26
- package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.js +75 -75
- package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.js +143 -143
- package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.js +44 -44
- package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.js +41 -41
- package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.js +71 -71
- package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.js +123 -123
- package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.js +26 -26
- package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.js +112 -112
- package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.js +56 -56
- package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.js +70 -70
- package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.js +187 -187
- package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.js +207 -207
- package/src/shade/renderer/geometry/virtual/format/VGEO_DEFECT.js +18 -18
- package/src/shade/renderer/geometry/virtual/format/VGeoDefect.js +38 -38
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +64 -64
- package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTE.js +13 -13
- package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTES.js +50 -50
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_allocate.js +28 -28
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_mask.js +23 -23
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +31 -31
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.js +32 -32
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +46 -46
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +19 -19
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +27 -27
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_asset_id_preimage.js +62 -62
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +20 -20
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CHILD_REF.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_CHILD.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_GROUP.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_SAME_PAGE.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +81 -81
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryGroup.js +37 -37
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryPageNode.js +55 -55
- package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_BLOCK_RECORDS.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/directory/vgeo_directory_block_count.js +12 -12
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_OFFSET.js +11 -11
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +11 -11
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_RAW.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_ALIGNMENT.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_HEADER_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_OFFSET.js +13 -13
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_PAGE_MAGIC.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameBlob.js +21 -21
- package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameExtent.js +59 -59
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +27 -27
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +84 -84
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +56 -56
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +35 -35
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_scan_frames.js +74 -74
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_HASH_ALGO_XXH3.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +31 -31
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_OFFSET.js +20 -20
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_RECORD_SIZE.js +9 -9
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAGIC.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGeoLevelRecord.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_OFFSET.js +21 -21
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FANOUT.js +9 -9
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FLAG_LEAF.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_OFFSET.js +10 -10
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_HEADER_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -16
- package/src/shade/renderer/geometry/virtual/format/payload/VGEO_ENCODING_FLAGS_MASK.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/payload/VGeoEncodedPayload.js +18 -18
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +200 -200
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_attribute_offset.js +39 -39
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_size.js +20 -20
- package/src/shade/renderer/geometry/virtual/format/prototypeVGEOFormatViewer.js +2063 -2063
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +1622 -1622
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +485 -485
- package/src/shade/renderer/gpu_primitive/sort/ffx/graph_radix_sort.js +115 -115
- package/src/shade/renderer/gpu_primitive/sort/one_sweep/verify_radix_sort_one_sweep.js +110 -110
- package/src/shade/renderer/light/environment/octahedral_resample_kernel.js +50 -50
- package/src/shade/renderer/light/environment/octahedral_uv_to_direction.js +30 -30
- package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +157 -157
- package/src/shade/renderer/light/make_sunlight.js +35 -35
- package/src/shade/renderer/light/model/io/light_record_point.js +39 -39
- package/src/shade/renderer/loader/gltf/GLTF_PRIMITIVE_MODE.js +23 -23
- package/src/shade/renderer/loader/gltf/MESHOPT_COMPRESSION_PLAN.md +793 -793
- package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtension.js +54 -54
- package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtensionSet.js +109 -109
- package/src/shade/renderer/loader/gltf/ext/gltf_apply_buffer_view_extensions.js +69 -69
- package/src/shade/renderer/loader/gltf/ext/gltf_buffer_view_extensions.js +22 -22
- package/src/shade/renderer/loader/gltf/ext/gltf_geometry_extensions.js +39 -39
- package/src/shade/renderer/loader/gltf/ext/meshopt/MeshoptBufferViewExtension.js +301 -301
- package/src/shade/renderer/loader/gltf/gltf_component_type.js +223 -223
- package/src/shade/renderer/loader/gltf/gltf_node_world_matrices.js +137 -137
- package/src/shade/renderer/loader/gltf/gltf_parse_container.js +241 -241
- package/src/shade/renderer/loader/gltf/gltf_read_accessor.js +294 -294
- package/src/shade/renderer/loader/gltf/gltf_used_image_indices.js +49 -49
- package/src/shade/renderer/loader/gltf/load_gltf.js +1253 -1253
- package/src/shade/renderer/loader/gltf/tiny-gltf.js +646 -646
- package/src/shade/renderer/loader/usd/UsdError.js +33 -33
- package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +69 -69
- package/src/shade/renderer/loader/usd/load_usd.js +226 -226
- package/src/shade/renderer/loader/usd/parse_usda.js +931 -931
- package/src/shade/renderer/loader/usd/unpack_usdz.js +106 -106
- package/src/shade/renderer/loader/usd/usd_build_scene.js +658 -658
- package/src/shade/renderer/loader/usd/usd_compose_transform.js +258 -258
- package/src/shade/renderer/loader/usd/usd_decode_image.js +384 -384
- package/src/shade/renderer/loader/usd/usd_lz4.js +182 -182
- package/src/shade/renderer/loader/usd/usd_triangulate.js +159 -159
- package/src/shade/renderer/material/ShadeMaterial.d.ts +2 -2
- package/src/shade/renderer/material/ShadeMaterial.js +2 -2
- package/src/shade/renderer/material/standard/fragment_gbuffer.js +334 -334
- package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
- package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +45 -45
- package/src/shade/renderer/material/standard_material_to_struct.js +23 -23
- package/src/shade/renderer/particles/prototypeParticleSystem.js +450 -450
- package/src/shade/renderer/particles/runtime/spawn_request_encoding.js +52 -52
- package/src/shade/renderer/pool/TransientResourcePool.js +392 -392
- package/src/shade/renderer/postprocess/bloom/bloom_chain_resolution.js +29 -29
- package/src/shade/renderer/postprocess/bloom/graph_bloom_downsample.js +109 -109
- package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +155 -155
- package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +114 -114
- package/src/shade/renderer/postprocess/dof/DOF_AUTOFOCUS_PARAMS_STRUCT.js +25 -25
- package/src/shade/renderer/postprocess/dof/DOF_FOCUS_STATE_STRUCT.js +23 -23
- package/src/shade/renderer/postprocess/dof/DOF_PRESETS.js +27 -27
- package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_diameter.js +41 -41
- package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_radius.js +32 -32
- package/src/shade/renderer/postprocess/dof/chunk_dof_coc_coefficient.js +26 -26
- package/src/shade/renderer/postprocess/dof/compute_dof_autofocus_params.js +64 -64
- package/src/shade/renderer/postprocess/dof/compute_dof_uniform.js +54 -54
- package/src/shade/renderer/postprocess/dof/gather/DOF2_CONFIG.js +91 -91
- package/src/shade/renderer/postprocess/dof/gather/DOF2_PRESETS.js +9 -9
- package/src/shade/renderer/postprocess/dof/gather/DOF2_SETTINGS_STRUCT.js +66 -66
- package/src/shade/renderer/postprocess/dof/gather/DOF2_TEXTURE_FORMAT.js +22 -22
- package/src/shade/renderer/postprocess/dof/gather/DepthOfFieldU.js +292 -292
- package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_coc_to_near_far.js +25 -25
- package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_gather_weight.js +30 -30
- package/src/shade/renderer/postprocess/dof/gather/compute_dof2_uniform.js +54 -54
- package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +194 -194
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_fill.js +68 -68
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +140 -140
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +142 -142
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +99 -99
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +102 -102
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_dilate.js +75 -75
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_max.js +73 -73
- package/src/shade/renderer/postprocess/dof/raymarch/DOF_SETTINGS_STRUCT.js +49 -49
- package/src/shade/renderer/postprocess/dof/raymarch/DepthOfField.js +298 -298
- package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +244 -244
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +91 -91
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_firefly_cleanup.js +61 -61
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +90 -90
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +267 -267
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +271 -271
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_dilate.js +70 -70
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_max.js +72 -72
- package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +99 -99
- package/src/shade/renderer/postprocess/gtao/generate_magic_square_noise.js +51 -51
- package/src/shade/renderer/postprocess/motion_blur/MotionBlur.js +56 -56
- package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +78 -78
- package/src/shade/renderer/postprocess/upscale-dither/UpscaleDither.js +95 -95
- package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +204 -204
- package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +58 -58
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +230 -230
- package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +278 -278
- package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.js +26 -26
- package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +235 -235
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +208 -208
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +206 -206
- package/src/shade/renderer/scene/DynamicMesh.js +70 -70
- package/src/shade/renderer/scene/DynamicMeshBatch.js +90 -90
- package/src/shade/renderer/scene/GPUSceneContext.js +901 -901
- package/src/shade/renderer/scene/SceneNodeBinding.js +56 -56
- package/src/shade/renderer/scene/SkinnedMesh.js +55 -55
- package/src/shade/renderer/scene/TransformAuthority.js +33 -33
- package/src/shade/renderer/scene/optimization/deduplicate_geometries.js +38 -38
- package/src/shade/renderer/scene/optimization/deduplicate_images.js +93 -93
- package/src/shade/renderer/scene/optimization/geometry/internal/match_refit_validate.js +127 -127
- package/src/shade/renderer/scene/optimization/geometry/internal/match_solve_transform.js +492 -492
- package/src/shade/renderer/scene/optimization/optimize_texture_set.js +81 -81
- package/src/shade/renderer/scene/optimization/source_to_sampler2d.js +21 -21
- package/src/shade/renderer/scene/serialization/STANDARD_MATERIAL_SERIALIZED_BYTE_SIZE.js +12 -12
- package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js +26 -26
- package/src/shade/renderer/shader/chunk/noise/chunk_interleaved_gradient_noise.js +20 -20
- package/src/shade/renderer/shader/chunk/spherical_harmonics/dering/chunk_sh2_dering_optimize_positive.js +61 -61
- package/src/shade/renderer/shader/chunk/texture/chunk_texture_filter_fxaa_lite.js +225 -225
- package/src/shade/renderer/shader/chunk/texture/dither/chunk_dither_color_8bit_gradient_noise.js +25 -25
- package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +384 -384
- package/src/shade/renderer/shadow/map/PointLightShadowmap.js +105 -105
- package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +63 -63
- package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +89 -89
- package/src/shade/renderer/shadow/map/shadowmap_update_score.js +232 -232
- package/src/shade/renderer/texture/GPUTextureAllocator.js +85 -85
- package/src/shade/renderer/texture/texture_pool_ops.js +171 -171
- package/src/shade/renderer/texture/virtual/PageTableQuadAllocator.js +253 -253
- package/src/shade/renderer/texture/virtual/README.md +276 -276
- package/src/shade/renderer/texture/virtual/VTPageTable.js +330 -330
- package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +349 -349
- package/src/shade/renderer/texture/virtual/VT_CONSTANTS.js +154 -154
- package/src/shade/renderer/texture/virtual/VT_STACK_INFO_STRUCT.js +20 -20
- package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +1276 -1276
- package/src/shade/renderer/texture/virtual/VirtualTextureStack.js +106 -106
- package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +150 -150
- package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +232 -232
- package/src/shade/renderer/texture/virtual/chunk_vt_request_key.js +28 -28
- package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +207 -207
- package/src/shade/renderer/texture/virtual/source/VTSourceProcedural.js +135 -135
- package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +132 -132
- package/src/shade/renderer/water/GPUWaterRenderer.js +167 -167
- package/src/shade/renderer/water/shader_water.js +246 -246
- package/src/shade/renderer/water/water_height_texture_from_sampler.js +56 -56
- package/src/shade/vitest.setup.mjs +89 -89
- package/src/shade/wgsl/validate_wgsl_source.js +316 -316
- package/src/view/controller/controls/NumericIntervalControl.js +90 -90
- package/src/view/controller/controls/Vector2Control.js +83 -83
- package/src/view/controller/controls/Vector3Control.js +90 -90
- package/src/view/elements/Group.js +56 -56
- package/src/view/elements/drawCheckers.js +33 -33
- package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
- package/src/view/graphics/WebGLContextFailureView.js +76 -76
- package/src/view/layout/DockPanel.js +51 -51
- package/src/view/layout/SplitView.js +365 -365
- package/src/view/layout/show_popup_menu.js +81 -81
- package/src/view/minimap/MinimapCanvasView.js +411 -411
- package/src/view/minimap/MinimapImageCache.js +88 -88
- package/src/view/minimap/MinimapMarkerCollection.d.ts +0 -1
- package/src/view/minimap/MinimapMarkerCollection.d.ts.map +1 -1
- package/src/view/minimap/MinimapMarkerCollection.js +180 -180
- package/src/view/minimap/dom/MinimapCameraView.js +206 -206
- package/src/view/minimap/minimap_focus_area.js +97 -97
- package/editor/process/symbolic/SocketsSymbolicDisplay.js +0 -192
- package/src/core/geom/3d/topology/struct/binary/io/moved_tests.txt +0 -37
- package/src/engine/ecs/attachment/Attachment.d.ts +0 -40
- package/src/engine/ecs/attachment/Attachment.d.ts.map +0 -1
- package/src/engine/ecs/attachment/Attachment.js +0 -57
- package/src/engine/ecs/attachment/AttachmentBinding.d.ts +0 -28
- package/src/engine/ecs/attachment/AttachmentBinding.d.ts.map +0 -1
- package/src/engine/ecs/attachment/AttachmentBinding.js +0 -41
- package/src/engine/ecs/attachment/AttachmentSystem.d.ts +0 -61
- package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +0 -1
- package/src/engine/ecs/attachment/AttachmentSystem.js +0 -307
- package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts +0 -27
- package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts.map +0 -1
- package/src/engine/ecs/attachment/BoneAttachmentBinding.js +0 -69
- package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts +0 -4
- package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +0 -1
- package/src/engine/ecs/attachment/TransformAttachmentBinding.js +0 -51
- package/src/engine/ecs/sockets/AttachmentSocket.d.ts +0 -32
- package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +0 -1
- package/src/engine/ecs/sockets/AttachmentSocket.js +0 -60
- package/src/engine/ecs/sockets/AttachmentSocketType.d.ts +0 -6
- package/src/engine/ecs/sockets/AttachmentSocketType.d.ts.map +0 -1
- package/src/engine/ecs/sockets/AttachmentSocketType.js +0 -8
- package/src/engine/ecs/sockets/AttachmentSockets.d.ts +0 -57
- package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +0 -1
- package/src/engine/ecs/sockets/AttachmentSockets.js +0 -130
- package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts +0 -29
- package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +0 -1
- package/src/engine/ecs/sockets/BoneAttachmentSocket.js +0 -56
- package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +0 -6
- package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +0 -1
- package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +0 -19
- package/src/shade/renderer/animation/_dump_resolve.d.mts +0 -2
- package/src/shade/renderer/animation/_dump_resolve.d.mts.map +0 -1
- package/src/shade/renderer/animation/_dump_resolve.mjs +0 -5
|
@@ -1,1622 +1,1622 @@
|
|
|
1
|
-
import { BinaryBuffer } from "../../../../../core/binary/BinaryBuffer.js";
|
|
2
|
-
import { round_up } from "../../../../../core/math/round_up.js";
|
|
3
|
-
import { VGEO_CHILD_TABLE_ENTRY_SIZE } from "./frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js";
|
|
4
|
-
import { VGEO_CLUSTER_RECORD_SIZE } from "./cluster/VGEO_CLUSTER_RECORD_SIZE.js";
|
|
5
|
-
import { VGEO_CODEC_LZ4 } from "./frame/VGEO_CODEC_LZ4.js";
|
|
6
|
-
import { VGEO_CODEC_RAW } from "./frame/VGEO_CODEC_RAW.js";
|
|
7
|
-
import { vgeo_compute_asset_id } from "./checksum/vgeo_compute_asset_id.js";
|
|
8
|
-
import { vgeo_compute_directory_block_checksum } from "./checksum/vgeo_compute_directory_block_checksum.js";
|
|
9
|
-
import { vgeo_compute_frame_checksum } from "./checksum/vgeo_compute_frame_checksum.js";
|
|
10
|
-
import { vgeo_compute_header_checksum } from "./checksum/vgeo_compute_header_checksum.js";
|
|
11
|
-
import { VGEO_DEFECT } from "./VGEO_DEFECT.js";
|
|
12
|
-
import { vgeo_directory_block_count } from "./directory/vgeo_directory_block_count.js";
|
|
13
|
-
import { VGEO_DIRECTORY_FLAG_ROOT_PAGE } from "./directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js";
|
|
14
|
-
import { VGEO_DIRECTORY_RECORD_SIZE } from "./directory/VGEO_DIRECTORY_RECORD_SIZE.js";
|
|
15
|
-
import { vgeo_decode_frame_blob } from "./frame/vgeo_decode_frame_blob.js";
|
|
16
|
-
import { VGEO_ENCODING_FLAGS_MASK } from "./payload/VGEO_ENCODING_FLAGS_MASK.js";
|
|
17
|
-
import { VGEO_FORMAT_VERSION } from "./header/VGEO_FORMAT_VERSION.js";
|
|
18
|
-
import { VGEO_FRAME_ALIGNMENT } from "./frame/VGEO_FRAME_ALIGNMENT.js";
|
|
19
|
-
import { vgeo_frame_decode_bound } from "./frame/vgeo_frame_decode_bound.js";
|
|
20
|
-
import { VGEO_FRAME_HEADER_SIZE } from "./frame/VGEO_FRAME_HEADER_SIZE.js";
|
|
21
|
-
import { VGEO_FRAME_OFFSET } from "./frame/VGEO_FRAME_OFFSET.js";
|
|
22
|
-
import { VGEO_GROUP_RECORD_SIZE } from "./page/VGEO_GROUP_RECORD_SIZE.js";
|
|
23
|
-
import { VGEO_HASH_ALGO_XXH3 } from "./header/VGEO_HASH_ALGO_XXH3.js";
|
|
24
|
-
import { VGEO_HEADER_OFFSET } from "./header/VGEO_HEADER_OFFSET.js";
|
|
25
|
-
import { VGEO_HEADER_SIZE } from "./header/VGEO_HEADER_SIZE.js";
|
|
26
|
-
import { VGEO_LEVEL_RECORD_SIZE } from "./header/VGEO_LEVEL_RECORD_SIZE.js";
|
|
27
|
-
import { VGEO_MAGIC } from "./header/VGEO_MAGIC.js";
|
|
28
|
-
import { VGEO_MAX_CHILD_REF } from "./cluster/VGEO_MAX_CHILD_REF.js";
|
|
29
|
-
import { VGEO_MAX_CLUSTER_TRIANGLE_COUNT } from "./cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js";
|
|
30
|
-
import { VGEO_MAX_CLUSTER_VERTEX_COUNT } from "./cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js";
|
|
31
|
-
import { VGEO_MAX_GROUP_CLUSTER_COUNT } from "./page/VGEO_MAX_GROUP_CLUSTER_COUNT.js";
|
|
32
|
-
import { VGEO_MIN_READER_VERSION } from "./header/VGEO_MIN_READER_VERSION.js";
|
|
33
|
-
import { VGEO_NO_CHILD } from "./cluster/VGEO_NO_CHILD.js";
|
|
34
|
-
import { VGEO_NO_GROUP } from "./cluster/VGEO_NO_GROUP.js";
|
|
35
|
-
import { VGEO_NODE_FANOUT } from "./page/VGEO_NODE_FANOUT.js";
|
|
36
|
-
import { VGEO_NODE_FLAG_LEAF } from "./page/VGEO_NODE_FLAG_LEAF.js";
|
|
37
|
-
import { VGEO_NODE_OFFSET } from "./page/VGEO_NODE_OFFSET.js";
|
|
38
|
-
import { VGEO_NODE_RECORD_SIZE } from "./page/VGEO_NODE_RECORD_SIZE.js";
|
|
39
|
-
import { VGEO_PAGE_HEADER_SIZE } from "./page/VGEO_PAGE_HEADER_SIZE.js";
|
|
40
|
-
import { VGEO_PAGE_MAGIC } from "./frame/VGEO_PAGE_MAGIC.js";
|
|
41
|
-
import { VGEO_PAGE_OFFSET } from "./page/VGEO_PAGE_OFFSET.js";
|
|
42
|
-
import { vgeo_payload_attribute_offset } from "./payload/vgeo_payload_attribute_offset.js";
|
|
43
|
-
import { VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1 } from "./header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js";
|
|
44
|
-
import { vgeo_payload_size } from "./payload/vgeo_payload_size.js";
|
|
45
|
-
import { VGEO_SAME_PAGE } from "./cluster/VGEO_SAME_PAGE.js";
|
|
46
|
-
import { vgeo_scan_frames } from "./frame/vgeo_scan_frames.js";
|
|
47
|
-
import { VGeoDefect } from "./VGeoDefect.js";
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* A full audit of a `.vgeo` file against VGEO_FORMAT §11 — every build invariant, every checksum,
|
|
51
|
-
* and the directory checked against what a frame scan reconstructs.
|
|
52
|
-
*
|
|
53
|
-
* It reads nothing but the bytes it is given: no builder state, no side channel. That is the point —
|
|
54
|
-
* a file that passes here is one a reader can trust, whoever produced it.
|
|
55
|
-
*
|
|
56
|
-
* @author Alex Goldring
|
|
57
|
-
* @copyright Company Named Limited (c) 2026
|
|
58
|
-
*/
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* A page's decoded form. Under codec 0 the decoded page is the frame's own bytes, so this holds the
|
|
62
|
-
* absolute offset of its first byte rather than a copy; a compressing codec decodes into its own
|
|
63
|
-
* buffer and `base` is 0 there. Everything downstream reads through {@link buffer} and {@link seek},
|
|
64
|
-
* so which of the two it was never reaches a check.
|
|
65
|
-
*/
|
|
66
|
-
class DecodedPage {
|
|
67
|
-
/**
|
|
68
|
-
* @type {number}
|
|
69
|
-
*/
|
|
70
|
-
page_index = 0;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* @type {VGeoFrameExtent}
|
|
74
|
-
*/
|
|
75
|
-
frame = null;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Where the decoded page's byte 0 sits in {@link buffer}.
|
|
79
|
-
* @type {number}
|
|
80
|
-
*/
|
|
81
|
-
base = 0;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @type {BinaryBuffer}
|
|
85
|
-
*/
|
|
86
|
-
buffer = null;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* @type {number}
|
|
90
|
-
*/
|
|
91
|
-
size = 0;
|
|
92
|
-
|
|
93
|
-
group_count = 0;
|
|
94
|
-
cluster_count = 0;
|
|
95
|
-
min_level = 0;
|
|
96
|
-
max_level = 0;
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* @type {number}
|
|
100
|
-
*/
|
|
101
|
-
node_count = 0;
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* @type {number}
|
|
105
|
-
*/
|
|
106
|
-
node_table_offset = 0;
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* @type {VGeoMetric}
|
|
110
|
-
*/
|
|
111
|
-
metric = null;
|
|
112
|
-
cluster_table_offset = 0;
|
|
113
|
-
payload_offset = 0;
|
|
114
|
-
payload_size = 0;
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* `[page_index, ...]` in child-table order.
|
|
118
|
-
* @type {number[]}
|
|
119
|
-
*/
|
|
120
|
-
child_pages = [];
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Position `buffer` at a page-relative offset.
|
|
124
|
-
* @param {number} offset
|
|
125
|
-
*/
|
|
126
|
-
seek(offset) {
|
|
127
|
-
this.buffer.position = this.base + offset;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* @param {BinaryBuffer} buffer positioned at the sphere
|
|
133
|
-
* @returns {number[]} `[x, y, z, radius]`
|
|
134
|
-
*/
|
|
135
|
-
function read_sphere(buffer) {
|
|
136
|
-
const result = new Float32Array(4);
|
|
137
|
-
|
|
138
|
-
buffer.readFloat32Array(result, 0, 4);
|
|
139
|
-
|
|
140
|
-
return Array.from(result);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* @param {BinaryBuffer} buffer positioned at the box
|
|
145
|
-
* @returns {number[]} `[min_x, min_y, min_z, max_x, max_y, max_z]`
|
|
146
|
-
*/
|
|
147
|
-
function read_aabb(buffer) {
|
|
148
|
-
const result = new Float32Array(6);
|
|
149
|
-
|
|
150
|
-
buffer.readFloat32Array(result, 0, 6);
|
|
151
|
-
|
|
152
|
-
return Array.from(result);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* @param {number[]} outer
|
|
157
|
-
* @param {number[]} inner
|
|
158
|
-
* @returns {boolean} whether `outer` contains `inner`, evaluated on the stored values exactly
|
|
159
|
-
*/
|
|
160
|
-
function sphere_contains_sphere(outer, inner) {
|
|
161
|
-
const dx = inner[0] - outer[0];
|
|
162
|
-
const dy = inner[1] - outer[1];
|
|
163
|
-
const dz = inner[2] - outer[2];
|
|
164
|
-
|
|
165
|
-
return Math.sqrt(dx * dx + dy * dy + dz * dz) + inner[3] <= outer[3];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* @param {number[]} sphere
|
|
170
|
-
* @param {number} x
|
|
171
|
-
* @param {number} y
|
|
172
|
-
* @param {number} z
|
|
173
|
-
* @returns {boolean}
|
|
174
|
-
*/
|
|
175
|
-
function sphere_contains_point(sphere, x, y, z) {
|
|
176
|
-
const dx = x - sphere[0];
|
|
177
|
-
const dy = y - sphere[1];
|
|
178
|
-
const dz = z - sphere[2];
|
|
179
|
-
|
|
180
|
-
return Math.sqrt(dx * dx + dy * dy + dz * dz) <= sphere[3];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* @param {BinaryBuffer} buffer whole file
|
|
185
|
-
* @param {VGeoDefect[]} out
|
|
186
|
-
* @returns {boolean} whether the header is sane enough for the rest of the audit to proceed
|
|
187
|
-
*/
|
|
188
|
-
function check_header(buffer, out) {
|
|
189
|
-
if (buffer.capacity < VGEO_HEADER_SIZE) {
|
|
190
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `file is ${buffer.capacity} bytes, shorter than the ${VGEO_HEADER_SIZE} byte header`));
|
|
191
|
-
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
let usable = true;
|
|
196
|
-
|
|
197
|
-
buffer.position = 0;
|
|
198
|
-
|
|
199
|
-
const magic = buffer.readUint32();
|
|
200
|
-
|
|
201
|
-
if (magic !== VGEO_MAGIC) {
|
|
202
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `magic is 0x${magic.toString(16)}, expected 0x${VGEO_MAGIC.toString(16)}`));
|
|
203
|
-
|
|
204
|
-
usable = false;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const format_version = buffer.readUint16();
|
|
208
|
-
|
|
209
|
-
if (format_version !== VGEO_FORMAT_VERSION) {
|
|
210
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `format_version is ${format_version}, this validator knows ${VGEO_FORMAT_VERSION}`));
|
|
211
|
-
|
|
212
|
-
usable = false;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const min_reader_version = buffer.readUint16();
|
|
216
|
-
|
|
217
|
-
if (min_reader_version !== VGEO_MIN_READER_VERSION) {
|
|
218
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `min_reader_version is ${min_reader_version}, expected ${VGEO_MIN_READER_VERSION}`));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const header_size = buffer.readUint16();
|
|
222
|
-
|
|
223
|
-
if (header_size !== VGEO_HEADER_SIZE) {
|
|
224
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `header_size is ${header_size}, expected ${VGEO_HEADER_SIZE}`));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const hash_algo = buffer.readUint8();
|
|
228
|
-
|
|
229
|
-
if (hash_algo !== VGEO_HASH_ALGO_XXH3) {
|
|
230
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `hash_algo is ${hash_algo}, expected ${VGEO_HASH_ALGO_XXH3}`));
|
|
231
|
-
|
|
232
|
-
usable = false;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (buffer.readUint8() !== 0) {
|
|
236
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'reserved byte at 11 is not zero'));
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const feature_flags_required = buffer.readUint32();
|
|
240
|
-
|
|
241
|
-
if (feature_flags_required !== 0) {
|
|
242
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `feature_flags_required is 0x${feature_flags_required.toString(16)}, no bit is defined`));
|
|
243
|
-
|
|
244
|
-
usable = false;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
buffer.position = VGEO_HEADER_OFFSET.PAYLOAD_ENCODING;
|
|
248
|
-
|
|
249
|
-
const payload_encoding = buffer.readUint32();
|
|
250
|
-
|
|
251
|
-
if (payload_encoding !== VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1) {
|
|
252
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `payload_encoding is ${payload_encoding}, expected ${VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1}`));
|
|
253
|
-
|
|
254
|
-
usable = false;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
buffer.position = VGEO_HEADER_OFFSET.TOTAL_PAGES;
|
|
258
|
-
|
|
259
|
-
const total_pages = buffer.readUint32();
|
|
260
|
-
|
|
261
|
-
if (total_pages < 1) {
|
|
262
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'total_pages is 0'));
|
|
263
|
-
|
|
264
|
-
usable = false;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const total_clusters = Number(buffer.readUint64());
|
|
268
|
-
|
|
269
|
-
if (total_clusters < 1) {
|
|
270
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'total_clusters is 0'));
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const total_groups = Number(buffer.readUint64());
|
|
274
|
-
|
|
275
|
-
if (total_groups < 1) {
|
|
276
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'total_groups is 0'));
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const root_page_offset = Number(buffer.readUint64());
|
|
280
|
-
|
|
281
|
-
buffer.position = VGEO_HEADER_OFFSET.DIRECTORY_OFFSET;
|
|
282
|
-
|
|
283
|
-
const directory_offset = Number(buffer.readUint64());
|
|
284
|
-
|
|
285
|
-
if (directory_offset % VGEO_FRAME_ALIGNMENT !== 0) {
|
|
286
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `directory_offset ${directory_offset} is not ${VGEO_FRAME_ALIGNMENT}-aligned`));
|
|
287
|
-
|
|
288
|
-
usable = false;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (directory_offset <= VGEO_HEADER_SIZE || directory_offset > buffer.capacity) {
|
|
292
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `directory_offset ${directory_offset} is outside the file`));
|
|
293
|
-
|
|
294
|
-
return false;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const dir_block_checksums_offset = Number(buffer.readUint64());
|
|
298
|
-
|
|
299
|
-
const expected_dir_block_offset = directory_offset + total_pages * VGEO_DIRECTORY_RECORD_SIZE;
|
|
300
|
-
|
|
301
|
-
if (dir_block_checksums_offset !== expected_dir_block_offset) {
|
|
302
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `dir_block_checksums_offset is ${dir_block_checksums_offset}, expected ${expected_dir_block_offset}`));
|
|
303
|
-
|
|
304
|
-
usable = false;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const expected_length = dir_block_checksums_offset + vgeo_directory_block_count(total_pages) * 8;
|
|
308
|
-
|
|
309
|
-
if (usable && buffer.capacity !== expected_length) {
|
|
310
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `file is ${buffer.capacity} bytes, the header describes ${expected_length}`));
|
|
311
|
-
|
|
312
|
-
usable = false;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
buffer.position = 100;
|
|
316
|
-
|
|
317
|
-
if (buffer.readUint16() !== 0) {
|
|
318
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'reserved u16 at 100 is not zero'));
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const level_count = buffer.readUint8();
|
|
322
|
-
|
|
323
|
-
if (level_count < 1) {
|
|
324
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'level_count is 0'));
|
|
325
|
-
|
|
326
|
-
usable = false;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// The frame region begins after the level table, not at a fixed offset — which is what keeps
|
|
330
|
-
// the header, the whole table and the always-resident root page in one contiguous head fetch.
|
|
331
|
-
const expected_root_page_offset = round_up(
|
|
332
|
-
VGEO_HEADER_SIZE + level_count * VGEO_LEVEL_RECORD_SIZE, VGEO_FRAME_ALIGNMENT
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
if (root_page_offset !== expected_root_page_offset) {
|
|
336
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `root_page_offset is ${root_page_offset}; ${level_count} levels of level table put it at ${expected_root_page_offset}`));
|
|
337
|
-
|
|
338
|
-
usable = false;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (buffer.readUint8() !== 0) {
|
|
342
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'reserved byte at 103 is not zero'));
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const source_vertex_count = Number(buffer.readUint64());
|
|
346
|
-
|
|
347
|
-
if (source_vertex_count < 3) {
|
|
348
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `source_vertex_count is ${source_vertex_count}, must be at least 3`));
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const source_face_count = Number(buffer.readUint64());
|
|
352
|
-
|
|
353
|
-
if (source_face_count < 1) {
|
|
354
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `source_face_count is ${source_face_count}, must be at least 1`));
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const object_sphere = read_sphere(buffer);
|
|
358
|
-
|
|
359
|
-
if (!object_sphere.every(Number.isFinite) || object_sphere[3] < 0) {
|
|
360
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `object_sphere is not a finite non-negative sphere: ${object_sphere}`));
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const object_bounds = read_aabb(buffer);
|
|
364
|
-
|
|
365
|
-
if (!object_bounds.every(Number.isFinite)) {
|
|
366
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `object_bounds is not finite: ${object_bounds}`));
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
for (let axis = 0; axis < 3; axis++) {
|
|
370
|
-
if (object_bounds[axis] > object_bounds[axis + 3]) {
|
|
371
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `object_bounds axis ${axis} has min > max`));
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
for (let offset = 160; offset < VGEO_HEADER_OFFSET.HEADER_CHECKSUM; offset++) {
|
|
376
|
-
if (buffer.raw_bytes[offset] !== 0) {
|
|
377
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `reserved byte at ${offset} is not zero`));
|
|
378
|
-
|
|
379
|
-
break;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return usable;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* @param {BinaryBuffer} buffer whole file
|
|
388
|
-
* @param {VGeoDefect[]} out
|
|
389
|
-
*/
|
|
390
|
-
function check_hashes(buffer, out) {
|
|
391
|
-
buffer.position = VGEO_HEADER_OFFSET.HEADER_CHECKSUM;
|
|
392
|
-
|
|
393
|
-
const stored_header_checksum = buffer.readUint64();
|
|
394
|
-
const computed_header_checksum = vgeo_compute_header_checksum(buffer);
|
|
395
|
-
|
|
396
|
-
if (stored_header_checksum !== computed_header_checksum) {
|
|
397
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHECKSUM, `header_checksum is ${stored_header_checksum.toString(16)}, computed ${computed_header_checksum.toString(16)}`));
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
buffer.position = VGEO_HEADER_OFFSET.ASSET_ID;
|
|
401
|
-
|
|
402
|
-
const stored_low = buffer.readUint64();
|
|
403
|
-
const stored_high = buffer.readUint64();
|
|
404
|
-
|
|
405
|
-
const computed = vgeo_compute_asset_id(buffer);
|
|
406
|
-
|
|
407
|
-
if (stored_low !== computed.low64 || stored_high !== computed.high64) {
|
|
408
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHECKSUM, `asset_id is ${stored_high.toString(16)}${stored_low.toString(16)}, computed ${computed.toString()}`));
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* @param {BinaryBuffer} buffer whole file
|
|
414
|
-
* @param {VGeoFrameExtent[]} frames
|
|
415
|
-
* @param {VGeoDefect[]} out
|
|
416
|
-
*/
|
|
417
|
-
function check_frames(buffer, frames, out) {
|
|
418
|
-
buffer.position = VGEO_HEADER_OFFSET.TOTAL_PAGES;
|
|
419
|
-
|
|
420
|
-
const total_pages = buffer.readUint32();
|
|
421
|
-
|
|
422
|
-
buffer.position = VGEO_HEADER_OFFSET.DIRECTORY_OFFSET;
|
|
423
|
-
|
|
424
|
-
const directory_offset = Number(buffer.readUint64());
|
|
425
|
-
|
|
426
|
-
if (frames.length !== total_pages) {
|
|
427
|
-
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `frame scan found ${frames.length} frames, header says total_pages = ${total_pages}`));
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (frames.length > 0) {
|
|
431
|
-
const last = frames[frames.length - 1];
|
|
432
|
-
|
|
433
|
-
const end = last.frame_offset + last.fetch_size;
|
|
434
|
-
|
|
435
|
-
if (end !== directory_offset) {
|
|
436
|
-
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `frame region ends at ${end}, directory_offset is ${directory_offset}; the two must coincide exactly`));
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
buffer.position = VGEO_HEADER_OFFSET.ASSET_ID;
|
|
441
|
-
|
|
442
|
-
const asset_id_low = buffer.readUint64();
|
|
443
|
-
|
|
444
|
-
for (const frame of frames) {
|
|
445
|
-
buffer.position = frame.frame_offset;
|
|
446
|
-
|
|
447
|
-
const magic = buffer.readUint32();
|
|
448
|
-
|
|
449
|
-
if (magic !== VGEO_PAGE_MAGIC) {
|
|
450
|
-
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: magic is 0x${magic.toString(16)}, expected 0x${VGEO_PAGE_MAGIC.toString(16)}`));
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const decodable = frame.codec === VGEO_CODEC_RAW || frame.codec === VGEO_CODEC_LZ4;
|
|
454
|
-
|
|
455
|
-
if (!decodable) {
|
|
456
|
-
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: codec is ${frame.codec}, this validator knows ${VGEO_CODEC_RAW} (raw) and ${VGEO_CODEC_LZ4} (lz4)`));
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (frame.flags !== 0) {
|
|
460
|
-
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: frame flags are 0x${frame.flags.toString(16)}, no bit is defined`));
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (frame.codec === VGEO_CODEC_RAW && frame.decoded_size !== frame.stored_size) {
|
|
464
|
-
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: raw frame has stored_size ${frame.stored_size} and decoded_size ${frame.decoded_size}`));
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// A `decoded_size` above what the stored bytes could physically produce is caught here
|
|
468
|
-
// rather than at decode, because a reader allocates against it *before* it decodes — see
|
|
469
|
-
// {@link vgeo_frame_decode_bound}.
|
|
470
|
-
if (decodable && frame.decoded_size > vgeo_frame_decode_bound(frame.codec, frame.stored_size)) {
|
|
471
|
-
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: decoded_size ${frame.decoded_size} is more than codec ${frame.codec} could produce from ${frame.stored_size} stored bytes`));
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (frame.frame_offset % VGEO_FRAME_ALIGNMENT !== 0) {
|
|
475
|
-
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: frame_offset ${frame.frame_offset} is not ${VGEO_FRAME_ALIGNMENT}-aligned`));
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (frame.frame_offset + frame.extent > directory_offset) {
|
|
479
|
-
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: frame runs past directory_offset`));
|
|
480
|
-
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
buffer.position = frame.frame_offset + VGEO_FRAME_OFFSET.CHECKSUM;
|
|
485
|
-
|
|
486
|
-
const stored_checksum = buffer.readUint64();
|
|
487
|
-
const computed_checksum = vgeo_compute_frame_checksum(buffer, frame, asset_id_low);
|
|
488
|
-
|
|
489
|
-
if (stored_checksum !== computed_checksum) {
|
|
490
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHECKSUM, `page ${frame.page_index}: frame checksum is ${stored_checksum.toString(16)}, computed ${computed_checksum.toString(16)}`));
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (frames.length > 0) {
|
|
495
|
-
buffer.position = VGEO_HEADER_OFFSET.ROOT_PAGE_FETCH_SIZE;
|
|
496
|
-
|
|
497
|
-
const root_fetch_size = buffer.readUint32();
|
|
498
|
-
const root_decoded_size = buffer.readUint32();
|
|
499
|
-
|
|
500
|
-
if (root_fetch_size !== frames[0].fetch_size) {
|
|
501
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `root_page_fetch_size is ${root_fetch_size}, the root frame's is ${frames[0].fetch_size}`));
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (root_decoded_size !== frames[0].decoded_size) {
|
|
505
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `root_page_decoded_size is ${root_decoded_size}, the root frame's is ${frames[0].decoded_size}`));
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* Decode one page and check its internal layout.
|
|
512
|
-
*
|
|
513
|
-
* @param {BinaryBuffer} buffer whole file
|
|
514
|
-
* @param {VGeoFrameExtent} frame
|
|
515
|
-
* @param {number} level_count
|
|
516
|
-
* @param {VGeoDefect[]} out
|
|
517
|
-
* @returns {DecodedPage|undefined} `undefined` when the page is too malformed to read further
|
|
518
|
-
*/
|
|
519
|
-
function decode_page(buffer, frame, level_count, out) {
|
|
520
|
-
const page = new DecodedPage();
|
|
521
|
-
|
|
522
|
-
page.page_index = frame.page_index;
|
|
523
|
-
page.frame = frame;
|
|
524
|
-
page.size = frame.decoded_size;
|
|
525
|
-
|
|
526
|
-
const blob = frame.frame_offset
|
|
527
|
-
+ VGEO_FRAME_HEADER_SIZE
|
|
528
|
-
+ VGEO_CHILD_TABLE_ENTRY_SIZE * frame.child_page_count;
|
|
529
|
-
|
|
530
|
-
// Everything below reads the blob, so it has to be in the file first. `check_frames` has
|
|
531
|
-
// already reported a frame that runs past the directory; what this stops is the reading.
|
|
532
|
-
if (blob + frame.stored_size > buffer.capacity) {
|
|
533
|
-
return undefined;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
if (frame.codec === VGEO_CODEC_RAW) {
|
|
537
|
-
// Nothing to decode: the page *is* those bytes. `check_frames` has already reported it if
|
|
538
|
-
// the two sizes disagree, and with them disagreeing the page's own length is not knowable,
|
|
539
|
-
// so there is nothing left to audit.
|
|
540
|
-
if (frame.stored_size !== frame.decoded_size) {
|
|
541
|
-
return undefined;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
page.buffer = buffer;
|
|
545
|
-
page.base = blob;
|
|
546
|
-
} else if (frame.codec === VGEO_CODEC_LZ4) {
|
|
547
|
-
// Bounded by `check_frames`, which has reported it, before it is believed — so a corrupt
|
|
548
|
-
// length field is a defect rather than an allocation.
|
|
549
|
-
if (frame.decoded_size > vgeo_frame_decode_bound(frame.codec, frame.stored_size)) {
|
|
550
|
-
return undefined;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
page.buffer = new BinaryBuffer();
|
|
554
|
-
page.base = 0;
|
|
555
|
-
|
|
556
|
-
page.buffer.setCapacity(frame.decoded_size);
|
|
557
|
-
|
|
558
|
-
try {
|
|
559
|
-
vgeo_decode_frame_blob(page.buffer.raw_bytes, 0, buffer.raw_bytes, frame);
|
|
560
|
-
} catch (error) {
|
|
561
|
-
// Not suppression — the decoder's own message is what says *how* the blob is malformed,
|
|
562
|
-
// and a validator that threw here would be unable to report the one thing it exists to
|
|
563
|
-
// report.
|
|
564
|
-
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: ${error.message}`));
|
|
565
|
-
|
|
566
|
-
return undefined;
|
|
567
|
-
}
|
|
568
|
-
} else {
|
|
569
|
-
// `check_frames` has already named the codec; there is no decoder to go on with.
|
|
570
|
-
return undefined;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
if (page.size < VGEO_PAGE_HEADER_SIZE) {
|
|
574
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: decoded page is ${page.size} bytes, shorter than its header`));
|
|
575
|
-
|
|
576
|
-
return undefined;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Everything from here on is a read of the *decoded* page, which under a compressing codec is
|
|
580
|
-
// not the file — so it goes through the page's own buffer and never the one passed in.
|
|
581
|
-
const decoded = page.buffer;
|
|
582
|
-
|
|
583
|
-
page.seek(0);
|
|
584
|
-
|
|
585
|
-
page.group_count = decoded.readUint16();
|
|
586
|
-
page.cluster_count = decoded.readUint16();
|
|
587
|
-
page.min_level = decoded.readUint8();
|
|
588
|
-
page.max_level = decoded.readUint8();
|
|
589
|
-
|
|
590
|
-
page.node_count = decoded.readUint16();
|
|
591
|
-
|
|
592
|
-
page.cluster_table_offset = decoded.readUint32();
|
|
593
|
-
page.payload_offset = decoded.readUint32();
|
|
594
|
-
page.payload_size = decoded.readUint32();
|
|
595
|
-
page.node_table_offset = decoded.readUint32();
|
|
596
|
-
|
|
597
|
-
for (let offset = 24; offset < VGEO_PAGE_OFFSET.METRIC; offset++) {
|
|
598
|
-
if (decoded.raw_bytes[page.base + offset] !== 0) {
|
|
599
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: reserved byte at ${offset} in the page header is not zero`));
|
|
600
|
-
|
|
601
|
-
break;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
page.seek(VGEO_PAGE_OFFSET.METRIC);
|
|
606
|
-
|
|
607
|
-
page.metric = read_metric(decoded);
|
|
608
|
-
|
|
609
|
-
if (page.group_count < 1) {
|
|
610
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: group_count is 0`));
|
|
611
|
-
|
|
612
|
-
return undefined;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
if (page.cluster_count < 1) {
|
|
616
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: cluster_count is 0`));
|
|
617
|
-
|
|
618
|
-
return undefined;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
if (page.min_level > page.max_level) {
|
|
622
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: min_level ${page.min_level} exceeds max_level ${page.max_level}`));
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
if (page.max_level >= level_count) {
|
|
626
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: max_level ${page.max_level} is outside level_count ${level_count}`));
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// One node per group is the worst the tree can come to — a leaf holds up to the fanout, and
|
|
630
|
-
// every interior node has at least two children — so 2·groups bounds it whatever the shape.
|
|
631
|
-
if (page.node_count < 1 || page.node_count > 2 * page.group_count) {
|
|
632
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: node_count is ${page.node_count}, outside 1..${2 * page.group_count} for ${page.group_count} groups`));
|
|
633
|
-
|
|
634
|
-
return undefined;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const expected_node_table_offset = VGEO_PAGE_HEADER_SIZE + page.group_count * VGEO_GROUP_RECORD_SIZE;
|
|
638
|
-
|
|
639
|
-
if (page.node_table_offset !== expected_node_table_offset) {
|
|
640
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: node_table_offset is ${page.node_table_offset}, expected ${expected_node_table_offset}`));
|
|
641
|
-
|
|
642
|
-
return undefined;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
const expected_cluster_table_offset = page.node_table_offset
|
|
646
|
-
+ page.node_count * VGEO_NODE_RECORD_SIZE;
|
|
647
|
-
|
|
648
|
-
if (page.cluster_table_offset !== expected_cluster_table_offset) {
|
|
649
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: cluster_table_offset is ${page.cluster_table_offset}, expected ${expected_cluster_table_offset}`));
|
|
650
|
-
|
|
651
|
-
return undefined;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
const expected_payload_offset = page.cluster_table_offset + page.cluster_count * VGEO_CLUSTER_RECORD_SIZE;
|
|
655
|
-
|
|
656
|
-
if (page.payload_offset !== expected_payload_offset) {
|
|
657
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: payload_offset is ${page.payload_offset}, expected ${expected_payload_offset}`));
|
|
658
|
-
|
|
659
|
-
return undefined;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
if (page.payload_offset + page.payload_size !== page.size) {
|
|
663
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: payload ends at ${page.payload_offset + page.payload_size}, the decoded page is ${page.size} bytes`));
|
|
664
|
-
|
|
665
|
-
return undefined;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
for (let i = 0; i < frame.child_page_count; i++) {
|
|
669
|
-
buffer.position = frame.frame_offset + VGEO_FRAME_HEADER_SIZE + i * VGEO_CHILD_TABLE_ENTRY_SIZE + 8;
|
|
670
|
-
|
|
671
|
-
page.child_pages.push(buffer.readUint32());
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
return page;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
/**
|
|
678
|
-
* A traversal metric, wherever it is carried.
|
|
679
|
-
*/
|
|
680
|
-
class VGeoMetric {
|
|
681
|
-
/**
|
|
682
|
-
* @type {Float32Array}
|
|
683
|
-
*/
|
|
684
|
-
sphere = new Float32Array(4);
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* @type {number}
|
|
688
|
-
*/
|
|
689
|
-
min_self_error = 0;
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* @type {number}
|
|
693
|
-
*/
|
|
694
|
-
max_parent_error = 0;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
* @param {BinaryBuffer} buffer positioned at the metric
|
|
699
|
-
* @returns {VGeoMetric}
|
|
700
|
-
*/
|
|
701
|
-
function read_metric(buffer) {
|
|
702
|
-
const result = new VGeoMetric();
|
|
703
|
-
|
|
704
|
-
buffer.readFloat32Array(result.sphere, 0, 4);
|
|
705
|
-
|
|
706
|
-
result.min_self_error = buffer.readFloat32();
|
|
707
|
-
result.max_parent_error = buffer.readFloat32();
|
|
708
|
-
|
|
709
|
-
return result;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* Does `outer` contain `inner`? Both are `[centre xyz, radius]`.
|
|
714
|
-
*
|
|
715
|
-
* Slack is a relative epsilon on the radius: the spheres a node aggregates were combined in float32
|
|
716
|
-
* pairwise, and an exact containment test would flag the last bit of that arithmetic as a defect.
|
|
717
|
-
*
|
|
718
|
-
* @param {Float32Array} outer
|
|
719
|
-
* @param {Float32Array} inner
|
|
720
|
-
* @returns {boolean}
|
|
721
|
-
*/
|
|
722
|
-
function sphere_contains(outer, inner) {
|
|
723
|
-
const dx = inner[0] - outer[0];
|
|
724
|
-
const dy = inner[1] - outer[1];
|
|
725
|
-
const dz = inner[2] - outer[2];
|
|
726
|
-
|
|
727
|
-
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
728
|
-
|
|
729
|
-
return distance + inner[3] <= outer[3] * (1 + 1e-4) + 1e-4;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
* A page's group tree: that it is a tree over exactly this page's groups, and that a node's metric
|
|
734
|
-
* really does bound everything under it.
|
|
735
|
-
*
|
|
736
|
-
* The second half is what makes the structure worth anything. A traversal skips a subtree when the
|
|
737
|
-
* node's `max_parent_error` projects to no more than the threshold, and culls one when its sphere
|
|
738
|
-
* misses the frustum; both are only sound if the node's numbers dominate every group beneath it. A
|
|
739
|
-
* node whose sphere is too small silently drops geometry, and nothing downstream would notice.
|
|
740
|
-
*
|
|
741
|
-
* @param {DecodedPage[]} pages
|
|
742
|
-
* @param {object[][]} page_groups
|
|
743
|
-
* @param {object[][]} page_clusters
|
|
744
|
-
* @param {VGeoDefect[]} out
|
|
745
|
-
*/
|
|
746
|
-
function check_page_nodes(pages, page_groups, page_clusters, out) {
|
|
747
|
-
for (const page of pages) {
|
|
748
|
-
const groups = page_groups[page.page_index];
|
|
749
|
-
const clusters = page_clusters[page.page_index];
|
|
750
|
-
|
|
751
|
-
const buffer = page.buffer;
|
|
752
|
-
|
|
753
|
-
const nodes = [];
|
|
754
|
-
|
|
755
|
-
for (let index = 0; index < page.node_count; index++) {
|
|
756
|
-
const base = page.base + page.node_table_offset + index * VGEO_NODE_RECORD_SIZE;
|
|
757
|
-
|
|
758
|
-
buffer.position = base + VGEO_NODE_OFFSET.METRIC;
|
|
759
|
-
|
|
760
|
-
const node = { metric: read_metric(buffer) };
|
|
761
|
-
|
|
762
|
-
buffer.position = base + VGEO_NODE_OFFSET.CHILD_FIRST;
|
|
763
|
-
|
|
764
|
-
node.child_first = buffer.readUint16();
|
|
765
|
-
node.child_count = buffer.readUint16();
|
|
766
|
-
|
|
767
|
-
const flags = buffer.readUint16();
|
|
768
|
-
|
|
769
|
-
node.is_leaf = (flags & VGEO_NODE_FLAG_LEAF) !== 0;
|
|
770
|
-
|
|
771
|
-
if ((flags & ~VGEO_NODE_FLAG_LEAF) !== 0) {
|
|
772
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: unknown flag bits ${flags}`));
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
nodes.push(node);
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* How many times each group is reached through a leaf. Exactly once, or the tree is not a
|
|
780
|
-
* partition of the page and a traversal either misses geometry or draws it twice.
|
|
781
|
-
* @type {Int32Array}
|
|
782
|
-
*/
|
|
783
|
-
const group_hits = new Int32Array(groups.length);
|
|
784
|
-
|
|
785
|
-
let structural = true;
|
|
786
|
-
|
|
787
|
-
nodes.forEach((node, index) => {
|
|
788
|
-
if (node.child_count < 1 || node.child_count > VGEO_NODE_FANOUT) {
|
|
789
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: child_count ${node.child_count} is outside 1..${VGEO_NODE_FANOUT}`));
|
|
790
|
-
|
|
791
|
-
structural = false;
|
|
792
|
-
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
const limit = node.is_leaf ? groups.length : nodes.length;
|
|
797
|
-
|
|
798
|
-
if (node.child_first + node.child_count > limit) {
|
|
799
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: children ${node.child_first}..${node.child_first + node.child_count} run past the ${node.is_leaf ? 'group' : 'node'} table's ${limit}`));
|
|
800
|
-
|
|
801
|
-
structural = false;
|
|
802
|
-
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
if (node.is_leaf) {
|
|
807
|
-
for (let i = 0; i < node.child_count; i++) {
|
|
808
|
-
group_hits[node.child_first + i]++;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
if (node.child_first <= index) {
|
|
815
|
-
// Breadth-first layout: a parent is always allocated before its children. Without
|
|
816
|
-
// it a reader has no bound on the walk and a malformed file can loop it forever.
|
|
817
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: child run starts at ${node.child_first}, which is not after the parent`));
|
|
818
|
-
|
|
819
|
-
structural = false;
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
if (!structural) {
|
|
824
|
-
continue;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
const unreached = group_hits.reduce((count, hits) => count + (hits === 0 ? 1 : 0), 0);
|
|
828
|
-
const repeated = group_hits.reduce((count, hits) => count + (hits > 1 ? 1 : 0), 0);
|
|
829
|
-
|
|
830
|
-
if (unreached !== 0 || repeated !== 0) {
|
|
831
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index}: the node tree reaches ${unreached} of its ${groups.length} groups not at all and ${repeated} more than once`));
|
|
832
|
-
|
|
833
|
-
continue;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
/**
|
|
837
|
-
* @param {number} index
|
|
838
|
-
* @param {VGeoMetric} covered
|
|
839
|
-
* @param {string} what
|
|
840
|
-
*/
|
|
841
|
-
function expect_bounds(index, covered, what) {
|
|
842
|
-
const metric = nodes[index].metric;
|
|
843
|
-
|
|
844
|
-
if (!sphere_contains(metric.sphere, covered.sphere)) {
|
|
845
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: its sphere does not contain ${what}`));
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
if (metric.min_self_error > covered.min_self_error) {
|
|
849
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: min_self_error ${metric.min_self_error} is above ${what}'s ${covered.min_self_error}`));
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
if (metric.max_parent_error < covered.max_parent_error) {
|
|
853
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: max_parent_error ${metric.max_parent_error} is below ${what}'s ${covered.max_parent_error}`));
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
nodes.forEach((node, index) => {
|
|
858
|
-
for (let i = 0; i < node.child_count; i++) {
|
|
859
|
-
if (!node.is_leaf) {
|
|
860
|
-
expect_bounds(index, nodes[node.child_first + i].metric, `node ${node.child_first + i}`);
|
|
861
|
-
|
|
862
|
-
continue;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
const group = groups[node.child_first + i];
|
|
866
|
-
|
|
867
|
-
const covered = new VGeoMetric();
|
|
868
|
-
|
|
869
|
-
covered.min_self_error = Number.POSITIVE_INFINITY;
|
|
870
|
-
covered.max_parent_error = 0;
|
|
871
|
-
|
|
872
|
-
let first = true;
|
|
873
|
-
|
|
874
|
-
for (let c = 0; c < group.cluster_count; c++) {
|
|
875
|
-
const cluster = clusters[group.cluster_first + c];
|
|
876
|
-
|
|
877
|
-
if (cluster === undefined) {
|
|
878
|
-
continue;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
covered.min_self_error = Math.min(covered.min_self_error, cluster.self_error);
|
|
882
|
-
covered.max_parent_error = Math.max(covered.max_parent_error, cluster.parent_error);
|
|
883
|
-
|
|
884
|
-
if (first) {
|
|
885
|
-
covered.sphere.set(cluster.parent_sphere);
|
|
886
|
-
|
|
887
|
-
first = false;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
if (first) {
|
|
892
|
-
continue;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
expect_bounds(index, covered, `group ${node.child_first + i}`);
|
|
896
|
-
}
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
const root = nodes[0];
|
|
900
|
-
|
|
901
|
-
if (
|
|
902
|
-
root !== undefined
|
|
903
|
-
&& (
|
|
904
|
-
!sphere_contains(page.metric.sphere, root.metric.sphere)
|
|
905
|
-
|| page.metric.min_self_error !== root.metric.min_self_error
|
|
906
|
-
|| page.metric.max_parent_error !== root.metric.max_parent_error
|
|
907
|
-
)
|
|
908
|
-
) {
|
|
909
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index}: the page header's metric disagrees with its root node's`));
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
/**
|
|
915
|
-
* @param {BinaryBuffer} buffer whole file
|
|
916
|
-
* @param {VGeoFrameExtent[]} frames
|
|
917
|
-
* @param {DecodedPage[]} pages
|
|
918
|
-
* @param {VGeoDefect[]} out
|
|
919
|
-
*/
|
|
920
|
-
function check_child_tables(buffer, frames, pages, out) {
|
|
921
|
-
for (const page of pages) {
|
|
922
|
-
const frame = page.frame;
|
|
923
|
-
|
|
924
|
-
const seen = new Set();
|
|
925
|
-
|
|
926
|
-
for (let i = 0; i < frame.child_page_count; i++) {
|
|
927
|
-
buffer.position = frame.frame_offset + VGEO_FRAME_HEADER_SIZE + i * VGEO_CHILD_TABLE_ENTRY_SIZE;
|
|
928
|
-
|
|
929
|
-
const child_frame_offset = Number(buffer.readUint64());
|
|
930
|
-
const child_page_index = buffer.readUint32();
|
|
931
|
-
const child_fetch_size = buffer.readUint32();
|
|
932
|
-
const child_decoded_size = buffer.readUint32();
|
|
933
|
-
const reserved = buffer.readUint32();
|
|
934
|
-
|
|
935
|
-
const metric = read_metric(buffer);
|
|
936
|
-
|
|
937
|
-
const child_page = pages.find((candidate) => candidate.page_index === child_page_index);
|
|
938
|
-
|
|
939
|
-
if (child_page !== undefined && child_page.metric !== null) {
|
|
940
|
-
// Copied, so it can be read before the child is fetched — which is the whole point
|
|
941
|
-
// — and therefore able to drift from the truth. Nothing else would catch it: a
|
|
942
|
-
// traversal that trusts a stale metric skips a page it needed and leaves a hole.
|
|
943
|
-
if (
|
|
944
|
-
metric.sphere.some((value, axis) => value !== child_page.metric.sphere[axis])
|
|
945
|
-
|| metric.min_self_error !== child_page.metric.min_self_error
|
|
946
|
-
|| metric.max_parent_error !== child_page.metric.max_parent_error
|
|
947
|
-
) {
|
|
948
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: its metric for page ${child_page_index} disagrees with that page's own`));
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
if (reserved !== 0) {
|
|
953
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: reserved is not zero`));
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
if (seen.has(child_page_index)) {
|
|
957
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index}: child page ${child_page_index} appears more than once; the table must be deduplicated`));
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
seen.add(child_page_index);
|
|
961
|
-
|
|
962
|
-
if (child_page_index >= frames.length) {
|
|
963
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: page_index ${child_page_index} does not exist`));
|
|
964
|
-
|
|
965
|
-
continue;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if (child_page_index === page.page_index) {
|
|
969
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: a page may not name itself; same-page children use the SAME_PAGE sentinel`));
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
const child = frames[child_page_index];
|
|
973
|
-
|
|
974
|
-
if (child_frame_offset !== child.frame_offset) {
|
|
975
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: frame_offset ${child_frame_offset}, the frame is at ${child.frame_offset}`));
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
if (child_frame_offset <= frame.frame_offset) {
|
|
979
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: child frame_offset ${child_frame_offset} is not strictly ahead of ${frame.frame_offset}; file order must be topological`));
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
if (child_fetch_size !== child.fetch_size) {
|
|
983
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: fetch_size ${child_fetch_size}, the frame's is ${child.fetch_size}`));
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
if (child_decoded_size !== child.decoded_size) {
|
|
987
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: decoded_size ${child_decoded_size}, the frame's is ${child.decoded_size}`));
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* @param {DecodedPage} page
|
|
995
|
-
* @param {VGeoDefect[]} out
|
|
996
|
-
* @returns {{cluster_first:number, cluster_count:number, parent_cluster_count:number}[]}
|
|
997
|
-
*/
|
|
998
|
-
function read_groups(page, out) {
|
|
999
|
-
const result = [];
|
|
1000
|
-
|
|
1001
|
-
const buffer = page.buffer;
|
|
1002
|
-
|
|
1003
|
-
let expected_first = 0;
|
|
1004
|
-
|
|
1005
|
-
for (let g = 0; g < page.group_count; g++) {
|
|
1006
|
-
page.seek(VGEO_PAGE_HEADER_SIZE + g * VGEO_GROUP_RECORD_SIZE);
|
|
1007
|
-
|
|
1008
|
-
const group = {
|
|
1009
|
-
cluster_first: buffer.readUint16(),
|
|
1010
|
-
cluster_count: buffer.readUint16(),
|
|
1011
|
-
parent_cluster_count: buffer.readUint16(),
|
|
1012
|
-
};
|
|
1013
|
-
|
|
1014
|
-
if (buffer.readUint16() !== 0) {
|
|
1015
|
-
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index} group ${g}: reserved is not zero`));
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
if (group.cluster_count < 1 || group.cluster_count > VGEO_MAX_GROUP_CLUSTER_COUNT) {
|
|
1019
|
-
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index} group ${g}: cluster_count ${group.cluster_count} is outside [1, ${VGEO_MAX_GROUP_CLUSTER_COUNT}]`));
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
if (group.cluster_first !== expected_first) {
|
|
1023
|
-
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index} group ${g}: cluster_first ${group.cluster_first}, expected ${expected_first}; a page's groups must tile its cluster records in order`));
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
expected_first = group.cluster_first + group.cluster_count;
|
|
1027
|
-
|
|
1028
|
-
result.push(group);
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
if (expected_first !== page.cluster_count) {
|
|
1032
|
-
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index}: groups cover ${expected_first} clusters, the page holds ${page.cluster_count}`));
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
return result;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
/**
|
|
1039
|
-
* Decode a cluster's local positions from its payload — the only way to check that the culling
|
|
1040
|
-
* bounds actually bound anything.
|
|
1041
|
-
*
|
|
1042
|
-
* @param {DecodedPage} page
|
|
1043
|
-
* @param {object} cluster
|
|
1044
|
-
* @returns {Float32Array} `vertex_count * 3` coordinates
|
|
1045
|
-
*/
|
|
1046
|
-
function read_cluster_positions(page, cluster) {
|
|
1047
|
-
page.seek(
|
|
1048
|
-
page.payload_offset
|
|
1049
|
-
+ cluster.payload_offset
|
|
1050
|
-
+ vgeo_payload_attribute_offset(0, cluster.vertex_count, cluster.triangle_count, cluster.encoding_flags)
|
|
1051
|
-
);
|
|
1052
|
-
|
|
1053
|
-
const result = new Float32Array(cluster.vertex_count * 3);
|
|
1054
|
-
|
|
1055
|
-
page.buffer.readFloat32Array(result, 0, result.length);
|
|
1056
|
-
|
|
1057
|
-
return result;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
/**
|
|
1061
|
-
* @param {DecodedPage} page
|
|
1062
|
-
* @param {VGeoDefect[]} out
|
|
1063
|
-
* @returns {object[]}
|
|
1064
|
-
*/
|
|
1065
|
-
function read_clusters(page, out) {
|
|
1066
|
-
const result = [];
|
|
1067
|
-
|
|
1068
|
-
const buffer = page.buffer;
|
|
1069
|
-
|
|
1070
|
-
for (let c = 0; c < page.cluster_count; c++) {
|
|
1071
|
-
page.seek(page.cluster_table_offset + c * VGEO_CLUSTER_RECORD_SIZE);
|
|
1072
|
-
|
|
1073
|
-
const cluster = {
|
|
1074
|
-
index: c,
|
|
1075
|
-
self_sphere: read_sphere(buffer),
|
|
1076
|
-
self_error: buffer.readFloat32(),
|
|
1077
|
-
parent_error: buffer.readFloat32(),
|
|
1078
|
-
parent_sphere: read_sphere(buffer),
|
|
1079
|
-
bounds_sphere: read_sphere(buffer),
|
|
1080
|
-
aabb: read_aabb(buffer),
|
|
1081
|
-
child_ref: buffer.readUint16(),
|
|
1082
|
-
child_group: buffer.readUint16(),
|
|
1083
|
-
cone_reserved: buffer.readUint32(),
|
|
1084
|
-
encoding_flags: buffer.readUint32(),
|
|
1085
|
-
payload_offset: buffer.readUint32(),
|
|
1086
|
-
payload_size: buffer.readUint32(),
|
|
1087
|
-
vertex_count: buffer.readUint8(),
|
|
1088
|
-
triangle_count: buffer.readUint8(),
|
|
1089
|
-
};
|
|
1090
|
-
|
|
1091
|
-
if (buffer.readUint16() !== 0 || buffer.readUint64() !== 0n) {
|
|
1092
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `page ${page.page_index} cluster ${c}: reserved tail is not zero`));
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
if (cluster.cone_reserved !== 0) {
|
|
1096
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `page ${page.page_index} cluster ${c}: cone_reserved is not zero`));
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
result.push(cluster);
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
return result;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
/**
|
|
1106
|
-
* Everything about one cluster that its own record settles: counts, flags, payload extent, bounds
|
|
1107
|
-
* that actually bound, and finite metric values.
|
|
1108
|
-
*
|
|
1109
|
-
* @param {DecodedPage} page
|
|
1110
|
-
* @param {object} cluster
|
|
1111
|
-
* @param {number[]} object_sphere
|
|
1112
|
-
* @param {number[]} object_bounds
|
|
1113
|
-
* @param {VGeoDefect[]} out
|
|
1114
|
-
*/
|
|
1115
|
-
function check_cluster_record(page, cluster, object_sphere, object_bounds, out) {
|
|
1116
|
-
const where = `page ${page.page_index} cluster ${cluster.index}`;
|
|
1117
|
-
|
|
1118
|
-
if (cluster.vertex_count < 3 || cluster.vertex_count > VGEO_MAX_CLUSTER_VERTEX_COUNT) {
|
|
1119
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: vertex_count ${cluster.vertex_count} is outside [3, ${VGEO_MAX_CLUSTER_VERTEX_COUNT}]`));
|
|
1120
|
-
|
|
1121
|
-
return;
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
if (cluster.triangle_count < 1 || cluster.triangle_count > VGEO_MAX_CLUSTER_TRIANGLE_COUNT) {
|
|
1125
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: triangle_count ${cluster.triangle_count} is outside [1, ${VGEO_MAX_CLUSTER_TRIANGLE_COUNT}]`));
|
|
1126
|
-
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
if ((cluster.encoding_flags & ~VGEO_ENCODING_FLAGS_MASK) !== 0) {
|
|
1131
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: encoding_flags 0x${cluster.encoding_flags.toString(16)} sets a bit that must be clear (skinning bits and reserved bits)`));
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
if (cluster.payload_offset % 4 !== 0 || cluster.payload_size % 4 !== 0) {
|
|
1135
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: payload offset ${cluster.payload_offset} / size ${cluster.payload_size} is not a multiple of 4`));
|
|
1136
|
-
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
const expected_size = vgeo_payload_size(cluster.vertex_count, cluster.triangle_count, cluster.encoding_flags);
|
|
1141
|
-
|
|
1142
|
-
if (cluster.payload_size !== expected_size) {
|
|
1143
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: payload_size ${cluster.payload_size}, the encoding needs ${expected_size}`));
|
|
1144
|
-
|
|
1145
|
-
return;
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
if (cluster.payload_offset + cluster.payload_size > page.payload_size) {
|
|
1149
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: payload runs past the page's payload section`));
|
|
1150
|
-
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
const index_base = page.base + page.payload_offset + cluster.payload_offset;
|
|
1155
|
-
|
|
1156
|
-
for (let i = 0; i < cluster.triangle_count * 3; i++) {
|
|
1157
|
-
const index = page.buffer.raw_bytes[index_base + i];
|
|
1158
|
-
|
|
1159
|
-
if (index >= cluster.vertex_count) {
|
|
1160
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: local index ${index} at ${i} is outside the cluster's ${cluster.vertex_count} vertices`));
|
|
1161
|
-
|
|
1162
|
-
break;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
const named_spheres = [
|
|
1167
|
-
['self_sphere', cluster.self_sphere],
|
|
1168
|
-
['parent_sphere', cluster.parent_sphere],
|
|
1169
|
-
['bounds_sphere', cluster.bounds_sphere],
|
|
1170
|
-
];
|
|
1171
|
-
|
|
1172
|
-
for (const [name, sphere] of named_spheres) {
|
|
1173
|
-
if (!sphere.every(Number.isFinite) || sphere[3] < 0) {
|
|
1174
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: ${name} is not a finite non-negative sphere: ${sphere}`));
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
if (!cluster.aabb.every(Number.isFinite)) {
|
|
1179
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: aabb is not finite`));
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
if (!Number.isFinite(cluster.self_error) || cluster.self_error < 0) {
|
|
1183
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: self_error ${cluster.self_error} must be finite and non-negative`));
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
if (Number.isNaN(cluster.parent_error) || cluster.parent_error < 0) {
|
|
1187
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: parent_error ${cluster.parent_error} must be non-negative and not NaN`));
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
if (cluster.parent_error < cluster.self_error) {
|
|
1191
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: parent_error ${cluster.parent_error} is below self_error ${cluster.self_error}`));
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
if (!sphere_contains_sphere(cluster.parent_sphere, cluster.self_sphere)) {
|
|
1195
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: parent_sphere does not contain self_sphere`));
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
const positions = read_cluster_positions(page, cluster);
|
|
1199
|
-
|
|
1200
|
-
for (let v = 0; v < cluster.vertex_count; v++) {
|
|
1201
|
-
const x = positions[v * 3];
|
|
1202
|
-
const y = positions[v * 3 + 1];
|
|
1203
|
-
const z = positions[v * 3 + 2];
|
|
1204
|
-
|
|
1205
|
-
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
|
|
1206
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: vertex ${v} is not finite`));
|
|
1207
|
-
|
|
1208
|
-
break;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
if (!sphere_contains_point(cluster.bounds_sphere, x, y, z)) {
|
|
1212
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: bounds_sphere does not contain vertex ${v}`));
|
|
1213
|
-
|
|
1214
|
-
break;
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
if (x < cluster.aabb[0] || y < cluster.aabb[1] || z < cluster.aabb[2]
|
|
1218
|
-
|| x > cluster.aabb[3] || y > cluster.aabb[4] || z > cluster.aabb[5]) {
|
|
1219
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: aabb does not contain vertex ${v}`));
|
|
1220
|
-
|
|
1221
|
-
break;
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
if (!sphere_contains_point(object_sphere, x, y, z)) {
|
|
1225
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: the header's object_sphere does not contain vertex ${v}`));
|
|
1226
|
-
|
|
1227
|
-
break;
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
if (x < object_bounds[0] || y < object_bounds[1] || z < object_bounds[2]
|
|
1231
|
-
|| x > object_bounds[3] || y > object_bounds[4] || z > object_bounds[5]) {
|
|
1232
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: the header's object_bounds does not contain vertex ${v}`));
|
|
1233
|
-
|
|
1234
|
-
break;
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
/**
|
|
1240
|
-
* The whole cross-page audit: child references resolve and point at a strictly coarser band than the
|
|
1241
|
-
* cluster making them, the DAG has exactly one root, every group's `parent_cluster_count` is exact,
|
|
1242
|
-
* the metric pair a step writes is the same on both sides of that step, and every page is reachable
|
|
1243
|
-
* from the root.
|
|
1244
|
-
*
|
|
1245
|
-
* @param {DecodedPage[]} pages
|
|
1246
|
-
* @param {object[][]} page_groups
|
|
1247
|
-
* @param {object[][]} page_clusters
|
|
1248
|
-
* @param {number} level_count
|
|
1249
|
-
* @param {VGeoDefect[]} out
|
|
1250
|
-
*/
|
|
1251
|
-
function check_dag(pages, page_groups, page_clusters, level_count, out) {
|
|
1252
|
-
/**
|
|
1253
|
-
* How many clusters name each `page:group`, so that `parent_cluster_count` can be counted rather
|
|
1254
|
-
* than trusted.
|
|
1255
|
-
* @type {Map<string, number>}
|
|
1256
|
-
*/
|
|
1257
|
-
const reference_counts = new Map();
|
|
1258
|
-
|
|
1259
|
-
for (const page of pages) {
|
|
1260
|
-
for (const cluster of page_clusters[page.page_index]) {
|
|
1261
|
-
const where = `page ${page.page_index} cluster ${cluster.index}`;
|
|
1262
|
-
|
|
1263
|
-
if (cluster.child_ref === VGEO_NO_CHILD) {
|
|
1264
|
-
if (cluster.child_group !== VGEO_NO_GROUP) {
|
|
1265
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: child_ref is NO_CHILD but child_group is ${cluster.child_group}`));
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
if (cluster.self_error !== 0) {
|
|
1269
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: a leaf must carry self_error 0, has ${cluster.self_error}`));
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
continue;
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
if (cluster.child_group === VGEO_NO_GROUP) {
|
|
1276
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: child_group is NO_GROUP but child_ref is ${cluster.child_ref}`));
|
|
1277
|
-
|
|
1278
|
-
continue;
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
let child_page_index;
|
|
1282
|
-
|
|
1283
|
-
if (cluster.child_ref === VGEO_SAME_PAGE) {
|
|
1284
|
-
if (page.min_level === page.max_level) {
|
|
1285
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: SAME_PAGE is legal only in a page whose band spans more than one level`));
|
|
1286
|
-
|
|
1287
|
-
continue;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
child_page_index = page.page_index;
|
|
1291
|
-
} else {
|
|
1292
|
-
if (cluster.child_ref > VGEO_MAX_CHILD_REF || cluster.child_ref >= page.child_pages.length) {
|
|
1293
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: child_ref ${cluster.child_ref} is outside the page's ${page.child_pages.length} child-table entries`));
|
|
1294
|
-
|
|
1295
|
-
continue;
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
child_page_index = page.child_pages[cluster.child_ref];
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
if (pages[child_page_index] === undefined) {
|
|
1302
|
-
continue;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
if (
|
|
1306
|
-
cluster.child_ref !== VGEO_SAME_PAGE
|
|
1307
|
-
&& pages[child_page_index].max_level >= page.max_level
|
|
1308
|
-
) {
|
|
1309
|
-
// The band is what puts pages in file order, and a child sitting at or above its
|
|
1310
|
-
// parent's band is a reference that points backwards — the one thing that makes the
|
|
1311
|
-
// file untraversable in a single forward pass.
|
|
1312
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `${where}: its child group is in page ${child_page_index}, whose band tops out at ${pages[child_page_index].max_level} — not below this page's ${page.max_level}`));
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
const child_groups = page_groups[child_page_index];
|
|
1316
|
-
|
|
1317
|
-
if (cluster.child_group >= child_groups.length) {
|
|
1318
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: child_group ${cluster.child_group} does not exist in page ${child_page_index}`));
|
|
1319
|
-
|
|
1320
|
-
continue;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
const key = `${child_page_index}:${cluster.child_group}`;
|
|
1324
|
-
|
|
1325
|
-
reference_counts.set(key, (reference_counts.get(key) ?? 0) + 1);
|
|
1326
|
-
|
|
1327
|
-
const group = child_groups[cluster.child_group];
|
|
1328
|
-
|
|
1329
|
-
for (let i = 0; i < group.cluster_count; i++) {
|
|
1330
|
-
const child = page_clusters[child_page_index][group.cluster_first + i];
|
|
1331
|
-
|
|
1332
|
-
if (child === undefined) {
|
|
1333
|
-
break;
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
if (child.parent_error !== cluster.self_error) {
|
|
1337
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: self_error ${cluster.self_error} disagrees with the consumed cluster's parent_error ${child.parent_error}; one group step writes one value on both sides`));
|
|
1338
|
-
|
|
1339
|
-
break;
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
if (child.parent_sphere.some((value, axis) => value !== cluster.self_sphere[axis])) {
|
|
1343
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: self_sphere disagrees with the consumed cluster's parent_sphere`));
|
|
1344
|
-
|
|
1345
|
-
break;
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
// Exactly one root. The runtime's cut starts at the root and walks down; a second parentless
|
|
1352
|
-
// cluster is a second entry point it would never visit, and its whole subtree with it. It is
|
|
1353
|
-
// also the shape a build that stopped converging leaves behind, which is the likelier cause.
|
|
1354
|
-
const roots = [];
|
|
1355
|
-
|
|
1356
|
-
for (const page of pages) {
|
|
1357
|
-
for (const cluster of page_clusters[page.page_index]) {
|
|
1358
|
-
if (cluster.parent_error === Number.POSITIVE_INFINITY) {
|
|
1359
|
-
roots.push({ page: page.page_index, cluster: cluster.index });
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
const where_roots = roots.slice(0, 4).map((root) => `page ${root.page} cluster ${root.cluster}`);
|
|
1365
|
-
|
|
1366
|
-
if (roots.length !== 1) {
|
|
1367
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `the DAG has ${roots.length} root clusters, expected exactly one${roots.length === 0 ? '' : `: ${where_roots.join(', ')}`}`));
|
|
1368
|
-
} else if (roots[0].page !== 0) {
|
|
1369
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `the root cluster is in ${where_roots[0]}, not the always-resident root page`));
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
for (const page of pages) {
|
|
1373
|
-
page_groups[page.page_index].forEach((group, group_index) => {
|
|
1374
|
-
const counted = reference_counts.get(`${page.page_index}:${group_index}`) ?? 0;
|
|
1375
|
-
|
|
1376
|
-
if (group.parent_cluster_count !== counted) {
|
|
1377
|
-
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index} group ${group_index}: parent_cluster_count is ${group.parent_cluster_count}, ${counted} clusters name it`));
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
const is_root_group = page.page_index === 0 && page.max_level === level_count - 1;
|
|
1381
|
-
|
|
1382
|
-
if (counted === 0 && !is_root_group) {
|
|
1383
|
-
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index} group ${group_index}: no cluster names it, yet it is not one of the root page's topmost-level groups`));
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
for (let i = 0; i < group.cluster_count; i++) {
|
|
1387
|
-
const cluster = page_clusters[page.page_index][group.cluster_first + i];
|
|
1388
|
-
|
|
1389
|
-
if (cluster === undefined) {
|
|
1390
|
-
break;
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
const is_root = cluster.parent_error === Number.POSITIVE_INFINITY;
|
|
1394
|
-
|
|
1395
|
-
if (is_root !== (counted === 0)) {
|
|
1396
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `page ${page.page_index} cluster ${cluster.index}: parent_error is ${cluster.parent_error} while its group has ${counted} parents; +Infinity holds exactly for a group nothing consumes`));
|
|
1397
|
-
|
|
1398
|
-
break;
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
if (is_root && cluster.parent_sphere.some((value, axis) => value !== cluster.self_sphere[axis])) {
|
|
1402
|
-
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `page ${page.page_index} cluster ${cluster.index}: a root's parent_sphere must duplicate its self_sphere`));
|
|
1403
|
-
|
|
1404
|
-
break;
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
});
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
const reached = new Set([0]);
|
|
1411
|
-
const queue = [0];
|
|
1412
|
-
|
|
1413
|
-
while (queue.length > 0) {
|
|
1414
|
-
const page_index = queue.pop();
|
|
1415
|
-
|
|
1416
|
-
for (const child of pages[page_index].child_pages) {
|
|
1417
|
-
if (pages[child] !== undefined && !reached.has(child)) {
|
|
1418
|
-
reached.add(child);
|
|
1419
|
-
queue.push(child);
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
for (const page of pages) {
|
|
1425
|
-
if (!reached.has(page.page_index)) {
|
|
1426
|
-
out.push(new VGeoDefect(VGEO_DEFECT.REACHABILITY, `page ${page.page_index} is not reachable from the root page`));
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
/**
|
|
1432
|
-
* @param {DecodedPage[]} pages
|
|
1433
|
-
* @param {number} level_count
|
|
1434
|
-
* @param {VGeoDefect[]} out
|
|
1435
|
-
*/
|
|
1436
|
-
function check_page_order(pages, level_count, out) {
|
|
1437
|
-
const root = pages[0];
|
|
1438
|
-
|
|
1439
|
-
if (root === undefined) {
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
if (root.max_level !== level_count - 1) {
|
|
1444
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_ORDER, `the root page's max_level is ${root.max_level}, the topmost level is ${level_count - 1}`));
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
for (let i = 1; i < pages.length; i++) {
|
|
1448
|
-
if (pages[i].max_level > pages[i - 1].max_level) {
|
|
1449
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_ORDER, `page ${i} has max_level ${pages[i].max_level} after page ${i - 1}'s ${pages[i - 1].max_level}; file order must run coarse first`));
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
if (pages[i].max_level === level_count - 1) {
|
|
1453
|
-
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_ORDER, `page ${i} also carries the topmost level; the root page must hold it complete`));
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
/**
|
|
1459
|
-
* The directory is derived data: rebuild it from a frame scan and require the file's copy to match
|
|
1460
|
-
* byte for byte, then verify its block checksums.
|
|
1461
|
-
*
|
|
1462
|
-
* @param {BinaryBuffer} buffer whole file
|
|
1463
|
-
* @param {VGeoFrameExtent[]} frames
|
|
1464
|
-
* @param {DecodedPage[]} pages
|
|
1465
|
-
* @param {VGeoDefect[]} out
|
|
1466
|
-
*/
|
|
1467
|
-
function check_directory(buffer, frames, pages, out) {
|
|
1468
|
-
buffer.position = VGEO_HEADER_OFFSET.TOTAL_PAGES;
|
|
1469
|
-
|
|
1470
|
-
const total_pages = buffer.readUint32();
|
|
1471
|
-
|
|
1472
|
-
buffer.position = VGEO_HEADER_OFFSET.DIRECTORY_OFFSET;
|
|
1473
|
-
|
|
1474
|
-
const directory_offset = Number(buffer.readUint64());
|
|
1475
|
-
const dir_block_checksums_offset = Number(buffer.readUint64());
|
|
1476
|
-
|
|
1477
|
-
const reconstructed = new BinaryBuffer();
|
|
1478
|
-
|
|
1479
|
-
reconstructed.setCapacity(total_pages * VGEO_DIRECTORY_RECORD_SIZE);
|
|
1480
|
-
|
|
1481
|
-
for (let i = 0; i < Math.min(total_pages, frames.length); i++) {
|
|
1482
|
-
const frame = frames[i];
|
|
1483
|
-
const page = pages[i];
|
|
1484
|
-
|
|
1485
|
-
reconstructed.position = i * VGEO_DIRECTORY_RECORD_SIZE;
|
|
1486
|
-
|
|
1487
|
-
reconstructed.writeUint64(BigInt(frame.frame_offset));
|
|
1488
|
-
reconstructed.writeUint32(frame.fetch_size);
|
|
1489
|
-
reconstructed.writeUint32(frame.decoded_size);
|
|
1490
|
-
|
|
1491
|
-
if (page === undefined) {
|
|
1492
|
-
continue;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
reconstructed.writeUint8(page.min_level);
|
|
1496
|
-
reconstructed.writeUint8(page.max_level);
|
|
1497
|
-
reconstructed.writeUint16(i === 0 ? VGEO_DIRECTORY_FLAG_ROOT_PAGE : 0);
|
|
1498
|
-
reconstructed.writeUint16(page.group_count);
|
|
1499
|
-
reconstructed.writeUint16(page.cluster_count);
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
const stored = buffer.raw_bytes;
|
|
1503
|
-
const expected = reconstructed.raw_bytes;
|
|
1504
|
-
|
|
1505
|
-
for (let i = 0; i < expected.length; i++) {
|
|
1506
|
-
if (stored[directory_offset + i] !== expected[i]) {
|
|
1507
|
-
const record = (i / VGEO_DIRECTORY_RECORD_SIZE) | 0;
|
|
1508
|
-
|
|
1509
|
-
out.push(new VGeoDefect(VGEO_DEFECT.DIRECTORY, `directory record ${record} disagrees with a frame scan at byte ${i % VGEO_DIRECTORY_RECORD_SIZE}: the file has ${stored[directory_offset + i]}, the frames say ${expected[i]}`));
|
|
1510
|
-
|
|
1511
|
-
break;
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
buffer.position = VGEO_HEADER_OFFSET.ASSET_ID + 8;
|
|
1516
|
-
|
|
1517
|
-
const asset_id_high = buffer.readUint64();
|
|
1518
|
-
|
|
1519
|
-
const block_count = vgeo_directory_block_count(total_pages);
|
|
1520
|
-
|
|
1521
|
-
for (let block = 0; block < block_count; block++) {
|
|
1522
|
-
buffer.position = dir_block_checksums_offset + block * 8;
|
|
1523
|
-
|
|
1524
|
-
const stored_checksum = buffer.readUint64();
|
|
1525
|
-
|
|
1526
|
-
const computed = vgeo_compute_directory_block_checksum(
|
|
1527
|
-
buffer, directory_offset, total_pages, block, asset_id_high
|
|
1528
|
-
);
|
|
1529
|
-
|
|
1530
|
-
if (stored_checksum !== computed) {
|
|
1531
|
-
out.push(new VGeoDefect(VGEO_DEFECT.CHECKSUM, `directory block ${block}: checksum is ${stored_checksum.toString(16)}, computed ${computed.toString(16)}`));
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
/**
|
|
1537
|
-
* Audit a `.vgeo` file.
|
|
1538
|
-
*
|
|
1539
|
-
* @param {BinaryBuffer} buffer the whole file, with `capacity` equal to its length
|
|
1540
|
-
* @returns {VGeoDefect[]} in discovery order; empty when the file satisfies every invariant
|
|
1541
|
-
*/
|
|
1542
|
-
export function vgeo_validate_container(buffer) {
|
|
1543
|
-
const out = [];
|
|
1544
|
-
|
|
1545
|
-
if (!check_header(buffer, out)) {
|
|
1546
|
-
return out;
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
buffer.position = VGEO_HEADER_OFFSET.LEVEL_COUNT;
|
|
1550
|
-
|
|
1551
|
-
const level_count = buffer.readUint8();
|
|
1552
|
-
|
|
1553
|
-
buffer.position = VGEO_HEADER_OFFSET.TOTAL_CLUSTERS;
|
|
1554
|
-
|
|
1555
|
-
const total_clusters = Number(buffer.readUint64());
|
|
1556
|
-
const total_groups = Number(buffer.readUint64());
|
|
1557
|
-
|
|
1558
|
-
buffer.position = VGEO_HEADER_OFFSET.OBJECT_SPHERE;
|
|
1559
|
-
|
|
1560
|
-
const object_sphere = read_sphere(buffer);
|
|
1561
|
-
const object_bounds = read_aabb(buffer);
|
|
1562
|
-
|
|
1563
|
-
check_hashes(buffer, out);
|
|
1564
|
-
|
|
1565
|
-
const frames = vgeo_scan_frames(buffer);
|
|
1566
|
-
|
|
1567
|
-
check_frames(buffer, frames, out);
|
|
1568
|
-
|
|
1569
|
-
/**
|
|
1570
|
-
* @type {DecodedPage[]}
|
|
1571
|
-
*/
|
|
1572
|
-
const pages = [];
|
|
1573
|
-
|
|
1574
|
-
for (const frame of frames) {
|
|
1575
|
-
const page = decode_page(buffer, frame, level_count, out);
|
|
1576
|
-
|
|
1577
|
-
if (page === undefined) {
|
|
1578
|
-
return out;
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
pages.push(page);
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
check_child_tables(buffer, frames, pages, out);
|
|
1585
|
-
check_page_order(pages, level_count, out);
|
|
1586
|
-
|
|
1587
|
-
const page_groups = [];
|
|
1588
|
-
const page_clusters = [];
|
|
1589
|
-
|
|
1590
|
-
let counted_clusters = 0;
|
|
1591
|
-
let counted_groups = 0;
|
|
1592
|
-
|
|
1593
|
-
for (const page of pages) {
|
|
1594
|
-
const groups = read_groups(page, out);
|
|
1595
|
-
const clusters = read_clusters(page, out);
|
|
1596
|
-
|
|
1597
|
-
page_groups.push(groups);
|
|
1598
|
-
page_clusters.push(clusters);
|
|
1599
|
-
|
|
1600
|
-
counted_groups += groups.length;
|
|
1601
|
-
counted_clusters += clusters.length;
|
|
1602
|
-
|
|
1603
|
-
for (const cluster of clusters) {
|
|
1604
|
-
check_cluster_record(page, cluster, object_sphere, object_bounds, out);
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
if (counted_clusters !== total_clusters) {
|
|
1609
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `total_clusters is ${total_clusters}, the pages hold ${counted_clusters}`));
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
if (counted_groups !== total_groups) {
|
|
1613
|
-
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `total_groups is ${total_groups}, the pages hold ${counted_groups}`));
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
check_page_nodes(pages, page_groups, page_clusters, out);
|
|
1617
|
-
|
|
1618
|
-
check_dag(pages, page_groups, page_clusters, level_count, out);
|
|
1619
|
-
check_directory(buffer, frames, pages, out);
|
|
1620
|
-
|
|
1621
|
-
return out;
|
|
1622
|
-
}
|
|
1
|
+
import { BinaryBuffer } from "../../../../../core/binary/BinaryBuffer.js";
|
|
2
|
+
import { round_up } from "../../../../../core/math/round_up.js";
|
|
3
|
+
import { VGEO_CHILD_TABLE_ENTRY_SIZE } from "./frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js";
|
|
4
|
+
import { VGEO_CLUSTER_RECORD_SIZE } from "./cluster/VGEO_CLUSTER_RECORD_SIZE.js";
|
|
5
|
+
import { VGEO_CODEC_LZ4 } from "./frame/VGEO_CODEC_LZ4.js";
|
|
6
|
+
import { VGEO_CODEC_RAW } from "./frame/VGEO_CODEC_RAW.js";
|
|
7
|
+
import { vgeo_compute_asset_id } from "./checksum/vgeo_compute_asset_id.js";
|
|
8
|
+
import { vgeo_compute_directory_block_checksum } from "./checksum/vgeo_compute_directory_block_checksum.js";
|
|
9
|
+
import { vgeo_compute_frame_checksum } from "./checksum/vgeo_compute_frame_checksum.js";
|
|
10
|
+
import { vgeo_compute_header_checksum } from "./checksum/vgeo_compute_header_checksum.js";
|
|
11
|
+
import { VGEO_DEFECT } from "./VGEO_DEFECT.js";
|
|
12
|
+
import { vgeo_directory_block_count } from "./directory/vgeo_directory_block_count.js";
|
|
13
|
+
import { VGEO_DIRECTORY_FLAG_ROOT_PAGE } from "./directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js";
|
|
14
|
+
import { VGEO_DIRECTORY_RECORD_SIZE } from "./directory/VGEO_DIRECTORY_RECORD_SIZE.js";
|
|
15
|
+
import { vgeo_decode_frame_blob } from "./frame/vgeo_decode_frame_blob.js";
|
|
16
|
+
import { VGEO_ENCODING_FLAGS_MASK } from "./payload/VGEO_ENCODING_FLAGS_MASK.js";
|
|
17
|
+
import { VGEO_FORMAT_VERSION } from "./header/VGEO_FORMAT_VERSION.js";
|
|
18
|
+
import { VGEO_FRAME_ALIGNMENT } from "./frame/VGEO_FRAME_ALIGNMENT.js";
|
|
19
|
+
import { vgeo_frame_decode_bound } from "./frame/vgeo_frame_decode_bound.js";
|
|
20
|
+
import { VGEO_FRAME_HEADER_SIZE } from "./frame/VGEO_FRAME_HEADER_SIZE.js";
|
|
21
|
+
import { VGEO_FRAME_OFFSET } from "./frame/VGEO_FRAME_OFFSET.js";
|
|
22
|
+
import { VGEO_GROUP_RECORD_SIZE } from "./page/VGEO_GROUP_RECORD_SIZE.js";
|
|
23
|
+
import { VGEO_HASH_ALGO_XXH3 } from "./header/VGEO_HASH_ALGO_XXH3.js";
|
|
24
|
+
import { VGEO_HEADER_OFFSET } from "./header/VGEO_HEADER_OFFSET.js";
|
|
25
|
+
import { VGEO_HEADER_SIZE } from "./header/VGEO_HEADER_SIZE.js";
|
|
26
|
+
import { VGEO_LEVEL_RECORD_SIZE } from "./header/VGEO_LEVEL_RECORD_SIZE.js";
|
|
27
|
+
import { VGEO_MAGIC } from "./header/VGEO_MAGIC.js";
|
|
28
|
+
import { VGEO_MAX_CHILD_REF } from "./cluster/VGEO_MAX_CHILD_REF.js";
|
|
29
|
+
import { VGEO_MAX_CLUSTER_TRIANGLE_COUNT } from "./cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js";
|
|
30
|
+
import { VGEO_MAX_CLUSTER_VERTEX_COUNT } from "./cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js";
|
|
31
|
+
import { VGEO_MAX_GROUP_CLUSTER_COUNT } from "./page/VGEO_MAX_GROUP_CLUSTER_COUNT.js";
|
|
32
|
+
import { VGEO_MIN_READER_VERSION } from "./header/VGEO_MIN_READER_VERSION.js";
|
|
33
|
+
import { VGEO_NO_CHILD } from "./cluster/VGEO_NO_CHILD.js";
|
|
34
|
+
import { VGEO_NO_GROUP } from "./cluster/VGEO_NO_GROUP.js";
|
|
35
|
+
import { VGEO_NODE_FANOUT } from "./page/VGEO_NODE_FANOUT.js";
|
|
36
|
+
import { VGEO_NODE_FLAG_LEAF } from "./page/VGEO_NODE_FLAG_LEAF.js";
|
|
37
|
+
import { VGEO_NODE_OFFSET } from "./page/VGEO_NODE_OFFSET.js";
|
|
38
|
+
import { VGEO_NODE_RECORD_SIZE } from "./page/VGEO_NODE_RECORD_SIZE.js";
|
|
39
|
+
import { VGEO_PAGE_HEADER_SIZE } from "./page/VGEO_PAGE_HEADER_SIZE.js";
|
|
40
|
+
import { VGEO_PAGE_MAGIC } from "./frame/VGEO_PAGE_MAGIC.js";
|
|
41
|
+
import { VGEO_PAGE_OFFSET } from "./page/VGEO_PAGE_OFFSET.js";
|
|
42
|
+
import { vgeo_payload_attribute_offset } from "./payload/vgeo_payload_attribute_offset.js";
|
|
43
|
+
import { VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1 } from "./header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js";
|
|
44
|
+
import { vgeo_payload_size } from "./payload/vgeo_payload_size.js";
|
|
45
|
+
import { VGEO_SAME_PAGE } from "./cluster/VGEO_SAME_PAGE.js";
|
|
46
|
+
import { vgeo_scan_frames } from "./frame/vgeo_scan_frames.js";
|
|
47
|
+
import { VGeoDefect } from "./VGeoDefect.js";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A full audit of a `.vgeo` file against VGEO_FORMAT §11 — every build invariant, every checksum,
|
|
51
|
+
* and the directory checked against what a frame scan reconstructs.
|
|
52
|
+
*
|
|
53
|
+
* It reads nothing but the bytes it is given: no builder state, no side channel. That is the point —
|
|
54
|
+
* a file that passes here is one a reader can trust, whoever produced it.
|
|
55
|
+
*
|
|
56
|
+
* @author Alex Goldring
|
|
57
|
+
* @copyright Company Named Limited (c) 2026
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A page's decoded form. Under codec 0 the decoded page is the frame's own bytes, so this holds the
|
|
62
|
+
* absolute offset of its first byte rather than a copy; a compressing codec decodes into its own
|
|
63
|
+
* buffer and `base` is 0 there. Everything downstream reads through {@link buffer} and {@link seek},
|
|
64
|
+
* so which of the two it was never reaches a check.
|
|
65
|
+
*/
|
|
66
|
+
class DecodedPage {
|
|
67
|
+
/**
|
|
68
|
+
* @type {number}
|
|
69
|
+
*/
|
|
70
|
+
page_index = 0;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @type {VGeoFrameExtent}
|
|
74
|
+
*/
|
|
75
|
+
frame = null;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Where the decoded page's byte 0 sits in {@link buffer}.
|
|
79
|
+
* @type {number}
|
|
80
|
+
*/
|
|
81
|
+
base = 0;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @type {BinaryBuffer}
|
|
85
|
+
*/
|
|
86
|
+
buffer = null;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @type {number}
|
|
90
|
+
*/
|
|
91
|
+
size = 0;
|
|
92
|
+
|
|
93
|
+
group_count = 0;
|
|
94
|
+
cluster_count = 0;
|
|
95
|
+
min_level = 0;
|
|
96
|
+
max_level = 0;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @type {number}
|
|
100
|
+
*/
|
|
101
|
+
node_count = 0;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @type {number}
|
|
105
|
+
*/
|
|
106
|
+
node_table_offset = 0;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @type {VGeoMetric}
|
|
110
|
+
*/
|
|
111
|
+
metric = null;
|
|
112
|
+
cluster_table_offset = 0;
|
|
113
|
+
payload_offset = 0;
|
|
114
|
+
payload_size = 0;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* `[page_index, ...]` in child-table order.
|
|
118
|
+
* @type {number[]}
|
|
119
|
+
*/
|
|
120
|
+
child_pages = [];
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Position `buffer` at a page-relative offset.
|
|
124
|
+
* @param {number} offset
|
|
125
|
+
*/
|
|
126
|
+
seek(offset) {
|
|
127
|
+
this.buffer.position = this.base + offset;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {BinaryBuffer} buffer positioned at the sphere
|
|
133
|
+
* @returns {number[]} `[x, y, z, radius]`
|
|
134
|
+
*/
|
|
135
|
+
function read_sphere(buffer) {
|
|
136
|
+
const result = new Float32Array(4);
|
|
137
|
+
|
|
138
|
+
buffer.readFloat32Array(result, 0, 4);
|
|
139
|
+
|
|
140
|
+
return Array.from(result);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {BinaryBuffer} buffer positioned at the box
|
|
145
|
+
* @returns {number[]} `[min_x, min_y, min_z, max_x, max_y, max_z]`
|
|
146
|
+
*/
|
|
147
|
+
function read_aabb(buffer) {
|
|
148
|
+
const result = new Float32Array(6);
|
|
149
|
+
|
|
150
|
+
buffer.readFloat32Array(result, 0, 6);
|
|
151
|
+
|
|
152
|
+
return Array.from(result);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {number[]} outer
|
|
157
|
+
* @param {number[]} inner
|
|
158
|
+
* @returns {boolean} whether `outer` contains `inner`, evaluated on the stored values exactly
|
|
159
|
+
*/
|
|
160
|
+
function sphere_contains_sphere(outer, inner) {
|
|
161
|
+
const dx = inner[0] - outer[0];
|
|
162
|
+
const dy = inner[1] - outer[1];
|
|
163
|
+
const dz = inner[2] - outer[2];
|
|
164
|
+
|
|
165
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz) + inner[3] <= outer[3];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {number[]} sphere
|
|
170
|
+
* @param {number} x
|
|
171
|
+
* @param {number} y
|
|
172
|
+
* @param {number} z
|
|
173
|
+
* @returns {boolean}
|
|
174
|
+
*/
|
|
175
|
+
function sphere_contains_point(sphere, x, y, z) {
|
|
176
|
+
const dx = x - sphere[0];
|
|
177
|
+
const dy = y - sphere[1];
|
|
178
|
+
const dz = z - sphere[2];
|
|
179
|
+
|
|
180
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz) <= sphere[3];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @param {BinaryBuffer} buffer whole file
|
|
185
|
+
* @param {VGeoDefect[]} out
|
|
186
|
+
* @returns {boolean} whether the header is sane enough for the rest of the audit to proceed
|
|
187
|
+
*/
|
|
188
|
+
function check_header(buffer, out) {
|
|
189
|
+
if (buffer.capacity < VGEO_HEADER_SIZE) {
|
|
190
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `file is ${buffer.capacity} bytes, shorter than the ${VGEO_HEADER_SIZE} byte header`));
|
|
191
|
+
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let usable = true;
|
|
196
|
+
|
|
197
|
+
buffer.position = 0;
|
|
198
|
+
|
|
199
|
+
const magic = buffer.readUint32();
|
|
200
|
+
|
|
201
|
+
if (magic !== VGEO_MAGIC) {
|
|
202
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `magic is 0x${magic.toString(16)}, expected 0x${VGEO_MAGIC.toString(16)}`));
|
|
203
|
+
|
|
204
|
+
usable = false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const format_version = buffer.readUint16();
|
|
208
|
+
|
|
209
|
+
if (format_version !== VGEO_FORMAT_VERSION) {
|
|
210
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `format_version is ${format_version}, this validator knows ${VGEO_FORMAT_VERSION}`));
|
|
211
|
+
|
|
212
|
+
usable = false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const min_reader_version = buffer.readUint16();
|
|
216
|
+
|
|
217
|
+
if (min_reader_version !== VGEO_MIN_READER_VERSION) {
|
|
218
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `min_reader_version is ${min_reader_version}, expected ${VGEO_MIN_READER_VERSION}`));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const header_size = buffer.readUint16();
|
|
222
|
+
|
|
223
|
+
if (header_size !== VGEO_HEADER_SIZE) {
|
|
224
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `header_size is ${header_size}, expected ${VGEO_HEADER_SIZE}`));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const hash_algo = buffer.readUint8();
|
|
228
|
+
|
|
229
|
+
if (hash_algo !== VGEO_HASH_ALGO_XXH3) {
|
|
230
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `hash_algo is ${hash_algo}, expected ${VGEO_HASH_ALGO_XXH3}`));
|
|
231
|
+
|
|
232
|
+
usable = false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (buffer.readUint8() !== 0) {
|
|
236
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'reserved byte at 11 is not zero'));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const feature_flags_required = buffer.readUint32();
|
|
240
|
+
|
|
241
|
+
if (feature_flags_required !== 0) {
|
|
242
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `feature_flags_required is 0x${feature_flags_required.toString(16)}, no bit is defined`));
|
|
243
|
+
|
|
244
|
+
usable = false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
buffer.position = VGEO_HEADER_OFFSET.PAYLOAD_ENCODING;
|
|
248
|
+
|
|
249
|
+
const payload_encoding = buffer.readUint32();
|
|
250
|
+
|
|
251
|
+
if (payload_encoding !== VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1) {
|
|
252
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `payload_encoding is ${payload_encoding}, expected ${VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1}`));
|
|
253
|
+
|
|
254
|
+
usable = false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
buffer.position = VGEO_HEADER_OFFSET.TOTAL_PAGES;
|
|
258
|
+
|
|
259
|
+
const total_pages = buffer.readUint32();
|
|
260
|
+
|
|
261
|
+
if (total_pages < 1) {
|
|
262
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'total_pages is 0'));
|
|
263
|
+
|
|
264
|
+
usable = false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const total_clusters = Number(buffer.readUint64());
|
|
268
|
+
|
|
269
|
+
if (total_clusters < 1) {
|
|
270
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'total_clusters is 0'));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const total_groups = Number(buffer.readUint64());
|
|
274
|
+
|
|
275
|
+
if (total_groups < 1) {
|
|
276
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'total_groups is 0'));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const root_page_offset = Number(buffer.readUint64());
|
|
280
|
+
|
|
281
|
+
buffer.position = VGEO_HEADER_OFFSET.DIRECTORY_OFFSET;
|
|
282
|
+
|
|
283
|
+
const directory_offset = Number(buffer.readUint64());
|
|
284
|
+
|
|
285
|
+
if (directory_offset % VGEO_FRAME_ALIGNMENT !== 0) {
|
|
286
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `directory_offset ${directory_offset} is not ${VGEO_FRAME_ALIGNMENT}-aligned`));
|
|
287
|
+
|
|
288
|
+
usable = false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (directory_offset <= VGEO_HEADER_SIZE || directory_offset > buffer.capacity) {
|
|
292
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `directory_offset ${directory_offset} is outside the file`));
|
|
293
|
+
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const dir_block_checksums_offset = Number(buffer.readUint64());
|
|
298
|
+
|
|
299
|
+
const expected_dir_block_offset = directory_offset + total_pages * VGEO_DIRECTORY_RECORD_SIZE;
|
|
300
|
+
|
|
301
|
+
if (dir_block_checksums_offset !== expected_dir_block_offset) {
|
|
302
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `dir_block_checksums_offset is ${dir_block_checksums_offset}, expected ${expected_dir_block_offset}`));
|
|
303
|
+
|
|
304
|
+
usable = false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const expected_length = dir_block_checksums_offset + vgeo_directory_block_count(total_pages) * 8;
|
|
308
|
+
|
|
309
|
+
if (usable && buffer.capacity !== expected_length) {
|
|
310
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `file is ${buffer.capacity} bytes, the header describes ${expected_length}`));
|
|
311
|
+
|
|
312
|
+
usable = false;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
buffer.position = 100;
|
|
316
|
+
|
|
317
|
+
if (buffer.readUint16() !== 0) {
|
|
318
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'reserved u16 at 100 is not zero'));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const level_count = buffer.readUint8();
|
|
322
|
+
|
|
323
|
+
if (level_count < 1) {
|
|
324
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'level_count is 0'));
|
|
325
|
+
|
|
326
|
+
usable = false;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// The frame region begins after the level table, not at a fixed offset — which is what keeps
|
|
330
|
+
// the header, the whole table and the always-resident root page in one contiguous head fetch.
|
|
331
|
+
const expected_root_page_offset = round_up(
|
|
332
|
+
VGEO_HEADER_SIZE + level_count * VGEO_LEVEL_RECORD_SIZE, VGEO_FRAME_ALIGNMENT
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
if (root_page_offset !== expected_root_page_offset) {
|
|
336
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `root_page_offset is ${root_page_offset}; ${level_count} levels of level table put it at ${expected_root_page_offset}`));
|
|
337
|
+
|
|
338
|
+
usable = false;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (buffer.readUint8() !== 0) {
|
|
342
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, 'reserved byte at 103 is not zero'));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const source_vertex_count = Number(buffer.readUint64());
|
|
346
|
+
|
|
347
|
+
if (source_vertex_count < 3) {
|
|
348
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `source_vertex_count is ${source_vertex_count}, must be at least 3`));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const source_face_count = Number(buffer.readUint64());
|
|
352
|
+
|
|
353
|
+
if (source_face_count < 1) {
|
|
354
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `source_face_count is ${source_face_count}, must be at least 1`));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const object_sphere = read_sphere(buffer);
|
|
358
|
+
|
|
359
|
+
if (!object_sphere.every(Number.isFinite) || object_sphere[3] < 0) {
|
|
360
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `object_sphere is not a finite non-negative sphere: ${object_sphere}`));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const object_bounds = read_aabb(buffer);
|
|
364
|
+
|
|
365
|
+
if (!object_bounds.every(Number.isFinite)) {
|
|
366
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `object_bounds is not finite: ${object_bounds}`));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
370
|
+
if (object_bounds[axis] > object_bounds[axis + 3]) {
|
|
371
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `object_bounds axis ${axis} has min > max`));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
for (let offset = 160; offset < VGEO_HEADER_OFFSET.HEADER_CHECKSUM; offset++) {
|
|
376
|
+
if (buffer.raw_bytes[offset] !== 0) {
|
|
377
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `reserved byte at ${offset} is not zero`));
|
|
378
|
+
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return usable;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* @param {BinaryBuffer} buffer whole file
|
|
388
|
+
* @param {VGeoDefect[]} out
|
|
389
|
+
*/
|
|
390
|
+
function check_hashes(buffer, out) {
|
|
391
|
+
buffer.position = VGEO_HEADER_OFFSET.HEADER_CHECKSUM;
|
|
392
|
+
|
|
393
|
+
const stored_header_checksum = buffer.readUint64();
|
|
394
|
+
const computed_header_checksum = vgeo_compute_header_checksum(buffer);
|
|
395
|
+
|
|
396
|
+
if (stored_header_checksum !== computed_header_checksum) {
|
|
397
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHECKSUM, `header_checksum is ${stored_header_checksum.toString(16)}, computed ${computed_header_checksum.toString(16)}`));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
buffer.position = VGEO_HEADER_OFFSET.ASSET_ID;
|
|
401
|
+
|
|
402
|
+
const stored_low = buffer.readUint64();
|
|
403
|
+
const stored_high = buffer.readUint64();
|
|
404
|
+
|
|
405
|
+
const computed = vgeo_compute_asset_id(buffer);
|
|
406
|
+
|
|
407
|
+
if (stored_low !== computed.low64 || stored_high !== computed.high64) {
|
|
408
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHECKSUM, `asset_id is ${stored_high.toString(16)}${stored_low.toString(16)}, computed ${computed.toString()}`));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @param {BinaryBuffer} buffer whole file
|
|
414
|
+
* @param {VGeoFrameExtent[]} frames
|
|
415
|
+
* @param {VGeoDefect[]} out
|
|
416
|
+
*/
|
|
417
|
+
function check_frames(buffer, frames, out) {
|
|
418
|
+
buffer.position = VGEO_HEADER_OFFSET.TOTAL_PAGES;
|
|
419
|
+
|
|
420
|
+
const total_pages = buffer.readUint32();
|
|
421
|
+
|
|
422
|
+
buffer.position = VGEO_HEADER_OFFSET.DIRECTORY_OFFSET;
|
|
423
|
+
|
|
424
|
+
const directory_offset = Number(buffer.readUint64());
|
|
425
|
+
|
|
426
|
+
if (frames.length !== total_pages) {
|
|
427
|
+
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `frame scan found ${frames.length} frames, header says total_pages = ${total_pages}`));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (frames.length > 0) {
|
|
431
|
+
const last = frames[frames.length - 1];
|
|
432
|
+
|
|
433
|
+
const end = last.frame_offset + last.fetch_size;
|
|
434
|
+
|
|
435
|
+
if (end !== directory_offset) {
|
|
436
|
+
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `frame region ends at ${end}, directory_offset is ${directory_offset}; the two must coincide exactly`));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
buffer.position = VGEO_HEADER_OFFSET.ASSET_ID;
|
|
441
|
+
|
|
442
|
+
const asset_id_low = buffer.readUint64();
|
|
443
|
+
|
|
444
|
+
for (const frame of frames) {
|
|
445
|
+
buffer.position = frame.frame_offset;
|
|
446
|
+
|
|
447
|
+
const magic = buffer.readUint32();
|
|
448
|
+
|
|
449
|
+
if (magic !== VGEO_PAGE_MAGIC) {
|
|
450
|
+
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: magic is 0x${magic.toString(16)}, expected 0x${VGEO_PAGE_MAGIC.toString(16)}`));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const decodable = frame.codec === VGEO_CODEC_RAW || frame.codec === VGEO_CODEC_LZ4;
|
|
454
|
+
|
|
455
|
+
if (!decodable) {
|
|
456
|
+
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: codec is ${frame.codec}, this validator knows ${VGEO_CODEC_RAW} (raw) and ${VGEO_CODEC_LZ4} (lz4)`));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (frame.flags !== 0) {
|
|
460
|
+
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: frame flags are 0x${frame.flags.toString(16)}, no bit is defined`));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (frame.codec === VGEO_CODEC_RAW && frame.decoded_size !== frame.stored_size) {
|
|
464
|
+
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: raw frame has stored_size ${frame.stored_size} and decoded_size ${frame.decoded_size}`));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// A `decoded_size` above what the stored bytes could physically produce is caught here
|
|
468
|
+
// rather than at decode, because a reader allocates against it *before* it decodes — see
|
|
469
|
+
// {@link vgeo_frame_decode_bound}.
|
|
470
|
+
if (decodable && frame.decoded_size > vgeo_frame_decode_bound(frame.codec, frame.stored_size)) {
|
|
471
|
+
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: decoded_size ${frame.decoded_size} is more than codec ${frame.codec} could produce from ${frame.stored_size} stored bytes`));
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (frame.frame_offset % VGEO_FRAME_ALIGNMENT !== 0) {
|
|
475
|
+
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: frame_offset ${frame.frame_offset} is not ${VGEO_FRAME_ALIGNMENT}-aligned`));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (frame.frame_offset + frame.extent > directory_offset) {
|
|
479
|
+
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: frame runs past directory_offset`));
|
|
480
|
+
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
buffer.position = frame.frame_offset + VGEO_FRAME_OFFSET.CHECKSUM;
|
|
485
|
+
|
|
486
|
+
const stored_checksum = buffer.readUint64();
|
|
487
|
+
const computed_checksum = vgeo_compute_frame_checksum(buffer, frame, asset_id_low);
|
|
488
|
+
|
|
489
|
+
if (stored_checksum !== computed_checksum) {
|
|
490
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHECKSUM, `page ${frame.page_index}: frame checksum is ${stored_checksum.toString(16)}, computed ${computed_checksum.toString(16)}`));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (frames.length > 0) {
|
|
495
|
+
buffer.position = VGEO_HEADER_OFFSET.ROOT_PAGE_FETCH_SIZE;
|
|
496
|
+
|
|
497
|
+
const root_fetch_size = buffer.readUint32();
|
|
498
|
+
const root_decoded_size = buffer.readUint32();
|
|
499
|
+
|
|
500
|
+
if (root_fetch_size !== frames[0].fetch_size) {
|
|
501
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `root_page_fetch_size is ${root_fetch_size}, the root frame's is ${frames[0].fetch_size}`));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (root_decoded_size !== frames[0].decoded_size) {
|
|
505
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `root_page_decoded_size is ${root_decoded_size}, the root frame's is ${frames[0].decoded_size}`));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Decode one page and check its internal layout.
|
|
512
|
+
*
|
|
513
|
+
* @param {BinaryBuffer} buffer whole file
|
|
514
|
+
* @param {VGeoFrameExtent} frame
|
|
515
|
+
* @param {number} level_count
|
|
516
|
+
* @param {VGeoDefect[]} out
|
|
517
|
+
* @returns {DecodedPage|undefined} `undefined` when the page is too malformed to read further
|
|
518
|
+
*/
|
|
519
|
+
function decode_page(buffer, frame, level_count, out) {
|
|
520
|
+
const page = new DecodedPage();
|
|
521
|
+
|
|
522
|
+
page.page_index = frame.page_index;
|
|
523
|
+
page.frame = frame;
|
|
524
|
+
page.size = frame.decoded_size;
|
|
525
|
+
|
|
526
|
+
const blob = frame.frame_offset
|
|
527
|
+
+ VGEO_FRAME_HEADER_SIZE
|
|
528
|
+
+ VGEO_CHILD_TABLE_ENTRY_SIZE * frame.child_page_count;
|
|
529
|
+
|
|
530
|
+
// Everything below reads the blob, so it has to be in the file first. `check_frames` has
|
|
531
|
+
// already reported a frame that runs past the directory; what this stops is the reading.
|
|
532
|
+
if (blob + frame.stored_size > buffer.capacity) {
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (frame.codec === VGEO_CODEC_RAW) {
|
|
537
|
+
// Nothing to decode: the page *is* those bytes. `check_frames` has already reported it if
|
|
538
|
+
// the two sizes disagree, and with them disagreeing the page's own length is not knowable,
|
|
539
|
+
// so there is nothing left to audit.
|
|
540
|
+
if (frame.stored_size !== frame.decoded_size) {
|
|
541
|
+
return undefined;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
page.buffer = buffer;
|
|
545
|
+
page.base = blob;
|
|
546
|
+
} else if (frame.codec === VGEO_CODEC_LZ4) {
|
|
547
|
+
// Bounded by `check_frames`, which has reported it, before it is believed — so a corrupt
|
|
548
|
+
// length field is a defect rather than an allocation.
|
|
549
|
+
if (frame.decoded_size > vgeo_frame_decode_bound(frame.codec, frame.stored_size)) {
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
page.buffer = new BinaryBuffer();
|
|
554
|
+
page.base = 0;
|
|
555
|
+
|
|
556
|
+
page.buffer.setCapacity(frame.decoded_size);
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
vgeo_decode_frame_blob(page.buffer.raw_bytes, 0, buffer.raw_bytes, frame);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
// Not suppression — the decoder's own message is what says *how* the blob is malformed,
|
|
562
|
+
// and a validator that threw here would be unable to report the one thing it exists to
|
|
563
|
+
// report.
|
|
564
|
+
out.push(new VGeoDefect(VGEO_DEFECT.FRAME, `page ${frame.page_index}: ${error.message}`));
|
|
565
|
+
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
// `check_frames` has already named the codec; there is no decoder to go on with.
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (page.size < VGEO_PAGE_HEADER_SIZE) {
|
|
574
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: decoded page is ${page.size} bytes, shorter than its header`));
|
|
575
|
+
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Everything from here on is a read of the *decoded* page, which under a compressing codec is
|
|
580
|
+
// not the file — so it goes through the page's own buffer and never the one passed in.
|
|
581
|
+
const decoded = page.buffer;
|
|
582
|
+
|
|
583
|
+
page.seek(0);
|
|
584
|
+
|
|
585
|
+
page.group_count = decoded.readUint16();
|
|
586
|
+
page.cluster_count = decoded.readUint16();
|
|
587
|
+
page.min_level = decoded.readUint8();
|
|
588
|
+
page.max_level = decoded.readUint8();
|
|
589
|
+
|
|
590
|
+
page.node_count = decoded.readUint16();
|
|
591
|
+
|
|
592
|
+
page.cluster_table_offset = decoded.readUint32();
|
|
593
|
+
page.payload_offset = decoded.readUint32();
|
|
594
|
+
page.payload_size = decoded.readUint32();
|
|
595
|
+
page.node_table_offset = decoded.readUint32();
|
|
596
|
+
|
|
597
|
+
for (let offset = 24; offset < VGEO_PAGE_OFFSET.METRIC; offset++) {
|
|
598
|
+
if (decoded.raw_bytes[page.base + offset] !== 0) {
|
|
599
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: reserved byte at ${offset} in the page header is not zero`));
|
|
600
|
+
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
page.seek(VGEO_PAGE_OFFSET.METRIC);
|
|
606
|
+
|
|
607
|
+
page.metric = read_metric(decoded);
|
|
608
|
+
|
|
609
|
+
if (page.group_count < 1) {
|
|
610
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: group_count is 0`));
|
|
611
|
+
|
|
612
|
+
return undefined;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (page.cluster_count < 1) {
|
|
616
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: cluster_count is 0`));
|
|
617
|
+
|
|
618
|
+
return undefined;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (page.min_level > page.max_level) {
|
|
622
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: min_level ${page.min_level} exceeds max_level ${page.max_level}`));
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (page.max_level >= level_count) {
|
|
626
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: max_level ${page.max_level} is outside level_count ${level_count}`));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// One node per group is the worst the tree can come to — a leaf holds up to the fanout, and
|
|
630
|
+
// every interior node has at least two children — so 2·groups bounds it whatever the shape.
|
|
631
|
+
if (page.node_count < 1 || page.node_count > 2 * page.group_count) {
|
|
632
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: node_count is ${page.node_count}, outside 1..${2 * page.group_count} for ${page.group_count} groups`));
|
|
633
|
+
|
|
634
|
+
return undefined;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const expected_node_table_offset = VGEO_PAGE_HEADER_SIZE + page.group_count * VGEO_GROUP_RECORD_SIZE;
|
|
638
|
+
|
|
639
|
+
if (page.node_table_offset !== expected_node_table_offset) {
|
|
640
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: node_table_offset is ${page.node_table_offset}, expected ${expected_node_table_offset}`));
|
|
641
|
+
|
|
642
|
+
return undefined;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const expected_cluster_table_offset = page.node_table_offset
|
|
646
|
+
+ page.node_count * VGEO_NODE_RECORD_SIZE;
|
|
647
|
+
|
|
648
|
+
if (page.cluster_table_offset !== expected_cluster_table_offset) {
|
|
649
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: cluster_table_offset is ${page.cluster_table_offset}, expected ${expected_cluster_table_offset}`));
|
|
650
|
+
|
|
651
|
+
return undefined;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const expected_payload_offset = page.cluster_table_offset + page.cluster_count * VGEO_CLUSTER_RECORD_SIZE;
|
|
655
|
+
|
|
656
|
+
if (page.payload_offset !== expected_payload_offset) {
|
|
657
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: payload_offset is ${page.payload_offset}, expected ${expected_payload_offset}`));
|
|
658
|
+
|
|
659
|
+
return undefined;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (page.payload_offset + page.payload_size !== page.size) {
|
|
663
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${frame.page_index}: payload ends at ${page.payload_offset + page.payload_size}, the decoded page is ${page.size} bytes`));
|
|
664
|
+
|
|
665
|
+
return undefined;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
for (let i = 0; i < frame.child_page_count; i++) {
|
|
669
|
+
buffer.position = frame.frame_offset + VGEO_FRAME_HEADER_SIZE + i * VGEO_CHILD_TABLE_ENTRY_SIZE + 8;
|
|
670
|
+
|
|
671
|
+
page.child_pages.push(buffer.readUint32());
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return page;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* A traversal metric, wherever it is carried.
|
|
679
|
+
*/
|
|
680
|
+
class VGeoMetric {
|
|
681
|
+
/**
|
|
682
|
+
* @type {Float32Array}
|
|
683
|
+
*/
|
|
684
|
+
sphere = new Float32Array(4);
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* @type {number}
|
|
688
|
+
*/
|
|
689
|
+
min_self_error = 0;
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* @type {number}
|
|
693
|
+
*/
|
|
694
|
+
max_parent_error = 0;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* @param {BinaryBuffer} buffer positioned at the metric
|
|
699
|
+
* @returns {VGeoMetric}
|
|
700
|
+
*/
|
|
701
|
+
function read_metric(buffer) {
|
|
702
|
+
const result = new VGeoMetric();
|
|
703
|
+
|
|
704
|
+
buffer.readFloat32Array(result.sphere, 0, 4);
|
|
705
|
+
|
|
706
|
+
result.min_self_error = buffer.readFloat32();
|
|
707
|
+
result.max_parent_error = buffer.readFloat32();
|
|
708
|
+
|
|
709
|
+
return result;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Does `outer` contain `inner`? Both are `[centre xyz, radius]`.
|
|
714
|
+
*
|
|
715
|
+
* Slack is a relative epsilon on the radius: the spheres a node aggregates were combined in float32
|
|
716
|
+
* pairwise, and an exact containment test would flag the last bit of that arithmetic as a defect.
|
|
717
|
+
*
|
|
718
|
+
* @param {Float32Array} outer
|
|
719
|
+
* @param {Float32Array} inner
|
|
720
|
+
* @returns {boolean}
|
|
721
|
+
*/
|
|
722
|
+
function sphere_contains(outer, inner) {
|
|
723
|
+
const dx = inner[0] - outer[0];
|
|
724
|
+
const dy = inner[1] - outer[1];
|
|
725
|
+
const dz = inner[2] - outer[2];
|
|
726
|
+
|
|
727
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
728
|
+
|
|
729
|
+
return distance + inner[3] <= outer[3] * (1 + 1e-4) + 1e-4;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* A page's group tree: that it is a tree over exactly this page's groups, and that a node's metric
|
|
734
|
+
* really does bound everything under it.
|
|
735
|
+
*
|
|
736
|
+
* The second half is what makes the structure worth anything. A traversal skips a subtree when the
|
|
737
|
+
* node's `max_parent_error` projects to no more than the threshold, and culls one when its sphere
|
|
738
|
+
* misses the frustum; both are only sound if the node's numbers dominate every group beneath it. A
|
|
739
|
+
* node whose sphere is too small silently drops geometry, and nothing downstream would notice.
|
|
740
|
+
*
|
|
741
|
+
* @param {DecodedPage[]} pages
|
|
742
|
+
* @param {object[][]} page_groups
|
|
743
|
+
* @param {object[][]} page_clusters
|
|
744
|
+
* @param {VGeoDefect[]} out
|
|
745
|
+
*/
|
|
746
|
+
function check_page_nodes(pages, page_groups, page_clusters, out) {
|
|
747
|
+
for (const page of pages) {
|
|
748
|
+
const groups = page_groups[page.page_index];
|
|
749
|
+
const clusters = page_clusters[page.page_index];
|
|
750
|
+
|
|
751
|
+
const buffer = page.buffer;
|
|
752
|
+
|
|
753
|
+
const nodes = [];
|
|
754
|
+
|
|
755
|
+
for (let index = 0; index < page.node_count; index++) {
|
|
756
|
+
const base = page.base + page.node_table_offset + index * VGEO_NODE_RECORD_SIZE;
|
|
757
|
+
|
|
758
|
+
buffer.position = base + VGEO_NODE_OFFSET.METRIC;
|
|
759
|
+
|
|
760
|
+
const node = { metric: read_metric(buffer) };
|
|
761
|
+
|
|
762
|
+
buffer.position = base + VGEO_NODE_OFFSET.CHILD_FIRST;
|
|
763
|
+
|
|
764
|
+
node.child_first = buffer.readUint16();
|
|
765
|
+
node.child_count = buffer.readUint16();
|
|
766
|
+
|
|
767
|
+
const flags = buffer.readUint16();
|
|
768
|
+
|
|
769
|
+
node.is_leaf = (flags & VGEO_NODE_FLAG_LEAF) !== 0;
|
|
770
|
+
|
|
771
|
+
if ((flags & ~VGEO_NODE_FLAG_LEAF) !== 0) {
|
|
772
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: unknown flag bits ${flags}`));
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
nodes.push(node);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* How many times each group is reached through a leaf. Exactly once, or the tree is not a
|
|
780
|
+
* partition of the page and a traversal either misses geometry or draws it twice.
|
|
781
|
+
* @type {Int32Array}
|
|
782
|
+
*/
|
|
783
|
+
const group_hits = new Int32Array(groups.length);
|
|
784
|
+
|
|
785
|
+
let structural = true;
|
|
786
|
+
|
|
787
|
+
nodes.forEach((node, index) => {
|
|
788
|
+
if (node.child_count < 1 || node.child_count > VGEO_NODE_FANOUT) {
|
|
789
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: child_count ${node.child_count} is outside 1..${VGEO_NODE_FANOUT}`));
|
|
790
|
+
|
|
791
|
+
structural = false;
|
|
792
|
+
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const limit = node.is_leaf ? groups.length : nodes.length;
|
|
797
|
+
|
|
798
|
+
if (node.child_first + node.child_count > limit) {
|
|
799
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: children ${node.child_first}..${node.child_first + node.child_count} run past the ${node.is_leaf ? 'group' : 'node'} table's ${limit}`));
|
|
800
|
+
|
|
801
|
+
structural = false;
|
|
802
|
+
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (node.is_leaf) {
|
|
807
|
+
for (let i = 0; i < node.child_count; i++) {
|
|
808
|
+
group_hits[node.child_first + i]++;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (node.child_first <= index) {
|
|
815
|
+
// Breadth-first layout: a parent is always allocated before its children. Without
|
|
816
|
+
// it a reader has no bound on the walk and a malformed file can loop it forever.
|
|
817
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: child run starts at ${node.child_first}, which is not after the parent`));
|
|
818
|
+
|
|
819
|
+
structural = false;
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
if (!structural) {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const unreached = group_hits.reduce((count, hits) => count + (hits === 0 ? 1 : 0), 0);
|
|
828
|
+
const repeated = group_hits.reduce((count, hits) => count + (hits > 1 ? 1 : 0), 0);
|
|
829
|
+
|
|
830
|
+
if (unreached !== 0 || repeated !== 0) {
|
|
831
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index}: the node tree reaches ${unreached} of its ${groups.length} groups not at all and ${repeated} more than once`));
|
|
832
|
+
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* @param {number} index
|
|
838
|
+
* @param {VGeoMetric} covered
|
|
839
|
+
* @param {string} what
|
|
840
|
+
*/
|
|
841
|
+
function expect_bounds(index, covered, what) {
|
|
842
|
+
const metric = nodes[index].metric;
|
|
843
|
+
|
|
844
|
+
if (!sphere_contains(metric.sphere, covered.sphere)) {
|
|
845
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: its sphere does not contain ${what}`));
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (metric.min_self_error > covered.min_self_error) {
|
|
849
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: min_self_error ${metric.min_self_error} is above ${what}'s ${covered.min_self_error}`));
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (metric.max_parent_error < covered.max_parent_error) {
|
|
853
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index} node ${index}: max_parent_error ${metric.max_parent_error} is below ${what}'s ${covered.max_parent_error}`));
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
nodes.forEach((node, index) => {
|
|
858
|
+
for (let i = 0; i < node.child_count; i++) {
|
|
859
|
+
if (!node.is_leaf) {
|
|
860
|
+
expect_bounds(index, nodes[node.child_first + i].metric, `node ${node.child_first + i}`);
|
|
861
|
+
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const group = groups[node.child_first + i];
|
|
866
|
+
|
|
867
|
+
const covered = new VGeoMetric();
|
|
868
|
+
|
|
869
|
+
covered.min_self_error = Number.POSITIVE_INFINITY;
|
|
870
|
+
covered.max_parent_error = 0;
|
|
871
|
+
|
|
872
|
+
let first = true;
|
|
873
|
+
|
|
874
|
+
for (let c = 0; c < group.cluster_count; c++) {
|
|
875
|
+
const cluster = clusters[group.cluster_first + c];
|
|
876
|
+
|
|
877
|
+
if (cluster === undefined) {
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
covered.min_self_error = Math.min(covered.min_self_error, cluster.self_error);
|
|
882
|
+
covered.max_parent_error = Math.max(covered.max_parent_error, cluster.parent_error);
|
|
883
|
+
|
|
884
|
+
if (first) {
|
|
885
|
+
covered.sphere.set(cluster.parent_sphere);
|
|
886
|
+
|
|
887
|
+
first = false;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (first) {
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
expect_bounds(index, covered, `group ${node.child_first + i}`);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
const root = nodes[0];
|
|
900
|
+
|
|
901
|
+
if (
|
|
902
|
+
root !== undefined
|
|
903
|
+
&& (
|
|
904
|
+
!sphere_contains(page.metric.sphere, root.metric.sphere)
|
|
905
|
+
|| page.metric.min_self_error !== root.metric.min_self_error
|
|
906
|
+
|| page.metric.max_parent_error !== root.metric.max_parent_error
|
|
907
|
+
)
|
|
908
|
+
) {
|
|
909
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `page ${page.page_index}: the page header's metric disagrees with its root node's`));
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* @param {BinaryBuffer} buffer whole file
|
|
916
|
+
* @param {VGeoFrameExtent[]} frames
|
|
917
|
+
* @param {DecodedPage[]} pages
|
|
918
|
+
* @param {VGeoDefect[]} out
|
|
919
|
+
*/
|
|
920
|
+
function check_child_tables(buffer, frames, pages, out) {
|
|
921
|
+
for (const page of pages) {
|
|
922
|
+
const frame = page.frame;
|
|
923
|
+
|
|
924
|
+
const seen = new Set();
|
|
925
|
+
|
|
926
|
+
for (let i = 0; i < frame.child_page_count; i++) {
|
|
927
|
+
buffer.position = frame.frame_offset + VGEO_FRAME_HEADER_SIZE + i * VGEO_CHILD_TABLE_ENTRY_SIZE;
|
|
928
|
+
|
|
929
|
+
const child_frame_offset = Number(buffer.readUint64());
|
|
930
|
+
const child_page_index = buffer.readUint32();
|
|
931
|
+
const child_fetch_size = buffer.readUint32();
|
|
932
|
+
const child_decoded_size = buffer.readUint32();
|
|
933
|
+
const reserved = buffer.readUint32();
|
|
934
|
+
|
|
935
|
+
const metric = read_metric(buffer);
|
|
936
|
+
|
|
937
|
+
const child_page = pages.find((candidate) => candidate.page_index === child_page_index);
|
|
938
|
+
|
|
939
|
+
if (child_page !== undefined && child_page.metric !== null) {
|
|
940
|
+
// Copied, so it can be read before the child is fetched — which is the whole point
|
|
941
|
+
// — and therefore able to drift from the truth. Nothing else would catch it: a
|
|
942
|
+
// traversal that trusts a stale metric skips a page it needed and leaves a hole.
|
|
943
|
+
if (
|
|
944
|
+
metric.sphere.some((value, axis) => value !== child_page.metric.sphere[axis])
|
|
945
|
+
|| metric.min_self_error !== child_page.metric.min_self_error
|
|
946
|
+
|| metric.max_parent_error !== child_page.metric.max_parent_error
|
|
947
|
+
) {
|
|
948
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: its metric for page ${child_page_index} disagrees with that page's own`));
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (reserved !== 0) {
|
|
953
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: reserved is not zero`));
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (seen.has(child_page_index)) {
|
|
957
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index}: child page ${child_page_index} appears more than once; the table must be deduplicated`));
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
seen.add(child_page_index);
|
|
961
|
+
|
|
962
|
+
if (child_page_index >= frames.length) {
|
|
963
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: page_index ${child_page_index} does not exist`));
|
|
964
|
+
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (child_page_index === page.page_index) {
|
|
969
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: a page may not name itself; same-page children use the SAME_PAGE sentinel`));
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const child = frames[child_page_index];
|
|
973
|
+
|
|
974
|
+
if (child_frame_offset !== child.frame_offset) {
|
|
975
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: frame_offset ${child_frame_offset}, the frame is at ${child.frame_offset}`));
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (child_frame_offset <= frame.frame_offset) {
|
|
979
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: child frame_offset ${child_frame_offset} is not strictly ahead of ${frame.frame_offset}; file order must be topological`));
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (child_fetch_size !== child.fetch_size) {
|
|
983
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: fetch_size ${child_fetch_size}, the frame's is ${child.fetch_size}`));
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (child_decoded_size !== child.decoded_size) {
|
|
987
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHILD_TABLE, `page ${page.page_index} entry ${i}: decoded_size ${child_decoded_size}, the frame's is ${child.decoded_size}`));
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* @param {DecodedPage} page
|
|
995
|
+
* @param {VGeoDefect[]} out
|
|
996
|
+
* @returns {{cluster_first:number, cluster_count:number, parent_cluster_count:number}[]}
|
|
997
|
+
*/
|
|
998
|
+
function read_groups(page, out) {
|
|
999
|
+
const result = [];
|
|
1000
|
+
|
|
1001
|
+
const buffer = page.buffer;
|
|
1002
|
+
|
|
1003
|
+
let expected_first = 0;
|
|
1004
|
+
|
|
1005
|
+
for (let g = 0; g < page.group_count; g++) {
|
|
1006
|
+
page.seek(VGEO_PAGE_HEADER_SIZE + g * VGEO_GROUP_RECORD_SIZE);
|
|
1007
|
+
|
|
1008
|
+
const group = {
|
|
1009
|
+
cluster_first: buffer.readUint16(),
|
|
1010
|
+
cluster_count: buffer.readUint16(),
|
|
1011
|
+
parent_cluster_count: buffer.readUint16(),
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
if (buffer.readUint16() !== 0) {
|
|
1015
|
+
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index} group ${g}: reserved is not zero`));
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
if (group.cluster_count < 1 || group.cluster_count > VGEO_MAX_GROUP_CLUSTER_COUNT) {
|
|
1019
|
+
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index} group ${g}: cluster_count ${group.cluster_count} is outside [1, ${VGEO_MAX_GROUP_CLUSTER_COUNT}]`));
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (group.cluster_first !== expected_first) {
|
|
1023
|
+
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index} group ${g}: cluster_first ${group.cluster_first}, expected ${expected_first}; a page's groups must tile its cluster records in order`));
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
expected_first = group.cluster_first + group.cluster_count;
|
|
1027
|
+
|
|
1028
|
+
result.push(group);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (expected_first !== page.cluster_count) {
|
|
1032
|
+
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index}: groups cover ${expected_first} clusters, the page holds ${page.cluster_count}`));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
return result;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Decode a cluster's local positions from its payload — the only way to check that the culling
|
|
1040
|
+
* bounds actually bound anything.
|
|
1041
|
+
*
|
|
1042
|
+
* @param {DecodedPage} page
|
|
1043
|
+
* @param {object} cluster
|
|
1044
|
+
* @returns {Float32Array} `vertex_count * 3` coordinates
|
|
1045
|
+
*/
|
|
1046
|
+
function read_cluster_positions(page, cluster) {
|
|
1047
|
+
page.seek(
|
|
1048
|
+
page.payload_offset
|
|
1049
|
+
+ cluster.payload_offset
|
|
1050
|
+
+ vgeo_payload_attribute_offset(0, cluster.vertex_count, cluster.triangle_count, cluster.encoding_flags)
|
|
1051
|
+
);
|
|
1052
|
+
|
|
1053
|
+
const result = new Float32Array(cluster.vertex_count * 3);
|
|
1054
|
+
|
|
1055
|
+
page.buffer.readFloat32Array(result, 0, result.length);
|
|
1056
|
+
|
|
1057
|
+
return result;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* @param {DecodedPage} page
|
|
1062
|
+
* @param {VGeoDefect[]} out
|
|
1063
|
+
* @returns {object[]}
|
|
1064
|
+
*/
|
|
1065
|
+
function read_clusters(page, out) {
|
|
1066
|
+
const result = [];
|
|
1067
|
+
|
|
1068
|
+
const buffer = page.buffer;
|
|
1069
|
+
|
|
1070
|
+
for (let c = 0; c < page.cluster_count; c++) {
|
|
1071
|
+
page.seek(page.cluster_table_offset + c * VGEO_CLUSTER_RECORD_SIZE);
|
|
1072
|
+
|
|
1073
|
+
const cluster = {
|
|
1074
|
+
index: c,
|
|
1075
|
+
self_sphere: read_sphere(buffer),
|
|
1076
|
+
self_error: buffer.readFloat32(),
|
|
1077
|
+
parent_error: buffer.readFloat32(),
|
|
1078
|
+
parent_sphere: read_sphere(buffer),
|
|
1079
|
+
bounds_sphere: read_sphere(buffer),
|
|
1080
|
+
aabb: read_aabb(buffer),
|
|
1081
|
+
child_ref: buffer.readUint16(),
|
|
1082
|
+
child_group: buffer.readUint16(),
|
|
1083
|
+
cone_reserved: buffer.readUint32(),
|
|
1084
|
+
encoding_flags: buffer.readUint32(),
|
|
1085
|
+
payload_offset: buffer.readUint32(),
|
|
1086
|
+
payload_size: buffer.readUint32(),
|
|
1087
|
+
vertex_count: buffer.readUint8(),
|
|
1088
|
+
triangle_count: buffer.readUint8(),
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
if (buffer.readUint16() !== 0 || buffer.readUint64() !== 0n) {
|
|
1092
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `page ${page.page_index} cluster ${c}: reserved tail is not zero`));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
if (cluster.cone_reserved !== 0) {
|
|
1096
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `page ${page.page_index} cluster ${c}: cone_reserved is not zero`));
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
result.push(cluster);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
return result;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Everything about one cluster that its own record settles: counts, flags, payload extent, bounds
|
|
1107
|
+
* that actually bound, and finite metric values.
|
|
1108
|
+
*
|
|
1109
|
+
* @param {DecodedPage} page
|
|
1110
|
+
* @param {object} cluster
|
|
1111
|
+
* @param {number[]} object_sphere
|
|
1112
|
+
* @param {number[]} object_bounds
|
|
1113
|
+
* @param {VGeoDefect[]} out
|
|
1114
|
+
*/
|
|
1115
|
+
function check_cluster_record(page, cluster, object_sphere, object_bounds, out) {
|
|
1116
|
+
const where = `page ${page.page_index} cluster ${cluster.index}`;
|
|
1117
|
+
|
|
1118
|
+
if (cluster.vertex_count < 3 || cluster.vertex_count > VGEO_MAX_CLUSTER_VERTEX_COUNT) {
|
|
1119
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: vertex_count ${cluster.vertex_count} is outside [3, ${VGEO_MAX_CLUSTER_VERTEX_COUNT}]`));
|
|
1120
|
+
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (cluster.triangle_count < 1 || cluster.triangle_count > VGEO_MAX_CLUSTER_TRIANGLE_COUNT) {
|
|
1125
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: triangle_count ${cluster.triangle_count} is outside [1, ${VGEO_MAX_CLUSTER_TRIANGLE_COUNT}]`));
|
|
1126
|
+
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
if ((cluster.encoding_flags & ~VGEO_ENCODING_FLAGS_MASK) !== 0) {
|
|
1131
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: encoding_flags 0x${cluster.encoding_flags.toString(16)} sets a bit that must be clear (skinning bits and reserved bits)`));
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (cluster.payload_offset % 4 !== 0 || cluster.payload_size % 4 !== 0) {
|
|
1135
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: payload offset ${cluster.payload_offset} / size ${cluster.payload_size} is not a multiple of 4`));
|
|
1136
|
+
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
const expected_size = vgeo_payload_size(cluster.vertex_count, cluster.triangle_count, cluster.encoding_flags);
|
|
1141
|
+
|
|
1142
|
+
if (cluster.payload_size !== expected_size) {
|
|
1143
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: payload_size ${cluster.payload_size}, the encoding needs ${expected_size}`));
|
|
1144
|
+
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
if (cluster.payload_offset + cluster.payload_size > page.payload_size) {
|
|
1149
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: payload runs past the page's payload section`));
|
|
1150
|
+
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const index_base = page.base + page.payload_offset + cluster.payload_offset;
|
|
1155
|
+
|
|
1156
|
+
for (let i = 0; i < cluster.triangle_count * 3; i++) {
|
|
1157
|
+
const index = page.buffer.raw_bytes[index_base + i];
|
|
1158
|
+
|
|
1159
|
+
if (index >= cluster.vertex_count) {
|
|
1160
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: local index ${index} at ${i} is outside the cluster's ${cluster.vertex_count} vertices`));
|
|
1161
|
+
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const named_spheres = [
|
|
1167
|
+
['self_sphere', cluster.self_sphere],
|
|
1168
|
+
['parent_sphere', cluster.parent_sphere],
|
|
1169
|
+
['bounds_sphere', cluster.bounds_sphere],
|
|
1170
|
+
];
|
|
1171
|
+
|
|
1172
|
+
for (const [name, sphere] of named_spheres) {
|
|
1173
|
+
if (!sphere.every(Number.isFinite) || sphere[3] < 0) {
|
|
1174
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: ${name} is not a finite non-negative sphere: ${sphere}`));
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
if (!cluster.aabb.every(Number.isFinite)) {
|
|
1179
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: aabb is not finite`));
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
if (!Number.isFinite(cluster.self_error) || cluster.self_error < 0) {
|
|
1183
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: self_error ${cluster.self_error} must be finite and non-negative`));
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
if (Number.isNaN(cluster.parent_error) || cluster.parent_error < 0) {
|
|
1187
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: parent_error ${cluster.parent_error} must be non-negative and not NaN`));
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
if (cluster.parent_error < cluster.self_error) {
|
|
1191
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: parent_error ${cluster.parent_error} is below self_error ${cluster.self_error}`));
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (!sphere_contains_sphere(cluster.parent_sphere, cluster.self_sphere)) {
|
|
1195
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: parent_sphere does not contain self_sphere`));
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
const positions = read_cluster_positions(page, cluster);
|
|
1199
|
+
|
|
1200
|
+
for (let v = 0; v < cluster.vertex_count; v++) {
|
|
1201
|
+
const x = positions[v * 3];
|
|
1202
|
+
const y = positions[v * 3 + 1];
|
|
1203
|
+
const z = positions[v * 3 + 2];
|
|
1204
|
+
|
|
1205
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
|
|
1206
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAYLOAD, `${where}: vertex ${v} is not finite`));
|
|
1207
|
+
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (!sphere_contains_point(cluster.bounds_sphere, x, y, z)) {
|
|
1212
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: bounds_sphere does not contain vertex ${v}`));
|
|
1213
|
+
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (x < cluster.aabb[0] || y < cluster.aabb[1] || z < cluster.aabb[2]
|
|
1218
|
+
|| x > cluster.aabb[3] || y > cluster.aabb[4] || z > cluster.aabb[5]) {
|
|
1219
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: aabb does not contain vertex ${v}`));
|
|
1220
|
+
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (!sphere_contains_point(object_sphere, x, y, z)) {
|
|
1225
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: the header's object_sphere does not contain vertex ${v}`));
|
|
1226
|
+
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
if (x < object_bounds[0] || y < object_bounds[1] || z < object_bounds[2]
|
|
1231
|
+
|| x > object_bounds[3] || y > object_bounds[4] || z > object_bounds[5]) {
|
|
1232
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: the header's object_bounds does not contain vertex ${v}`));
|
|
1233
|
+
|
|
1234
|
+
break;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* The whole cross-page audit: child references resolve and point at a strictly coarser band than the
|
|
1241
|
+
* cluster making them, the DAG has exactly one root, every group's `parent_cluster_count` is exact,
|
|
1242
|
+
* the metric pair a step writes is the same on both sides of that step, and every page is reachable
|
|
1243
|
+
* from the root.
|
|
1244
|
+
*
|
|
1245
|
+
* @param {DecodedPage[]} pages
|
|
1246
|
+
* @param {object[][]} page_groups
|
|
1247
|
+
* @param {object[][]} page_clusters
|
|
1248
|
+
* @param {number} level_count
|
|
1249
|
+
* @param {VGeoDefect[]} out
|
|
1250
|
+
*/
|
|
1251
|
+
function check_dag(pages, page_groups, page_clusters, level_count, out) {
|
|
1252
|
+
/**
|
|
1253
|
+
* How many clusters name each `page:group`, so that `parent_cluster_count` can be counted rather
|
|
1254
|
+
* than trusted.
|
|
1255
|
+
* @type {Map<string, number>}
|
|
1256
|
+
*/
|
|
1257
|
+
const reference_counts = new Map();
|
|
1258
|
+
|
|
1259
|
+
for (const page of pages) {
|
|
1260
|
+
for (const cluster of page_clusters[page.page_index]) {
|
|
1261
|
+
const where = `page ${page.page_index} cluster ${cluster.index}`;
|
|
1262
|
+
|
|
1263
|
+
if (cluster.child_ref === VGEO_NO_CHILD) {
|
|
1264
|
+
if (cluster.child_group !== VGEO_NO_GROUP) {
|
|
1265
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: child_ref is NO_CHILD but child_group is ${cluster.child_group}`));
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
if (cluster.self_error !== 0) {
|
|
1269
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: a leaf must carry self_error 0, has ${cluster.self_error}`));
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
if (cluster.child_group === VGEO_NO_GROUP) {
|
|
1276
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: child_group is NO_GROUP but child_ref is ${cluster.child_ref}`));
|
|
1277
|
+
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
let child_page_index;
|
|
1282
|
+
|
|
1283
|
+
if (cluster.child_ref === VGEO_SAME_PAGE) {
|
|
1284
|
+
if (page.min_level === page.max_level) {
|
|
1285
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: SAME_PAGE is legal only in a page whose band spans more than one level`));
|
|
1286
|
+
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
child_page_index = page.page_index;
|
|
1291
|
+
} else {
|
|
1292
|
+
if (cluster.child_ref > VGEO_MAX_CHILD_REF || cluster.child_ref >= page.child_pages.length) {
|
|
1293
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: child_ref ${cluster.child_ref} is outside the page's ${page.child_pages.length} child-table entries`));
|
|
1294
|
+
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
child_page_index = page.child_pages[cluster.child_ref];
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
if (pages[child_page_index] === undefined) {
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
if (
|
|
1306
|
+
cluster.child_ref !== VGEO_SAME_PAGE
|
|
1307
|
+
&& pages[child_page_index].max_level >= page.max_level
|
|
1308
|
+
) {
|
|
1309
|
+
// The band is what puts pages in file order, and a child sitting at or above its
|
|
1310
|
+
// parent's band is a reference that points backwards — the one thing that makes the
|
|
1311
|
+
// file untraversable in a single forward pass.
|
|
1312
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `${where}: its child group is in page ${child_page_index}, whose band tops out at ${pages[child_page_index].max_level} — not below this page's ${page.max_level}`));
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
const child_groups = page_groups[child_page_index];
|
|
1316
|
+
|
|
1317
|
+
if (cluster.child_group >= child_groups.length) {
|
|
1318
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `${where}: child_group ${cluster.child_group} does not exist in page ${child_page_index}`));
|
|
1319
|
+
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
const key = `${child_page_index}:${cluster.child_group}`;
|
|
1324
|
+
|
|
1325
|
+
reference_counts.set(key, (reference_counts.get(key) ?? 0) + 1);
|
|
1326
|
+
|
|
1327
|
+
const group = child_groups[cluster.child_group];
|
|
1328
|
+
|
|
1329
|
+
for (let i = 0; i < group.cluster_count; i++) {
|
|
1330
|
+
const child = page_clusters[child_page_index][group.cluster_first + i];
|
|
1331
|
+
|
|
1332
|
+
if (child === undefined) {
|
|
1333
|
+
break;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
if (child.parent_error !== cluster.self_error) {
|
|
1337
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: self_error ${cluster.self_error} disagrees with the consumed cluster's parent_error ${child.parent_error}; one group step writes one value on both sides`));
|
|
1338
|
+
|
|
1339
|
+
break;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
if (child.parent_sphere.some((value, axis) => value !== cluster.self_sphere[axis])) {
|
|
1343
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `${where}: self_sphere disagrees with the consumed cluster's parent_sphere`));
|
|
1344
|
+
|
|
1345
|
+
break;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Exactly one root. The runtime's cut starts at the root and walks down; a second parentless
|
|
1352
|
+
// cluster is a second entry point it would never visit, and its whole subtree with it. It is
|
|
1353
|
+
// also the shape a build that stopped converging leaves behind, which is the likelier cause.
|
|
1354
|
+
const roots = [];
|
|
1355
|
+
|
|
1356
|
+
for (const page of pages) {
|
|
1357
|
+
for (const cluster of page_clusters[page.page_index]) {
|
|
1358
|
+
if (cluster.parent_error === Number.POSITIVE_INFINITY) {
|
|
1359
|
+
roots.push({ page: page.page_index, cluster: cluster.index });
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
const where_roots = roots.slice(0, 4).map((root) => `page ${root.page} cluster ${root.cluster}`);
|
|
1365
|
+
|
|
1366
|
+
if (roots.length !== 1) {
|
|
1367
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CLUSTER, `the DAG has ${roots.length} root clusters, expected exactly one${roots.length === 0 ? '' : `: ${where_roots.join(', ')}`}`));
|
|
1368
|
+
} else if (roots[0].page !== 0) {
|
|
1369
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_LAYOUT, `the root cluster is in ${where_roots[0]}, not the always-resident root page`));
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
for (const page of pages) {
|
|
1373
|
+
page_groups[page.page_index].forEach((group, group_index) => {
|
|
1374
|
+
const counted = reference_counts.get(`${page.page_index}:${group_index}`) ?? 0;
|
|
1375
|
+
|
|
1376
|
+
if (group.parent_cluster_count !== counted) {
|
|
1377
|
+
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index} group ${group_index}: parent_cluster_count is ${group.parent_cluster_count}, ${counted} clusters name it`));
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const is_root_group = page.page_index === 0 && page.max_level === level_count - 1;
|
|
1381
|
+
|
|
1382
|
+
if (counted === 0 && !is_root_group) {
|
|
1383
|
+
out.push(new VGeoDefect(VGEO_DEFECT.GROUP, `page ${page.page_index} group ${group_index}: no cluster names it, yet it is not one of the root page's topmost-level groups`));
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
for (let i = 0; i < group.cluster_count; i++) {
|
|
1387
|
+
const cluster = page_clusters[page.page_index][group.cluster_first + i];
|
|
1388
|
+
|
|
1389
|
+
if (cluster === undefined) {
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
const is_root = cluster.parent_error === Number.POSITIVE_INFINITY;
|
|
1394
|
+
|
|
1395
|
+
if (is_root !== (counted === 0)) {
|
|
1396
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `page ${page.page_index} cluster ${cluster.index}: parent_error is ${cluster.parent_error} while its group has ${counted} parents; +Infinity holds exactly for a group nothing consumes`));
|
|
1397
|
+
|
|
1398
|
+
break;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
if (is_root && cluster.parent_sphere.some((value, axis) => value !== cluster.self_sphere[axis])) {
|
|
1402
|
+
out.push(new VGeoDefect(VGEO_DEFECT.METRIC, `page ${page.page_index} cluster ${cluster.index}: a root's parent_sphere must duplicate its self_sphere`));
|
|
1403
|
+
|
|
1404
|
+
break;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
const reached = new Set([0]);
|
|
1411
|
+
const queue = [0];
|
|
1412
|
+
|
|
1413
|
+
while (queue.length > 0) {
|
|
1414
|
+
const page_index = queue.pop();
|
|
1415
|
+
|
|
1416
|
+
for (const child of pages[page_index].child_pages) {
|
|
1417
|
+
if (pages[child] !== undefined && !reached.has(child)) {
|
|
1418
|
+
reached.add(child);
|
|
1419
|
+
queue.push(child);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
for (const page of pages) {
|
|
1425
|
+
if (!reached.has(page.page_index)) {
|
|
1426
|
+
out.push(new VGeoDefect(VGEO_DEFECT.REACHABILITY, `page ${page.page_index} is not reachable from the root page`));
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* @param {DecodedPage[]} pages
|
|
1433
|
+
* @param {number} level_count
|
|
1434
|
+
* @param {VGeoDefect[]} out
|
|
1435
|
+
*/
|
|
1436
|
+
function check_page_order(pages, level_count, out) {
|
|
1437
|
+
const root = pages[0];
|
|
1438
|
+
|
|
1439
|
+
if (root === undefined) {
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
if (root.max_level !== level_count - 1) {
|
|
1444
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_ORDER, `the root page's max_level is ${root.max_level}, the topmost level is ${level_count - 1}`));
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
for (let i = 1; i < pages.length; i++) {
|
|
1448
|
+
if (pages[i].max_level > pages[i - 1].max_level) {
|
|
1449
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_ORDER, `page ${i} has max_level ${pages[i].max_level} after page ${i - 1}'s ${pages[i - 1].max_level}; file order must run coarse first`));
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
if (pages[i].max_level === level_count - 1) {
|
|
1453
|
+
out.push(new VGeoDefect(VGEO_DEFECT.PAGE_ORDER, `page ${i} also carries the topmost level; the root page must hold it complete`));
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* The directory is derived data: rebuild it from a frame scan and require the file's copy to match
|
|
1460
|
+
* byte for byte, then verify its block checksums.
|
|
1461
|
+
*
|
|
1462
|
+
* @param {BinaryBuffer} buffer whole file
|
|
1463
|
+
* @param {VGeoFrameExtent[]} frames
|
|
1464
|
+
* @param {DecodedPage[]} pages
|
|
1465
|
+
* @param {VGeoDefect[]} out
|
|
1466
|
+
*/
|
|
1467
|
+
function check_directory(buffer, frames, pages, out) {
|
|
1468
|
+
buffer.position = VGEO_HEADER_OFFSET.TOTAL_PAGES;
|
|
1469
|
+
|
|
1470
|
+
const total_pages = buffer.readUint32();
|
|
1471
|
+
|
|
1472
|
+
buffer.position = VGEO_HEADER_OFFSET.DIRECTORY_OFFSET;
|
|
1473
|
+
|
|
1474
|
+
const directory_offset = Number(buffer.readUint64());
|
|
1475
|
+
const dir_block_checksums_offset = Number(buffer.readUint64());
|
|
1476
|
+
|
|
1477
|
+
const reconstructed = new BinaryBuffer();
|
|
1478
|
+
|
|
1479
|
+
reconstructed.setCapacity(total_pages * VGEO_DIRECTORY_RECORD_SIZE);
|
|
1480
|
+
|
|
1481
|
+
for (let i = 0; i < Math.min(total_pages, frames.length); i++) {
|
|
1482
|
+
const frame = frames[i];
|
|
1483
|
+
const page = pages[i];
|
|
1484
|
+
|
|
1485
|
+
reconstructed.position = i * VGEO_DIRECTORY_RECORD_SIZE;
|
|
1486
|
+
|
|
1487
|
+
reconstructed.writeUint64(BigInt(frame.frame_offset));
|
|
1488
|
+
reconstructed.writeUint32(frame.fetch_size);
|
|
1489
|
+
reconstructed.writeUint32(frame.decoded_size);
|
|
1490
|
+
|
|
1491
|
+
if (page === undefined) {
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
reconstructed.writeUint8(page.min_level);
|
|
1496
|
+
reconstructed.writeUint8(page.max_level);
|
|
1497
|
+
reconstructed.writeUint16(i === 0 ? VGEO_DIRECTORY_FLAG_ROOT_PAGE : 0);
|
|
1498
|
+
reconstructed.writeUint16(page.group_count);
|
|
1499
|
+
reconstructed.writeUint16(page.cluster_count);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const stored = buffer.raw_bytes;
|
|
1503
|
+
const expected = reconstructed.raw_bytes;
|
|
1504
|
+
|
|
1505
|
+
for (let i = 0; i < expected.length; i++) {
|
|
1506
|
+
if (stored[directory_offset + i] !== expected[i]) {
|
|
1507
|
+
const record = (i / VGEO_DIRECTORY_RECORD_SIZE) | 0;
|
|
1508
|
+
|
|
1509
|
+
out.push(new VGeoDefect(VGEO_DEFECT.DIRECTORY, `directory record ${record} disagrees with a frame scan at byte ${i % VGEO_DIRECTORY_RECORD_SIZE}: the file has ${stored[directory_offset + i]}, the frames say ${expected[i]}`));
|
|
1510
|
+
|
|
1511
|
+
break;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
buffer.position = VGEO_HEADER_OFFSET.ASSET_ID + 8;
|
|
1516
|
+
|
|
1517
|
+
const asset_id_high = buffer.readUint64();
|
|
1518
|
+
|
|
1519
|
+
const block_count = vgeo_directory_block_count(total_pages);
|
|
1520
|
+
|
|
1521
|
+
for (let block = 0; block < block_count; block++) {
|
|
1522
|
+
buffer.position = dir_block_checksums_offset + block * 8;
|
|
1523
|
+
|
|
1524
|
+
const stored_checksum = buffer.readUint64();
|
|
1525
|
+
|
|
1526
|
+
const computed = vgeo_compute_directory_block_checksum(
|
|
1527
|
+
buffer, directory_offset, total_pages, block, asset_id_high
|
|
1528
|
+
);
|
|
1529
|
+
|
|
1530
|
+
if (stored_checksum !== computed) {
|
|
1531
|
+
out.push(new VGeoDefect(VGEO_DEFECT.CHECKSUM, `directory block ${block}: checksum is ${stored_checksum.toString(16)}, computed ${computed.toString(16)}`));
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* Audit a `.vgeo` file.
|
|
1538
|
+
*
|
|
1539
|
+
* @param {BinaryBuffer} buffer the whole file, with `capacity` equal to its length
|
|
1540
|
+
* @returns {VGeoDefect[]} in discovery order; empty when the file satisfies every invariant
|
|
1541
|
+
*/
|
|
1542
|
+
export function vgeo_validate_container(buffer) {
|
|
1543
|
+
const out = [];
|
|
1544
|
+
|
|
1545
|
+
if (!check_header(buffer, out)) {
|
|
1546
|
+
return out;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
buffer.position = VGEO_HEADER_OFFSET.LEVEL_COUNT;
|
|
1550
|
+
|
|
1551
|
+
const level_count = buffer.readUint8();
|
|
1552
|
+
|
|
1553
|
+
buffer.position = VGEO_HEADER_OFFSET.TOTAL_CLUSTERS;
|
|
1554
|
+
|
|
1555
|
+
const total_clusters = Number(buffer.readUint64());
|
|
1556
|
+
const total_groups = Number(buffer.readUint64());
|
|
1557
|
+
|
|
1558
|
+
buffer.position = VGEO_HEADER_OFFSET.OBJECT_SPHERE;
|
|
1559
|
+
|
|
1560
|
+
const object_sphere = read_sphere(buffer);
|
|
1561
|
+
const object_bounds = read_aabb(buffer);
|
|
1562
|
+
|
|
1563
|
+
check_hashes(buffer, out);
|
|
1564
|
+
|
|
1565
|
+
const frames = vgeo_scan_frames(buffer);
|
|
1566
|
+
|
|
1567
|
+
check_frames(buffer, frames, out);
|
|
1568
|
+
|
|
1569
|
+
/**
|
|
1570
|
+
* @type {DecodedPage[]}
|
|
1571
|
+
*/
|
|
1572
|
+
const pages = [];
|
|
1573
|
+
|
|
1574
|
+
for (const frame of frames) {
|
|
1575
|
+
const page = decode_page(buffer, frame, level_count, out);
|
|
1576
|
+
|
|
1577
|
+
if (page === undefined) {
|
|
1578
|
+
return out;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
pages.push(page);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
check_child_tables(buffer, frames, pages, out);
|
|
1585
|
+
check_page_order(pages, level_count, out);
|
|
1586
|
+
|
|
1587
|
+
const page_groups = [];
|
|
1588
|
+
const page_clusters = [];
|
|
1589
|
+
|
|
1590
|
+
let counted_clusters = 0;
|
|
1591
|
+
let counted_groups = 0;
|
|
1592
|
+
|
|
1593
|
+
for (const page of pages) {
|
|
1594
|
+
const groups = read_groups(page, out);
|
|
1595
|
+
const clusters = read_clusters(page, out);
|
|
1596
|
+
|
|
1597
|
+
page_groups.push(groups);
|
|
1598
|
+
page_clusters.push(clusters);
|
|
1599
|
+
|
|
1600
|
+
counted_groups += groups.length;
|
|
1601
|
+
counted_clusters += clusters.length;
|
|
1602
|
+
|
|
1603
|
+
for (const cluster of clusters) {
|
|
1604
|
+
check_cluster_record(page, cluster, object_sphere, object_bounds, out);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
if (counted_clusters !== total_clusters) {
|
|
1609
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `total_clusters is ${total_clusters}, the pages hold ${counted_clusters}`));
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
if (counted_groups !== total_groups) {
|
|
1613
|
+
out.push(new VGeoDefect(VGEO_DEFECT.HEADER, `total_groups is ${total_groups}, the pages hold ${counted_groups}`));
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
check_page_nodes(pages, page_groups, page_clusters, out);
|
|
1617
|
+
|
|
1618
|
+
check_dag(pages, page_groups, page_clusters, level_count, out);
|
|
1619
|
+
check_directory(buffer, frames, pages, out);
|
|
1620
|
+
|
|
1621
|
+
return out;
|
|
1622
|
+
}
|