Skip to content

Commit e8040f9

Browse files
Merge branch 'dev' into bugsweep/toggle-hints-tooltip-position
2 parents 677f189 + cfec223 commit e8040f9

737 files changed

Lines changed: 43542 additions & 2951 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/code-standards/SKILL.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,31 @@ A loop that re-queues unresolved items will spin forever when the upstream sourc
303303

304304
If `X` does nothing useful without `Y`, and there is no second consumer of `X`, merge them. Splits must pay for themselves in polymorphism, reuse, or test isolation.
305305

306+
### 8. Accessing `Option<T>.Value` without checking `Has`
307+
308+
`Option<T>.Value` (`Utility/Types/Result.cs`) is `default` — null for reference types — when `Has` is false. A blind read silently propagates an invalid value far from its source.
309+
310+
```csharp
311+
// WRONG — Value is default/null when Has is false
312+
UserId userId = UserId.New(raw).Value;
313+
314+
// WRONG in production — Unwrap() hides the absence case instead of modeling it
315+
UserId userId = UserId.New(raw).Unwrap();
316+
317+
// RIGHT — branch on Has when the input may be invalid
318+
Option<UserId> userId = UserId.New(raw);
319+
if (!userId.Has) return;
320+
Use(userId.Value);
321+
322+
// RIGHT — a factory that is valid by construction needs no Option at all
323+
UserId userId = UserId.NewRandom();
324+
325+
// RIGHT in tests only — Unwrap() for known-valid constants; it throws loudly at the source
326+
UserId userId = UserId.New(KNOWN_CONSTANT).Unwrap();
327+
```
328+
329+
In production code, handle `None` explicitly (early return, propagation) or use a by-construction-valid factory. `Unwrap()` is a test-only affordance — and in tests, known-valid constants go through `Unwrap()`, never bare `.Value`.
330+
306331
## PR Standards
307332

308333
- **Branches:** Based on `dev` branch

.claude/skills/diagnostics-and-logging/SKILL.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Drop a `.json` file in the build root folder, launch with `--use-log-matrix "fil
9797

9898
```json
9999
{
100-
"override": true,
100+
"isOverride": true,
101101
"debugLogMatrix": [
102102
{ "category": "VOICE_CHAT", "severity": "Warning" }
103103
],
@@ -107,8 +107,10 @@ Drop a `.json` file in the build root folder, launch with `--use-log-matrix "fil
107107
}
108108
```
109109

110-
- `"override": true` — Only use file values (replaces entire matrix)
111-
- `"override": false` — Merge with existing matrix values
110+
Keys must match `CategorySeverityMatrixDto` field names — `JsonUtility` ignores unknown ones. `{ "allOverride": true }` enables every category at every severity in the log file only, and takes precedence over `isOverride`/`debugLogMatrix`. A log captured under `allOverride` contains resolved media stream URLs with signed `sig`/`expire` query params — do not attach it to a public issue.
111+
112+
- `"isOverride": true` — Only use file values (replaces entire matrix)
113+
- `"isOverride": false` — Merge with existing matrix values
112114

113115
### Use Cases
114116

.gitattributes

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,28 @@ Explorer/Assets/StreamingAssets/AssetBundles/**/*.meta -filter=lfs -diff=lfs -me
1414
*.glb filter=lfs diff=lfs merge=lfs -text
1515
*.png filter=lfs diff=lfs merge=lfs -text
1616
*.dll filter=lfs diff=lfs merge=lfs -text
17+
*.dylib filter=lfs diff=lfs merge=lfs -text
18+
Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/x86_64/uuav-helper.exe filter=lfs diff=lfs merge=lfs -text
19+
Explorer/Assets/Plugins/UUAV/Packages/UUAV/Runtime/Plugins/macOS/uuav-helper filter=lfs diff=lfs merge=lfs -text
20+
21+
##############################################################################
22+
# UUAV native - hashed bytes, so the line endings are part of the content
23+
#
24+
# scripts/uuav/uuav-binaries.lock.json digests these files as raw bytes, and
25+
# scripts/uuav/build-canonical.sh compiles them with LF forced
26+
# (-c core.autocrlf=false -c core.eol=lf). Without eol=lf a checkout on a host
27+
# with core.autocrlf=true holds CRLF copies, so `verify-binaries.py --update`
28+
# there records digests no other host can reproduce. The shell scripts are here
29+
# too: bash on the Windows runner does not accept CRLF.
30+
##############################################################################
31+
Explorer/Assets/Plugins/UUAV/native/**/*.rs text eol=lf
32+
Explorer/Assets/Plugins/UUAV/native/**/*.toml text eol=lf
33+
Explorer/Assets/Plugins/UUAV/native/**/*.lock text eol=lf
34+
Explorer/Assets/Plugins/UUAV/native/**/*.sb text eol=lf
35+
Explorer/Assets/Plugins/UUAV/**/*.sh text eol=lf
36+
scripts/uuav/*.sh text eol=lf
37+
# --update rewrites the lock, so its own newlines must not depend on the host
38+
scripts/uuav/uuav-binaries.lock.json text eol=lf
1739

1840

1941
##############################################################################
@@ -29,3 +51,13 @@ Explorer/Assets/StreamingAssets/AssetBundles/**/*.meta -filter=lfs -diff=lfs -me
2951
*.asset binary
3052
*.controller binary
3153
*.asset text diff=yaml merge=yaml
54+
55+
# Root-level compiler rsp files must check out byte-identical to what
56+
# scripts/generate-ignore-warnings.sh writes (LF), or its drift check rewrites
57+
# them on CRLF-converting builders and re-triggers a Bee DAG rebuild.
58+
Explorer/Assets/*.rsp text eol=lf
59+
60+
# Shell scripts must check out LF everywhere: CRLF-converting Windows builders
61+
# fail them with "$'\r': command not found" (seen on every Windows cloud build
62+
# running the UBA preBuildScript).
63+
*.sh text eol=lf
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Upsert CI Status Comment
2+
description: >-
3+
Create or update the single unified CI status comment on a PR, replacing only
4+
the given section (build | lint | tests). Seeds a skeleton with all three
5+
sections the first time it runs, and re-reads/retries so concurrent writers
6+
(build vs. Unity Test) never clobber each other's section.
7+
8+
inputs:
9+
pr-number:
10+
description: Pull request number to comment on.
11+
required: true
12+
section:
13+
description: Which section to replace — one of build, lint, tests.
14+
required: true
15+
body:
16+
description: Markdown for this section (inline badge + message). Rendered as-is between the section markers.
17+
required: true
18+
github-token:
19+
description: Token with pull-requests:write used to read and upsert the comment.
20+
required: true
21+
22+
runs:
23+
using: composite
24+
steps:
25+
- name: Upsert unified CI status comment
26+
shell: bash
27+
env:
28+
GH_TOKEN: ${{ inputs.github-token }}
29+
REPO: ${{ github.repository }}
30+
PR_NUMBER: ${{ inputs.pr-number }}
31+
SECTION: ${{ inputs.section }}
32+
SECTION_BODY: ${{ inputs.body }}
33+
run: bash "$GITHUB_ACTION_PATH/upsert-ci-status.sh"
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/usr/bin/env bash
2+
# Create or update the single unified CI status comment on a PR, replacing only
3+
# one section (build | lint | tests). All three CI comment workflows call this
4+
# through the ci-status-comment composite action, so the three separate bot
5+
# comments collapse into one.
6+
#
7+
# The comment is keyed by the hidden <!-- ci-status --> marker and holds three
8+
# sections, each fenced by its own start/end markers:
9+
#
10+
# <!-- ci-status -->
11+
# ### 🚦 CI Status
12+
# <!-- ci:build:start --> …build… <!-- ci:build:end -->
13+
# <!-- ci:lint:start --> …lint… <!-- ci:lint:end -->
14+
# <!-- ci:tests:start --> …tests… <!-- ci:tests:end -->
15+
#
16+
# Build and Unity Test run as independent workflows whose comment writers can
17+
# fire at the same time, so a plain read-modify-write would drop a section or
18+
# create a duplicate comment. Each attempt collapses any duplicates (keeping the
19+
# oldest), rewrites only its own section on that comment, then re-reads to
20+
# confirm the section landed and no duplicate slipped in — retrying otherwise.
21+
set -euo pipefail
22+
23+
MARKER="<!-- ci-status -->"
24+
HEADER="### 🚦 CI Status"
25+
BOT="github-actions[bot]"
26+
START="<!-- ci:${SECTION}:start -->"
27+
END="<!-- ci:${SECTION}:end -->"
28+
29+
# Neutral "waiting" placeholder for a section that has not reported yet. Used
30+
# only when seeding a brand-new comment; a real run always overwrites its own.
31+
section_default() {
32+
case "$1" in
33+
build) printf '![Build](https://img.shields.io/badge/Build-Waiting-lightgrey?logo=unity&logoColor=white&style=for-the-badge)\n\n_Waiting for the build to start…_' ;;
34+
lint) printf '![Lint](https://img.shields.io/badge/Lint-Waiting-lightgrey?logo=jetbrains&logoColor=white&style=for-the-badge)\n\n_Waiting for lint to start…_' ;;
35+
tests) printf '![Tests](https://img.shields.io/badge/Tests-Waiting-lightgrey?logo=codecov&logoColor=white&style=for-the-badge)\n\n_Waiting for tests to start…_' ;;
36+
esac
37+
}
38+
39+
# One section, fenced by its start/end markers.
40+
wrap_section() { printf '<!-- ci:%s:start -->\n%s\n<!-- ci:%s:end -->' "$1" "$2" "$1"; }
41+
42+
# A fresh comment with every section defaulted to "waiting".
43+
skeleton() {
44+
printf '%s\n%s\n\n%s\n\n%s\n\n%s\n' \
45+
"$MARKER" "$HEADER" \
46+
"$(wrap_section build "$(section_default build)")" \
47+
"$(wrap_section lint "$(section_default lint)")" \
48+
"$(wrap_section tests "$(section_default tests)")"
49+
}
50+
51+
# Emit the section body for this run to a file so awk can splice it verbatim,
52+
# free of shell quoting concerns. Parts of the body (lint findings, failed test
53+
# names) originate in the untrusted pull_request job, so drop any line shaped
54+
# like a section marker before writing it — a body line must never open or close
55+
# a section fence, or it would scramble the comment structure / wedge the survive
56+
# check below.
57+
printf '%s\n' "$SECTION_BODY" \
58+
| grep -vE '^[[:space:]]*<!-- ci[-:][^>]*-->[[:space:]]*$' > section_body.md || true
59+
WANT="$(cat section_body.md)"
60+
61+
# Replace the content between START and END in $1 with section_body.md.
62+
replace_section() {
63+
awk -v s="$START" -v e="$END" -v f="section_body.md" '
64+
$0==s { print; while ((getline line < f) > 0) print line; close(f); skip=1; next }
65+
$0==e { print; skip=0; next }
66+
skip { next }
67+
{ print }
68+
' <<< "$1"
69+
}
70+
71+
# Trimmed content currently between START and END in $1 (for the survive check).
72+
extract_section() {
73+
awk -v s="$START" -v e="$END" '
74+
$0==s { grab=1; next }
75+
$0==e { grab=0; next }
76+
grab { print }
77+
' <<< "$1"
78+
}
79+
80+
# Normalise the comment list to a flat array, whether `--paginate --slurp` hands
81+
# back a flat array of comments or an array of per-page arrays.
82+
flatten_pages() { jq -c '[.[] | if type=="array" then .[] else . end]' <<< "$1"; }
83+
84+
# IDs of every marker-bearing bot comment on the PR, oldest first. Flattened
85+
# first so sort_by orders globally rather than only within a page.
86+
marker_ids() {
87+
jq -r --arg m "$MARKER" --arg bot "$BOT" \
88+
'[.[] | select(.user.login==$bot and (.body|contains($m)))] | sort_by(.id) | .[].id' <<< "$(flatten_pages "$1")"
89+
}
90+
91+
for attempt in 1 2 3 4 5; do
92+
COMMENTS=$(gh api "/repos/$REPO/issues/$PR_NUMBER/comments" --paginate --slurp)
93+
IDS=()
94+
while IFS= read -r line; do [ -n "$line" ] && IDS+=("$line"); done <<< "$(marker_ids "$COMMENTS")"
95+
COMMENT_ID="${IDS[0]:-}"
96+
97+
# Collapse accidental duplicates from a create race: keep the oldest, drop the rest.
98+
if [ "${#IDS[@]}" -gt 1 ]; then
99+
for extra in "${IDS[@]:1}"; do
100+
echo "Deleting duplicate CI status comment $extra."
101+
gh api -X DELETE "/repos/$REPO/issues/comments/$extra" >/dev/null || true
102+
done
103+
fi
104+
105+
if [ -n "$COMMENT_ID" ]; then
106+
CURRENT_BODY=$(jq -r --arg id "$COMMENT_ID" '.[] | select(.id==($id|tonumber)) | .body' <<< "$(flatten_pages "$COMMENTS")")
107+
else
108+
CURRENT_BODY=""
109+
fi
110+
111+
# No unified comment yet, or one missing our section markers: start clean so
112+
# all three sections are always present.
113+
if [ -z "$CURRENT_BODY" ] || ! grep -qF "$START" <<< "$CURRENT_BODY"; then
114+
CURRENT_BODY="$(skeleton)"
115+
fi
116+
117+
NEW_BODY="$(replace_section "$CURRENT_BODY")"
118+
119+
if [ -z "$COMMENT_ID" ]; then
120+
RESULT=$(jq -n --arg b "$NEW_BODY" '{body:$b}' \
121+
| gh api -X POST "/repos/$REPO/issues/$PR_NUMBER/comments" --input -)
122+
COMMENT_ID=$(jq -r '.id' <<< "$RESULT")
123+
else
124+
jq -n --arg b "$NEW_BODY" '{body:$b}' \
125+
| gh api -X PATCH "/repos/$REPO/issues/comments/$COMMENT_ID" --input - >/dev/null
126+
fi
127+
128+
# Re-read and confirm our section landed on the surviving comment, and that no
129+
# concurrent writer left a duplicate behind.
130+
sleep 1
131+
RECHECK=$(gh api "/repos/$REPO/issues/$PR_NUMBER/comments" --paginate --slurp)
132+
RIDS=()
133+
while IFS= read -r line; do [ -n "$line" ] && RIDS+=("$line"); done <<< "$(marker_ids "$RECHECK")"
134+
LIVE_BODY=$(jq -r --arg id "$COMMENT_ID" '.[] | select(.id==($id|tonumber)) | .body' <<< "$(flatten_pages "$RECHECK")")
135+
136+
# Success means our section landed on the comment we wrote — nothing more.
137+
# Duplicate collapsing is best-effort cleanup (the DELETE above may lack
138+
# permission); a duplicate we could not remove must not block reporting that
139+
# already succeeded, or every run would burn all 5 attempts and warn forever.
140+
if [ "$(extract_section "$LIVE_BODY")" = "$WANT" ]; then
141+
echo "CI status '$SECTION' section updated (attempt $attempt)."
142+
if [ "${#RIDS[@]}" -gt 1 ]; then
143+
echo "A duplicate CI status comment remains (could not be deleted); it will be retried next run."
144+
fi
145+
exit 0
146+
fi
147+
148+
echo "Section '$SECTION' not settled (attempt $attempt); retrying."
149+
sleep $((attempt * 2))
150+
done
151+
152+
echo "::warning::Could not confirm the '$SECTION' CI status section after 5 attempts."
153+
exit 0

.github/workflows/build-release-main-page.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ jobs:
5656
uses: dawidd6/action-download-artifact@v6
5757
with:
5858
run_id: ${{ github.event.workflow_run.id }}
59-
name: Decentraland_.*
59+
# Anchored to the four player zips: Decentraland_.* also matched the multi-GB
60+
# *_debug_symbols artifacts, downloading them only to discard them.
61+
name: Decentraland_(windows64|macos)(_epic)?$
6062
name_is_regexp: true
6163
skip_unpack: true
6264

0 commit comments

Comments
 (0)