|
|
@@ -0,0 +1,285 @@
|
|
|
+# Real CQ2 Game Login Implementation Plan
|
|
|
+
|
|
|
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
+
|
|
|
+**Goal:** Implement and verify the real CQ2 TCP login through authoritative SC2 world initialization.
|
|
|
+
|
|
|
+**Architecture:** Correct the transport boundary first, then add a focused detailed-server-list decoder, then implement the login state machine over the tested transport, and finally wire it into `LauncherFacade` and `Cq2Client`. Network-facing behavior is dependency-injected or exercised with loopback servers so protocol tests remain deterministic.
|
|
|
+
|
|
|
+**Tech Stack:** Python 3.12, asyncio, Pydantic 2, httpx, pytest, pytest-asyncio
|
|
|
+
|
|
|
+**Spec:** `docs/superpowers/specs/2026-09-18-real-game-login-design.md`
|
|
|
+
|
|
|
+## Global Constraints
|
|
|
+
|
|
|
+- Header layout is exactly `param3,msg_id,recog,param1,param2,sdata,codec,body_len,sequence`.
|
|
|
+- Wire headers are 48-byte Encode6 values decoded to 36-byte `<9i` values; bodies remain plaintext for `codec=0`.
|
|
|
+- Production sequence starts at zero and resets on every reconnect.
|
|
|
+- Login succeeds only after SC658 gates CS1018 and SC2 is received.
|
|
|
+- Empty role lists require an explicitly supplied role name; never create a role implicitly during a smoke test.
|
|
|
+- Existing credentials, tokens, and capture logs must not be added to Git or emitted by tests.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 1: Correct Header Semantics and Connection State
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `cq2_sdk/tests/unit/test_framing.py`
|
|
|
+- Modify: `cq2_sdk/tests/unit/test_tcp_client.py`
|
|
|
+- Modify: `cq2_sdk/tests/unit/test_message_registry.py`
|
|
|
+- Modify: `cq2_sdk/src/cq2_sdk/transport/framing.py`
|
|
|
+- Modify: `cq2_sdk/src/cq2_sdk/transport/tcp.py`
|
|
|
+
|
|
|
+**Interfaces:**
|
|
|
+- Produces: `FrameHeader.param3`, `FrameHeader.sdata`
|
|
|
+- Produces: `encode_frame(..., *, sdata=0, codec=0, sequence=0)`
|
|
|
+- Produces: `TcpClient.set_sdata(value: int) -> None`
|
|
|
+- Produces: `await TcpClient.reconnect(host: str, port: int) -> None`
|
|
|
+
|
|
|
+- [ ] **Step 1: Write failing framing tests**
|
|
|
+
|
|
|
+Assert that the literal captured heartbeat decodes to `param3=0`,
|
|
|
+`msg_id=60000`, `recog=1789709744`, `sdata=149`, and `sequence=112`. Add a
|
|
|
+round-trip test with distinct `param3=7` and `sdata=11` so swapping the words
|
|
|
+fails visibly.
|
|
|
+
|
|
|
+- [ ] **Step 2: Run the framing tests and verify RED**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_framing.py -q`
|
|
|
+
|
|
|
+Expected: failures for the missing `sdata` field and wrong word-zero/word-five
|
|
|
+mapping.
|
|
|
+
|
|
|
+- [ ] **Step 3: Implement the corrected header mapping**
|
|
|
+
|
|
|
+Rename `pre` to `param3`, add `sdata`, pack business `param3` into word zero,
|
|
|
+and pack `sdata` into word five. Keep the positional send signature unchanged.
|
|
|
+
|
|
|
+- [ ] **Step 4: Run framing tests and verify GREEN**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_framing.py -q`
|
|
|
+
|
|
|
+- [ ] **Step 5: Write failing TCP state tests**
|
|
|
+
|
|
|
+Use loopback servers to assert that `set_sdata(149)` reaches word five, two
|
|
|
+sends use sequences 0 and 1, reconnect sends sequence 0 again, and heartbeat
|
|
|
+`recog` is an injected current Unix timestamp rather than zero.
|
|
|
+
|
|
|
+- [ ] **Step 6: Run TCP tests and verify RED**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_tcp_client.py -q`
|
|
|
+
|
|
|
+- [ ] **Step 7: Implement connection state behavior**
|
|
|
+
|
|
|
+Add `sdata` ownership, reconnect cleanup/reset, and timestamped heartbeat while
|
|
|
+preserving receive-order error behavior.
|
|
|
+
|
|
|
+- [ ] **Step 8: Run transport and registry tests and verify GREEN**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_framing.py tests/unit/test_tcp_client.py tests/unit/test_message_registry.py tests/unit/test_recorder.py -q`
|
|
|
+
|
|
|
+- [ ] **Step 9: Commit Task 1**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add cq2_sdk/src/cq2_sdk/transport cq2_sdk/tests/unit/test_framing.py cq2_sdk/tests/unit/test_tcp_client.py cq2_sdk/tests/unit/test_message_registry.py cq2_sdk/tests/unit/test_recorder.py
|
|
|
+git commit -m "fix: use verified cq2 header semantics"
|
|
|
+```
|
|
|
+
|
|
|
+### Task 2: Decode and Resolve the Detailed Server List
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `cq2_sdk/src/cq2_sdk/login/server_list.py`
|
|
|
+- Create: `cq2_sdk/tests/unit/test_server_list.py`
|
|
|
+- Modify: `cq2_sdk/src/cq2_sdk/login/models.py`
|
|
|
+- Modify: `cq2_sdk/src/cq2_sdk/login/__init__.py`
|
|
|
+
|
|
|
+**Interfaces:**
|
|
|
+- Produces: `decode_detailed_server_list(payload: bytes) -> dict`
|
|
|
+- Produces: `resolve_server(data: dict, server_id: int, *, channel_id: str, game_id: int) -> ServerInfo`
|
|
|
+- Produces: `await fetch_server_info(server_id: int, *, channel_id: str, game_id: int, client: httpx.AsyncClient | None = None) -> ServerInfo`
|
|
|
+- Produces: expanded immutable `ServerInfo` fields required by login.
|
|
|
+
|
|
|
+- [ ] **Step 1: Write failing decoder tests**
|
|
|
+
|
|
|
+Generate one fixed encrypted fixture from a literal JSON document and assert
|
|
|
+decoded `srvRoot`, compact field expansion, explicit-IP precedence, main-server
|
|
|
+host construction, proxy port parsing, and typed failures for malformed input
|
|
|
+and unknown IDs.
|
|
|
+
|
|
|
+- [ ] **Step 2: Run decoder tests and verify RED**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_server_list.py -q`
|
|
|
+
|
|
|
+- [ ] **Step 3: Implement the self-contained cocos XXTEA decoder and resolver**
|
|
|
+
|
|
|
+Decode Base64, validate `b"4meJnPyl"`, decrypt little-endian XXTEA with
|
|
|
+`b"34UFXMgtkz".ljust(16, b"\\0")`, validate the embedded plaintext length,
|
|
|
+parse JSON, and map compact fields `d,p,s,u,m,t,n,g,i,x,b`.
|
|
|
+
|
|
|
+- [ ] **Step 4: Run server-list tests and verify GREEN**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_server_list.py -q`
|
|
|
+
|
|
|
+- [ ] **Step 5: Commit Task 2**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add cq2_sdk/src/cq2_sdk/login cq2_sdk/tests/unit/test_server_list.py
|
|
|
+git commit -m "feat: resolve cq2 tcp servers"
|
|
|
+```
|
|
|
+
|
|
|
+### Task 3: Implement the Login State Machine
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Replace: `cq2_sdk/tests/unit/test_game_login.py`
|
|
|
+- Modify: `cq2_sdk/src/cq2_sdk/login/game.py`
|
|
|
+- Modify: `cq2_sdk/src/cq2_sdk/messages/constants.py`
|
|
|
+- Modify: `cq2_sdk/src/cq2_sdk/messages/registry.py`
|
|
|
+
|
|
|
+**Interfaces:**
|
|
|
+- Produces: `GameLoginError(message_id: int, error_code: int, detail: str)`
|
|
|
+- Produces: `await GameLogin.login(session, server, *, char_name=None, job=0, sex=0, timeout=5.0) -> GameSession`
|
|
|
+- Produces: `GameSession(char_id: str, char_name: str, session_id: int, user_id: str, world_ready: bool)`
|
|
|
+
|
|
|
+- [ ] **Step 1: Write failing new-login/existing-role integration test**
|
|
|
+
|
|
|
+Run a real loopback `TcpClient` against a scripted server. Decode and record
|
|
|
+every outbound frame, send SC529/530/520/525/658/2 responses across the role
|
|
|
+and world connections, then assert exact literal bodies and header fields for
|
|
|
+CS2001, CS104, CS100, CS103, CS20, and CS1018.
|
|
|
+
|
|
|
+- [ ] **Step 2: Run the existing-role test and verify RED**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_game_login.py::test_new_login_existing_role_enters_world -q`
|
|
|
+
|
|
|
+- [ ] **Step 3: Implement minimal existing-role state machine**
|
|
|
+
|
|
|
+Add Encode6 token/channel helpers, expected-message filtering with a shared
|
|
|
+deadline, new-login `sdata`, role selection, world reconnect, SC658 gating,
|
|
|
+CS1018 construction, and SC2 completion.
|
|
|
+
|
|
|
+- [ ] **Step 4: Run the existing-role test and verify GREEN**
|
|
|
+
|
|
|
+Run the command from Step 2.
|
|
|
+
|
|
|
+- [ ] **Step 5: Write failing create-role and old-login tests**
|
|
|
+
|
|
|
+Assert empty SC520 without `char_name` fails before CS101; with `char_name`,
|
|
|
+CS101 JSON is exact and SC521 feeds CS103. Separately assert `SC529.recog=0`
|
|
|
+uses the old CS104 body and SC530 body reconnects before CS100.
|
|
|
+
|
|
|
+- [ ] **Step 6: Run the new tests and verify RED**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_game_login.py -q`
|
|
|
+
|
|
|
+- [ ] **Step 7: Implement role creation and old-login branches**
|
|
|
+
|
|
|
+Keep role ID as a string to avoid truncating the server's large identifiers.
|
|
|
+Prefer `last==1`, reject banned roles, and use compact UTF-8 JSON.
|
|
|
+
|
|
|
+- [ ] **Step 8: Write and verify failing error-path tests**
|
|
|
+
|
|
|
+Cover SC503, SC527, SC522, SC526, malformed slash bodies, malformed role JSON,
|
|
|
+and timeout while unrelated messages arrive.
|
|
|
+
|
|
|
+- [ ] **Step 9: Implement typed protocol errors and verify GREEN**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_game_login.py -q`
|
|
|
+
|
|
|
+- [ ] **Step 10: Commit Task 3**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add cq2_sdk/src/cq2_sdk/login/game.py cq2_sdk/src/cq2_sdk/messages cq2_sdk/tests/unit/test_game_login.py
|
|
|
+git commit -m "feat: implement cq2 game login state machine"
|
|
|
+```
|
|
|
+
|
|
|
+### Task 4: Wire the High-Level Client
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `cq2_sdk/tests/unit/test_client.py`
|
|
|
+- Modify: `cq2_sdk/src/cq2_sdk/client.py`
|
|
|
+- Modify: `cq2_sdk/src/cq2_sdk/tasks/context.py`
|
|
|
+- Modify: `cq2_sdk/README.md`
|
|
|
+- Modify: `cq2_sdk/docs/runbook.md`
|
|
|
+- Modify: `cq2_sdk/CHANGELOG.md`
|
|
|
+
|
|
|
+**Interfaces:**
|
|
|
+- Produces: `LauncherFacade(box, session, owner=None)` with a working
|
|
|
+ `run_until_enter_game(..., char_name=None, job=0, sex=0) -> GameSession`.
|
|
|
+- `Cq2Client` owns the connected `TcpClient`, resulting `GameSession`, and
|
|
|
+ initialized `TaskRegistry`.
|
|
|
+
|
|
|
+- [ ] **Step 1: Write failing facade orchestration test**
|
|
|
+
|
|
|
+Use complete HTTP fixtures and injected server resolver/TCP/login factories.
|
|
|
+Assert today-server selection, `EnterGameClient`, detailed resolution, connect,
|
|
|
+login arguments, retained session, and heartbeat start.
|
|
|
+
|
|
|
+- [ ] **Step 2: Run client tests and verify RED**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_client.py -q`
|
|
|
+
|
|
|
+- [ ] **Step 3: Implement high-level ownership and orchestration**
|
|
|
+
|
|
|
+Remove obsolete imports and placeholder exceptions. Build `TaskContext` with
|
|
|
+the existing movement/combat/mission components after login without exposing
|
|
|
+credentials in logging.
|
|
|
+
|
|
|
+- [ ] **Step 4: Run client tests and verify GREEN**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest tests/unit/test_client.py -q`
|
|
|
+
|
|
|
+- [ ] **Step 5: Update user documentation**
|
|
|
+
|
|
|
+Document required `char_name` behavior, the SC2 success boundary, and that
|
|
|
+live smoke tests do not create roles unless explicitly requested.
|
|
|
+
|
|
|
+- [ ] **Step 6: Commit Task 4**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add cq2_sdk/src/cq2_sdk/client.py cq2_sdk/src/cq2_sdk/tasks/context.py cq2_sdk/tests/unit/test_client.py cq2_sdk/README.md cq2_sdk/docs/runbook.md cq2_sdk/CHANGELOG.md
|
|
|
+git commit -m "feat: enable high-level cq2 game login"
|
|
|
+```
|
|
|
+
|
|
|
+### Task 5: Full Verification and Branch Review
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify only files required by failures or review findings.
|
|
|
+
|
|
|
+**Interfaces:**
|
|
|
+- Consumes all prior tasks.
|
|
|
+- Produces a verified, review-ready feature branch.
|
|
|
+
|
|
|
+- [ ] **Step 1: Run the full suite**
|
|
|
+
|
|
|
+Run: `cd cq2_sdk && uv run pytest -q`
|
|
|
+
|
|
|
+Expected: zero failures and zero errors.
|
|
|
+
|
|
|
+- [ ] **Step 2: Run compile and repository hygiene checks**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd cq2_sdk && uv run python -m compileall -q src tests
|
|
|
+cd .. && git status --short && git diff --check
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: Review the cumulative diff against the design**
|
|
|
+
|
|
|
+Check every header field, transition, timeout, failure response, ownership
|
|
|
+boundary, and sensitive-file exclusion named in the spec.
|
|
|
+
|
|
|
+- [ ] **Step 4: Request code review and resolve all Critical/Important findings**
|
|
|
+
|
|
|
+Use the requesting-code-review skill against the pre-plan base SHA and current
|
|
|
+HEAD. Re-run the focused tests for each correction.
|
|
|
+
|
|
|
+- [ ] **Step 5: Re-run complete verification after review changes**
|
|
|
+
|
|
|
+Run the commands from Steps 1 and 2 again and inspect their full output.
|
|
|
+
|
|
|
+- [ ] **Step 6: Commit remaining fixes and push the feature branch**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add -A
|
|
|
+git commit -m "test: verify real cq2 game login"
|
|
|
+git push -u origin feat/real-tcp-wire-codec
|
|
|
+```
|