mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 13:35:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
415acf27cd |
@@ -3,9 +3,18 @@
|
||||
# for now: filter out annoying TASTY warnings
|
||||
common --ui_event_filters=-INFO
|
||||
|
||||
common --enable_bzlmod
|
||||
|
||||
# Use pre-built protoc binary instead of compiling from source
|
||||
common --incompatible_enable_proto_toolchain_resolution
|
||||
common --@com_google_protobuf//bazel/toolchains:prefer_prebuilt_protoc=true
|
||||
|
||||
# Don't use toolchains_llvm for the swift app build
|
||||
common:mactools --ignore_dev_dependency
|
||||
|
||||
# Try to speed up sandboxes
|
||||
common --experimental_reuse_sandbox_directories
|
||||
|
||||
common --enable_platform_specific_config
|
||||
|
||||
common --strategy=Scalac=worker
|
||||
@@ -16,44 +25,10 @@ common --jobs=64
|
||||
|
||||
common --cxxopt="--std=c++23"
|
||||
common --cxxopt="-Wno-deprecated-non-prototype"
|
||||
common --per_file_copt=src/test/cpp/.*@-Wno-character-conversion
|
||||
common --per_file_copt=src/test/cpp/.*@-Wno-deprecated-enum-compare
|
||||
common --per_file_copt=src/test/cpp/.*@-Wno-sign-compare
|
||||
common --per_file_copt=src/test/cpp/.*@-Wno-unknown-warning-option
|
||||
common --host_cxxopt="--std=c++23"
|
||||
|
||||
# C++ sanitizer configs for targeted Shardok safety checks.
|
||||
# Example: bazel test --config=asan //src/test/cpp/net/eagle0/shardok/library/...
|
||||
build:asan --compilation_mode=dbg
|
||||
build:asan --strip=never
|
||||
build:asan --copt=-fno-omit-frame-pointer
|
||||
build:asan --copt=-Wno-macro-redefined
|
||||
build:asan --copt=-fsanitize=address
|
||||
build:asan --linkopt=-fsanitize=address
|
||||
test:asan --test_env=ASAN_OPTIONS=detect_leaks=0:strict_init_order=1
|
||||
|
||||
build:ubsan --compilation_mode=dbg
|
||||
build:ubsan --strip=never
|
||||
build:ubsan --copt=-fno-omit-frame-pointer
|
||||
build:ubsan --copt=-Wno-macro-redefined
|
||||
build:ubsan --copt=-fsanitize=undefined
|
||||
build:ubsan --linkopt=-fsanitize=undefined
|
||||
test:ubsan --test_env=UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1
|
||||
|
||||
build:tsan --compilation_mode=dbg
|
||||
build:tsan --strip=never
|
||||
build:tsan --copt=-fno-omit-frame-pointer
|
||||
build:tsan --copt=-Wno-macro-redefined
|
||||
build:tsan --copt=-fsanitize=thread
|
||||
build:tsan --linkopt=-fsanitize=thread
|
||||
test:tsan --test_env=TSAN_OPTIONS=halt_on_error=1
|
||||
|
||||
common --javacopt="-Xlint:-options"
|
||||
|
||||
# Prefer protobuf's prebuilt protoc toolchain instead of building protoc from
|
||||
# source when compatible binaries are available.
|
||||
common --@com_google_protobuf//bazel/toolchains:prefer_prebuilt_protoc
|
||||
|
||||
# std::filesystem and other modern C++ deps require macOS 10.15+.
|
||||
common:macos --macos_minimum_os=10.15
|
||||
common:macos --host_macos_minimum_os=10.15
|
||||
@@ -62,8 +37,13 @@ common:macos --host_macos_minimum_os=10.15
|
||||
# Use host_linkopt for macOS-specific flags to avoid passing them to Linux cross-compilation
|
||||
common:macos --host_linkopt=-Wl,-no_warn_duplicate_libraries
|
||||
|
||||
# Pin default DEVELOPER_DIR so Apple repo rules use full Xcode instead of
|
||||
# Command Line Tools when they are needed.
|
||||
# Workaround for grpc cf_event_engine build error on macOS with Bazel 7.x
|
||||
# See: https://github.com/grpc/grpc/issues/37619
|
||||
common:macos --features=-module_maps
|
||||
|
||||
# Pin DEVELOPER_DIR so Apple repo rules use full Xcode instead of Command Line
|
||||
# Tools. The sync script writes .bazelrc.xcode with an Xcode build-version
|
||||
# marker so in-place Xcode updates refresh Bazel's generated Apple toolchains.
|
||||
common:macos --repo_env=DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
|
||||
|
||||
# Local machine or CI overrides. This lets Bazel-only runners use Command Line
|
||||
@@ -71,9 +51,8 @@ common:macos --repo_env=DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
|
||||
# generated Xcode settings win when both files exist.
|
||||
try-import %workspace%/.bazelrc.local
|
||||
|
||||
# Xcode config for Apple tool builds. Generated by scripts/sync_bazel_xcode.sh.
|
||||
# Bakes the Xcode build version into mactools action cache keys without making
|
||||
# ordinary macOS Bazel builds depend on the installed Xcode build number.
|
||||
# Xcode config for macOS builds. Generated by scripts/sync_bazel_xcode.sh.
|
||||
# Bakes the Xcode build version into action cache keys and sets DEVELOPER_DIR.
|
||||
try-import %workspace%/.bazelrc.xcode
|
||||
|
||||
common --java_language_version=25
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
9.1.1
|
||||
8.5.1
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
Checks: >
|
||||
-*,
|
||||
bugprone-*,
|
||||
performance-*,
|
||||
readability-*,
|
||||
modernize-*,
|
||||
cppcoreguidelines-*,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
-cppcoreguidelines-pro-type-const-cast,
|
||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||
-cppcoreguidelines-pro-type-union-access,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-modernize-use-trailing-return-type,
|
||||
-readability-convert-member-functions-to-static,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-identifier-length,
|
||||
-readability-magic-numbers
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: 'src/(main|test)/cpp/net/eagle0/(shardok|common)/.*'
|
||||
FormatStyle: file
|
||||
CheckOptions:
|
||||
readability-braces-around-statements.ShortStatementLines: '1'
|
||||
readability-function-size.LineThreshold: '160'
|
||||
readability-function-size.StatementThreshold: '80'
|
||||
readability-function-size.BranchThreshold: '20'
|
||||
modernize-use-nullptr.NullMacros: 'NULL'
|
||||
@@ -1,9 +0,0 @@
|
||||
name: Setup Bazel
|
||||
description: Ensure Bazel is installed on a self-hosted runner.
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Ensure Bazel installed
|
||||
shell: bash
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
@@ -0,0 +1,76 @@
|
||||
name: Artifact Storage Check
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run every 6 hours
|
||||
- cron: '0 */6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
cleanup-expired:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Delete expired artifacts and artifacts older than 3 days
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
echo "Fetching all artifacts..."
|
||||
gh api "repos/${{ github.repository }}/actions/artifacts" \
|
||||
--paginate -q '.artifacts[] | "\(.id)\t\(.created_at)\t\(.expired)\t\(.name)"' > /tmp/all_artifacts.txt
|
||||
|
||||
total=$(wc -l < /tmp/all_artifacts.txt)
|
||||
echo "Found $total total artifacts"
|
||||
|
||||
cutoff=$(date -u -d '3 days ago' '+%Y-%m-%dT%H:%M:%SZ')
|
||||
echo "Deleting expired artifacts and artifacts created before $cutoff"
|
||||
|
||||
deleted=0
|
||||
while IFS=$'\t' read -r id created_at expired name; do
|
||||
if [[ "$expired" == "true" || "$created_at" < "$cutoff" ]]; then
|
||||
gh api -X DELETE "repos/${{ github.repository }}/actions/artifacts/$id" 2>/dev/null && deleted=$((deleted + 1))
|
||||
if [ $((deleted % 50)) -eq 0 ]; then
|
||||
echo "Deleted $deleted artifacts so far..."
|
||||
fi
|
||||
fi
|
||||
done < /tmp/all_artifacts.txt
|
||||
|
||||
echo "Cleanup complete. Deleted $deleted artifacts out of $total total."
|
||||
rm -f /tmp/all_artifacts.txt
|
||||
|
||||
check-storage:
|
||||
needs: cleanup-expired
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check artifact storage size
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Calculate active artifact storage. The artifacts API can list expired
|
||||
# artifacts until they are explicitly deleted, so do not count them.
|
||||
total_bytes=$(gh api "repos/${{ github.repository }}/actions/artifacts" \
|
||||
--paginate -q '.artifacts[] | select(.expired == false) | .size_in_bytes' | awk '{sum+=$1} END {print sum}')
|
||||
|
||||
total_mb=$(( ${total_bytes:-0} / 1024 / 1024 ))
|
||||
echo "Total artifact storage: ${total_mb} MB"
|
||||
|
||||
# Fail if over 500MB
|
||||
if [ "$total_mb" -gt 500 ]; then
|
||||
echo "::error::Artifact storage is ${total_mb} MB, which exceeds the 500 MB threshold!"
|
||||
echo ""
|
||||
echo "Largest artifacts:"
|
||||
# Save to temp file to avoid SIGPIPE/broken pipe errors with head
|
||||
gh api "repos/${{ github.repository }}/actions/artifacts" \
|
||||
--paginate -q '.artifacts[] | select(.expired == false) | "\(.size_in_bytes)\t\(.name)\t\(.created_at)"' > /tmp/artifacts.txt
|
||||
sort -rn /tmp/artifacts.txt | head -20 | \
|
||||
awk -F'\t' '{printf "%d MB\t%s\t%s\n", $1/1024/1024, $2, $3}'
|
||||
rm -f /tmp/artifacts.txt
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Storage is within acceptable limits."
|
||||
@@ -6,38 +6,12 @@ on:
|
||||
paths:
|
||||
- 'src/main/go/net/eagle0/authservice/**'
|
||||
- 'src/main/go/net/eagle0/authcli/**'
|
||||
- 'src/main/go/net/eagle0/common/**'
|
||||
- 'src/main/go/net/eagle0/util/**'
|
||||
- 'src/main/protobuf/net/eagle0/eagle/api/auth.proto'
|
||||
- 'src/main/protobuf/net/eagle0/eagle/api/admin/**'
|
||||
- 'src/main/protobuf/net/eagle0/eagle/internal/auth_internal.proto'
|
||||
- 'src/main/resources/net/eagle0/attributions.json'
|
||||
- 'ci/BUILD.bazel'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/auth_build.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/main/go/net/eagle0/authservice/**'
|
||||
- 'src/main/go/net/eagle0/authcli/**'
|
||||
- 'src/main/go/net/eagle0/common/**'
|
||||
- 'src/main/go/net/eagle0/util/**'
|
||||
- 'src/main/protobuf/net/eagle0/eagle/api/auth.proto'
|
||||
- 'src/main/protobuf/net/eagle0/eagle/api/admin/**'
|
||||
- 'src/main/protobuf/net/eagle0/eagle/internal/auth_internal.proto'
|
||||
- 'src/main/resources/net/eagle0/attributions.json'
|
||||
- 'ci/BUILD.bazel'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/auth_build.yml'
|
||||
workflow_dispatch:
|
||||
@@ -52,8 +26,8 @@ permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || 'deploy' }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
group: auth-build-deploy
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-auth:
|
||||
@@ -70,12 +44,6 @@ jobs:
|
||||
git config --global filter.lfs.smudge "cat"
|
||||
git config --global filter.lfs.clean "cat"
|
||||
git config --global filter.lfs.required false
|
||||
if [ -d ".git" ]; then
|
||||
git config --local --unset-all filter.lfs.process || true
|
||||
git config --local filter.lfs.smudge "cat"
|
||||
git config --local filter.lfs.clean "cat"
|
||||
git config --local filter.lfs.required false
|
||||
fi
|
||||
rm -f .git/hooks/post-checkout .git/hooks/post-merge .git/hooks/pre-push
|
||||
|
||||
- name: Checkout repository
|
||||
@@ -88,7 +56,7 @@ jobs:
|
||||
lfs: false
|
||||
|
||||
- name: Ensure Bazel installed
|
||||
uses: ./.github/actions/setup-bazel
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
|
||||
- name: Build Auth Server Docker image
|
||||
id: build-auth
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Bazel Cache Cleanup
|
||||
|
||||
on:
|
||||
# Disabled: bazel clean fails when another runner shares the output_base
|
||||
# on /Volumes/remote_cache (unlinkat "Directory not empty" race).
|
||||
# See https://github.com/nolen777/eagle0/issues/TBD for details.
|
||||
# schedule:
|
||||
# - cron: '0 0 * * 0'
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: [self-hosted, bazel]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
|
||||
- name: Show disk usage before cleanup
|
||||
run: |
|
||||
echo "=== Disk usage before cleanup ==="
|
||||
BAZEL_USER_ROOT="/private/var/tmp/_bazel_$(whoami)"
|
||||
echo "Bazel user root: $BAZEL_USER_ROOT"
|
||||
du -sh "$BAZEL_USER_ROOT" 2>/dev/null || echo "Not found"
|
||||
df -h .
|
||||
|
||||
- name: Run bazel clean
|
||||
run: |
|
||||
echo "=== Running bazel clean ==="
|
||||
bazel clean
|
||||
echo "Clean complete"
|
||||
|
||||
- name: Show disk usage after cleanup
|
||||
run: |
|
||||
echo "=== Disk usage after cleanup ==="
|
||||
BAZEL_USER_ROOT="/private/var/tmp/_bazel_$(whoami)"
|
||||
du -sh "$BAZEL_USER_ROOT" 2>/dev/null || echo "Not found"
|
||||
df -h .
|
||||
@@ -1,136 +0,0 @@
|
||||
name: Bazel Cache Maintenance
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: 'Maintenance target'
|
||||
required: true
|
||||
default: 'parity'
|
||||
type: choice
|
||||
options:
|
||||
- parity
|
||||
- clean
|
||||
schedule:
|
||||
- cron: '17 11 * * *'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
warm-cache:
|
||||
if: github.event_name == 'schedule' || github.event.inputs.target == 'parity'
|
||||
runs-on: [self-hosted, bazel]
|
||||
|
||||
steps:
|
||||
- name: Clean workspace
|
||||
run: |
|
||||
git sparse-checkout disable 2>/dev/null || true
|
||||
git config --local core.sparseCheckout false 2>/dev/null || true
|
||||
git config --local --unset extensions.worktreeConfig 2>/dev/null || true
|
||||
rm -f .git/info/sparse-checkout .git/config.worktree 2>/dev/null || true
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS=(/opt/homebrew/bin /usr/local/bin)
|
||||
for path in "${COMMON_PATHS[@]}"; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
export PATH="$path:$PATH"
|
||||
fi
|
||||
done
|
||||
|
||||
if command -v git-lfs >/dev/null 2>&1; then
|
||||
git-lfs version
|
||||
exit 0
|
||||
fi
|
||||
|
||||
brew install git-lfs
|
||||
git-lfs version
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
- name: Ensure Bazel installed
|
||||
uses: ./.github/actions/setup-bazel
|
||||
- name: Warm remote cache
|
||||
run: ./scripts/check_bazel_remote_cache_parity.sh warm
|
||||
|
||||
verify-cache:
|
||||
needs: warm-cache
|
||||
if: github.event_name == 'schedule' || github.event.inputs.target == 'parity'
|
||||
runs-on: [self-hosted, bazel]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
probe: [a, b]
|
||||
|
||||
steps:
|
||||
- name: Clean workspace
|
||||
run: |
|
||||
git sparse-checkout disable 2>/dev/null || true
|
||||
git config --local core.sparseCheckout false 2>/dev/null || true
|
||||
git config --local --unset extensions.worktreeConfig 2>/dev/null || true
|
||||
rm -f .git/info/sparse-checkout .git/config.worktree 2>/dev/null || true
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS=(/opt/homebrew/bin /usr/local/bin)
|
||||
for path in "${COMMON_PATHS[@]}"; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
export PATH="$path:$PATH"
|
||||
fi
|
||||
done
|
||||
|
||||
if command -v git-lfs >/dev/null 2>&1; then
|
||||
git-lfs version
|
||||
exit 0
|
||||
fi
|
||||
|
||||
brew install git-lfs
|
||||
git-lfs version
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
- name: Ensure Bazel installed
|
||||
uses: ./.github/actions/setup-bazel
|
||||
- name: Verify remote cache parity
|
||||
run: ./scripts/check_bazel_remote_cache_parity.sh verify
|
||||
|
||||
clean-cache:
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'clean'
|
||||
runs-on: [self-hosted, bazel]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
|
||||
- name: Show disk usage before cleanup
|
||||
run: |
|
||||
echo "=== Disk usage before cleanup ==="
|
||||
BAZEL_USER_ROOT="/private/var/tmp/_bazel_$(whoami)"
|
||||
echo "Bazel user root: $BAZEL_USER_ROOT"
|
||||
du -sh "$BAZEL_USER_ROOT" 2>/dev/null || echo "Not found"
|
||||
df -h .
|
||||
|
||||
- name: Run bazel clean
|
||||
run: |
|
||||
echo "=== Running bazel clean ==="
|
||||
bazel clean
|
||||
echo "Clean complete"
|
||||
|
||||
- name: Show disk usage after cleanup
|
||||
run: |
|
||||
echo "=== Disk usage after cleanup ==="
|
||||
BAZEL_USER_ROOT="/private/var/tmp/_bazel_$(whoami)"
|
||||
du -sh "$BAZEL_USER_ROOT" 2>/dev/null || echo "Not found"
|
||||
df -h .
|
||||
@@ -7,12 +7,9 @@ on:
|
||||
- 'src/**'
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- 'ci/github_actions/summarize_bazel_bep.py'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- '.github/workflows/bazel_test.yml'
|
||||
- '!src/main/csharp/**'
|
||||
- '!src/test/csharp/**'
|
||||
@@ -21,12 +18,9 @@ on:
|
||||
- 'src/**'
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- 'ci/github_actions/summarize_bazel_bep.py'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- '.github/workflows/bazel_test.yml'
|
||||
- '!src/main/csharp/**'
|
||||
- '!src/test/csharp/**'
|
||||
@@ -110,13 +104,10 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
- name: Setup Bazel
|
||||
uses: ./.github/actions/setup-bazel
|
||||
- name: Ensure Bazel installed
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
- name: Run tests
|
||||
run: bazel test --build_event_json_file=test.json //src/test/... //src/main/go/...
|
||||
- name: Summarize Bazel build metrics
|
||||
if: always()
|
||||
run: python3 ci/github_actions/summarize_bazel_bep.py test.json
|
||||
- name: Collect failed test logs
|
||||
if: always()
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Blob Cleanup
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run daily at 04:00 UTC
|
||||
- cron: '0 4 * * *'
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: [self-hosted, bazel]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
clean: false
|
||||
|
||||
- name: Ensure Bazel installed
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
|
||||
- name: Clean up unreferenced blobs
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: |
|
||||
bazel run //src/main/go/net/eagle0/build/blob_cleanup:blob_cleanup -- --min-age=1h
|
||||
@@ -22,36 +22,22 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-sysroot:
|
||||
name: Build Linux Sysroot (${{ matrix.architecture }})
|
||||
build-sysroot-amd64:
|
||||
if: ${{ inputs.architecture == 'amd64' || inputs.architecture == 'both' }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(inputs.architecture == 'both' && '{"include":[{"architecture":"amd64","build_script":"./tools/sysroot/build_sysroot.sh","artifact_name":"ubuntu-noble-sysroot-amd64","archive_name":"ubuntu_noble_amd64_sysroot.tar.xz","checksum_name":"ubuntu_noble_amd64_sysroot.sha256","module_name":"linux_sysroot"},{"architecture":"arm64","build_script":"./tools/sysroot/build_sysroot_arm64.sh","artifact_name":"ubuntu-noble-sysroot-arm64","archive_name":"ubuntu_noble_arm64_sysroot.tar.xz","checksum_name":"ubuntu_noble_arm64_sysroot.sha256","module_name":"linux_sysroot_arm64"}]}' || inputs.architecture == 'arm64' && '{"include":[{"architecture":"arm64","build_script":"./tools/sysroot/build_sysroot_arm64.sh","artifact_name":"ubuntu-noble-sysroot-arm64","archive_name":"ubuntu_noble_arm64_sysroot.tar.xz","checksum_name":"ubuntu_noble_arm64_sysroot.sha256","module_name":"linux_sysroot_arm64"}]}' || '{"include":[{"architecture":"amd64","build_script":"./tools/sysroot/build_sysroot.sh","artifact_name":"ubuntu-noble-sysroot-amd64","archive_name":"ubuntu_noble_amd64_sysroot.tar.xz","checksum_name":"ubuntu_noble_amd64_sysroot.sha256","module_name":"linux_sysroot"}]}') }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up QEMU for ARM64 emulation
|
||||
if: ${{ matrix.architecture == 'arm64' }}
|
||||
uses: docker/setup-qemu-action@v4
|
||||
with:
|
||||
platforms: arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ matrix.architecture == 'arm64' }}
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build sysroot
|
||||
run: ${{ matrix.build_script }}
|
||||
run: ./tools/sysroot/build_sysroot.sh
|
||||
|
||||
- name: Upload sysroot artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
name: ubuntu-noble-sysroot-amd64
|
||||
path: tools/sysroot/output/
|
||||
retention-days: 1
|
||||
|
||||
@@ -69,25 +55,89 @@ jobs:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: |
|
||||
# Upload sysroot tarball to DO Spaces
|
||||
aws s3 cp "tools/sysroot/output/${{ matrix.archive_name }}" \
|
||||
"s3://eagle0-sysroot/${{ inputs.version }}/${{ matrix.archive_name }}" \
|
||||
aws s3 cp tools/sysroot/output/ubuntu_noble_amd64_sysroot.tar.xz \
|
||||
s3://eagle0-sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz \
|
||||
--endpoint-url https://sfo3.digitaloceanspaces.com \
|
||||
--acl public-read
|
||||
|
||||
# Upload sha256 file
|
||||
aws s3 cp "tools/sysroot/output/${{ matrix.checksum_name }}" \
|
||||
"s3://eagle0-sysroot/${{ inputs.version }}/${{ matrix.checksum_name }}" \
|
||||
aws s3 cp tools/sysroot/output/ubuntu_noble_amd64_sysroot.sha256 \
|
||||
s3://eagle0-sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.sha256 \
|
||||
--endpoint-url https://sfo3.digitaloceanspaces.com \
|
||||
--acl public-read
|
||||
|
||||
echo ""
|
||||
echo "=== ${{ matrix.architecture }} Sysroot uploaded ==="
|
||||
echo "URL: https://eagle0-sysroot.sfo3.digitaloceanspaces.com/${{ inputs.version }}/${{ matrix.archive_name }}"
|
||||
echo "SHA256: $(cat "tools/sysroot/output/${{ matrix.checksum_name }}")"
|
||||
echo "=== AMD64 Sysroot uploaded ==="
|
||||
echo "URL: https://eagle0-sysroot.sfo3.digitaloceanspaces.com/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz"
|
||||
echo "SHA256: $(cat tools/sysroot/output/ubuntu_noble_amd64_sysroot.sha256)"
|
||||
echo ""
|
||||
echo "Update MODULE.bazel with:"
|
||||
echo "sysroot("
|
||||
echo " name = \"${{ matrix.module_name }}\","
|
||||
echo " sha256 = \"$(cat "tools/sysroot/output/${{ matrix.checksum_name }}")\","
|
||||
echo " urls = [\"https://eagle0-sysroot.sfo3.digitaloceanspaces.com/${{ inputs.version }}/${{ matrix.archive_name }}\"],"
|
||||
echo " name = \"linux_sysroot\","
|
||||
echo " sha256 = \"$(cat tools/sysroot/output/ubuntu_noble_amd64_sysroot.sha256)\","
|
||||
echo " urls = [\"https://eagle0-sysroot.sfo3.digitaloceanspaces.com/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz\"],"
|
||||
echo ")"
|
||||
|
||||
build-sysroot-arm64:
|
||||
if: ${{ inputs.architecture == 'arm64' || inputs.architecture == 'both' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up QEMU for ARM64 emulation
|
||||
uses: docker/setup-qemu-action@v4
|
||||
with:
|
||||
platforms: arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build ARM64 sysroot
|
||||
run: ./tools/sysroot/build_sysroot_arm64.sh
|
||||
|
||||
- name: Upload sysroot artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ubuntu-noble-sysroot-arm64
|
||||
path: tools/sysroot/output/
|
||||
retention-days: 1
|
||||
|
||||
- name: Install AWS CLI
|
||||
run: |
|
||||
if ! command -v aws &> /dev/null; then
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||
unzip -q awscliv2.zip
|
||||
sudo ./aws/install
|
||||
fi
|
||||
|
||||
- name: Upload to DigitalOcean Spaces
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: |
|
||||
# Upload sysroot tarball to DO Spaces
|
||||
aws s3 cp tools/sysroot/output/ubuntu_noble_arm64_sysroot.tar.xz \
|
||||
s3://eagle0-sysroot/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.tar.xz \
|
||||
--endpoint-url https://sfo3.digitaloceanspaces.com \
|
||||
--acl public-read
|
||||
|
||||
# Upload sha256 file
|
||||
aws s3 cp tools/sysroot/output/ubuntu_noble_arm64_sysroot.sha256 \
|
||||
s3://eagle0-sysroot/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.sha256 \
|
||||
--endpoint-url https://sfo3.digitaloceanspaces.com \
|
||||
--acl public-read
|
||||
|
||||
echo ""
|
||||
echo "=== ARM64 Sysroot uploaded ==="
|
||||
echo "URL: https://eagle0-sysroot.sfo3.digitaloceanspaces.com/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.tar.xz"
|
||||
echo "SHA256: $(cat tools/sysroot/output/ubuntu_noble_arm64_sysroot.sha256)"
|
||||
echo ""
|
||||
echo "Update MODULE.bazel with:"
|
||||
echo "sysroot("
|
||||
echo " name = \"linux_sysroot_arm64\","
|
||||
echo " sha256 = \"$(cat tools/sysroot/output/ubuntu_noble_arm64_sysroot.sha256)\","
|
||||
echo " urls = [\"https://eagle0-sysroot.sfo3.digitaloceanspaces.com/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.tar.xz\"],"
|
||||
echo ")"
|
||||
|
||||
@@ -19,33 +19,7 @@ on:
|
||||
- 'src/main/resources/**'
|
||||
- 'ci/BUILD.bazel'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- '.bazelrc'
|
||||
- 'docker-compose.prod.yml'
|
||||
- 'nginx/**'
|
||||
- '.github/workflows/docker_build.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
# Note: Auth changes trigger auth_build.yml instead
|
||||
# Note: Windows installer changes trigger installer_build.yml instead
|
||||
- 'src/main/go/net/eagle0/admin_server/**'
|
||||
- 'src/main/go/net/eagle0/common/**'
|
||||
- 'src/main/go/net/eagle0/util/**'
|
||||
- 'src/main/scala/**'
|
||||
- 'src/main/protobuf/**'
|
||||
- '!src/main/protobuf/net/eagle0/eagle/api/auth.proto'
|
||||
- '!src/main/protobuf/net/eagle0/eagle/internal/auth_internal.proto'
|
||||
- 'src/main/resources/**'
|
||||
- 'ci/BUILD.bazel'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- 'ci/github_actions/fetch_lfs.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- '.bazelrc'
|
||||
- 'docker-compose.prod.yml'
|
||||
- 'nginx/**'
|
||||
@@ -62,8 +36,8 @@ on:
|
||||
# The build-all job checks if it's still the latest commit on main and skips if not,
|
||||
# so intermediate commits don't waste time building when a newer one is already queued.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || 'deploy' }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
group: docker-build-deploy
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -79,10 +53,8 @@ jobs:
|
||||
eagle_image_tag: ${{ steps.push-images.outputs.eagle_image_tag }}
|
||||
admin_image_tag: ${{ steps.push-images.outputs.admin_image_tag }}
|
||||
jfr_sidecar_image_tag: ${{ steps.push-images.outputs.jfr_sidecar_image_tag }}
|
||||
skip_build: ${{ steps.check-latest.outputs.skip }}
|
||||
steps:
|
||||
- name: Skip if superseded
|
||||
if: github.event_name != 'pull_request'
|
||||
id: check-latest
|
||||
run: |
|
||||
# Check if there's a newer run of this workflow waiting in the queue.
|
||||
@@ -123,7 +95,7 @@ jobs:
|
||||
|
||||
- name: Ensure Bazel installed
|
||||
if: steps.check-latest.outputs.skip != 'true'
|
||||
uses: ./.github/actions/setup-bazel
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
|
||||
- name: Fetch LFS files needed for admin server
|
||||
if: steps.check-latest.outputs.skip != 'true'
|
||||
@@ -144,7 +116,7 @@ jobs:
|
||||
bazel build \
|
||||
--stamp \
|
||||
--platforms=//:linux_x86_64 \
|
||||
--extra_toolchains=@llvm_toolchain_linux//:cc-toolchain-x86_64-linux \
|
||||
--extra_toolchains=@llvm_toolchain_linux//:all \
|
||||
//ci:eagle_server_image \
|
||||
//ci:admin_server_image \
|
||||
//ci:jfr_sidecar_image \
|
||||
@@ -169,7 +141,7 @@ jobs:
|
||||
echo "JFR Sidecar: $JFR_PATH"
|
||||
|
||||
- name: Upload warmup binary
|
||||
if: steps.check-latest.outputs.skip != 'true' && github.event_name != 'pull_request'
|
||||
if: steps.check-latest.outputs.skip != 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: warmup-binary
|
||||
@@ -189,7 +161,7 @@ jobs:
|
||||
|
||||
- name: Push all images to DO registry
|
||||
id: push-images
|
||||
if: steps.check-latest.outputs.skip != 'true' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true'))
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
env:
|
||||
DOCKER_CONFIG: ${{ github.workspace }}/.docker
|
||||
run: |
|
||||
@@ -255,9 +227,9 @@ jobs:
|
||||
echo "=== All images pushed successfully ==="
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, bazel]
|
||||
needs: [build-all]
|
||||
if: needs.build-all.outputs.skip_build != 'true' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true'))
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
environment: production
|
||||
env:
|
||||
EAGLE_IMAGE: ${{ needs.build-all.outputs.eagle_image_tag }}
|
||||
@@ -268,7 +240,6 @@ jobs:
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
GPT_MODEL_NAME: ${{ secrets.GPT_MODEL_NAME }}
|
||||
EAGLE_ENABLE_S3: ${{ secrets.EAGLE_ENABLE_S3 }}
|
||||
DO_SPACES_ENDPOINT: ${{ secrets.DO_SPACES_ENDPOINT }}
|
||||
DO_SPACES_ACCESS_KEY: ${{ secrets.DO_SPACES_ACCESS_KEY }}
|
||||
DO_SPACES_SECRET_KEY: ${{ secrets.DO_SPACES_SECRET_KEY }}
|
||||
# Admin server uses different credentials (for eagle0-assets bucket)
|
||||
@@ -334,11 +305,25 @@ jobs:
|
||||
ssh-keyscan -H "$DO_DROPLET_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
|
||||
|
||||
- name: Download warmup binary
|
||||
id: download-warmup
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: warmup-binary
|
||||
path: scripts/bin/
|
||||
|
||||
- name: Build warmup binary fallback
|
||||
if: steps.download-warmup.outcome != 'success'
|
||||
run: |
|
||||
echo "::warning::warmup-binary artifact was unavailable; rebuilding warmup binary before deploy"
|
||||
./ci/github_actions/ensure_bazel_installed.sh
|
||||
bazel build \
|
||||
--platforms=//:linux_x86_64 \
|
||||
--extra_toolchains=@llvm_toolchain_linux//:all \
|
||||
//ci:warmup_tar
|
||||
mkdir -p scripts/bin
|
||||
tar -xf bazel-bin/ci/warmup_tar.tar -C scripts/bin --strip-components=1
|
||||
|
||||
- name: Copy config files to droplet
|
||||
run: |
|
||||
# Create directory structure on remote
|
||||
@@ -561,7 +546,7 @@ jobs:
|
||||
|
||||
cleanup:
|
||||
needs: [build-all, deploy]
|
||||
if: always() && github.event_name != 'pull_request'
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Delete warmup-binary artifact
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Eagle Build
|
||||
|
||||
on:
|
||||
# Main pushes are covered by docker_build.yml which builds the same target
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/main/scala/**'
|
||||
- 'src/main/protobuf/net/eagle0/eagle/**'
|
||||
- 'src/main/protobuf/net/eagle0/common/**'
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- '.github/workflows/eagle_build.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [self-hosted, bazel]
|
||||
|
||||
steps:
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS=(/opt/homebrew/bin /usr/local/bin)
|
||||
for path in "${COMMON_PATHS[@]}"; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
export PATH="$path:$PATH"
|
||||
fi
|
||||
done
|
||||
|
||||
if command -v git-lfs >/dev/null 2>&1; then
|
||||
git-lfs version
|
||||
exit 0
|
||||
fi
|
||||
|
||||
brew install git-lfs
|
||||
git-lfs version
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
- name: Ensure Bazel installed
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
- name: Build Eagle server
|
||||
run: bazel build //src/main/scala/net/eagle0/eagle:eagle_server
|
||||
@@ -5,16 +5,12 @@ on:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- ".github/workflows/installer_build.yml"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- ".bazelrc"
|
||||
- "src/main/go/net/eagle0/clients/win/installer/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/installer_build.yml"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- ".bazelrc"
|
||||
- "src/main/go/net/eagle0/clients/win/installer/**"
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -50,8 +46,8 @@ jobs:
|
||||
lfs: false
|
||||
clean: false
|
||||
|
||||
- name: Setup Bazel
|
||||
uses: ./.github/actions/setup-bazel
|
||||
- name: Ensure Bazel installed
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
|
||||
- name: Build Go installer for Windows
|
||||
env:
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check for relevant changes since last successful run
|
||||
id: check
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
if (context.eventName === 'workflow_dispatch') {
|
||||
@@ -74,7 +74,6 @@ jobs:
|
||||
'ci/', // CI scripts & workflows
|
||||
'.github/workflows/ios_testflight.yml',
|
||||
'MODULE.bazel',
|
||||
'MODULE.bazel.lock',
|
||||
'.bazelrc',
|
||||
];
|
||||
|
||||
@@ -166,23 +165,21 @@ jobs:
|
||||
./ci/github_actions/build_unity_ios.sh "$EAGLE0_BUILD_DIR/eagle0iOS"
|
||||
|
||||
- name: Upload Addressables to CDN
|
||||
if: success() && github.event.inputs.skip_upload != 'true'
|
||||
if: success()
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: ./ci/github_actions/upload_addressables.sh iOS
|
||||
|
||||
- name: Purge CDN cache for iOS addressables
|
||||
if: success() && github.event.inputs.skip_upload != 'true'
|
||||
if: success()
|
||||
env:
|
||||
DO_CDN_PAT: ${{ secrets.DO_CDN_PAT }}
|
||||
run: |
|
||||
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
|
||||
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
|
||||
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
|
||||
-H "Authorization: Bearer $DO_CDN_PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"files\": [\"addressables/iOS/$UNITY_MAJOR_MINOR/*\"]}" \
|
||||
-d '{"files": ["addressables/iOS/*"]}' \
|
||||
--fail || echo "Warning: CDN purge failed (non-fatal)"
|
||||
|
||||
- name: Archive Build Log
|
||||
@@ -251,28 +248,16 @@ jobs:
|
||||
tar -xzf "$RUNNER_TEMP/eagle0-ios-project.tar.gz" -C "$EAGLE0_BUILD_DIR"
|
||||
|
||||
- name: Delete generated iOS project artifact
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const artifactName = 'eagle0-ios-project-${{ github.run_id }}';
|
||||
const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: context.runId,
|
||||
});
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
artifact_id=$(gh api "repos/${{ github.repository }}/actions/artifacts" \
|
||||
--paginate \
|
||||
--jq '.artifacts[] | select(.name == "eagle0-ios-project-${{ github.run_id }}") | .id')
|
||||
|
||||
const artifact = artifacts.find(a => a.name === artifactName);
|
||||
if (artifact === undefined) {
|
||||
console.log(`Artifact not found: ${artifactName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.actions.deleteArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: artifact.id,
|
||||
});
|
||||
console.log(`Deleted artifact: ${artifactName} (${artifact.id})`);
|
||||
if [ -n "$artifact_id" ]; then
|
||||
gh api -X DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id"
|
||||
fi
|
||||
|
||||
- name: Install Signing Certificate
|
||||
env:
|
||||
|
||||
+153
-225
@@ -14,7 +14,6 @@ on:
|
||||
- "src/main/go/net/eagle0/build/mac_build_handler/**"
|
||||
- "scripts/build_protos.sh"
|
||||
- "scripts/build_mac_plugin.sh"
|
||||
- "scripts/build_sparkle_plugin.sh"
|
||||
- "scripts/inject_sparkle.sh"
|
||||
- "scripts/codesign_mac_app.sh"
|
||||
- "scripts/notarize_submit.sh"
|
||||
@@ -22,14 +21,10 @@ on:
|
||||
- "ci/github_actions/build_mac.sh"
|
||||
- "ci/github_actions/build_unity_mac.sh"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- "ci/github_actions/detect_addressables_changes.sh"
|
||||
- "ci/github_actions/upload_addressables.sh"
|
||||
- "ci/github_actions/ensure_unity_installed.sh"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
- ".bazelrc"
|
||||
- "ci/mac/**"
|
||||
- "src/main/objc/net/eagle0/clients/unity/sparkle/**"
|
||||
pull_request:
|
||||
# On PRs, only build Mac when Mac-specific files change. Shared C#/proto
|
||||
# changes are covered by the Windows build — if it passes, Mac will too.
|
||||
@@ -38,11 +33,6 @@ on:
|
||||
- "src/main/go/net/eagle0/build/mac_build_handler/**"
|
||||
- "scripts/build_mac_plugin.sh"
|
||||
- "scripts/build_sparkle_plugin.sh"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Editor/BuildScript.cs"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Eagle/SparkleInitializer.cs"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Eagle/SparkleUpdater.cs"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/common/UpdateNotification/UpdateNotificationManager.cs"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/common/WindowFocusManager.cs"
|
||||
- "src/main/objc/net/eagle0/clients/unity/sparkle/**"
|
||||
- "scripts/inject_sparkle.sh"
|
||||
- "scripts/codesign_mac_app.sh"
|
||||
@@ -50,13 +40,7 @@ on:
|
||||
- "scripts/notarize_wait.sh"
|
||||
- "ci/github_actions/build_mac.sh"
|
||||
- "ci/github_actions/build_unity_mac.sh"
|
||||
- "ci/github_actions/ensure_unity_installed.sh"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- "ci/github_actions/detect_addressables_changes.sh"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectSettings.asset"
|
||||
- ".bazelrc"
|
||||
- "ci/mac/**"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -66,6 +50,10 @@ on:
|
||||
default: 'false'
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write # Required to delete artifacts after deploy
|
||||
@@ -77,14 +65,11 @@ env:
|
||||
KEYCHAIN_NAME: build-${{ github.run_id }}.keychain
|
||||
|
||||
jobs:
|
||||
build-mac:
|
||||
build-and-sign:
|
||||
runs-on: [self-hosted, macOS, unity-mac]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-build-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
outputs:
|
||||
submission_id: ${{ steps.notarize-submit.outputs.submission_id }}
|
||||
should_deploy: ${{ steps.check-deploy.outputs.should_deploy }}
|
||||
addressables_changed: ${{ steps.addressables.outputs.changed }}
|
||||
|
||||
steps:
|
||||
- name: Prune stale PR refs
|
||||
@@ -125,25 +110,11 @@ jobs:
|
||||
- name: Clean stale files
|
||||
run: |
|
||||
git clean -ffd
|
||||
LIBRARY_DIR="src/main/csharp/net/eagle0/clients/unity/eagle0/Library"
|
||||
VERSION_CACHE="$LIBRARY_DIR/.last_unity_version"
|
||||
PROJECT_VERSION_FILE="src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
CURRENT_VERSION=$(grep "m_EditorVersion:" "$PROJECT_VERSION_FILE" | head -1 | sed 's/m_EditorVersion: //')
|
||||
|
||||
# Nuke Library/ when Unity version changes to avoid import loops
|
||||
if [ -f "$VERSION_CACHE" ]; then
|
||||
CACHED_VERSION=$(cat "$VERSION_CACHE")
|
||||
if [ "$CACHED_VERSION" != "$CURRENT_VERSION" ]; then
|
||||
echo "Unity version changed ($CACHED_VERSION -> $CURRENT_VERSION) -- clearing Library/"
|
||||
rm -rf "$LIBRARY_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Only clear Bee/ when C# files were added/deleted/renamed (structural
|
||||
# changes that stale the DAG). Content-only modifications are handled by
|
||||
# Bee's incremental compilation. See persist_library.sh for background.
|
||||
BEE_DIR="$LIBRARY_DIR/Bee"
|
||||
SHA_FILE="$LIBRARY_DIR/.last_built_sha"
|
||||
BEE_DIR="src/main/csharp/net/eagle0/clients/unity/eagle0/Library/Bee"
|
||||
SHA_FILE="src/main/csharp/net/eagle0/clients/unity/eagle0/Library/.last_built_sha"
|
||||
if [ -f "$SHA_FILE" ] && [ -d "$BEE_DIR" ]; then
|
||||
LAST_SHA=$(cat "$SHA_FILE")
|
||||
if git diff --diff-filter=ADR --name-only "$LAST_SHA" HEAD -- '*.cs' '*.csproj' '*.asmdef' 2>/dev/null | grep -q .; then
|
||||
@@ -165,34 +136,14 @@ jobs:
|
||||
- name: Ensure Unity version installed
|
||||
run: ./ci/github_actions/ensure_unity_installed.sh mac
|
||||
|
||||
- name: Ensure .NET SDK installed
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Ensure Bazel installed
|
||||
uses: ./.github/actions/setup-bazel
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
|
||||
- name: Sync Bazel Xcode config
|
||||
run: ./scripts/sync_bazel_xcode.sh
|
||||
|
||||
- name: Detect Addressables changes
|
||||
id: addressables
|
||||
if: success()
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
||||
else
|
||||
BASE_SHA="${{ github.event.before }}"
|
||||
fi
|
||||
|
||||
./ci/github_actions/detect_addressables_changes.sh "$BASE_SHA" "${{ github.sha }}"
|
||||
|
||||
- name: Build Mac Unity
|
||||
run: ./ci/github_actions/build_unity_mac.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC" "${{ steps.addressables.outputs.changed }}" "${{ steps.addressables.outputs.changed }}"
|
||||
run: ./ci/github_actions/build_unity_mac.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC"
|
||||
|
||||
- name: Save build SHA for Bee/ cache invalidation
|
||||
if: success()
|
||||
@@ -200,11 +151,20 @@ jobs:
|
||||
mkdir -p src/main/csharp/net/eagle0/clients/unity/eagle0/Library
|
||||
git rev-parse HEAD > src/main/csharp/net/eagle0/clients/unity/eagle0/Library/.last_built_sha
|
||||
|
||||
- name: Save Unity version for Library/ cache invalidation
|
||||
- name: Upload Addressables to CDN
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: ./ci/github_actions/upload_addressables.sh StandaloneOSX
|
||||
|
||||
- name: Inject Sparkle Framework
|
||||
if: success()
|
||||
env:
|
||||
SPARKLE_EDDSA_PUBLIC_KEY: ${{ secrets.SPARKLE_EDDSA_PUBLIC_KEY }}
|
||||
run: |
|
||||
mkdir -p src/main/csharp/net/eagle0/clients/unity/eagle0/Library
|
||||
grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //' > src/main/csharp/net/eagle0/clients/unity/eagle0/Library/.last_unity_version
|
||||
chmod +x ./scripts/inject_sparkle.sh
|
||||
./scripts/inject_sparkle.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC/eagle0.app"
|
||||
|
||||
- name: Check if should deploy
|
||||
id: check-deploy
|
||||
@@ -219,146 +179,8 @@ jobs:
|
||||
echo "should_deploy=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Zip unsigned app for deploy
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
run: |
|
||||
cd ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
ditto -c -k --keepParent eagle0.app eagle0.app.zip
|
||||
|
||||
- name: Upload unsigned app
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: unsigned-mac-app-${{ github.run_id }}
|
||||
path: ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC/eagle0.app.zip
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload Mac Addressables for deploy
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true' && steps.addressables.outputs.changed == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: mac-addressables-${{ github.run_id }}
|
||||
path: src/main/csharp/net/eagle0/clients/unity/eagle0/ServerData/StandaloneOSX
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
- name: Archive Build Log
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: editor_mac.log
|
||||
path: ${{ env.EAGLE0_BUILD_DIR }}/editor_mac.log
|
||||
retention-days: 3
|
||||
- name: Archive Addressables build reports
|
||||
if: (success() || failure()) && steps.addressables.outputs.changed == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: mac-addressables-build-reports
|
||||
path: src/main/csharp/net/eagle0/clients/unity/eagle0/Library/com.unity.addressables/BuildReports
|
||||
if-no-files-found: ignore
|
||||
retention-days: 3
|
||||
- name: Cleanup build directory
|
||||
if: always()
|
||||
run: rm -rf "${{ env.EAGLE0_BUILD_DIR }}"
|
||||
|
||||
deploy-mac:
|
||||
needs: build-mac
|
||||
if: needs.build-mac.outputs.should_deploy == 'true'
|
||||
runs-on: [self-hosted, macOS, notarize]
|
||||
outputs:
|
||||
deployed_version: ${{ steps.deploy-mac.outputs.deployed_version }}
|
||||
deployed_unity_major_minor: ${{ steps.deploy-mac.outputs.deployed_unity_major_minor }}
|
||||
concurrency:
|
||||
group: mac-deploy-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
steps:
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
export PATH="${COMMON_PATHS}:${PATH}"
|
||||
for path in /opt/homebrew/bin /usr/local/bin; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
fi
|
||||
done
|
||||
if ! command -v git-lfs >/dev/null 2>&1; then
|
||||
brew install git-lfs
|
||||
fi
|
||||
git-lfs --version
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
env:
|
||||
GIT_LFS_SKIP_SMUDGE: 1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # For version numbering
|
||||
|
||||
- name: Check if run is still latest main
|
||||
id: check-latest
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then
|
||||
LATEST_SHA=$(curl -fsSL \
|
||||
-H "Authorization: Bearer ${{ github.token }}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/commits/main" \
|
||||
| python3 -c 'import json, sys; print(json.load(sys.stdin)["sha"])')
|
||||
if [[ "$LATEST_SHA" != "${{ github.sha }}" ]]; then
|
||||
echo "Skipping deploy because ${{ github.sha }} is no longer latest main ($LATEST_SHA is latest)"
|
||||
echo "should_deploy=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
echo "should_deploy=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clean download directory
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
run: rm -rf ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
|
||||
- name: Download unsigned app
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: unsigned-mac-app-${{ github.run_id }}
|
||||
path: ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
|
||||
- name: Unzip unsigned app
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
run: |
|
||||
cd ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
ditto -x -k eagle0.app.zip .
|
||||
rm eagle0.app.zip
|
||||
ls -la ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC/eagle0.app/
|
||||
|
||||
- name: Clean Addressables directory
|
||||
if: steps.check-latest.outputs.should_deploy == 'true' && needs.build-mac.outputs.addressables_changed == 'true'
|
||||
run: rm -rf src/main/csharp/net/eagle0/clients/unity/eagle0/ServerData/StandaloneOSX
|
||||
|
||||
- name: Download Mac Addressables
|
||||
if: steps.check-latest.outputs.should_deploy == 'true' && needs.build-mac.outputs.addressables_changed == 'true'
|
||||
uses: actions/download-artifact@v8
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: mac-addressables-${{ github.run_id }}
|
||||
path: src/main/csharp/net/eagle0/clients/unity/eagle0/ServerData/StandaloneOSX
|
||||
|
||||
- name: Upload Addressables to CDN
|
||||
if: steps.check-latest.outputs.should_deploy == 'true' && needs.build-mac.outputs.addressables_changed == 'true'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: ./ci/github_actions/upload_addressables.sh StandaloneOSX
|
||||
|
||||
- name: Inject Sparkle Framework
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
env:
|
||||
SPARKLE_EDDSA_PUBLIC_KEY: ${{ secrets.SPARKLE_EDDSA_PUBLIC_KEY }}
|
||||
run: |
|
||||
chmod +x ./scripts/inject_sparkle.sh
|
||||
./scripts/inject_sparkle.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC/eagle0.app"
|
||||
|
||||
- name: Import Code Signing Certificate
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||
@@ -401,7 +223,7 @@ jobs:
|
||||
rm certificate.p12
|
||||
|
||||
- name: Code Sign App
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
env:
|
||||
SIGNING_IDENTITY: ${{ secrets.SIGNING_IDENTITY }}
|
||||
run: |
|
||||
@@ -410,7 +232,7 @@ jobs:
|
||||
|
||||
- name: Submit for Notarization
|
||||
id: notarize-submit
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
|
||||
@@ -419,47 +241,156 @@ jobs:
|
||||
chmod +x ./scripts/notarize_submit.sh
|
||||
./scripts/notarize_submit.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC/eagle0.app" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cleanup Keychain
|
||||
if: always()
|
||||
run: |
|
||||
security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true
|
||||
|
||||
- name: Zip signed app for artifact
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
run: |
|
||||
cd ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
ditto -c -k --keepParent eagle0.app eagle0.app.zip
|
||||
|
||||
- name: Upload signed app
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: signed-mac-app-${{ github.run_id }}
|
||||
path: ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC/eagle0.app.zip
|
||||
retention-days: 1
|
||||
|
||||
- name: Archive Build Log
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: editor_mac.log
|
||||
path: ${{ env.EAGLE0_BUILD_DIR }}/editor_mac.log
|
||||
retention-days: 3
|
||||
- name: Cleanup build directory
|
||||
if: always()
|
||||
run: rm -rf "${{ env.EAGLE0_BUILD_DIR }}"
|
||||
|
||||
wait-notarization:
|
||||
needs: build-and-sign
|
||||
if: needs.build-and-sign.outputs.should_deploy == 'true'
|
||||
runs-on: [self-hosted, macOS, notarize]
|
||||
|
||||
steps:
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
export PATH="${COMMON_PATHS}:${PATH}"
|
||||
for path in /opt/homebrew/bin /usr/local/bin; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
fi
|
||||
done
|
||||
if ! command -v git-lfs >/dev/null 2>&1; then
|
||||
brew install git-lfs
|
||||
fi
|
||||
git-lfs --version
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
env:
|
||||
GIT_LFS_SKIP_SMUDGE: 1
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: scripts
|
||||
|
||||
- name: Clean download directory
|
||||
run: rm -rf ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
|
||||
- name: Download signed app
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: signed-mac-app-${{ github.run_id }}
|
||||
path: ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
|
||||
- name: Unzip signed app
|
||||
run: |
|
||||
cd ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
ditto -x -k eagle0.app.zip .
|
||||
rm eagle0.app.zip
|
||||
ls -la ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC/eagle0.app/
|
||||
|
||||
- name: Wait for Notarization and Staple
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
|
||||
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
chmod +x ./scripts/notarize_wait.sh
|
||||
./scripts/notarize_wait.sh "${{ steps.notarize-submit.outputs.submission_id }}" "${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC/eagle0.app"
|
||||
|
||||
- name: Cleanup Keychain
|
||||
if: always()
|
||||
run: |
|
||||
security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true
|
||||
./scripts/notarize_wait.sh "${{ needs.build-and-sign.outputs.submission_id }}" "${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC/eagle0.app"
|
||||
|
||||
- name: Zip notarized app for artifact
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
run: |
|
||||
cd ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
rm -f eagle0.app.zip
|
||||
ditto -c -k --keepParent eagle0.app eagle0.app.zip
|
||||
|
||||
- name: Upload notarized app
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: notarized-mac-app-${{ github.run_id }}
|
||||
path: ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC/eagle0.app.zip
|
||||
retention-days: 1
|
||||
- name: Cleanup build directory
|
||||
if: always()
|
||||
run: rm -rf "${{ env.EAGLE0_BUILD_DIR }}"
|
||||
|
||||
deploy:
|
||||
needs: [build-and-sign, wait-notarization]
|
||||
if: needs.build-and-sign.outputs.should_deploy == 'true'
|
||||
runs-on: [self-hosted, macOS, notarize]
|
||||
outputs:
|
||||
deployed_version: ${{ steps.deploy-mac.outputs.deployed_version }}
|
||||
|
||||
steps:
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
export PATH="${COMMON_PATHS}:${PATH}"
|
||||
for path in /opt/homebrew/bin /usr/local/bin; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
fi
|
||||
done
|
||||
if ! command -v git-lfs >/dev/null 2>&1; then
|
||||
brew install git-lfs
|
||||
fi
|
||||
git-lfs --version
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
env:
|
||||
GIT_LFS_SKIP_SMUDGE: 1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # For version numbering
|
||||
|
||||
- name: Clean download directory
|
||||
run: rm -rf ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
|
||||
- name: Download notarized app
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: notarized-mac-app-${{ github.run_id }}
|
||||
path: ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
|
||||
- name: Unzip notarized app
|
||||
run: |
|
||||
cd ${{ env.EAGLE0_BUILD_DIR }}/eagle0MAC
|
||||
ditto -x -k eagle0.app.zip .
|
||||
rm eagle0.app.zip
|
||||
|
||||
- name: Ensure Bazel installed
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
uses: ./.github/actions/setup-bazel
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
|
||||
- name: Sync Bazel Xcode config
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
run: ./scripts/sync_bazel_xcode.sh
|
||||
|
||||
- name: Deploy Mac Build
|
||||
id: deploy-mac
|
||||
if: steps.check-latest.outputs.should_deploy == 'true'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
@@ -496,9 +427,6 @@ jobs:
|
||||
rm "$SPARKLE_PRIVATE_KEY_PATH"
|
||||
|
||||
echo "deployed_version=$VERSION" >> $GITHUB_OUTPUT
|
||||
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
|
||||
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
|
||||
echo "deployed_unity_major_minor=$UNITY_MAJOR_MINOR" >> $GITHUB_OUTPUT
|
||||
|
||||
# Artifact cleanup is handled by the cleanup job on ubuntu-latest
|
||||
- name: Cleanup build directory
|
||||
@@ -506,8 +434,8 @@ jobs:
|
||||
run: rm -rf "${{ env.EAGLE0_BUILD_DIR }}"
|
||||
|
||||
notify-mac:
|
||||
needs: deploy-mac
|
||||
if: needs.deploy-mac.outputs.deployed_version != ''
|
||||
needs: deploy
|
||||
if: needs.deploy.outputs.deployed_version != ''
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Purge CDN cache and notify clients
|
||||
@@ -519,16 +447,16 @@ jobs:
|
||||
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
|
||||
-H "Authorization: Bearer $DO_CDN_PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"files": ["mac/*", "addressables/StandaloneOSX/${{ needs.deploy-mac.outputs.deployed_unity_major_minor }}/*"]}' \
|
||||
-d '{"files": ["mac/*", "addressables/StandaloneOSX/*"]}' \
|
||||
--fail || echo "Warning: CDN purge failed (non-fatal)"
|
||||
|
||||
curl -X POST "https://admin.eagle0.net/notify-update?platform=mac&version=${{ needs.deploy-mac.outputs.deployed_version }}&required=false" \
|
||||
curl -X POST "https://admin.eagle0.net/notify-update?platform=mac&version=${{ needs.deploy.outputs.deployed_version }}&required=false" \
|
||||
-H "X-Notify-Secret: $NOTIFY_SECRET" \
|
||||
--fail --silent --show-error || echo "Warning: Failed to notify clients (non-fatal)"
|
||||
|
||||
# Cleanup job runs regardless of success/failure to prevent artifact accumulation
|
||||
cleanup:
|
||||
needs: [build-mac, deploy-mac, notify-mac]
|
||||
needs: [build-and-sign, wait-notarization, deploy, notify-mac]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -538,7 +466,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Delete this run's artifacts (names include run ID to avoid conflicts)
|
||||
for artifact_name in unsigned-mac-app-${{ github.run_id }} notarized-mac-app-${{ github.run_id }} mac-addressables-${{ github.run_id }}; do
|
||||
for artifact_name in signed-mac-app-${{ github.run_id }} notarized-mac-app-${{ github.run_id }}; do
|
||||
echo "Deleting artifact: $artifact_name"
|
||||
artifact_id=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" \
|
||||
-q ".artifacts[] | select(.name == \"$artifact_name\") | .id")
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
name: Cleanup Old Container Images
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run daily at 3am UTC
|
||||
- cron: '0 3 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: 'Dry run (show what would be deleted without deleting)'
|
||||
required: true
|
||||
default: 'true'
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install doctl
|
||||
uses: digitalocean/action-doctl@v2
|
||||
with:
|
||||
token: ${{ secrets.DO_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Cleanup old images
|
||||
env:
|
||||
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true' }}
|
||||
run: |
|
||||
set -e
|
||||
|
||||
RETENTION_DAYS=5
|
||||
CUTOFF_DATE=$(date -d "-${RETENTION_DAYS} days" +%s)
|
||||
REGISTRY="eagle0"
|
||||
|
||||
echo "Cleaning up images older than ${RETENTION_DAYS} days"
|
||||
echo "Cutoff date: $(date -d "@${CUTOFF_DATE}" -Iseconds)"
|
||||
echo "Dry run: ${DRY_RUN}"
|
||||
echo ""
|
||||
|
||||
# List repositories via JSON. Text output can wrap table rows and make
|
||||
# headers/tags/digests look like repository names.
|
||||
REPOS=$(doctl registry repository list-v2 "${REGISTRY}" --output json | jq -r '.[] | .name // .Name // empty')
|
||||
|
||||
for REPO in $REPOS; do
|
||||
echo "=== Processing repository: ${REPO} ==="
|
||||
|
||||
# Get all manifests with their tags and dates using JSON output for reliable parsing
|
||||
MANIFESTS_JSON=$(doctl registry repository list-manifests "${REPO}" --output json 2>/dev/null || echo "[]")
|
||||
|
||||
if [ "$MANIFESTS_JSON" = "[]" ] || [ -z "$MANIFESTS_JSON" ]; then
|
||||
echo " No manifests found"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse JSON and process each manifest
|
||||
echo "$MANIFESTS_JSON" | jq -r '.[] | "\(.digest) \(.updated_at) \(.tags // [] | join(","))"' | while read -r DIGEST UPDATED_AT TAGS; do
|
||||
# Skip if no digest or if it doesn't look like a valid digest (sha256:...)
|
||||
if [ -z "$DIGEST" ] || ! echo "$DIGEST" | grep -q '^sha256:'; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse the date (ISO 8601 format from JSON)
|
||||
MANIFEST_DATE=$(date -d "$UPDATED_AT" +%s 2>/dev/null || echo "0")
|
||||
|
||||
# Skip protected tags (latest, arm64-latest)
|
||||
if echo ",$TAGS," | grep -qE ',(latest|arm64-latest),'; then
|
||||
echo " KEEP: ${DIGEST:0:20}... (protected tag: $TAGS)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if older than cutoff
|
||||
if [ "$MANIFEST_DATE" -lt "$CUTOFF_DATE" ]; then
|
||||
echo " DELETE: ${DIGEST:0:20}... (updated: $UPDATED_AT, tags: $TAGS)"
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
doctl registry repository delete-manifest "${REPO}" "$DIGEST" --force
|
||||
fi
|
||||
else
|
||||
echo " KEEP: ${DIGEST:0:20}... (updated: $UPDATED_AT, tags: $TAGS)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
- name: Run garbage collection
|
||||
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false')
|
||||
run: |
|
||||
echo "Starting garbage collection..."
|
||||
set +e
|
||||
GC_OUTPUT=$(doctl registry garbage-collection start --force 2>&1)
|
||||
GC_STATUS=$?
|
||||
set -e
|
||||
|
||||
echo "$GC_OUTPUT"
|
||||
if [ "$GC_STATUS" -eq 0 ]; then
|
||||
echo "Garbage collection started. It may take a few minutes to complete."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if echo "$GC_OUTPUT" | grep -q "automated garbage collection is enabled"; then
|
||||
echo "Automated garbage collection is enabled for this registry; skipping manual garbage collection."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit "$GC_STATUS"
|
||||
@@ -1,219 +0,0 @@
|
||||
name: Renovate Dependency Artifacts
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- 'maven_install.json'
|
||||
- 'renovate.json'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- '.github/workflows/renovate_dependency_artifacts.yml'
|
||||
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- 'maven_install.json'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'renovate.json'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- '.github/workflows/renovate_dependency_artifacts.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.head.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
bazel: ${{ steps.changed-files.outputs.bazel }}
|
||||
go: ${{ steps.changed-files.outputs.go }}
|
||||
update_matrix: ${{ steps.changed-files.outputs.update_matrix }}
|
||||
steps:
|
||||
- name: Detect changed dependency files
|
||||
id: changed-files
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const files = await github.paginate(
|
||||
github.rest.pulls.listFiles,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
per_page: 100,
|
||||
},
|
||||
);
|
||||
|
||||
const changed = files.map((file) => file.filename);
|
||||
const bazelPatterns = [
|
||||
/^MODULE\.bazel$/,
|
||||
/^MODULE\.bazel\.lock$/,
|
||||
/^maven_install\.json$/,
|
||||
/^renovate\.json$/,
|
||||
/^ci\/github_actions\/ensure_bazel_installed\.sh$/,
|
||||
/^\.github\/actions\/setup-bazel\//,
|
||||
/^\.github\/workflows\/renovate_dependency_artifacts\.yml$/,
|
||||
];
|
||||
const goPatterns = [
|
||||
/^go\.mod$/,
|
||||
/^go\.sum$/,
|
||||
/^renovate\.json$/,
|
||||
/^\.github\/workflows\/renovate_dependency_artifacts\.yml$/,
|
||||
];
|
||||
|
||||
core.setOutput(
|
||||
'bazel',
|
||||
changed.some((file) => bazelPatterns.some((pattern) => pattern.test(file))),
|
||||
);
|
||||
core.setOutput(
|
||||
'go',
|
||||
changed.some((file) => goPatterns.some((pattern) => pattern.test(file))),
|
||||
);
|
||||
|
||||
const include = [];
|
||||
if (changed.some((file) => bazelPatterns.some((pattern) => pattern.test(file)))) {
|
||||
include.push({
|
||||
artifact: 'bazel',
|
||||
name: 'Bazel lockfiles',
|
||||
changed_files: 'MODULE.bazel.lock maven_install.json',
|
||||
commit_message: 'Update generated dependency lockfiles',
|
||||
});
|
||||
}
|
||||
if (changed.some((file) => goPatterns.some((pattern) => pattern.test(file)))) {
|
||||
include.push({
|
||||
artifact: 'go',
|
||||
name: 'Go artifacts',
|
||||
changed_files: 'go.sum',
|
||||
commit_message: 'Update generated Go artifacts',
|
||||
});
|
||||
}
|
||||
core.setOutput('update_matrix', JSON.stringify({include}));
|
||||
|
||||
check-lockfile:
|
||||
if: >-
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.user.login != 'renovate[bot]'
|
||||
runs-on: [self-hosted, bazel]
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
lfs: false
|
||||
|
||||
- name: Ensure Bazel installed
|
||||
uses: ./.github/actions/setup-bazel
|
||||
|
||||
- name: Update Maven lockfile
|
||||
env:
|
||||
REPIN: "1"
|
||||
run: bazel run @maven//:pin
|
||||
|
||||
- name: Refresh Bazel lockfile
|
||||
run: bazel build --nobuild //src/main/scala/net/eagle0/eagle:eagle_server //src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
|
||||
- name: Verify generated lockfiles are current
|
||||
run: |
|
||||
if git diff --quiet -- MODULE.bazel.lock maven_install.json; then
|
||||
echo "Generated dependency lockfiles are current"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Generated dependency lockfiles are stale. Run the lockfile update commands and commit the result:"
|
||||
echo " REPIN=1 bazel run @maven//:pin"
|
||||
echo " bazel build --nobuild //src/main/scala/net/eagle0/eagle:eagle_server //src/main/cpp/net/eagle0/shardok:shardok-server"
|
||||
git diff -- MODULE.bazel.lock maven_install.json
|
||||
exit 1
|
||||
|
||||
update-artifacts:
|
||||
name: Update ${{ matrix.name }}
|
||||
needs: changes
|
||||
if: >-
|
||||
github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.user.login == 'renovate[bot]' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
startsWith(github.event.pull_request.head.ref, 'renovate/') &&
|
||||
needs.changes.outputs.update_matrix != '{"include":[]}'
|
||||
runs-on: [self-hosted, bazel]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.changes.outputs.update_matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS=(/opt/homebrew/bin /usr/local/bin)
|
||||
for path in "${COMMON_PATHS[@]}"; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
export PATH="$path:$PATH"
|
||||
fi
|
||||
done
|
||||
|
||||
if command -v git-lfs >/dev/null 2>&1; then
|
||||
git-lfs version
|
||||
exit 0
|
||||
fi
|
||||
|
||||
brew install git-lfs
|
||||
git-lfs version
|
||||
|
||||
- name: Checkout Renovate branch
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
token: ${{ secrets.RENOVATE_LOCKFILE_TOKEN || github.token }}
|
||||
lfs: false
|
||||
|
||||
- name: Ensure Bazel installed
|
||||
if: matrix.artifact == 'bazel'
|
||||
uses: ./.github/actions/setup-bazel
|
||||
|
||||
- name: Update Maven lockfile
|
||||
if: matrix.artifact == 'bazel'
|
||||
env:
|
||||
REPIN: "1"
|
||||
run: bazel run @maven//:pin
|
||||
|
||||
- name: Refresh Bazel lockfile
|
||||
if: matrix.artifact == 'bazel'
|
||||
run: bazel build --nobuild //src/main/scala/net/eagle0/eagle:eagle_server //src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
|
||||
- name: Set up Go
|
||||
if: matrix.artifact == 'go'
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
- name: Update Go module sums
|
||||
if: matrix.artifact == 'go'
|
||||
run: go mod download all
|
||||
|
||||
- name: Commit generated artifact updates
|
||||
run: |
|
||||
if git diff --quiet -- ${{ matrix.changed_files }}; then
|
||||
echo "${{ matrix.name }} are already up to date"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add ${{ matrix.changed_files }}
|
||||
git commit -m "${{ matrix.commit_message }}"
|
||||
git push origin "HEAD:${{ github.event.pull_request.head.ref }}"
|
||||
@@ -1,189 +0,0 @@
|
||||
name: Repository Cleanup
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Registry cleanup: run daily at 03:00 UTC
|
||||
- cron: '0 3 * * *'
|
||||
# Artifact cleanup and storage check: run every 6 hours
|
||||
- cron: '0 */6 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: 'Cleanup target'
|
||||
required: true
|
||||
default: 'all'
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- artifacts
|
||||
- registry
|
||||
registry_dry_run:
|
||||
description: 'Dry run registry cleanup'
|
||||
required: true
|
||||
default: 'true'
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
cleanup-and-check-artifacts:
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
(github.event.inputs.target == 'all' || github.event.inputs.target == 'artifacts') ||
|
||||
github.event_name == 'schedule' &&
|
||||
github.event.schedule == '0 */6 * * *'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Delete expired artifacts and artifacts older than 3 days
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
echo "Fetching all artifacts..."
|
||||
gh api "repos/${{ github.repository }}/actions/artifacts" \
|
||||
--paginate -q '.artifacts[] | "\(.id)\t\(.created_at)\t\(.expired)\t\(.name)"' > /tmp/all_artifacts.txt
|
||||
|
||||
total=$(wc -l < /tmp/all_artifacts.txt)
|
||||
echo "Found $total total artifacts"
|
||||
|
||||
cutoff=$(date -u -d '3 days ago' '+%Y-%m-%dT%H:%M:%SZ')
|
||||
echo "Deleting expired artifacts and artifacts created before $cutoff"
|
||||
|
||||
deleted=0
|
||||
while IFS=$'\t' read -r id created_at expired name; do
|
||||
if [[ "$expired" == "true" || "$created_at" < "$cutoff" ]]; then
|
||||
gh api -X DELETE "repos/${{ github.repository }}/actions/artifacts/$id" 2>/dev/null && deleted=$((deleted + 1))
|
||||
if [ $((deleted % 50)) -eq 0 ]; then
|
||||
echo "Deleted $deleted artifacts so far..."
|
||||
fi
|
||||
fi
|
||||
done < /tmp/all_artifacts.txt
|
||||
|
||||
echo "Cleanup complete. Deleted $deleted artifacts out of $total total."
|
||||
rm -f /tmp/all_artifacts.txt
|
||||
|
||||
- name: Check artifact storage size
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Calculate active artifact storage. The artifacts API can list expired
|
||||
# artifacts until they are explicitly deleted, so do not count them.
|
||||
total_bytes=$(gh api "repos/${{ github.repository }}/actions/artifacts" \
|
||||
--paginate -q '.artifacts[] | select(.expired == false) | .size_in_bytes' | awk '{sum+=$1} END {print sum}')
|
||||
|
||||
total_mb=$(( ${total_bytes:-0} / 1024 / 1024 ))
|
||||
echo "Total artifact storage: ${total_mb} MB"
|
||||
|
||||
# Fail if over 500MB
|
||||
if [ "$total_mb" -gt 500 ]; then
|
||||
echo "::error::Artifact storage is ${total_mb} MB, which exceeds the 500 MB threshold!"
|
||||
echo ""
|
||||
echo "Largest artifacts:"
|
||||
# Save to temp file to avoid SIGPIPE/broken pipe errors with head
|
||||
gh api "repos/${{ github.repository }}/actions/artifacts" \
|
||||
--paginate -q '.artifacts[] | select(.expired == false) | "\(.size_in_bytes)\t\(.name)\t\(.created_at)"' > /tmp/artifacts.txt
|
||||
sort -rn /tmp/artifacts.txt | head -20 | \
|
||||
awk -F'\t' '{printf "%d MB\t%s\t%s\n", $1/1024/1024, $2, $3}'
|
||||
rm -f /tmp/artifacts.txt
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Storage is within acceptable limits."
|
||||
|
||||
cleanup-registry:
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
(github.event.inputs.target == 'all' || github.event.inputs.target == 'registry') ||
|
||||
github.event_name == 'schedule' &&
|
||||
github.event.schedule == '0 3 * * *'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Install doctl
|
||||
uses: digitalocean/action-doctl@v2
|
||||
with:
|
||||
token: ${{ secrets.DO_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Cleanup old images
|
||||
env:
|
||||
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.registry_dry_run == 'true' }}
|
||||
run: |
|
||||
set -e
|
||||
|
||||
RETENTION_DAYS=5
|
||||
CUTOFF_DATE=$(date -d "-${RETENTION_DAYS} days" +%s)
|
||||
REGISTRY="eagle0"
|
||||
|
||||
echo "Cleaning up images older than ${RETENTION_DAYS} days"
|
||||
echo "Cutoff date: $(date -d "@${CUTOFF_DATE}" -Iseconds)"
|
||||
echo "Dry run: ${DRY_RUN}"
|
||||
echo ""
|
||||
|
||||
# List repositories via JSON. Text output can wrap table rows and make
|
||||
# headers/tags/digests look like repository names.
|
||||
REPOS=$(doctl registry repository list-v2 "${REGISTRY}" --output json | jq -r '.[] | .name // .Name // empty')
|
||||
|
||||
for REPO in $REPOS; do
|
||||
echo "=== Processing repository: ${REPO} ==="
|
||||
|
||||
# Get all manifests with their tags and dates using JSON output for reliable parsing
|
||||
MANIFESTS_JSON=$(doctl registry repository list-manifests "${REPO}" --output json 2>/dev/null || echo "[]")
|
||||
|
||||
if [ "$MANIFESTS_JSON" = "[]" ] || [ -z "$MANIFESTS_JSON" ]; then
|
||||
echo " No manifests found"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse JSON and process each manifest
|
||||
echo "$MANIFESTS_JSON" | jq -r '.[] | "\(.digest) \(.updated_at) \(.tags // [] | join(","))"' | while read -r DIGEST UPDATED_AT TAGS; do
|
||||
# Skip if no digest or if it doesn't look like a valid digest (sha256:...)
|
||||
if [ -z "$DIGEST" ] || ! echo "$DIGEST" | grep -q '^sha256:'; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse the date (ISO 8601 format from JSON)
|
||||
MANIFEST_DATE=$(date -d "$UPDATED_AT" +%s 2>/dev/null || echo "0")
|
||||
|
||||
# Skip protected tags (latest, arm64-latest)
|
||||
if echo ",$TAGS," | grep -qE ',(latest|arm64-latest),'; then
|
||||
echo " KEEP: ${DIGEST:0:20}... (protected tag: $TAGS)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if older than cutoff
|
||||
if [ "$MANIFEST_DATE" -lt "$CUTOFF_DATE" ]; then
|
||||
echo " DELETE: ${DIGEST:0:20}... (updated: $UPDATED_AT, tags: $TAGS)"
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
doctl registry repository delete-manifest "${REPO}" "$DIGEST" --force
|
||||
fi
|
||||
else
|
||||
echo " KEEP: ${DIGEST:0:20}... (updated: $UPDATED_AT, tags: $TAGS)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
- name: Run garbage collection
|
||||
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.registry_dry_run == 'false')
|
||||
run: |
|
||||
echo "Starting garbage collection..."
|
||||
set +e
|
||||
GC_OUTPUT=$(doctl registry garbage-collection start --force 2>&1)
|
||||
GC_STATUS=$?
|
||||
set -e
|
||||
|
||||
echo "$GC_OUTPUT"
|
||||
if [ "$GC_STATUS" -eq 0 ]; then
|
||||
echo "Garbage collection started. It may take a few minutes to complete."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if echo "$GC_OUTPUT" | grep -q "automated garbage collection is enabled"; then
|
||||
echo "Automated garbage collection is enabled for this registry; skipping manual garbage collection."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit "$GC_STATUS"
|
||||
@@ -0,0 +1,86 @@
|
||||
name: S3 Archive Cleanup
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run weekly on Sunday at 05:00 UTC
|
||||
- cron: '0 5 * * 0'
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: [self-hosted, bazel]
|
||||
|
||||
steps:
|
||||
- name: Ensure AWS CLI is available
|
||||
run: |
|
||||
if ! command -v aws &> /dev/null; then
|
||||
brew install awscli
|
||||
fi
|
||||
|
||||
- name: Delete old archived game folders
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.DO_SPACES_ACCESS_KEY }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.DO_SPACES_SECRET_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
S3_ENDPOINT="https://sfo3.digitaloceanspaces.com"
|
||||
BUCKET="s3://eagle0/eagle/archived/"
|
||||
|
||||
# macOS date syntax
|
||||
CUTOFF=$(date -v-1m +%s)
|
||||
DELETED=0
|
||||
SKIPPED=0
|
||||
NO_DIR_FILE=0
|
||||
|
||||
echo "Cutoff date: $(date -r ${CUTOFF} '+%Y-%m-%dT%H:%M:%S')"
|
||||
echo ""
|
||||
|
||||
FOLDERS=$(aws s3 ls "$BUCKET" --endpoint-url "$S3_ENDPOINT" \
|
||||
| awk '/PRE/{gsub(/\/$/,"",$2); print $2}')
|
||||
|
||||
if [ -z "$FOLDERS" ]; then
|
||||
echo "No archived folders found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TOTAL=$(echo "$FOLDERS" | wc -l | tr -d ' ')
|
||||
CURRENT=0
|
||||
|
||||
for game_id in $FOLDERS; do
|
||||
CURRENT=$((CURRENT + 1))
|
||||
|
||||
DIR_INFO=$(aws s3 ls "${BUCKET}${game_id}/directory.e0i" \
|
||||
--endpoint-url "$S3_ENDPOINT" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$DIR_INFO" ]; then
|
||||
echo "[$CURRENT/$TOTAL] DELETE $game_id (no directory.e0i)"
|
||||
aws s3 rm --recursive "${BUCKET}${game_id}/" \
|
||||
--endpoint-url "$S3_ENDPOINT" > /dev/null 2>&1
|
||||
DELETED=$((DELETED + 1))
|
||||
NO_DIR_FILE=$((NO_DIR_FILE + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
FILE_DATE=$(echo "$DIR_INFO" | awk '{print $1 " " $2}')
|
||||
FILE_EPOCH=$(date -j -f '%Y-%m-%d %H:%M:%S' "$FILE_DATE" +%s 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$FILE_EPOCH" -lt "$CUTOFF" ]; then
|
||||
echo "[$CURRENT/$TOTAL] DELETE $game_id (directory.e0i from $FILE_DATE)"
|
||||
aws s3 rm --recursive "${BUCKET}${game_id}/" \
|
||||
--endpoint-url "$S3_ENDPOINT" > /dev/null 2>&1
|
||||
DELETED=$((DELETED + 1))
|
||||
else
|
||||
echo "[$CURRENT/$TOTAL] KEEP $game_id (directory.e0i from $FILE_DATE)"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Summary ==="
|
||||
echo "Total folders: $TOTAL"
|
||||
echo "Deleted: $DELETED"
|
||||
echo "Kept (recent): $SKIPPED"
|
||||
@@ -1,153 +0,0 @@
|
||||
name: Server Build
|
||||
|
||||
on:
|
||||
# Main pushes are covered by deploy workflows that build these targets.
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/main/scala/**'
|
||||
- 'src/main/cpp/**'
|
||||
- 'src/main/protobuf/net/eagle0/eagle/**'
|
||||
- 'src/main/protobuf/net/eagle0/shardok/**'
|
||||
- 'src/main/protobuf/net/eagle0/common/**'
|
||||
- 'src/main/go/net/eagle0/build/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- 'scripts/build_eagle_ci.sh'
|
||||
- 'scripts/build_shardok_ci.sh'
|
||||
- 'scripts/build_shardok_linux_arm64_ci.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- '.github/workflows/server_build.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
eagle: ${{ steps.filter.outputs.eagle }}
|
||||
shardok: ${{ steps.filter.outputs.shardok }}
|
||||
build_matrix: ${{ steps.filter.outputs.build_matrix }}
|
||||
steps:
|
||||
- name: Detect changed server areas
|
||||
id: filter
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const changed = files.map(file => file.filename);
|
||||
const matches = patterns => changed.some(file =>
|
||||
patterns.some(pattern =>
|
||||
pattern.endsWith('/') ? file.startsWith(pattern) : file === pattern
|
||||
)
|
||||
);
|
||||
|
||||
const sharedPatterns = [
|
||||
'src/main/protobuf/net/eagle0/common/',
|
||||
'src/main/go/net/eagle0/build/',
|
||||
'go.mod',
|
||||
'go.sum',
|
||||
'WORKSPACE',
|
||||
'MODULE.bazel',
|
||||
'MODULE.bazel.lock',
|
||||
'BUILD.bazel',
|
||||
'.bazelrc',
|
||||
'ci/github_actions/ensure_bazel_installed.sh',
|
||||
'.github/actions/setup-bazel/',
|
||||
'.github/workflows/server_build.yml',
|
||||
];
|
||||
|
||||
const eaglePatterns = [
|
||||
'src/main/scala/',
|
||||
'src/main/protobuf/net/eagle0/eagle/',
|
||||
'scripts/build_eagle_ci.sh',
|
||||
];
|
||||
|
||||
const shardokPatterns = [
|
||||
'src/main/cpp/',
|
||||
'src/main/protobuf/net/eagle0/shardok/',
|
||||
'scripts/build_shardok_ci.sh',
|
||||
'scripts/build_shardok_linux_arm64_ci.sh',
|
||||
];
|
||||
|
||||
const shared = matches(sharedPatterns);
|
||||
const eagle = shared || matches(eaglePatterns);
|
||||
const shardok = shared || matches(shardokPatterns);
|
||||
|
||||
core.info(`Changed files:\n${changed.join('\n')}`);
|
||||
core.info(`Run Eagle build: ${eagle}`);
|
||||
core.info(`Run Shardok build: ${shardok}`);
|
||||
core.setOutput('eagle', String(eagle));
|
||||
core.setOutput('shardok', String(shardok));
|
||||
const include = [];
|
||||
if (eagle) {
|
||||
include.push({
|
||||
name: 'Eagle server',
|
||||
command: './scripts/build_eagle_ci.sh',
|
||||
});
|
||||
}
|
||||
if (shardok) {
|
||||
include.push({
|
||||
name: 'Shardok server',
|
||||
command: './scripts/build_shardok_ci.sh',
|
||||
});
|
||||
include.push({
|
||||
name: 'Shardok Linux ARM64 server',
|
||||
command: './scripts/build_shardok_linux_arm64_ci.sh',
|
||||
});
|
||||
}
|
||||
core.setOutput('build_matrix', JSON.stringify({include}));
|
||||
|
||||
server-build:
|
||||
name: Build ${{ matrix.name }}
|
||||
needs: changes
|
||||
if: needs.changes.outputs.build_matrix != '{"include":[]}'
|
||||
runs-on: [self-hosted, bazel]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.changes.outputs.build_matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS=(/opt/homebrew/bin /usr/local/bin)
|
||||
for path in "${COMMON_PATHS[@]}"; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
export PATH="$path:$PATH"
|
||||
fi
|
||||
done
|
||||
|
||||
if command -v git-lfs >/dev/null 2>&1; then
|
||||
git-lfs version
|
||||
exit 0
|
||||
fi
|
||||
|
||||
brew install git-lfs
|
||||
git-lfs version
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
- name: Setup Bazel
|
||||
uses: ./.github/actions/setup-bazel
|
||||
- name: Build server
|
||||
run: ${{ matrix.command }}
|
||||
@@ -7,15 +7,10 @@ on:
|
||||
- 'src/main/cpp/**'
|
||||
- 'src/main/protobuf/net/eagle0/shardok/**'
|
||||
- 'src/main/protobuf/net/eagle0/common/**'
|
||||
- 'src/main/go/net/eagle0/build/**'
|
||||
- 'src/main/resources/net/eagle0/shardok/**'
|
||||
- 'ci/BUILD.bazel'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/shardok_arm64_build.yml'
|
||||
workflow_dispatch:
|
||||
@@ -48,12 +43,6 @@ jobs:
|
||||
git config --global filter.lfs.smudge "cat"
|
||||
git config --global filter.lfs.clean "cat"
|
||||
git config --global filter.lfs.required false
|
||||
if [ -d ".git" ]; then
|
||||
git config --local --unset-all filter.lfs.process || true
|
||||
git config --local filter.lfs.smudge "cat"
|
||||
git config --local filter.lfs.clean "cat"
|
||||
git config --local filter.lfs.required false
|
||||
fi
|
||||
rm -f .git/hooks/post-checkout .git/hooks/post-merge .git/hooks/pre-push
|
||||
|
||||
- name: Checkout repository
|
||||
@@ -66,7 +55,7 @@ jobs:
|
||||
lfs: false
|
||||
|
||||
- name: Ensure Bazel installed
|
||||
uses: ./.github/actions/setup-bazel
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
|
||||
- name: Build Shardok ARM64 binary (cross-compile for Linux ARM64)
|
||||
run: |
|
||||
@@ -75,7 +64,7 @@ jobs:
|
||||
echo "=== Building shardok-server binary for linux-aarch64 ==="
|
||||
bazel build \
|
||||
--platforms=//:linux_arm64 \
|
||||
--extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux \
|
||||
--extra_toolchains=@llvm_toolchain_linux_arm64//:all \
|
||||
//src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
|
||||
LINUX_BIN="bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server"
|
||||
@@ -116,10 +105,9 @@ jobs:
|
||||
run: |
|
||||
set -ex
|
||||
|
||||
# Keep this in sync with the standalone binary build above.
|
||||
bazel build \
|
||||
--platforms=//:linux_arm64 \
|
||||
--extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux \
|
||||
--extra_toolchains=@llvm_toolchain_linux_arm64//:all \
|
||||
//ci:shardok_server_image_arm64
|
||||
|
||||
IMAGE_PATH=$(readlink -f bazel-bin/ci/shardok_server_image_arm64)
|
||||
@@ -196,9 +184,7 @@ jobs:
|
||||
echo "Also tagged as: registry.digitalocean.com/eagle0/shardok-server:arm64-latest"
|
||||
|
||||
deploy-hetzner:
|
||||
# Keep this on a self-hosted Mac runner for IPv6 reachability to Hetzner,
|
||||
# but do not require a Bazel runner slot for artifact transport/deploy work.
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
runs-on: [self-hosted, bazel]
|
||||
needs: [build-shardok-arm64]
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
environment: production
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Shardok Build
|
||||
|
||||
on:
|
||||
# Main pushes are covered by shardok_arm64_build.yml which builds the same target
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/main/cpp/**'
|
||||
- 'src/main/protobuf/net/eagle0/shardok/**'
|
||||
- 'src/main/protobuf/net/eagle0/common/**'
|
||||
- 'src/main/go/net/eagle0/build/**'
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- '.github/workflows/shardok_build.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [self-hosted, bazel]
|
||||
|
||||
steps:
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS=(/opt/homebrew/bin /usr/local/bin)
|
||||
for path in "${COMMON_PATHS[@]}"; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
export PATH="$path:$PATH"
|
||||
fi
|
||||
done
|
||||
|
||||
if command -v git-lfs >/dev/null 2>&1; then
|
||||
git-lfs version
|
||||
exit 0
|
||||
fi
|
||||
|
||||
brew install git-lfs
|
||||
git-lfs version
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
- name: Ensure Bazel installed
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
- name: Build Shardok server
|
||||
run: bazel build -c opt //src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
@@ -1,74 +0,0 @@
|
||||
name: Storage Cleanup
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Blob cleanup: run daily at 04:00 UTC
|
||||
- cron: '0 4 * * *'
|
||||
# S3 archive cleanup: run weekly on Sunday at 05:00 UTC
|
||||
- cron: '0 5 * * 0'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: 'Cleanup target'
|
||||
required: true
|
||||
default: 'all'
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- blob
|
||||
- archive
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: [self-hosted, bazel]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'all' && '{"include":[{"name":"Blob cleanup","command":"bazel run //src/main/go/net/eagle0/build/blob_cleanup:blob_cleanup -- --min-age=1h"},{"name":"S3 archive cleanup","command":"bazel run //src/main/go/net/eagle0/build/s3_archive_cleanup:s3_archive_cleanup -- --min-age=720h"}]}' || github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'archive' && '{"include":[{"name":"S3 archive cleanup","command":"bazel run //src/main/go/net/eagle0/build/s3_archive_cleanup:s3_archive_cleanup -- --min-age=720h"}]}' || github.event_name == 'schedule' && github.event.schedule == '0 5 * * 0' && '{"include":[{"name":"S3 archive cleanup","command":"bazel run //src/main/go/net/eagle0/build/s3_archive_cleanup:s3_archive_cleanup -- --min-age=720h"}]}' || '{"include":[{"name":"Blob cleanup","command":"bazel run //src/main/go/net/eagle0/build/blob_cleanup:blob_cleanup -- --min-age=1h"}]}') }}
|
||||
|
||||
steps:
|
||||
- name: Prepare non-LFS checkout
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig-no-lfs
|
||||
GIT_CONFIG_NOSYSTEM: "1"
|
||||
run: |
|
||||
git config --global --unset-all filter.lfs.process || true
|
||||
git config --global filter.lfs.smudge "cat"
|
||||
git config --global filter.lfs.clean "cat"
|
||||
git config --global filter.lfs.required false
|
||||
if [ -d ".git" ]; then
|
||||
git config --local --unset-all filter.lfs.process || true
|
||||
git config --local filter.lfs.smudge "cat"
|
||||
git config --local filter.lfs.clean "cat"
|
||||
git config --local filter.lfs.required false
|
||||
fi
|
||||
rm -f .git/hooks/post-checkout .git/hooks/post-merge .git/hooks/pre-push
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig-no-lfs
|
||||
GIT_CONFIG_NOSYSTEM: "1"
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
clean: false
|
||||
|
||||
- name: Ensure Bazel installed
|
||||
uses: ./.github/actions/setup-bazel
|
||||
|
||||
- name: Run blob cleanup
|
||||
if: matrix.name == 'Blob cleanup'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: ${{ matrix.command }}
|
||||
|
||||
- name: Run S3 archive cleanup
|
||||
if: matrix.name == 'S3 archive cleanup'
|
||||
env:
|
||||
DO_SPACES_ACCESS_KEY: ${{ secrets.DO_SPACES_ACCESS_KEY }}
|
||||
DO_SPACES_SECRET_KEY: ${{ secrets.DO_SPACES_SECRET_KEY }}
|
||||
run: ${{ matrix.command }}
|
||||
@@ -17,14 +17,10 @@ on:
|
||||
- "ci/github_actions/build_unity.sh"
|
||||
- "ci/github_actions/test_unity_editmode.sh"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- "ci/github_actions/detect_addressables_changes.sh"
|
||||
- "ci/github_actions/upload_addressables.sh"
|
||||
- "ci/github_actions/ensure_unity_installed.sh"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
- "MODULE.bazel"
|
||||
- "MODULE.bazel.lock"
|
||||
- ".bazelrc"
|
||||
- "WORKSPACE"
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
@@ -42,145 +38,28 @@ on:
|
||||
- "ci/github_actions/build_unity.sh"
|
||||
- "ci/github_actions/test_unity_editmode.sh"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- "ci/github_actions/detect_addressables_changes.sh"
|
||||
- "ci/github_actions/upload_addressables.sh"
|
||||
- "ci/github_actions/ensure_unity_installed.sh"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
- "MODULE.bazel"
|
||||
- "MODULE.bazel.lock"
|
||||
- ".bazelrc"
|
||||
- "WORKSPACE"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
|
||||
env:
|
||||
# Runner-specific build directory to allow parallel builds on multiple runners
|
||||
EAGLE0_BUILD_DIR: /tmp/eagle0-${{ github.run_id }}-${{ github.job }}
|
||||
EAGLE0_BUILD_DIR: /tmp/eagle0-${{ github.run_id }}
|
||||
|
||||
jobs:
|
||||
unity-editmode-tests:
|
||||
runs-on: [self-hosted, macOS, unity-windows]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-tests-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
steps:
|
||||
- name: Prune stale PR refs
|
||||
run: |
|
||||
# Self-hosted runners persist .git between runs. When a PR is updated,
|
||||
# old local refs (refs/remotes/pull/*/merge) may point to commits whose
|
||||
# objects were never fetched or have been pruned. Remove these stale refs
|
||||
# before checkout to prevent "missing object" errors.
|
||||
if [ -d ".git" ]; then
|
||||
echo "Pruning stale PR refs..."
|
||||
git for-each-ref --format='%(refname)' refs/remotes/pull/ 2>/dev/null | \
|
||||
xargs -r git update-ref -d 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
export PATH="${COMMON_PATHS}:${PATH}"
|
||||
for path in /opt/homebrew/bin /usr/local/bin; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
fi
|
||||
done
|
||||
if ! command -v git-lfs >/dev/null 2>&1; then
|
||||
brew install git-lfs
|
||||
fi
|
||||
git-lfs --version
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
env:
|
||||
GIT_LFS_SKIP_SMUDGE: 1
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false # Fetch LFS after checkout to avoid stale ref issues
|
||||
clean: false # Library/ persists between runs on self-hosted runners
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Clean stale files
|
||||
run: |
|
||||
git clean -ffd
|
||||
UNITY_ROOT="src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
# These ignored proto outputs are mutually exclusive across proto-generation
|
||||
# layouts. Clean both before regenerating to avoid stale DLL/source duplicates
|
||||
# when a self-hosted runner switches between main and PR branches.
|
||||
rm -rf "$UNITY_ROOT/Assets/GeneratedProtos"
|
||||
rm -rf "$UNITY_ROOT/Assets/Plugins/Eagle0Protos"
|
||||
LIBRARY_DIR="src/main/csharp/net/eagle0/clients/unity/eagle0/Library"
|
||||
VERSION_CACHE="$LIBRARY_DIR/.last_unity_version"
|
||||
PROJECT_VERSION_FILE="src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
CURRENT_VERSION=$(grep "m_EditorVersion:" "$PROJECT_VERSION_FILE" | head -1 | sed 's/m_EditorVersion: //')
|
||||
|
||||
# Nuke Library/ when Unity version changes to avoid import loops
|
||||
if [ -f "$VERSION_CACHE" ]; then
|
||||
CACHED_VERSION=$(cat "$VERSION_CACHE")
|
||||
if [ "$CACHED_VERSION" != "$CURRENT_VERSION" ]; then
|
||||
echo "Unity version changed ($CACHED_VERSION -> $CURRENT_VERSION) -- clearing Library/"
|
||||
rm -rf "$LIBRARY_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Only clear Bee/ when C# files were added/deleted/renamed (structural
|
||||
# changes that stale the DAG). Content-only modifications are handled by
|
||||
# Bee's incremental compilation. See persist_library.sh for background.
|
||||
BEE_DIR="$LIBRARY_DIR/Bee"
|
||||
SHA_FILE="$LIBRARY_DIR/.last_built_sha"
|
||||
if [ -f "$SHA_FILE" ] && [ -d "$BEE_DIR" ]; then
|
||||
LAST_SHA=$(cat "$SHA_FILE")
|
||||
if git diff --diff-filter=ADR --name-only "$LAST_SHA" HEAD -- '*.cs' '*.csproj' '*.asmdef' 2>/dev/null | grep -q .; then
|
||||
echo "C# files added/deleted/renamed since $LAST_SHA -- clearing Bee/"
|
||||
rm -rf "$BEE_DIR"
|
||||
else
|
||||
echo "No structural C# changes since $LAST_SHA -- keeping Bee/"
|
||||
fi
|
||||
else
|
||||
echo "No previous build SHA or no Bee/ -- clearing Bee/ as safe default"
|
||||
rm -rf "$BEE_DIR"
|
||||
fi
|
||||
|
||||
- name: Fetch LFS files
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: ./ci/github_actions/fetch_lfs.sh
|
||||
- name: Ensure Unity version installed
|
||||
run: ./ci/github_actions/ensure_unity_installed.sh windows
|
||||
- name: Ensure .NET SDK installed
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
- name: Ensure Bazel installed
|
||||
uses: ./.github/actions/setup-bazel
|
||||
|
||||
- name: Run Unity EditMode tests
|
||||
run: ./ci/github_actions/test_unity_editmode.sh
|
||||
|
||||
- name: Archive EditMode test artifacts
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: unity-editmode-tests
|
||||
path: |
|
||||
${{ env.EAGLE0_BUILD_DIR }}/editor_editmode_tests.log
|
||||
${{ env.EAGLE0_BUILD_DIR }}/editmode-test-results.xml
|
||||
retention-days: 3
|
||||
- name: Cleanup build directory
|
||||
if: always()
|
||||
run: rm -rf "${{ env.EAGLE0_BUILD_DIR }}"
|
||||
|
||||
windows-unity:
|
||||
runs-on: [self-hosted, macOS, unity-windows]
|
||||
outputs:
|
||||
addressables_changed: ${{ steps.addressables.outputs.changed }}
|
||||
deployed_version: ${{ steps.get-version.outputs.deployed_version }}
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-build-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
steps:
|
||||
- name: Prune stale PR refs
|
||||
@@ -216,17 +95,10 @@ jobs:
|
||||
persist-credentials: false
|
||||
lfs: false # Fetch LFS after checkout to avoid stale ref issues
|
||||
clean: false # Library/ persists between runs on self-hosted runners
|
||||
fetch-depth: 2 # Keep the previous main commit available for Addressables change detection
|
||||
|
||||
- name: Clean stale files
|
||||
run: |
|
||||
git clean -ffd
|
||||
UNITY_ROOT="src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
# These ignored proto outputs are mutually exclusive across proto-generation
|
||||
# layouts. Clean both before regenerating to avoid stale DLL/source duplicates
|
||||
# when a self-hosted runner switches between main and PR branches.
|
||||
rm -rf "$UNITY_ROOT/Assets/GeneratedProtos"
|
||||
rm -rf "$UNITY_ROOT/Assets/Plugins/Eagle0Protos"
|
||||
LIBRARY_DIR="src/main/csharp/net/eagle0/clients/unity/eagle0/Library"
|
||||
VERSION_CACHE="$LIBRARY_DIR/.last_unity_version"
|
||||
PROJECT_VERSION_FILE="src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
@@ -265,29 +137,12 @@ jobs:
|
||||
run: ./ci/github_actions/fetch_lfs.sh
|
||||
- name: Ensure Unity version installed
|
||||
run: ./ci/github_actions/ensure_unity_installed.sh windows
|
||||
- name: Ensure .NET SDK installed
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
- name: Ensure Bazel installed
|
||||
uses: ./.github/actions/setup-bazel
|
||||
|
||||
- name: Detect Addressables changes
|
||||
id: addressables
|
||||
if: success()
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
||||
else
|
||||
BASE_SHA="${{ github.event.before }}"
|
||||
fi
|
||||
|
||||
./ci/github_actions/detect_addressables_changes.sh "$BASE_SHA" "${{ github.sha }}"
|
||||
run: ./ci/github_actions/ensure_bazel_installed.sh
|
||||
- name: Run Unity EditMode tests
|
||||
run: ./ci/github_actions/test_unity_editmode.sh
|
||||
- name: Build Windows unity
|
||||
run: ./ci/github_actions/build_unity.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0WIN" true "${{ steps.addressables.outputs.changed }}"
|
||||
run: ./ci/github_actions/build_unity.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0WIN"
|
||||
|
||||
- name: Save build SHA for Bee/ cache invalidation
|
||||
if: success()
|
||||
@@ -301,89 +156,21 @@ jobs:
|
||||
mkdir -p src/main/csharp/net/eagle0/clients/unity/eagle0/Library
|
||||
grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //' > src/main/csharp/net/eagle0/clients/unity/eagle0/Library/.last_unity_version
|
||||
|
||||
- name: Check if run is still latest main for Addressables
|
||||
id: check-addressables-latest
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && steps.addressables.outputs.changed == 'true'
|
||||
run: |
|
||||
LATEST_SHA=$(curl -fsSL \
|
||||
-H "Authorization: Bearer ${{ github.token }}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/commits/main" \
|
||||
| python3 -c 'import json, sys; print(json.load(sys.stdin)["sha"])')
|
||||
if [[ "$LATEST_SHA" != "${{ github.sha }}" ]]; then
|
||||
echo "Skipping Addressables upload because ${{ github.sha }} is no longer latest main ($LATEST_SHA is latest)"
|
||||
echo "should_upload=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "should_upload=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Wait for EditMode tests before publishing
|
||||
- name: Upload Addressables to CDN
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
deadline=$((SECONDS + 600))
|
||||
while [ "$SECONDS" -lt "$deadline" ]; do
|
||||
result=$(curl -fsSL \
|
||||
-H "Authorization: Bearer $GH_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs" \
|
||||
| python3 -c 'import json, sys; job = next(j for j in json.load(sys.stdin)["jobs"] if j["name"] == "unity-editmode-tests"); print("{} {}".format(job["status"], job["conclusion"] or ""))')
|
||||
status="${result%% *}"
|
||||
conclusion="${result#* }"
|
||||
if [ "$status" = "completed" ]; then
|
||||
if [ "$conclusion" = "success" ]; then
|
||||
echo "EditMode tests passed; publishing may continue."
|
||||
exit 0
|
||||
fi
|
||||
echo "EditMode tests completed with conclusion: $conclusion"
|
||||
exit 1
|
||||
fi
|
||||
echo "EditMode tests are still $status; waiting..."
|
||||
sleep 5
|
||||
done
|
||||
echo "Timed out waiting for EditMode tests."
|
||||
exit 1
|
||||
|
||||
- name: Check if run is still latest main for publish
|
||||
id: check-publish-latest
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
run: |
|
||||
LATEST_SHA=$(curl -fsSL \
|
||||
-H "Authorization: Bearer ${{ github.token }}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/commits/main" \
|
||||
| python3 -c 'import json, sys; print(json.load(sys.stdin)["sha"])')
|
||||
if [[ "$LATEST_SHA" != "${{ github.sha }}" ]]; then
|
||||
echo "Skipping publish because ${{ github.sha }} is no longer latest main ($LATEST_SHA is latest)"
|
||||
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Stage Windows blobs for publish
|
||||
if: success() && steps.check-publish-latest.outputs.should_publish == 'true'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: bazel run //src/main/go/net/eagle0/build/unity3d_windows_build_handler:unity3d_windows_build_handler -- --skip-previous-manifest "${{ env.EAGLE0_BUILD_DIR }}/eagle0WIN" "${{ env.EAGLE0_BUILD_DIR }}/unity_manifest.txt"
|
||||
|
||||
- name: Upload Windows Addressables to CDN
|
||||
if: success() && steps.check-publish-latest.outputs.should_publish == 'true' && steps.check-addressables-latest.outputs.should_upload == 'true'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: ./ci/github_actions/upload_addressables.sh StandaloneWindows64
|
||||
|
||||
- name: Publish previous Windows manifest
|
||||
if: success() && steps.check-publish-latest.outputs.should_publish == 'true'
|
||||
- name: Deploy Windows unity
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: bazel run //src/main/go/net/eagle0/build/unity3d_windows_build_handler:unity3d_windows_build_handler -- --publish-previous-manifest "${{ env.EAGLE0_BUILD_DIR }}/unity_manifest.txt"
|
||||
run: bazel run //src/main/go/net/eagle0/build/unity3d_windows_build_handler:unity3d_windows_build_handler -- "${{ env.EAGLE0_BUILD_DIR }}/eagle0WIN" "/tmp/unity_manifest.txt"
|
||||
|
||||
- name: Update unified manifest
|
||||
if: success() && steps.check-publish-latest.outputs.should_publish == 'true'
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
@@ -401,16 +188,16 @@ jobs:
|
||||
fi
|
||||
|
||||
# Update the v2 manifest at installer/v2/eagle0_manifest.txt
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- unity3d-v2 "${{ env.EAGLE0_BUILD_DIR }}/unity_manifest.txt" $SIGNING_ARGS
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- unity3d-v2 /tmp/unity_manifest.txt $SIGNING_ARGS
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/manifest_signing_key
|
||||
|
||||
- name: Export deployed version
|
||||
id: get-version
|
||||
if: success() && steps.check-publish-latest.outputs.should_publish == 'true'
|
||||
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
run: |
|
||||
VERSION=$(grep "^version=" "${{ env.EAGLE0_BUILD_DIR }}/unity_manifest.txt" | cut -d= -f2 || date +%Y.%m.%d)
|
||||
VERSION=$(grep "^version=" /tmp/unity_manifest.txt | cut -d= -f2 || date +%Y.%m.%d)
|
||||
echo "deployed_version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Archive build log
|
||||
@@ -420,13 +207,14 @@ jobs:
|
||||
name: editor_win.log
|
||||
path: ${{ env.EAGLE0_BUILD_DIR }}/editor_win.log
|
||||
retention-days: 3
|
||||
- name: Archive Addressables build reports
|
||||
if: (success() || failure()) && steps.addressables.outputs.changed == 'true'
|
||||
- name: Archive EditMode test artifacts
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: windows-addressables-build-reports
|
||||
path: src/main/csharp/net/eagle0/clients/unity/eagle0/Library/com.unity.addressables/BuildReports
|
||||
if-no-files-found: ignore
|
||||
name: unity_editmode_tests
|
||||
path: |
|
||||
${{ env.EAGLE0_BUILD_DIR }}/editor_editmode_tests.log
|
||||
${{ env.EAGLE0_BUILD_DIR }}/editmode-test-results.xml
|
||||
retention-days: 3
|
||||
- name: Cleanup build directory
|
||||
if: always()
|
||||
|
||||
@@ -40,7 +40,6 @@ scripts/refresh_name_layers/refresh_name_layers.zip
|
||||
api_keys.txt
|
||||
|
||||
src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/GeneratedProtos/
|
||||
src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Plugins/Eagle0Protos.meta
|
||||
src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Plugins/Eagle0Protos/
|
||||
src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/Packages/com.unity.dedicated-server/
|
||||
node_modules/
|
||||
|
||||
@@ -6,11 +6,6 @@
|
||||
|
||||
**NEVER prefix a command with `cd`.** Run `git`, `gh`, `bazel`, etc. directly from whatever the cwd already is. The cwd is a subdirectory of the worktree; `git`/`gh` find the repo via `.git` discovery and `bazel` finds the workspace via MODULE.bazel discovery — walking up from a subdir works fine. The shell resets cwd after every command anyway, so a `cd` never persists. (And `cd <dir> && <cmd>` violates the no-chaining rule above and forces an approval prompt every time.) Only `cd` in the rare case a tool genuinely cannot locate its root, and even then as its own Bash call, never a chain.
|
||||
|
||||
## CRITICAL UNITY PROCESS RULES (NEVER VIOLATE)
|
||||
|
||||
**NEVER kill Unity without asking the user first.** The user might be actively using the Unity Editor. Do not kill,
|
||||
force-quit, terminate, or otherwise stop Unity processes unless the user explicitly approves that specific action.
|
||||
|
||||
## CRITICAL GIT RULES (NEVER VIOLATE)
|
||||
|
||||
**NEVER use `git -C`.** Just run `git` directly from the cwd — it finds the repo via `.git` discovery. Do not `cd` to the repo root either (see the no-`cd` bash rule above).
|
||||
@@ -29,16 +24,6 @@ If you catch yourself about to run `git push origin main` or `git push origin <b
|
||||
|
||||
If you catch yourself about to run `gh pr merge`, STOP. Only the user merges PRs.
|
||||
|
||||
## Commit History During Review
|
||||
|
||||
Prefer new follow-up commits over amending existing commits once a PR is open and the user is actively reviewing or
|
||||
testing it. This preserves working interim states so the user can inspect, compare, or return to a known-good point
|
||||
while improvements continue.
|
||||
|
||||
Use `git commit --amend` and force-push only when the user explicitly asks for history cleanup, when fixing metadata
|
||||
before review has started, or when repairing a local commit that has not been pushed. If you believe squashing is the
|
||||
right choice despite an open review, explain why and ask first.
|
||||
|
||||
## Worktree Usage
|
||||
|
||||
Prefer using the current worktree for small requested fixes. Create a separate worktree only when the user asks for one,
|
||||
@@ -64,19 +49,6 @@ When code changes appear correct locally, create the PR promptly even if long-ru
|
||||
Opening the PR starts CI on the build machines, uploads results to the Bazel remote cache, and can speed up subsequent
|
||||
local validation. Continue monitoring any already-started local and CI validation after opening the PR.
|
||||
|
||||
For PRs expected to trigger substantial Bazel work in CI, run the relevant Bazel builds and tests locally too. This
|
||||
workstation is fast, and local Bazel runs can help hydrate the remote cache for the CI builders while still giving
|
||||
earlier signal on failures.
|
||||
|
||||
## Cleanup PR Batching
|
||||
|
||||
When making mechanical cleanup changes, group 3-5 files per PR when the files are receiving the same kind of change
|
||||
and can be reviewed as one pattern. Do not open one PR per file for nearly identical few-line cleanups, such as replacing
|
||||
the same unsafe accessor pattern with the same contextual failure pattern across multiple Scala files.
|
||||
|
||||
Keep separate PRs for changes that involve different semantics, materially different risk, unrelated subsystems, or
|
||||
files whose tests/validation strategy makes them better reviewed independently.
|
||||
|
||||
## GitHub CLI PR Bodies
|
||||
|
||||
When creating or editing PR descriptions with multiline bodies, write the body to a temporary markdown file and pass it
|
||||
@@ -84,17 +56,9 @@ with `gh pr create --body-file <path>` or `gh pr edit <number> --body-file <path
|
||||
shell quoting for PR bodies; Codex's permission matching may treat that as a complex shell command instead of a clean
|
||||
`gh pr` invocation, causing unnecessary approval prompts.
|
||||
|
||||
Write these temporary body files under `/private/tmp/eagle0-pr-bodies/`, creating that directory first when needed. This
|
||||
directory is the canonical writable scratch location for PR descriptions. Do not write PR body files to protected paths,
|
||||
repo-tracked paths, or ad hoc locations that might require additional user permission.
|
||||
|
||||
Use ordinary Bash file-writing commands for these temporary PR body files. First run a standalone command to create the
|
||||
directory. Then run a standalone `printf` command that writes to `/private/tmp/eagle0-pr-bodies/<name>.md`. **Do not use
|
||||
`apply_patch` for temporary PR body files**, because patch tools are for tracked workspace edits and may incorrectly
|
||||
request user approval for scratch paths outside the repository.
|
||||
|
||||
Write other scratch files needed for commands under `/private/tmp/eagle0-scratch/`, creating that directory first when
|
||||
needed. Do not use protected paths that require additional user permission just to create or edit temporary files.
|
||||
Write these temporary body files, and any other scratch files needed for commands, under a known writable location such
|
||||
as `/private/tmp`, `$TMPDIR`, or the current repository workspace. Do not use protected paths that require additional
|
||||
user permission just to create or edit temporary files.
|
||||
|
||||
---
|
||||
|
||||
@@ -137,14 +101,14 @@ Unity Client ↔ Eagle (gRPC streaming) ↔ Shardok (internal gRPC)
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Build Eagle server using the same target as GitHub Actions
|
||||
./scripts/build_eagle_ci.sh
|
||||
# Build Eagle server (Scala strategic layer)
|
||||
bazel build //src/main/scala/net/eagle0/eagle:eagle_server_deploy.jar
|
||||
|
||||
# Build Shardok server using the same target and flags as GitHub Actions
|
||||
./scripts/build_shardok_ci.sh
|
||||
# Build Shardok server (C++ tactical layer)
|
||||
bazel build -c opt //src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
|
||||
# Warm the remote cache for the main GitHub Actions Bazel builds
|
||||
./scripts/hydrate_bazel_remote_cache.sh
|
||||
# Shardok server includes both AI algorithms
|
||||
bazel build //src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
|
||||
# Build Unity/C# client
|
||||
./scripts/build_protos.sh # Protocol buffer generation for Unity
|
||||
@@ -197,19 +161,6 @@ bazel run gazelle # Update Go build files
|
||||
|
||||
The pre-commit hook runs gazelle but only checks if it succeeds - it does NOT verify the BUILD files are in canonical format. The `gazelle_test` will fail if deps are not alphabetically sorted. **Always run gazelle manually after BUILD file changes.**
|
||||
|
||||
### Bazel Cache Hydration
|
||||
|
||||
When making changes that will trigger GitHub Actions Bazel builds, run the matching CI script instead of hand-writing
|
||||
`bazel build` commands. The scripts keep local cache hydration aligned with CI targets, compilation modes, and flags:
|
||||
|
||||
```bash
|
||||
./scripts/build_eagle_ci.sh
|
||||
./scripts/build_shardok_ci.sh
|
||||
./scripts/hydrate_bazel_remote_cache.sh
|
||||
```
|
||||
|
||||
Use direct `bazel build` commands only for targeted debugging where CI cache hydration is not the goal.
|
||||
|
||||
### Code Formatting
|
||||
|
||||
```bash
|
||||
@@ -270,7 +221,7 @@ ShardokAIClient client(playerId, isDefender, hexMap, settings, AIAlgorithmType::
|
||||
|
||||
```bash
|
||||
# Build the server (includes both AI algorithms)
|
||||
./scripts/build_shardok_ci.sh
|
||||
bazel build //src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
|
||||
# Test both algorithms
|
||||
bazel test //src/test/cpp/net/eagle0/shardok/ai:ai_iterative_deepening_test
|
||||
@@ -313,7 +264,7 @@ to be used for different players or game situations within the same server proce
|
||||
**C# (Unity Client):**
|
||||
|
||||
- Located in `/src/main/csharp/net/eagle0/clients/unity/eagle0/`
|
||||
- Uses Unity 6.5 (6000.5.0f1) with comprehensive protobuf integration (100+ .proto files)
|
||||
- Uses Unity 6 (6000.4.10f1) with comprehensive protobuf integration (100+ .proto files)
|
||||
- Key components: `EagleConnection.cs` (gRPC client), `EagleGameController.cs` (main game logic)
|
||||
- Real-time bidirectional streaming with server via `PersistentClientConnection.cs`
|
||||
- Strategic map UI in `Assets/Eagle/`, tactical battle UI in `Assets/Shardok/`
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
load("@bazel_gazelle//:def.bzl", "gazelle")
|
||||
load("@io_bazel_rules_go//go:def.bzl", "nogo")
|
||||
load("@rules_shell//shell:sh_test.bzl", "sh_test")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
@@ -11,7 +10,6 @@ platform(
|
||||
"@platforms//os:linux",
|
||||
"@platforms//cpu:x86_64",
|
||||
],
|
||||
missing_toolchain_error = "No Linux x86_64 toolchain matched. Docker/Eagle image builds should pass --platforms=//:linux_x86_64 and --extra_toolchains=@llvm_toolchain_linux//:cc-toolchain-x86_64-linux when compiling C++ code.",
|
||||
)
|
||||
|
||||
# Platform for cross-compiling to Linux ARM64
|
||||
@@ -21,7 +19,6 @@ platform(
|
||||
"@platforms//os:linux",
|
||||
"@platforms//cpu:aarch64",
|
||||
],
|
||||
missing_toolchain_error = "No Linux ARM64 toolchain matched. Shardok ARM64 builds should pass --platforms=//:linux_arm64 and --extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux.",
|
||||
)
|
||||
|
||||
gazelle(name = "gazelle")
|
||||
|
||||
+29
-64
@@ -9,7 +9,7 @@ SCALAPB_VERSION = "1.0.0-alpha.5"
|
||||
|
||||
SCALAPB_JSON4S_VERSION = "1.0.0-alpha.1"
|
||||
|
||||
AWS_SDK_VERSION = "2.46.9"
|
||||
AWS_SDK_VERSION = "2.46.7"
|
||||
|
||||
SLF4J_VERSION = "2.0.18"
|
||||
|
||||
@@ -18,22 +18,14 @@ SLF4J_VERSION = "2.0.18"
|
||||
#
|
||||
|
||||
bazel_dep(name = "bazel_skylib", version = "1.9.0")
|
||||
bazel_dep(name = "aspect_rules_esbuild", version = "0.26.0")
|
||||
bazel_dep(name = "aspect_rules_js", version = "3.2.2")
|
||||
bazel_dep(name = "bazel_jar_jar", version = "0.1.15", repo_name = "bazel_jar_jar")
|
||||
bazel_dep(name = "platforms", version = "1.1.0")
|
||||
bazel_dep(name = "rules_dotnet", version = "0.21.5")
|
||||
bazel_dep(name = "rules_foreign_cc", version = "0.15.1")
|
||||
bazel_dep(name = "rules_android", version = "0.7.3")
|
||||
bazel_dep(name = "rules_nodejs", version = "6.7.4")
|
||||
bazel_dep(name = "rules_pkg", version = "1.2.0")
|
||||
bazel_dep(name = "rules_shell", version = "0.8.0")
|
||||
|
||||
#
|
||||
# Language Support - Scala
|
||||
#
|
||||
|
||||
bazel_dep(name = "rules_scala", version = "7.2.6")
|
||||
bazel_dep(name = "rules_scala", version = "7.2.5")
|
||||
|
||||
scala_config = use_extension(
|
||||
"@rules_scala//scala/extensions:config.bzl",
|
||||
@@ -53,31 +45,21 @@ scala_deps.scala_proto()
|
||||
# Language Support - C++
|
||||
#
|
||||
|
||||
bazel_dep(name = "toolchains_llvm", version = "1.8.0")
|
||||
bazel_dep(name = "toolchains_llvm", version = "1.7.0")
|
||||
|
||||
llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm")
|
||||
|
||||
LLVM_VERSION = "22.1.7"
|
||||
|
||||
LLVM_DISTRIBUTIONS = {
|
||||
"LLVM-22.1.7-Linux-ARM64.tar.xz": "118ca2d3ad9da34367e05735317854e7977db45dc4c02a32af58da64c23b8789",
|
||||
"LLVM-22.1.7-Linux-X64.tar.xz": "edb0522b41e261819c06ea437d249f9b8acfa413d3805bc9920eec6fb76ff830",
|
||||
"LLVM-22.1.7-macOS-ARM64.tar.xz": "4177245188b0a30a6539c96b361dea56f253485756bfd8927a6a59e7301e7806",
|
||||
}
|
||||
|
||||
# Native toolchain (macOS -> macOS, Linux -> Linux)
|
||||
llvm.toolchain(
|
||||
name = "llvm_toolchain",
|
||||
extra_llvm_distributions = LLVM_DISTRIBUTIONS,
|
||||
llvm_version = LLVM_VERSION,
|
||||
llvm_version = "20.1.8",
|
||||
)
|
||||
|
||||
# Cross-compilation toolchain (macOS -> Linux x86_64)
|
||||
# Uses the same LLVM distribution but with a Linux sysroot
|
||||
llvm.toolchain(
|
||||
name = "llvm_toolchain_linux",
|
||||
extra_llvm_distributions = LLVM_DISTRIBUTIONS,
|
||||
llvm_version = LLVM_VERSION,
|
||||
llvm_version = "20.1.8",
|
||||
)
|
||||
|
||||
# Linux x86_64 sysroot for cross-compilation
|
||||
@@ -90,8 +72,7 @@ llvm.sysroot(
|
||||
# Cross-compilation toolchain (macOS -> Linux ARM64)
|
||||
llvm.toolchain(
|
||||
name = "llvm_toolchain_linux_arm64",
|
||||
extra_llvm_distributions = LLVM_DISTRIBUTIONS,
|
||||
llvm_version = LLVM_VERSION,
|
||||
llvm_version = "20.1.8",
|
||||
)
|
||||
|
||||
# Linux ARM64 sysroot for cross-compilation
|
||||
@@ -172,16 +153,8 @@ use_repo(crate, "map_generator_crates")
|
||||
# Platform Support - Apple/iOS
|
||||
#
|
||||
|
||||
bazel_dep(name = "apple_support", version = "2.6.1", repo_name = "build_bazel_apple_support")
|
||||
bazel_dep(name = "rules_apple", version = "4.5.3", repo_name = "build_bazel_rules_apple")
|
||||
|
||||
multiple_version_override(
|
||||
module_name = "rules_swift",
|
||||
versions = [
|
||||
"2.1.1",
|
||||
"3.5.0",
|
||||
],
|
||||
)
|
||||
bazel_dep(name = "apple_support", version = "1.24.5", repo_name = "build_bazel_apple_support")
|
||||
bazel_dep(name = "rules_apple", version = "4.3.3", repo_name = "build_bazel_rules_apple")
|
||||
|
||||
# Register Apple CC toolchain for Objective-C compilation
|
||||
apple_cc_configure = use_extension(
|
||||
@@ -194,15 +167,11 @@ use_repo(apple_cc_configure, "local_config_apple_cc", "local_config_apple_cc_too
|
||||
# Protocol Buffers & RPC
|
||||
#
|
||||
|
||||
bazel_dep(name = "protobuf", version = "35.1", repo_name = "com_google_protobuf")
|
||||
single_version_override(
|
||||
module_name = "protobuf",
|
||||
version = "35.1",
|
||||
)
|
||||
bazel_dep(name = "protobuf", version = "33.6", repo_name = "com_google_protobuf")
|
||||
|
||||
# Use pre-built protoc binaries instead of compiling from source.
|
||||
# See: https://protobuf.dev/reference/cpp/cpp-generated/#invocation
|
||||
prebuilt_protoc = use_extension("@com_google_protobuf//bazel/private/oss/toolchains/prebuilt:protoc_extension.bzl", "protoc")
|
||||
prebuilt_protoc = use_extension("@com_google_protobuf//bazel/private:prebuilt_protoc_extension.bzl", "protoc")
|
||||
use_repo(
|
||||
prebuilt_protoc,
|
||||
"prebuilt_protoc.linux_aarch_64",
|
||||
@@ -218,14 +187,17 @@ use_repo(
|
||||
|
||||
bazel_dep(name = "rules_proto", version = "7.1.0")
|
||||
bazel_dep(name = "rules_cc", version = "0.2.19")
|
||||
bazel_dep(name = "grpc", version = "1.81.1")
|
||||
bazel_dep(name = "grpc", version = "1.78.0")
|
||||
|
||||
# gRPC 1.81.1's generated upb module map trips Clang's layering check in the
|
||||
# Linux ARM64 cross-toolchain even though the normal Bazel deps are present.
|
||||
# Patch grpc 1.78.0 to fix rules_python incompatibility: grpc requests Python
|
||||
# 3.14.0b2 toolchain but rules_python >= 1.6.0 removed it in favor of 3.14.
|
||||
# Upstream fix: https://github.com/grpc/grpc/commit/459279b
|
||||
# TODO: Remove this override once a fixed grpc version is published to BCR.
|
||||
single_version_override(
|
||||
module_name = "grpc",
|
||||
patch_strip = 1,
|
||||
patches = ["//third_party/grpc/patches:disable_xds_client_layering_check.patch"],
|
||||
patches = ["//third_party/patches:grpc_fix_python_version.patch"],
|
||||
version = "1.78.0",
|
||||
)
|
||||
|
||||
bazel_dep(name = "grpc-java", version = "1.78.0.bcr.1")
|
||||
@@ -253,19 +225,19 @@ oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")
|
||||
# curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json" "https://registry-1.docker.io/v2/library/eclipse-temurin/manifests/25-jre" | python3 -c "import json,sys; m=json.load(sys.stdin); [print(p['digest']) for p in m.get('manifests',[]) if p['platform']['architecture']=='amd64']"
|
||||
oci.pull(
|
||||
name = "eclipse_temurin_25_jre",
|
||||
digest = "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
|
||||
digest = "sha256:98236ffdfb61cff3fc4f8e40e5b80460a5a4c35a0f56a5b4182a926844a7db49",
|
||||
image = "docker.io/library/eclipse-temurin",
|
||||
platforms = ["linux/amd64"],
|
||||
tag = "26-jre",
|
||||
tag = "25-jre",
|
||||
)
|
||||
|
||||
# Base image for JFR sidecar (Java 25 JDK - includes jcmd for JFR dumps)
|
||||
oci.pull(
|
||||
name = "eclipse_temurin_25_jdk",
|
||||
digest = "sha256:41c3a5b8a607d226c73e81538cd94d0e66f746f41f057561447eb47b40a91a65",
|
||||
digest = "sha256:bc2d562355813350f47bc472b9721b0b84761866671ad95d9a9f1151fa69da6e",
|
||||
image = "docker.io/library/eclipse-temurin",
|
||||
platforms = ["linux/amd64"],
|
||||
tag = "26-jdk",
|
||||
tag = "25-jdk",
|
||||
)
|
||||
|
||||
# Base image for Shardok (Ubuntu 24.04 for C++ runtime)
|
||||
@@ -276,16 +248,15 @@ oci.pull(
|
||||
"linux/amd64",
|
||||
"linux/arm64/v8",
|
||||
],
|
||||
tag = "26.04",
|
||||
tag = "24.04",
|
||||
)
|
||||
|
||||
# Base image for Admin Server (Alpine for lightweight Go binary)
|
||||
oci.pull(
|
||||
name = "alpine_linux",
|
||||
digest = "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
|
||||
image = "docker.io/library/alpine",
|
||||
platforms = ["linux/amd64"],
|
||||
tag = "3.24",
|
||||
tag = "3.21",
|
||||
)
|
||||
use_repo(oci, "alpine_linux", "alpine_linux_linux_amd64", "eclipse_temurin_25_jdk", "eclipse_temurin_25_jdk_linux_amd64", "eclipse_temurin_25_jre", "eclipse_temurin_25_jre_linux_amd64", "ubuntu_24_04", "ubuntu_24_04_linux_amd64", "ubuntu_24_04_linux_arm64_v8")
|
||||
|
||||
@@ -294,7 +265,7 @@ use_repo(oci, "alpine_linux", "alpine_linux_linux_amd64", "eclipse_temurin_25_jd
|
||||
#
|
||||
|
||||
bazel_dep(name = "rules_java", version = "9.6.1")
|
||||
bazel_dep(name = "rules_jvm_external", version = "7.0")
|
||||
bazel_dep(name = "rules_jvm_external", version = "6.10")
|
||||
|
||||
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
|
||||
maven.install(
|
||||
@@ -369,10 +340,6 @@ maven.install(
|
||||
],
|
||||
duplicate_version_warning = "error",
|
||||
fail_if_repin_required = True,
|
||||
known_contributing_modules = [
|
||||
"grpc-java",
|
||||
"protobuf",
|
||||
],
|
||||
lock_file = "//:maven_install.json",
|
||||
repositories = [
|
||||
"https://repo.maven.apache.org/maven2",
|
||||
@@ -405,7 +372,7 @@ maven.artifact(
|
||||
artifact = "guava",
|
||||
force_version = True,
|
||||
group = "com.google.guava",
|
||||
version = "33.6.0-jre",
|
||||
version = "33.6.0-android",
|
||||
)
|
||||
use_repo(maven, "maven", "unpinned_maven")
|
||||
|
||||
@@ -425,8 +392,6 @@ GTL_SHA = "1969c45dd76eac0dd87e9e2b65cffe358617f4fe1bcd203f72f427742537913a"
|
||||
http_archive(
|
||||
name = "gtl",
|
||||
build_file_content = """
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
|
||||
cc_library(
|
||||
name = "gtl",
|
||||
hdrs = glob(["include/gtl/*.hpp"]),
|
||||
@@ -454,12 +419,12 @@ http_archive(
|
||||
)
|
||||
|
||||
# Sparkle framework for macOS auto-updates
|
||||
SPARKLE_VERSION = "2.9.3"
|
||||
SPARKLE_VERSION = "2.6.4"
|
||||
|
||||
http_archive(
|
||||
name = "sparkle",
|
||||
build_file = "//src/main/objc/net/eagle0/clients/unity/sparkle/external:BUILD.sparkle",
|
||||
sha256 = "74a07da821f92b79310009954c0e15f350173374a3abe39095b4fc5096916be6",
|
||||
sha256 = "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b",
|
||||
strip_prefix = "",
|
||||
url = "https://github.com/sparkle-project/Sparkle/releases/download/%s/Sparkle-%s.tar.xz" % (SPARKLE_VERSION, SPARKLE_VERSION),
|
||||
)
|
||||
@@ -560,7 +525,7 @@ register_toolchains("@local_config_apple_cc_toolchains//:all")
|
||||
# Set dev_dependency so we can turn this off for swift MacOS builds
|
||||
register_toolchains(
|
||||
"@llvm_toolchain//:all",
|
||||
"@llvm_toolchain_linux//:cc-toolchain-x86_64-linux",
|
||||
"@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux",
|
||||
"@llvm_toolchain_linux//:all",
|
||||
"@llvm_toolchain_linux_arm64//:all",
|
||||
dev_dependency = True,
|
||||
)
|
||||
|
||||
Generated
+2338
-1793
File diff suppressed because one or more lines are too long
+2
-2
@@ -212,9 +212,9 @@ oci_push(
|
||||
)
|
||||
|
||||
#
|
||||
# Shardok Server ARM64 Docker Image (for the Hetzner deployment host)
|
||||
# Shardok Server ARM64 Docker Image (for Hetzner on-demand compute)
|
||||
#
|
||||
# Build: bazel build //ci:shardok_server_image_arm64 --platforms=//:linux_arm64 --extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux
|
||||
# Build: bazel build //ci:shardok_server_image_arm64 --platforms=//:linux_arm64 --extra_toolchains=@llvm_toolchain_linux_arm64//:all
|
||||
# Load: bazel run //ci:shardok_server_load_arm64
|
||||
# Push: bazel run //ci:shardok_server_push_arm64
|
||||
#
|
||||
|
||||
@@ -5,7 +5,8 @@ set -x
|
||||
|
||||
COMMAND_LOG="$(bazel info command_log)"
|
||||
bazel build \
|
||||
//src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
//src/main/cpp/net/eagle0/shardok:shardok-server \
|
||||
//src/main/cpp/net/eagle0/shardok:shardok-inspector
|
||||
STATUS="$?"
|
||||
|
||||
if [ $STATUS -eq 0 ]
|
||||
@@ -25,6 +26,7 @@ echo "Setting up Shardok files..."
|
||||
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server ./shardok/
|
||||
/bin/mkdir -p ./shardok/shardok-server.runfiles/net_eagle0/src/main/
|
||||
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server.runfiles/net_eagle0/src/main/resources ./shardok/shardok-server.runfiles/net_eagle0/src/main/
|
||||
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-inspector ./shardok/
|
||||
|
||||
echo "tar & zip shardok files..."
|
||||
/bin/tar cfh - shardok | /usr/bin/pigz > ./deploy/shardok.tar.gz
|
||||
|
||||
@@ -40,6 +40,19 @@ if [ -f "$INFO_PLIST" ]; then
|
||||
/usr/libexec/PlistBuddy -c "Set :ITSAppUsesNonExemptEncryption false" "$INFO_PLIST" 2>/dev/null \
|
||||
|| /usr/libexec/PlistBuddy -c "Add :ITSAppUsesNonExemptEncryption bool false" "$INFO_PLIST"
|
||||
echo "Set ITSAppUsesNonExemptEncryption=false in Info.plist"
|
||||
|
||||
# Remove UIApplicationSceneManifest if present.
|
||||
# Unity 6 generates this key, which opts the app into the iOS scene-based
|
||||
# lifecycle. This breaks Application.deepLinkActivated because iOS routes
|
||||
# deep link URLs through UISceneDelegate instead of UIApplicationDelegate,
|
||||
# and Unity doesn't implement the scene delegate path.
|
||||
# See: https://issuetracker.unity3d.com/issues/in-135632
|
||||
if /usr/libexec/PlistBuddy -c "Print :UIApplicationSceneManifest" "$INFO_PLIST" 2>/dev/null; then
|
||||
echo "Found UIApplicationSceneManifest in Info.plist — removing to fix deep link handling"
|
||||
/usr/libexec/PlistBuddy -c "Delete :UIApplicationSceneManifest" "$INFO_PLIST"
|
||||
else
|
||||
echo "No UIApplicationSceneManifest in Info.plist (deep links should work)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Unity always generates "Unity-iPhone" as the main app scheme
|
||||
|
||||
@@ -9,23 +9,13 @@ WORKSPACE=$(pwd)
|
||||
UNITY_INSTALL_PATH="/Applications/Unity/Hub/Editor"
|
||||
BUILD_DIR=$1
|
||||
LOG_PATH=$2
|
||||
BUILD_ADDRESSABLES=${3:-true}
|
||||
ADDRESSABLES_BUILD_LAYOUT=${4:-$BUILD_ADDRESSABLES}
|
||||
|
||||
echo "Building Mac in $BUILD_DIR"
|
||||
echo "Build Addressables: $BUILD_ADDRESSABLES"
|
||||
echo "Generate Addressables build layout: $ADDRESSABLES_BUILD_LAYOUT"
|
||||
|
||||
echo "Cleaning up $BUILD_DIR"
|
||||
/bin/rm -rf "$BUILD_DIR"
|
||||
/bin/mkdir -p "$BUILD_DIR"
|
||||
|
||||
if [ -n "${DOTNET_ROOT:-}" ]; then
|
||||
export PATH="$DOTNET_ROOT:$PATH"
|
||||
export DOTNET_HOST_PATH="$DOTNET_ROOT/dotnet"
|
||||
fi
|
||||
dotnet --list-sdks
|
||||
|
||||
# Use custom build script that builds Addressables before the player
|
||||
# Capture exit code to show log on failure
|
||||
set +e
|
||||
@@ -35,8 +25,6 @@ ${UNITY_INSTALL_PATH}/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \
|
||||
-quit \
|
||||
-executeMethod BuildScript.BuildMacPlayer \
|
||||
-buildPath "$BUILD_DIR/eagle0.app" \
|
||||
-buildAddressables "$BUILD_ADDRESSABLES" \
|
||||
-addressablesBuildLayout "$ADDRESSABLES_BUILD_LAYOUT" \
|
||||
-logFile "$LOG_PATH" \
|
||||
-projectPath "$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
UNITY_EXIT_CODE=$?
|
||||
|
||||
@@ -15,7 +15,6 @@ git log -3
|
||||
/bin/echo "build Windows"
|
||||
LOG_PATH="${BUILD_BASE}/editor_win.log"
|
||||
BUILD_DIR=$1
|
||||
BUILD_ADDRESSABLES=${2:-true}
|
||||
ADDRESSABLES_BUILD_LAYOUT=${3:-$BUILD_ADDRESSABLES}
|
||||
|
||||
./ci/github_actions/build_windows.sh "$BUILD_DIR" "$LOG_PATH" "$BUILD_ADDRESSABLES" "$ADDRESSABLES_BUILD_LAYOUT"
|
||||
./ci/github_actions/build_windows.sh $BUILD_DIR $LOG_PATH
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ BUILD_BASE="${EAGLE0_BUILD_DIR:-/tmp/eagle0}"
|
||||
|
||||
WORKSPACE=$(pwd)
|
||||
BUILD_PATH=${1:-"${BUILD_BASE}/eagle0iOS"}
|
||||
BUILD_ADDRESSABLES=${2:-true}
|
||||
ADDRESSABLES_BUILD_LAYOUT=${3:-$BUILD_ADDRESSABLES}
|
||||
LOG_PATH="${BUILD_BASE}/editor_ios.log"
|
||||
|
||||
# Generate unique build number from git commit count
|
||||
@@ -27,8 +25,6 @@ echo "Building protos"
|
||||
UNITY_INSTALL_PATH="/Applications/Unity/Hub/Editor"
|
||||
|
||||
echo "Building iOS Unity player to: $BUILD_PATH"
|
||||
echo "Build Addressables: $BUILD_ADDRESSABLES"
|
||||
echo "Generate Addressables build layout: $ADDRESSABLES_BUILD_LAYOUT"
|
||||
|
||||
mkdir -p "$(dirname "$LOG_PATH")"
|
||||
mkdir -p "$BUILD_PATH"
|
||||
@@ -43,8 +39,6 @@ ${UNITY_INSTALL_PATH}/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \
|
||||
-executeMethod BuildScript.BuildiOSPlayer \
|
||||
-buildPath "$BUILD_PATH" \
|
||||
-buildNumber "$BUILD_NUMBER" \
|
||||
-buildAddressables "$BUILD_ADDRESSABLES" \
|
||||
-addressablesBuildLayout "$ADDRESSABLES_BUILD_LAYOUT" \
|
||||
-logFile "$LOG_PATH" \
|
||||
-projectPath "$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
UNITY_EXIT_CODE=$?
|
||||
@@ -59,20 +53,6 @@ if [ $UNITY_EXIT_CODE -ne 0 ]; then
|
||||
exit $UNITY_EXIT_CODE
|
||||
fi
|
||||
|
||||
if [ ! -f "$LOG_PATH" ]; then
|
||||
echo ""
|
||||
echo "ERROR: Unity exited successfully but did not write an editor log at $LOG_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$BUILD_PATH/Data/Raw/aa/settings.json" ]; then
|
||||
echo ""
|
||||
echo "ERROR: iOS player is missing Addressables runtime data at:"
|
||||
echo " $BUILD_PATH/Data/Raw/aa/settings.json"
|
||||
echo "Build Addressables before packaging the player so Addressables can initialize at runtime."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Fail the build if any prefab references are broken — this produces a player
|
||||
# that launches but has null Inspector fields, which is hard to debug.
|
||||
if grep -q "Missing Prefab" "$LOG_PATH"; then
|
||||
|
||||
@@ -18,7 +18,5 @@ git log -3
|
||||
/bin/echo "build Mac"
|
||||
LOG_PATH="${BUILD_BASE}/editor_mac.log"
|
||||
BUILD_DIR=$1
|
||||
BUILD_ADDRESSABLES=${2:-true}
|
||||
ADDRESSABLES_BUILD_LAYOUT=${3:-$BUILD_ADDRESSABLES}
|
||||
|
||||
./ci/github_actions/build_mac.sh "$BUILD_DIR" "$LOG_PATH" "$BUILD_ADDRESSABLES" "$ADDRESSABLES_BUILD_LAYOUT"
|
||||
./ci/github_actions/build_mac.sh "$BUILD_DIR" "$LOG_PATH"
|
||||
|
||||
@@ -9,23 +9,13 @@ WORKSPACE=`pwd`
|
||||
UNITY_INSTALL_PATH="/Applications/Unity/Hub/Editor"
|
||||
BUILD_DIR=$1
|
||||
LOG_PATH=$2
|
||||
BUILD_ADDRESSABLES=${3:-true}
|
||||
ADDRESSABLES_BUILD_LAYOUT=${4:-$BUILD_ADDRESSABLES}
|
||||
|
||||
echo "Building in $1"
|
||||
echo "Build Addressables: $BUILD_ADDRESSABLES"
|
||||
echo "Generate Addressables build layout: $ADDRESSABLES_BUILD_LAYOUT"
|
||||
|
||||
echo "Cleaning up $1"
|
||||
/bin/rm -rf $1
|
||||
/bin/mkdir -p $1
|
||||
|
||||
if [ -n "${DOTNET_ROOT:-}" ]; then
|
||||
export PATH="$DOTNET_ROOT:$PATH"
|
||||
export DOTNET_HOST_PATH="$DOTNET_ROOT/dotnet"
|
||||
fi
|
||||
dotnet --list-sdks
|
||||
|
||||
# Use custom build script that builds Addressables before the player
|
||||
# Capture exit code to show log on failure
|
||||
set +e
|
||||
@@ -35,8 +25,6 @@ ${UNITY_INSTALL_PATH}/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \
|
||||
-quit \
|
||||
-executeMethod BuildScript.BuildWindowsPlayer \
|
||||
-buildPath "$BUILD_DIR/eagle0.exe" \
|
||||
-buildAddressables "$BUILD_ADDRESSABLES" \
|
||||
-addressablesBuildLayout "$ADDRESSABLES_BUILD_LAYOUT" \
|
||||
-logFile $LOG_PATH \
|
||||
-projectPath "$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
UNITY_EXIT_CODE=$?
|
||||
@@ -57,14 +45,6 @@ if [ ! -f "$LOG_PATH" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$BUILD_DIR/eagle0_Data/StreamingAssets/aa/settings.json" ]; then
|
||||
echo ""
|
||||
echo "ERROR: Windows player is missing Addressables runtime data at:"
|
||||
echo " $BUILD_DIR/eagle0_Data/StreamingAssets/aa/settings.json"
|
||||
echo "Build Addressables before packaging the player so Addressables can initialize at runtime."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Fail the build if any prefab references are broken — this produces a player
|
||||
# that launches but has null Inspector fields, which is hard to debug.
|
||||
if grep -q "Missing Prefab" "$LOG_PATH"; then
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE_SHA=${1:-}
|
||||
HEAD_SHA=${2:-HEAD}
|
||||
OUTPUT_FILE=${GITHUB_OUTPUT:-}
|
||||
|
||||
if [ -z "$BASE_SHA" ] || [[ "$BASE_SHA" =~ ^0+$ ]]; then
|
||||
echo "No usable base SHA for this run; building Addressables as a safe default."
|
||||
changed=true
|
||||
elif ! git cat-file -e "$BASE_SHA^{commit}"; then
|
||||
echo "Base SHA $BASE_SHA is not available locally; building Addressables as a safe default."
|
||||
changed=true
|
||||
else
|
||||
UNITY_PROJECT="src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
ADDRESSABLE_PREFIXES=(
|
||||
"$UNITY_PROJECT/Assets/AddressableAssetsData/"
|
||||
"$UNITY_PROJECT/Assets/Eagle/Effects/"
|
||||
"$UNITY_PROJECT/Assets/Hex Tiles/"
|
||||
"$UNITY_PROJECT/Assets/TileableBridgePack/"
|
||||
"$UNITY_PROJECT/Assets/Shardok/LargeFlames.prefab"
|
||||
"$UNITY_PROJECT/Assets/Shardok/soundEffects/"
|
||||
"$UNITY_PROJECT/Assets/Shardok/Sounds/"
|
||||
"$UNITY_PROJECT/Assets/Medieval Combat Sounds/"
|
||||
"$UNITY_PROJECT/Assets/Magic Spells Sound Effects LITE/"
|
||||
"$UNITY_PROJECT/Assets/Fantasy Interface Sounds/"
|
||||
"$UNITY_PROJECT/Assets/AustraliaAnimalsPackv1/"
|
||||
"$UNITY_PROJECT/Assets/Music/"
|
||||
"$UNITY_PROJECT/Assets/Editor/BuildScript.cs"
|
||||
)
|
||||
|
||||
is_addressables_input() {
|
||||
local file="$1"
|
||||
for prefix in "${ADDRESSABLE_PREFIXES[@]}"; do
|
||||
if [[ "$file" == "$prefix"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
changed=false
|
||||
while IFS= read -r file; do
|
||||
if is_addressables_input "$file"; then
|
||||
echo "Addressables-impacting change: $file"
|
||||
changed=true
|
||||
break
|
||||
fi
|
||||
done < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
|
||||
fi
|
||||
|
||||
if [ "$changed" = "true" ]; then
|
||||
echo "Building Addressables because their inputs changed."
|
||||
else
|
||||
echo "Skipping Addressables build; no Addressables inputs changed."
|
||||
fi
|
||||
|
||||
if [ -n "$OUTPUT_FILE" ]; then
|
||||
echo "changed=$changed" >> "$OUTPUT_FILE"
|
||||
else
|
||||
echo "changed=$changed"
|
||||
fi
|
||||
@@ -5,26 +5,8 @@ set -euo pipefail
|
||||
COMMON_PATHS="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
export PATH="${COMMON_PATHS}:${PATH}"
|
||||
|
||||
repo_contents_cache_dir() {
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
printf '%s\n' "${HOME}/Library/Caches/eagle0-bazel-repo-contents"
|
||||
else
|
||||
printf '%s\n' "${XDG_CACHE_HOME:-${HOME}/.cache}/eagle0-bazel-repo-contents"
|
||||
fi
|
||||
}
|
||||
|
||||
write_bazel_local_config() {
|
||||
local repo_contents_cache
|
||||
repo_contents_cache="$(repo_contents_cache_dir)"
|
||||
|
||||
write_developer_dir_override() {
|
||||
if [ "$(uname -s)" != "Darwin" ]; then
|
||||
if [ -n "${CI:-}" ]; then
|
||||
mkdir -p "$repo_contents_cache"
|
||||
{
|
||||
echo "# Generated by ci/github_actions/ensure_bazel_installed.sh; do not edit."
|
||||
printf 'common --repo_contents_cache=%s\n' "$repo_contents_cache"
|
||||
} > .bazelrc.local
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -48,10 +30,8 @@ write_bazel_local_config() {
|
||||
if [ -f .bazelrc.local ] && [ -z "${CI:-}" ]; then
|
||||
echo "Keeping existing .bazelrc.local outside CI"
|
||||
else
|
||||
mkdir -p "$repo_contents_cache"
|
||||
{
|
||||
echo "# Generated by ci/github_actions/ensure_bazel_installed.sh; do not edit."
|
||||
printf 'common --repo_contents_cache=%s\n' "$repo_contents_cache"
|
||||
printf 'common:macos --repo_env=DEVELOPER_DIR=%s\n' "$developer_dir"
|
||||
} > .bazelrc.local
|
||||
fi
|
||||
@@ -63,7 +43,7 @@ write_bazel_local_config() {
|
||||
echo "Using Apple developer directory at $developer_dir"
|
||||
}
|
||||
|
||||
write_bazel_local_config
|
||||
write_developer_dir_override
|
||||
|
||||
if [ -n "${GITHUB_PATH:-}" ]; then
|
||||
for path in /opt/homebrew/bin /usr/local/bin; do
|
||||
|
||||
@@ -49,12 +49,16 @@ check_modules_installed() {
|
||||
fi
|
||||
;;
|
||||
mac)
|
||||
if ! has_mac_il2cpp_support "$unity_path" "$unity_app_contents_path"; then
|
||||
# Mac IL2CPP module
|
||||
if [ ! -d "${unity_app_contents_path}/PlaybackEngines/MacStandaloneSupport/Source/Player/MacPlayer/GameAssembly.xcodeproj" ] && \
|
||||
[ ! -d "${unity_path}/PlaybackEngines/MacStandaloneSupport/Variations/macos_development_il2cpp" ] && \
|
||||
[ ! -d "${unity_path}/PlaybackEngines/MacStandaloneSupport" ]; then
|
||||
echo "✗ Mac IL2CPP module not installed for Unity ${UNITY_VERSION}"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
windows)
|
||||
# Windows mono module
|
||||
if [ ! -d "${unity_path}/PlaybackEngines/WindowsStandaloneSupport" ]; then
|
||||
echo "✗ Windows module not installed for Unity ${UNITY_VERSION}"
|
||||
return 1
|
||||
@@ -67,8 +71,9 @@ check_modules_installed() {
|
||||
echo "✗ iOS module missing"
|
||||
missing=1
|
||||
fi
|
||||
if ! has_mac_il2cpp_support "$unity_path" "$unity_app_contents_path"; then
|
||||
echo "✗ Mac IL2CPP module missing"
|
||||
if [ ! -d "${unity_app_contents_path}/PlaybackEngines/MacStandaloneSupport/Source/Player/MacPlayer/GameAssembly.xcodeproj" ] && \
|
||||
[ ! -d "${unity_path}/PlaybackEngines/MacStandaloneSupport" ]; then
|
||||
echo "✗ Mac module missing"
|
||||
missing=1
|
||||
fi
|
||||
if [ ! -d "${unity_path}/PlaybackEngines/WindowsStandaloneSupport" ]; then
|
||||
@@ -84,27 +89,6 @@ check_modules_installed() {
|
||||
return 0
|
||||
}
|
||||
|
||||
has_mac_il2cpp_support() {
|
||||
local unity_path="$1"
|
||||
local unity_app_contents_path="$2"
|
||||
|
||||
has_il2cpp_variation "${unity_app_contents_path}/PlaybackEngines/MacStandaloneSupport/Variations" ||
|
||||
has_il2cpp_variation "${unity_path}/PlaybackEngines/MacStandaloneSupport/Variations"
|
||||
}
|
||||
|
||||
has_il2cpp_variation() {
|
||||
local variations_path="$1"
|
||||
local variation
|
||||
|
||||
for variation in "${variations_path}"/*il2cpp*; do
|
||||
if [ -d "$variation" ]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if already installed with required modules
|
||||
if check_modules_installed; then
|
||||
echo "✓ Unity ${UNITY_VERSION} is already installed with ${PLATFORM} support"
|
||||
|
||||
@@ -38,23 +38,15 @@ fi
|
||||
echo "Using git-lfs at $(command -v git-lfs)"
|
||||
git lfs version
|
||||
|
||||
git lfs install --local --force
|
||||
git lfs install --force
|
||||
|
||||
echo "LFS objects before pull:"
|
||||
git lfs ls-files | wc -l
|
||||
|
||||
GIT_LFS_PULL=(git lfs pull)
|
||||
FALLBACK_GITHUB_LFS_PULL=()
|
||||
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
BASIC_AUTH=$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')
|
||||
GIT_LFS_PULL=(git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic $BASIC_AUTH" lfs pull)
|
||||
FALLBACK_GITHUB_LFS_PULL=(
|
||||
git
|
||||
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic $BASIC_AUTH"
|
||||
-c "lfs.url=https://github.com/nolen777/eagle0.git/info/lfs"
|
||||
lfs
|
||||
pull
|
||||
)
|
||||
fi
|
||||
|
||||
for i in $(seq 1 $MAX_ATTEMPTS); do
|
||||
@@ -69,14 +61,5 @@ for i in $(seq 1 $MAX_ATTEMPTS); do
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#FALLBACK_GITHUB_LFS_PULL[@]}" -gt 0 ]; then
|
||||
echo "LFS mirror pull failed after $MAX_ATTEMPTS attempts; trying GitHub LFS fallback"
|
||||
if "${FALLBACK_GITHUB_LFS_PULL[@]}" "$@"; then
|
||||
echo "LFS objects after GitHub fallback pull:"
|
||||
git lfs ls-files | wc -l
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "LFS pull failed after $MAX_ATTEMPTS attempts"
|
||||
exit 1
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Print a compact timing summary from a Bazel build event JSON file."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from collections import Counter, defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def duration_to_ms(value: Any) -> float | None:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, (int, float)):
|
||||
return float(value)
|
||||
if isinstance(value, str):
|
||||
if value.endswith("s"):
|
||||
try:
|
||||
return float(value[:-1]) * 1000
|
||||
except ValueError:
|
||||
return None
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
return None
|
||||
if isinstance(value, dict):
|
||||
seconds = value.get("seconds", 0)
|
||||
nanos = value.get("nanos", 0)
|
||||
try:
|
||||
return float(seconds) * 1000 + float(nanos) / 1_000_000
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def format_ms(milliseconds: float | None) -> str | None:
|
||||
if milliseconds is None:
|
||||
return None
|
||||
if milliseconds >= 1000:
|
||||
return f"{milliseconds / 1000:.2f}s"
|
||||
return f"{milliseconds:.0f}ms"
|
||||
|
||||
|
||||
def nested_get(data: dict[str, Any], *keys: str) -> Any:
|
||||
current: Any = data
|
||||
for key in keys:
|
||||
if not isinstance(current, dict):
|
||||
return None
|
||||
current = current.get(key)
|
||||
return current
|
||||
|
||||
|
||||
def read_events(path: Path) -> list[dict[str, Any]]:
|
||||
events = []
|
||||
with path.open() as file:
|
||||
for line_number, line in enumerate(file, start=1):
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
try:
|
||||
event = json.loads(stripped)
|
||||
except json.JSONDecodeError as error:
|
||||
print(f"Ignoring malformed BEP line {line_number}: {error}")
|
||||
continue
|
||||
if isinstance(event, dict):
|
||||
events.append(event)
|
||||
return events
|
||||
|
||||
|
||||
def collect_metric_events(events: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
metric_events = []
|
||||
for event in events:
|
||||
if isinstance(event.get("buildMetrics"), dict):
|
||||
metric_events.append(event["buildMetrics"])
|
||||
return metric_events
|
||||
|
||||
|
||||
def first_present(data: dict[str, Any], keys: list[str]) -> Any:
|
||||
for key in keys:
|
||||
value = data.get(key)
|
||||
if value is not None:
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def summarize_timing(build_metrics: dict[str, Any]) -> list[str]:
|
||||
timing = build_metrics.get("timingMetrics")
|
||||
if not isinstance(timing, dict):
|
||||
return []
|
||||
|
||||
labels = [
|
||||
("Wall time", ["wallTimeInMs", "wallTime", "elapsedTime"]),
|
||||
("Loading phase", ["loadingPhaseTimeInMs", "loadingPhaseTime"]),
|
||||
("Analysis phase", ["analysisPhaseTimeInMs", "analysisPhaseTime"]),
|
||||
("Execution phase", ["executionPhaseTimeInMs", "executionPhaseTime"]),
|
||||
("Critical path", ["criticalPathTimeInMs", "criticalPathTime"]),
|
||||
]
|
||||
|
||||
lines = []
|
||||
for label, keys in labels:
|
||||
value = format_ms(duration_to_ms(first_present(timing, keys)))
|
||||
if value is not None:
|
||||
lines.append(f" {label}: {value}")
|
||||
return lines
|
||||
|
||||
|
||||
def summarize_counts(build_metrics: dict[str, Any]) -> list[str]:
|
||||
summaries: list[tuple[str, Any]] = []
|
||||
|
||||
action_summary = build_metrics.get("actionSummary")
|
||||
if isinstance(action_summary, dict):
|
||||
summaries.extend(
|
||||
[
|
||||
("Actions created", action_summary.get("actionsCreated")),
|
||||
("Actions executed", action_summary.get("actionsExecuted")),
|
||||
("Action cache hits", action_summary.get("actionCacheHits")),
|
||||
("Remote cache hits", action_summary.get("remoteCacheHits")),
|
||||
]
|
||||
)
|
||||
|
||||
target_metrics = build_metrics.get("targetMetrics")
|
||||
if isinstance(target_metrics, dict):
|
||||
summaries.extend(
|
||||
[
|
||||
("Targets configured", target_metrics.get("targetsConfigured")),
|
||||
("Targets loaded", target_metrics.get("targetsLoaded")),
|
||||
]
|
||||
)
|
||||
|
||||
package_metrics = build_metrics.get("packageMetrics")
|
||||
if isinstance(package_metrics, dict):
|
||||
summaries.extend(
|
||||
[
|
||||
("Packages loaded", package_metrics.get("packagesLoaded")),
|
||||
("Packages successfully loaded", package_metrics.get("packagesSuccessfullyLoaded")),
|
||||
]
|
||||
)
|
||||
|
||||
return [f" {label}: {value}" for label, value in summaries if value is not None]
|
||||
|
||||
|
||||
def worker_mnemonic(metric: dict[str, Any]) -> str:
|
||||
for path in [
|
||||
("mnemonic",),
|
||||
("workerKey", "mnemonic"),
|
||||
("workerKeyInfo", "mnemonic"),
|
||||
]:
|
||||
value = nested_get(metric, *path)
|
||||
if value:
|
||||
return str(value)
|
||||
return "unknown"
|
||||
|
||||
|
||||
def worker_actions(metric: dict[str, Any]) -> int | None:
|
||||
for key in ["actionsExecuted", "executedActionCount", "actionCount", "actions"]:
|
||||
value = metric.get(key)
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def summarize_workers(build_metrics: dict[str, Any]) -> list[str]:
|
||||
worker_metrics = build_metrics.get("workerMetrics")
|
||||
if not isinstance(worker_metrics, list) or not worker_metrics:
|
||||
return []
|
||||
|
||||
counts = Counter()
|
||||
actions_by_mnemonic: defaultdict[str, int] = defaultdict(int)
|
||||
saw_action_counts = False
|
||||
|
||||
for metric in worker_metrics:
|
||||
if not isinstance(metric, dict):
|
||||
continue
|
||||
mnemonic = worker_mnemonic(metric)
|
||||
counts[mnemonic] += 1
|
||||
actions = worker_actions(metric)
|
||||
if actions is not None:
|
||||
saw_action_counts = True
|
||||
actions_by_mnemonic[mnemonic] += actions
|
||||
|
||||
if not counts:
|
||||
return []
|
||||
|
||||
lines = [" Worker metric entries:"]
|
||||
for mnemonic, count in counts.most_common():
|
||||
suffix = ""
|
||||
if saw_action_counts:
|
||||
suffix = f", actions={actions_by_mnemonic[mnemonic]}"
|
||||
lines.append(f" {mnemonic}: workers={count}{suffix}")
|
||||
return lines
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: summarize_bazel_bep.py <build_event_json_file>")
|
||||
return 0
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
if not path.exists():
|
||||
print(f"Bazel BEP summary: {path} does not exist")
|
||||
return 0
|
||||
|
||||
events = read_events(path)
|
||||
metric_events = collect_metric_events(events)
|
||||
if not metric_events:
|
||||
print("Bazel BEP summary: no buildMetrics event found")
|
||||
return 0
|
||||
|
||||
build_metrics = metric_events[-1]
|
||||
sections = [
|
||||
summarize_timing(build_metrics),
|
||||
summarize_counts(build_metrics),
|
||||
summarize_workers(build_metrics),
|
||||
]
|
||||
lines = [line for section in sections for line in section]
|
||||
|
||||
if not lines:
|
||||
print("Bazel BEP summary: buildMetrics event had no recognized metrics")
|
||||
return 0
|
||||
|
||||
print("Bazel BEP summary:")
|
||||
for line in lines:
|
||||
print(line)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -13,158 +13,15 @@ set -euxo pipefail
|
||||
BUILD_TARGET=$1
|
||||
WORKSPACE=$(pwd)
|
||||
UNITY_PROJECT="$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
UNITY_VERSION=$(grep "m_EditorVersion:" "$UNITY_PROJECT/ProjectSettings/ProjectVersion.txt" | head -1 | sed 's/m_EditorVersion: //')
|
||||
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
|
||||
ADDRESSABLES_PREFIX="addressables/$BUILD_TARGET/$UNITY_MAJOR_MINOR"
|
||||
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET/$UNITY_MAJOR_MINOR"
|
||||
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET"
|
||||
|
||||
# DigitalOcean Spaces configuration (same region as other eagle0 buckets)
|
||||
DO_ENDPOINT="https://sfo3.digitaloceanspaces.com"
|
||||
DO_BUCKET="eagle0-assets"
|
||||
DEFAULT_MAX_UPLOAD_BYTES=$((1024 * 1024 * 1024))
|
||||
DEFAULT_MAX_SINGLE_FILE_BYTES=$((512 * 1024 * 1024))
|
||||
MAX_UPLOAD_BYTES=${ADDRESSABLES_MAX_UPLOAD_BYTES:-$DEFAULT_MAX_UPLOAD_BYTES}
|
||||
MAX_SINGLE_FILE_BYTES=${ADDRESSABLES_MAX_SINGLE_FILE_BYTES:-$DEFAULT_MAX_SINGLE_FILE_BYTES}
|
||||
TOP_UPLOAD_COUNT=${ADDRESSABLES_TOP_UPLOAD_COUNT:-20}
|
||||
|
||||
COMMON_PATHS="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
export PATH="${COMMON_PATHS}:${PATH}"
|
||||
|
||||
format_bytes() {
|
||||
local bytes=$1
|
||||
local unit="B"
|
||||
local value=$bytes
|
||||
|
||||
if [ "$bytes" -ge $((1024 * 1024 * 1024)) ]; then
|
||||
unit="GiB"
|
||||
value=$(awk "BEGIN { printf \"%.2f\", $bytes / 1024 / 1024 / 1024 }")
|
||||
elif [ "$bytes" -ge $((1024 * 1024)) ]; then
|
||||
unit="MiB"
|
||||
value=$(awk "BEGIN { printf \"%.2f\", $bytes / 1024 / 1024 }")
|
||||
elif [ "$bytes" -ge 1024 ]; then
|
||||
unit="KiB"
|
||||
value=$(awk "BEGIN { printf \"%.2f\", $bytes / 1024 }")
|
||||
fi
|
||||
|
||||
echo "$value $unit"
|
||||
}
|
||||
|
||||
file_size() {
|
||||
local path=$1
|
||||
if stat -f%z "$path" >/dev/null 2>&1; then
|
||||
stat -f%z "$path"
|
||||
else
|
||||
stat -c%s "$path"
|
||||
fi
|
||||
}
|
||||
|
||||
append_summary() {
|
||||
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
|
||||
echo "$1" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
}
|
||||
|
||||
budget_enabled() {
|
||||
local value=$1
|
||||
[ "$value" -gt 0 ]
|
||||
}
|
||||
|
||||
summarize_pending_uploads() {
|
||||
local sync_args_file
|
||||
sync_args_file=$(mktemp)
|
||||
local uploads_file
|
||||
uploads_file=$(mktemp)
|
||||
local sorted_uploads_file
|
||||
sorted_uploads_file=$(mktemp)
|
||||
trap 'rm -f "$sync_args_file" "$uploads_file" "$sorted_uploads_file"' RETURN
|
||||
|
||||
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
|
||||
--endpoint-url "$DO_ENDPOINT" \
|
||||
--acl public-read \
|
||||
--size-only \
|
||||
--delete \
|
||||
--dryrun > "$sync_args_file"
|
||||
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*upload:*)
|
||||
local source_path=${line#*upload: }
|
||||
source_path=${source_path% to s3://*}
|
||||
if [ -f "$source_path" ]; then
|
||||
local size
|
||||
size=$(file_size "$source_path")
|
||||
printf "%s\t%s\n" "$size" "$source_path" >> "$uploads_file"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done < "$sync_args_file"
|
||||
|
||||
local upload_count=0
|
||||
local upload_bytes=0
|
||||
if [ -s "$uploads_file" ]; then
|
||||
while IFS=$'\t' read -r size _path; do
|
||||
upload_count=$((upload_count + 1))
|
||||
upload_bytes=$((upload_bytes + size))
|
||||
done < "$uploads_file"
|
||||
fi
|
||||
|
||||
echo "Addressables upload preflight: $upload_count changed files, $(format_bytes "$upload_bytes") to upload."
|
||||
append_summary "## Addressables upload preflight"
|
||||
append_summary ""
|
||||
append_summary "- Target: \`$BUILD_TARGET\`"
|
||||
append_summary "- Changed files: \`$upload_count\`"
|
||||
append_summary "- Changed upload bytes: \`$(format_bytes "$upload_bytes")\`"
|
||||
append_summary "- Upload byte budget: \`$(format_bytes "$MAX_UPLOAD_BYTES")\`"
|
||||
append_summary "- Single-file budget: \`$(format_bytes "$MAX_SINGLE_FILE_BYTES")\`"
|
||||
|
||||
local guardrail_failed=0
|
||||
|
||||
if [ -s "$uploads_file" ]; then
|
||||
echo "Largest pending Addressables uploads:"
|
||||
append_summary ""
|
||||
append_summary "### Largest pending uploads"
|
||||
append_summary ""
|
||||
append_summary "| Size | Path |"
|
||||
append_summary "| ---: | --- |"
|
||||
sort -nr "$uploads_file" > "$sorted_uploads_file"
|
||||
local listed_uploads=0
|
||||
while IFS=$'\t' read -r size path; do
|
||||
if [ "$listed_uploads" -ge "$TOP_UPLOAD_COUNT" ]; then
|
||||
break
|
||||
fi
|
||||
local relative_path=${path#"$SERVER_DATA"/}
|
||||
echo " $(format_bytes "$size") $relative_path"
|
||||
append_summary "| $(format_bytes "$size") | \`$relative_path\` |"
|
||||
listed_uploads=$((listed_uploads + 1))
|
||||
done < "$sorted_uploads_file"
|
||||
|
||||
if budget_enabled "$MAX_SINGLE_FILE_BYTES"; then
|
||||
while IFS=$'\t' read -r size path; do
|
||||
if [ "$size" -gt "$MAX_SINGLE_FILE_BYTES" ]; then
|
||||
local relative_path=${path#"$SERVER_DATA"/}
|
||||
echo "ERROR: Pending Addressables upload '$relative_path' is $(format_bytes "$size"), exceeding single-file budget $(format_bytes "$MAX_SINGLE_FILE_BYTES")."
|
||||
append_summary ""
|
||||
append_summary ":x: \`$relative_path\` exceeds the single-file budget at \`$(format_bytes "$size")\`."
|
||||
guardrail_failed=1
|
||||
fi
|
||||
done < "$uploads_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
if budget_enabled "$MAX_UPLOAD_BYTES" && [ "$upload_bytes" -gt "$MAX_UPLOAD_BYTES" ]; then
|
||||
echo "ERROR: Addressables upload is expected to send $(format_bytes "$upload_bytes"), exceeding budget $(format_bytes "$MAX_UPLOAD_BYTES")."
|
||||
echo "A small asset change may have invalidated an oversized bundle; inspect the largest pending uploads above."
|
||||
append_summary ""
|
||||
append_summary ":x: Changed upload bytes exceed budget: \`$(format_bytes "$upload_bytes")\` > \`$(format_bytes "$MAX_UPLOAD_BYTES")\`."
|
||||
guardrail_failed=1
|
||||
fi
|
||||
|
||||
if [ "$guardrail_failed" -ne 0 ]; then
|
||||
echo "Refusing to upload Addressables. Raise ADDRESSABLES_MAX_UPLOAD_BYTES or ADDRESSABLES_MAX_SINGLE_FILE_BYTES only after confirming the upload is intentional."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
if ! command -v aws >/dev/null 2>&1; then
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
brew install awscli
|
||||
@@ -181,34 +38,19 @@ if [ ! -d "$SERVER_DATA" ]; then
|
||||
fi
|
||||
|
||||
echo "Uploading Addressables bundles from $SERVER_DATA"
|
||||
echo "Target: s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/"
|
||||
echo "Target: s3://$DO_BUCKET/addressables/$BUILD_TARGET/"
|
||||
|
||||
# Configure AWS CLI for DigitalOcean Spaces
|
||||
export AWS_ACCESS_KEY_ID="$ACCESS_KEY_ID"
|
||||
export AWS_SECRET_ACCESS_KEY="$SECRET_KEY"
|
||||
|
||||
summarize_pending_uploads
|
||||
|
||||
# Sync bundles to Spaces. Addressable bundle filenames include content hashes, so a
|
||||
# same-sized existing bundle is already the same content. Use --size-only to avoid
|
||||
# re-uploading every rebuilt bundle just because Unity gave it a fresh local mtime.
|
||||
# Metadata files keep stable names, so sync them again without --size-only below.
|
||||
#
|
||||
# --delete removes files in destination that don't exist in source.
|
||||
# --acl public-read makes files publicly accessible.
|
||||
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
|
||||
# Sync bundles to Spaces
|
||||
# --delete removes files in destination that don't exist in source
|
||||
# --acl public-read makes files publicly accessible
|
||||
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
|
||||
--endpoint-url "$DO_ENDPOINT" \
|
||||
--acl public-read \
|
||||
--size-only \
|
||||
--delete
|
||||
|
||||
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
|
||||
--endpoint-url "$DO_ENDPOINT" \
|
||||
--acl public-read \
|
||||
--exclude "*" \
|
||||
--include "*.bin" \
|
||||
--include "*.hash" \
|
||||
--include "*.json"
|
||||
|
||||
echo "Addressables upload complete"
|
||||
echo "Files available at: https://assets.eagle0.net/$ADDRESSABLES_PREFIX/"
|
||||
echo "Files available at: https://assets.eagle0.net/addressables/$BUILD_TARGET/"
|
||||
|
||||
@@ -5,8 +5,6 @@ lower OCI layer and frequently-changing first-party jars in a small top layer,
|
||||
so most pushes only re-upload the small layer.
|
||||
"""
|
||||
|
||||
load("@rules_java//java/common:java_info.bzl", "JavaInfo")
|
||||
|
||||
def _unique_name(jar):
|
||||
# short_path is unique per jar and stable across commits (it depends only on
|
||||
# the jar's own package/coordinate, not on unrelated targets), so the deps
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar")
|
||||
|
||||
#pkg_tar(
|
||||
# name = "eagle_servers",
|
||||
# strip_prefix = "/src/main/scala/net/eagle0",
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
# Hetzner Setup Guide
|
||||
|
||||
This guide walks through setting up Hetzner Cloud infrastructure for running Shardok on-demand compute.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- All code PRs merged (#4990, #4996, #4998, #5001, #5009)
|
||||
- Access to DigitalOcean Container Registry (for pulling Shardok ARM64 image)
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Hetzner Cloud Account
|
||||
|
||||
1. Go to https://console.hetzner.cloud/
|
||||
2. Sign up and add payment method
|
||||
3. Create a new project (e.g., "eagle0")
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Generate Hetzner API Token
|
||||
|
||||
1. In Hetzner Console → Security → API Tokens
|
||||
2. Click "Generate API Token"
|
||||
3. Give it **Read & Write** permissions
|
||||
4. Copy the token (you'll only see it once)
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Generate Shardok Auth Token
|
||||
|
||||
Generate a 256-bit random token for Eagle-Shardok authentication:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
Save this output - it's the shared secret between Eagle and Shardok.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Store Secrets in GitHub Actions
|
||||
|
||||
Add these secrets in GitHub → Settings → Secrets and variables → Actions:
|
||||
|
||||
| Secret Name | Description |
|
||||
|-------------|-------------|
|
||||
| `HETZNER_API_TOKEN` | From Step 2 - for Hetzner API calls |
|
||||
| `SHARDOK_AUTH_TOKEN` | From Step 3 - shared secret for gRPC auth |
|
||||
|
||||
Note: `DO_REGISTRY_TOKEN` already exists and will be used for Hetzner to pull container images.
|
||||
|
||||
These secrets will be passed to Eagle at runtime via `docker_build.yml`, similar to how `OPENAI_API_KEY` and other secrets are handled.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: DNS Setup (for Let's Encrypt)
|
||||
|
||||
You need a domain pointing to the Shardok instance for TLS certificates.
|
||||
|
||||
### Option A: Floating IP (Recommended)
|
||||
|
||||
1. In Hetzner Console → Networking → Floating IPs
|
||||
2. Create a **Floating IPv6** in **Hillsboro, Oregon (hil)** region
|
||||
- IPv6 costs €1/month vs €3/month for IPv4
|
||||
- Hillsboro has better latency to DigitalOcean SFO than Ashburn
|
||||
- Server-to-server communication works fine with IPv6-only
|
||||
3. Point `shardok.prod.eagle0.net` to this IP via AAAA record
|
||||
4. The ShardokInstanceManager will attach this IP to instances on spin-up
|
||||
|
||||
**Location choice**: Hillsboro, OR (`hil`) is recommended for US West Coast. Same pricing as Ashburn (`ash`).
|
||||
|
||||
### Option B: Dynamic DNS
|
||||
|
||||
Update DNS programmatically when instance spins up. More complex but avoids floating IP cost.
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Upload SSH Key to Hetzner
|
||||
|
||||
For debugging access to instances:
|
||||
|
||||
1. In Hetzner Console → Security → SSH Keys
|
||||
2. Click "Add SSH Key"
|
||||
3. Paste your public key (e.g., `~/.ssh/id_rsa.pub`)
|
||||
4. Give it a name (e.g., "eagle-deploy")
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Wire Security Config into Eagle
|
||||
|
||||
Update Eagle's startup code to use the security config when connecting to remote Shardok:
|
||||
|
||||
```scala
|
||||
val securityConfig = ShardokSecurityConfig(
|
||||
useTls = true,
|
||||
authToken = Some(sys.env("SHARDOK_AUTH_TOKEN"))
|
||||
)
|
||||
|
||||
val channel = ServerSetupHelpers.newChannel(
|
||||
"shardok.prod.eagle0.net",
|
||||
50051,
|
||||
securityConfig
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Manual Instance Spin-up
|
||||
|
||||
Test the Hetzner integration by triggering instance creation:
|
||||
|
||||
```scala
|
||||
val manager = new ShardokInstanceManager(
|
||||
hetznerApiToken = sys.env("HETZNER_API_TOKEN"),
|
||||
// ... other config
|
||||
)
|
||||
|
||||
manager.ensureInstanceRunning()
|
||||
```
|
||||
|
||||
### Verify TLS and Auth
|
||||
|
||||
1. Instance spins up and gets Let's Encrypt certificate
|
||||
2. Eagle connects via TLS
|
||||
3. Auth token is validated on each request
|
||||
|
||||
---
|
||||
|
||||
## Cost Estimate
|
||||
|
||||
| Component | Cost |
|
||||
|-----------|------|
|
||||
| CAX41 (16 ARM cores) | ~$0.04/hour |
|
||||
| Floating IP | ~$4/month |
|
||||
| Typical usage (20 hrs/week) | ~$3.50/month compute |
|
||||
|
||||
**Total: ~$7-8/month** for typical usage.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Instance won't start
|
||||
- Check Hetzner API token has Read & Write permissions
|
||||
- Verify you're using the correct region (`hil` for Hillsboro OR, or `ash` for Ashburn VA)
|
||||
|
||||
### TLS certificate fails
|
||||
- Ensure DNS points to the instance IP before certbot runs
|
||||
- Check port 80 is open for Let's Encrypt HTTP-01 challenge
|
||||
|
||||
### Auth failures
|
||||
- Verify `SHARDOK_AUTH_TOKEN` matches on both Eagle and Shardok
|
||||
- Check the token file is readable by Shardok container
|
||||
|
||||
### Can't pull container image
|
||||
- Ensure `DO_REGISTRY_TOKEN` is passed to cloud-init
|
||||
- Verify the ARM64 image exists: `registry.digitalocean.com/eagle0/shardok-server:arm64-latest`
|
||||
@@ -1,121 +1,23 @@
|
||||
# New Profession Proposals
|
||||
|
||||
This document proposes new hero professions for Eagle0. Each profession should matter in both the Eagle strategic layer
|
||||
and the Shardok tactical layer, and should be earned through a prime stat in the same way existing professions are.
|
||||
This document proposes 5 new hero professions for Eagle0. Each profession has abilities for both the Eagle (strategic) and Shardok (tactical) game layers.
|
||||
|
||||
## Current Professions Reference
|
||||
|
||||
Professions are currently gained when a no-profession hero crosses the profession stat threshold and wins a profession
|
||||
roll. The current stat mapping is:
|
||||
|
||||
| Prime Stat | Professions |
|
||||
|------------|-------------|
|
||||
| **Strength** | Champion |
|
||||
| **Agility** | Engineer, Ranger |
|
||||
| **Wisdom** | Mage |
|
||||
| **Charisma** | Necromancer, Paladin |
|
||||
| **Constitution** | None |
|
||||
|
||||
The current profession capabilities are:
|
||||
|
||||
| Profession | Eagle Ability | Shardok Abilities | Current Niche |
|
||||
|------------|---------------|-------------------|---------------|
|
||||
| **Mage** | Control Weather: start/end blizzards and droughts in the current or neighboring province | Lightning Bolt, Meteor, Freeze Water, Start Fire access/enhancement | Strategic weather control and high-impact elemental battlefield effects |
|
||||
| **Necromancer** | Start Epidemic in the current or neighboring province | Raise Dead, Fear | Attrition pressure, undead creation, morale attack |
|
||||
| **Engineer** | Improve command recommendation and bonus improvement output | Repair, Fortify, Build Bridge, Reduce siege | Infrastructure, battlefield construction, mechanical support |
|
||||
| **Paladin** | Alms priority and increased support from food | Holy Wave | Public support, holy area effect, anti-undead flavor |
|
||||
| **Ranger** | Recon against enemy provinces | Scout, Hide access/enhancement, Brave Water enhancement | Information, stealth, terrain crossing |
|
||||
| **Champion** | Train command recommendation and training bonus | Challenge Duel | Martial excellence, battalion training, direct hero confrontation |
|
||||
|
||||
## Recommendation
|
||||
|
||||
Add **Warden** first.
|
||||
|
||||
Warden is the cleanest next profession because Constitution is the only hero stat that does not currently unlock any
|
||||
profession. A Constitution profession also creates a distinctive tactical identity: not another caster, scout, or damage
|
||||
dealer, but a durable hero who keeps important units alive. That fills a real mechanical gap without forcing a large
|
||||
rewrite of the profession system.
|
||||
|
||||
The first version should be intentionally simple:
|
||||
|
||||
- **Prime stat:** Constitution
|
||||
- **Eagle ability:** Garrison, a defensive province action or passive defense bonus when a Warden is present with enough
|
||||
vigor; or Custody, a prisoner-control ability for captured heroes
|
||||
- **Shardok ability:** Guard, a command that marks an adjacent friendly unit until the Warden's next turn; the first
|
||||
attack against that unit is redirected to the Warden's unit or reduced by a fixed amount
|
||||
- **Battalion suitability:** Optimal with durable melee infantry; suboptimal with fragile archers or stealth-focused
|
||||
units if we want a stricter identity
|
||||
- **Balance direction:** Spend action points and/or vigor to protect another unit, rather than adding free passive
|
||||
prevention every round
|
||||
|
||||
This keeps the profession legible: high-Constitution heroes become the people who can hold formation, anchor a province,
|
||||
and protect fragile specialists.
|
||||
| Profession | Eagle Ability | Shardok Abilities |
|
||||
|------------|---------------|-------------------|
|
||||
| **Mage** | Control Weather | Lightning Bolt, Meteor, Freeze Water, Start Fire (enhanced) |
|
||||
| **Necromancer** | Start Epidemic | Raise Dead, Fear |
|
||||
| **Engineer** | (general) | Repair, Fortify, Build Bridge, Reduce (siege) |
|
||||
| **Paladin** | Alms (prioritized) | Holy Wave |
|
||||
| **Ranger** | Recon | Scout, Hide (enhanced), Brave Water (enhanced) |
|
||||
| **Champion** | (general) | Challenge Duel |
|
||||
|
||||
---
|
||||
|
||||
## Proposed New Professions
|
||||
|
||||
### 1. WARDEN (Defense & Protection Specialist)
|
||||
|
||||
**Fantasy:** The stalwart defender who holds the line and protects allies.
|
||||
|
||||
**Why it fits now:**
|
||||
- Gives Constitution a profession path
|
||||
- Adds a defensive/tank identity that no current profession owns
|
||||
- Creates counterplay to burst damage, duel pressure, and fragile high-value units
|
||||
|
||||
**Eagle Ability: "Garrison"**
|
||||
- A province with a Warden-led unit gets a defense bonus when attacked
|
||||
- Alternative active version: spend vigor to fortify the province until next round
|
||||
- Synergy: works well in border provinces, choke points, and provinces recovering after losses
|
||||
|
||||
**Alternative Eagle Ability: "Custody"**
|
||||
- A Warden can handle captured heroes with extra authority: execute them immediately or move them to a neighboring ruled
|
||||
province before normal prisoner management
|
||||
- Passive custody version: each new round, prisoners in a Warden-guarded province become more likely to join the ruling
|
||||
faction
|
||||
- Prefer implementing this as a small positive addition to the prisoner's existing `factionBiases` entry for the ruling
|
||||
faction, rather than changing the prisoner `roundsInType` multiplier from `+5/round` to `+10/round`
|
||||
- Rationale: changing the multiplier would be retroactive. A prisoner held for ten rounds would immediately receive a
|
||||
large odds jump the moment a Warden arrived. Accumulating a Warden-specific bias only rewards rounds actually spent
|
||||
under Warden custody, and the existing faction-bias decay makes the effect fade naturally if the Warden leaves
|
||||
- Stronger version: if the Warden's side captures enemy heroes during battle but still loses the province, the Warden can
|
||||
evacuate one captured hero to a neighboring ruled province instead of losing custody
|
||||
- This should be limited to one captured hero per battle or require a high-vigor Warden, because keeping prisoners after
|
||||
losing the battle is a major strategic swing
|
||||
- Best flavor: Wardens are not just defenders of walls, but keepers of oaths, chains, and battlefield custody
|
||||
|
||||
**Shardok Ability: "Guard"**
|
||||
- Target adjacent friendly unit is protected until the Warden's next turn
|
||||
- First incoming attack against the protected unit is redirected to the Warden's unit or reduced
|
||||
- Costs action points and cannot target self
|
||||
- Creates a clear positioning puzzle without introducing another long-range damage button
|
||||
|
||||
**Implementation Notes:**
|
||||
- Add `Warden` to the Scala profession enum and common/client profession enums
|
||||
- Map Constitution to `Vector(Profession.Warden)` in `HeroStatGainAction`
|
||||
- Add display names, profession-gained notification copy, tutorial trigger mapping, and headshot bucket support
|
||||
- Add strategic availability and command handling only after deciding whether Warden uses Garrison, Custody, or both
|
||||
- For Custody, prefer extending the existing captured-hero/prisoner-management flow instead of creating a parallel
|
||||
prisoner system
|
||||
- For passive prisoner conversion, update `NewRoundAction` near the existing unaffiliated-hero `roundsInType` and
|
||||
`factionBiases` maintenance. If the province is ruled, contains a ruling-faction Warden, and the unaffiliated hero is
|
||||
a prisoner, add the Warden custody bonus to that prisoner's `factionBiases(rulingFactionId)`.
|
||||
- For Shardok, start with a single-turn guard status before attempting more complex interception chains
|
||||
|
||||
**Current Follow-Up Priorities:**
|
||||
- Teach the Shardok AI when to use the Warden evacuation command. The command is valuable when the Warden's side has
|
||||
captured enemy heroes and can secure them by leaving the battle, especially when the battle outcome is uncertain or
|
||||
trending against that side.
|
||||
- Add the strategic passive custody bonus: each round, prisoners in a province guarded by a ruling-faction Warden should
|
||||
get a small accumulated positive bias toward joining that faction. Prefer storing this as an addition to the existing
|
||||
`factionBiases` entry rather than changing the global `roundsInType` multiplier, so the bonus only reflects rounds
|
||||
actually spent under Warden custody.
|
||||
- Make tactical AI more protective of heroes at risk of capture when a Warden may be present. Warden evacuation makes
|
||||
captured heroes harder to recover, so the AI should treat exposing high-value heroes to capture as more dangerous than
|
||||
before. This matters even when the AI does not have perfect knowledge that a Warden is in the battle.
|
||||
|
||||
### 2. HERALD (Morale & Communication Specialist)
|
||||
### 1. HERALD (Morale & Communication Specialist)
|
||||
|
||||
**Fantasy:** The inspiring leader who rallies troops and carries messages across the battlefield.
|
||||
|
||||
@@ -130,7 +32,7 @@ and protect fragile specialists.
|
||||
|
||||
---
|
||||
|
||||
### 3. ALCHEMIST (Fire & Transformation Specialist)
|
||||
### 2. ALCHEMIST (Fire & Transformation Specialist)
|
||||
|
||||
**Fantasy:** The mad scientist who manipulates the elements through science, not magic.
|
||||
|
||||
@@ -145,6 +47,21 @@ and protect fragile specialists.
|
||||
|
||||
---
|
||||
|
||||
### 3. WARDEN (Defensive Specialist)
|
||||
|
||||
**Fantasy:** The stalwart defender who holds the line and protects allies.
|
||||
|
||||
**Eagle Ability: "Garrison"**
|
||||
- A province with a Warden-led unit gets +1 to defense when attacked
|
||||
- Encourages strategic placement of defensive heroes
|
||||
|
||||
**Shardok Ability: "Intercept"**
|
||||
- Once per turn, when an adjacent friendly unit is attacked, the Warden's unit can take the hit instead
|
||||
- Creates a "bodyguard" mechanic that protects valuable units
|
||||
- Costs action points to maintain readiness
|
||||
|
||||
---
|
||||
|
||||
### 4. INQUISITOR (Anti-Magic & Intelligence)
|
||||
|
||||
**Fantasy:** The witch-hunter who counters supernatural threats and uncovers secrets.
|
||||
@@ -179,174 +96,8 @@ and protect fragile specialists.
|
||||
|
||||
These professions were designed to:
|
||||
|
||||
1. **Fill mechanical gaps**: Warden gives Constitution a profession and adds defensive depth
|
||||
2. **Create counterplay**: Inquisitor vs Mage/Necromancer, Warden vs burst damage, Beastmaster vs beast events
|
||||
3. **Avoid overlap**: Each profession should own a tactical and strategic niche not covered by existing professions
|
||||
4. **Support both layers**: Each ability should be meaningful in Eagle and Shardok
|
||||
5. **Enable interesting decisions**: Guard creates positioning choices, Inspire creates action economy choices, Transmute
|
||||
creates resource tradeoffs
|
||||
|
||||
## Unified Candidate Ranking
|
||||
|
||||
This ranking considers every candidate together, regardless of where the idea came from. The main criteria are mechanical
|
||||
fit, overlap with existing professions, implementation clarity, strategic-layer value, Shardok value, and narrative
|
||||
payoff.
|
||||
|
||||
1. **Warden**: Best immediate fit. It gives Constitution a profession path and creates a distinctive custody/protection
|
||||
identity.
|
||||
2. **Quartermaster**: Best strategic depth after Warden. Logistics touches resources, armies, travel, and attrition
|
||||
without adding another damage specialist.
|
||||
3. **Envoy**: Best diplomacy/prisoner-system extension. Strong narrative payoff and clear Eagle-side value.
|
||||
4. **Surgeon**: Best loss-mitigation role. Makes battle aftermath less binary and gives non-magical healing a place.
|
||||
5. **Herald**: Strong morale and action-economy support. Distinct from Paladin if kept secular and battlefield-command
|
||||
focused.
|
||||
6. **Marshal**: Strong army identity, especially for formation play and troop organization, but needs careful separation
|
||||
from Champion.
|
||||
7. **Inquisitor**: Useful counterplay against Mage and Necromancer, though anti-magic should not become too narrow.
|
||||
8. **Pathfinder**: Good campaign movement role, but should focus on routes and terrain rather than replacing Ranger
|
||||
scouting.
|
||||
9. **Alchemist**: Good resource and terrain manipulation fantasy, but risks crowding Mage and Engineer unless scoped
|
||||
around risky transformations.
|
||||
10. **Spymaster**: Strong flavor and intelligence value, but needs slow strategic effects to avoid replacing Recon.
|
||||
11. **Artificer**: Fun temporary-device fantasy, but overlaps Engineer unless it owns one-shot preparation rather than
|
||||
construction.
|
||||
12. **Beastmaster**: Connects nicely to beast events, but overlaps Ranger unless focused on province events and wild
|
||||
allies.
|
||||
13. **Harbinger**: Excellent flavor, but probably later because fear/morale already touches Necromancer and Paladin
|
||||
space.
|
||||
|
||||
The best near-term sequence is likely **Warden**, then **Quartermaster** or **Envoy**. Avoid adding another pure damage
|
||||
caster until defensive, logistical, diplomatic, morale, and aftermath niches are better represented.
|
||||
|
||||
## Additional Candidate Details
|
||||
|
||||
These ideas come from looking at open mechanical space in Eagle and Shardok: logistics, loyalty, prisoners, province
|
||||
events, diplomacy, battlefield control, and non-damage support.
|
||||
|
||||
### Marshal
|
||||
|
||||
**Fantasy:** The army organizer who turns a pile of units into an actual campaign force.
|
||||
|
||||
**Best stat fit:** Strength or Charisma
|
||||
|
||||
**Eagle Ability: "Muster"**
|
||||
- Reorganize, reinforce, or prepare battalions more efficiently than a normal hero
|
||||
- Could reduce vigor cost for Organize Troops or improve training/armament transfer efficiency
|
||||
|
||||
**Shardok Ability: "Command Formation"**
|
||||
- Adjacent friendly units gain a small defensive or morale bonus while holding formation
|
||||
- Creates a tactical identity around positioning several units together
|
||||
|
||||
**Why it is interesting:** Champion currently owns heroic combat and training, but not army-level coordination. Marshal
|
||||
would be about disciplined formations rather than duels.
|
||||
|
||||
### Quartermaster
|
||||
|
||||
**Fantasy:** The logistics expert who keeps armies fed, paid, armed, and moving.
|
||||
|
||||
**Best stat fit:** Constitution or Wisdom
|
||||
|
||||
**Eagle Ability: "Provision"**
|
||||
- Move food/gold/supplies with less waste or farther than normal
|
||||
- Reduce attrition or readiness loss for armies operating away from strong provinces
|
||||
|
||||
**Shardok Ability: "Resupply"**
|
||||
- Restore limited ammunition, repair light damage, or grant a one-turn readiness buff to a nearby unit
|
||||
|
||||
**Why it is interesting:** The strategic layer already has meaningful resources. A profession that manipulates logistics
|
||||
would create strong choices without simply adding another combat spell.
|
||||
|
||||
### Envoy
|
||||
|
||||
**Fantasy:** The negotiator, hostage-broker, and oath-maker.
|
||||
|
||||
**Best stat fit:** Charisma
|
||||
|
||||
**Eagle Ability: "Parley"**
|
||||
- Improve diplomacy outcomes, ransom terms, prisoner returns, or truce/alliance offer odds
|
||||
- Could reduce the risk of ambassadors being imprisoned or create better return-prisoner rewards
|
||||
|
||||
**Shardok Ability: "Demand Surrender"**
|
||||
- Attempt to force a damaged or isolated enemy unit to flee, with odds based on charisma and battlefield state
|
||||
|
||||
**Why it is interesting:** Eagle has relationships, ransoms, truces, alliances, and prisoner choices. Envoy would make
|
||||
diplomacy feel like a profession rather than only a menu action.
|
||||
|
||||
### Spymaster
|
||||
|
||||
**Fantasy:** The patient handler of informants, rumors, sabotage, and false trails.
|
||||
|
||||
**Best stat fit:** Wisdom or Charisma
|
||||
|
||||
**Eagle Ability: "Infiltrate"**
|
||||
- Plant a delayed intelligence effect in an enemy province, revealing troop movements or weakening support
|
||||
- Could counter or complement Ranger recon without duplicating it
|
||||
|
||||
**Shardok Ability: "Sabotage"**
|
||||
- Before or during battle, reduce one enemy unit's readiness, movement, or first action effectiveness
|
||||
|
||||
**Why it is interesting:** Ranger is field reconnaissance. Spymaster can be slower, political, and province-focused.
|
||||
|
||||
### Surgeon
|
||||
|
||||
**Fantasy:** The healer who saves lives after the dramatic part of the story is over.
|
||||
|
||||
**Best stat fit:** Wisdom or Constitution
|
||||
|
||||
**Eagle Ability: "Triage"**
|
||||
- Reduce hero vigor loss, casualty severity, or prisoner death/execution fallout after battles
|
||||
- Could improve recovery in provinces with many wounded heroes or battered battalions
|
||||
|
||||
**Shardok Ability: "Stabilize"**
|
||||
- Prevent a nearby friendly hero unit from being captured or destroyed once per battle, leaving it routed or exhausted
|
||||
instead
|
||||
|
||||
**Why it is interesting:** Paladin has holy support, but not mundane medical recovery. Surgeon creates a grounded support
|
||||
role that can make losses less binary.
|
||||
|
||||
### Artificer
|
||||
|
||||
**Fantasy:** The maker of rare devices, siege tools, lenses, traps, and battlefield instruments.
|
||||
|
||||
**Best stat fit:** Wisdom or Agility
|
||||
|
||||
**Eagle Ability: "Prototype"**
|
||||
- Invest gold and vigor to create a temporary province or battalion enhancement
|
||||
- Examples: better siege readiness, scouting lenses, defensive traps, or weatherproof stores
|
||||
|
||||
**Shardok Ability: "Deploy Device"**
|
||||
- Place a one-use trap, barricade, signal flare, or field tool on a nearby hex
|
||||
|
||||
**Why it is interesting:** Engineer currently owns building and repair. Artificer can own temporary inventions and
|
||||
one-shot tactical preparation.
|
||||
|
||||
### Harbinger
|
||||
|
||||
**Fantasy:** The terrifying omen-bearer whose arrival changes morale before the first blow lands.
|
||||
|
||||
**Best stat fit:** Charisma or Wisdom
|
||||
|
||||
**Eagle Ability: "Portent"**
|
||||
- Lower enemy support, increase unrest, or amplify the psychological effect of victories and executions
|
||||
- Could be risky: fear-based rule damages diplomacy or loyalty if overused
|
||||
|
||||
**Shardok Ability: "Dread Standard"**
|
||||
- Enemies near the Harbinger suffer morale penalties or worse flee odds
|
||||
|
||||
**Why it is interesting:** Necromancer has supernatural fear, but Harbinger could be political and symbolic rather than
|
||||
undead-focused.
|
||||
|
||||
### Pathfinder
|
||||
|
||||
**Fantasy:** The guide who knows hidden passes, river crossings, and winter roads.
|
||||
|
||||
**Best stat fit:** Agility or Constitution
|
||||
|
||||
**Eagle Ability: "Find Passage"**
|
||||
- Move armies or heroes through difficult terrain, winter, blizzards, or river-heavy borders with lower penalties
|
||||
- Could create one-turn temporary travel links between neighboring provinces under specific conditions
|
||||
|
||||
**Shardok Ability: "Open Route"**
|
||||
- Let a nearby unit ignore one terrain penalty or cross a difficult hex safely this turn
|
||||
|
||||
**Why it is interesting:** Ranger owns stealth and scouting. Pathfinder owns movement and campaign geography.
|
||||
1. **Fill mechanical gaps**: Warden provides defensive depth, Inquisitor counters magic-heavy strategies
|
||||
2. **Create counterplay**: Inquisitor vs Mage/Necromancer, Beastmaster vs Rangers (nature vs nature)
|
||||
3. **Avoid overlap**: Each has a unique niche not covered by existing professions
|
||||
4. **Support both layers**: Each ability is meaningful in its respective game mode
|
||||
5. **Enable interesting decisions**: Intercept creates bodyguard tactics, Inspire creates action economy choices
|
||||
|
||||
@@ -486,7 +486,7 @@ jobs:
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: false
|
||||
|
||||
@@ -517,7 +517,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: []
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: false
|
||||
|
||||
@@ -532,7 +532,7 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/bazel
|
||||
key: bazel-linux-${{ hashFiles('MODULE.bazel', 'MODULE.bazel.lock', 'WORKSPACE') }}
|
||||
key: bazel-linux-${{ hashFiles('MODULE.bazel', 'WORKSPACE') }}
|
||||
|
||||
- name: Build Shardok server
|
||||
run: bazel build -c opt //src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
@@ -555,7 +555,7 @@ jobs:
|
||||
needs: [build-eagle, build-shardok]
|
||||
environment: production
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy to DigitalOcean
|
||||
uses: appleboy/ssh-action@v1.0.0
|
||||
|
||||
+234
-130
@@ -1,163 +1,267 @@
|
||||
# Tutorial Battle System
|
||||
|
||||
This document describes the current opening tutorial battle. It is based on the live implementation in `TutorialGameCreation.scala`, `TutorialBattleController.cpp`, `ShardokGamesManager.cpp`, `ResolveBattleAction.scala`, and the Unity dialogue scripts.
|
||||
|
||||
For the Unity tutorial UI/dialogue architecture, see `TUTORIAL_SYSTEM.md`. For copy and content notes, see `TUTORIAL_CONTENT.md`.
|
||||
This document describes the tutorial battle system for first-time players. The system provides a scripted introductory battle that teaches combat basics while telling a story.
|
||||
|
||||
---
|
||||
|
||||
## Current Flow
|
||||
## Overview
|
||||
|
||||
When a player creates a tutorial game at the default `OpeningBattle` phase:
|
||||
When a new player starts their first game in tutorial mode, they experience:
|
||||
|
||||
1. `TutorialGameCreation.createTutorialGame()` creates a `GameType.Tutorial(TutorialPhase.OpeningBattle)` game starting in `RoundPhase.BattleRequest`.
|
||||
2. Province 14, **Onmaa**, belongs to Sadar Rakon's faction and already has a defending army.
|
||||
3. Bregos Fyar's faction has a hostile attacking army, led by Ikhaan Tarn, already moving from province 31 to Onmaa.
|
||||
4. `RequestBattlesAction` immediately creates the Shardok battle and tags tutorial reinforcement hero IDs `100` and `101`.
|
||||
5. `GamesManager` stores the generated `TutorialBattleConfig` for this game and passes it to Shardok when the battle starts.
|
||||
6. Shardok creates the battle with the configured timed reinforcement events.
|
||||
7. Unity plays the active dialogue scripts from `tutorial_strategic.json` and `tutorial_battle.json`.
|
||||
|
||||
There is also a tutorial phase-start path. Starting after `OpeningBattle` applies precomputed tutorial setup results from `TutorialPhaseResultsLoader`; this is why the Unity post-battle trigger logic handles a "battle was never observed" auto-resolve path.
|
||||
1. **Narrative Intro** - Story screens introducing the scenario
|
||||
2. **Immediate Battle** - Skip strategic map, start directly in combat
|
||||
3. **Scripted Flee** - Enemy flees when player is "on the ropes"
|
||||
4. **Reinforcements** - Allied heroes arrive mid-battle
|
||||
5. **Heroes Join** - Reinforcement heroes join player's faction after battle
|
||||
|
||||
---
|
||||
|
||||
## Battle Setup
|
||||
## Battle Configuration
|
||||
|
||||
### Defender: Sadar Rakon's Faction
|
||||
### Defender (Player - John Ranil)
|
||||
- **Heroes**: John Ranil + 2 random vassals (3 total)
|
||||
- **Units**:
|
||||
- 2x Light Infantry (300 troops each, 60 training/armament)
|
||||
- 1x Longbowmen (200 troops, 60 training/armament)
|
||||
- **Province**: 14 (Onmaa) with 40 support, 500 gold, 3000 food
|
||||
- **Restriction**: Cannot flee (must defend)
|
||||
|
||||
Configured in `src/main/resources/net/eagle0/eagle/tutorial_parameters.json`:
|
||||
### Attacker (Ikhaan Tarn)
|
||||
- **Heroes**: Ikhaan Tarn + 2 sworn brothers (3 total)
|
||||
- **Units**:
|
||||
- 1x Heavy Cavalry (600 troops, 80 training/armament)
|
||||
- 1x Heavy Infantry (500 troops, 80 training/armament)
|
||||
- 1x Longbowmen (300 troops, 80 training/armament)
|
||||
- **Origin Province**: 31
|
||||
|
||||
- **Faction ID:** 3
|
||||
- **Province:** 14, Onmaa
|
||||
- **Starting support:** 23
|
||||
- **Starting resources:** 50 gold, 4000 food
|
||||
- **Heroes:** Sadar Rakon, Old Marek the Learned, Agamemnon
|
||||
- **Battalions:**
|
||||
- Rakon's Loyalists: Light Infantry, 529 size, 80 training, 75 armament
|
||||
- Onmaa Defenders: Light Infantry, 311 size, 80 training, 60 armament
|
||||
- Hunters of the Steppe: Longbowmen, 478 size, 80 training, 60 armament
|
||||
### Flee Trigger
|
||||
Attacker flees when ANY of these conditions are met:
|
||||
- Player loses 1 unit
|
||||
- 5 rounds have passed
|
||||
|
||||
### Attacker: Bregos Fyar's Faction
|
||||
|
||||
The opening attacking army is configured under Bregos Fyar's `attackingArmies`:
|
||||
|
||||
- **Faction ID:** 2
|
||||
- **Origin province:** 31
|
||||
- **Destination province:** 14, Onmaa
|
||||
- **Heroes:** Ikhaan Tarn, Tall Edgtheow, Waylaid Julius, Luke the Prank-tricker
|
||||
- **Battalions:**
|
||||
- Doomriders: Heavy Cavalry, 592 size, 80 training, 80 armament
|
||||
- The Shardok's Guard: Heavy Infantry, 507 size, 80 training, 80 armament
|
||||
- Bowmen of Nikemi: Longbowmen, 291 size, 80 training, 80 armament
|
||||
- Swift Sabres: Light Cavalry, 450 size, 80 training, 80 armament
|
||||
|
||||
Tarn's unit is made visible to the defender from the start through `TutorialBattleConfig.initial_visibilities`.
|
||||
### Reinforcements
|
||||
When Tarn flees, these heroes arrive as player reinforcements:
|
||||
- Elena Fyar
|
||||
- Hedrick
|
||||
- The Boulder
|
||||
|
||||
---
|
||||
|
||||
## Reinforcements
|
||||
## Implementation Architecture
|
||||
|
||||
The current tutorial config has two timed reinforcement events. It does not currently configure a scripted Tarn flee event.
|
||||
### Phase 1: Narrative Intro System
|
||||
|
||||
| Event ID | Trigger | Hero ID | Hero | Battalion |
|
||||
|----------|---------|---------|------|-----------|
|
||||
| `elena_reinforcement_round_5` | End of round 5 or later | 101 | Elena Fyar | Fyar's Vanguard, Heavy Infantry, 535 size, 75 training, 75 armament |
|
||||
| `ranil_reinforcement_round_7` | End of round 7 or later | 100 | John Ranil | Ranil's Riders, Heavy Cavalry, 458 size, 80 training, 80 armament |
|
||||
**Proto Changes:**
|
||||
- `game_state_view.proto`: Added `TutorialNarrativeScreen` message and `pending_narrative_screens` field
|
||||
- `game_parameters.proto`: Added `TutorialNarrativeScreen` and `tutorial_narrative_screens` field
|
||||
|
||||
Shardok creates reinforcement units at battle creation time with `PENDING_REINFORCEMENT` status. `TutorialBattleController` activates them when their event triggers and returns a `TUTORIAL_REINFORCEMENTS_ARRIVED` action result. Unity maps that action to profession-specific dialogue triggers:
|
||||
**Unity Client:**
|
||||
- `NarrativeScreenController.cs`: Modal screen with title, body text, optional image
|
||||
- `EagleGameController.cs`: Checks for pending narrative screens on game start
|
||||
|
||||
- `tutorial_reinforcement_paladin` for Elena Fyar
|
||||
- `tutorial_reinforcement_engineer` for John Ranil
|
||||
**Narrative Content** (defined in `tutorial_game_parameters.json`):
|
||||
```
|
||||
Screen 1: "The Engineer of Onmaa" - Player backstory
|
||||
Screen 2: "The Traitor Arrives" - Tarn's attack
|
||||
Screen 3: "Defend Your Home" - Call to action
|
||||
```
|
||||
|
||||
The reinforcement action includes an `attacker_starting_position_index`; Shardok resolves that to currently open map positions and starts a reinforcement placement phase.
|
||||
### Phase 2: Auto-Start Battle
|
||||
|
||||
**Configuration:**
|
||||
- `tutorial_game_parameters.json`: Contains `tutorialBattle` config with attacker, target, flee conditions
|
||||
- `game_parameters.proto`: `TutorialBattleConfig` message
|
||||
|
||||
**Game Creation:**
|
||||
- `NewGameCreation.scala`: `setupTutorialBattle()` method creates:
|
||||
- `HostileArmyGroup` with `Attacking` status (bypasses decision phase)
|
||||
- `Army` for defender
|
||||
- Both added to target province
|
||||
|
||||
**Battle Creation:**
|
||||
- When round advances to `BattleRequest` phase, `RequestBattlesAction` automatically creates the `ShardokBattle`
|
||||
|
||||
### Phase 3: Scripted Flee Mechanism
|
||||
|
||||
**C++ Component:**
|
||||
- `TutorialBattleController.hpp/cpp`: Controller for scripted battle logic
|
||||
|
||||
**Key Methods:**
|
||||
```cpp
|
||||
// Check all events and execute triggered actions (called at end of each round)
|
||||
std::vector<ActionResult> CheckAndExecuteEvents(
|
||||
const GameStateW& state,
|
||||
const SettingsGetter& settings,
|
||||
const std::shared_ptr<RandomGenerator>& randomGenerator);
|
||||
```
|
||||
|
||||
**Integration:**
|
||||
- `ShardokEngine.cpp`: Calls `CheckAndExecuteEvents()` at end of each round in `HandlePlayerTurnEnd()`
|
||||
- Events fire in config order, each event fires at most once
|
||||
|
||||
### Phase 4: Mid-Battle Reinforcements
|
||||
|
||||
**Action Types** (in `action_type.proto`):
|
||||
- `TUTORIAL_ENEMY_FLED = 78`
|
||||
- `TUTORIAL_REINFORCEMENTS_ARRIVED = 79`
|
||||
|
||||
**Implementation (TODO):**
|
||||
- When scripted flee triggers, create reinforcement units in defender's reserves
|
||||
- Units have `UnitStatus_RESERVE_UNIT` status
|
||||
- Reinforcements enter battle on subsequent turns
|
||||
|
||||
### Phase 5: Tutorial Popups
|
||||
|
||||
**Content Definitions** (in `TutorialContentDefinitions.cs`):
|
||||
- `tutorial_battle_started`: "Defend your province!"
|
||||
- `tutorial_enemy_fled`: "The enemy retreats!"
|
||||
- `tutorial_reinforcements_arrived`: "Allies have arrived!"
|
||||
- `tutorial_battle_victory`: Victory celebration
|
||||
|
||||
**Trigger Integration:**
|
||||
- `TutorialTriggerRegistry.cs`: Handles tutorial action types from `ActionResultView`
|
||||
- `ShardokGameController.cs`: Detects tutorial events and triggers popups
|
||||
|
||||
### Phase 6: Post-Battle Hero Joining
|
||||
|
||||
**Implementation (TODO):**
|
||||
- `ResolveBattleAction.scala`: Check if battle was tutorial and defender won
|
||||
- Create `TutorialHeroJoined` action results adding reinforcement heroes to player faction
|
||||
- Set appropriate loyalty, vigor, location for joined heroes
|
||||
|
||||
### Phase 7: Post-Battle Strategic Dialogue
|
||||
|
||||
After the battle ends and the player returns to the strategic map, narrative dialogues guide them through the next steps.
|
||||
|
||||
**Dialogue 1: Battle Aftermath** (trigger: `tutorial_battle_ended`)
|
||||
- Old Marek reflects on the close call
|
||||
- Tarn has vanished — rumors of sorcery or escape, no one knows
|
||||
- Captured lieutenants: they followed orders, not to blame for Tarn's madness
|
||||
- Player should try to recruit them or hold them
|
||||
- No instruction text needed — Handle Captured Heroes UI is self-explanatory
|
||||
|
||||
**Dialogue 2: Rebuild Support** (trigger: `tutorial_rebuild_support`)
|
||||
- Fires when available commands no longer include HandleCapturedHeroCommand
|
||||
- Marek explains the province needs support rebuilt after the battle
|
||||
- Engineers (John Ranil) can **Improve** the province — build infrastructure, develop economy
|
||||
- Paladins (Elena Fyar) can **Give Alms** — distribute food to win hearts
|
||||
- Instructions highlight **Improve** and **Give Alms** buttons
|
||||
- Goal: get Support to **40** before January for tax revenue
|
||||
|
||||
**Implementation:**
|
||||
- Dialogues defined in `tutorial_strategic.json`
|
||||
- `tutorial_battle_ended`: fire when tutorial battle is removed from `RunningShardokGameModels`
|
||||
- `tutorial_rebuild_support`: fire when available commands no longer include `HandleCapturedHeroCommand`
|
||||
- Register `ImproveButton` and `AlmsButton` as highlight targets in `TutorialTargetRegistry`
|
||||
|
||||
See `TUTORIAL_CONTENT.md` for full dialogue text.
|
||||
|
||||
---
|
||||
|
||||
## Scripted Event Controller
|
||||
|
||||
`TutorialBattleController` is a generic event-driven controller, even though the current tutorial battle only uses timed reinforcements.
|
||||
|
||||
Supported trigger types:
|
||||
|
||||
- `after_round`
|
||||
- `units_lost`
|
||||
- `damage_taken`
|
||||
- `unit_killed`
|
||||
|
||||
Supported action types:
|
||||
|
||||
- `flee`
|
||||
- `reinforcements`
|
||||
|
||||
Events are checked at the end of each round, evaluated in config order, and each `event_id` fires at most once.
|
||||
|
||||
---
|
||||
|
||||
## Battle Resolution
|
||||
|
||||
`RequestBattlesAction` marks tutorial opening battles with `reinforcementHeroIds = Set(100, 101)`. `ResolveBattleAction` uses that metadata so reinforcement heroes are accepted when battle results return, even though they were not part of the original defending army.
|
||||
|
||||
After the opening battle resolves, `ResolveBattleAction` applies `TutorialTarnDisappearsAction` for `GameType.Tutorial(TutorialPhase.OpeningBattle)`. Tarn is removed from captured/unaffiliated province state and from his faction's leaders list, matching the post-battle dialogue that he has vanished.
|
||||
|
||||
`TutorialBattleAutoResolve` provides a synthetic defender victory for tutorial setup paths that skip past the opening battle. In that synthetic result:
|
||||
|
||||
- The defender wins.
|
||||
- Reinforcement heroes 100 and 101 survive.
|
||||
- Tarn is marked outlawed/escaped.
|
||||
- Other attacker heroes are captured.
|
||||
- Defender battalions take about 30% casualties.
|
||||
- Attacker battalions are destroyed.
|
||||
|
||||
---
|
||||
|
||||
## Unity Dialogue Hooks
|
||||
|
||||
Strategic tutorial dialogue:
|
||||
|
||||
- `game_started`: opening Onmaa monologue, ending with `FightButton` highlighted.
|
||||
- `tutorial_battle_ended`: post-battle aftermath.
|
||||
- `tutorial_rebuild_support`: support rebuilding guidance after captured-hero handling is done.
|
||||
|
||||
Battle tutorial dialogue:
|
||||
|
||||
- `shardok_placement_started`: placement and unit overview.
|
||||
- `shardok_battle_running`: first player turn guidance.
|
||||
- Ability/terrain triggers such as `archery_available`, `melee_available`, `ability_charge_available`, `start_fire_available`, `thunderstorm`, `duel_available`, `hide_available`, and `engineer_near_enemy`.
|
||||
- Reinforcement triggers `tutorial_reinforcement_paladin` and `tutorial_reinforcement_engineer`.
|
||||
- Capture triggers `enemy_hero_captured` and `friendly_hero_captured`.
|
||||
- `shardok_battle_reset`: replay dialogue after a battle reset.
|
||||
|
||||
---
|
||||
|
||||
## Files To Check First
|
||||
## File Summary
|
||||
|
||||
### New Files
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/main/resources/net/eagle0/eagle/tutorial_parameters.json` | Tutorial map, factions, starting provinces, armies |
|
||||
| `src/main/scala/net/eagle0/eagle/service/new_game_creation/TutorialGameCreation.scala` | Opening battle setup and `TutorialBattleConfig` creation |
|
||||
| `src/main/protobuf/net/eagle0/common/tutorial_battle_config.proto` | Scripted event config schema |
|
||||
| `src/main/cpp/net/eagle0/shardok/server/ShardokGamesManager.cpp` | Adds pending reinforcement units and applies initial visibility |
|
||||
| `src/main/cpp/net/eagle0/shardok/library/tutorial/TutorialBattleController.cpp` | Evaluates scripted events and creates Shardok action results |
|
||||
| `src/main/scala/net/eagle0/eagle/library/actions/impl/action/RequestBattlesAction.scala` | Creates the opening battle and tags reinforcement hero IDs |
|
||||
| `src/main/scala/net/eagle0/eagle/library/actions/impl/action/ResolveBattleAction.scala` | Resolves reinforcements and applies Tarn disappearance |
|
||||
| `src/main/scala/net/eagle0/eagle/service/tutorial/TutorialBattleAutoResolve.scala` | Synthetic result for phase-start paths after the opening battle |
|
||||
| `src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Tutorial/Triggers/TutorialTriggerRegistry.cs` | Converts strategic and tactical events into dialogue triggers |
|
||||
| `src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Resources/Dialogues/tutorial_battle.json` | Active battle dialogue content |
|
||||
| `TutorialBattleController.hpp/cpp` | C++ scripted flee and reinforcement logic |
|
||||
| `NarrativeScreenController.cs` | Unity narrative screen display |
|
||||
|
||||
### Modified Files
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `tutorial_game_parameters.json` | Battle configuration and narrative content |
|
||||
| `game_state_view.proto` | Narrative screens field |
|
||||
| `game_parameters.proto` | Tutorial battle config |
|
||||
| `player_info.proto` | Tutorial battle flags for Shardok |
|
||||
| `action_type.proto` | Tutorial action types |
|
||||
| `NewGameCreation.scala` | Auto-start battle setup |
|
||||
| `EagleGameController.cs` | Narrative display integration |
|
||||
| `TutorialContentDefinitions.cs` | Battle tutorial content |
|
||||
| `TutorialTriggerRegistry.cs` | Tutorial event triggers |
|
||||
|
||||
---
|
||||
|
||||
## Remaining Implementation
|
||||
|
||||
### Disable Player Flee
|
||||
In `FleeCommandFactory.cpp`:
|
||||
- Check if tutorial mode is enabled
|
||||
- If defender, don't offer flee commands
|
||||
|
||||
### Reinforcement Units
|
||||
In `TutorialBattleController::ExecuteScriptedFlee()`:
|
||||
- Create `Unit` objects for Elena Fyar, Hedrick, The Boulder
|
||||
- Add to defender's reserve units
|
||||
- Generate `TUTORIAL_REINFORCEMENTS_ARRIVED` action
|
||||
|
||||
### Post-Battle Hero Joining
|
||||
In Eagle's battle resolution:
|
||||
- Detect tutorial battle completion
|
||||
- Create heroes in player's faction with proper stats
|
||||
- Generate notification action results
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Tutorial game starts in BattleRequest and creates an Onmaa battle immediately.
|
||||
- [ ] Opening strategic dialogue highlights the Fight button.
|
||||
- [ ] Tarn's unit is visible to the defender from battle start.
|
||||
- [ ] Placement dialogue fires during setup.
|
||||
- [ ] First-turn battle-running dialogue fires on the player's first active turn.
|
||||
- [ ] Elena Fyar arrives from the configured round-5 event.
|
||||
- [ ] John Ranil arrives from the configured round-7 event.
|
||||
- [ ] Reinforcement arrival dialogues fire once per hero.
|
||||
- [ ] Battle reset clears battle dialogue completion for replay.
|
||||
- [ ] Post-battle aftermath fires after the battle leaves `RunningShardokGameModels`.
|
||||
- [ ] Rebuild-support dialogue waits until captured-hero handling and visible notifications are done.
|
||||
- [ ] Starting a tutorial at a later phase uses precomputed setup and still reaches coherent strategic dialogue state.
|
||||
- [ ] New user sees narrative screens before battle
|
||||
- [ ] Battle starts immediately after narratives (no strategic map)
|
||||
- [ ] Player has correct units (3 heroes, 3 battalions)
|
||||
- [ ] Attacker has correct units (3 heroes, 3 battalions)
|
||||
- [ ] Player cannot flee
|
||||
- [ ] Tarn flees after player loses 1 unit
|
||||
- [ ] Tarn flees after 5 rounds (if no units lost)
|
||||
- [ ] Reinforcements appear when Tarn flees
|
||||
- [ ] Tutorial popups appear at correct moments
|
||||
- [ ] Reinforcement heroes join faction after battle victory
|
||||
|
||||
---
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### tutorial_game_parameters.json
|
||||
```json
|
||||
{
|
||||
"tutorialBattle": {
|
||||
"attackerFactionHead": "Ikhaan Tarn",
|
||||
"targetProvinceId": 14,
|
||||
"fleeAfterDefenderUnitsLost": 1,
|
||||
"fleeAfterRounds": 5,
|
||||
"reinforcements": ["Elena Fyar", "Hedrick", "The Boulder"]
|
||||
},
|
||||
"tutorialNarrativeScreens": [
|
||||
{ "title": "...", "bodyText": "...", "imagePath": "" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### TutorialBattleConfig Proto (Shardok)
|
||||
```protobuf
|
||||
message TutorialBattleConfig {
|
||||
bool enabled = 1;
|
||||
repeated TutorialEvent events = 2;
|
||||
repeated UnitInitialSize initial_sizes = 3; // For damage tracking
|
||||
}
|
||||
|
||||
message TutorialEvent {
|
||||
string event_id = 1;
|
||||
TutorialTrigger trigger = 2;
|
||||
TutorialAction action = 3;
|
||||
}
|
||||
|
||||
message TutorialTrigger {
|
||||
oneof trigger_type {
|
||||
RoundTrigger after_round = 1;
|
||||
UnitsLostTrigger units_lost = 2;
|
||||
DamageTakenTrigger damage_taken = 3;
|
||||
UnitKilledTrigger unit_killed = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message TutorialAction {
|
||||
oneof action_type {
|
||||
FleeAction flee = 1;
|
||||
ReinforcementsAction reinforcements = 2; // Uses CommonUnit
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
+13
-13
@@ -1,8 +1,6 @@
|
||||
# Tutorial Content Guide
|
||||
|
||||
This document describes tutorial content. The active tutorial experience is driven by the narrative dialogue JSON files under `src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Resources/Dialogues/`.
|
||||
|
||||
The older step-based modal/overlay content in `TutorialContentDefinitions.cs` is currently dormant: `RegisterAll()` returns before registering any sequences. Keep that in mind when editing this file; changing `TutorialContentDefinitions.cs` will not affect the live tutorial until the early return is removed and the overlap with dialogue triggers is audited.
|
||||
This document defines all tutorial content. Edit this to refine the text, then update `TutorialContentDefinitions.cs` to match.
|
||||
|
||||
---
|
||||
|
||||
@@ -183,16 +181,17 @@ These fire after the first tutorial battle ends and the player returns to the st
|
||||
>
|
||||
> Any of our heroes can develop the land or give alms to the people — nothing wins hearts faster than a full belly. Engineers like John Ranil are especially effective at improving a province, and paladins like Elena Fyar are the best at winning hearts. Either way, we need the people's support before the tax collectors come round in January.
|
||||
|
||||
**Instructions:** John Ranil's step highlights `ImproveProvinceButton` and explains **Improve**. Elena Fyar's step highlights `GiveAlmsButton` and explains **Give Alms**. You must have at least **40** support in the province by January in order to collect gold and food in taxes.
|
||||
**Instructions:** Use **Improve** and **Give Alms** to raise Support. You must have at least **40** support in the province by January in order to collect gold and food in taxes.
|
||||
|
||||
**Highlight targets:** `ImproveProvinceButton`, `GiveAlmsButton`
|
||||
**Highlight targets:** `SupportField`
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
- Both dialogues are defined in `tutorial_strategic.json`.
|
||||
- `tutorial_battle_ended` fires from `TutorialTriggerRegistry.CheckTutorialBattleEnded()` when the opening battle is gone and strategic commands are available. It also handles the server-side auto-resolve path where the Unity client never observed a running Shardok model.
|
||||
- `tutorial_rebuild_support` fires from `TutorialTriggerRegistry.CheckTutorialRebuildSupport()` when available commands no longer include `HandleCapturedHeroCommand` and the notification panel has no visible info.
|
||||
- `ImproveProvinceButton` and `GiveAlmsButton` are runtime command-button targets registered by `CommandButtonPanelController`.
|
||||
- Both dialogues go in `tutorial_strategic.json`
|
||||
- Need new trigger events: `tutorial_battle_ended` and `tutorial_rebuild_support`
|
||||
- `tutorial_battle_ended` should fire when the tutorial battle is removed from `RunningShardokGameModels` in `EagleGameModel.ApplyGameStateViewDiff`
|
||||
- `tutorial_rebuild_support` should fire when available commands no longer include `HandleCapturedHeroCommand` (i.e. the captured heroes phase is done and the player has regular commands available)
|
||||
- The "rebuild" dialogue should highlight the Improve and Give Alms buttons in the command panel (requires registering them as highlight targets)
|
||||
|
||||
---
|
||||
|
||||
@@ -211,10 +210,11 @@ These fire after the first tutorial battle ends and the player returns to the st
|
||||
## Adding New Tutorials
|
||||
|
||||
1. Add entry to this document
|
||||
2. For active tutorial content, add or edit a script in `Assets/Resources/Dialogues/tutorial_strategic.json` or `tutorial_battle.json`.
|
||||
3. Ensure the trigger event exists in `TutorialTriggerRegistry.cs` or is fired directly by the relevant controller.
|
||||
4. If reviving the dormant step-UI system, remove the early return in `TutorialContentDefinitions.RegisterAll()` only after auditing duplicate trigger coverage with the dialogue JSON.
|
||||
5. Test the flow.
|
||||
2. Update `TutorialContentDefinitions.cs`:
|
||||
- For onboarding: add to `CreateOnboardingSequence()`
|
||||
- For contextual: add to `RegisterStrategicTutorials()` or `RegisterTacticalTutorials()`
|
||||
3. Ensure trigger event exists in `TutorialTriggerRegistry.cs`
|
||||
4. Test the flow
|
||||
|
||||
---
|
||||
|
||||
|
||||
+11
-16
@@ -3,7 +3,7 @@
|
||||
This document describes how the Unity client's tutorial system is wired together: the classes involved, the trigger catalog, the step lifecycle, and the expected first-session flow. It complements two existing docs:
|
||||
|
||||
- **`TUTORIAL_CONTENT.md`** — human-facing copy for tutorial steps and dialogues
|
||||
- **`TUTORIAL_BATTLE_SYSTEM.md`** — the scripted opening battle at Onmaa
|
||||
- **`TUTORIAL_BATTLE_SYSTEM.md`** — the scripted first-battle scenario (Tarn vs. John Ranil)
|
||||
|
||||
It also overlaps slightly with the in-tree `Assets/Tutorial/TUTORIAL_PLAN.md`, which is an older implementation plan.
|
||||
|
||||
@@ -20,13 +20,12 @@ There are **two parallel subsystems** that share the same trigger plumbing:
|
||||
| Step-based tutorial UI (modals, overlays, hints) | **Dormant** | `Tutorial/TutorialManager.cs`, `Tutorial/UI/*`, `Tutorial/Content/*` |
|
||||
| Narrative dialogue system | **Live** | `Tutorial/Dialogue/*`, `Resources/Dialogues/*.json` |
|
||||
|
||||
`TutorialContentDefinitions.RegisterAll()` returns early with the comment *"Old tutorial content suppressed — replaced by narrative dialogue system."* As a result:
|
||||
`TutorialContentDefinitions.RegisterAll()` returns early at line 17 with the comment *"Old tutorial content suppressed — replaced by narrative dialogue system."* As a result:
|
||||
|
||||
- `OnboardingSequence` is never assigned, so `StartOnboarding()` no-ops at the "No onboarding sequence assigned" branch.
|
||||
- No contextual tutorial sequences are registered with the trigger registry.
|
||||
- The trigger registry still fires events normally, but nothing on the step-UI side is listening.
|
||||
- `DialogueManager` consumes those same events and matches them against scripts in `Resources/Dialogues/` (`tutorial_strategic.json`, `tutorial_battle.json`).
|
||||
- Dialogue completion is persisted per game through `TutorialDialogueProgressStore`, so completed scripts do not replay after a reconnect or scene reload for the same game.
|
||||
|
||||
**In practice today, the entire active tutorial experience runs through the dialogue system.** The step-UI machinery is preserved for future use — content definitions still exist below the early return in `TutorialContentDefinitions.cs` for reference.
|
||||
|
||||
@@ -38,7 +37,6 @@ There are **two parallel subsystems** that share the same trigger plumbing:
|
||||
- **`Tutorial/TutorialManager.cs`** — Singleton. Owns `OnboardingSequence`, the active step queue, the trigger registry, and the dialogue manager handle. Initialized by `EagleGameController.SetUpGame()` and `ShardokGameController.SetUpGame()`.
|
||||
- **`Tutorial/TutorialState.cs`** — PlayerPrefs persistence. Tracks `OnboardingCompleted`, `OnboardingStepReached`, completed sequence IDs (HashSet for O(1) lookup), dismissed hints, and a global "tutorials disabled" preference.
|
||||
- **`Tutorial/TutorialTargetRegistry.cs`** — Maps string IDs to `RectTransform`s for highlighting. Static targets are Inspector-assigned (`ProvinceInfoPanel`, `SupportField`, `CommitButton`, etc.); dynamic targets register at runtime (e.g., hero rows from `HeroesAndBattalionsPanelController`).
|
||||
- **`Tutorial/TutorialDialogueCoordinator.cs`** — Recomputes durable dialogue triggers from the current strategic model and sends them directly to `DialogueManager`. This covers reconnects, phase-start tutorial games, and strategic beats that should be recoverable from state.
|
||||
|
||||
### Triggers
|
||||
- **`Tutorial/Triggers/TutorialTriggerRegistry.cs`** (~1100 lines) — Routes game events to both the step UI (when sequences are registered) and `DialogueManager`. Maintains one-shot session flags so the same first-encounter trigger doesn't fire twice.
|
||||
@@ -96,14 +94,14 @@ Panels can be anchored via `TutorialPanelAnchor` (`Center` / `Left` / `Right` /
|
||||
|
||||
## Trigger Catalog
|
||||
|
||||
Most event-style triggers are raised via `TutorialTriggerRegistry`. Durable strategic dialogue beats are also recomputed by `TutorialDialogueCoordinator` on each model update and sent directly to `DialogueManager.TriggerDialogue()`. Both paths match against the dialogue JSON; if step-UI sequences are re-registered, only the registry path will route to them. File:line citations are for the registry unless noted; line numbers may drift.
|
||||
All triggers are raised via `TutorialTriggerRegistry`. Today these flow to `DialogueManager.TriggerDialogue()` and are matched against the dialogue JSON; if step-UI sequences are re-registered, they will also route there. File:line citations are for the registry unless noted; line numbers may drift.
|
||||
|
||||
### Bootstrap / first-session
|
||||
| Trigger | Fires from | When |
|
||||
|---------|-----------|------|
|
||||
| `game_started` | `TutorialDialogueCoordinator.EligibleTriggers()` | Any tutorial strategic model update; dialogue progress keeps it from replaying |
|
||||
| `game_started` | `EagleGameController.SetUpGame()` | First time entering a tutorial game |
|
||||
| `first_battle_available` | `OnModelUpdated()` ~L204 | A `RunningShardokGameModel` first appears |
|
||||
| `battle_entered` | `OnBattleEntered()` ~L793 | User clicks **Fight!** |
|
||||
| `battle_entered` | `OnBattleEntered()` ~L793 | User clicks **Battle!** |
|
||||
| `tutorial_battle_ended` | `CheckTutorialBattleEnded()` ~L594 | Tutorial battle removed from running models |
|
||||
| `tutorial_rebuild_support` | `CheckTutorialRebuildSupport()` ~L616 | Captured-heroes phase done |
|
||||
| `tutorial_taxes_collected` | `CheckTaxesCollected()` ~L680 | New Year action with positive tax delta |
|
||||
@@ -137,7 +135,7 @@ Most event-style triggers are raised via `TutorialTriggerRegistry`. Durable stra
|
||||
| `hero_stat_gained` | ~L281 | `HeroStatGained` action result |
|
||||
| `hero_profession_gained` | ~L284 | `ProfessionGained` action result |
|
||||
| `tutorial_faction_appears` | ~L287 | `TutorialFactionAppears` action — the Fracture Covenant landing |
|
||||
| `tutorial_hero_faction_appears` | ~L290 | `TutorialHeroFactionAppears` action; has a four-step strategic dialogue today |
|
||||
| `tutorial_hero_faction_appears` | ~L290 | `TutorialHeroFactionAppears` action (no dialogue script today) |
|
||||
| `tutorial_hero_departed` | ~L299 | First `HeroesDeparted` action against another player (King's hero abandons service) |
|
||||
| `tutorial_hero_departed_again` | ~L302 | Second `HeroesDeparted` in a *different* month than the first |
|
||||
|
||||
@@ -222,8 +220,8 @@ Script shape (see `tutorial_strategic.json`):
|
||||
"speakerName": "Old Marek the Learned",
|
||||
"speakerImagePath": "fixed/old_marek_the_learned.png",
|
||||
"dialogueText": "...",
|
||||
"instructionText": "Click <b>Fight!</b> to enter tactical combat.",
|
||||
"highlightTarget": "FightButton",
|
||||
"instructionText": "Click Battle! to enter tactical combat.",
|
||||
"highlightTarget": "GoToBattleButton",
|
||||
"persistHighlight": true,
|
||||
"highlightProvince": "Onmaa",
|
||||
"completionEvent": null
|
||||
@@ -249,12 +247,10 @@ Dialogue panel position defaults sensibly per scene (`top` during combat) and ca
|
||||
|
||||
Reset paths:
|
||||
- `TutorialState.Reset()` clears everything.
|
||||
- Entering a tutorial game (`GameType.Tutorial`) resets `TutorialState`; dialogue replay is controlled separately by the per-game dialogue progress key.
|
||||
- Entering a tutorial game (`GameType.Tutorial`) resets so it can be replayed.
|
||||
- Settings → "Reset Tutorials" calls `TutorialManager.ResetAllProgress()`, which also calls `DialogueManager.ResetCompletedScripts()`.
|
||||
|
||||
`TutorialDialogueProgressStore` separately stores completed dialogue script IDs per game under PlayerPrefs keys named `Eagle0_TutorialDialogueProgress_<gameId>`.
|
||||
|
||||
`DialogueManager` keeps the active game's completed script IDs in memory and saves them through `TutorialDialogueProgressStore`. `ResetCompletedScripts()` clears the current game's dialogue progress; `ClearCurrentGameProgress()` deletes the current game's persisted dialogue key.
|
||||
`DialogueManager` tracks completed scripts in memory only — they reset whenever `TutorialState` does.
|
||||
|
||||
---
|
||||
|
||||
@@ -280,7 +276,7 @@ You play **Sadar Rakon**, formerly Ikhaan Tarn's most trusted lieutenant. Tarn's
|
||||
|
||||
| # | Trigger | What happens |
|
||||
|---|---------|--------------|
|
||||
| 1 | `game_started` | **Opening monologue** (3 steps): Sadar's backstory, the Reclamation, last stand at Onmaa. Ends highlighting the **Fight!** button (`FightButton`, `persistHighlight`) |
|
||||
| 1 | `game_started` | **Opening monologue** (3 steps): Sadar's backstory, the Reclamation, last stand at Onmaa. Ends highlighting the **Battle!** button (`persistHighlight`) |
|
||||
| 2 | `tutorial_battle_ended` | **Aftermath**: Tarn has *vanished*; you've captured his lieutenants. Recruit them or hold them |
|
||||
| 3 | `tutorial_rebuild_support` | **Rebuild Onmaa** (3 steps): Marek frames it; **John Ranil** introduces *Improve* (engineer bonus); **Elena Fyar** introduces *Give Alms* (paladin bonus). Goal: 40 support by January |
|
||||
| 4 | `tutorial_loyalty_warning` | November-ish, low-loyalty hero may leave at year end → use *Give Gold* / *Feast* |
|
||||
@@ -292,7 +288,6 @@ You play **Sadar Rakon**, formerly Ikhaan Tarn's most trusted lieutenant. Tarn's
|
||||
| 10 | `tutorial_faction_appears` | **The twist**: Fracture Covenant lands at Ingia and Soria. *The Eagle* commands them. Tarn may be with them |
|
||||
| 11 | `tutorial_hero_departed` | First King's hero abandons service — "something deeper is at work" |
|
||||
| 12 | `tutorial_hero_departed_again` | Second one in a different month — "something is wrong, I can feel it" (foreshadowing) |
|
||||
| 13 | `tutorial_hero_faction_appears` | John Ranil and Elena Fyar leave Sadar's service and reappear as independent tutorial factions |
|
||||
|
||||
### Tactical battle flow (`tutorial_battle.json`)
|
||||
|
||||
|
||||
+136
-253
@@ -1,290 +1,173 @@
|
||||
# URP Migration Completion Notes
|
||||
# URP Migration Plan
|
||||
|
||||
Last refreshed: 2026-06-14
|
||||
## Why
|
||||
|
||||
## Why This Matters
|
||||
Unity is removing Built-In Render Pipeline (BiRP) support after Unity 6.7. We're currently on Unity 6.4. Each Unity minor version ships roughly quarterly, giving us a few release cycles before BiRP is dropped. This document lays out the migration plan.
|
||||
|
||||
Unity is steering projects away from the Built-in Render Pipeline (BiRP) toward the
|
||||
Universal Render Pipeline (URP). Eagle0 has completed the URP migration and now runs
|
||||
on URP:
|
||||
## Strategy: Long-Lived Feature Branch
|
||||
|
||||
- `ProjectSettings/GraphicsSettings.asset` points at
|
||||
`Assets/Settings/URP/Eagle0URPPipeline.asset`.
|
||||
- Every active quality level in `ProjectSettings/QualitySettings.asset` uses the
|
||||
same URP pipeline asset.
|
||||
- Legacy Post Processing Stack v2 has been removed from `Packages/manifest.json`;
|
||||
keep verifying it stays out with the migration inventory.
|
||||
All URP conversion work happens on a **long-lived `urp-migration` branch**. Main stays fully functional on BiRP throughout the migration.
|
||||
|
||||
The migration is no longer an active compatibility project. Keep this document as the
|
||||
completion record and maintenance checklist for future Unity, URP, shader, material,
|
||||
camera layering, or render-order changes.
|
||||
- **Main branch**: Continues using BiRP. All gameplay, AI, and server development proceeds normally.
|
||||
- **URP branch**: Accumulates rendering pipeline changes across all phases.
|
||||
- **Regular rebasing**: Periodically rebase/merge `main` into the URP branch to stay current. Shader and material changes rarely conflict with gameplay code, so merge conflicts should be manageable.
|
||||
- **Merge to main**: Only when everything renders correctly and visual QA passes.
|
||||
|
||||
## Current Recommendation
|
||||
This avoids the "everything is pink" problem of switching the pipeline on main before shaders are converted.
|
||||
|
||||
Keep URP enabled on main. Do not continue speculative URP cleanup. Use the baseline
|
||||
checklist and inventory tooling only when a future change touches shaders, materials,
|
||||
camera layering, render order, pipeline settings, or a visible rendering regression.
|
||||
## Current State
|
||||
|
||||
Maintenance rules:
|
||||
|
||||
1. Keep the visual baseline current for Connection, Eagle, Shardok, and Settings.
|
||||
2. Refresh the shader/material inventory after meaningful asset or scene changes.
|
||||
3. Remove or isolate unused rendering dependencies.
|
||||
4. Keep the existing CI guardrails passing.
|
||||
5. Treat future pipeline experiments as separate branches with explicit findings.
|
||||
|
||||
Known URP regressions found during playtesting have been fixed. Normal gameplay work
|
||||
can proceed; future rendering PRs should be driven by observable regressions or
|
||||
intentional rendering changes.
|
||||
|
||||
## Maintenance Workflow
|
||||
|
||||
### 1. Create Visual Baselines
|
||||
|
||||
Capture a small set of known-good views before any future change to URP settings,
|
||||
custom shaders, render ordering, or third-party visual materials. These should be
|
||||
reproducible enough that a human can compare screenshots after each rendering change.
|
||||
|
||||
Minimum baseline scenes and states:
|
||||
|
||||
| Area | Required View |
|
||||
|---|---|
|
||||
| Connection | Connect panel, stored account list, running games list |
|
||||
| Eagle map | Normal province map with borders, faction highlights, selected province tooltip |
|
||||
| Eagle weather | Drought/heat shimmer, flood, blizzard/rain overlays |
|
||||
| Eagle beasts | Bird/animal/monster beast effects on map and notification panel |
|
||||
| Eagle UI | Command panel, notifications, settings panel, generated text |
|
||||
| Shardok | Terrain, grid lines, labels/icons, bridges, fires, overlays, command buttons |
|
||||
| Scene transitions | Connection -> Eagle -> Shardok -> Eagle |
|
||||
|
||||
Keep the manual checklist and editor-only capture tool working so rendering PRs can
|
||||
leave screenshots in an ignored directory.
|
||||
|
||||
Use `docs/URP_VISUAL_BASELINE_CHECKLIST.md` for the current manual baseline capture
|
||||
set.
|
||||
|
||||
### 2. Refresh The Shader And Material Inventory When Needed
|
||||
|
||||
The inventory is useful before significant rendering work. It reports:
|
||||
|
||||
- all shaders under `Assets/`
|
||||
- all materials and the shader each material uses
|
||||
- all scenes/prefabs that reference custom shaders or Post Processing components
|
||||
- all usages of Built-in-only shader features:
|
||||
- `GrabPass`
|
||||
- `#pragma surface`
|
||||
- `UnityCG.cginc`
|
||||
- `UnityUI.cginc`
|
||||
- tessellation
|
||||
|
||||
Run `Eagle0 > Rendering > Generate URP Migration Inventory` in the Unity editor to
|
||||
generate the current report. The tool writes markdown and CSV files under Unity
|
||||
`Temp/urp_migration/`, so running it should not dirty project assets.
|
||||
|
||||
The generated CSVs include both the full material list and production material
|
||||
references, which helps separate game-path shader risk from unused package/demo
|
||||
assets.
|
||||
|
||||
`docs/URP_SHADER_DEBT_INVENTORY.md` records the current production-vs-package
|
||||
classification for remaining Built-in-style shader patterns.
|
||||
|
||||
Current inventory snapshot:
|
||||
|
||||
- Render pipeline settings are URP for both graphics default and active quality.
|
||||
- Post Processing Stack reference hits are still zero.
|
||||
- The all-material inventory is noisy: most material assets are `Standard`, and
|
||||
many Built-in-only shader hits are in third-party or package/demo assets.
|
||||
- The production material-reference report is the better starting point for the
|
||||
probe. Current production shader references are concentrated in TextMeshPro,
|
||||
Eagle custom map/effect shaders, Shardok grid/fire materials, and a small number
|
||||
of standard-material beast assets.
|
||||
- The project-owned Eagle and Shardok production custom shaders have been hardened
|
||||
for URP and passed Windows Unity builds after merge.
|
||||
- Project-owned runtime-created Eagle/Shardok materials now use serialized shader or
|
||||
material references instead of project shader-name fallbacks.
|
||||
- No project-owned production shader under `Assets/Eagle` or `Assets/Shardok`
|
||||
currently contains the scanned Built-in-era shader patterns.
|
||||
- Package/third-party shader debt remains documented but is not on the production
|
||||
path unless a future inventory run finds new references.
|
||||
|
||||
### 3. Verify Legacy Post Processing Usage
|
||||
|
||||
The project no longer depends on `com.unity.postprocessing`. The old references were
|
||||
limited to an unused Orc/Ogre sample scene profile and scene-template type metadata,
|
||||
not production scenes.
|
||||
|
||||
Keep the project free of Post Processing Stack v2 references and use the inventory
|
||||
report to catch regressions.
|
||||
|
||||
If references reappear:
|
||||
|
||||
- document the exact scene/camera/profile usage
|
||||
- either remove the stale dependency or plan the equivalent URP Volume settings
|
||||
|
||||
### 4. Keep Drought Effects URP-Friendly
|
||||
|
||||
The old `Eagle/HeatShimmer` shader used a Built-in-style `GrabPass`, but shimmer was
|
||||
already disabled in `DroughtEffect` because it did not work well with the UI Canvas.
|
||||
Drought now intentionally uses the animated sun overlay as its current visual signal.
|
||||
|
||||
Keep drought visuals free of `GrabPass` unless we intentionally add a new
|
||||
URP-friendly effect.
|
||||
|
||||
If stronger drought visuals are needed later, prefer:
|
||||
|
||||
- an animated transparent texture overlay clipped like the existing province particles
|
||||
- a small particle/sprite effect around the sun icon
|
||||
- a URP renderer feature only if screen-space distortion is truly worth the complexity
|
||||
|
||||
### 5. Leave Third-Party Shader Conversion Demand-Driven
|
||||
|
||||
The riskiest third-party shader set is Polytope Studio. Do not hand-convert package
|
||||
shader sets speculatively.
|
||||
|
||||
`docs/URP_THIRD_PARTY_ASSET_AUDIT.md` records the current production GUID-reference
|
||||
scan. The important result is that production assets use Polytope character prefabs
|
||||
for Eagle beast effects, but the Polytope environment/water assets that still contain
|
||||
`GrabPass` were not found on the production path.
|
||||
|
||||
If a future visible regression or inventory update makes third-party conversion
|
||||
necessary, record:
|
||||
|
||||
- current installed version
|
||||
- whether a URP-compatible version exists
|
||||
- whether upgrading is license/account-accessible
|
||||
- whether the package is still used in production scenes
|
||||
|
||||
### 6. Keep Probe Guidance For Future Pipeline Experiments
|
||||
|
||||
The original disposable probe has served its main purpose: URP is enabled on main and
|
||||
the biggest visual regressions were fixed in focused PRs. Keep the probe playbook as
|
||||
a template for future risky pipeline experiments, not as current migration guidance.
|
||||
|
||||
Use `docs/URP_PROBE_PLAYBOOK.md` when we need to measure a future Unity or render
|
||||
pipeline change cheaply before committing to a mergeable branch.
|
||||
|
||||
## Known Maintenance Risks
|
||||
|
||||
| Risk | Why It Matters | Right-Now Action |
|
||||
| Category | Count | Notes |
|
||||
|---|---|---|
|
||||
| `UnityUI.cginc` usage in package shaders | URP does not provide the same include path/semantics for hand-authored shaders | Leave package-managed TMP shaders alone unless a visible regression appears. |
|
||||
| `GrabPass` in unused package shaders | No direct URP equivalent | Ignore until production starts referencing those assets. |
|
||||
| Surface shaders in third-party packages | URP does not support Built-in surface shader generation | Covered by beast material compatibility on current production paths; convert only for a visible regression. |
|
||||
| Post Processing Stack v2 | Replaced by URP Volume system | Keep package and runtime/project-setting references from returning. |
|
||||
| Scene lighting | URP lighting/shadow settings differ from BiRP | Baseline and rebake only for visible regressions or intentional lighting work. |
|
||||
| Render ordering | Shardok and Eagle overlays depend on careful layering | Include affected overlays in baseline whenever render order changes. |
|
||||
| Shaders | 47 | 7 custom Eagle, 1 hex mesh, 20 Polytope, 13 TextMesh Pro, 6 other third-party |
|
||||
| Materials | 325 | Mix of Standard shader, custom, and third-party |
|
||||
| ShaderGraph files | 4 | All TextMesh Pro (URP + HDRP variants already exist) |
|
||||
| Scenes with baked lighting | 5 | Connection, Eagle, Shardok, Shared, Map Editor |
|
||||
| Post-processing | 1 profile | PPP_Orc.asset (Post Processing Stack v2, FXAA/TAA, AO) |
|
||||
| C# rendering scripts | 16 | Material/shader property manipulation only |
|
||||
| Rendering path | Forward | Matches URP default |
|
||||
|
||||
## Follow-Up Strategy
|
||||
### Positive Findings
|
||||
|
||||
Main now carries the completed URP switch. Future rendering work should be split by
|
||||
observable behavior:
|
||||
- **No OnRenderImage, Graphics.Blit, CommandBuffer, or GL.\* usage** in C# code
|
||||
- Forward rendering already in use (matches URP default)
|
||||
- TextMesh Pro already has URP ShaderGraph variants in the project
|
||||
- No custom render passes or ScriptableRenderFeatures
|
||||
- Shader property manipulation (SetTexture, SetFloat, SetColor) is URP-compatible
|
||||
|
||||
- **Compatibility fixes**: small PRs for specific pink/missing/dark assets.
|
||||
- **Render ordering fixes**: small PRs for Shardok/Eagle layer order regressions.
|
||||
- **Inventory/docs updates**: docs-only or editor-tool PRs that do not alter runtime
|
||||
visuals.
|
||||
- **Pipeline experiments**: isolated branches using the probe playbook, with findings
|
||||
copied into a mergeable follow-up.
|
||||
### Known Risk Areas
|
||||
|
||||
This keeps the project from accumulating another broad, hard-to-revert rendering PR.
|
||||
- **GrabPass shaders** (HeatShimmerShader, PT_Water_Shader) have no direct URP equivalent
|
||||
- **ProvinceMapShader** is complex (province ID lookup, border rendering, ocean animation, faction highlighting, UI clipping)
|
||||
- **Surface shaders** (`#pragma surface surf Standard`) must be rewritten as HLSL or ShaderGraph
|
||||
- **Polytope Studio shaders** (20 shaders) have no vendor-provided URP variants
|
||||
- **Post Processing Stack v2** must be replaced with URP's integrated Volume system
|
||||
- **Tessellation** (PT_Water_Shader) is not natively supported in URP
|
||||
|
||||
## Completed Phases
|
||||
## Shader Inventory
|
||||
|
||||
### Phase 0: Preparation And Switch
|
||||
### Custom Eagle Shaders (7) -- HIGH PRIORITY
|
||||
|
||||
- Visual baseline checklist/screenshots: complete, keep current
|
||||
- Repeatable shader/material inventory: complete, keep current
|
||||
- Post Processing Stack usage decision: complete, keep removed
|
||||
- Drought visual decision: complete, use non-GrabPass visual signal
|
||||
- Third-party URP availability check: documented
|
||||
- Disposable URP probe findings: superseded by the merged URP switch
|
||||
- CI guardrails for pipeline settings, runtime shader lookup, project shader
|
||||
patterns, and beast-material compatibility: complete
|
||||
| Shader | Complexity | Key Issues |
|
||||
|---|---|---|
|
||||
| ProvinceMapShader | High | Province ID texture lookup, border rendering, ocean animation, faction highlighting, `UnityUI.cginc` dependency, stencil/clipping |
|
||||
| ProvinceWeatherMapShader | Medium | Weather overlay rendering |
|
||||
| ProvinceWeatherShader | Medium | Province weather effects |
|
||||
| ProvinceParticleShader | Low | Custom particle rendering |
|
||||
| HeatShimmerShader | High | **GrabPass** for screen distortion, province masking |
|
||||
| ClipRectParticleUnlit | Low | UI-clipped particle shader |
|
||||
| maskShader | Low | UI masking |
|
||||
|
||||
### Phase 1: Pipeline Setup
|
||||
### Hex Mesh Shader (1) -- MODERATE PRIORITY
|
||||
|
||||
- Install/configure URP: complete
|
||||
- Create URP Pipeline Asset and Renderer Asset: complete
|
||||
- Configure forward rendering, shadows, HDR, and renderer features: complete enough
|
||||
for current gameplay paths
|
||||
- Run Unity's Render Pipeline Converter: complete for the merged switch
|
||||
- Keep a record of every auto-converted material: use the inventory/audit tools
|
||||
Uses `#pragma surface surf Standard` with GPU instancing. Straightforward conversion to URP Lit or ShaderGraph.
|
||||
|
||||
### Phase 2: Runtime Visual QA And Cleanup
|
||||
### Third-Party Shaders (39)
|
||||
|
||||
Project-owned custom shader conversion, shader-name fallback cleanup, and deletion
|
||||
cleanup are complete. Current CI coverage keeps the URP pipeline assigned, required
|
||||
runtime shader references serialized, required player-runtime-only shaders included,
|
||||
project-owned Eagle/Shardok shaders free of scanned Built-in-only patterns, key
|
||||
Addressables labels covered, and major Eagle/Shardok visual wiring guarded.
|
||||
| Source | Count | Complexity | Notes |
|
||||
|---|---|---|---|
|
||||
| Polytope Studio | 20 | Moderate-High | PBR, Toon, vegetation (custom lighting), water (**GrabPass + tessellation**). No vendor URP pack available. |
|
||||
| TextMesh Pro | 13 | Low | URP ShaderGraph variants already exist in project |
|
||||
| RRFreelance | 3 | Low | Standard surface shaders, direct conversion |
|
||||
| Clown.fat | 2 | Moderate | Custom ToonRamp lighting model |
|
||||
| GUI Pro Kit | 1 | Low | Hidden particle shader |
|
||||
|
||||
Runtime visual QA was completed during playtesting. Known regressions from that pass
|
||||
were fixed before the migration was called done.
|
||||
### GrabPass Shaders (Require Special Handling)
|
||||
|
||||
Follow-up notes:
|
||||
GrabPass does not exist in URP. These must be reimplemented using `ScriptableRenderPass` + `Renderer Features` or Blit-based alternatives:
|
||||
|
||||
- Use `docs/URP_VISUAL_BASELINE_CHECKLIST.md` for each future rendering follow-up.
|
||||
- Keep screenshots in an ignored local directory, not in git.
|
||||
- Treat package/third-party shader conversions as response work for visible
|
||||
regressions or new production references, not as speculative cleanup.
|
||||
1. **HeatShimmerShader** -- Screen distortion effect (currently has a shimmer-disabled TODO, may be deprioritized)
|
||||
2. **PT_Water_Shader** -- Water refraction/transparency (PT_Water_Shader_WebGl exists without GrabPass as a reference)
|
||||
|
||||
### Phase 3: GrabPass And Special Effects Maintenance
|
||||
## Phased Approach
|
||||
|
||||
- Keep drought visuals on the current animated sun overlay unless a new URP-friendly
|
||||
effect is intentionally added.
|
||||
- Reimplement or replace the Polytope `PT_Water_Shader` GrabPass shaders if they are
|
||||
ever become used by production assets.
|
||||
- Avoid leaving screen-space effects until the end; they are likely to drive renderer
|
||||
feature requirements.
|
||||
### Phase 1: Pipeline Setup + Auto-Conversion (Days 1-3)
|
||||
|
||||
### Phase 4: Third-Party Shaders Maintenance
|
||||
- Install URP package
|
||||
- Create URP Pipeline Asset and Renderer Asset
|
||||
- Configure basic pipeline settings (forward rendering, shadow settings)
|
||||
- Run Unity's **Render Pipeline Converter** (Edit > Rendering > Render Pipeline Converter)
|
||||
- Auto-converts Standard shader materials and some built-in shaders
|
||||
- Handles a significant portion of the 325 materials
|
||||
- Will NOT touch custom shaders
|
||||
- Create a test scene to validate basic rendering
|
||||
- Migrate viewport clipping system (`RendererViewportClipper`, `ViewportClipper`, `PopupClipper`) to use URP-compatible global shader properties
|
||||
|
||||
- Upgrade packages where vendor URP shaders exist and a production need appears.
|
||||
- Convert remaining PBR/toon/vegetation shaders manually only for production
|
||||
references or visible regressions.
|
||||
- Prefer replacement or deletion for unused demo-only assets.
|
||||
### Phase 2: Custom Eagle Shaders (Weeks 1-3)
|
||||
|
||||
### Phase 5: Materials, Lighting, And Post Processing Maintenance
|
||||
This is the critical path. Without these, the game is unplayable.
|
||||
|
||||
- Batch-update materials only for production paths or visible regressions.
|
||||
- Replace Post Processing Stack v2 with URP Volumes only if a production need returns.
|
||||
- Rebake lighting for production scenes when scene lighting is intentionally changed.
|
||||
- Tune shadow and HDR settings when visual QA or performance work justifies it.
|
||||
**Week 1-2: ProvinceMapShader**
|
||||
- Convert from BiRP Cg/HLSL to URP HLSL
|
||||
- Replace `UnityCG.cginc` includes with `Core.hlsl` / `Common.hlsl`
|
||||
- Replace `UnityUI.cginc` with custom URP-compatible UI clipping
|
||||
- Preserve province ID texture lookup, border rendering, ocean animation, faction highlighting
|
||||
- Validate stencil operations and render queue ordering
|
||||
|
||||
### Phase 6: Validation Maintenance
|
||||
**Week 2-3: Remaining Eagle shaders**
|
||||
- ProvinceWeatherMapShader + ProvinceWeatherShader
|
||||
- ProvinceParticleShader + ClipRectParticleUnlit
|
||||
- maskShader
|
||||
- Hex Mesh Shader (convert surface shader to URP Lit)
|
||||
- HeatShimmerShader (reimplement without GrabPass, or defer if shimmer remains disabled)
|
||||
|
||||
Required visual QA for future rendering changes:
|
||||
### Phase 3: Third-Party Shaders (Weeks 4-6)
|
||||
|
||||
- Connection lobby
|
||||
- Eagle map and command flows
|
||||
- Eagle weather/beasts/notifications
|
||||
- Shardok battle map overlays, unit text/icons, bridges, fires
|
||||
- Scene transitions
|
||||
- Mac and Windows builds
|
||||
- Performance sanity check
|
||||
**Polytope Studio (20 shaders)**
|
||||
- Convert PBR shaders (Armors, NPC, Weapons, Props, Rock) from surface shaders to URP Lit / ShaderGraph
|
||||
- Convert Toon shaders to custom URP shader or ShaderGraph with custom lighting
|
||||
- Convert vegetation shaders (custom `StandardCustom` lighting) to ShaderGraph
|
||||
- PT_Water_Shader: Reimplement without GrabPass and tessellation (use PT_Water_Shader_WebGl as reference for non-GrabPass approach)
|
||||
|
||||
## Rough Effort
|
||||
**Other third-party**
|
||||
- Clown.fat ToonRamp shaders: Convert custom lighting model to ShaderGraph
|
||||
- RRFreelance shaders: Direct Standard-to-URP-Lit conversion
|
||||
- GUI Pro Kit particle shader: Convert to URP particle shader
|
||||
|
||||
| Work | Status |
|
||||
|---|---|
|
||||
| Preparation/probe | Complete |
|
||||
| Pipeline setup | Complete |
|
||||
| Core custom shaders | Complete |
|
||||
| Runtime shader/build guardrails | Complete; project-owned runtime paths use serialized shader/material references |
|
||||
| GrabPass/special effects | Deferred until a production reference or new effect need appears |
|
||||
| Third-party shaders/materials | Covered by runtime compatibility and visual QA unless a visible regression appears |
|
||||
| Lighting/post-processing | No current Post Processing Stack dependency; rebake/tune only for visible regressions |
|
||||
| QA/polish | Completed for the migration; repeat for future rendering changes |
|
||||
### Phase 4: Materials, Lighting & Post-Processing (Weeks 7-8)
|
||||
|
||||
**Materials**
|
||||
- Batch-update any remaining materials not handled by the auto-converter
|
||||
- Verify all 325 materials render correctly
|
||||
- Fix any visual differences from lighting model changes
|
||||
|
||||
**Lighting**
|
||||
- Rebake lightmaps for all 5 scenes (Connection, Eagle, Shardok, Shared, Map Editor)
|
||||
- Configure URP shadow cascade settings to match current quality levels
|
||||
- Verify HDR rendering
|
||||
|
||||
**Post-Processing**
|
||||
- Remove Post Processing Stack v2 dependency (`com.unity.postprocessing: 3.5.1`)
|
||||
- Replace with URP integrated Volume system
|
||||
- Recreate PPP_Orc effects (FXAA/TAA, Ambient Occlusion) using URP Volume overrides
|
||||
|
||||
### Phase 5: Testing & Validation (Weeks 9-11)
|
||||
|
||||
- Visual QA across all 6 scenes
|
||||
- Verify beast/character rendering (vertex colors, animations)
|
||||
- Verify weather effects (blizzard, drought, flood)
|
||||
- Verify particle systems (fire & explosion effects, UI particles)
|
||||
- Verify UI rendering (GUI Pro Kit, Modern UI Pack, TextMesh Pro)
|
||||
- Performance profiling (URP has different performance characteristics)
|
||||
- Test on target platforms
|
||||
- Bug fixes and visual polish
|
||||
|
||||
## Effort Estimates
|
||||
|
||||
| Phase | Best Case | Realistic | Worst Case |
|
||||
|---|---|---|---|
|
||||
| 1. Pipeline Setup | 1-2 days | 2-3 days | 3-5 days |
|
||||
| 2. Custom Eagle Shaders | 1.5 weeks | 2-3 weeks | 3-4 weeks |
|
||||
| 3. Third-Party Shaders | 1.5 weeks | 2-3 weeks | 3-4 weeks |
|
||||
| 4. Materials & Lighting | 3-5 days | 1-1.5 weeks | 2 weeks |
|
||||
| 5. Testing & Validation | 1 week | 1.5-2 weeks | 2-3 weeks |
|
||||
| **Total** | **6-8 weeks** | **8-12 weeks** | **12-16 weeks** |
|
||||
|
||||
The biggest variable is Polytope Studio shader conversion. If vendor URP packs become available, Phase 3 shrinks significantly.
|
||||
|
||||
## Key Reminders
|
||||
|
||||
- URP is already enabled on main; keep follow-up work narrowly scoped.
|
||||
- Project-owned shader-name fallbacks should stay out of runtime-created materials;
|
||||
use serialized shader/material references instead.
|
||||
- `Shader.Find` remains acceptable for Unity/URP-provided shader names inside
|
||||
third-party compatibility shims, where there is no project asset to serialize.
|
||||
- Do not hand-convert package shaders unless runtime visual QA exposes a concrete
|
||||
regression or a new production reference appears.
|
||||
- Keep C# changes minimal; current rendering scripts mostly set material properties.
|
||||
- Preserve recent Shardok render-order behavior while changing pipelines.
|
||||
- Treat screenshots as migration tests; compilation alone is not meaningful here.
|
||||
- **Don't leave GrabPass rewrites and ProvinceMapShader for the end** -- they're the riskiest pieces and should be tackled early.
|
||||
- The migration window (~1 year) is comfortable. Spreading the work across this window is fine, but front-load the hard shader work.
|
||||
- C# code changes should be minimal -- the codebase avoids direct rendering API usage.
|
||||
- `UnityUI.cginc` has no direct URP equivalent. Custom implementation will be needed for UI clipping in shaders.
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
# URP Probe Playbook
|
||||
|
||||
Last refreshed: 2026-06-14
|
||||
|
||||
This playbook is historical reference for the disposable URP probe described in
|
||||
`docs/URP_MIGRATION_PLAN.md`. The URP migration is complete; keep this only as a
|
||||
template for future risky render-pipeline experiments. A probe branch is not a
|
||||
production branch and should not be merged as-is.
|
||||
|
||||
## Goals
|
||||
|
||||
- Use the already-installed URP package in an isolated branch.
|
||||
- Create the minimum URP pipeline/renderer assets needed to enter Play Mode.
|
||||
- Run Unity's Render Pipeline Converter.
|
||||
- Record compile errors, pink materials, broken shaders, and visual regressions.
|
||||
- Preserve findings in docs, then discard or reset the probe branch.
|
||||
|
||||
## Branch Shape
|
||||
|
||||
Use two branches:
|
||||
|
||||
| Branch | Purpose | Mergeable |
|
||||
|---|---|---|
|
||||
| `codex/urp-probe` | Disposable package/settings/material conversion experiment | No |
|
||||
| `codex/urp-probe-findings` | Small docs-only summary copied from the probe | Yes |
|
||||
|
||||
Do not open a PR from `codex/urp-probe` unless it is clearly marked draft and
|
||||
experimental. Prefer opening the PR from `codex/urp-probe-findings`.
|
||||
|
||||
## Before Starting
|
||||
|
||||
1. Confirm `origin/main` is fresh.
|
||||
2. Confirm the worktree is clean.
|
||||
3. Capture or review the baseline checklist in
|
||||
`docs/URP_VISUAL_BASELINE_CHECKLIST.md`.
|
||||
4. Generate a current inventory with
|
||||
`Eagle0 > Rendering > Generate URP Migration Inventory`.
|
||||
5. Keep the inventory output under Unity `Temp/urp_migration/`; do not commit
|
||||
generated reports unless they are intentionally summarized.
|
||||
|
||||
## Probe Steps
|
||||
|
||||
1. Create `codex/urp-probe` from `origin/main`.
|
||||
2. Confirm `com.unity.render-pipelines.universal` is installed in
|
||||
`Packages/manifest.json`.
|
||||
3. If testing new pipeline settings, create alternate URP Pipeline/Renderer assets
|
||||
under a clearly named temporary folder.
|
||||
4. Assign the alternate URP pipeline asset in:
|
||||
- `ProjectSettings/GraphicsSettings.asset`
|
||||
- `ProjectSettings/QualitySettings.asset`
|
||||
5. Run Unity's Render Pipeline Converter only if the experiment intentionally tests
|
||||
material conversion.
|
||||
6. Enter Play Mode from `Assets/Scenes/Main.unity`.
|
||||
7. Exercise the baseline views:
|
||||
- Connection lobby
|
||||
- Eagle map
|
||||
- Eagle weather and beast effects
|
||||
- Shardok terrain, grid, labels, bridges, fires, overlays
|
||||
- Connection -> Eagle -> Shardok -> Eagle transitions
|
||||
8. Record the exact failure mode for every broken visual:
|
||||
- compile error
|
||||
- pink material
|
||||
- invisible mesh or sprite
|
||||
- wrong render order
|
||||
- lighting/shadow difference
|
||||
- input or scene-load regression
|
||||
|
||||
## Expected High-Risk Areas
|
||||
|
||||
- Eagle map rendering:
|
||||
- province colors, borders, and ocean animation
|
||||
- weather overlays and province particles
|
||||
- beast effects on the map and in notifications
|
||||
- Shardok map rendering:
|
||||
- terrain, bridges, fire overlays, and grid overlays
|
||||
- foreground UI/effect overlays
|
||||
- unit labels/icons over fires, bridges, and one-shot animations
|
||||
- Third-party 3D materials:
|
||||
- Polytope Studio character prefabs used by Eagle human-type beast effects
|
||||
- RRFreelance Orc/Ogre materials used by `OgreEffect`
|
||||
- Render ordering in Shardok:
|
||||
- unit text/icons versus fires
|
||||
- unit text/icons versus bridges
|
||||
- selection/command overlays versus labels
|
||||
|
||||
## Findings Template
|
||||
|
||||
Copy this template into the mergeable findings branch after the probe:
|
||||
|
||||
```markdown
|
||||
# URP Probe Findings
|
||||
|
||||
Date:
|
||||
Unity version:
|
||||
URP package version:
|
||||
Probe branch commit:
|
||||
|
||||
## Summary
|
||||
|
||||
- Entered Play Mode:
|
||||
- Connection usable:
|
||||
- Eagle usable:
|
||||
- Shardok usable:
|
||||
- Build attempted:
|
||||
|
||||
## Converter Results
|
||||
|
||||
- Materials converted:
|
||||
- Materials left pink:
|
||||
- Shader compile errors:
|
||||
|
||||
## Visual Regressions
|
||||
|
||||
| Area | Expected | Actual | Likely Cause | Next Action |
|
||||
|---|---|---|---|---|
|
||||
|
||||
## Required Migration PRs
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Notes
|
||||
|
||||
-
|
||||
```
|
||||
|
||||
## After The Probe
|
||||
|
||||
1. Copy findings into a docs-only branch.
|
||||
2. Delete or abandon `codex/urp-probe`.
|
||||
3. Do not merge probe-generated package/settings/material churn into main until the
|
||||
findings have been reviewed and broken visuals have owner PRs.
|
||||
@@ -1,145 +0,0 @@
|
||||
# URP Shader Debt Inventory
|
||||
|
||||
Last refreshed: 2026-06-14
|
||||
|
||||
This inventory classifies remaining Built-in Render Pipeline shader patterns after
|
||||
the completed URP switch. The project is running URP, but some package shader source
|
||||
files still use Built-in-era helpers or features. That is package/vendor debt, not
|
||||
evidence that the old pipeline is active and not an active migration blocker.
|
||||
|
||||
## Scan Method
|
||||
|
||||
The scan looked for these patterns in Unity shader-like assets:
|
||||
|
||||
- `GrabPass`
|
||||
- `#pragma surface`
|
||||
- `UnityCG.cginc`
|
||||
- `UnityUI.cginc`
|
||||
- `UNITY_MATRIX_`
|
||||
- `sampler2D _GrabTexture`
|
||||
- `tessell`
|
||||
- `Tessellation`
|
||||
|
||||
It then mapped every matching shader asset to its Unity GUID and scanned production
|
||||
serialized assets under:
|
||||
|
||||
- `Assets/Scenes/`
|
||||
- `Assets/Eagle/`
|
||||
- `Assets/Shardok/`
|
||||
- `Assets/ConnectionHandler/`
|
||||
- `Assets/UI/`
|
||||
- `Assets/common/`
|
||||
- `Assets/Tutorial/`
|
||||
- `Assets/Resources/`
|
||||
- `Assets/Materials/`
|
||||
|
||||
This catches serialized production references. It does not prove runtime code never
|
||||
loads an asset by path, but it separates game-path shader debt from package/demo
|
||||
noise well enough to decide whether future rendering work is needed.
|
||||
|
||||
## Summary
|
||||
|
||||
| Bucket | Shader Assets With Built-in Patterns | Serialized Production References |
|
||||
|---|---:|---:|
|
||||
| Project-owned production shaders | 0 | 0 referenced |
|
||||
| Project-owned runtime-loaded shaders | 0 | 0 serialized refs |
|
||||
| Project-owned unreferenced shaders | 0 | 0 referenced |
|
||||
| TextMeshPro package shaders | 17 | 1 referenced |
|
||||
| Third-party/package shaders | 25 | 0 referenced |
|
||||
|
||||
Project-owned runtime-created materials now use serialized shader or material
|
||||
references for Eagle weather, Eagle fireworks, Shardok foreground UI/effects, and
|
||||
Shardok foreground text. Remaining production `Shader.Find` usage is limited to
|
||||
Unity/URP-provided shader names inside `BeastMaterialCompatibility`, plus
|
||||
third-party package code outside our ownership.
|
||||
|
||||
## Completed Project-Owned Shader Hardening
|
||||
|
||||
These production shaders have been converted to URP-compatible HLSL and passed the
|
||||
Windows Unity build after merge.
|
||||
|
||||
| Shader | Production References | Completion Notes |
|
||||
|---|---|---|
|
||||
| `Assets/Eagle/Shaders/ProvinceMapShader.shader` | `Assets/Eagle/Materials/ProvinceMapMaterial.mat` | Converted with province color, border, ocean, and UI clipping behavior preserved. |
|
||||
| `Assets/Eagle/Shaders/ProvinceWeatherMapShader.shader` | `Assets/Eagle/Materials/ProvinceWeatherMapMaterial.mat` | Converted with weather lookup, overlay alignment, clipping, and initialized effect/tint values. |
|
||||
| `Assets/Eagle/Shaders/ProvinceWeatherShader.shader` | `Assets/Eagle/Weather/BlizzardEffect.mat`, `Assets/Eagle/Weather/DroughtEffect.mat`, `Assets/Eagle/Weather/FloodEffect.mat` | Converted for the live drought/flood/blizzard material path. |
|
||||
| `Assets/Eagle/Shaders/ProvinceParticleShader.shader` | `Assets/Eagle/Effects/ProvinceParticleMaterial.mat` | Converted for Eagle map province particles. Weather effect prefabs serialize the shared particle material instead of relying on shader-name fallback. |
|
||||
| `Assets/Eagle/Shaders/ClipRectParticleUnlit.shader` | `Assets/Eagle/Effects/Fireworks.prefab`, `Assets/Eagle/Effects/ParticleAlphaBlendMaterial.mat`, `Assets/Eagle/Effects/ParticleStandardUnlitMaterial.mat` | Converted for Eagle one-shot effects and clipped particle UI paths; Fireworks serializes this shader instead of using shader-name fallback. |
|
||||
| `Assets/Eagle/maskShader.shader` | Formerly `Assets/Eagle/Materials/map_color_nolabels.mat`, `Assets/Eagle/Materials/map_color_whitened.mat` | Deleted after confirming the serialized material path was stale. |
|
||||
| `Assets/Hex Mesh Shader.shader` | None | Deleted after confirming no serialized or code references to the asset, shader name, or GUID. |
|
||||
| `Assets/Shardok/ShardokFireOverlay.shader` | `Assets/Shardok/ShardokFireOverlay.mat` | Converted with the Shardok fire overlay layering path preserved. |
|
||||
| `Assets/Shardok/Shaders/ForegroundUIOverlay.shader` | Serialized Shardok foreground UI/effect shader references | URP pass is serialized on the Shardok scene/container and included in `ProjectSettings/GraphicsSettings.asset`; the stale Built-in fallback pass has been removed. |
|
||||
| `Assets/TextMesh Pro/Resources/Shaders/TMP_SDF Overlay.shader` | Serialized Shardok foreground text shader references | Serialized on the Shardok scene/container and included in `ProjectSettings/GraphicsSettings.asset` for required foreground text overlay material. |
|
||||
|
||||
## CI Guardrails
|
||||
|
||||
- `URPPipelineSettingsTests` keeps Graphics and Quality settings on the same URP
|
||||
pipeline asset.
|
||||
- `RuntimeShaderValidationTests` keeps required runtime shader assets present,
|
||||
serialized Shardok foreground shader references assigned, and player-runtime-only
|
||||
shaders listed in Always Included Shaders.
|
||||
- `WeatherEffectMaterialTests` keeps Eagle flood/blizzard prefabs wired to the
|
||||
shared URP province particle material and prevents shader-name fallbacks from
|
||||
returning to those runtime paths.
|
||||
- `EpidemicEffectMaterialTests` keeps Eagle epidemic particles wired to the shared
|
||||
clipped particle material.
|
||||
- `EagleMapMaterialWiringTests` keeps Eagle map/weather controllers wired to the
|
||||
project map materials and expected URP shaders.
|
||||
- `ProjectShaderPatternTests` fails if project-owned Eagle/Shardok shaders
|
||||
reintroduce the scanned Built-in-only patterns.
|
||||
- `RuntimeShaderFindUsageTests` fails if project-owned Eagle/Shardok runtime
|
||||
scripts reintroduce shader-name lookups outside documented compatibility shims.
|
||||
- `BeastMaterialCompatibilityTests` keeps runtime third-party beast material
|
||||
conversion covered.
|
||||
- `ProvinceBeastsControllerTests` keeps Eagle beast content names routed to
|
||||
specific animated effect prefabs instead of silently falling back to the generic
|
||||
circling-bird effect.
|
||||
- `FireworksEffectTests` keeps the fireworks prefab wired to the project-owned
|
||||
clipped particle shader and prevents shader-name fallback from returning.
|
||||
- `TutorialSpriteAssetTests` keeps tutorial TMP sprite assets and the shared dialogue
|
||||
panel instruction sprite asset renderable.
|
||||
- `ProvinceActionAnimatorSpriteTests` keeps Eagle action animation sprite fields
|
||||
assigned in the Eagle scene.
|
||||
- `PostProcessingStackRemovalTests` keeps legacy Post Processing Stack v2 package
|
||||
and runtime/project-setting references from returning.
|
||||
- `ProductionShaderReferenceTests` keeps shaders with Built-in-only patterns, and
|
||||
materials that use them, off the production serialized asset path except for the
|
||||
package-managed TextMeshPro shader family.
|
||||
|
||||
## Highest Priority: Referenced Project-Owned Shader Debt
|
||||
|
||||
No project-owned production shader under `Assets/Eagle` or `Assets/Shardok`
|
||||
currently contains the scanned Built-in-era shader patterns.
|
||||
|
||||
| Shader | Patterns | Production References | Recommended Action |
|
||||
|---|---|---|---|
|
||||
| None | None | None | No active migration work. Re-run inventory after future rendering changes. |
|
||||
|
||||
## Referenced Package Shader
|
||||
|
||||
| Shader | Patterns | Production References | Recommended Action |
|
||||
|---|---|---|---|
|
||||
| `Assets/TextMesh Pro/Resources/Shaders/TMP_Sprite.shader` | `UnityCG.cginc`, `UnityUI.cginc`, `UNITY_MATRIX_` | 16 tutorial sprite assets under `Assets/Tutorial/Sprite Assets/` | Leave alone unless Unity/URP surfaces a visible TextMeshPro sprite regression. Prefer package/vendor updates over hand-editing TMP package shaders. |
|
||||
|
||||
## Third-Party And Package Hits Without Serialized Production References
|
||||
|
||||
These files still contain Built-in-only shader patterns, but the current GUID scan
|
||||
did not find serialized production references. Treat them as package/demo cleanup,
|
||||
not as urgent game-path blockers.
|
||||
|
||||
| Package Area | Examples | Patterns | Recommended Action |
|
||||
|---|---|---|---|
|
||||
| Polytope Studio characters/weapons/props | Modular NPC, armor, weapons, props shaders | `#pragma surface`, `UnityCG.cginc` | Keep runtime beast material compatibility in place; only import/replace vendor URP materials if we intentionally remove the compatibility shim. |
|
||||
| Polytope Studio environment/water | Water, vegetation, rock shaders | `GrabPass`, `#pragma surface`, `UnityCG.cginc`, tessellation | Ignore unless production starts referencing these assets. Water shaders are the only remaining `GrabPass` hits. |
|
||||
| RRFreelance Orc/Ogre | Ogre, armor, weapon shaders | `#pragma surface`, `UnityCG.cginc` | Current production references are handled by compatibility/material fixes; do not hand-convert unless a visible ogre regression returns. |
|
||||
| TextMeshPro package shaders | TMP SDF/bitmap/surface shaders | `UnityCG.cginc`, `UnityUI.cginc`, `UNITY_MATRIX_`, surface shader variants | Prefer package updates. Avoid editing package shaders unless a concrete TMP visual regression exists. |
|
||||
| Clown.fat and TileableBridgePack | Toony shaders, bridge cutout shader | `#pragma surface`, `UnityCG.cginc`, `UNITY_MATRIX_` | Leave as low priority unless a production reference appears. |
|
||||
|
||||
## Maintenance Guidance
|
||||
|
||||
Runtime visual QA for the URP migration was completed during playtesting and known
|
||||
regressions from that pass were fixed. Leave unreferenced third-party/package shaders
|
||||
alone until they have a visible regression or production reference.
|
||||
|
||||
Future rendering PRs should use `docs/URP_VISUAL_BASELINE_CHECKLIST.md` and include
|
||||
affected screenshots in an ignored local directory, not in git.
|
||||
@@ -1,122 +0,0 @@
|
||||
# URP Third-Party Asset Audit
|
||||
|
||||
Last refreshed: 2026-06-14
|
||||
|
||||
This audit scopes third-party visual packages after the completed URP migration. It
|
||||
is based on a GUID reference scan from production Unity assets under:
|
||||
|
||||
- `Assets/Scenes`
|
||||
- `Assets/Eagle`
|
||||
- `Assets/Shardok`
|
||||
- `Assets/ConnectionHandler`
|
||||
- `Assets/UI`
|
||||
- `Assets/common`
|
||||
- `Assets/Tutorial`
|
||||
- `Assets/Resources`
|
||||
- `Assets/Materials`
|
||||
|
||||
The scan maps GUIDs from each package folder, then finds production scenes,
|
||||
prefabs, materials, controllers, assets, and scripts that reference those GUIDs.
|
||||
It does not prove runtime code never loads assets by path, but it catches the
|
||||
serialized references that matter most for future render-pipeline maintenance.
|
||||
|
||||
## Summary
|
||||
|
||||
| Package | Production Use | URP Risk | Notes |
|
||||
|---|---:|---|---|
|
||||
| Polytope Studio | 20 refs / 18 assets | Medium | Used by Eagle human-type beast effects. Runtime beast-material compatibility converts active renderers to URP materials; production refs are character prefabs, not environment/water assets. |
|
||||
| GUI Pro Kit Fantasy RPG | 138 refs / 58 assets | Low | Production use is UI sprites/icons. No package shader dependency found in production refs. |
|
||||
| Modern UI Pack v4.2.0 | 23 refs / 6 assets | Low | Production use is UI textures/icons. |
|
||||
| RRFreelance Orc/Ogre | 2 refs / 2 assets | Medium | Ogre effect uses custom Built-in surface shaders in source assets, but active renderers are covered by runtime beast-material compatibility under URP. |
|
||||
| DungeonMonsters2D | 12 refs / 12 assets | Medium | 2D monster prefabs for Eagle beast effects. Expected to be mostly SpriteRenderer/Animator; keep covered in beast-effect visual checks. |
|
||||
| Animal pack deluxe v2 | 7 refs / 7 assets | Medium | Elephant effect and controller refs. Verify lit-material conversion. |
|
||||
| Animal pack deluxe | 24 refs / 24 assets | Medium | Multiple Eagle animal beast effects. Verify lit-material conversion. |
|
||||
| AfricaAnimalsPackLowPoly V2 | 10 refs / 10 assets | Medium | Eagle beast effects and controllers. Verify lit-material conversion. |
|
||||
| AfricaAnimalsPackv1 | 8 refs / 8 assets | Medium | Eagle beast effects and controllers. Verify lit-material conversion. |
|
||||
| AustraliaAnimalsPackv1 | 6 refs / 6 assets | Medium | Eagle beast effects and controllers. Verify lit-material conversion. |
|
||||
| DinoPackLowPolyV1 | 3 refs / 3 assets | Medium | Velociraptor effect and controller. Verify lit-material conversion. |
|
||||
| HEROIC FANTASY CREATURES FULL PACK Vol 2 | 12 refs / 12 assets | Medium | Hippogryph effect and controller. Verify lit-material conversion. |
|
||||
| TileableBridgePack | 0 refs | Low | No production serialized refs found. Shardok bridge visuals appear to use project-owned/runtime assets instead. |
|
||||
| Stylize Water Texture | 1 ref / 1 asset | Low | Eagle province map material uses one water texture, not a shader. |
|
||||
| Terrain Hexes | 2 refs / 1 asset | Low | Shardok scene/container use one rock texture. |
|
||||
| Hex Tiles | 0 refs | Low | No production serialized refs found. |
|
||||
| 4000_Fantasy_Icons | 273 refs / 117 assets | Low | Sprite/icon assets only. |
|
||||
| StrategyGameIcons | 21 refs / 12 assets | Low | Sprite/icon assets only. |
|
||||
| Clown.fat | 14 refs / 10 assets | Medium | Clown effects and animation clips; also retargeted by Polytope human effects. |
|
||||
| Dragon | 2 refs / 1 asset | Medium | Dragon effect mesh/animation. Verify material conversion. |
|
||||
| HoneyBadger | 2 refs / 1 asset | Medium | Honey badger effect mesh/animation. Verify material conversion. |
|
||||
| Raccoon | 2 refs / 1 asset | Medium | Raccoon effect mesh/animation. Verify material conversion. |
|
||||
|
||||
## Runtime Beast Findings
|
||||
|
||||
### Polytope Studio
|
||||
|
||||
Production assets reference Polytope character prefabs for Eagle map effects:
|
||||
|
||||
- `Assets/Eagle/Effects/ArcherEffect.prefab`
|
||||
- `Assets/Eagle/Effects/GiantEffect.prefab`
|
||||
- `Assets/Eagle/Effects/KnightEffect.prefab`
|
||||
- `Assets/Eagle/Effects/MilitiaEffect.prefab`
|
||||
- `Assets/Eagle/Effects/PeasantEffect.prefab`
|
||||
- `Assets/Eagle/Effects/SoldierEffect.prefab`
|
||||
|
||||
These prefabs ultimately use `PT_NPC_Mat`, which references
|
||||
`Polytope Studio/Lowpoly_Characters/Sources/Modular_NPC/Shaders/PT_Modular_NPC_Shader_PBR.shader`.
|
||||
That shader uses Built-in surface shader generation and is not URP-ready as-is.
|
||||
At runtime, Eagle beast effects pass their renderers through
|
||||
`BeastMaterialCompatibility.ConfigureRenderer`, which replaces non-URP materials
|
||||
with URP/Lit materials and bakes Polytope palette textures when needed. Treat this
|
||||
as covered by the compatibility path unless a future visual regression appears.
|
||||
|
||||
Polytope environment and water shaders still exist in the project and include
|
||||
Built-in-only features such as `GrabPass`, `UnityCG.cginc`, surface shaders, and
|
||||
tessellation. However, this scan found no production serialized references to the
|
||||
Polytope environment or water assets. Treat them as demo/package risk, not current
|
||||
runtime risk, unless a later inventory run finds production references.
|
||||
|
||||
### RRFreelance Orc/Ogre
|
||||
|
||||
Production assets reference the Orc/Ogre package from:
|
||||
|
||||
- `Assets/Eagle/Effects/OgreEffect.prefab`
|
||||
- `Assets/Eagle/Effects/OgreMapAnims.controller`
|
||||
|
||||
The custom materials reference package shaders that use Built-in surface shader
|
||||
generation in the source assets:
|
||||
|
||||
- `RRFreelance-Characters/Orc-Ogre/Shaders/OgreShader.shader`
|
||||
- `RRFreelance-Characters/Orc-Ogre/Shaders/ArmorShader2sided.shader`
|
||||
- `RRFreelance-Characters/Orc-Ogre/Shaders/WeaponShader.shader`
|
||||
|
||||
At runtime, the Eagle monster-effect path also uses
|
||||
`BeastMaterialCompatibility.ConfigureRenderer`, so these renderers should be
|
||||
left on the compatibility path unless the Ogre effect becomes pink, dark, or
|
||||
otherwise visibly broken.
|
||||
|
||||
## Maintenance Guidance
|
||||
|
||||
The URP migration is complete; do not convert third-party packages speculatively.
|
||||
|
||||
1. Keep the runtime beast-effect compatibility path covered by existing tests and
|
||||
future visual checks when beast rendering changes.
|
||||
2. If compatibility leaves a beast pink, dark, or visibly broken, replace the affected
|
||||
materials with URP/Lit materials before touching unused demo-only shader packs.
|
||||
3. Leave Polytope water/vegetation/environment conversion until a production
|
||||
reference appears. They are noisy in the shader inventory but not currently on
|
||||
the game path.
|
||||
4. Include Eagle beast effects in the visual baseline. They cover most third-party
|
||||
3D package risk in one place.
|
||||
5. Treat UI sprite packs as low pipeline risk. Their risk is texture import/render
|
||||
ordering, not shader conversion.
|
||||
|
||||
## Vendor Version / Access Notes
|
||||
|
||||
Known versions from existing repo docs:
|
||||
|
||||
- Modern UI Pack: `v4.2.0`
|
||||
|
||||
Most other package versions are not recorded in repo-local metadata. Before hand
|
||||
converting large third-party shader sets, check the Unity Asset Store or vendor
|
||||
account for URP-compatible updates. This especially matters for Polytope Studio,
|
||||
because the package contains many Built-in surface shaders even though only a
|
||||
small character subset is currently used by production assets.
|
||||
@@ -1,61 +0,0 @@
|
||||
# URP Visual Baseline Checklist
|
||||
|
||||
Use this checklist before any future branch changes render-pipeline settings, custom
|
||||
shaders, materials, lighting, camera layering, or render order. The URP migration is
|
||||
complete; this checklist is now a maintenance tool. The goal is not exhaustive
|
||||
gameplay QA, but a stable set of visual states that can be compared by eye after
|
||||
rendering changes.
|
||||
|
||||
Save screenshots under:
|
||||
|
||||
`src/main/csharp/net/eagle0/clients/unity/eagle0/Temp/urp-baselines/`
|
||||
|
||||
That directory is ignored by git.
|
||||
|
||||
Use `Eagle0 > Rendering > Capture URP Baseline Screenshot` in the Unity editor to
|
||||
write a timestamped Game view capture to that directory.
|
||||
|
||||
## Capture Setup
|
||||
|
||||
- Use the same Unity version as the branch under test.
|
||||
- Start from `Assets/Scenes/Main.unity`.
|
||||
- Use the same Game view resolution for every capture in a comparison set.
|
||||
- Capture the full Game view, not cropped panels.
|
||||
- Name files with a stable prefix and short state name, for example
|
||||
`main_01_connection_accounts.png` and `branch_01_connection_accounts.png`.
|
||||
- If a view depends on server data or an account, write a short note next to the
|
||||
screenshot naming the game/account/state used.
|
||||
|
||||
## Required Captures
|
||||
|
||||
| ID | Area | State | Must Verify |
|
||||
|---|---|---|---|
|
||||
| 01 | Connection | Connect panel with stored accounts visible | Background color, panel layout, text contrast, profession icons |
|
||||
| 02 | Connection | Running games list visible | Row icons, timestamps, buttons, selected/hover states if practical |
|
||||
| 03 | Eagle map | Normal province map after joining a game | Province colors, borders, ocean animation frame, faction highlights |
|
||||
| 04 | Eagle map | Province hovered/selected | On-map province tooltip, selected outline, side-panel readability |
|
||||
| 05 | Eagle weather | Drought province | Heat shimmer/drought signal remains visible and does not obscure UI |
|
||||
| 06 | Eagle weather | Flood/rain or blizzard province | Weather overlay color, particle ordering, map readability |
|
||||
| 07 | Eagle beasts | Beast effect on map | Beast animation visible in Game view and positioned over province |
|
||||
| 08 | Eagle beasts | Beast notification open | Animated beast visible in notification and not hidden behind panel art |
|
||||
| 09 | Eagle UI | Command panel with generated text | Text glyphs, fallback glyphs, panel clipping, button states |
|
||||
| 10 | Eagle UI | Settings panel open | Top-right settings button, Escape behavior, modal layering |
|
||||
| 11 | Shardok setup | Initial placement | Terrain tiles, grid lines, unit labels/icons, placement overlays |
|
||||
| 12 | Shardok battle | Unit selected with destination overlays | Overlay labels/icons draw above terrain, bridges, fires, and highlights |
|
||||
| 13 | Shardok battle | Bridge and fire visible near units | Bridge/fire render order does not cover unit labels/icons |
|
||||
| 14 | Shardok battle | Command buttons visible | Right-side UX, disabled/enabled button states, tooltip readability |
|
||||
| 15 | Scene transition | Eagle to Shardok to Eagle | No black/blank frame persists, returning Eagle map is visually intact |
|
||||
|
||||
## Acceptance Notes
|
||||
|
||||
For each rendering follow-up, record:
|
||||
|
||||
- Unity version and branch name.
|
||||
- Screenshot directory.
|
||||
- Any pink/missing materials.
|
||||
- Any invisible UI, labels, particle effects, bridges, fires, or weather effects.
|
||||
- Any render-order differences, especially Shardok overlays and Eagle beast effects.
|
||||
- Whether the difference is acceptable, needs shader work, or needs asset/material work.
|
||||
|
||||
Do not merge a rendering branch until the affected Connection, Eagle, Shardok, or
|
||||
Settings baseline captures are visually acceptable.
|
||||
Binary file not shown.
@@ -1,40 +1,38 @@
|
||||
module github.com/nolen777/eagle0
|
||||
|
||||
go 1.25.0
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.26.4
|
||||
toolchain go1.25.11
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.25
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.103.3
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.8
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.10
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.51
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
|
||||
golang.org/x/sys v0.46.0
|
||||
google.golang.org/grpc v1.81.1
|
||||
google.golang.org/protobuf v1.36.11
|
||||
golang.org/x/sys v0.33.0
|
||||
google.golang.org/grpc v1.71.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.2.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 // indirect
|
||||
github.com/aws/smithy-go v1.27.2 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.6 // indirect
|
||||
github.com/aws/smithy-go v1.22.1 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
)
|
||||
|
||||
@@ -1,184 +1,66 @@
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.8 h1:cZV+NUS/eGxKXMtmyhtYPJ7Z4YLoI/V8bkTdRZfYhGo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.8/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 h1:p1BBrg/Hhp6uK7zpejeI8QFXHJeC/mynzi04Sl03k9g=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13/go.mod h1:8cIfkE9MDhkRZGpQ22aV6/lkYeYSozpz16Smrs5x4Ls=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.10 h1:fKODZHfqQu06pCzR69KJ3GuttraRJkhlC8g80RZ0Dfg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.10/go.mod h1:PvdxRYZ5Um9QMq9PQ0zHHNdtKK+he2NHtFCUFMXWXeg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.25 h1:ACCejvStYoilgwrfegSt5ZntCbPrk52qfwyNcnl3omM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.25/go.mod h1:LJyU8sDRbXUxFn8xMJIGP+v9QYYwveNLI8a/giAOiAs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.51 h1:F/9Sm6Y6k4LqDesZDPJCLxQGXNNHd/ZtJiWd0lCZKRk=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.51/go.mod h1:TKbzCHm43AoPyA+iLGGcruXd4AFhF8tOmLex2R9jWNQ=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24 h1:2hQqYCV9yqyePQ9o6dCrZc/zO8U3TwPr9mIKlZnPu/I=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24/go.mod h1:IDwpACtwqHLISdzfwUUNq4P9DsB/h5BLg4FwJPNfqFY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23 h1:IBAoD/1d8A8/1aA8g4MBVtTRHhXRiNAgwdbo/xRM2DI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23/go.mod h1:vfENuCM7dofkgKpYzuzf1VT1UKkA/YL3qanfBn7HCaA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 h1:r6qZHbT+wxgWO/e9vYNUEtg7lv5+UN3pRqKhLXvnArg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29/go.mod h1:QRnaRcTVGKPGRy8w78HMQtKUGRYcnMZAANATkeVA6Mo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 h1:jSJjSBzw8VDIbWv+mmvBSP8ezsztMYJGH+eKqi9AmNs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27/go.mod h1:/DAhLbFRgwhmvJdOfSm+WwikZrCuUJiA4WgJG0fTNSw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 h1:l+X4K77Dui85pIj5foXDhPlnqcNRG2QUyvca300lXh8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27/go.mod h1:KvZXSFEXm6x84yE8qffKvT3x8J5clWnVFXphpohhzJ8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.27 h1:AmB5QxnD+fBFrg9LcqzkgF/CaYvMyU/BTlejG4t1S7Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.27/go.mod h1:Sai7P3xTiyv9ZUYO3IFxMnmiIP759/67iQbU4kdmkyU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 h1:VTGy885W5DKBxWRUJbym9hytNaYzsyaPkCHGRRMAOhU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30/go.mod h1:AS0HycUvJRFvTt613AYDOgO2jzw+00cVSMny8XB3yMY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 h1:ZD2+BSw9vFsNlKYIasSNt3uDbjqqXIBcM13UJv/Lx2k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12/go.mod h1:Ms4zlcVBbXbiP7EVLhl+lgjvA/a7YphqQ3Ih3174EmI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.8 h1:iwYS40JnrBeA9e9aI5S6KKN4EB2zR4iUVYN0nwVivz4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.8/go.mod h1:Fm9Mi+ApqmFiknZtGpohVcBGvpTu542VC4XO9YudRi0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 h1:V51LGlOq/1VsDsHUdoklAQi7rMmx4qQubvFYAlP2254=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22/go.mod h1:4Pzhyz8hJOm2bepgl+NjvRx8vlUFAIIvJnZ/MkcNPpU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8 h1:cWno7lefSH6Pp+mSznagKCgfDGeZRin66UvYUqAkyeA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8/go.mod h1:tPD+VjU3ABTBoEJ3nctu5Nyg4P4yjqSH5bJGGkY4+XE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 h1:DRebniUGZ2MqiiIVmQJ04vIXr918hubdHMnarSLEWyU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29/go.mod h1:LfRkPCD8YHDM2E5eTkos2UpwYeZnBcVarTa8L59bJHA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.8 h1:/Mn7gTedG86nbpjT4QEKsN1D/fThiYe1qvq7WsBGNHg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.8/go.mod h1:Ae3va9LPmvjj231ukHB6UeT8nS7wTPfC3tMZSZMwNYg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29 h1:hiME6pBzC7OTl9LMtlyTWBuEl1f4QBcUmFDKC7MLXtc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29/go.mod h1:G7RP+uhagpKtKhd1BM9N6JQqjCcGEU47K5lBVZQyRQw=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2 h1:a7aQ3RW+ug4IbhoQp29NZdc7vqrzKZZfWZSaQAXOZvQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2/go.mod h1:xMekrnhmJ5aqmyxtmALs7mlvXw5xRh+eYjOjvrIIFJ4=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.103.3 h1:JRseEu/vIDMaWis4bSw0QbXL+cvIGc1XnX076H5ZXLE=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.103.3/go.mod h1:77ZAgynvx1txMvDG8gGWoWkO1augYDxkp9JElWFgjQU=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.2.0 h1:3nXpRcFwRCW8n7HgO2QGy0Dc20eQNfBuUemGQhpF8m8=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.2.0/go.mod h1:LxYujSTLPRlp2vTtcUO/+1ilrew8ytt6SvQyOgejzFQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.9 h1:YqtxripbjWb2QLyzRK9pByfEDvgg95gpC2AyDq4hFE8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.9/go.mod h1:lV8iQpg6OLOfBnqbGMBKYjilBlf633qwHnBEiMSPoHY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 h1:ey1XLTYXb9PcLt4535632o5kCGXNXEhNb620Dqwuylo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3/go.mod h1:Lk7PlmoTYryQmyBG0EXqj5BcUbj3whXdU2s3yGI3EAc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8 h1:6dBT1Lz8fK11m22R+AqfRsFn8320K0T5DTGxxOQBSMw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8/go.mod h1:/kiBvRQXBc6xeJTYzhSdGvJ5vm1tjaDEjH+MSeRJnlY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 h1:yLr03zQE/5Eu5l3QU0Si+xMbLMbSDF2YXsigqXngs6g=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6/go.mod h1:Q5N6icH+KJZDLh+ESNwzdv6cZ6vLFF/egy3IOxWhmz4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.6 h1:VwhTrsTuVn52an4mXx29PqRzs2Dvu921NpGk7y43tAM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.6/go.mod h1:+8h7PZb3yY5ftmVLD7ocEoE98hdc8PoKS0H3wfx1dlc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 h1:VrIhKRCSK1umelSgB9RghvA9RTUYeQffyAS5ApXehNI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3/go.mod h1:r8wkDOuLaaMFqFiYAb8dGY2A3gJCOujMc6CFOVC4Zhc=
|
||||
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
|
||||
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/aws/smithy-go v1.27.1 h1:4T340VFndXtADGF52gYa1POyL7s9E4Z1OeZ1hCscIw8=
|
||||
github.com/aws/smithy-go v1.27.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk=
|
||||
github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=
|
||||
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad h1:3iLyITS/sySRwbUKoC7ogfj2Yr1Cjs0pfaRKj5U5HEw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:KdNqO+rCIWgFumrNBSEDlDNrkrQnpkax7Tv1WxNY8V4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad h1:45WmJvIV6C2+O/jjLkPUH+F3aOj/1miDoU2DD0+NWbg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
|
||||
+140
-118
@@ -13,8 +13,10 @@
|
||||
"com.google.code.gson:gson": 579810017,
|
||||
"com.google.errorprone:error_prone_annotations": 950460708,
|
||||
"com.google.guava:failureaccess": 1582410452,
|
||||
"com.google.guava:guava": 1001533771,
|
||||
"com.google.guava:guava": 582798237,
|
||||
"com.google.j2objc:j2objc-annotations": 2003271689,
|
||||
"com.google.protobuf:protobuf-java": -1795397632,
|
||||
"com.google.protobuf:protobuf-java-util": -1033086717,
|
||||
"com.google.re2j:re2j": 1525409503,
|
||||
"com.google.s2a.proto.v2:s2a-proto": 898932763,
|
||||
"com.google.truth:truth": 1696781452,
|
||||
@@ -63,13 +65,13 @@
|
||||
"org.slf4j:slf4j-simple": 1999565066,
|
||||
"org.xerial:sqlite-jdbc": 1165654128,
|
||||
"repositories": 514854214,
|
||||
"software.amazon.awssdk:aws-core": 578934204,
|
||||
"software.amazon.awssdk:http-client-spi": 1408046502,
|
||||
"software.amazon.awssdk:regions": -746327146,
|
||||
"software.amazon.awssdk:s3": -1435226835,
|
||||
"software.amazon.awssdk:s3-transfer-manager": 1723315567,
|
||||
"software.amazon.awssdk:sdk-core": -371247329,
|
||||
"software.amazon.awssdk:utils": -405122936
|
||||
"software.amazon.awssdk:aws-core": -101124166,
|
||||
"software.amazon.awssdk:http-client-spi": 727988132,
|
||||
"software.amazon.awssdk:regions": -1426385516,
|
||||
"software.amazon.awssdk:s3": -2115285205,
|
||||
"software.amazon.awssdk:s3-transfer-manager": 1043257197,
|
||||
"software.amazon.awssdk:sdk-core": -1051305699,
|
||||
"software.amazon.awssdk:utils": -1085181306
|
||||
},
|
||||
"__RESOLVED_ARTIFACTS_HASH": {
|
||||
"com.amazonaws:aws-lambda-java-core": -56323966,
|
||||
@@ -85,22 +87,23 @@
|
||||
"com.google.api.grpc:proto-google-common-protos": 1506674904,
|
||||
"com.google.api:api-common": 1729001290,
|
||||
"com.google.auth:google-auth-library-credentials": 563614471,
|
||||
"com.google.auth:google-auth-library-oauth2-http": -1252786723,
|
||||
"com.google.auth:google-auth-library-oauth2-http": -1424193143,
|
||||
"com.google.auto.value:auto-value": -1399392164,
|
||||
"com.google.auto.value:auto-value-annotations": 1092025564,
|
||||
"com.google.code.findbugs:jsr305": -1038659426,
|
||||
"com.google.code.gson:gson": 592204959,
|
||||
"com.google.errorprone:error_prone_annotations": 1213248887,
|
||||
"com.google.guava:failureaccess": -56291903,
|
||||
"com.google.guava:guava": 2119270017,
|
||||
"com.google.guava:guava": 1970967483,
|
||||
"com.google.guava:listenablefuture": 1588902908,
|
||||
"com.google.http-client:google-http-client": 1756875461,
|
||||
"com.google.http-client:google-http-client-gson": 1267916085,
|
||||
"com.google.http-client:google-http-client": -136457690,
|
||||
"com.google.http-client:google-http-client-gson": -1055701869,
|
||||
"com.google.j2objc:j2objc-annotations": -2074422376,
|
||||
"com.google.protobuf:protobuf-java": -1065767575,
|
||||
"com.google.protobuf:protobuf-java-util": 675279053,
|
||||
"com.google.re2j:re2j": -461460138,
|
||||
"com.google.s2a.proto.v2:s2a-proto": 833783597,
|
||||
"com.google.truth:truth": 1846223773,
|
||||
"com.google.s2a.proto.v2:s2a-proto": 1108303781,
|
||||
"com.google.truth:truth": 1427899897,
|
||||
"com.lihaoyi:fansi_2.13": -224971606,
|
||||
"com.lihaoyi:sourcecode_2.13": 2048976,
|
||||
"com.nimbusds:nimbus-jose-jwt": -1763138105,
|
||||
@@ -114,7 +117,7 @@
|
||||
"com.thesamet.scalapb:protoc-bridge_3": -1984150046,
|
||||
"com.thesamet.scalapb:protoc-gen_3": 1860775213,
|
||||
"com.thesamet.scalapb:scalapb-json4s_3": 1579843487,
|
||||
"com.thesamet.scalapb:scalapb-runtime-grpc_3": 97373115,
|
||||
"com.thesamet.scalapb:scalapb-runtime-grpc_3": 580120277,
|
||||
"com.thesamet.scalapb:scalapb-runtime_3": 818778125,
|
||||
"com.typesafe:config": -1791448454,
|
||||
"commons-codec:commons-codec": 1377992676,
|
||||
@@ -133,11 +136,11 @@
|
||||
"io.github.alexarchambault:concurrent-reference-hash-map": -1702289188,
|
||||
"io.github.alexarchambault:is-terminal": 357931298,
|
||||
"io.github.java-diff-utils:java-diff-utils": 1636095913,
|
||||
"io.grpc:grpc-api": -947568169,
|
||||
"io.grpc:grpc-context": -22548221,
|
||||
"io.grpc:grpc-protobuf": -1259537716,
|
||||
"io.grpc:grpc-protobuf-lite": -1244666196,
|
||||
"io.grpc:grpc-stub": -1164705340,
|
||||
"io.grpc:grpc-api": -1732683013,
|
||||
"io.grpc:grpc-context": -862685412,
|
||||
"io.grpc:grpc-protobuf": -793298635,
|
||||
"io.grpc:grpc-protobuf-lite": -766127633,
|
||||
"io.grpc:grpc-stub": -1619717353,
|
||||
"io.netty:netty-buffer": -870582473,
|
||||
"io.netty:netty-codec": -1546709832,
|
||||
"io.netty:netty-codec-http": 1825361781,
|
||||
@@ -158,9 +161,9 @@
|
||||
"io.netty:netty-transport-classes-epoll": 704589083,
|
||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64": -2050990961,
|
||||
"io.netty:netty-transport-native-unix-common": -2090924105,
|
||||
"io.opencensus:opencensus-api": -1002511030,
|
||||
"io.opencensus:opencensus-contrib-grpc-metrics": -1797408624,
|
||||
"io.opencensus:opencensus-contrib-http-util": -541858674,
|
||||
"io.opencensus:opencensus-api": 709780868,
|
||||
"io.opencensus:opencensus-contrib-grpc-metrics": 576725745,
|
||||
"io.opencensus:opencensus-contrib-http-util": -1406323089,
|
||||
"io.perfmark:perfmark-api": -752483303,
|
||||
"io.sentry:sentry": 1564972879,
|
||||
"javax.activation:javax.activation-api": -217704356,
|
||||
@@ -238,43 +241,44 @@
|
||||
"org.virtuslab.scala-cli:config_2.13": -113493958,
|
||||
"org.virtuslab.scala-cli:specification-level_2.13": -176416420,
|
||||
"org.xerial:sqlite-jdbc": 532995836,
|
||||
"software.amazon.awssdk:annotations": 998747622,
|
||||
"software.amazon.awssdk:apache5-client": -1074889369,
|
||||
"software.amazon.awssdk:arns": -343721679,
|
||||
"software.amazon.awssdk:auth": -2029384821,
|
||||
"software.amazon.awssdk:aws-core": -972563193,
|
||||
"software.amazon.awssdk:aws-query-protocol": -1912351735,
|
||||
"software.amazon.awssdk:aws-xml-protocol": -1468671355,
|
||||
"software.amazon.awssdk:checksums": 402393687,
|
||||
"software.amazon.awssdk:checksums-spi": 291168436,
|
||||
"software.amazon.awssdk:crt-core": -188683876,
|
||||
"software.amazon.awssdk:endpoints-spi": 1744208155,
|
||||
"software.amazon.awssdk:http-auth": 1606749494,
|
||||
"software.amazon.awssdk:http-auth-aws": 2126373059,
|
||||
"software.amazon.awssdk:http-auth-aws-eventstream": 460259189,
|
||||
"software.amazon.awssdk:http-auth-spi": -453223244,
|
||||
"software.amazon.awssdk:http-client-spi": 1562372132,
|
||||
"software.amazon.awssdk:identity-spi": -746538651,
|
||||
"software.amazon.awssdk:json-utils": 437812378,
|
||||
"software.amazon.awssdk:metrics-spi": -1546848631,
|
||||
"software.amazon.awssdk:netty-nio-client": -1338701602,
|
||||
"software.amazon.awssdk:profiles": -182531104,
|
||||
"software.amazon.awssdk:protocol-core": -1345470077,
|
||||
"software.amazon.awssdk:regions": -1834663820,
|
||||
"software.amazon.awssdk:retries": -1413373007,
|
||||
"software.amazon.awssdk:retries-spi": 1300004784,
|
||||
"software.amazon.awssdk:s3": 615621373,
|
||||
"software.amazon.awssdk:s3-transfer-manager": -1895735125,
|
||||
"software.amazon.awssdk:sdk-core": 463109330,
|
||||
"software.amazon.awssdk:third-party-jackson-core": -957610333,
|
||||
"software.amazon.awssdk:utils": -972192996,
|
||||
"software.amazon.awssdk:utils-lite": 1790983243,
|
||||
"software.amazon.awssdk:annotations": 265461227,
|
||||
"software.amazon.awssdk:apache5-client": -35839286,
|
||||
"software.amazon.awssdk:arns": 1467380022,
|
||||
"software.amazon.awssdk:auth": 810405042,
|
||||
"software.amazon.awssdk:aws-core": -314904404,
|
||||
"software.amazon.awssdk:aws-query-protocol": -1985454850,
|
||||
"software.amazon.awssdk:aws-xml-protocol": 1737917993,
|
||||
"software.amazon.awssdk:checksums": 1310517997,
|
||||
"software.amazon.awssdk:checksums-spi": 1399080780,
|
||||
"software.amazon.awssdk:crt-core": -1945112513,
|
||||
"software.amazon.awssdk:endpoints-spi": 1609685570,
|
||||
"software.amazon.awssdk:http-auth": -818458676,
|
||||
"software.amazon.awssdk:http-auth-aws": 1242479472,
|
||||
"software.amazon.awssdk:http-auth-aws-eventstream": -1313563447,
|
||||
"software.amazon.awssdk:http-auth-spi": 1886317627,
|
||||
"software.amazon.awssdk:http-client-spi": -1309520306,
|
||||
"software.amazon.awssdk:identity-spi": -1635986660,
|
||||
"software.amazon.awssdk:json-utils": -1751579383,
|
||||
"software.amazon.awssdk:metrics-spi": -873062065,
|
||||
"software.amazon.awssdk:netty-nio-client": 43577390,
|
||||
"software.amazon.awssdk:profiles": 99667206,
|
||||
"software.amazon.awssdk:protocol-core": -1863122418,
|
||||
"software.amazon.awssdk:regions": -1272622788,
|
||||
"software.amazon.awssdk:retries": 1885379009,
|
||||
"software.amazon.awssdk:retries-spi": -1748788271,
|
||||
"software.amazon.awssdk:s3": 1927677616,
|
||||
"software.amazon.awssdk:s3-transfer-manager": 1781977356,
|
||||
"software.amazon.awssdk:sdk-core": -2145297823,
|
||||
"software.amazon.awssdk:third-party-jackson-core": 597754902,
|
||||
"software.amazon.awssdk:utils": 1158804846,
|
||||
"software.amazon.awssdk:utils-lite": 1105261816,
|
||||
"software.amazon.eventstream:eventstream": 1951806649
|
||||
},
|
||||
"conflict_resolution": {
|
||||
"com.google.api.grpc:proto-google-common-protos:2.63.1": "com.google.api.grpc:proto-google-common-protos:2.64.1",
|
||||
"com.google.guava:failureaccess:1.0.1": "com.google.guava:failureaccess:1.0.3",
|
||||
"com.google.j2objc:j2objc-annotations:2.8": "com.google.j2objc:j2objc-annotations:3.1",
|
||||
"com.google.protobuf:protobuf-java:4.27.2": "com.google.protobuf:protobuf-java:4.35.0",
|
||||
"io.netty:netty-transport-native-unix-common:4.1.127.Final": "io.netty:netty-transport-native-unix-common:4.1.135.Final",
|
||||
"io.opencensus:opencensus-api:0.31.0": "io.opencensus:opencensus-api:0.31.1",
|
||||
"org.codehaus.mojo:animal-sniffer-annotations:1.26": "org.codehaus.mojo:animal-sniffer-annotations:1.27"
|
||||
@@ -402,9 +406,9 @@
|
||||
},
|
||||
"com.google.guava:guava": {
|
||||
"shasums": {
|
||||
"jar": "dc573e1fca4fd5454f4a5fd3d7da2df03002876a4175bafc14a95980dd7713b3"
|
||||
"jar": "9532ca9ba8aceb4cd2067171f981c799988b7f5844e3e274811a3bb757034bc4"
|
||||
},
|
||||
"version": "33.6.0-jre"
|
||||
"version": "33.6.0-android"
|
||||
},
|
||||
"com.google.guava:listenablefuture": {
|
||||
"shasums": {
|
||||
@@ -436,6 +440,12 @@
|
||||
},
|
||||
"version": "4.35.0"
|
||||
},
|
||||
"com.google.protobuf:protobuf-java-util": {
|
||||
"shasums": {
|
||||
"jar": "a2665294d3e4675482bde593df8283f8c965f0207785e8e9b223f790644f5b08"
|
||||
},
|
||||
"version": "4.27.2"
|
||||
},
|
||||
"com.google.re2j:re2j": {
|
||||
"shasums": {
|
||||
"jar": "7b52c72156dd7f98b3237a5b35c1d34fba381b21048c89208913ad80a45dfbd7"
|
||||
@@ -1253,189 +1263,189 @@
|
||||
},
|
||||
"software.amazon.awssdk:annotations": {
|
||||
"shasums": {
|
||||
"jar": "4d958c5c96d7e6b33c99a62f25459f42e1358e5ff1389b49f17445734b4f093e"
|
||||
"jar": "0e66de12a3ed381780f586fff21a3d8cbbee34f7a750d591e6808f0eb7ff11d7"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:apache5-client": {
|
||||
"shasums": {
|
||||
"jar": "63e3c1c5eb9ad2810e63b518f1bb72aa50fb595ca9320ebf4557b2ac7193b7fa"
|
||||
"jar": "fc2686aa92371ddce1588ae6abe5da94e85659c19d37788505e203a839a502f1"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:arns": {
|
||||
"shasums": {
|
||||
"jar": "5276d27bc7c3e68f73486dd99426eaba7905953dd00476dde53358d77c87b559"
|
||||
"jar": "43bcdc92354d04bfbc7c0146a2142e7441da765b5a94f53cb042dcc639b41746"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:auth": {
|
||||
"shasums": {
|
||||
"jar": "a40a57c6bed6a549247d7a80e739547443928b84db872618bbb0625e909f3118"
|
||||
"jar": "d243b7d35e9f4359379327893615493f29951e65a0f9b2877dc57ef802cb5d43"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:aws-core": {
|
||||
"shasums": {
|
||||
"jar": "b4648b2ca40b6a6d60224be45ff887b4859b594380d58c4d8d52f91610a9dd08"
|
||||
"jar": "3a11acee17918f0748902e8ce3361cecaba3cdd7c8bd938fcf3f215f0af72b8e"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:aws-query-protocol": {
|
||||
"shasums": {
|
||||
"jar": "5c16146da08e5f8dd810c9606bab14b0e3a0588d89013f9c2e0a06242f7e3f86"
|
||||
"jar": "13bea147a77a7ca1b401e57f1a957821fbdef21366854bfff028922272ef66b6"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:aws-xml-protocol": {
|
||||
"shasums": {
|
||||
"jar": "98d67746d464aa0e9b6e011746b85acaff294cbe114ed3e978e94c6bac52c0f6"
|
||||
"jar": "17c45c621eb8d09bb1d3d8300ff71ab47285247d7be7f0e7f42ed1db5b59e9eb"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:checksums": {
|
||||
"shasums": {
|
||||
"jar": "6f94e74f1d9aba3d4b92d45e22cd231480133d0a88775bde0db09bfb9ed6b609"
|
||||
"jar": "6ff01216cb086fe5d49e8e65130dacdb85ab45d0f75236b902f3c22f3b855dde"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:checksums-spi": {
|
||||
"shasums": {
|
||||
"jar": "9c87e9fe06653d05f487f1521958e4ea37033e150ec678bc988beab1ba4453a1"
|
||||
"jar": "d3b0e2f03b42a2f243bb3b98fc5947fd91df6e4c3613eb144da9be7296c51d07"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:crt-core": {
|
||||
"shasums": {
|
||||
"jar": "6fef98d96273ebc24d6cc62a36c94a2994f5d146551156f32500eac0b4baed52"
|
||||
"jar": "f30033d6d5e70d05baeb23bd6e02136a48a82c0bca43809345bda8cca5de1eba"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:endpoints-spi": {
|
||||
"shasums": {
|
||||
"jar": "00fee4c88523c977ed485ae3d4d1fec6834ed359da9223745e628cb356060b8d"
|
||||
"jar": "0a182c0c57711a25894489b41a64cb6db39d9e3e238d2612028fe8e8215a0260"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:http-auth": {
|
||||
"shasums": {
|
||||
"jar": "c92579221cf4c3e17e189a6e64608903a68fd8368f0c65a5976a0a5286f4d3d2"
|
||||
"jar": "cfb65870787413dc20585c12be24988fdf3860940e29ed4a9abbf2429c75ba70"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:http-auth-aws": {
|
||||
"shasums": {
|
||||
"jar": "e8e306d86d0038f07dcde68d1bcff1c1fceeb88589cbe40700471855298aeefd"
|
||||
"jar": "7db13526c33499bbfff15aa4e322140cf4506b338c74443debf8b0b6f0a4e49a"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:http-auth-aws-eventstream": {
|
||||
"shasums": {
|
||||
"jar": "c1ddff7616b9d12658871b5caf058c12ab4f5778ac52fa57f89a97b78b460206"
|
||||
"jar": "d44351439fbd6cab242f3668a717fee893432bee2e7fe847361bf101eab07290"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:http-auth-spi": {
|
||||
"shasums": {
|
||||
"jar": "96727d540da477fcd2025ad35cdbd5c2274adecc722633d7eaf72bba084d1fca"
|
||||
"jar": "db8ce223e673f25560feae07239164521b407aae6e7813f46f0f9184611b20a5"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:http-client-spi": {
|
||||
"shasums": {
|
||||
"jar": "df32c5ee9e8df76393c0c870b7179bbaeb5ffda98ffb2cc3090a7a9ab2fb76d9"
|
||||
"jar": "6ac595a06a587c3aa5043337622913c267b6fb6d8703ee9941b33ba956d6d9b0"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:identity-spi": {
|
||||
"shasums": {
|
||||
"jar": "757fa46994a020a179b5774521d273f9fecb62d30749f715a22fc4cd1609fbb6"
|
||||
"jar": "04037b0af50aa081b7af15cbe6436cc5a13de38918a195447f48d9e9ff69ef6f"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:json-utils": {
|
||||
"shasums": {
|
||||
"jar": "473961ab15697d6aedd234d4b458014190fe48f568ffb2622d2e66c864f7f2bd"
|
||||
"jar": "87cc4f9e28b6f25cb17560dab8b880dd6434697fd14eedce1b75b616c34e67a3"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:metrics-spi": {
|
||||
"shasums": {
|
||||
"jar": "340fb3b0e8453bedfe09e20f5207539325529c3878a8aaf7b5969aaee37db628"
|
||||
"jar": "11cb8851d2606b49b247f99bced6ee1badbf0bade142a716d4b40dfb0535c437"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:netty-nio-client": {
|
||||
"shasums": {
|
||||
"jar": "147defd5c892787a52c40614a803b3fd44f960df82efb50d6d6f185524e29d96"
|
||||
"jar": "8f631a70708510ad38655ed6c097e4ed0e5f5ee442c13c855cab8d7b54dbae0f"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:profiles": {
|
||||
"shasums": {
|
||||
"jar": "170bdbaa9d2221e3a36dd666d0cecf2b6ad8836154b3ec4a517ab28e4dd308a7"
|
||||
"jar": "7fc1e03b890ac2196f2a9c597f8b0074710ed62b67f9e753a62897893002ffd3"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:protocol-core": {
|
||||
"shasums": {
|
||||
"jar": "9a66e0f6164eb196cda42219a667472a2e8e5823cba1d9a703118560cae91678"
|
||||
"jar": "b83955e778376391157ffc653baacd8527694bb15c8e542e7e49333bd5cef98b"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:regions": {
|
||||
"shasums": {
|
||||
"jar": "67c407fe27498cf229d895b0e629793e3d3292bf9d6be3c84aabecaddd4ec838"
|
||||
"jar": "b5d24f9cc4c7c8b251df9c211a9b9207f23dc93d4615a5d84edfff53149a0fae"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:retries": {
|
||||
"shasums": {
|
||||
"jar": "d4d74aa94eff9ee72b072277424639f08b932ff6cf87ec42477dba62b32206f9"
|
||||
"jar": "40bd2c5dfeb7ec2107de09d9732d100fded1cef86501604a7b200ce4a878d7b6"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:retries-spi": {
|
||||
"shasums": {
|
||||
"jar": "2ab77d59f8cd38dc1c3ab90cf650712608cbcea04da5e1298691fc7e4952fab7"
|
||||
"jar": "3b4d05cc05436413266b555b9a8682c2cca79ac7456912d27a37ad2123141724"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:s3": {
|
||||
"shasums": {
|
||||
"jar": "62b095e2911326d7b18f440ba647e7b1159c01a40393af25b0c88df7b4ace2bf"
|
||||
"jar": "8621845c19a7b5f47d1c2c7c80a7c2a9385f79173930a3540c2d1b9599668cd0"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:s3-transfer-manager": {
|
||||
"shasums": {
|
||||
"jar": "01b5ba24b93329a5471298861e4bfbe9b42bd8dc4f7dbe6824e2ce7f9d6fcb44"
|
||||
"jar": "1e0c8874a55a40573a595fc073509ea09b2a511e1e8648eb17b7fb2de307fb3e"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:sdk-core": {
|
||||
"shasums": {
|
||||
"jar": "3a3bb0201d1a71904aa84fd9ea51efbf721ff9b3ae10e7ae1b4e886e11148b53"
|
||||
"jar": "9ce1df0c7e0c088fb5d32167a8240b5b7b7eb324128f0bedb6ad5878243d05e9"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:third-party-jackson-core": {
|
||||
"shasums": {
|
||||
"jar": "a8598b1ec191fa9b7478df6d56c17195ca969741110fc9f472d4f7e0209a0cdd"
|
||||
"jar": "d53b00bec0af215d25c57fa05b19737fe7a6fb841ad895ac700fd998eab10e78"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:utils": {
|
||||
"shasums": {
|
||||
"jar": "9cfa5a97ca9d6fa3e4664d4b86139878d1976a563131e4af0aedd5b028a48154"
|
||||
"jar": "b6403e4ee0640678bb228dc6340499750eea3e48918dc4c8a76ff8c26d22690e"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.awssdk:utils-lite": {
|
||||
"shasums": {
|
||||
"jar": "86f3a7a8b21d387567e67391914169b9a17e87a332c33ab0925690e0f6678ef2"
|
||||
"jar": "b7b3ebb6a902f6f89edd2ba12b3145b8f000245b891d5b2ea21f4c005b842dd0"
|
||||
},
|
||||
"version": "2.46.9"
|
||||
"version": "2.46.7"
|
||||
},
|
||||
"software.amazon.eventstream:eventstream": {
|
||||
"shasums": {
|
||||
@@ -1508,6 +1518,14 @@
|
||||
"com.google.code.gson:gson",
|
||||
"com.google.http-client:google-http-client"
|
||||
],
|
||||
"com.google.protobuf:protobuf-java-util": [
|
||||
"com.google.code.findbugs:jsr305",
|
||||
"com.google.code.gson:gson",
|
||||
"com.google.errorprone:error_prone_annotations",
|
||||
"com.google.guava:guava",
|
||||
"com.google.j2objc:j2objc-annotations",
|
||||
"com.google.protobuf:protobuf-java"
|
||||
],
|
||||
"com.google.s2a.proto.v2:s2a-proto": [
|
||||
"io.grpc:grpc-protobuf",
|
||||
"io.grpc:grpc-stub"
|
||||
@@ -2421,6 +2439,9 @@
|
||||
"com.google.protobuf",
|
||||
"com.google.protobuf.compiler"
|
||||
],
|
||||
"com.google.protobuf:protobuf-java-util": [
|
||||
"com.google.protobuf.util"
|
||||
],
|
||||
"com.google.re2j:re2j": [
|
||||
"com.google.re2j"
|
||||
],
|
||||
@@ -4081,6 +4102,7 @@
|
||||
"com.google.http-client:google-http-client-gson",
|
||||
"com.google.j2objc:j2objc-annotations",
|
||||
"com.google.protobuf:protobuf-java",
|
||||
"com.google.protobuf:protobuf-java-util",
|
||||
"com.google.re2j:re2j",
|
||||
"com.google.s2a.proto.v2:s2a-proto",
|
||||
"com.google.truth:truth",
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
],
|
||||
"dependencyDashboard": true,
|
||||
"enabledManagers": [
|
||||
"bazel-module",
|
||||
"cargo",
|
||||
"github-actions",
|
||||
"gomod",
|
||||
"swift"
|
||||
],
|
||||
"labels": [
|
||||
"dependencies"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Bazel module artifact updates run in GitHub Actions so hosted Renovate does not need unsafe bazel mod deps execution",
|
||||
"matchManagers": [
|
||||
"bazel-module"
|
||||
],
|
||||
"skipArtifactsUpdate": true
|
||||
},
|
||||
{
|
||||
"description": "Go packages import Bazel-generated protobuf packages, so Renovate's generic Go artifact updater cannot run ./...",
|
||||
"matchManagers": [
|
||||
"gomod"
|
||||
],
|
||||
"skipArtifactsUpdate": true
|
||||
}
|
||||
],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"schedule": [
|
||||
"before 6am on monday"
|
||||
]
|
||||
},
|
||||
"prConcurrentLimit": 3,
|
||||
"prHourlyLimit": 1,
|
||||
"rebaseWhen": "behind-base-branch",
|
||||
"schedule": [
|
||||
"before 6am on monday"
|
||||
],
|
||||
"separateMajorMinor": true,
|
||||
"timezone": "America/Los_Angeles"
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
bazel_cmd=(bazel)
|
||||
if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then
|
||||
bazel_cmd+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
|
||||
fi
|
||||
if [ -n "${BAZEL_OUTPUT_BASE:-}" ]; then
|
||||
bazel_cmd+=("--output_base=${BAZEL_OUTPUT_BASE}")
|
||||
fi
|
||||
|
||||
"${bazel_cmd[@]}" build "$@" //src/main/scala/net/eagle0/eagle:eagle_server
|
||||
+53
-15
@@ -1,30 +1,68 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
set -euxo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
UNITY_ROOT="$REPO_ROOT/src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
PLUGIN_DIR="$UNITY_ROOT/Assets/Plugins/Eagle0Protos"
|
||||
GENERATED_SOURCES_DIR="$UNITY_ROOT/Assets/GeneratedProtos"
|
||||
PROTO_DLL_TARGET="//src/main/csharp/net/eagle0/clients/unity/eagle0:eagle0_protos"
|
||||
PROTO_DLL_OUTPUT="$REPO_ROOT/bazel-bin/src/main/csharp/net/eagle0/clients/unity/eagle0/eagle0_protos/netstandard2.1"
|
||||
OUTPUT_DIR="$UNITY_ROOT/Assets/GeneratedProtos"
|
||||
|
||||
/bin/echo "Building C# protobuf DLL..."
|
||||
bazel build "$PROTO_DLL_TARGET"
|
||||
# All C# protobuf generation targets
|
||||
TARGETS=(
|
||||
"//src/main/protobuf/net/eagle0/common:common_csharp_proto_srcs"
|
||||
"//src/main/protobuf/net/eagle0/shardok/storage:storage_csharp_proto_srcs"
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:common_csharp_proto_srcs"
|
||||
"//src/main/protobuf/net/eagle0/shardok/api:api_csharp_proto_srcs"
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:common_csharp_proto_srcs"
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:views_csharp_proto_srcs"
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:api_csharp_proto_srcs"
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:api_csharp_grpc_srcs"
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:util_csharp_proto_srcs"
|
||||
)
|
||||
|
||||
/bin/echo "Syncing generated protobuf DLL to $PLUGIN_DIR..."
|
||||
# Subdirectory for each target (avoids filename collisions across packages)
|
||||
SUBDIRS=(
|
||||
"net/eagle0/common"
|
||||
"net/eagle0/shardok/storage"
|
||||
"net/eagle0/shardok/common"
|
||||
"net/eagle0/shardok/api"
|
||||
"net/eagle0/eagle/common"
|
||||
"net/eagle0/eagle/views"
|
||||
"net/eagle0/eagle/api"
|
||||
"net/eagle0/eagle/api"
|
||||
"net/eagle0/eagle/api/command/util"
|
||||
)
|
||||
|
||||
/bin/echo "Building C# protobuf sources..."
|
||||
bazel build "${TARGETS[@]}"
|
||||
|
||||
/bin/echo "Syncing generated .cs files to $OUTPUT_DIR..."
|
||||
|
||||
# Stage new files in a temp directory, then rsync to preserve timestamps
|
||||
# on unchanged files. This avoids triggering Unity reimport when protos
|
||||
# haven't changed, saving ~7 minutes of script recompilation.
|
||||
STAGING_DIR=$(mktemp -d)
|
||||
trap "rm -rf '$STAGING_DIR'" EXIT
|
||||
|
||||
cp "$PROTO_DLL_OUTPUT/Eagle0Protos.dll" "$STAGING_DIR/"
|
||||
cp "$PROTO_DLL_OUTPUT/Eagle0Protos.xml" "$STAGING_DIR/"
|
||||
for i in "${!TARGETS[@]}"; do
|
||||
target="${TARGETS[$i]}"
|
||||
subdir="${SUBDIRS[$i]}"
|
||||
|
||||
# Convert target label to bazel-bin path
|
||||
# //src/main/protobuf/net/eagle0/common:common_csharp_proto_srcs
|
||||
# -> bazel-bin/src/main/protobuf/net/eagle0/common/common_csharp_proto_srcs
|
||||
target_path="${target#//}"
|
||||
pkg="${target_path%%:*}"
|
||||
name="${target_path##*:}"
|
||||
bin_dir="$REPO_ROOT/bazel-bin/$pkg/$name"
|
||||
|
||||
dest_dir="$STAGING_DIR/$subdir"
|
||||
mkdir -p "$dest_dir"
|
||||
find "$bin_dir" -name "*.cs" -exec cp {} "$dest_dir/" \;
|
||||
done
|
||||
|
||||
# Sync only changed files and delete removed ones; --checksum compares
|
||||
# content not timestamps so unchanged files keep their original mtime.
|
||||
mkdir -p "$PLUGIN_DIR"
|
||||
rsync -rc --delete --exclude "*.meta" "$STAGING_DIR/" "$PLUGIN_DIR/"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
rsync -rc --delete "$STAGING_DIR/" "$OUTPUT_DIR/"
|
||||
|
||||
rm -rf "$GENERATED_SOURCES_DIR"
|
||||
|
||||
/bin/echo "Done. Generated C# proto DLL in $PLUGIN_DIR"
|
||||
/bin/echo "Done. Generated C# proto sources in $OUTPUT_DIR"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
bazel_cmd=(bazel)
|
||||
if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then
|
||||
bazel_cmd+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
|
||||
fi
|
||||
if [ -n "${BAZEL_OUTPUT_BASE:-}" ]; then
|
||||
bazel_cmd+=("--output_base=${BAZEL_OUTPUT_BASE}")
|
||||
fi
|
||||
|
||||
"${bazel_cmd[@]}" build -c opt "$@" //src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
bazel_cmd=(bazel)
|
||||
if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then
|
||||
bazel_cmd+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
|
||||
fi
|
||||
if [ -n "${BAZEL_OUTPUT_BASE:-}" ]; then
|
||||
bazel_cmd+=("--output_base=${BAZEL_OUTPUT_BASE}")
|
||||
fi
|
||||
|
||||
"${bazel_cmd[@]}" build \
|
||||
--platforms=//:linux_arm64 \
|
||||
--extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux \
|
||||
"$@" \
|
||||
//src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
@@ -1,87 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 <warm|verify> [bazel targets...]"
|
||||
echo
|
||||
echo "warm: build targets normally so local results can upload to the remote cache"
|
||||
echo "verify: require all target actions to be available from remote cache"
|
||||
}
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mode="$1"
|
||||
shift
|
||||
|
||||
case "$mode" in
|
||||
warm | verify)
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
tmp_root="${RUNNER_TEMP:-/private/tmp}"
|
||||
run_id="${GITHUB_RUN_ID:-local}-$$"
|
||||
output_user_root="${tmp_root%/}/eagle0-bazel-user-root-${run_id}"
|
||||
output_base="${tmp_root%/}/eagle0-cache-parity-${mode}-${run_id}"
|
||||
|
||||
echo "Mode: $mode"
|
||||
echo "Output user root: $output_user_root"
|
||||
echo "Output base: $output_base"
|
||||
|
||||
run_ci_build_script() {
|
||||
local script="$1"
|
||||
shift
|
||||
|
||||
echo "Build script: $script"
|
||||
echo "Build args:"
|
||||
printf ' %s\n' "$@"
|
||||
|
||||
BAZEL_OUTPUT_USER_ROOT="$output_user_root" BAZEL_OUTPUT_BASE="$output_base" "$script" "$@"
|
||||
}
|
||||
|
||||
cache_check_args() {
|
||||
local bazel_args=(
|
||||
"--remote_download_all"
|
||||
)
|
||||
|
||||
if [ "$mode" = "verify" ]; then
|
||||
bazel_args+=(
|
||||
"--remote_upload_local_results=false"
|
||||
"--experimental_remote_require_cached"
|
||||
"--disk_cache="
|
||||
)
|
||||
fi
|
||||
|
||||
printf '%s\n' "${bazel_args[@]}"
|
||||
}
|
||||
|
||||
run_bazel_targets() {
|
||||
bazel_args=()
|
||||
while IFS= read -r arg; do
|
||||
bazel_args+=("$arg")
|
||||
done < <(cache_check_args)
|
||||
|
||||
echo "Bazel args:"
|
||||
printf ' %s\n' "${bazel_args[@]}" "$@"
|
||||
|
||||
bazel "--output_user_root=$output_user_root" "--output_base=$output_base" build "${bazel_args[@]}" "$@"
|
||||
}
|
||||
|
||||
if [ "$#" -gt 0 ]; then
|
||||
run_bazel_targets "$@"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
bazel_args=()
|
||||
while IFS= read -r arg; do
|
||||
bazel_args+=("$arg")
|
||||
done < <(cache_check_args)
|
||||
run_ci_build_script scripts/build_eagle_ci.sh "${bazel_args[@]}"
|
||||
run_ci_build_script scripts/build_shardok_ci.sh "${bazel_args[@]}"
|
||||
@@ -1,352 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import random
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
|
||||
API_KEY_FILE = Path("src/main/scala/net/eagle0/common/llm_integration/api_keys.txt")
|
||||
IMAGE_ENDPOINT = "https://api.openai.com/v1/images/generations"
|
||||
|
||||
APPEARANCE_PROFILES = [
|
||||
"West African features, very dark brown skin",
|
||||
"East African features, deep brown skin",
|
||||
"North African features, olive-brown skin",
|
||||
"Arabian Peninsula features, warm medium-brown skin",
|
||||
"Persian features, olive skin",
|
||||
"Turkic Central Asian features, tan skin",
|
||||
"Mongolian features, golden-tan skin",
|
||||
"North Indian features, medium-brown skin",
|
||||
"South Indian features, dark brown skin",
|
||||
"Bengali features, warm brown skin",
|
||||
"Southeast Asian features, medium tan skin",
|
||||
"Vietnamese features, light golden skin",
|
||||
"Chinese features, light olive skin",
|
||||
"Korean features, fair skin",
|
||||
"Japanese features, fair-to-light olive skin",
|
||||
"Indigenous Andean features, copper-brown skin",
|
||||
"Mesoamerican features, medium brown skin",
|
||||
"Native North American features, warm brown skin",
|
||||
"Maori or Polynesian features, medium brown skin",
|
||||
"Aboriginal Australian features, dark brown skin",
|
||||
"Afro-Caribbean features, dark brown skin",
|
||||
"Afro-Latin features, medium-dark brown skin",
|
||||
"Brazilian mixed-heritage features, medium brown skin",
|
||||
"Mexican mestizo features, tan skin",
|
||||
"Mediterranean European features, olive skin",
|
||||
"Iberian features, light olive skin",
|
||||
"Italian features, olive skin",
|
||||
"Greek features, olive skin",
|
||||
"French features, light skin",
|
||||
"Irish features, fair skin with freckles",
|
||||
"Scottish features, ruddy fair skin",
|
||||
"Nordic features, very fair skin",
|
||||
"Slavic features, fair-to-light olive skin",
|
||||
"Ashkenazi Jewish features, light olive skin",
|
||||
"Armenian features, olive skin",
|
||||
"Ethiopian highland features, medium-dark brown skin",
|
||||
"Moroccan Amazigh features, tan olive skin",
|
||||
"Egyptian features, warm tan skin",
|
||||
"Filipino features, medium golden-brown skin",
|
||||
"Pacific Islander features, warm brown skin",
|
||||
]
|
||||
|
||||
AGE_PROFILES = [
|
||||
"young adult",
|
||||
"adult",
|
||||
"seasoned middle-aged",
|
||||
"older veteran",
|
||||
]
|
||||
|
||||
FACE_PROFILES = [
|
||||
"broad, sturdy face",
|
||||
"narrow, severe face",
|
||||
"round, watchful face",
|
||||
"angular, weathered face",
|
||||
"scarred but composed face",
|
||||
"plain, practical face",
|
||||
"commanding, heavy-browed face",
|
||||
"tired but alert face",
|
||||
]
|
||||
|
||||
HAIR_PROFILES = [
|
||||
"cropped dark hair",
|
||||
"close-cropped gray hair",
|
||||
"braided black hair",
|
||||
"shaved head",
|
||||
"curly dark hair pulled back",
|
||||
"straight black hair tied back",
|
||||
"auburn hair streaked with gray",
|
||||
"white hair cut short",
|
||||
"dark beard and close hair",
|
||||
"neat graying beard",
|
||||
"coiled natural hair",
|
||||
"long dark hair bound under a cap",
|
||||
]
|
||||
|
||||
|
||||
def api_key():
|
||||
env_value = os.environ.get("OPENAI_API_KEY")
|
||||
if env_value:
|
||||
return env_value
|
||||
|
||||
if API_KEY_FILE.exists():
|
||||
for line in API_KEY_FILE.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
if key.strip() == "openai_api_key":
|
||||
value = value.strip()
|
||||
if value and not value.startswith("your_"):
|
||||
return value
|
||||
|
||||
raise RuntimeError(
|
||||
"OpenAI API key not found. Set OPENAI_API_KEY or add openai_api_key to "
|
||||
f"{API_KEY_FILE}."
|
||||
)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate Warden hero headshots and update generated_heroes.tsv image paths."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--hero-tsv",
|
||||
default="src/main/resources/net/eagle0/eagle/generated_heroes.tsv",
|
||||
help="Generated heroes TSV to read and update.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--base-image-dir",
|
||||
default=str(Path.home() / "Documents/headshots"),
|
||||
help="Local headshot root that sync_headshots.sh uploads to S3.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--model",
|
||||
default="gpt-image-2",
|
||||
help="OpenAI image generation model.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--quality",
|
||||
default="low",
|
||||
choices=("low", "medium", "high", "auto"),
|
||||
help="Image generation quality.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--size",
|
||||
default="1024x1024",
|
||||
help="Generated image size.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--limit",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Generate at most this many missing images; 0 means all missing images.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Print planned paths without calling the API or writing files.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--overwrite",
|
||||
action="store_true",
|
||||
help="Regenerate images even if the target PNG already exists.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--concurrency",
|
||||
type=int,
|
||||
default=4,
|
||||
help="Number of parallel image generation requests.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def read_rows(path):
|
||||
lines = path.read_text().splitlines()
|
||||
fieldnames = lines[0].split("\t")
|
||||
rows = []
|
||||
for line in lines[1:]:
|
||||
fields = line.split("\t")
|
||||
row = dict(zip(fieldnames, fields))
|
||||
row["_fields"] = fields
|
||||
rows.append(row)
|
||||
return fieldnames, rows
|
||||
|
||||
|
||||
def write_rows(path, fieldnames, rows):
|
||||
image_path_index = fieldnames.index("image_path")
|
||||
lines = ["\t".join(fieldnames)]
|
||||
for row in rows:
|
||||
fields = list(row["_fields"])
|
||||
fields[image_path_index] = row["image_path"]
|
||||
lines.append("\t".join(fields))
|
||||
path.write_text("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def image_gender(row, warden_index):
|
||||
gender = row["gender"]
|
||||
if gender in ("male", "female"):
|
||||
return gender
|
||||
|
||||
digest = hashlib.sha256(row["name"].encode("utf-8")).digest()
|
||||
return "male" if (digest[0] + warden_index) % 2 == 0 else "female"
|
||||
|
||||
|
||||
def image_filename(row, warden_index):
|
||||
digest = hashlib.sha256(row["name"].encode("utf-8")).hexdigest()[:16]
|
||||
return f"{warden_index:04d}_{digest}.png"
|
||||
|
||||
|
||||
def choose_profile(items, row, warden_index, salt):
|
||||
digest = hashlib.sha256(f"{salt}:{warden_index}:{row['name']}".encode("utf-8")).digest()
|
||||
return items[int.from_bytes(digest[:4], "big") % len(items)]
|
||||
|
||||
|
||||
def prompt_for(row, warden_index):
|
||||
prime_notes = [
|
||||
f"strength {row['strength']}",
|
||||
f"agility {row['agility']}",
|
||||
f"constitution {row['constitution']}",
|
||||
f"wisdom {row['wisdom']}",
|
||||
f"charisma {row['charisma']}",
|
||||
]
|
||||
appearance = choose_profile(APPEARANCE_PROFILES, row, warden_index, "appearance")
|
||||
age = choose_profile(AGE_PROFILES, row, warden_index, "age")
|
||||
face = choose_profile(FACE_PROFILES, row, warden_index, "face")
|
||||
hair = choose_profile(HAIR_PROFILES, row, warden_index, "hair")
|
||||
return (
|
||||
"Fantasy strategy game hero portrait, square headshot composition. "
|
||||
f"Create a distinctive medieval Warden named {row['name']}. "
|
||||
f"Gender presentation: {row['gender']}. "
|
||||
f"Appearance: {age}, {appearance}, {face}, {hair}. "
|
||||
f"Personality traits: {row['personality']}. "
|
||||
"The character is a jailer, keeper of prisoners, and battlefield custodian: "
|
||||
"stern keys, austere authority, prison-watch uniform, no modern clothing. "
|
||||
"Show a resilient, high-constitution figure with weathered endurance and moral judgment. "
|
||||
f"Stat cues: {', '.join(prime_notes)}. "
|
||||
"Painted realistic fantasy style, expressive face, shoulders visible, neutral background, "
|
||||
"soft dramatic lighting, no text, no logo, no frame, no watermark."
|
||||
)
|
||||
|
||||
|
||||
def request_image(key, model, quality, size, prompt):
|
||||
body = {
|
||||
"model": model,
|
||||
"prompt": prompt,
|
||||
"size": size,
|
||||
"quality": quality,
|
||||
"n": 1,
|
||||
"output_format": "png",
|
||||
}
|
||||
request = urllib.request.Request(
|
||||
IMAGE_ENDPOINT,
|
||||
data=json.dumps(body).encode("utf-8"),
|
||||
headers={
|
||||
"Authorization": f"Bearer {key}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=300) as response:
|
||||
response_body = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
data = response_body["data"][0]
|
||||
if "b64_json" in data:
|
||||
return base64.b64decode(data["b64_json"])
|
||||
|
||||
if "url" in data:
|
||||
with urllib.request.urlopen(data["url"], timeout=300) as response:
|
||||
return response.read()
|
||||
|
||||
raise RuntimeError("Image response did not include b64_json or url")
|
||||
|
||||
|
||||
def generate_with_retries(key, args, prompt, attempts=5):
|
||||
for attempt in range(1, attempts + 1):
|
||||
try:
|
||||
return request_image(key, args.model, args.quality, args.size, prompt)
|
||||
except urllib.error.HTTPError as error:
|
||||
body = error.read().decode("utf-8", errors="replace")
|
||||
retryable = error.code in (408, 409, 429, 500, 502, 503, 504)
|
||||
if not retryable or attempt == attempts:
|
||||
raise RuntimeError(f"OpenAI image request failed: {error.code} {body}") from error
|
||||
except (TimeoutError, urllib.error.URLError):
|
||||
if attempt == attempts:
|
||||
raise
|
||||
|
||||
time.sleep(min(60, 2**attempt) + random.random())
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
hero_tsv = Path(args.hero_tsv)
|
||||
base_image_dir = Path(args.base_image_dir).expanduser()
|
||||
fieldnames, rows = read_rows(hero_tsv)
|
||||
|
||||
key = None if args.dry_run else api_key()
|
||||
warden_index = 0
|
||||
generation_tasks = []
|
||||
|
||||
for row in rows:
|
||||
if row["profession"] != "warden":
|
||||
continue
|
||||
|
||||
warden_index += 1
|
||||
gender = image_gender(row, warden_index)
|
||||
rel_path = f"warden/{gender}/{image_filename(row, warden_index)}"
|
||||
target_path = base_image_dir / rel_path
|
||||
row["image_path"] = rel_path
|
||||
|
||||
if target_path.exists() and not args.overwrite:
|
||||
print(f"exists {rel_path}", flush=True)
|
||||
continue
|
||||
|
||||
generation_tasks.append(
|
||||
(row["name"], rel_path, target_path, prompt_for(row, warden_index))
|
||||
)
|
||||
|
||||
if args.dry_run:
|
||||
print(f"Dry run planned {warden_index} Warden image paths", flush=True)
|
||||
else:
|
||||
if args.limit:
|
||||
generation_tasks = generation_tasks[: args.limit]
|
||||
|
||||
for _, _, target_path, _ in generation_tasks:
|
||||
target_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def generate_task(task):
|
||||
name, rel_path, target_path, prompt = task
|
||||
print(f"generate {rel_path} for {name}", flush=True)
|
||||
image_bytes = generate_with_retries(key, args, prompt)
|
||||
if not image_bytes.startswith(b"\x89PNG\r\n\x1a\n"):
|
||||
raise RuntimeError(f"Generated image for {name} is not a PNG")
|
||||
target_path.write_bytes(image_bytes)
|
||||
return rel_path
|
||||
|
||||
generated_count = 0
|
||||
if generation_tasks:
|
||||
with ThreadPoolExecutor(max_workers=max(1, args.concurrency)) as executor:
|
||||
futures = [executor.submit(generate_task, task) for task in generation_tasks]
|
||||
for future in as_completed(futures):
|
||||
rel_path = future.result()
|
||||
generated_count += 1
|
||||
print(
|
||||
f"wrote {rel_path} ({generated_count}/{len(generation_tasks)})",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
write_rows(hero_tsv, fieldnames, rows)
|
||||
print(f"Updated {hero_tsv}; generated {generated_count} missing images", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
scripts/build_eagle_ci.sh
|
||||
scripts/build_shardok_ci.sh
|
||||
@@ -8,12 +8,12 @@
|
||||
#
|
||||
# Optional environment variables:
|
||||
# SPARKLE_FEED_URL - Appcast URL (default: https://assets.eagle0.net/mac/appcast.xml)
|
||||
# SPARKLE_VERSION - Sparkle version to use (default: 2.9.3)
|
||||
# SPARKLE_VERSION - Sparkle version to use (default: 2.6.4)
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
APP_PATH="$1"
|
||||
SPARKLE_VERSION="${SPARKLE_VERSION:-2.9.3}"
|
||||
SPARKLE_VERSION="${SPARKLE_VERSION:-2.6.4}"
|
||||
SPARKLE_FEED_URL="${SPARKLE_FEED_URL:-https://assets.eagle0.net/mac/appcast.xml}"
|
||||
SPARKLE_CACHE_DIR="/tmp/sparkle-cache"
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Runs the repository-pinned clang-tidy binary from the workspace root.
|
||||
#
|
||||
# Bazel's `run` command executes clang-tidy from its runfiles tree, which makes
|
||||
# workspace-relative source paths and .clang-tidy discovery fail. This wrapper
|
||||
# builds the tool and then invokes the produced binary directly.
|
||||
|
||||
set -e
|
||||
|
||||
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
||||
|
||||
repo_root="$(git rev-parse --show-toplevel)"
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "Usage: scripts/run-clang-tidy.sh [clang-tidy args] <files> [-- <compiler args>]"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " scripts/run-clang-tidy.sh --verify-config"
|
||||
echo " scripts/run-clang-tidy.sh src/main/cpp/net/eagle0/shardok/library/CombatDamage.hpp -- -I$repo_root -std=c++23"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
bazel build @llvm_toolchain//:clang-tidy
|
||||
|
||||
bazel_bin="$(bazel info bazel-bin)"
|
||||
clang_tidy="$bazel_bin/external/toolchains_llvm++llvm+llvm_toolchain/clang-tidy"
|
||||
|
||||
if [ ! -x "$clang_tidy" ]; then
|
||||
echo "Unable to find clang-tidy binary at $clang_tidy"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$clang_tidy" --config-file="$repo_root/.clang-tidy" "$@"
|
||||
@@ -6,9 +6,9 @@
|
||||
# Xcode version. If Xcode is upgraded in place, the DEVELOPER_DIR path stays the
|
||||
# same, so Bazel can otherwise keep using stale Xcode metadata.
|
||||
#
|
||||
# 1. Writes .bazelrc.xcode with mactools-scoped flags:
|
||||
# - action_env for Apple build remote cache key invalidation
|
||||
# - DEVELOPER_DIR pointing to the active Xcode for Apple builds
|
||||
# 1. Writes .bazelrc.xcode with macOS-scoped flags:
|
||||
# - action_env for remote cache key invalidation
|
||||
# - DEVELOPER_DIR pointing to the active Xcode
|
||||
#
|
||||
# 2. Runs `bazel clean --expunge` when the version actually changes, because
|
||||
# local_config_apple_cc (a cached repository rule) bakes in the old version
|
||||
@@ -35,7 +35,7 @@ if [ -z "$XCODE_BUILD_VERSION" ]; then
|
||||
fi
|
||||
|
||||
BAZELRC_XCODE=".bazelrc.xcode"
|
||||
EXPECTED_LINE="common:mactools --action_env=XCODE_BUILD_VERSION=${XCODE_BUILD_VERSION}"
|
||||
EXPECTED_LINE="common:macos --action_env=XCODE_BUILD_VERSION=${XCODE_BUILD_VERSION}"
|
||||
|
||||
# Check if the file already has the right version (first line is the version marker)
|
||||
if [ -f "$BAZELRC_XCODE" ]; then
|
||||
@@ -45,19 +45,19 @@ if [ -f "$BAZELRC_XCODE" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Xcode Bazel config changed (or first run) — expunge local cache to clear
|
||||
# Xcode version changed (or first run) — expunge local cache to clear
|
||||
# stale local_config_apple_cc, then write the new config
|
||||
OLD_CONFIG="missing"
|
||||
OLD_VERSION="unknown"
|
||||
if [ -f "$BAZELRC_XCODE" ]; then
|
||||
OLD_CONFIG=$(head -1 "$BAZELRC_XCODE")
|
||||
OLD_VERSION=$(sed -n 's/.*XCODE_BUILD_VERSION=//p' "$BAZELRC_XCODE" | head -1)
|
||||
fi
|
||||
echo "Xcode Bazel config changed: ${OLD_CONFIG} -> ${EXPECTED_LINE}"
|
||||
echo "Xcode build version changed: ${OLD_VERSION} -> ${XCODE_BUILD_VERSION}"
|
||||
echo "Running bazel clean --expunge to clear stale toolchain config..."
|
||||
bazel clean --expunge 2>/dev/null || true
|
||||
|
||||
cat > "$BAZELRC_XCODE" << EOF
|
||||
${EXPECTED_LINE}
|
||||
common:mactools --repo_env=DEVELOPER_DIR=${DEVELOPER_DIR}
|
||||
common:macos --repo_env=DEVELOPER_DIR=${DEVELOPER_DIR}
|
||||
EOF
|
||||
|
||||
if [ -n "${GITHUB_ENV:-}" ]; then
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/zsh
|
||||
|
||||
/opt/homebrew/bin/s3cmd sync \
|
||||
--acl-public \
|
||||
--exclude="*dream_log.txt" \
|
||||
--exclude="*.DS_Store" \
|
||||
--delete-removed \
|
||||
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo -ne "\033[0;32m"
|
||||
echo 'Updating bazel dependencies. This will take about five minutes.'
|
||||
echo -ne "\033[0m"
|
||||
set -e
|
||||
|
||||
if [ "$(uname -s)" == "Linux" ]; then
|
||||
BAZEL_DEPS_URL=https://github.com/johnynek/bazel-deps/releases/download/v0.1-9/bazel-deps-linux
|
||||
BAZEL_DEPS_SHA256=034ce4d8674a200d73d97aa60774786a3771c0f3871197858bf55fd930677b15
|
||||
elif [ "$(uname -s)" == "Darwin" ]; then
|
||||
BAZEL_DEPS_URL=https://github.com/johnynek/bazel-deps/releases/download/v0.1-9/bazel-deps-macos
|
||||
BAZEL_DEPS_SHA256=97e1b932cccc60e30475fc0ef56a538c6b8a732adbdf0eaec1988f0b4f1dc5dc
|
||||
else
|
||||
echo "Your platform '$(uname -s)' is unsupported, sorry"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# This is some bash snippet designed to find the location of the script.
|
||||
# we operate under the presumption this script is checked into the repo being operated on
|
||||
# so we goto the script location, then use git to find the repo root.
|
||||
SCRIPT_LOCATION="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
cd $SCRIPT_LOCATION
|
||||
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
|
||||
BAZEL_DEPS_DIR="$HOME/.bazel-deps-cache"
|
||||
BAZEL_DEPS_PATH="${BAZEL_DEPS_DIR}/v0.1-9"
|
||||
|
||||
if [ ! -f ${BAZEL_DEPS_PATH} ]; then
|
||||
( # Opens a subshell
|
||||
set -e
|
||||
echo "Fetching bazel deps."
|
||||
curl -L -o /tmp/bazel-deps-bin $BAZEL_DEPS_URL
|
||||
|
||||
GENERATED_SHA_256=$(shasum -a 256 /tmp/bazel-deps-bin | awk '{print $1}')
|
||||
|
||||
if [ "$GENERATED_SHA_256" != "$BAZEL_DEPS_SHA256" ]; then
|
||||
echo "Sha 256 does not match, expected: $BAZEL_DEPS_SHA256"
|
||||
echo "But found $GENERATED_SHA_256"
|
||||
echo "You may need to update the sha in this script, or the download was corrupted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x /tmp/bazel-deps-bin
|
||||
mkdir -p ${BAZEL_DEPS_DIR}
|
||||
mv /tmp/bazel-deps-bin ${BAZEL_DEPS_PATH}
|
||||
)
|
||||
fi
|
||||
|
||||
cd $REPO_ROOT
|
||||
set +e
|
||||
$BAZEL_DEPS_PATH generate -r $REPO_ROOT -s 3rdparty/workspace.bzl -d dependencies.yaml --target-file 3rdparty/target_file.bzl --disable-3rdparty-in-repo
|
||||
RET_CODE=$?
|
||||
set -e
|
||||
|
||||
if [ $RET_CODE == 0 ]; then
|
||||
echo "Success, going to format files"
|
||||
else
|
||||
echo "Failure, checking out 3rdparty/jvm"
|
||||
cd $REPO_ROOT
|
||||
git checkout 3rdparty/jvm 3rdparty/workspace.bzl
|
||||
exit $RET_CODE
|
||||
fi
|
||||
|
||||
$BAZEL_DEPS_PATH format-deps -d $REPO_ROOT/dependencies.yaml -o
|
||||
@@ -1,4 +1,3 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
load("//tools:copts.bzl", "COPTS")
|
||||
|
||||
cc_library(
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
|
||||
#include "tools/cpp/runfiles/runfiles.h"
|
||||
@@ -38,7 +37,7 @@ auto rloc(const string& execPath) -> string {
|
||||
const std::unique_ptr<Runfiles> runfiles(Runfiles::Create(execPath, &error));
|
||||
|
||||
if (runfiles == nullptr) {
|
||||
std::cerr << "Error! " << error << '\n';
|
||||
fprintf(stderr, "Error! %s\n", error.c_str());
|
||||
abort();
|
||||
// error handling
|
||||
}
|
||||
@@ -79,9 +78,9 @@ auto FilesystemUtils::MapFilesDirectory() -> string {
|
||||
|
||||
void FilesystemUtils::MakeDirectoryIfNecessary(const string& directoryPath) {
|
||||
if (fs::create_directories(directoryPath))
|
||||
std::cerr << "Directory " << directoryPath << " created\n";
|
||||
fprintf(stderr, "Directory %s created\n", directoryPath.c_str());
|
||||
else
|
||||
std::cerr << "No new directory created for " << directoryPath << '\n';
|
||||
fprintf(stderr, "No new directory created for %s\n", directoryPath.c_str());
|
||||
}
|
||||
|
||||
auto FilesystemUtils::SaveFilesDirectory() -> string {
|
||||
@@ -141,11 +140,11 @@ auto FilesystemUtils::AtomicallySaveToPath(const string& path, const byte_vector
|
||||
if (ostr.good()) {
|
||||
const int err = rename(tempPath.c_str(), path.c_str());
|
||||
if (err == -1) {
|
||||
std::cerr << "Failed to move file to " << path << "! Errno " << errno << '\n';
|
||||
fprintf(stderr, "Failed to move file to %s! Errno %d\n", path.c_str(), errno);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Failed writing to " << tempPath << "!\n";
|
||||
fprintf(stderr, "Failed writing to %s!\n", tempPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
// Copyright © 2017 none. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_COMMON_FILESYSTEM_UTILS_HPP
|
||||
#define EAGLE0_COMMON_FILESYSTEM_UTILS_HPP
|
||||
#ifndef FilesystemUtils_hpp
|
||||
#define FilesystemUtils_hpp
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -36,4 +36,4 @@ public:
|
||||
static auto LoadFromPath(const string& path) -> byte_vector;
|
||||
};
|
||||
|
||||
#endif // EAGLE0_COMMON_FILESYSTEM_UTILS_HPP
|
||||
#endif /* FilesystemUtils_hpp */
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
// Copyright © 2018 none. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_COMMON_MAP_UTILS_HPP
|
||||
#define EAGLE0_COMMON_MAP_UTILS_HPP
|
||||
#ifndef MapUtils_hpp
|
||||
#define MapUtils_hpp
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -19,4 +19,4 @@ auto IntForKey(const std::unordered_map<std::string, std::string>& map, const st
|
||||
auto BoolForKey(const std::unordered_map<std::string, std::string>& map, const std::string& key)
|
||||
-> bool;
|
||||
|
||||
#endif // EAGLE0_COMMON_MAP_UTILS_HPP
|
||||
#endif /* MapUtils_hpp */
|
||||
|
||||
@@ -6,17 +6,16 @@
|
||||
// Copyright © 2019 none. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_COMMON_PROTOBUF_WARNING_SUPPRESSION_HPP
|
||||
#define EAGLE0_COMMON_PROTOBUF_WARNING_SUPPRESSION_HPP
|
||||
#ifndef ProtobufWarningSuppression_h
|
||||
#define ProtobufWarningSuppression_h
|
||||
|
||||
#define SUPPRESS_PROTOBUF_WARNINGS \
|
||||
_Pragma("GCC diagnostic ignored \"-Wpragmas\"") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") _Pragma( \
|
||||
"GCC diagnostic ignored \"-Wsign-conversion\"") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wsign-conversion\"") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") _Pragma( \
|
||||
"GCC diagnostic ignored \"-Wdocumentation-deprecated-sync\"") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wdocumentation\"") _Pragma( \
|
||||
"GCC diagnostic ignored \"-Wshorten-64-to-32\"") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wnested-anon-types\"")
|
||||
|
||||
#endif // EAGLE0_COMMON_PROTOBUF_WARNING_SUPPRESSION_HPP
|
||||
#endif /* ProtobufWarningSuppression_h */
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
// Copyright (c) 2015 none. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_COMMON_RANDOM_GENERATOR_HPP
|
||||
#define EAGLE0_COMMON_RANDOM_GENERATOR_HPP
|
||||
#ifndef __eagle0__ShardokRandomGenerator__
|
||||
#define __eagle0__ShardokRandomGenerator__
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
class RandomGenerator;
|
||||
|
||||
@@ -24,10 +22,6 @@ private:
|
||||
|
||||
public:
|
||||
RandomGenerator() = default;
|
||||
RandomGenerator(const RandomGenerator&) = default;
|
||||
auto operator=(const RandomGenerator&) -> RandomGenerator& = default;
|
||||
RandomGenerator(RandomGenerator&&) = default;
|
||||
auto operator=(RandomGenerator&&) -> RandomGenerator& = default;
|
||||
|
||||
virtual ~RandomGenerator() = default;
|
||||
|
||||
@@ -41,7 +35,7 @@ public:
|
||||
|
||||
template<class T>
|
||||
auto RandomElement(const std::vector<T>& vec) -> T {
|
||||
return vec.at(static_cast<std::size_t>(IntBelow(static_cast<int>(vec.size()))));
|
||||
return vec[IntBelow((int)vec.size())];
|
||||
}
|
||||
|
||||
static auto ChanceOpenEndedPercentileAtOrAbove(double value) -> double;
|
||||
@@ -62,4 +56,4 @@ public:
|
||||
auto IntBetween(int min, int max) -> int override; // inclusive, exclusive (min<=return<max)
|
||||
};
|
||||
|
||||
#endif // EAGLE0_COMMON_RANDOM_GENERATOR_HPP
|
||||
#endif /* defined(__eagle0__ShardokRandomGenerator__) */
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
// Copyright © 2018 none. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
|
||||
#define EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
|
||||
#ifndef SequenceRandomGenerator_h
|
||||
#define SequenceRandomGenerator_h
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
|
||||
@@ -25,22 +24,22 @@
|
||||
// which produces: initial=2 (triggers open-ended), accumulated=52, final=2-52=-50
|
||||
class SequenceRandomGenerator : public ::RandomGenerator {
|
||||
private:
|
||||
std::vector<double> sequence;
|
||||
const std::vector<double> sequence;
|
||||
size_t position = 0;
|
||||
|
||||
auto DoubleZeroToOne() -> double override {
|
||||
const double nextVal = sequence.at(position);
|
||||
const double nextVal = sequence[position];
|
||||
position++;
|
||||
if (position >= sequence.size()) { position = 0; }
|
||||
return nextVal;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit SequenceRandomGenerator(std::vector<double> s) : sequence(std::move(s)) {}
|
||||
explicit SequenceRandomGenerator(std::vector<double> s) : sequence(std::move(s)), position(0) {}
|
||||
static auto WithSequence(const std::vector<double>& s)
|
||||
-> std::shared_ptr<SequenceRandomGenerator> {
|
||||
return std::make_shared<SequenceRandomGenerator>(s);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
|
||||
#endif /* SequenceRandomGenerator_h */
|
||||
|
||||
@@ -80,7 +80,7 @@ void EmitShardokLatencyTrace(
|
||||
out << "}";
|
||||
|
||||
std::scoped_lock lock(LogMutex());
|
||||
std::cerr << out.str() << '\n';
|
||||
std::cerr << out.str() << std::endl;
|
||||
}
|
||||
|
||||
ScopedShardokLatencyTrace::ScopedShardokLatencyTrace(
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
@@ -172,7 +171,7 @@ public:
|
||||
// Get detailed queue information for debugging
|
||||
void debug_queue_state() const {
|
||||
std::unique_lock<std::mutex> lock(const_cast<std::mutex&>(queue_mutex));
|
||||
std::cout << "ThreadPool: Queue size: " << tasks.size() << '\n';
|
||||
printf("ThreadPool: Queue size: %zu\n", tasks.size());
|
||||
if (!tasks.empty()) {
|
||||
// Create a copy to inspect priorities without modifying queue
|
||||
auto queue_copy = tasks;
|
||||
@@ -181,9 +180,9 @@ public:
|
||||
priorities.push_back(queue_copy.top().priority);
|
||||
queue_copy.pop();
|
||||
}
|
||||
std::cout << "ThreadPool: Priorities in queue: ";
|
||||
for (int p : priorities) { std::cout << p << ' '; }
|
||||
std::cout << '\n';
|
||||
printf("ThreadPool: Priorities in queue: ");
|
||||
for (int p : priorities) { printf("%d ", p); }
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,4 +197,4 @@ public:
|
||||
|
||||
} // namespace eagle0::common
|
||||
|
||||
#endif // EAGLE0_THREADPOOL_HPP
|
||||
#endif // EAGLE0_THREADPOOL_HPP
|
||||
@@ -6,14 +6,14 @@
|
||||
// Copyright © 2018 none. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_COMMON_TSV_PARSER_HPP
|
||||
#define EAGLE0_COMMON_TSV_PARSER_HPP
|
||||
#ifndef TsvParser_hpp
|
||||
#define TsvParser_hpp
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
using StringMap = std::unordered_map<std::string, std::string>;
|
||||
typedef std::unordered_map<std::string, std::string> StringMap;
|
||||
|
||||
// Parses a TSV, with the first column becoming map keys and subsequent columns becoming values
|
||||
// for different maps in the returned vector.
|
||||
@@ -23,4 +23,4 @@ public:
|
||||
std::vector<StringMap> ParseColumnEntryTsv(std::string tsv);
|
||||
};
|
||||
|
||||
#endif // EAGLE0_COMMON_TSV_PARSER_HPP
|
||||
#endif /* TsvParser_hpp */
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
// Copyright © 2017 none. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_COMMON_BYTE_VECTOR_HPP
|
||||
#define EAGLE0_COMMON_BYTE_VECTOR_HPP
|
||||
#ifndef byte_vector_h
|
||||
#define byte_vector_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -184,4 +184,4 @@ inline auto operator<<(std::ostream& ostr, byte_vector vec) -> std::ostream& {
|
||||
return ostr;
|
||||
}
|
||||
|
||||
#endif // EAGLE0_COMMON_BYTE_VECTOR_HPP
|
||||
#endif /* byte_vector_h */
|
||||
|
||||
@@ -9,11 +9,9 @@
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
@@ -864,12 +862,12 @@ auto AbstractMCTSAI::LogSearchResults(
|
||||
// Log selected command
|
||||
const std::string selectedDesc =
|
||||
bestChild->action ? bestChild->action->getDescription() : "Unknown";
|
||||
std::ostringstream selectedLine;
|
||||
selectedLine << std::fixed << std::setprecision(2) << "MCTS: Selected " << selectedDesc
|
||||
<< " (visit:" << bestChild->visitCount << ", reward:" << bestChild->averageReward
|
||||
<< ", lookahead:" << bestChild->lookaheadScore << ") from "
|
||||
<< rootNode->children.size() << " options";
|
||||
std::cout << selectedLine.str() << '\n';
|
||||
printf("MCTS: Selected %s (visit:%d, reward:%.2f, lookahead:%.2f) from %zu options\n",
|
||||
selectedDesc.c_str(),
|
||||
bestChild->visitCount,
|
||||
bestChild->averageReward,
|
||||
bestChild->lookaheadScore,
|
||||
rootNode->children.size());
|
||||
|
||||
// Log top 3 actions by visits
|
||||
std::vector<const MCTSNode*> sortedChildren;
|
||||
@@ -881,17 +879,20 @@ auto AbstractMCTSAI::LogSearchResults(
|
||||
|
||||
// Show all actions if there are <= 10, otherwise top 5
|
||||
const size_t numToShow = sortedChildren.size() <= 10 ? sortedChildren.size() : 5;
|
||||
std::cout << "MCTS: Top " << numToShow << " actions by visits (out of " << sortedChildren.size()
|
||||
<< " total):\n";
|
||||
printf("MCTS: Top %zu actions by visits (out of %zu total):\n",
|
||||
numToShow,
|
||||
sortedChildren.size());
|
||||
for (size_t i = 0; i < numToShow; ++i) {
|
||||
const auto* child = sortedChildren[i];
|
||||
std::ostringstream line;
|
||||
line << std::fixed << std::setprecision(2) << " [" << i << "] visits:" << child->visitCount
|
||||
<< " avgReward:" << child->averageReward << " immediate:" << child->immediateScore
|
||||
<< " lookahead:" << child->lookaheadScore;
|
||||
printf(" [%zu] visits:%d avgReward:%.2f immediate:%.2f lookahead:%.2f",
|
||||
i,
|
||||
child->visitCount,
|
||||
child->averageReward,
|
||||
child->immediateScore,
|
||||
child->lookaheadScore);
|
||||
|
||||
// Show the action's own description
|
||||
if (child->action) { line << ' ' << child->action->getDescription(); }
|
||||
if (child->action) { printf(" %s", child->action->getDescription().c_str()); }
|
||||
|
||||
// Show sequence preview
|
||||
if (!child->children.empty()) {
|
||||
@@ -904,15 +905,16 @@ auto AbstractMCTSAI::LogSearchResults(
|
||||
}
|
||||
}
|
||||
if (bestNext && bestNext->action) {
|
||||
line << " -> " << bestNext->action->getDescription();
|
||||
printf(" -> %s", bestNext->action->getDescription().c_str());
|
||||
}
|
||||
}
|
||||
std::cout << line.str() << '\n';
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
std::cout << "MCTS: Tree stats - max depth:" << result.searchDepth
|
||||
<< ", total nodes:" << result.nodesEvaluated
|
||||
<< ", root visits:" << rootNode->visitCount << '\n';
|
||||
printf("MCTS: Tree stats - max depth:%d, total nodes:%d, root visits:%d\n",
|
||||
result.searchDepth,
|
||||
result.nodesEvaluated,
|
||||
rootNode->visitCount);
|
||||
|
||||
// Log best sequence from chosen action (following most-visited path)
|
||||
std::vector<const MCTSNode*> bestSequence;
|
||||
@@ -938,10 +940,7 @@ auto AbstractMCTSAI::LogSearchResults(
|
||||
}
|
||||
|
||||
if (!bestSequence.empty()) {
|
||||
std::ostringstream sequenceLine;
|
||||
sequenceLine << std::fixed << std::setprecision(2)
|
||||
<< "MCTS: Best sequence from chosen action (final: " << sequenceScore << "):";
|
||||
std::cout << sequenceLine.str() << '\n';
|
||||
printf("MCTS: Best sequence from chosen action (final: %.2f):\n", sequenceScore);
|
||||
int displayedStep = 0;
|
||||
for (size_t i = 0; i < bestSequence.size(); ++i) {
|
||||
const auto* node = bestSequence[i];
|
||||
@@ -950,31 +949,29 @@ auto AbstractMCTSAI::LogSearchResults(
|
||||
if (i > 0 && node->parent && node->parent->IsChanceNode()) { continue; }
|
||||
|
||||
displayedStep++;
|
||||
std::ostringstream line;
|
||||
line << " " << displayedStep << ".";
|
||||
if (node->action) { line << ' ' << node->action->getDescription(); }
|
||||
printf(" %d.", displayedStep);
|
||||
if (node->action) { printf(" %s", node->action->getDescription().c_str()); }
|
||||
|
||||
// If this is a chance node, display outcome probabilities and scores
|
||||
if (node->IsChanceNode() && !node->outcomeProbabilities.empty()) {
|
||||
line << " [";
|
||||
printf(" [");
|
||||
for (size_t j = 0; j < node->outcomeProbabilities.size(); ++j) {
|
||||
if (j > 0) { line << ", "; }
|
||||
if (j > 0) printf(", ");
|
||||
const double prob = node->outcomeProbabilities[j] * 100;
|
||||
// Show lookahead score for each outcome if child exists
|
||||
if (j < node->children.size() && node->children[j]->visitCount > 0) {
|
||||
line << std::fixed << std::setprecision(0) << prob << "%->"
|
||||
<< std::setprecision(1) << node->children[j]->lookaheadScore;
|
||||
printf("%.0f%%->%.1f", prob, node->children[j]->lookaheadScore);
|
||||
} else {
|
||||
line << std::fixed << std::setprecision(0) << prob << "%->?";
|
||||
printf("%.0f%%->?", prob);
|
||||
}
|
||||
}
|
||||
line << "]";
|
||||
printf("]");
|
||||
}
|
||||
|
||||
line << std::fixed << std::setprecision(2) << " (visits:" << node->visitCount
|
||||
<< ", immediate:" << node->immediateScore << ", lookahead:" << node->lookaheadScore
|
||||
<< ")";
|
||||
std::cout << line.str() << '\n';
|
||||
printf(" (visits:%d, immediate:%.2f, lookahead:%.2f)\n",
|
||||
node->visitCount,
|
||||
node->immediateScore,
|
||||
node->lookaheadScore);
|
||||
|
||||
// For non-root nodes in the sequence, show what the top alternatives were
|
||||
// Skip showing alternatives for chance nodes (they have outcome children, not action
|
||||
@@ -994,17 +991,17 @@ auto AbstractMCTSAI::LogSearchResults(
|
||||
});
|
||||
|
||||
// Show top 3 alternatives at this decision point
|
||||
std::cout << " Alternatives at this node (" << siblings.size() << " total):\n";
|
||||
printf(" Alternatives at this node (%zu total):\n", siblings.size());
|
||||
const size_t topN = std::min(siblings.size(), size_t(3));
|
||||
for (size_t j = 0; j < topN; ++j) {
|
||||
const auto* alt = siblings[j];
|
||||
std::ostringstream altLine;
|
||||
altLine << std::fixed << std::setprecision(2) << " [" << j
|
||||
<< "] visits:" << alt->visitCount
|
||||
<< " immediate:" << alt->immediateScore
|
||||
<< " lookahead:" << alt->lookaheadScore;
|
||||
if (alt->action) { altLine << ' ' << alt->action->getDescription(); }
|
||||
std::cout << altLine.str() << '\n';
|
||||
printf(" [%zu] visits:%d immediate:%.2f lookahead:%.2f",
|
||||
j,
|
||||
alt->visitCount,
|
||||
alt->immediateScore,
|
||||
alt->lookaheadScore);
|
||||
if (alt->action) { printf(" %s", alt->action->getDescription().c_str()); }
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1016,7 +1013,7 @@ auto AbstractMCTSAI::DumpTreeToFile(const MCTSNode* root, const std::string& fil
|
||||
|
||||
std::ofstream out(filepath);
|
||||
if (!out) {
|
||||
std::cerr << "Failed to open dump file: " << filepath << '\n';
|
||||
fprintf(stderr, "Failed to open dump file: %s\n", filepath.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1045,7 +1042,7 @@ auto AbstractMCTSAI::DumpTreeToFile(const MCTSNode* root, const std::string& fil
|
||||
out << "\n=== End of Tree Dump ===\n";
|
||||
out.close();
|
||||
|
||||
std::cout << "MCTS: Tree dumped to " << filepath << '\n';
|
||||
printf("MCTS: Tree dumped to %s\n", filepath.c_str());
|
||||
}
|
||||
|
||||
auto AbstractMCTSAI::DumpNodeRecursive(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
load("//tools:copts.bzl", "COPTS")
|
||||
|
||||
cc_library(
|
||||
|
||||
@@ -141,8 +141,9 @@ public:
|
||||
// Default implementation: no filtering, so filtered index = original index
|
||||
[[nodiscard]] virtual size_t mapFilteredIndexToOriginal(
|
||||
size_t filteredIndex,
|
||||
const MCTSGameState& /*state*/) const {
|
||||
const MCTSGameState& state) const {
|
||||
// Default: no filtering, index stays the same
|
||||
(void)state; // Suppress unused parameter warning
|
||||
return filteredIndex;
|
||||
}
|
||||
|
||||
@@ -157,4 +158,4 @@ public:
|
||||
} // namespace mcts
|
||||
} // namespace shardok
|
||||
|
||||
#endif // EAGLE0_MCTS_GAME_ENGINE_HPP
|
||||
#endif // EAGLE0_MCTS_GAME_ENGINE_HPP
|
||||
@@ -6,11 +6,8 @@
|
||||
#define EAGLE0_ABSTRACT_MCTSNODE_HPP
|
||||
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "MCTSAction.hpp"
|
||||
@@ -208,13 +205,13 @@ struct MCTSNode {
|
||||
isMaximizingPlayer ? child->lookaheadScore : -child->lookaheadScore;
|
||||
const double explorationValue =
|
||||
explorationConstant * std::sqrt(std::log(visitCount) / child->visitCount);
|
||||
std::ostringstream line;
|
||||
line << std::fixed << std::setprecision(2)
|
||||
<< " UCB: " << (isMaximizingPlayer ? "MAX" : "MIN")
|
||||
<< " lookahead=" << child->lookaheadScore << " expl=" << exploitationValue
|
||||
<< " (+" << explorationValue << ") = " << value << " ["
|
||||
<< (child->action ? child->action->getDescription() : "root") << "]";
|
||||
std::cout << line.str() << '\n';
|
||||
printf(" UCB: %s lookahead=%.2f expl=%.2f (+%.2f) = %.2f [%s]\n",
|
||||
isMaximizingPlayer ? "MAX" : "MIN",
|
||||
child->lookaheadScore,
|
||||
exploitationValue,
|
||||
explorationValue,
|
||||
value,
|
||||
child->action ? child->action->getDescription().c_str() : "root");
|
||||
}
|
||||
|
||||
if (value > bestValue) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
|
||||
load("//tools:copts.bzl", "COPTS")
|
||||
|
||||
cc_library(
|
||||
@@ -28,6 +27,36 @@ cc_binary(
|
||||
deps = [":shardok-server-lib"],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "shardok-inspector",
|
||||
srcs = ["shardok_inspector.cpp"],
|
||||
copts = COPTS,
|
||||
data = [
|
||||
"//src/main/resources/net/eagle0/shardok:battalion_types",
|
||||
"//src/main/resources/net/eagle0/shardok:settings",
|
||||
"//src/main/resources/net/eagle0/shardok/maps",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//src/main/cpp/net/eagle0/common:byte_vector",
|
||||
"//src/main/cpp/net/eagle0/common:filesystem_utils",
|
||||
"//src/main/cpp/net/eagle0/common:tsv_parser",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:engine",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "corrupt-game-checker",
|
||||
srcs = ["CorruptGameChecker.cpp"],
|
||||
copts = COPTS,
|
||||
deps = [
|
||||
"//src/main/cpp/net/eagle0/common:byte_vector",
|
||||
"//src/main/cpp/net/eagle0/common:filesystem_utils",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:game_state_w",
|
||||
"//src/main/protobuf/net/eagle0/common:shardok_internal_interface_cc_grpc",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "hex-map-migrator",
|
||||
srcs = ["HexMapMigrator.cpp"],
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Created by Dan Crosby on 1/9/22.
|
||||
//
|
||||
|
||||
#include "src/main/cpp/net/eagle0/common/byte_vector.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
|
||||
#include "src/main/protobuf/net/eagle0/common/shardok_internal_interface.pb.h"
|
||||
#include "src/main/protobuf/net/eagle0/shardok/storage/game.pb.h"
|
||||
|
||||
auto main(int argc, char** argv) -> int {
|
||||
char* path = argv[1];
|
||||
|
||||
printf("Checking file %s\n", path);
|
||||
|
||||
auto bytes = byte_vector::FromPath(path);
|
||||
|
||||
net::eagle0::shardok::storage::Game game;
|
||||
game.ParseFromArray(bytes.data(), bytes.size());
|
||||
|
||||
printf("The game ID is %s\n", game.game_id().c_str());
|
||||
printf("The size is %lu\n", bytes.size());
|
||||
|
||||
const int arCount = game.action_result_size();
|
||||
printf("There are %d results\n", arCount);
|
||||
|
||||
for (int arIndex = 0; arIndex < arCount; arIndex++) {
|
||||
shardok::GameStateW gameState =
|
||||
shardok::GameStateW::FromByteString(game.action_result(arIndex).state_after_fb());
|
||||
const auto* hexMap = gameState->hex_map();
|
||||
|
||||
for (int terrainIndex = 0; terrainIndex < hexMap->terrain()->size(); terrainIndex++) {
|
||||
const auto* terrain = hexMap->terrain()->Get(terrainIndex);
|
||||
if (terrain->type() == net::eagle0::shardok::storage::fb::Terrain_::Type_STILL_WATER)
|
||||
continue;
|
||||
if (terrain->type() == net::eagle0::shardok::storage::fb::Terrain_::Type_RIVER)
|
||||
continue;
|
||||
|
||||
if (terrain->modifier().bridge().present()) {
|
||||
printf("Caught one! arIndex %d, terrainIndex %d\n", arIndex, terrainIndex);
|
||||
printf("Action result was %s\n",
|
||||
game.action_result(arIndex).action_result().DebugString().c_str());
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// Created by Dan Crosby on 3/19/20.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdio>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/common/byte_vector.hpp"
|
||||
@@ -14,7 +14,7 @@ void ModifyMap(HexMapProto& map);
|
||||
|
||||
int main(const int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " input_path output_path\n";
|
||||
printf("Usage: %s input_path output_path\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -22,25 +22,21 @@ int main(const int argc, char** argv) {
|
||||
const std::string outputPath = argv[2];
|
||||
const byte_vector mapProtoBytes = byte_vector::FromPath(inputPath);
|
||||
net::eagle0::shardok::common::HexMap mapProto;
|
||||
if (!mapProto.ParseFromArray(mapProtoBytes.data(), static_cast<int>(mapProtoBytes.size()))) {
|
||||
std::cerr << "Failed to parse hex map from " << inputPath << '\n';
|
||||
return -1;
|
||||
}
|
||||
mapProto.ParseFromArray(mapProtoBytes.data(), (int)mapProtoBytes.size());
|
||||
|
||||
ModifyMap(mapProto);
|
||||
const std::string outputString = mapProto.SerializeAsString();
|
||||
if (FilesystemUtils::AtomicallySaveToPath(outputPath, byte_vector(outputString))) return 0;
|
||||
|
||||
std::cerr << "Failed to save.\n";
|
||||
printf("Failed to save.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ModifyMap(HexMapProto& map) {
|
||||
const int terrainCount = map.terrain_size();
|
||||
for (int i = 0; i < terrainCount; i++) {
|
||||
for (int i = 0; i < map.terrain_size(); i++) {
|
||||
const auto existingTerrainType = map.terrain(i).type();
|
||||
const auto newTerrain =
|
||||
static_cast<net::eagle0::shardok::common::Terrain_Type>(existingTerrainType + 2);
|
||||
(net::eagle0::shardok::common::Terrain_Type)(existingTerrainType + 2);
|
||||
map.mutable_terrain(i)->set_type(newTerrain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,52 +4,50 @@
|
||||
|
||||
#include "MapInfoCalculator.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIWaterCrossingCalculator.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/HexMapHelpers.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
|
||||
|
||||
auto LoadMapFb(const std::string& mapName) -> shardok::Wrapper<shardok::HexMap> {
|
||||
using namespace shardok;
|
||||
using std::endl;
|
||||
|
||||
auto LoadMapFb(const string& mapName) -> Wrapper<HexMap> {
|
||||
const HexMapProto mapProto = LoadMap(mapName);
|
||||
|
||||
flatbuffers::FlatBufferBuilder fbb{};
|
||||
const auto hexMapOffset = shardok::fb::ConvertHexMapProto(fbb, mapProto);
|
||||
const auto hexMapOffset = fb::ConvertHexMapProto(fbb, mapProto);
|
||||
fbb.Finish(hexMapOffset);
|
||||
return shardok::Wrapper<shardok::HexMap>(fbb);
|
||||
return Wrapper<HexMap>(fbb);
|
||||
}
|
||||
|
||||
auto CalculateMap(
|
||||
const std::string& mapName,
|
||||
const std::shared_ptr<shardok::ActionPointDistancesCache>& apdCache,
|
||||
const shardok::BattalionTypeSPtr& battalionType) -> OneMapInfo {
|
||||
const string& mapName,
|
||||
const std::shared_ptr<ActionPointDistancesCache>& apdCache,
|
||||
const BattalionTypeSPtr& battalionType) -> OneMapInfo {
|
||||
auto hexMap = LoadMapFb(mapName);
|
||||
|
||||
const auto mapId = shardok::ActionPointDistancesCache::GetMapId(hexMap);
|
||||
const auto mapId = ActionPointDistancesCache::GetMapId(hexMap);
|
||||
|
||||
const shardok::CoordsSet criticalTileLocations = shardok::GetCriticalTileLocations(hexMap);
|
||||
const CoordsSet criticalTileLocations = GetCriticalTileLocations(hexMap);
|
||||
|
||||
OneMapInfo info{
|
||||
.castleCount = criticalTileLocations.size(),
|
||||
.name = mapName,
|
||||
.positionsRequiringCrossing = {}};
|
||||
|
||||
for (std::size_t i = 0; i < hexMap->attacker_starting_positions()->size(); i++) {
|
||||
for (unsigned int i = 0; i < hexMap->attacker_starting_positions()->size(); i++) {
|
||||
const auto* positionList = hexMap->attacker_starting_positions()->Get(i);
|
||||
const auto* positions = positionList->positions();
|
||||
if (positions->empty()) continue;
|
||||
if (positions->size() != 10) {
|
||||
std::cerr << "Should have 10 starting positions!" << '\n';
|
||||
std::abort();
|
||||
if (positionList->positions()->size() < 1) continue;
|
||||
if (positionList->positions()->size() != 10) {
|
||||
printf("Should have 10 starting positions!");
|
||||
abort();
|
||||
}
|
||||
|
||||
int positionsRequiringCrossing = 0;
|
||||
for (const auto* startingPosition : *positions) {
|
||||
for (const shardok::Coords& castlePosition : criticalTileLocations) {
|
||||
if (!shardok::CanReach(
|
||||
for (const auto* startingPosition : *positionList->positions()) {
|
||||
for (const Coords& castlePosition : criticalTileLocations) {
|
||||
if (!CanReach(
|
||||
*startingPosition,
|
||||
castlePosition,
|
||||
hexMap,
|
||||
@@ -58,11 +56,11 @@ auto CalculateMap(
|
||||
battalionType)) {
|
||||
++positionsRequiringCrossing;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
info.positionsRequiringCrossing[static_cast<int>(i)] = positionsRequiringCrossing;
|
||||
info.positionsRequiringCrossing[i] = positionsRequiringCrossing;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,19 @@
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
|
||||
|
||||
struct alignas(64) OneMapInfo {
|
||||
using namespace shardok;
|
||||
using std::map;
|
||||
using std::string;
|
||||
|
||||
struct OneMapInfo {
|
||||
uint32_t castleCount;
|
||||
std::string name;
|
||||
std::map<int, int> positionsRequiringCrossing;
|
||||
};
|
||||
string name;
|
||||
map<int, int> positionsRequiringCrossing;
|
||||
} __attribute__((aligned(64)));
|
||||
|
||||
auto CalculateMap(
|
||||
const std::string& mapName,
|
||||
const std::shared_ptr<shardok::ActionPointDistancesCache>& apdCache,
|
||||
const shardok::BattalionTypeSPtr& battalionType) -> OneMapInfo;
|
||||
const string& mapName,
|
||||
const std::shared_ptr<ActionPointDistancesCache>& apdCache,
|
||||
const BattalionTypeSPtr& battalionType) -> OneMapInfo;
|
||||
|
||||
#endif // EAGLE0_MAPINFOCALCULATOR_HPP
|
||||
|
||||
@@ -2,42 +2,37 @@
|
||||
// Created by Dan Crosby on 4/18/22.
|
||||
//
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "MapInfoCalculator.hpp"
|
||||
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/util/BattalionTypeRegistrar.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
|
||||
|
||||
using namespace shardok;
|
||||
using std::endl;
|
||||
|
||||
auto main(const int argc, char** argv) -> int {
|
||||
FilesystemUtils::SetExecPath(argv[0]);
|
||||
|
||||
std::ostream* outputStreamPtr = &std::cout;
|
||||
std::ofstream outputFile{};
|
||||
if (argc > 1) {
|
||||
const std::string outputPath = argv[1];
|
||||
outputFile.open(outputPath);
|
||||
outputStreamPtr = &outputFile;
|
||||
const string outputPath = argv[1];
|
||||
outputStreamPtr = new std::ofstream(outputPath);
|
||||
}
|
||||
std::ostream& outputStream = *outputStreamPtr;
|
||||
|
||||
const auto gameSettings = std::make_shared<shardok::GameSettings>();
|
||||
const auto gameSettings = std::make_shared<GameSettings>();
|
||||
auto setter = gameSettings->GetSetter();
|
||||
shardok::BattalionTypeRegistrar::RegisterBattalionTypes(setter);
|
||||
const shardok::BattalionTypeSPtr& battalionType = gameSettings->GetGetter().GetBattalionType(
|
||||
BattalionTypeRegistrar::RegisterBattalionTypes(setter);
|
||||
const BattalionTypeSPtr& battalionType = gameSettings->GetGetter().GetBattalionType(
|
||||
net::eagle0::shardok::storage::fb::BattalionTypeId_LIGHT_INFANTRY);
|
||||
|
||||
const std::vector<std::string> mapNames = GetMapNames();
|
||||
std::vector<OneMapInfo> mapInfos{};
|
||||
mapInfos.reserve(mapNames.size());
|
||||
for (const std::string& mapName : mapNames) {
|
||||
std::shared_ptr<shardok::ActionPointDistancesCache> apdCache =
|
||||
std::make_shared<shardok::ActionPointDistancesCache>();
|
||||
vector<OneMapInfo> mapInfos{};
|
||||
for (const string& mapName : GetMapNames()) {
|
||||
std::shared_ptr<ActionPointDistancesCache> apdCache =
|
||||
std::make_shared<ActionPointDistancesCache>();
|
||||
|
||||
mapInfos.push_back(CalculateMap(mapName, apdCache, battalionType));
|
||||
}
|
||||
@@ -46,29 +41,29 @@ auto main(const int argc, char** argv) -> int {
|
||||
bool firstMap = true;
|
||||
for (const OneMapInfo& mapInfo : mapInfos) {
|
||||
if (firstMap) {
|
||||
outputStream << '\n';
|
||||
outputStream << endl;
|
||||
firstMap = false;
|
||||
} else {
|
||||
outputStream << "," << '\n';
|
||||
outputStream << "," << endl;
|
||||
}
|
||||
outputStream << " {" << '\n';
|
||||
outputStream << R"( "name": ")" << mapInfo.name << "\"," << '\n';
|
||||
outputStream << " {" << endl;
|
||||
outputStream << R"( "name": ")" << mapInfo.name << "\"," << endl;
|
||||
|
||||
outputStream << " \"castleCount\": " << mapInfo.castleCount << "," << '\n';
|
||||
outputStream << " \"castleCount\": " << mapInfo.castleCount << "," << endl;
|
||||
outputStream << " \"positions\": {";
|
||||
|
||||
bool firstPosition = true;
|
||||
for (const auto& [position, count] : mapInfo.positionsRequiringCrossing) {
|
||||
if (firstPosition) {
|
||||
outputStream << '\n';
|
||||
outputStream << endl;
|
||||
firstPosition = false;
|
||||
} else {
|
||||
outputStream << "," << '\n';
|
||||
outputStream << "," << endl;
|
||||
}
|
||||
|
||||
outputStream << " \"" << position << "\": " << count;
|
||||
}
|
||||
outputStream << '\n' << " }" << '\n' << " }";
|
||||
outputStream << endl << " }" << endl << " }";
|
||||
}
|
||||
outputStream << '\n' << "]" << '\n';
|
||||
outputStream << endl << "]" << endl;
|
||||
}
|
||||
|
||||
@@ -2,17 +2,10 @@
|
||||
// Created by Dan Crosby on 3/29/21.
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/common/UnitConversions.hpp"
|
||||
@@ -21,6 +14,8 @@
|
||||
#include "src/main/protobuf/net/eagle0/common/random_units.pb.h"
|
||||
|
||||
using net::eagle0::common::RandomUnits;
|
||||
using std::shared_ptr;
|
||||
using std::vector;
|
||||
|
||||
constexpr int THREAD_COUNT = 16;
|
||||
|
||||
@@ -33,130 +28,96 @@ std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
#endif
|
||||
|
||||
std::mutex randomMutex;
|
||||
std::vector<std::string> fileNames{};
|
||||
vector<string> fileNames{};
|
||||
|
||||
std::unique_ptr<shardok::ShardokGamesManager> manager;
|
||||
using namespace shardok;
|
||||
|
||||
ShardokGamesManager* manager;
|
||||
|
||||
auto randInt(const int lowInc, const int hiInc) {
|
||||
std::lock_guard<std::mutex> lock(randomMutex);
|
||||
return std::uniform_int_distribution(lowInc, hiInc)(g);
|
||||
}
|
||||
|
||||
void runBattle(const std::vector<shardok::Unit>& units) {
|
||||
if (fileNames.empty()) { throw std::runtime_error("No map files found"); }
|
||||
if (units.size() < 2) { throw std::runtime_error("Need at least two units to run a battle"); }
|
||||
void runBattle(const vector<shared_ptr<const UnitProto>>& units) {
|
||||
auto mapName = fileNames[randInt(0, (int)fileNames.size() - 1)];
|
||||
auto month = randInt(1, 12);
|
||||
|
||||
const auto mapName = fileNames[randInt(0, static_cast<int>(fileNames.size()) - 1)];
|
||||
const auto month = randInt(1, 12);
|
||||
|
||||
const auto maxP1UnitCount = std::min<size_t>(20, units.size() - 1);
|
||||
const auto p1UnitCount = randInt(1, static_cast<int>(maxP1UnitCount));
|
||||
std::vector<shardok::Unit> p1Units{};
|
||||
p1Units.reserve(p1UnitCount);
|
||||
auto p1UnitCount = randInt(1, 20);
|
||||
vector<UnitProto> p1Units{};
|
||||
for (int i = 0; i < p1UnitCount; i++) {
|
||||
auto unit = units[i];
|
||||
unit.mutate_player_id(0);
|
||||
p1Units.push_back(std::move(unit));
|
||||
auto unit = *units[i];
|
||||
unit.set_player_id(0);
|
||||
p1Units.push_back(unit);
|
||||
}
|
||||
|
||||
const auto maxP2UnitCount = std::min<size_t>(20, units.size() - p1UnitCount);
|
||||
const auto p2UnitCount = randInt(1, static_cast<int>(maxP2UnitCount));
|
||||
std::vector<shardok::Unit> p2Units{};
|
||||
p2Units.reserve(p2UnitCount);
|
||||
auto p2UnitCount = randInt(1, 20);
|
||||
vector<UnitProto> p2Units{};
|
||||
for (int i = p1UnitCount; i < p1UnitCount + p2UnitCount; i++) {
|
||||
auto unit = units[i];
|
||||
unit.mutate_player_id(1);
|
||||
p2Units.push_back(std::move(unit));
|
||||
auto unit = *units[i];
|
||||
unit.set_player_id(1);
|
||||
p2Units.push_back(unit);
|
||||
}
|
||||
|
||||
std::cout << "map name is " << mapName << ", " << p1UnitCount << " attacking " << p2UnitCount
|
||||
<< '\n';
|
||||
printf("map name is %s, %d attacking %d\n", mapName.c_str(), p1UnitCount, p2UnitCount);
|
||||
|
||||
std::vector<VictoryConditionProto> attVictoryConditions{
|
||||
vector<VictoryConditionProto> attVictoryConditions{
|
||||
VictoryConditionProto::VICTORY_CONDITION_HOLDS_CRITICAL_TILES,
|
||||
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING};
|
||||
std::vector<VictoryConditionProto> defVictoryConditions{
|
||||
vector<VictoryConditionProto> defVictoryConditions{
|
||||
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING,
|
||||
VictoryConditionProto::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS};
|
||||
|
||||
std::vector<shardok::PlayerInfoWithUnits> playerInfos;
|
||||
playerInfos.reserve(2);
|
||||
playerInfos.emplace_back(
|
||||
0,
|
||||
true,
|
||||
5,
|
||||
false,
|
||||
10000,
|
||||
attVictoryConditions,
|
||||
std::vector<shardok::PlayerId>{},
|
||||
p1Units,
|
||||
false);
|
||||
playerInfos.emplace_back(
|
||||
1,
|
||||
true,
|
||||
7,
|
||||
true,
|
||||
10000,
|
||||
defVictoryConditions,
|
||||
std::vector<shardok::PlayerId>{},
|
||||
p2Units,
|
||||
false);
|
||||
auto p1 = PlayerInfoWithUnits(0, true, 5, false, 10000, attVictoryConditions, {}, p1Units);
|
||||
auto p2 = PlayerInfoWithUnits(1, false, 7, false, 10000, defVictoryConditions, {}, p2Units);
|
||||
|
||||
const std::string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
|
||||
string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
|
||||
|
||||
manager->UnlockedCreateSpecifiedGame(
|
||||
gameId,
|
||||
playerInfos,
|
||||
mapName,
|
||||
"",
|
||||
month,
|
||||
TutorialBattleConfigProto{},
|
||||
"");
|
||||
manager->LockedCreateSpecifiedGame(gameId, {p1, p2}, mapName, "", month, "");
|
||||
}
|
||||
|
||||
auto LoadUnits() -> std::vector<shardok::Unit> {
|
||||
auto LoadUnits() -> vector<shared_ptr<const UnitProto>> {
|
||||
const auto bytes = byte_vector::FromPath("/tmp/random_heroes.bin");
|
||||
|
||||
RandomUnits commonUnits;
|
||||
if (!commonUnits.ParseFromArray(bytes.data(), static_cast<int>(bytes.size()))) {
|
||||
throw std::runtime_error("Failed to parse /tmp/random_heroes.bin");
|
||||
}
|
||||
std::cout << "Loaded " << commonUnits.units_size() << " units\n";
|
||||
commonUnits.ParseFromArray(bytes.data(), (int)bytes.size());
|
||||
printf("Loaded %d units\n", commonUnits.units_size());
|
||||
|
||||
std::vector<shardok::Unit> units{};
|
||||
vector<shared_ptr<const UnitProto>> units{};
|
||||
units.reserve(commonUnits.units_size());
|
||||
|
||||
for (const auto& commonUnit : commonUnits.units()) {
|
||||
// FIXME: assumes 2 players
|
||||
units.push_back(shardok::ConvertUnit(commonUnit, 0, {0, 1}));
|
||||
units.push_back(std::make_shared<const UnitProto>(ConvertUnit(commonUnit, 0, {0, 1})));
|
||||
}
|
||||
std::shuffle(units.begin(), units.end(), g);
|
||||
|
||||
return units;
|
||||
}
|
||||
|
||||
[[noreturn]] void runBattlesThread(const std::vector<shardok::Unit>& units) {
|
||||
[[noreturn]] void runBattlesThread(const vector<shared_ptr<const UnitProto>>& units) {
|
||||
while (true) { runBattle(units); }
|
||||
}
|
||||
|
||||
auto main(int /*argc*/, char** argv) -> int {
|
||||
auto main(int argc, char** argv) -> int {
|
||||
#if FIXED_SEED
|
||||
std::srand(4564564);
|
||||
g.seed(378473);
|
||||
#else
|
||||
std::srand(std::time(nullptr));
|
||||
#endif
|
||||
|
||||
FilesystemUtils::SetExecPath(argv[0]);
|
||||
|
||||
auto randomUnits = LoadUnits();
|
||||
|
||||
std::cout << "Map file directory is " << FilesystemUtils::MapFilesDirectory() << '\n';
|
||||
printf("Map file directory is %s\n", FilesystemUtils::MapFilesDirectory().c_str());
|
||||
fileNames = FilesystemUtils::FilesInDirectory(FilesystemUtils::MapFilesDirectory(), ".e0m");
|
||||
|
||||
for (auto& fileName : fileNames) { fileName.resize(fileName.size() - 4); }
|
||||
|
||||
manager = std::make_unique<shardok::ShardokGamesManager>(std::vector<std::string>());
|
||||
manager = new ShardokGamesManager(nullptr, std::vector<std::string>());
|
||||
|
||||
std::array<std::thread, THREAD_COUNT> threads;
|
||||
std::thread threads[THREAD_COUNT];
|
||||
|
||||
for (int i = 0; i < THREAD_COUNT; i++) {
|
||||
threads[i] = std::thread(runBattlesThread, randomUnits);
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace shardok {
|
||||
constexpr double kOverpowerRatio = 2.0;
|
||||
constexpr double kBraveWaterCostMultiplier = 1.2;
|
||||
|
||||
using std::pair;
|
||||
using std::shared_ptr;
|
||||
|
||||
DIST_T NormalizedCostWhenBraving(const DIST_T cost) {
|
||||
if (cost >= static_cast<double>(ActionPointDistances::IMPOSSIBLE) / kBraveWaterCostMultiplier)
|
||||
return ActionPointDistances::IMPOSSIBLE;
|
||||
@@ -153,7 +156,6 @@ auto GenerateTargetPriorities(
|
||||
vector<TargetAndAttackLocations>{});
|
||||
|
||||
vector<TargetAndDistance> targetsWithDistance;
|
||||
targetsWithDistance.reserve(targets.size());
|
||||
|
||||
// Get APDs directly from cache (now with built-in thread-local optimization)
|
||||
const auto& battType = battalionTypeGetter(unit->battalion().type());
|
||||
@@ -233,4 +235,4 @@ auto GenerateTargetPriorities(
|
||||
return allTargetsUnitsAndDistances;
|
||||
}
|
||||
|
||||
} // namespace shardok
|
||||
} // namespace shardok
|
||||
@@ -2,8 +2,8 @@
|
||||
// Created by Dan Crosby on 6/19/23.
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_SHARDOK_AI_AI_ATTACK_GROUPS_HPP
|
||||
#define EAGLE0_SHARDOK_AI_AI_ATTACK_GROUPS_HPP
|
||||
#ifndef EAGLE0_AIATTACKGROUPS_HPP
|
||||
#define EAGLE0_AIATTACKGROUPS_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
@@ -20,6 +20,7 @@ namespace shardok {
|
||||
using GameState = net::eagle0::shardok::storage::fb::GameState;
|
||||
using Unit = net::eagle0::shardok::storage::fb::Unit;
|
||||
using net::eagle0::shardok::storage::fb::PlayerInfo;
|
||||
using std::vector;
|
||||
|
||||
using BattalionTypeGetter = std::function<BattalionTypeSPtr(BattalionTypeId)>;
|
||||
|
||||
@@ -32,9 +33,9 @@ struct TargetAndAttackLocations {
|
||||
|
||||
struct TargetPriorityList {
|
||||
UnitId attackingUnitId;
|
||||
std::vector<TargetAndAttackLocations> priorityOrder;
|
||||
vector<TargetAndAttackLocations> priorityOrder;
|
||||
|
||||
TargetPriorityList(const UnitId uid, const std::vector<TargetAndAttackLocations>& po)
|
||||
TargetPriorityList(const UnitId uid, const vector<TargetAndAttackLocations>& po)
|
||||
: attackingUnitId(uid),
|
||||
priorityOrder(po) {}
|
||||
};
|
||||
@@ -63,17 +64,17 @@ auto EffectiveDistance(
|
||||
|
||||
// Chooses a list of targets in priority order for each unit.
|
||||
auto GenerateTargetPriorities(
|
||||
const std::vector<const Unit*>& occupants,
|
||||
const vector<const Unit*>& occupants,
|
||||
const HexMap* map,
|
||||
const CoordsSet& targets,
|
||||
PlayerId attackerId,
|
||||
const std::vector<const Unit*>& remainingUnits,
|
||||
const vector<const Unit*>& remainingUnits,
|
||||
const APDCache& apdCache,
|
||||
const ALCache& alCache,
|
||||
const BattalionTypeGetter& battalionTypeGetter,
|
||||
ActionPoints braveWaterCost,
|
||||
bool isLateGame = false) -> std::vector<TargetPriorityList>;
|
||||
bool isLateGame = false) -> vector<TargetPriorityList>;
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
#endif // EAGLE0_SHARDOK_AI_AI_ATTACK_GROUPS_HPP
|
||||
#endif // EAGLE0_AIATTACKGROUPS_HPP
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
namespace shardok {
|
||||
|
||||
using Unit = net::eagle0::shardok::storage::fb::Unit;
|
||||
using std::shared_ptr;
|
||||
using std::vector;
|
||||
|
||||
class alignas(128) AttackLocations {
|
||||
class AttackLocations {
|
||||
private:
|
||||
const MapIndex rowCount;
|
||||
const MapIndex columnCount;
|
||||
@@ -145,9 +147,9 @@ public:
|
||||
adjacentLocations += cs;
|
||||
allLocations += cs;
|
||||
}
|
||||
};
|
||||
} __attribute__((aligned(128)));
|
||||
|
||||
struct alignas(128) AttackLocationsCache {
|
||||
struct AttackLocationsCache {
|
||||
private:
|
||||
const MapIndex rowCount;
|
||||
const MapIndex columnCount;
|
||||
@@ -163,8 +165,8 @@ public:
|
||||
|
||||
auto CachedLocations(const Coords& singleTarget, bool lateGame) -> const AttackLocations&;
|
||||
auto CachedLocations(const CoordsSet& targets, bool lateGame) -> AttackLocations;
|
||||
auto CachedLocations(const std::vector<const Unit*>& enemies, bool lateGame) -> AttackLocations;
|
||||
};
|
||||
auto CachedLocations(const vector<const Unit*>& enemies, bool lateGame) -> AttackLocations;
|
||||
} __attribute__((aligned(128)));
|
||||
|
||||
using ALCache = std::unique_ptr<AttackLocationsCache>;
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
|
||||
bool canFlee = false;
|
||||
|
||||
vector<const Unit*> attackerUnits{};
|
||||
attackerUnits.reserve(gameState->units()->size());
|
||||
CoordsSet defenderPositions(gameState->hex_map());
|
||||
for (const Unit* unit : *gameState->units()) {
|
||||
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
|
||||
@@ -132,4 +131,4 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
|
||||
return chosenStrategy;
|
||||
}
|
||||
|
||||
} // namespace shardok
|
||||
} // namespace shardok
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user