Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .codearbiter/overrides.log
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,4 @@
[2026-08-11T20:30:56.4488092Z] | BY: SUaDtL@users.noreply.github.com | GATE: H-18 CONTEXT.md patch frontmatter-verification limitation | REASON: User explicitly approved byte-preserving removal of the falsely attributed 2026-08-11T15:19:39.3840881Z tail and this one-time H-18 override for the exact current authenticated-production-diagnostics vocabulary diff; the arbiter and stage frontmatter remains byte-for-byte unchanged.
[2026-08-13T01:50:16.6956918Z] | BY: SUaDtL@users.noreply.github.com | GATE: H-05 audit-log repair | REASON: Correct host line-ending normalization in the uncommitted sprint log while retaining committed history byte-for-byte; record this turn evidence in the existing milestone report.
[2026-08-15T09:35:00-04:00] | BY: SUaDtL@users.noreply.github.com | GATE: H-05 append-only audit-log hook | REASON: Repairing pre-existing missing physical delimiters would require rewriting immutable history. Preserve the audit bytes and append an explicit parsing-boundary correction for the immediate receipt review.
[2026-08-15T08:26:00-04:00] | BY: SUaDtL@users.noreply.github.com | GATE: H-05 append-only audit log | REASON: Correct the single trailing whitespace byte introduced by this uncommitted Quick Operations receipt, then re-append the identical receipt without whitespace so git diff --check can pass. Scope: only the current local receipt line; no historical audit record changes.
39 changes: 39 additions & 0 deletions .codearbiter/plans/quick-operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Quick Operations Plan

**Spec:** `.codearbiter/specs/quick-operations.md`
**Initiative:** `career.initiative.0001`

## Task 1 — Pure operation catalog

- [x] Add failing tests for the complete catalog, stable ids, exact option
projections, immutable returns, and unknown-id fallback.
- [x] Implement a pure shared-or-client catalog with Standard, Crosswind Range,
Caldera Run, and Last Light Siege. Use only existing `GameOptions` fields.
- [x] Add a deterministic engine replay/clone regression per non-standard card.

## Task 2 — Quick Duel composition

- [x] Add failing Lobby/view tests for the card chooser, Standard default,
selected-card briefing, and exact selected options passed into the existing
Quick Duel start path.
- [x] Implement the chooser as part of the existing deployment flow; do not add
a modal or duplicate launcher.
- [x] Add failing HUD tests for an inert operation identity in the ledger/report
and verify all non-Quick-Duel routes receive none.

## Task 3 — Real-browser player loop

- [x] Add desktop, compact, and Pixel RED browser journeys for card selection,
actual launch, one fire/settle, report identity, and visual containment.
- [x] Implement responsive composition and preserve existing touch target,
no-scroll, and safe-rail invariants.

## Task 4 — Governed delivery

- [x] Append SMARTS, RED/GREEN, and exact-package evidence to the sprint log.
- [x] Run focused tests, full client, deterministic harnesses, Edge suite,
typecheck, build, and complete Playwright matrix.
- [x] Give spec, plan, log, tests, and final diff to an adversarial reviewer;
resolve all Critical, High, and merge-blocking findings.
- [ ] Commit, open PR, require exact-head hosted CI/CodeQL/Pages, merge under
standing authority, verify deployed provenance and one production launch.
55 changes: 55 additions & 0 deletions .codearbiter/specs/quick-operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Quick Operations

**Initiative:** `career.initiative.0001`
**Status:** approved by the standing continuous-improvement authority
**Decision:** `career.operations.0001`

## Player outcome

Quick Duel stops being one repeatedly identical exhibition match. Before launch, a
player can choose a short Operations card with a clear battlefield identity and
then play it through the ordinary deterministic Hot Seat engine. The card tells
the player what changes and why it matters; it does not promise rewards,
progression, or an alternate authority path.

## Contract

- Provide exactly three curated cards plus the existing standard Quick Duel:
**Crosswind Range** (wraparound walls and wind-aided ranging), **Caldera Run** (lava hazard
terrain), and **Last Light Siege** (best-of-three with a bounded sudden-death
turn).
- Each card is a pure, immutable `GameOptions` projection with a stable id,
player-facing title, concise briefing, and authored world. The selected card
fixes only existing option fields; it creates no action type, random stream,
persistence row, reward, or entitlement.
- Quick Duel launches its existing human-versus-Medium-CPU flow using the selected
projection. Standard remains the default and keeps its current configuration.
- The operation identity is visible in the pre-launch card, in-match ledger, and
After Action Report. It is presentation derived from the local config and
never contributes to an account claim or verified-deployment evidence.
- Online rooms, Local Battle, verified deployments, anonymous progression,
network protocol, Edge functions, schema, and auth are byte-path unchanged in
this delivery. The pure catalog is deliberately reusable by a later online
operations slice.

## Determinism and safety

Every changed value is already read from `GameOptions` by `GameEngine`. The
catalog owns no RNG; the regular game seed remains the only terrain/wind source.
The test oracle must prove each card creates a deterministic engine clone/replay
identical to a direct construction with the same selected options and action
log. Unknown ids fail closed to Standard.

## Acceptance evidence

1. Unit RED/GREEN proves the exact card catalog, immutable option projections,
unknown-id fallback, and deterministic replay parity.
2. Lobby/HUD RED/GREEN proves selection, launch wiring, visible operation
identity, and absence from every excluded route.
3. Desktop, compact fine-pointer, and Pixel browser paths choose each card,
launch a real Quick Duel, retain the identity through a shot/report, and
prove fitted/no-scroll/44px touch geometry.
4. Existing client, engine, Edge, and full browser matrices remain green.
5. One adversarial review clears the exact staged package before commit; exact
PR-head CI, CodeQL, Pages provenance, and a production Quick Operations
launch complete delivery.
3 changes: 3 additions & 0 deletions .codearbiter/sprint-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -4297,3 +4297,6 @@ pm run check passed the complete chain in 75.8s; state-free staged secret scan r
[2026-08-15T06:57:26-04:00] career.initiative.0001 Shot Commitment Loop adversarial correction and local acceptance - Reviewer found the First Salvo coach exception missing from the card contract. The spec now states that an active coach owns the commitment region; its real all-profile journey reaches step 3, proves the card hidden, skips the coach, proves the card returns, then fires. Re-review PASS CLEAR with Critical 0 High 0 Medium 0 Low 0 and zero merge blockers. Final local evidence: Playwright 325 passed and 38 profile-gated skips, client 163 files and 1620 tests passed, deterministic checks passed, Edge 352/352 passed, strict typecheck passed, secret scan returned [], and diff-check passed. Boundary unchanged: UI presentation and test fixture only; exact-head commit, PR, hosted CI/CodeQL/Pages, deployment, and bounded production smoke remain required.
[2026-08-15T07:12:00-04:00] career.initiative.0001 Shot Commitment Loop delivery receipt - PR 429 reviewed head 53b8b500f09ae7d51851ab77850a61921bfc2a7f merged as main bfa552ef9e4314dacfaf388d4472f3384329e29b. Exact PR CI 31881002549 and CodeQL 31881002548 passed before merge. Exact-main CI 31881295517, CodeQL 31881295535, and Pages 31881295520 passed. Pages verified current main source and deployed provenance, then completed post-deploy live render smoke. Live deploy-meta reports bfa552ef9e4314dacfaf388d4472f3384329e29b and run 31881295520; independent production desktop browser proof of the fitted live weapon-linked firing-solution card passed. CI rendering recorded one retried weapon-intel flake but completed green with 324 passed and 38 intended skips. Boundary: client-only UI delivery; no Supabase deployment or migration. Transition: Shot Commitment Loop accepted; career.initiative.0001 remains active.
[2026-08-15T07:16:00-04:00] career.initiative.0001 Shot Commitment Loop receipt traceability correction - The delivery plan now records the exact independent production browser command: E2E_LIVE_URL=https://suadtl.github.io/singedTerra/ npx playwright test e2e/hud-layout.spec.ts grep turn command console is coherent project desktop-fine workers 1. It exited 0 with 1 passed against deployed main bfa552ef9e4314dacfaf388d4472f3384329e29b. This adds the evidence locator only; no product, deployment, or acceptance claim changes.
[2026-08-15T07:35:00-04:00] career.operations.0001 SMARTS | Selected Quick Operations over free-form advanced settings (too opaque) and a new mission-rule engine (duplicates proven deterministic options). Scores S=5 M=5 A=5 R=5 T=5 Satisfaction=5, confidence high. Three curated Quick Duel profiles reuse existing GameOptions/seed/replay paths, expose no auth/reward/authority change, and leave online/verified routes untouched for this first delivery. | Next: causal RED catalog, lobby/HUD composition, and browser journeys.
[2026-08-15T08:24:00-04:00] career.operations.0001 Quick Operations local proof | TDD RED: the pure catalog suite first failed because no option-composition seam existed; the card chooser tests first failed because no operation cards or selected briefing existed; the report test first failed because the HUD had no safely built report identity; the After Action browser route first failed because the deterministic victory fixture did not carry a selected card. GREEN: immutable Standard/Crosswind/Caldera/Last Light catalog with unknown-id fallback and per-card deterministic clone/replay parity; Quick Duel carries only public identity in local config, ledger, and report; cards launch across desktop, Pixel, and compact, and Crosswind completes one real salvo with identity retained. A full Pixel browser run exposed the chooser intruding beneath the compact commander dossier; production CSS now reserves the left deployment lane and the existing no-overlap oracle is green. | Evidence: focused client 63/63; full client 164 files/1,630 tests; deterministic npm run check PASS; Edge 352/352; typecheck and production build PASS; complete Playwright 340 passed/38 intended skips/0 failed; diff-check PASS. | Boundary: Quick Duel local composition/presentation only; no engine authority, action/protocol, online route, verified deployment, Auth, schema, Edge, award, migration, or dependency change. | Transition: final exact-package adversarial review, commit, PR, exact-head hosted CI/CodeQL/Pages, Pages provenance, and one normal production Quick Operations launch remain open.
[2026-08-15T08:44:00-04:00] career.operations.0001 Quick Operations adversarial correction | Reviewer BLOCK findings resolved: the sprint log was restored byte-for-byte to HEAD and now adds only the two Quick Operations entries; Crosswind now carries real wraparound walls through the existing Quick Duel config and truthful card/spec copy; the CPU contract correctly names Medium; every operation card has real browser focus and 44x44 geometry coverage; Crosswind's real salvo proof waits from tracking/resolving to the CPU handoff with ledger identity retained. Re-review PASS CLEAR: Critical 0 High 0 Medium 0 Low 0, merge blockers 0. | Final local evidence: full client 164 files/1,632 tests; complete Playwright 340 passed/38 intended skips/0 failed; npm run check PASS; Edge 352/352; typecheck/build PASS; diff-check PASS. | Boundary unchanged: local Quick Duel composition/presentation only; exact commit, PR, hosted CI/CodeQL/Pages, provenance, and production launch remain open.
124 changes: 124 additions & 0 deletions client/src/client/quickOperations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { describe, expect, it } from 'vitest'
import { GameEngine } from '@shared/engine/GameEngine'
import type { GameOptions } from '@shared/types/GameOptions'
import {
QUICK_OPERATIONS,
quickOperationById,
quickOperationOptions,
type QuickOperation,
} from './quickOperations'

const QUICK_DUEL_BASE_OPTIONS: GameOptions = {
maxPlayers: 2,
seed: 0x0bada55,
players: [
{ name: 'Player 1', color: '#ef5350' },
{ name: 'CPU 1', color: '#42a5f5', ai: 'easy' },
],
walls: 'concrete',
battlefieldWorld: 'ember-dusk',
hazards: 'none',
rounds: 1,
suddenDeathTurn: 0,
}

function operationOptions(value: unknown): Readonly<GameOptions> {
return quickOperationOptions(value, QUICK_DUEL_BASE_OPTIONS)
}

function finish(engine: GameEngine): void {
let ticks = 0
while (engine.getState().phase === 'FIRING' || engine.getState().phase === 'RESOLVING') {
engine.tick()
ticks += 1
expect(ticks).toBeLessThan(2_000)
}
}

function applyShotLog(engine: GameEngine): void {
expect(engine.applyAction({ type: 'set_angle', angle: 90 })).toBe(true)
expect(engine.applyAction({ type: 'set_power', power: 1 })).toBe(true)
expect(engine.applyAction({ type: 'fire' })).toBe(true)
finish(engine)
}

describe('Quick Operations catalog', () => {
it('ships the exact curated deterministic profiles', () => {
expect(QUICK_OPERATIONS).toEqual([
{ id: 'standard', title: 'Standard Duel', briefing: 'A balanced three-round duel.', settings: {} },
{
id: 'crosswind-range',
title: 'Crosswind Range',
briefing: 'Wraparound walls turn shifting wind into a ranging test.',
settings: { walls: 'wrap', battlefieldWorld: 'glassstorm-expanse' },
},
{
id: 'caldera-run',
title: 'Caldera Run',
briefing: 'Lava terrain turns every crater into a positional risk.',
settings: { hazards: 'lava', battlefieldWorld: 'obsidian-caldera' },
},
{
id: 'last-light-siege',
title: 'Last Light Siege',
briefing: 'A best-of-three duel that tightens into sudden death.',
settings: { rounds: 3, suddenDeathTurn: 12, battlefieldWorld: 'ember-dusk' },
},
] satisfies QuickOperation[])
expect(Object.isFrozen(QUICK_OPERATIONS)).toBe(true)
for (const operation of QUICK_OPERATIONS) {
expect(Object.isFrozen(operation)).toBe(true)
expect(Object.isFrozen(operation.settings)).toBe(true)
}
})

it('fails closed to Standard through catalog lookup and launch composition', () => {
const fallback = quickOperationById('untrusted-operation')
expect(fallback.id).toBe('standard')

const standardOptions = operationOptions('standard')
const unknownOptions = operationOptions('untrusted-operation')
expect(standardOptions).toEqual(QUICK_DUEL_BASE_OPTIONS)
expect(unknownOptions).toEqual(standardOptions)
expect(standardOptions).not.toBe(QUICK_DUEL_BASE_OPTIONS)
expect(unknownOptions).not.toBe(QUICK_DUEL_BASE_OPTIONS)
})

it('makes Crosswind mechanically distinct from the unchanged Standard duel', () => {
expect(operationOptions('standard').walls).toBe('concrete')
expect(operationOptions('crosswind-range').walls).toBe('wrap')
})

it.each([
'crosswind-range',
'caldera-run',
'last-light-siege',
] as const)('%s composes the selected profile once and keeps clone/replay deterministic', (id) => {
const options = operationOptions(id)
expect(options).toMatchObject(quickOperationById(id).settings)
expect(options).toMatchObject({
maxPlayers: 2,
seed: 0x0bada55,
players: QUICK_DUEL_BASE_OPTIONS.players,
})
expect(options).not.toBe(QUICK_DUEL_BASE_OPTIONS)

const original = new GameEngine(options)
expect(original.applyAction({ type: 'set_angle', angle: 90 })).toBe(true)
expect(original.applyAction({ type: 'set_power', power: 1 })).toBe(true)

const clone = original.clone()
expect(clone.getState()).toEqual(original.getState())

expect(original.applyAction({ type: 'fire' })).toBe(true)
expect(clone.applyAction({ type: 'fire' })).toBe(true)
finish(original)
finish(clone)

const replay = new GameEngine(options)
applyShotLog(replay)

expect(clone.getState()).toEqual(original.getState())
expect(replay.getState()).toEqual(original.getState())
}, 15_000)
})
46 changes: 46 additions & 0 deletions client/src/client/quickOperations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { GameOptions } from '@shared/types/GameOptions'

export type QuickOperationId = 'standard' | 'crosswind-range' | 'caldera-run' | 'last-light-siege'

export interface QuickOperation {
readonly id: QuickOperationId
readonly title: string
readonly briefing: string
readonly settings: Readonly<Pick<GameOptions,
'walls' | 'battlefieldWorld' | 'hazards' | 'rounds' | 'suddenDeathTurn'>>
}

function operation(
id: QuickOperationId,
title: string,
briefing: string,
settings: QuickOperation['settings'],
): QuickOperation {
return Object.freeze({ id, title, briefing, settings: Object.freeze({ ...settings }) })
}

export const QUICK_OPERATIONS: readonly QuickOperation[] = Object.freeze([
operation('standard', 'Standard Duel', 'A balanced three-round duel.', {}),
operation('crosswind-range', 'Crosswind Range', 'Wraparound walls turn shifting wind into a ranging test.', {
walls: 'wrap', battlefieldWorld: 'glassstorm-expanse',
}),
operation('caldera-run', 'Caldera Run', 'Lava terrain turns every crater into a positional risk.', {
hazards: 'lava', battlefieldWorld: 'obsidian-caldera',
}),
operation('last-light-siege', 'Last Light Siege', 'A best-of-three duel that tightens into sudden death.', {
rounds: 3, suddenDeathTurn: 12, battlefieldWorld: 'ember-dusk',
}),
])

export function quickOperationById(value: unknown): QuickOperation {
return QUICK_OPERATIONS.find((candidate) => candidate.id === value) ?? QUICK_OPERATIONS[0]!
}

/**
* Apply the selected operation's immutable projection without mutating the
* caller-owned launch configuration. Every Quick Duel engine receives options
* from this one composition seam rather than reimplementing profile merges.
*/
export function quickOperationOptions(value: unknown, base: GameOptions): GameOptions {
return { ...base, ...quickOperationById(value).settings }
}
Loading
Loading