og-edopro-server-ts/scripts/entrypoint.sh
Diango Gavidia 83e8cd5a73
feat(edison): MR1 (2010) format — forked core, pre-errata pool, resource pipeline (#316)
* fix(chat): mark replay hint as system message

* fix(ygopro): use full 256-bit seed entropy for server deck shuffle

The pre-duel shuffle folded the 8-word seed into a 32-bit xorshift32
state, limiting the shuffle space to 2^32 permutations and discarding
224 bits of generated entropy. Seed the shuffle with xoshiro256**
through per-lane SplitMix64 instead, preserving the full seed.

Fisher-Yates and rejection sampling are unchanged. No replay or
recovery impact: shuffled decks are stored in DuelRecord and never
re-derived from the seed.

* fix(edopro): preserve 64-bit duel seeds when launching CoreIntegrator

Seeds were passed to the core child process through Number(), whose
53-bit mantissa rounds ~99.6% of random uint64 values. The core played
with rounded seeds while the replay stored the exact ones, so replays
re-simulated with a different RNG stream and desynced.

Serialize the launch payload with the seeds spliced in as raw JSON
integer literals. The splice runs only over the config serialization,
so player-provided strings cannot forge the placeholder.

* test(edison): add MR1 behavior suite against headless ocgcore WASM

In-process harness (no worker threads) boots the bundled WASM core with
deterministic seeds and drives scripted duels. Verifies MR1 era rules on
the pinned binary (koishipro-core ^1.5.2):

- turn-1 draw for the starting player
- ignition priority (Exiled Force vs Trap Hole, duel_rule 2 differential)
- single face-up Field Spell destroyed with REASON_RULE (rule 5 differential)
- damage step: flip effects in substep 6, activation-window masking,
  Honest resolution-time ATK math

Documents two confirmed gaps as it.failing executable documentation:
- core implements modern 0 ATK vs 0 ATK battle (neither destroyed);
  2010 rule requires mutual destruction — ungated by duel_rule
- Honest script cannot activate during damage calculation (2010 ruling
  allowed substep 4; modern PSCT behavior)

* docs(edison): add format compliance roadmap

Living checklist for Edison (TCG March 2010) correctness across repos:
MR1 rules verified in core source and now by the behavior suite, banlist
audit results, April 2010 pool cutoff, pre-errata coverage (11 present,
25 missing), server wiring drift, client MR1 board layout gap, and
matchmaking scope. Records the two suite-confirmed gaps (0-ATK battle
core rule, Honest damage-calculation window).

* feat(edison): verify core compliance + 3 duel_rule<=1 fork gates

Re-verify all 13 official Edison rule-differences against the ocgcore WASM.
Fix the 3 real core gaps in the fork, each gated by duel_rule<=1 so modern
duels are untouched:
- #3 Union 1-per-monster (card::get_union_count folds modern+old)
- #13 0-ATK mutual destruction (field::calculate_battle_damage)
- #10 LP-cost-to-0 refusal (field::check_lp_cost)

Add the MR1 behavior + pre-errata behavior test suites (headless ocgcore),
green on BOTH the stock and forked WASM. Fork patch + reproducible build in
src/test-support/ocgcore/wasm/.

* feat(build): private manifest override + GitHub token for private sources

Generic add-on to fetch PRIVATE git sources (the pre-errata scripts moat today,
any private repo tomorrow) without leaking them into the public repo:

- resources.manifest.json stays PUBLIC (no private source/assembly).
- resources.manifest.private.json (gitignored) declares the private source(s) +
  their assembly; resources-lib.sh merges it over the base into an effective
  manifest at build and runtime. Explicit MANIFEST_PATH (tests) skips the merge.
- resources.manifest.private.example.json documents the format.
- scripts/setup-git-credentials.sh: env-based git credential helper for
  github.com HTTPS using a read-only GH_PRIVATE_TOKEN. Never persisted to a
  layer or disk; no-op when unset.
- Dockerfile: BuildKit secret (id=gh_private_token) for the resource builder;
  COPY resources.manifest*.json into builder + final image.
- entrypoint.sh: set up credentials before the runtime updater loop.

Host setup (not in repo): create resources.manifest.private.json; add
GH_PRIVATE_TOKEN to .env; pass --secret to docker build.

* feat(edison): switch server pool to pre-errata.es.cdb, drop classic from pool

Eliminate classic from the server card pool (base + whitelist + pre-errata
overlay model):
- manifest: assemble pre-errata.es.cdb (bilingual pool, 28 cards) instead of the
  old edison-pre-errata.cdb; remove `classic` from runtime.standard. classic.cdb
  stays assembled (the ocgcore differential tests use its codes as baselines).
- harness: load pre-errata.es.cdb.
Suite green on both cores; ResourcePoolResolver + manifest bats unaffected.

* feat(edison): deliver fork ocgcore as a resources add-on + boot verifier

The Edison fork WASM is now delivered through the resources manifest (source
"edison-core" → assembly ygopro/core/ocgcore-worker) instead of a bespoke
Docker step, so it is fetched, seeded, and refreshed by the same pipeline as
the cdbs/lflists and can be bumped by editing the manifest.

verifyEdisonCore() runs at boot: it hashes the resolved core and logs loudly
(or aborts when EDISON_CORE_REQUIRED=true) if it is missing or its sha256 does
not match the expected fork build — ending the silent fallback to the stock
core that koishipro-core.js does when the binary is absent. YGOProResourceLoader
now resolves the core path through the shared edisonCorePath() so the loader and
the verifier can never disagree.

* test: relocate ocgcore integration tests to fork and pre-errata repos

These 28 tests plus the HeadlessDuel harness exercise the core engine, not server code.

They coupled npm test to assembled resources and private pre-errata scripts.

Moved 9 engine/MR1 tests to evolution-ygopro-core and 19 pre-errata tests to the private repo.

Production CardStorage stays; the harness is vendored into each destination.

* chore: ignore local session dir and fetched core artifact

The .claude/ session dir and the root ocgcore-worker WASM (fetched via the

manifest add-on) are local artifacts and must not be versioned.

* docs: move edison roadmap out of the public repo

The roadmap is edison pre-errata research; it referenced relocated tests,

the removed harness, and the private erratas doc. Preserved in the private repo.

* chore(edison): remove damage-step window debug tracer

The DUEL_DEBUG_DS_WINDOWS trace was pre-errata research instrumentation.

Its purpose left with the relocated damage-step suite; dropped from the server.

* refactor(core): rename edisonCore to generic ocgcoreFork

The forked ocgcore is the single core the server loads for ALL rooms; its

duel_rule gates make it behave like stock in modern formats and apply pre-

errata rulings across legacy eras (Edison, GOAT, HAT). Naming it 'edisonCore'

wrongly implied it was Edison-only. Renamed module + symbols; env var

EDISON_CORE_REQUIRED -> OCGCORE_FORK_REQUIRED (new in this PR, no deployments affected).

* refactor(core): collapse ocgcore fork verifier into a logged load decision

Removed the separate boot verifier, the sha256 pin, and the OCGCORE_FORK_REQUIRED

env var. The fork-vs-stock choice is now a single explicit decision logged at

card-load time (resolveForkCorePath): fork present -> use it; absent -> loud warn

+ stock fallback. Download integrity belongs to the manifest delivery layer, and

a corrupt WASM fails loudly at instantiation, so the per-boot sha check was redundant.

* chore(docker): drop build-time token machinery; private sources are runtime-only

The forked-core seed is assembled public-only at build; the private manifest override

is .dockerignored (never in the build context) and mounted at runtime, with the token

from the container env. Removes the dead --secret mount, GH_PRIVATE_TOKEN export, and

build-time setup-git-credentials call; updates the manifest example + stale comments.

* refactor(core): resolve fork-core path once, fix stale runtime comments

resolveForkCorePath ran on every card-storage (re)load, re-logging the fork/stock

decision each 10-min refresh. Cache it in the loader (logged once); the worker still

re-reads the binary per load. Also drop the stale build-time BuildKit-secret mention

in setup-git-credentials.sh (runtime-only now).
2026-08-05 09:34:06 -04:00

18 lines
734 B
Bash

#!/bin/bash
# Container entrypoint: run the resource updater loop in the background so card
# databases / banlists refresh live (picked up by the in-memory reload), and run
# the server as the main foreground process. The image ships a baked resource
# seed, so the server boots immediately while the first refresh runs in the
# background. dumb-init (PID 1) forwards signals to the server and reaps the loop.
set -u
# Configure git to authenticate private manifest sources (read-only token from
# GH_PRIVATE_TOKEN in the container env / --env-file). No-op when unset. Must run
# before the updater loop, which re-clones sources.
bash scripts/setup-git-credentials.sh
bash scripts/resources-updater.sh &
exec node ./src/index.js