Apr 19, 2-3 PM (23)
Apr 19, 3-4 PM (7)
Apr 19, 4-5 PM (4)
Apr 19, 5-6 PM (3)
Apr 19, 6-7 PM (8)
Apr 19, 7-8 PM (3)
Apr 19, 8-9 PM (8)
Apr 19, 9-10 PM (6)
Apr 19, 10-11 PM (25)
Apr 19, 11-12 AM (23)
Apr 20, 12-1 AM (4)
Apr 20, 1-2 AM (5)
Apr 20, 2-3 AM (2)
Apr 20, 3-4 AM (7)
Apr 20, 4-5 AM (1)
Apr 20, 5-6 AM (8)
Apr 20, 6-7 AM (12)
Apr 20, 7-8 AM (29)
Apr 20, 8-9 AM (42)
Apr 20, 9-10 AM (37)
Apr 20, 10-11 AM (95)
Apr 20, 11-12 PM (42)
Apr 20, 12-1 PM (39)
Apr 20, 1-2 PM (53)
Apr 20, 2-3 PM (68)
Apr 20, 3-4 PM (47)
Apr 20, 4-5 PM (41)
Apr 20, 5-6 PM (31)
Apr 20, 6-7 PM (24)
Apr 20, 7-8 PM (10)
Apr 20, 8-9 PM (7)
Apr 20, 9-10 PM (16)
Apr 20, 10-11 PM (28)
Apr 20, 11-12 AM (18)
Apr 21, 12-1 AM (24)
Apr 21, 1-2 AM (5)
Apr 21, 2-3 AM (13)
Apr 21, 3-4 AM (4)
Apr 21, 4-5 AM (3)
Apr 21, 5-6 AM (8)
Apr 21, 6-7 AM (15)
Apr 21, 7-8 AM (44)
Apr 21, 8-9 AM (119)
Apr 21, 9-10 AM (36)
Apr 21, 10-11 AM (35)
Apr 21, 11-12 PM (98)
Apr 21, 12-1 PM (57)
Apr 21, 1-2 PM (71)
Apr 21, 2-3 PM (60)
Apr 21, 3-4 PM (33)
Apr 21, 4-5 PM (31)
Apr 21, 5-6 PM (27)
Apr 21, 6-7 PM (38)
Apr 21, 7-8 PM (35)
Apr 21, 8-9 PM (37)
Apr 21, 9-10 PM (14)
Apr 21, 10-11 PM (34)
Apr 21, 11-12 AM (12)
Apr 22, 12-1 AM (2)
Apr 22, 1-2 AM (3)
Apr 22, 2-3 AM (3)
Apr 22, 3-4 AM (4)
Apr 22, 4-5 AM (3)
Apr 22, 5-6 AM (17)
Apr 22, 6-7 AM (34)
Apr 22, 7-8 AM (21)
Apr 22, 8-9 AM (37)
Apr 22, 9-10 AM (18)
Apr 22, 10-11 AM (47)
Apr 22, 11-12 PM (45)
Apr 22, 12-1 PM (56)
Apr 22, 1-2 PM (64)
Apr 22, 2-3 PM (44)
Apr 22, 3-4 PM (86)
Apr 22, 4-5 PM (46)
Apr 22, 5-6 PM (17)
Apr 22, 6-7 PM (10)
Apr 22, 7-8 PM (18)
Apr 22, 8-9 PM (15)
Apr 22, 9-10 PM (23)
Apr 22, 10-11 PM (31)
Apr 22, 11-12 AM (17)
Apr 23, 12-1 AM (7)
Apr 23, 1-2 AM (4)
Apr 23, 2-3 AM (4)
Apr 23, 3-4 AM (6)
Apr 23, 4-5 AM (3)
Apr 23, 5-6 AM (8)
Apr 23, 6-7 AM (17)
Apr 23, 7-8 AM (26)
Apr 23, 8-9 AM (33)
Apr 23, 9-10 AM (33)
Apr 23, 10-11 AM (29)
Apr 23, 11-12 PM (30)
Apr 23, 12-1 PM (51)
Apr 23, 1-2 PM (69)
Apr 23, 2-3 PM (65)
Apr 23, 3-4 PM (26)
Apr 23, 4-5 PM (21)
Apr 23, 5-6 PM (7)
Apr 23, 6-7 PM (7)
Apr 23, 7-8 PM (11)
Apr 23, 8-9 PM (14)
Apr 23, 9-10 PM (6)
Apr 23, 10-11 PM (28)
Apr 23, 11-12 AM (18)
Apr 24, 12-1 AM (7)
Apr 24, 1-2 AM (4)
Apr 24, 2-3 AM (7)
Apr 24, 3-4 AM (5)
Apr 24, 4-5 AM (8)
Apr 24, 5-6 AM (13)
Apr 24, 6-7 AM (12)
Apr 24, 7-8 AM (33)
Apr 24, 8-9 AM (40)
Apr 24, 9-10 AM (41)
Apr 24, 10-11 AM (71)
Apr 24, 11-12 PM (57)
Apr 24, 12-1 PM (37)
Apr 24, 1-2 PM (53)
Apr 24, 2-3 PM (34)
Apr 24, 3-4 PM (19)
Apr 24, 4-5 PM (16)
Apr 24, 5-6 PM (38)
Apr 24, 6-7 PM (26)
Apr 24, 7-8 PM (12)
Apr 24, 8-9 PM (41)
Apr 24, 9-10 PM (17)
Apr 24, 10-11 PM (30)
Apr 24, 11-12 AM (16)
Apr 25, 12-1 AM (8)
Apr 25, 1-2 AM (1)
Apr 25, 2-3 AM (10)
Apr 25, 3-4 AM (5)
Apr 25, 4-5 AM (3)
Apr 25, 5-6 AM (13)
Apr 25, 6-7 AM (1)
Apr 25, 7-8 AM (4)
Apr 25, 8-9 AM (24)
Apr 25, 9-10 AM (17)
Apr 25, 10-11 AM (4)
Apr 25, 11-12 PM (4)
Apr 25, 12-1 PM (13)
Apr 25, 1-2 PM (3)
Apr 25, 2-3 PM (10)
Apr 25, 3-4 PM (6)
Apr 25, 4-5 PM (10)
Apr 25, 5-6 PM (16)
Apr 25, 6-7 PM (12)
Apr 25, 7-8 PM (30)
Apr 25, 8-9 PM (55)
Apr 25, 9-10 PM (13)
Apr 25, 10-11 PM (20)
Apr 25, 11-12 AM (21)
Apr 26, 12-1 AM (5)
Apr 26, 1-2 AM (0)
Apr 26, 2-3 AM (2)
Apr 26, 3-4 AM (5)
Apr 26, 4-5 AM (2)
Apr 26, 5-6 AM (2)
Apr 26, 6-7 AM (3)
Apr 26, 7-8 AM (8)
Apr 26, 8-9 AM (3)
Apr 26, 9-10 AM (0)
Apr 26, 10-11 AM (2)
Apr 26, 11-12 PM (1)
Apr 26, 12-1 PM (5)
Apr 26, 1-2 PM (4)
Apr 26, 2-3 PM (0)
3,778 commits this week Apr 19, 2026 - Apr 26, 2026
feat(asteria-player): spawn-ship succeeds end-to-end against the cluster (#56)
Three coupled changes that take the spawn flow from build-only to
accepted-on-chain in the antithesis local cluster.

`Game.hs` no longer emits an explicit `payTo` for the new PILOT
NFT — `balanceTx`'s residual-MA folding (lambdasistemi/cardano-node-clients
PR #77) drops the NFT into the player's ADA change output, matching
mainnet asteria's 3-output spawn shape.

`PlayerMain.hs` calls the new `buildWith` with
`boExUnitsMargin = 1.2x` to mirror cardano-cli's submit-time
overshoot. Without it the asteria spend script's actual cost on
the cluster's submit-time evaluator runs ~751 mem above what the
client-side `evalTxExUnits` reports (cardano-ledger version drift
between CHaP 2026-02-17 here and cardano-node 10.7.1's CHaP).

`configurator.sh` writes Conway-mainnet `maxTxExUnits` (16.5 M
mem, 10 B steps) into `alonzo-genesis.json` so the cluster's
budget matches what the validator was deployed against on
mainnet. `docker-compose.yaml` mounts the script for local
iteration without rebuilding the configurator image, and
`apply-params.sh` uses `--trace-level silent` so the validator's
runtime trace overhead is stripped.

Pin bumped to the merged main SHA of cardano-node-clients
(f578d6cf...).

Verified: asteria_player_ship_spawn_attempted_1 →
ship_spawn_built_1 → ship_spawned_1 on a single attempt with no
failures, on the local docker-compose cluster.
fix(cardano): correct getWalletsByTag URL path and encode tag
The endpoint was hitting `api/project-wallet/{projectId}/cardano/tag/{tag}`,
which does not exist on the server — the route is
`api/project-wallet/{projectId}/tag/{tagstring}`. Every call returned 404
HTML even when matching wallets existed.

Also URL-encode the tag so emails and any tag with special characters
(`@`, `/`, spaces, etc.) round-trip safely through the path segment.

Bumped version to 0.2.3.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
feat: add getProjectWalletsByTag and normalize backend wallet shape
- Add `getProjectWalletsByTag(tag)` on `WalletDeveloperControlled`,
  consuming the existing `GET /api/project-wallet/:projectId/tag/:tag`
  endpoint. URL-encodes the tag and rejects empty/whitespace input.

- Add `normalizeWalletInfo` helper that maps the backend's flat
  `Web3ProjectWallet` response (top-level `pubKeyHash`,
  `stakeCredentialHash`) into the SDK's nested `MultiChainWalletInfo`
  shape. Apply it in `getProjectWallet` and `getProjectWalletsByTag`,
  replacing the unsafe `data as MultiChainWalletInfo` casts that
  previously left `walletInfo.chains.cardano.*` undefined for callers.
  Spark is intentionally omitted because the backend doesn't persist
  Spark public keys.

- Drop the dead `walletInfo.chains.spark` gate in `getWallet`. Spark
  wallets are derived entirely from the mnemonic; the gate previously
  prevented the spark branch from ever running in production.

- Update tests to mock the actual flat backend shape and assert the
  normalized output. 40/40 passing.

Bumps version 0.2.0 -> 0.2.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
flake.lock: Update
Flake lock file updates:

• Updated input 'CHaP':
    'github:input-output-hk/cardano-haskell-packages/d8156d61840f90f0721c396f0598652f7aaf402a?narHash=sha256-kB2azmnVPcQ4pFBvXCc3iKlMuoLsaPRbVP0LfD5j2Zg%3D' (2026-04-15)
  → 'github:input-output-hk/cardano-haskell-packages/3932849518c1c50018ee3dfba06b391f93a33f76?narHash=sha256-c9oScWEa0q5X0VF7G6mSsOrh%2BglHfwnUGdbHnPTVPVQ%3D' (2026-04-24)
• Updated input 'emanote':
    'github:srid/emanote/ce5b43a53e340d86b6efcd93b0c7b8015b83d930?narHash=sha256-W%2BJxQDt4Ia5dNsoKqVP4mita%2BGYBc0j/ANqeoYT8reA%3D' (2026-04-15)
  → 'github:srid/emanote/51993925f8120c6c1fa40b5c6061ac05c459054f?narHash=sha256-k4WxRhYi0n3hheV/DtWV53qTMdA2NhWNEdQ/KjcBXL4%3D' (2026-04-26)
• Added input 'emanote/dpella-jsonrpc':
    'github:dpella/jsonrpc/0a708eb6c2744e1d69822d1cb90e9e352455a51b?narHash=sha256-gdBzrXo/tm9Xcs35yYiZkf9OI3S/FEV/5eGY183ywm4%3D' (2026-02-16)
• Added input 'emanote/dpella-mcp':
    'github:dpella/mcp/52d13472d23ec11b9f6109f0fbf5159e9fda93da?narHash=sha256-wfh76/wO6j5JCIB%2B%2BvfZn4LaFz1KAdy7hBjaFIpW5ZI%3D' (2026-02-19)
• Updated input 'emanote/ema':
    'github:srid/ema/7ff434cf8f494c62de7fe6c1a36d8681929beb93?narHash=sha256-vDhjw%2BCm7HniaiIHirwM0B2yzzLYLO3HHMaZsQRL3uw%3D' (2025-07-22)
  → 'github:srid/ema/e92e52dbefea57de08ef64b4db0ea795170d6b81?narHash=sha256-rMb1a7VzMS2nmfEWAtJEKgZYg9Mc5JdSNK4y%2Bz1S/ec%3D' (2026-04-23)
• Updated input 'emanote/haskell-flake':
    'github:srid/haskell-flake/39065472d2587af93a502423276bfb98c2c6fb09?narHash=sha256-Yif99ho8GNgXP0l9vxPHCKi7X16Cf7rwVd%2BHW1cMVeQ%3D' (2025-07-14)
  → 'github:srid/haskell-flake/f52ac89b2232dd50e5d1110416ebc5bbb09265bd?narHash=sha256-Na95Y2awqZsLhFNfBNbLj0hk4zyE3eKUROB2o9Qdqi8%3D' (2026-04-21)
• Updated input 'emanote/heist-extra':
    'github:srid/heist-extra/81f1ea0cf1226215430171dbe613a2988c6cc46a?narHash=sha256-ytHgIoRlkI5K0SDq33znlY0wjlqcwoQCe1z9JfHT/Fw%3D' (2025-12-19)
  → 'github:srid/heist-extra/bddc871dd0fe68c6376c6514498320b23ff9320e?narHash=sha256-4ArDqHLR82XJJE9AA3sX1w9eyChY1HRVwAzTYwRdEx8%3D' (2026-04-25)
• Added input 'emanote/nixpkgs-latest':
    'github:nixos/nixpkgs/01fbdeef22b76df85ea168fbfe1bfd9e63681b30?narHash=sha256-GMSVw35Q%2B294GlrTUKlx087E31z7KurReQ1YHSKp5iw%3D' (2026-04-23)
• Updated input 'haskell-nix':
    'github:input-output-hk/haskell.nix/d2cffda795ee9dc29fa6c1f0904049bf5f10741a?narHash=sha256-kC0%2BbV0Iqv7XQdbPyPVusg70FxHFvUdO4r0QQuy3XlA%3D' (2026-04-19)
  → 'github:input-output-hk/haskell.nix/0d65e5a5caa0d79b2aa02c49602eff0c3a079f23?narHash=sha256-T5wVbrpXJGVPefF6%2BttKcNepqk0fAST6OKxSstJyzSc%3D' (2026-04-26)
• Updated input 'haskell-nix/hackage':
    'github:input-output-hk/hackage.nix/666ee3dc235848d35582cc60e0b18ffe811b27c9?narHash=sha256-FbPp0Go9vbeA%2BzhxqkCrsWYguBzVryu2Ecg01Ph2nyA%3D' (2026-04-19)
  → 'github:input-output-hk/hackage.nix/164126a0bf3cf52632a0d90cbeeb930720a02b44?narHash=sha256-USlr9HTzI/HjQC3COQ6PUZ0jtlA3grrffVOM3fr%2BSeQ%3D' (2026-04-26)
• Updated input 'haskell-nix/hackage-for-stackage':
    'github:input-output-hk/hackage.nix/eea910a8d440a735513d00224cb018ab934bc25d?narHash=sha256-P5G4kyt3MGSSl2R4bqy5iqPEVtZRvj%2B6DARbcuTULS0%3D' (2026-04-19)
  → 'github:input-output-hk/hackage.nix/b0ff6df0b5220214704f2590ead8a58e97af84a0?narHash=sha256-VpWgq0FevPggXeiuHickC/q62qxm6czZIDZ2aeIkNuA%3D' (2026-04-26)
• Updated input 'haskell-nix/nixpkgs-2511':
    'github:NixOS/nixpkgs/b0924ea1889b366de6bb0018a9db70b2c43a15f8?narHash=sha256-hLp6T/vKdrBQolpbN3EhJOKTXZYxJZPzpnoZz%2BfEGlE%3D' (2025-12-01)
  → 'github:NixOS/nixpkgs/74b87959b2d16f59f54d8559cf3cf26b9d907949?narHash=sha256-msT6frWJSQ2WR%2B0cpk%2BKPcZdLTLagUIsJwQwIX9JNSo%3D' (2026-04-09)
• Updated input 'haskell-nix/nixpkgs-unstable':
    'github:NixOS/nixpkgs/c1cb7d097cb250f6e1904aacd5f2ba5ffd8a49ce?narHash=sha256-hdFa0TAVQAQLDF31cEW3enWmBP%2Bb592OvHs6WVe3D8k%3D' (2025-12-01)
  → 'github:NixOS/nixpkgs/13043924aaa7375ce482ebe2494338e058282925?narHash=sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90%3D' (2026-04-11)
• Updated input 'haskell-nix/stackage':
    'github:input-output-hk/stackage.nix/687b5f55faed3953a03d4621e80ec6e51d911501?narHash=sha256-AyAVUulMNWXGGBNx2nUtS5Ywt3tERGVGYiG9gqAnm/E%3D' (2026-04-19)
  → 'github:input-output-hk/stackage.nix/3b6af61aee3e6d6479ffd8f8ef012df6e96f0983?narHash=sha256-3qY4r/l1vEpz5cybYkA0A42t6Ou4mhtx6x/awuRTyRU%3D' (2026-04-26)
• Updated input 'hercules-ci-effects':
    'github:hercules-ci/hercules-ci-effects/4a80b7e95a298b7bb4418c0a2b55fe95a662c377?narHash=sha256-3A2B8k6YCuzt5pT/CQEltUghtE6heSlk2tMYkg/fUWI%3D' (2026-04-16)
  → 'github:hercules-ci/hercules-ci-effects/e2456ee419f9d75f8382e3d6c5af4690b316a5a8?narHash=sha256-wA%2BONiwbvQIy7ERJx/ruhV7y5xku6XKstXCII5bIbdI%3D' (2026-04-19)
• Updated input 'hercules-ci-effects/flake-parts':
    'github:hercules-ci/flake-parts/f20dc5d9b8027381c474144ecabc9034d6a839a3?narHash=sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0%3D' (2026-03-01)
  → 'github:hercules-ci/flake-parts/3107b77cd68437b9a76194f0f7f9c55f2329ca5b?narHash=sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA%3D' (2026-04-01)
• Updated input 'hercules-ci-effects/nixpkgs':
    'github:NixOS/nixpkgs/c06b4ae3d6599a672a6210b7021d699c351eebda?narHash=sha256-wvfdLLWJ2I9oEpDd9PfMA8osfIZicoQ5MT1jIwNs9Tk%3D' (2026-03-13)
  → 'github:NixOS/nixpkgs/4c1018dae018162ec878d42fec712642d214fdfa?narHash=sha256-ar3rofg%2BawPB8QXDaFJhJ2jJhu%2BKqN/PRCXeyuXR76E%3D' (2026-04-09)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/399894fbc01ae0a44cf50137800290e9c2b113b1?narHash=sha256-bMHNn/otkxF8gERYpjX/C21nvimwatbrKb9PPX5vIyM%3D' (2026-04-19)
  → 'github:NixOS/nixpkgs/a5951fb3cf2eeeac787ece2e04fd23823c75ab73?narHash=sha256-RpIS/Whk/kw4A78sfi23kKzXumQLxxHQRf/11K5J80g%3D' (2026-04-26)
• Updated input 'pre-commit-hooks':
    'github:cachix/pre-commit-hooks.nix/580633fa3fe5fc0379905986543fd7495481913d?narHash=sha256-8Psjt%2BTWvE4thRKktJsXfR6PA/fWWsZ04DVaY6PUhr4%3D' (2026-04-07)
  → 'github:cachix/pre-commit-hooks.nix/3cfd774b0a530725a077e17354fbdb87ea1c4aad?narHash=sha256-PcRvlWayisPSjd0UcRQbhG8Oqw78AcPE6x872cPRHN8%3D' (2026-04-21)
feat(asteria-player): spawn-ship machinery + cluster ExUnits bump (#56)
Iteration 6b. The player binary now attempts to spawn a ship via
the asteria add_new_ship transaction:

- src/Asteria/Game.hs: SpawnShipParams + spawnShipProgram. Builds
  the full asteria add_new_ship tx shape — spendScript asteria
  with AddNewShip redeemer, attach asteria/spacetime/pellet
  scripts, mint SHIP+PILOT (spacetime) + initial FUEL (pellet),
  output updated asteria UTxO at the asteria addr, output ship
  UTxO at the spacetime addr, output pilot NFT to the wallet,
  validity range upper bound from current wallclock + buffer.
  All values mirror the iter-4 game constants exactly.

- src/Asteria/Datums.hs: ShipyardRedeemer (MintShip / BurnShip)
  and FuelRedeemer (MintFuel / BurnFuel) added.

- app/PlayerMain.hs: when player_id=1 and not yet spawned, calls
  attemptSpawn. Computes validToSlot from posixMsToSlot + 60s
  buffer to dodge OutsideValidityIntervalUTxO. Emits SDK events
  at every milestone (attempted, built, spawned/failed) with the
  full error in the details body.

- testnets/cardano_node_master/docker-compose.yaml: mount
  utxo-keys onto both player containers (was bootstrap-only).

- components/configurator/configurator.sh: bump Alonzo
  maxTxExUnits to 14M / 14B (was 10M / 10B). Asteria's
  add_new_ship script is large enough to exceed the
  cardano-cli default per-tx CPU step limit.

What works:
- spawn tx builds: passes asteria validator's structural checks
  (datum, value, ship name, pilot name, fuel mint, validity range,
  pilot NFT to wallet).
- balanceTx + signing produces a tx that's mempool-accepted
  (Submitted _ returned).

Outstanding upstream blocker: lambdasistemi/cardano-node-clients#76
— evalBudgetExUnits is hardcoded at 10B steps in the TxBuild
library, so even with the cluster's per-tx limit raised to 14B,
the redeemer's exUnits get capped at the eval budget. Phase-2
re-runs the script and reports "overspending the budget" by ~600k
steps. Iter 6c picks up after that fix lands; the spawn machinery
in this PR is correct, just constrained by the upstream cap.
feat(asteria-player): spawn-ship machinery + cluster ExUnits bump (#56)
Iteration 6b. The player binary now attempts to spawn a ship via
the asteria add_new_ship transaction:

- src/Asteria/Game.hs: SpawnShipParams + spawnShipProgram. Builds
  the full asteria add_new_ship tx shape — spendScript asteria
  with AddNewShip redeemer, attach asteria/spacetime/pellet
  scripts, mint SHIP+PILOT (spacetime) + initial FUEL (pellet),
  output updated asteria UTxO at the asteria addr, output ship
  UTxO at the spacetime addr, output pilot NFT to the wallet,
  validity range upper bound from current wallclock + buffer.
  All values mirror the iter-4 game constants exactly.

- src/Asteria/Datums.hs: ShipyardRedeemer (MintShip / BurnShip)
  and FuelRedeemer (MintFuel / BurnFuel) added.

- app/PlayerMain.hs: when player_id=1 and not yet spawned, calls
  attemptSpawn. Computes validToSlot from posixMsToSlot + 60s
  buffer to dodge OutsideValidityIntervalUTxO. Emits SDK events
  at every milestone (attempted, built, spawned/failed) with the
  full error in the details body.

- testnets/cardano_node_master/docker-compose.yaml: mount
  utxo-keys onto both player containers (was bootstrap-only).

- components/configurator/configurator.sh: bump Alonzo
  maxTxExUnits to 14M / 14B (was 10M / 10B). Asteria's
  add_new_ship script is large enough to exceed the
  cardano-cli default per-tx CPU step limit.

What works:
- spawn tx builds: passes asteria validator's structural checks
  (datum, value, ship name, pilot name, fuel mint, validity range,
  pilot NFT to wallet).
- balanceTx + signing produces a tx that's mempool-accepted
  (Submitted _ returned).

Outstanding upstream blocker: lambdasistemi/cardano-node-clients#76
— evalBudgetExUnits is hardcoded at 10B steps in the TxBuild
library, so even with the cluster's per-tx limit raised to 14B,
the redeemer's exUnits get capped at the eval budget. Phase-2
re-runs the script and reports "overspending the budget" by ~600k
steps. Iter 6c picks up after that fix lands; the spawn machinery
in this PR is correct, just constrained by the upstream cap.
feat(asteria-player): player observes asteria + plans random moves (#56)
Iteration 6.

- src/Asteria/RandomSource.hs: pluggable randomness as a record-of-
  functions. newSystemSource seeds System.Random from the player's
  ASTERIA_PLAYER_ID hash so each replica has a distinct deterministic
  stream. randomInRange wraps getU64 with a modulo-based range draw.
  Iteration 7 will add a sibling implementation that subprocesses to
  Python's antithesis.random.get_random() so the hypervisor controls
  every decision; the consuming code is already abstracted over the
  RandomSource interface.

- app/PlayerMain.hs: replaces the pp-query loop with the asteria
  observation loop:
    1. queryUTxOs at the asteria spend address.
    2. emit asteria_player_asteria_observed_<id> sometimes(true/false)
       depending on whether the bootstrap-created UTxO is visible.
    3. decode the inline AsteriaDatum and emit
       asteria_player_ship_counter_<id> with the current ship_counter.
    4. draw a random (delta_x, delta_y) in [-5, 5] via randomInRange,
       emit asteria_player_move_planned_<id> with the deltas.
    5. sleep a random 1..5 seconds.

This proves discovery + decode + RandomSource end-to-end against the
running cluster. Iter 6b will replace step 4's plan with actually
building and submitting the mintShip / moveShip txs.

Verified:
  asteria_player_ship_counter_1 {"ship_counter": 0}
  asteria_player_move_planned_1 {"delta_x": 3, "delta_y": 4}
  asteria_player_ship_counter_2 {"ship_counter": 0}
  asteria_player_move_planned_2 {"delta_x": 0, "delta_y": -4}

Distinct deltas per replica per iteration confirm the seed-by-id
strategy is producing independent streams.
feat(asteria-player): player observes asteria + plans random moves (#56)
Iteration 6.

- src/Asteria/RandomSource.hs: pluggable randomness as a record-of-
  functions. newSystemSource seeds System.Random from the player's
  ASTERIA_PLAYER_ID hash so each replica has a distinct deterministic
  stream. randomInRange wraps getU64 with a modulo-based range draw.
  Iteration 7 will add a sibling implementation that subprocesses to
  Python's antithesis.random.get_random() so the hypervisor controls
  every decision; the consuming code is already abstracted over the
  RandomSource interface.

- app/PlayerMain.hs: replaces the pp-query loop with the asteria
  observation loop:
    1. queryUTxOs at the asteria spend address.
    2. emit asteria_player_asteria_observed_<id> sometimes(true/false)
       depending on whether the bootstrap-created UTxO is visible.
    3. decode the inline AsteriaDatum and emit
       asteria_player_ship_counter_<id> with the current ship_counter.
    4. draw a random (delta_x, delta_y) in [-5, 5] via randomInRange,
       emit asteria_player_move_planned_<id> with the deltas.
    5. sleep a random 1..5 seconds.

This proves discovery + decode + RandomSource end-to-end against the
running cluster. Iter 6b will replace step 4's plan with actually
building and submitting the mintShip / moveShip txs.

Verified:
  asteria_player_ship_counter_1 {"ship_counter": 0}
  asteria_player_move_planned_1 {"delta_x": 3, "delta_y": 4}
  asteria_player_ship_counter_2 {"ship_counter": 0}
  asteria_player_move_planned_2 {"delta_x": 0, "delta_y": -4}

Distinct deltas per replica per iteration confirm the seed-by-id
strategy is producing independent streams.
feat(asteria-player): bootstrap creates the asteria UTxO (#56)
Iteration 5b. The bootstrap binary now mints the asteria admin
NFT and locks it at the asteria spend address with the initial
inline AsteriaDatum — the on-chain "asteria UTxO" that the
move_ship / mine_asteria / consume_asteria validators reference.

- aiken/validators/admin_mint.ak: new always-true mint policy.
  Its hash is baked into admin_token by apply-params.sh so the
  four parameterized validators can reference it.
- aiken/apply-params.sh: computes admin_mint hash from the
  freshly-built plutus.json and uses it as admin_token's
  policy_id (was 28 zero bytes, now the real hash).
- aiken/plutus-applied.json: regenerated with the corrected
  admin_token. New hashes:
    pellet:     009080306c62ac5db9c4c04ca4eed6e6b567405c69c34049381824dc
    deploy:     4e2021af9fd97e24d9c0ae06c616ebcaa5f947b2ea5b643733467d3d
    asteria:    0824601a437559eb07d613f2cfb74bb87b2825b2f9b30e7a2ecb88e5
    spacetime:  dd1a2baf950b225d26208acc981dbdb93f7891e3d933c4a20590e31e
    admin_mint: def68337867cb4f1f95b6b811fedbfcdd7780d10a95cc072077088ea
- src/Asteria/Validators.hs: expose adminMintScript.
- src/Asteria/Crypto.hs: tiny helper that bridges
  cardano-crypto-class hashes to PlutusTx BuiltinByteString.
- src/Asteria/Datums.hs: AsteriaDatum, ShipDatum, PelletDatum,
  AsteriaRedeemer, ShipRedeemer, PelletRedeemer with ToData /
  FromData matching Aiken's encoding.
- app/BootstrapMain.hs: replace the no-op self-pay with the
  asteria-creation tx — spend genesis UTxO, attach admin_mint,
  mint 1 admin NFT, lock 5 ADA + admin NFT at the asteria spend
  address with inline AsteriaDatum {ship_counter=0,
  shipyard_policy=spacetime_hash}. Bumped wait timeout to 180s
  to cover cluster cold-start block-propagation latency.

Verified locally: docker compose up -d → asteria-bootstrap
exits 0, on-chain query confirms a UTxO at addr_test1wq...
with inline datum {ship_counter=0, shipyard_policy=dd1a2baf...}.