Veydrift is an onchain space project targeting Base. The public surface is intentionally quiet for now: the website should signal that something is coming without describing gameplay mechanics, economy details, factions, resources, or release promises before those systems are approved.
apps/
backend/ Bun + TypeScript HTTP service with health and GraphQL endpoints
frontend/ Preact + TypeScript + Tailwind public coming-soon app
packages/
contracts/ Foundry Solidity playable smart-contract MVP for Base deployments
circuits/ Retired circuit placeholder workspace; no zk gameplay roadmap
- Bun 1.1 or newer
- Node.js 20 or newer for frontend tooling launched by Bun
- Foundry for contract checks
bun installRun all available checks:
bun run checkRun all tests:
bun run testBuild all packages:
bun run buildStart local development servers:
bun run dev:backend
bun run dev:frontendapps/backend exposes:
GET /healthGET /debug/configGET /wallet/:address/settlementGET /wallet/:address/queuesGET /planets/:planetIdGET /universe/galaxies/:galaxy/systems/:systemGET /universe/systems?galaxy=1¢er=250&radius=2POST /webhooks/alchemyGET /graphql/POST /graphqlfor the existing minimal service status response
Index DB alignment is an explicit operator action through backend package scripts, not an HTTP route. Use the canonical sync when historical DB state must be repaired against on-chain truth:
cd apps/backend
bun run index:sync -- --from-block <block>The sync command first fetches/stores raw contract logs for the requested range, rebuilds
materialized event-derived DB tables from the full stored indexed_event_logs ledger, then runs the
explicit canonical rebuild that reads current on-chain resources, buildings, ships, defenses, queues,
research, moons, and alliance snapshots. This is the operator-only repair path for historical drift
that cannot be reconstructed from old event logs alone. Unlike startup cold rebuilds, the canonical
sync does not apply VEYDRIFT_REBUILD_DEADLINE_MS by default; pass
--sync-deadline-ms <milliseconds> only when an operator intentionally wants to cap a manual run.
For a log-only rematerialization that performs no canonical state reads, use:
cd apps/backend
bun run index:replay -- --from-block <block>For a narrow alliance-state repair that only refreshes alliance directory/member counts, join requests, and directed diplomacy from the alliance contract, use:
cd apps/backend
bun run index:sync -- --alliance-state-seedBoth commands are explicit operator tools and do not add request-time, startup, or periodic canonical RPC self-heal.
Copy apps/backend/.env.example to apps/backend/.env and provide the Base
Sepolia RPC configuration before using chain-backed routes:
VEYDRIFT_DEPLOYMENT_MODE=local
VEYDRIFT_CHAIN_ID=84532
VEYDRIFT_CONTRACT_ADDRESS=0x...
VEYDRIFT_SETTLEMENT_CONTRACT_ADDRESS=0x...
VEYDRIFT_GAME_CONTRACT_ADDRESS=0x...
VEYDRIFT_METAL_TOKEN_ADDRESS=0x...
VEYDRIFT_CRYSTAL_TOKEN_ADDRESS=0x...
VEYDRIFT_DEUTERIUM_TOKEN_ADDRESS=0x...
VEYDRIFT_INDEX_DB_PATH=.data/contract-state.sqlite
VEYDRIFT_INDEX_FROM_BLOCK=0
VEYDRIFT_MISSION_RESOLVER_PRIVATE_KEY=0x...
# Optional alternative when the RPC node has an unlocked funded resolver account.
VEYDRIFT_MISSION_RESOLVER_ADDRESS=0x...
VEYDRIFT_ALCHEMY_WEBHOOK_SIGNING_KEY=...
ALCHEMY_BASE_SEPOLIA_API_KEY=...
# Optional explicit websocket overrides; otherwise the Alchemy key derives the Base Sepolia WS URL.
VEYDRIFT_WS_RPC_URL=
ALCHEMY_BASE_SEPOLIA_WS_URL=The backend accepts ALCHEMY_BASE_SEPOLIA_API_KEY,
ALCHEMY_BASE_SEPOLIA_RPC_URL, BASE_SEPOLIA_RPC_URL, or VEYDRIFT_RPC_URL.
For websocket chain sync it accepts VEYDRIFT_WS_RPC_URL or
ALCHEMY_BASE_SEPOLIA_WS_URL, and falls back to
wss://base-sepolia.g.alchemy.com/v2/<key> when only
ALCHEMY_BASE_SEPOLIA_API_KEY is set. HTTP RPC is for writer-side event replay,
explicit operator sync, and mutating/on-chain workers; frontend/API read
requests must be served from the SQLite index or configured static metadata.
VEYDRIFT_GAME_CONTRACT_ADDRESS or the legacy VEYDRIFT_CONTRACT_ADDRESS must
point at the deployed VeydriftGame proxy for game-state APIs and runtime
Shipyard transactions.
VEYDRIFT_SETTLEMENT_CONTRACT_ADDRESS is the compact first-planet settlement
contract used for settlement/universe context and must not be used as the game
contract.
VEYDRIFT_SETTLEMENT_START_PRICE_WEI is the configured static start price used
by GET /wallet/:address/settlement-funding; the endpoint deliberately does
not call RPC to read startPrice or the wallet's native ETH balance.
VEYDRIFT_METAL_TOKEN_ADDRESS, VEYDRIFT_CRYSTAL_TOKEN_ADDRESS, and
VEYDRIFT_DEUTERIUM_TOKEN_ADDRESS expose the upgradeable ERC-20 resource token
proxies deployed for the game.
On test deployments, VEYDRIFT_MISSION_RESOLVER_PRIVATE_KEY enables the
writer-only fleet settlement keeper that submits due resolveFleetMission and
completeFleetMissionReturn transactions. VEYDRIFT_MISSION_RESOLVER_ADDRESS
is supported for RPC nodes with an unlocked funded resolver account.
Health/debug responses only report safe configuration metadata and never echo
RPC URLs or API keys. Ownership remains canonical onchain; the normal backend
serves frontend/API reads from the SQLite-backed contract state index and mutates
that DB through contract event replay/listeners only. GET /health reports the
last reconciled block, latest indexed block, in-progress state, reorg detection,
and last reconciliation error. GET /chain/events streams backend chain-event
notifications to the frontend. Manual DB alignment is intentionally separated
from HTTP routes and runs through bun run index:sync -- --from-block <block>
when canonical on-chain repair is required, or bun run index:replay -- --from-block <block>
when only stored-log rematerialization is intended. For alliance-only drift, use
bun run index:sync -- --alliance-state-seed to avoid reseeding planet/resource tables.
POST /webhooks/alchemy accepts Alchemy contract log webhook payloads, verifies
X-Alchemy-Signature when VEYDRIFT_ALCHEMY_WEBHOOK_SIGNING_KEY is configured,
and applies duplicate-safe indexed event updates.
apps/frontend is a Vite Preact app with Tailwind CSS. The current production
surface is a space-themed coming-soon page for https://veydrift.com with no
game-specific details.
packages/contracts contains the first playable Solidity MVP. VeydriftGame
is an OpenZeppelin UUPS upgradeable contract behind an ERC1967 proxy with one
home planet per wallet, deterministic coordinates, lazy resource settlement,
building upgrades, defense and ship production, technology research, and
deployment/upgrade scripts.
Veydrift is in open alpha as of 2026-05-29. Contract-affecting work must
preserve existing player state. Prefer proxy upgrades when the deployed contract
supports them; if a full redeploy is unavoidable, follow
docs/open-alpha-state-preservation.md and
docs/veydrift-contract-redeploy-runbook.md before broadcasting or marking the
task done.
cd packages/contracts
forge fmt --check
forge build
forge testResource ERC-20 deployment for an already deployed game contract:
PRIVATE_KEY=... ADMIN_ADDRESS=0xAdmin VEYDRIFT_GAME_CONTRACT_ADDRESS=0xGame \
forge script script/DeployResourceTokens.s.sol:DeployResourceTokens \
--rpc-url "$BASE_SEPOLIA_RPC_URL" --broadcast --verifyThe token deploy script emits and returns the Metal, Crystal, and Deuterium proxy addresses. Configure those addresses as backend/runtime environment variables after deployment.
Veydrift gameplay state is public onchain state. There is no privacy, zk,
hidden-state backend, committed-root, or espionage roadmap for gameplay. See
docs/public-onchain-state-architecture.md for the canonical state model and
backend/indexer boundary, and
docs/espionage-hidden-intel-decision-VEY-KANEO-196.md for the explicit
classic-espionage exclusion decision.
packages/circuits contains retired placeholder files only. It remains in the
workspace so existing scripts keep passing until a cleanup task removes or
repurposes it; it must not be used as product guidance for zk gameplay.
docs/ogame-parity-scope-VEY-KANEO-198.md is the current scope record for
Veydrift-only mechanics versus classic OGame parity work.
cd packages/circuits
bun run checkThe initial production target is the existing Hetzner Easypanel instance. The
contract deployment path is state-preserving by policy: do not redeploy alpha
contracts as a reset, and do not update backend runtime addresses without a
proxy-upgrade, no-state, or migrated-redeploy note in the Kaneo/PR handoff.
For the approved Base Sepolia VeydriftGame replacement path, follow
docs/veydriftgame-replacement-plan-VEY-KANEO-313.md before any broadcast or
runtime address change.
veydrift/frontend service is sourced from this GitHub repository on main.
Use the repository root as the EasyPanel source/build path and configure
Nixpacks with frontend-scoped commands so Bun can install the whole monorepo
workspace, including @veydrift/universe:
bun install --frozen-lockfile && rm -rf /root/.cache/nix
cd apps/frontend
bun run build
rm -rf /root/.bun/install/cache /tmp/*
bun run serveDeploy only the frontend package until backend, contract, circuit, indexing, and game-specific systems have separate implementation scopes.
The frontend also publishes Farcaster Mini App metadata at
/.well-known/farcaster.json. Before submitting it to Farcaster/Base discovery,
generate the signed accountAssociation for veydrift.com with the owning
Farcaster/Base account and replace the manifest values. The production build
emits veydrift.com manifest URLs, while the test build emits
test.veydrift.com manifest URLs with the signed test-domain association.
Share card image and button metadata live in the page-level fc:miniapp and
backward-compatible fc:frame tags; do not re-add the deprecated manifest
imageUrl or buttonTitle fields.
If the test-domain signature rotates, set
VEYDRIFT_TEST_FARCASTER_ACCOUNT_ASSOCIATION or the split
VEYDRIFT_TEST_FARCASTER_ACCOUNT_ASSOCIATION_HEADER /
VEYDRIFT_TEST_FARCASTER_ACCOUNT_ASSOCIATION_PAYLOAD /
VEYDRIFT_TEST_FARCASTER_ACCOUNT_ASSOCIATION_SIGNATURE values in the
veydrift/frontend-test deploy environment.
The separate EasyPanel app for test.veydrift.com uses two services in the
existing veydrift EasyPanel project:
veydrift/frontendremains the production coming-soon service forhttps://veydrift.com.veydrift/frontend-testserves the injected-wallet settlement test frontend athttps://test.veydrift.com.veydrift/backend-testserves the test API athttps://api-test.veydrift.com.
The test frontend must also use the repository root as the EasyPanel
source/build path. Do not set the source/build path to /apps/frontend, because
that excludes the root workspaces metadata and makes workspace:*
dependencies such as @veydrift/universe unavailable. Configure
veydrift/frontend-test with:
Source path: /
Build type: Nixpacks
Nixpacks version: 1.34.1
Install command: bun install --frozen-lockfile && cd apps/frontend && NODE_ENV=development bun install --frozen-lockfile --force && rm -rf /root/.cache/nix
Build command: cd apps/frontend && node scripts/generate-image-variants.mjs && ../../node_modules/.bin/vite build --mode settlement && rm -rf /root/.bun/install/cache /tmp/*
Start command: cd apps/frontend && bun run serve
Those commands are equivalent to running:
bun install --frozen-lockfile && cd apps/frontend
NODE_ENV=development bun install --frozen-lockfile --force && rm -rf /root/.cache/nix
node scripts/generate-image-variants.mjs
../../node_modules/.bin/vite build --mode settlement
rm -rf /root/.bun/install/cache /tmp/*
bun run serveThe test build sets VITE_VEYDRIFT_SURFACE=settlement, uses
https://test.veydrift.com for canonical/social URLs, and emits noindex
robots metadata so the injected-wallet first-planet flow can be reviewed separately from the quiet
production veydrift.com surface. Configure the frontend test service with:
VITE_VEYDRIFT_API_URL=https://api-test.veydrift.com
VITE_VEYDRIFT_SETTLEMENT_ADDRESS=0x8bA1807073ac642A55596A4934c49115E400cD2f
VEYDRIFT_TEST_FARCASTER_ACCOUNT_ASSOCIATION={"header":"...","payload":"...","signature":"..."}
PORT=80
VEYDRIFT_TEST_FARCASTER_ACCOUNT_ASSOCIATION must be generated by the
Farcaster Mini App Manifest Tool for the exact domain test.veydrift.com. As
an alternative, set the three split variables
VEYDRIFT_TEST_FARCASTER_ACCOUNT_ASSOCIATION_HEADER,
VEYDRIFT_TEST_FARCASTER_ACCOUNT_ASSOCIATION_PAYLOAD, and
VEYDRIFT_TEST_FARCASTER_ACCOUNT_ASSOCIATION_SIGNATURE. The settlement build
intentionally rejects a missing or wrong-domain association so the test app does
not ship a veydrift.com account association.
If the Nixpacks deploy needs to be rolled back, keep the same environment
variables and switch only veydrift/frontend-test back to Dockerfile build with
Dockerfile path apps/frontend/Dockerfile.test from the repository root. Do not
repoint or modify the production veydrift/frontend service while rolling back
the test frontend.
The backend test service must also use the repository root as the EasyPanel
source/build path so Bun can install the monorepo workspace and resolve
@veydrift/universe. Do not set the source/build path to /apps/backend.
Configure veydrift/backend-test with:
Source path: /
Build type: Nixpacks
Nixpacks version: 1.34.1
Nixpacks config path: apps/backend/nixpacks.test.toml
Install command: bun install --frozen-lockfile && rm -rf /root/.cache/nix
Build command: cd apps/backend && bun run build && rm -rf /root/.bun/install/cache /tmp/*
Start command: cd apps/backend && bun run start
The committed apps/backend/nixpacks.test.toml build command also writes
.veydrift-backend-build-sha at both the repository root and apps/backend/
from git rev-parse HEAD. The backend reads that artifact for
backend.build.gitSha so /runtime-config identifies the actual source image
even when contract deployment manifest variables such as
VEYDRIFT_DEPLOYMENT_COMMIT point at an older contract redeploy commit.
Configure it with:
VEYDRIFT_ALLOWED_ORIGIN=https://test.veydrift.com
VEYDRIFT_CHAIN_ID=84532
VEYDRIFT_CONTRACT_ADDRESS=<Base Sepolia VeydriftGame proxy address>
VEYDRIFT_SETTLEMENT_CONTRACT_ADDRESS=<Base Sepolia compact settlement address>
VEYDRIFT_GAME_CONTRACT_ADDRESS=<Base Sepolia VeydriftGame proxy address>
VEYDRIFT_SETTLEMENT_START_PRICE_WEI=<VeydriftGame startPrice in wei, for example 50000000000000000>
VEYDRIFT_ALLIANCE_CONTRACT_ADDRESS=<Base Sepolia VeydriftAllianceSystem proxy address>
VEYDRIFT_MOON_CONTRACT_ADDRESS=<Base Sepolia VeydriftMoonSystem proxy address>
VEYDRIFT_RANDOMNESS_ENGINE_ADDRESS=<Base Sepolia RandomnessEngine proxy address>
VEYDRIFT_METAL_TOKEN_ADDRESS=<Base Sepolia VeydriftMetal ERC-20 proxy address>
VEYDRIFT_CRYSTAL_TOKEN_ADDRESS=<Base Sepolia VeydriftCrystal ERC-20 proxy address>
VEYDRIFT_DEUTERIUM_TOKEN_ADDRESS=<Base Sepolia VeydriftDeuterium ERC-20 proxy address>
VEYDRIFT_BURNING_CHICKEN_NFT_CONTRACT_ADDRESS=<Base mainnet Burning Chicken NFT address>
VEYDRIFT_BURNING_CHICKEN_BURN_CONTRACT_ADDRESS=<Base mainnet Burning Chicken burn contract address>
VEYDRIFT_BURNING_CHICKEN_BURN_SELECTOR=<4-byte burnForMoon(uint256,uint256) selector, set to 0xe1775196>
VEYDRIFT_BASE_MAINNET_RPC_URL=<Base mainnet public or redacted provider RPC URL>
VEYDRIFT_NETWORK_NAME=Base Sepolia
VEYDRIFT_PUBLIC_API_URL=https://api-test.veydrift.com
VEYDRIFT_PUBLIC_GRAPHQL_URL=https://api-test.veydrift.com/graphql
VEYDRIFT_RPC_URL=<Alchemy Base Sepolia RPC URL>
VEYDRIFT_WS_RPC_URL=<Alchemy Base Sepolia websocket RPC URL>
PORT=4000
VEYDRIFT_RPC_URL, VEYDRIFT_WS_RPC_URL, and deployer keys must come from
Vaultwarden or EasyPanel secret storage and must not be committed. When the
shared ALCHEMY_BASE_SEPOLIA_API_KEY is used, the backend derives both HTTPS
and websocket Alchemy RPC URLs.
VEYDRIFT_BURNING_CHICKEN_NFT_CONTRACT_ADDRESS,
VEYDRIFT_BURNING_CHICKEN_BURN_CONTRACT_ADDRESS, and
VEYDRIFT_BURNING_CHICKEN_BURN_SELECTOR must be set on the backend test service
before /runtime-config reports featureSupport.chickenBurnConfigured: true.
The Base mainnet RPC URL is public runtime metadata for frontend reads; prefer a
redacted/proxy provider URL if the deploy uses a paid key.
The Chicken burn listener is a separate EasyPanel service in the same veydrift
project. Deploy this after the planet-id-only Chicken burn surface is in the
image. The listener then consumes
ChickenBurned(address indexed burner,uint256 indexed tokenId,uint256 planetId);
the Veydrift moon system resolves coordinates from current Veydrift planet state
before granting the moon. Configure it from the repository root with:
Source path: /
Build type: Nixpacks
Nixpacks version: 1.34.1
Nixpacks config path: apps/chicken-burn-listener/nixpacks.toml
Install command: bun install --frozen-lockfile
Build command: cd apps/chicken-burn-listener && bun run check
Start command: cd apps/chicken-burn-listener && bun run start
Configure veydrift/chicken-burn-listener-test with these deploy secrets and
runtime values:
BASE_MAINNET_HTTP_RPC_URL=<Base mainnet Alchemy HTTPS URL from secret storage>
BASE_MAINNET_WS_RPC_URL=<Base mainnet Alchemy WSS URL from secret storage>
CHICKEN_CONTRACT_ADDRESS=<Base mainnet Burning Chicken burn/NFT contract address>
CHICKEN_BURN_EVENT_SIGNATURE=<optional deployed event signature override>
CHICKEN_BURN_START_BLOCK=<first block to scan, or 0 for full configured backfill window>
VEYDRIFT_RPC_URL=<self-hosted Base Sepolia RPC URL>
VEYDRIFT_CHAIN_ID=84532
VEYDRIFT_MOON_SYSTEM_ADDRESS=<Base Sepolia VeydriftMoonSystem proxy address>
VEYDRIFT_GRANT_PRIVATE_KEY=<moon proxy owner/admin key from secret storage>
STATE_FILE=/data/chicken-burn-listener-state.json
BACKFILL_INTERVAL_MS=15000
BACKFILL_BLOCKS=2000
MAX_RANGE_BLOCKS=90000
PORT=8080
Mount /data as durable service storage so replayed Base mainnet logs do not
resubmit already processed burn ids after a restart. The moon contract also
keeps chickenBurnMoonGranted(bytes32) as the on-chain replay guard.
To upgrade the live Base Sepolia moon proxy after a merge, run a dry run first and then broadcast from the current moon proxy owner:
cd packages/contracts
MOON_PROXY_ADDRESS=<Base Sepolia VeydriftMoonSystem proxy> PRIVATE_KEY=<owner-key> \
forge script script/UpgradeMoonSystem.s.sol:UpgradeMoonSystem --rpc-url "$VEYDRIFT_RPC_URL"
MOON_PROXY_ADDRESS=<Base Sepolia VeydriftMoonSystem proxy> PRIVATE_KEY=<owner-key> \
forge script script/UpgradeMoonSystem.s.sol:UpgradeMoonSystem --rpc-url "$VEYDRIFT_RPC_URL" --broadcastRecord the old proxy, new implementation, upgrade transaction, backend
/runtime-config, and listener /health evidence in the Kaneo handoff. Do not
paste full RPC URLs or private keys into Kaneo, GitHub, or source control.
veydrift/backend-test redeploys with the start-first (zero-downtime) strategy:
EasyPanel starts the new container before stopping the old one. Without a health
gate the proxy cuts traffic to the new container the instant the process starts —
before Bun has bound the port — so a redeploy drops requests for a second or two.
Configure EasyPanel's Health Check on veydrift/backend-test so the new
container only receives traffic once GET /health answers 200 and
{ "ok": true }:
Health check path: /health
Health check port: 4000
Interval: 5s
Timeout: 3s
Retries: 3
Start period: 30s
GET /health returns 503 with { "ok": false } until configuration is valid,
the chain-sync poller has completed a healthy head/log pass, and the SQLite
index snapshot is safe to serve. It returns 200 with { "ok": true } only
after those public-read prerequisites are ready, so EasyPanel/Swarm start-first
deploys keep the old container in rotation until the replacement can serve real
requests. This is intentionally still fast: the SQLite index DB is opened
synchronously during request-handler construction (before Bun binds the port),
and the poller kicks an immediate first pass. Heavy event replay and explicit
operator DB alignment continue in the background and must not block readiness.
Operator-run DB alignment is explicit (bun run index:sync -- --from-block <block>) rather than
a backend startup self-heal; it replays missing RPC logs, rebuilds materialized tables from the
stored event ledger, and runs the explicit canonical repair read without the startup rebuild deadline
unless --sync-deadline-ms <milliseconds> is supplied. Use
bun run index:replay -- --from-block <block> only for log-only rematerialization. The same
readiness gate is embedded as a Docker HEALTHCHECK in
apps/backend/Dockerfile.test, so the Dockerfile rollback build path carries it
automatically; Nixpacks builds cannot embed a HEALTHCHECK, so the EasyPanel
Health Check above is required for the primary Nixpacks deploy.
Before and after any backend deploy configuration change, run the continuous probe from a terminal outside the container and keep the JSON output as handoff evidence:
node scripts/veydrift-redeploy-readiness-probe.mjs \
--api-url https://api-test.veydrift.com \
--duration-seconds 180During a normal redeploy, the summary should show no multi-minute 5xx or network error window; target perceived downtime is under one second.
If the backend Nixpacks deploy needs to be rolled back, keep the same
environment variables and switch only
veydrift/backend-test back to Dockerfile build with Dockerfile path
apps/backend/Dockerfile.test from the repository root. Broader rollback is
service-local: remove the test.veydrift.com and api-test.veydrift.com
domains from the test services, or scale/delete frontend-test and
backend-test. Do not repoint or delete the production veydrift/frontend
service while rolling back the test app.
bun run start (apps/backend/src/index.ts) launches a supervisor that spawns
N = CPU count worker processes, all binding the same PORT with
SO_REUSEPORT (Bun.serve reusePort: true). The kernel load-balances incoming
connections across the workers, so a slow request handled by one worker no
longer blocks the others (VEY-KANEO-466).
- Worker 0 is the single writer. It runs chain-sync ingestion, the
websocket/polling event replay, mission resolution, and the randomness
committer. The committers submit on-chain transactions, so they must run on
exactly one worker. Explicit operator syncs such as
bun run index:replayare run outside frontend request handling. - The remaining workers are readers. They serve read requests
(
GET/HEAD/OPTIONS) from the shared SQLite index database, which is opened in WAL mode (PRAGMA journal_mode = WAL) so many readers run concurrently with the single writer. Readers skip every background loop. Every remaining mutating request (display-name and/webhooks/alchemy) is forwarded over loopback to the writer's private listener, so the writer stays the sole request-time mutator of the index and the only holder of the in-memory indexer state. The writer binds that private listener on127.0.0.1:<PORT+1>(override withVEYDRIFT_WRITER_INTERNAL_PORT) in addition to the shared reusePort socket. - The supervisor respawns a worker that exits unexpectedly and forwards
SIGTERM/SIGINT so a redeploy or Ctrl-C tears the whole pool down cleanly
(each worker still answers
GET /healththe instant it binds the port, so the EasyPanel health gate above is unchanged).
Tuning env vars:
VEYDRIFT_WORKER_COUNT=<N> # target worker count (default: min(host CPU count, 2); set 1 to force
# the original single-process behavior). Values above 2 are clamped
# to the deploy-safe cap.
VEYDRIFT_MAX_WORKER_COUNT=<N> # hard deployment cap retained for legacy service configs; this can
# lower the built-in cap but cannot raise it.
VEYDRIFT_WRITER_INTERNAL_PORT=<P> # override the writer's private loopback write listener (default
# PORT+1); only relevant when the pool has more than one worker.
# VEYDRIFT_WORKER_ROLE / VEYDRIFT_WORKER_INDEX are managed internally
# by the supervisor and should not be set by hand.
- Keep public copy non-specific until the game direction is approved.
- Do not commit secrets, credentials, generated proving artifacts, or deployment tokens.
- Keep implementation PRs small and include validation evidence.
- Track work through the Veydrift Kaneo project and Symphony.