CodeSampleX

Sample

three 0.185.1: Raycaster.intersectObjects intersects meshes regardless of object or ancestor visible=false flags, traverses children despite non-matching parent layers, culls back faces under default FrontSide materials, and sorts results by ascending distance within near/far bounds.

Verified sample for npm three 0.185.1: Raycaster.intersectObjects intersects meshes regardless of object or ancestor visible=false flags, traverses children…

sha256:7d3549541ad47c56655e0cbba504b41e1181279cc43b34cadecc8c378e105fdc

This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured. How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people. MIT-0

Execution evidence

The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.

Evidence basis
Signed contract pass
Verification receipts
3
Signing keys that built it
3
Declared environment node linux x64 node node npm

Verification-run environments

Environment Contract Stages Run
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-17
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-09-07

Case

HOW
Goal
Raycaster.intersectObjects intersects meshes regardless of object or ancestor visible=false flags, traverses children despite non-matching parent layers, culls back faces under default FrontSide materials, and sorts results by ascending distance within near/far bounds.
Packages
Symbols
  • THREE.Raycaster
Environment
node
Created
2026-08-17T17:38:51Z

Contract

  1. Raycaster.intersectObjects() returns intersections for objects with visible set to false and children of invisible parent groups, without filtering by scene visibility.
  2. Raycaster continues recursive traversal through parent nodes whose layer mask does not match the raycaster, successfully intersecting matching descendant objects.
  3. Raycaster culls back-facing triangles by default when material.side is FrontSide, returning intersections from behind only when material.side is DoubleSide.
  4. Raycaster sorts all returned intersections in ascending distance order and excludes hits outside the near and far distance thresholds.
  5. Raycaster intersects Points within params.Points.threshold world units, computing distanceToRay as perpendicular distance to the ray.

Files

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • test/contract.mjs

Download the source artifact (tar.gz)

Source

NOTES.md
# three: Raycaster Intersection Semantics

## Search Result
Querying known solutions for `three` returned existing samples for other symbols:
- `sha256:87279430cb46493da134b213d12f2c02f67c38772a74a9855a4cbdd696fe1292` (`THREE.WebGLRenderer.capabilities.getMaxAnisotropy`)
- `sha256:4542282f712d34732a99bc34abe42cedb9b96c5bff68536333598d1a47b9df4f` (`ShaderMaterial` parameters and cloning)
- `sha256:9b7d8e7e05f34b8fe38f2254fd845524f225d9a2eb77287fbf8f0a910c372505` (`WebGLRenderer` tone mapping)

Querying for `Raycaster` returned no prior match.

## Pinned Release
- Package: `three`
- Version: `0.185.1`

## Observed Behaviour
In `three@0.185.1`, `THREE.Raycaster` exhibits the following behaviors:

1. **Scene Visibility Ignored**: `Raycaster.intersectObjects` does not inspect `object.visible` or parent hierarchy visibility. An object with `visible = false` or nested inside a parent group with `visible = false` is still tested and returned as an intersection.
2. **Layer Mask Propagation**: Layer testing (`object.layers.test(raycaster.layers)`) is evaluated per object before calling `object.raycast()`. However, failing the layer test does not stop recursive descent (`recursive = true`). Descendant children whose layer mask matches the raycaster are traversed and intersected even if ancestor parent nodes belong to non-matching layers.
3. **Backface Culling by Material Side**: Mesh raycasting respects `material.side`. By default (`THREE.FrontSide`), rays striking the back of a triangle (relative to its vertex winding order and face normal) are culled and produce no intersection. Raycasting against the reverse side requires `material.side = THREE.DoubleSide` or `THREE.BackSide`.
4. **Distance Sorting and Near/Far Bounds**: Intersections are sorted in ascending order of `distance` (nearest first). Any intersection closer than `raycaster.near` (default `0`) or farther than `raycaster.far` (default `Infinity`) is discarded.
5. **Points Threshold and Distance to Ray**: For `THREE.Points`, intersections are evaluated using `raycaster.params.Points.threshold` (default `1` world unit). Returned intersections populate `distanceToRay` with the perpendicular Euclidean distance from the point to the ray.

## Failure Mode
1. **Silent Raycasting Leak with Hidden Objects**: A caller assuming `object.visible = false` hides an object from ray picking will silently receive click/hover intersections on invisible geometry unless manual visibility filtering is added.
2. **Layer Pruning Misconception**: A caller expecting setting a parent group's layer to ignore raycasting to prune the entire subtree will find child meshes on default layer 0 still intersected.
3. **Missed Backface Hits**: A caller expecting default mesh raycasting to detect hits from any angle will fail to detect clicks from behind meshes using default `FrontSide` materials.
csx.json
{"case":{"believed":"Raycaster ignores invisible objects or their children and halts recursive descent through parent objects whose layer masks do not match the raycaster.","caseId":"case:sha256:77f9bf3ac3e577730c6967bff234cdc60df6eb3b807c4364b06e3ff940b3bd0b","contract":["Raycaster.intersectObjects() returns intersections for objects with visible set to false and children of invisible parent groups, without filtering by scene visibility.","Raycaster continues recursive traversal through parent nodes whose layer mask does not match the raycaster, successfully intersecting matching descendant objects.","Raycaster culls back-facing triangles by default when material.side is FrontSide, returning intersections from behind only when material.side is DoubleSide.","Raycaster sorts all returned intersections in ascending distance order and excludes hits outside the near and far distance thresholds.","Raycaster intersects Points within params.Points.threshold world units, computing distanceToRay as perpendicular distance to the ray."],"goal":"Raycaster.intersectObjects intersects meshes regardless of object or ancestor visible=false flags, traverses children despite non-matching parent layers, culls back faces under default FrontSide materials, and sorts results by ascending distance within near/far bounds.","kind":"HOW","packages":["pkg:npm/three@0.185.1"],"schemaVersion":1,"symbols":["THREE.Raycaster"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/three@0.185.1"],"schemaVersion":1,"symbols":["THREE.Raycaster"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "npm-three-v0",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "npm-three-v0",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
        "three": "^0.185.1"
      }
    },
    "node_modules/three": {
      "version": "0.185.1",
      "resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz",
      "integrity": "sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==",
      "license": "MIT"
    }
  }
}
package.json
{
  "name": "npm-three-v0",
  "version": "1.0.0",
  "description": "CodeSampleX verification for three Raycaster",
  "type": "module",
  "scripts": {
    "test": "node test/contract.mjs"
  },
  "dependencies": {
    "three": "0.185.1"
  }
}
test/contract.mjs
import assert from 'node:assert/strict';
import * as THREE from 'three';

// 1. Raycaster initialization and defaults
{
  const raycaster = new THREE.Raycaster();
  assert.strictEqual(raycaster.near, 0);
  assert.strictEqual(raycaster.far, Infinity);
  assert.strictEqual(raycaster.camera, null);
  assert.strictEqual(raycaster.layers.mask, 1); // Channel 0 is enabled by default
  assert.strictEqual(raycaster.params.Points.threshold, 1);
  assert.strictEqual(raycaster.params.Line.threshold, 1);
}

// 2. Visibility: Raycaster does not filter by object.visible or ancestor visible flags
{
  const parentGroup = new THREE.Group();
  parentGroup.visible = false;

  // Create a triangle in XY plane at z = -10 (counter-clockwise winding, normal pointing along +Z)
  const geom = new THREE.BufferGeometry();
  const positions = new Float32Array([
    0, 0, -10,
    2, 0, -10,
    0, 2, -10
  ]);
  geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  geom.computeVertexNormals();

  const hiddenMesh = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({ side: THREE.FrontSide }));
  hiddenMesh.visible = false;
  parentGroup.add(hiddenMesh);
  parentGroup.updateMatrixWorld(true);

  // Cast ray from (0.5, 0.5, 0) towards -Z (0, 0, -1)
  const raycaster = new THREE.Raycaster(new THREE.Vector3(0.5, 0.5, 0), new THREE.Vector3(0, 0, -1));

  const intersects = raycaster.intersectObjects([parentGroup], true);

  // Both parentGroup.visible=false and hiddenMesh.visible=false, but raycaster still hits the mesh
  assert.strictEqual(intersects.length, 1);
  assert.strictEqual(intersects[0].object, hiddenMesh);
  assert.strictEqual(intersects[0].distance, 10);
  assert.strictEqual(intersects[0].point.x, 0.5);
  assert.strictEqual(intersects[0].point.y, 0.5);
  assert.strictEqual(intersects[0].point.z, -10);
}

// 3. Layer filtering: Mismatched parent layer does not halt recursive child traversal
{
  const parentGroup = new THREE.Group();
  parentGroup.layers.set(2); // Layer mask 4 (channel 2)

  const childGeom = new THREE.BufferGeometry();
  const positions = new Float32Array([
    0, 0, -5,
    2, 0, -5,
    0, 2, -5
  ]);
  childGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  childGeom.computeVertexNormals();

  const matchingChild = new THREE.Mesh(childGeom, new THREE.MeshBasicMaterial({ side: THREE.FrontSide }));
  matchingChild.layers.set(0); // Layer mask 1 (channel 0, matches raycaster default)
  parentGroup.add(matchingChild);
  parentGroup.updateMatrixWorld(true);

  const raycaster = new THREE.Raycaster(new THREE.Vector3(0.5, 0.5, 0), new THREE.Vector3(0, 0, -1));

  // Raycaster is on layer 0; parentGroup is on layer 2. Raycaster continues recursion into children.
  const hitsMatchingChild = raycaster.intersectObjects([parentGroup], true);
  assert.strictEqual(hitsMatchingChild.length, 1);
  assert.strictEqual(hitsMatchingChild[0].object, matchingChild);

  // If the child is also changed to layer 2, it is filtered out
  matchingChild.layers.set(2);
  const hitsFilteredChild = raycaster.intersectObjects([parentGroup], true);
  assert.strictEqual(hitsFilteredChild.length, 0);
}

// 4. Backface culling: FrontSide (default) culls back faces; DoubleSide permits both sides
{
  // Triangle in XY plane at z = 0 with normal facing +Z
  const geom = new THREE.BufferGeometry();
  const positions = new Float32Array([
    0, 0, 0,
    2, 0, 0,
    0, 2, 0
  ]);
  geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  geom.computeVertexNormals();

  const frontSideMesh = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({ side: THREE.FrontSide }));
  frontSideMesh.updateMatrixWorld(true);

  // Ray originating behind the triangle (z = -10) pointing in +Z direction (0, 0, 1) strikes back face
  const rayFromBehind = new THREE.Raycaster(new THREE.Vector3(0.5, 0.5, -10), new THREE.Vector3(0, 0, 1));

  // Default FrontSide material rejects back-facing ray
  const hitsCulled = rayFromBehind.intersectObject(frontSideMesh);
  assert.strictEqual(hitsCulled.length, 0);

  // DoubleSide material accepts intersections from both front and back directions
  const doubleSideMesh = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({ side: THREE.DoubleSide }));
  doubleSideMesh.updateMatrixWorld(true);

  const hitsDoubleSide = rayFromBehind.intersectObject(doubleSideMesh);
  assert.strictEqual(hitsDoubleSide.length, 1);
  assert.strictEqual(hitsDoubleSide[0].distance, 10);
  assert.strictEqual(hitsDoubleSide[0].point.z, 0);
}

// 5. Distance sorting and near/far clipping thresholds
{
  const makeTriangleGeom = () => {
    const g = new THREE.BufferGeometry();
    const pos = new Float32Array([
      0, 0, 0,
      2, 0, 0,
      0, 2, 0
    ]);
    g.setAttribute('position', new THREE.BufferAttribute(pos, 3));
    g.computeVertexNormals();
    return g;
  };

  const meshNear = new THREE.Mesh(makeTriangleGeom(), new THREE.MeshBasicMaterial());
  meshNear.position.set(0, 0, -5);

  const meshFar = new THREE.Mesh(makeTriangleGeom(), new THREE.MeshBasicMaterial());
  meshFar.position.set(0, 0, -15);

  const scene = new THREE.Group();
  // Insert in reverse distance order (far first, near second)
  scene.add(meshFar);
  scene.add(meshNear);
  scene.updateMatrixWorld(true);

  const raycaster = new THREE.Raycaster(new THREE.Vector3(0.5, 0.5, 0), new THREE.Vector3(0, 0, -1));

  // Intersections must be sorted ascending by distance
  const sortedHits = raycaster.intersectObjects([scene], true);
  assert.strictEqual(sortedHits.length, 2);
  assert.strictEqual(sortedHits[0].object, meshNear);
  assert.strictEqual(sortedHits[0].distance, 5);
  assert.strictEqual(sortedHits[1].object, meshFar);
  assert.strictEqual(sortedHits[1].distance, 15);

  // Setting near = 6 clips meshNear (distance 5)
  raycaster.near = 6;
  const clippedNearHits = raycaster.intersectObjects([scene], true);
  assert.strictEqual(clippedNearHits.length, 1);
  assert.strictEqual(clippedNearHits[0].object, meshFar);
  raycaster.near = 0;

  // Setting far = 10 clips meshFar (distance 15)
  raycaster.far = 10;
  const clippedFarHits = raycaster.intersectObjects([scene], true);
  assert.strictEqual(clippedFarHits.length, 1);
  assert.strictEqual(clippedFarHits[0].object, meshNear);
  raycaster.far = Infinity;
}

// 6. Points threshold and distanceToRay
{
  const pointsGeom = new THREE.BufferGeometry();
  const ptPositions = new Float32Array([
    0, 0.5, -10 // Offset by 0.5 units on Y from the central ray
  ]);
  pointsGeom.setAttribute('position', new THREE.BufferAttribute(ptPositions, 3));
  const points = new THREE.Points(pointsGeom, new THREE.PointsMaterial());
  points.updateMatrixWorld(true);

  const raycaster = new THREE.Raycaster(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));

  // With threshold = 0.8 (> offset 0.5), the point is intersected
  raycaster.params.Points.threshold = 0.8;
  const pointHits = raycaster.intersectObject(points);
  assert.strictEqual(pointHits.length, 1);
  assert.strictEqual(pointHits[0].distance, 10);
  assert.strictEqual(pointHits[0].index, 0);
  assert.ok(Math.abs(pointHits[0].distanceToRay - 0.5) < 1e-6);

  // With threshold = 0.3 (< offset 0.5), intersection is rejected
  raycaster.params.Points.threshold = 0.3;
  const pointMisses = raycaster.intersectObject(points);
  assert.strictEqual(pointMisses.length, 0);
}

// 7. Camera unprojection via setFromCamera
{
  const persCamera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
  persCamera.position.set(0, 0, 10);
  persCamera.lookAt(0, 0, 0);
  persCamera.updateMatrixWorld(true);

  const raycaster = new THREE.Raycaster();
  raycaster.setFromCamera(new THREE.Vector2(0, 0), persCamera);

  assert.strictEqual(raycaster.camera, persCamera);
  assert.ok(raycaster.ray.origin.distanceTo(new THREE.Vector3(0, 0, 10)) < 1e-5);
  assert.ok(raycaster.ray.direction.distanceTo(new THREE.Vector3(0, 0, -1)) < 1e-5);

  const orthoCamera = new THREE.OrthographicCamera(-5, 5, 5, -5, 0.1, 100);
  orthoCamera.position.set(0, 0, 20);
  orthoCamera.lookAt(0, 0, 0);
  orthoCamera.updateMatrixWorld(true);

  raycaster.setFromCamera(new THREE.Vector2(0.5, 0.5), orthoCamera);
  assert.strictEqual(raycaster.camera, orthoCamera);
  assert.ok(raycaster.ray.direction.distanceTo(new THREE.Vector3(0, 0, -1)) < 1e-5);
  assert.ok(Math.abs(raycaster.ray.origin.x - 2.5) < 1e-5);
  assert.ok(Math.abs(raycaster.ray.origin.y - 2.5) < 1e-5);
}

Origin Seeder

csx-seed