Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,17 @@ Create a test file at a location matching the system's purpose:

Look at `test/ecs/events/videoEventsSystem.spec.ts` for a reference test structure.

## ColliderLayer mask semantics

When generating helpers or tests for components with a `collision_mask` / `collisionMask` field of type `ColliderLayer`, keep these rules in mind (full table + examples in `unity-explorer/docs/how-to-implement-new-sdk-components.md` under "ColliderLayer mask semantics"):

- **Additive avatar semantics:** main player is tagged with both `CL_PLAYER` and `CL_MAIN_PLAYER`; remote avatars only with `CL_PLAYER`.
- **Unified main-player qualification** (Raycast + TriggerArea): the main player qualifies only when the mask contains `CL_PLAYER` or `CL_MAIN_PLAYER` (`PLAYER_QUALIFYING_BITS`). `CL_PHYSICS`, `CL_POINTER`, `CL_CUSTOM*`, and `CL_NONE` do NOT qualify the main player.
- **`CL_PHYSICS` targets scene-mesh walls / floors**, not the character. Scenes that need to detect the player must opt in via `CL_PLAYER` or `CL_MAIN_PLAYER`.
- **Remote avatars qualify only on `CL_PLAYER`.**
- **`CL_MAIN_PLAYER`-only TriggerArea** is fast-pathed via `targetOnlyMainPlayer` / `TargetTransform` early-out. Any other mask must NOT enable this.
- **Scene-mesh routing:** for `MeshCollider` / `GltfContainer`, avatar-only masks (`CL_PLAYER` / `CL_MAIN_PLAYER`, no other bits) route to the `SDKAvatarHit` Unity layer — pass-through for the player capsule, raycast- and trigger-detectable via the matrix. Mixed masks containing `CL_PHYSICS` route to `CharacterOnly` and remain solid.

## GROWN_ONLY_COMPONENTS (GOVS)

For grow-only result components (not LWW), add the component name to:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,41 @@ sdk7-test-scenes/

Each scene folder follows the naming convention: `{x},{y}-{scene-name}`

## MANDATORY: Folder Naming Convention

**Every new scene folder MUST be named `{x},{y}-{scene-name}` where `{x},{y}` matches the parcel coordinates declared in `scene.json` (`scene.parcels[0]` / the base parcel).**

Examples:
- ✅ `scenes/5,5-collider-layer-main-player` (parcel `5,5`)
- ✅ `scenes/0,7-particle-system` (parcel `0,7`)
- ✅ `scenes/100,100-mannakia-test-scene` (parcel `100,100`)
- ❌ `scenes/collider-layer-main-player` — missing coordinate prefix
- ❌ `scenes/my-feature-test` — missing coordinate prefix
- ❌ `scenes/3,3-feature` while `scene.json` says base `5,5` — folder/parcel mismatch

Tooling and humans alike rely on this prefix:
- `dcl-workspace.json` lists scenes alphabetically by folder name — coordinate-prefixed entries sort coherently.
- `npm run check-parcels` validates parcel collisions; a missing prefix slips past spatial review.
- Operators locate scenes by parcel when bug-bashing; an unprefixed folder is invisible to that workflow.

If you ever find yourself about to create a folder without `{x},{y}-` at the start, stop and pick the parcel first.

## Scene Creation Workflow

### Step 1: Duplicate an existing scene

Choose a scene close to your use case and copy it:
Choose a scene close to your use case and copy it. **The destination folder name MUST be `{x},{y}-{scene-name}`** — pick the parcel before creating the folder:

```bash
cd ../sdk7-test-scenes
cp -r scenes/0,0-cube-spawner scenes/<x>,<y>-<new-scene-name>
```

Example for a new scene at parcel `5,5`:
```bash
cp -r scenes/0,0-cube-spawner scenes/5,5-my-feature-test
```

### Step 2: Update scene metadata

**`package.json`** — Update the `name` field:
Expand Down Expand Up @@ -170,14 +195,23 @@ npm run check-parcels # Validates all scene parcels, update

## Completion Gate

**Do not report success until `npm run build` passes with zero errors.** Always run before finishing:

```bash
cd ../sdk7-test-scenes/scenes/<x>,<y>-<scene-name>
npm run build # TypeScript compilation must succeed with no errors
```

If the build fails, diagnose and fix before reporting done. Do not hand off a scene that does not compile.
Before reporting success, ALL of the following must hold:

1. **Folder name matches `{x},{y}-{scene-name}`** and `{x},{y}` equals the parcel coordinates declared in `scene.json`. Verify with:
```bash
cd ../sdk7-test-scenes
ls -d scenes/<x>,<y>-<scene-name> # folder exists with coordinate prefix
grep -E '"base"|"parcels"' scenes/<x>,<y>-<scene-name>/scene.json # matches folder prefix
```
If the folder is missing the coordinate prefix (or the prefix does not match the parcel in `scene.json`), rename it now via `mv` and update `dcl-workspace.json` accordingly — do NOT defer this to the user.
2. `npm run check-parcels` (from repo root) reports `✅ No collisions found`.
3. `npm run build` (from inside the scene folder) passes with zero TypeScript errors:
```bash
cd ../sdk7-test-scenes/scenes/<x>,<y>-<scene-name>
npm run build
```

If any gate fails, diagnose and fix before reporting done. Do not hand off a scene that does not compile, lacks the coordinate prefix, or collides with another parcel.

## Git Rules

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2105,7 +2105,7 @@ Camera:
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 1677918839
m_Bits: 1678967415
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@ public static class PhysicsLayers
private const ColliderLayer LAYER_PHYSICS = ColliderLayer.ClPhysics;
private const ColliderLayer LAYER_POINTER = ColliderLayer.ClPointer;
private const ColliderLayer LAYER_PHYSICS_POINTER = LAYER_PHYSICS | LAYER_POINTER;
private const ColliderLayer LAYER_PLAYER = ColliderLayer.ClPlayer;
private const ColliderLayer LAYER_MAIN_PLAYER = ColliderLayer.ClMainPlayer;

/// <summary>
/// Bits that qualify the main player on Raycast and TriggerArea queries.
/// Remote avatars only match on <see cref="ColliderLayer.ClPlayer"/>.
/// </summary>
public const ColliderLayer PLAYER_QUALIFYING_BITS = ColliderLayer.ClPlayer | ColliderLayer.ClMainPlayer;

private const ColliderLayer NON_CUSTOM_LAYERS = ColliderLayer.ClPhysics
| ColliderLayer.ClPointer
| ColliderLayer.ClNone
| ColliderLayer.ClPlayer
| ColliderLayer.ClReserved2
| ColliderLayer.ClMainPlayer
| ColliderLayer.ClReserved3
| ColliderLayer.ClReserved4
| ColliderLayer.ClReserved5
Expand All @@ -31,7 +39,8 @@ public static class PhysicsLayers
public static readonly int SDK_CUSTOM_LAYER = LayerMask.NameToLayer("SDKCustomLayer");
public static readonly int OTHER_AVATARS_LAYER = LayerMask.NameToLayer("OtherAvatars");
public static readonly int SDK_ENTITY_TRIGGER_AREA = LayerMask.NameToLayer("SDKEntityTriggerArea");
public static readonly int ALL_AVATARS = LayerMask.NameToLayer("AllAvatars");
public static readonly int SDK_AVATAR_TRIGGER_AREA = LayerMask.NameToLayer("SDKAvatarTriggerArea");
public static readonly int SDK_AVATAR_HIT_LAYER = LayerMask.NameToLayer("SDKAvatarHit");

public static readonly LayerMask PLAYER_ORIGIN_RAYCAST_MASK = (1 << ON_POINTER_EVENT_LAYER) | (1 << DEFAULT_LAYER) | (1 << OTHER_AVATARS_LAYER);
public static readonly LayerMask CHARACTER_ONLY_MASK = (1 << DEFAULT_LAYER) | (1 << FLOOR_LAYER) | (1 << CHARACTER_ONLY_LAYER);
Expand All @@ -40,6 +49,15 @@ public static class PhysicsLayers
public static bool LayerMaskHasAnySDKCustomLayer(ColliderLayer layerMask) =>
(layerMask & ~NON_CUSTOM_LAYERS) != 0;

/// <summary>
/// True when the mask is exclusively avatar bits (CL_PLAYER / CL_MAIN_PLAYER, no other bits).
/// Used to route SDK colliders to the SDKAvatarHit Unity layer.
/// </summary>
public static bool IsAvatarOnlyMask(ColliderLayer sdkMask) =>
sdkMask != ColliderLayer.ClNone
&& (sdkMask & PLAYER_QUALIFYING_BITS) != 0
&& (sdkMask & ~PLAYER_QUALIFYING_BITS) == 0;

public static bool LayerMaskContainsTargetLayer(uint layerMask, uint targetLayer)
=> (layerMask & targetLayer) != 0;

Expand All @@ -51,7 +69,16 @@ public static bool LayerMaskContainsTargetLayer(ColliderLayer layerMask, Collide

public static int CreateUnityLayerMaskFromSDKMask(ColliderLayer sdkMask)
{
int unityLayerMask = (1 << CHARACTER_LAYER) | (1 << DEFAULT_LAYER);
// Default keeps catching SDK meshes on the Default layer regardless of which SDK bits are set.
int unityLayerMask = 1 << DEFAULT_LAYER;

// Player-qualifying bits include the main player capsule (CHARACTER_LAYER) and the SDK avatar-hit
// layer (SDKAvatarHit) where SDK MeshCollider/GltfContainer colliders tagged with avatar bits live.
if ((sdkMask & PLAYER_QUALIFYING_BITS) != 0)
{
unityLayerMask |= 1 << CHARACTER_LAYER;
unityLayerMask |= 1 << SDK_AVATAR_HIT_LAYER;
}

unityLayerMask |= sdkMask switch
{
Expand All @@ -60,6 +87,10 @@ public static int CreateUnityLayerMaskFromSDKMask(ColliderLayer sdkMask)
_ => (1 << CHARACTER_ONLY_LAYER) | (1 << ON_POINTER_EVENT_LAYER),
};

// CL_PLAYER targets any avatar: include OTHER_AVATARS_LAYER. CL_MAIN_PLAYER alone is local-only.
if ((sdkMask & LAYER_PLAYER) == LAYER_PLAYER)
unityLayerMask |= 1 << OTHER_AVATARS_LAYER;

// 8 Custom SDK Layers are projected onto a single Unity layer
if (LayerMaskHasAnySDKCustomLayer(sdkMask))
unityLayerMask |= 1 << SDK_CUSTOM_LAYER;
Expand Down Expand Up @@ -87,6 +118,14 @@ public static bool TryGetUnityLayerFromSDKLayer(ColliderLayer sdkMask, out int u
return true;
}

// Avatar-only masks route to SDKAvatarHit. Player capsule passes through (matrix-disabled);
// trigger areas and raycasts targeting avatar bits still detect them.
if (IsAvatarOnlyMask(sdkMask))
{
unityLayer = SDK_AVATAR_HIT_LAYER;
return true;
}

if (LayerMaskHasAnySDKCustomLayer(sdkMask))
{
unityLayer = SDK_CUSTOM_LAYER;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"reference": "GUID:da80994a355e49d5b84f91c0a84a721f"
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading