@littlepartytime/sdk 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/GAME_DEV_GUIDE.md CHANGED
@@ -982,6 +982,101 @@ Renderer receives "stateUpdate" event with filtered state
982
982
  React re-renders with new state
983
983
  ```
984
984
 
985
+ ## Game Settlement Lifecycle
986
+
987
+ When `isGameOver()` returns `true`, the platform **immediately** takes over: it sends `game:result` to all clients, transitions to the settlement screen, and **unloads the game renderer**. This happens in the same tick as the final `game:state` broadcast:
988
+
989
+ ```
990
+ engine.handleAction(state, playerId, action) → newState
991
+ broadcastPlayerViews(newState) ← last game frame sent
992
+ engine.isGameOver(newState) → true
993
+ handleGameEnd() ← platform takes over, renderer unloaded
994
+ ```
995
+
996
+ If your game needs an in-game settlement screen (animations, rankings, replay, etc.), you **must not** let `isGameOver()` return `true` until that screen is done.
997
+
998
+ ### Two-Phase Settlement Pattern
999
+
1000
+ Split game ending into two phases:
1001
+
1002
+ ```
1003
+ playing → (winner decided) → settlement → (player confirms or timer expires) → finished
1004
+ ↑ ↑
1005
+ isGameOver = false isGameOver = true
1006
+ game renderer shows platform takes over
1007
+ its own result screen
1008
+ ```
1009
+
1010
+ #### Engine Implementation
1011
+
1012
+ ```typescript
1013
+ handleAction(state: GameState, playerId: string, action: GameAction): GameState {
1014
+ const data = state.data as MyGameData;
1015
+
1016
+ // 1. Normal gameplay — when winner is decided, enter settlement
1017
+ if (state.phase === "playing" && winnerDecided(data)) {
1018
+ return {
1019
+ ...state,
1020
+ phase: "settlement",
1021
+ data: { ...data, rankings: computeRankings(data) },
1022
+ };
1023
+ }
1024
+
1025
+ // 2. Settlement — wait for confirm action, then finish
1026
+ if (state.phase === "settlement" && action.type === "CONFIRM_RESULT") {
1027
+ return { ...state, phase: "finished" };
1028
+ }
1029
+
1030
+ return state;
1031
+ },
1032
+
1033
+ isGameOver(state: GameState): boolean {
1034
+ return state.phase === "finished"; // NOT "settlement"!
1035
+ },
1036
+
1037
+ getPlayerView(state: GameState, playerId: string): Partial<GameState> {
1038
+ if (state.phase === "settlement") {
1039
+ // Return rankings / stats for the in-game result screen
1040
+ return {
1041
+ ...state,
1042
+ data: { rankings: (state.data as MyGameData).rankings },
1043
+ };
1044
+ }
1045
+ // ...
1046
+ },
1047
+ ```
1048
+
1049
+ #### Renderer Implementation
1050
+
1051
+ ```tsx
1052
+ export default function GameRenderer({ platform, state }: GameRendererProps) {
1053
+ if (state.phase === "settlement") {
1054
+ return (
1055
+ <div>
1056
+ {/* Show your in-game result screen here */}
1057
+ <Rankings data={state.data.rankings} />
1058
+ <button onClick={() => platform.send({ type: "CONFIRM_RESULT" })}>
1059
+ 确认
1060
+ </button>
1061
+ </div>
1062
+ );
1063
+ }
1064
+ // ... normal gameplay UI
1065
+ }
1066
+ ```
1067
+
1068
+ ### Common Mistakes
1069
+
1070
+ | Mistake | Consequence |
1071
+ |---------|------------|
1072
+ | `isGameOver()` returns `true` in `settlement` phase | In-game result screen is skipped — platform unloads the renderer immediately |
1073
+ | No `settlement` phase; jumping straight from `playing` to `finished` | Players never see the in-game result animation / ranking |
1074
+ | Calling `platform.reportResult()` from the renderer | No-op — settlement is driven entirely by the server via `isGameOver` + `getResult` |
1075
+
1076
+ ### One-Liner Rule
1077
+
1078
+ > **`isGameOver()` is the platform takeover switch. Do not return `true` until your in-game result screen is done.**
1079
+
985
1080
  ## Complete Example: Number Guessing Game
986
1081
 
987
1082
  See the [`examples/number-guess`](../../examples/number-guess) directory for a complete working example.
@@ -32,6 +32,16 @@ export interface GameConfig {
32
32
  export interface GameEngine {
33
33
  init(players: Player[], options?: Record<string, unknown>): GameState;
34
34
  handleAction(state: GameState, playerId: string, action: GameAction): GameState;
35
+ /**
36
+ * Called after every handleAction. When this returns true, the platform
37
+ * immediately sends game:result, unloads the renderer, and shows the
38
+ * platform settlement screen.
39
+ *
40
+ * If your game has an in-game result screen (animations, rankings, etc.),
41
+ * keep returning false during that phase and only return true when the
42
+ * in-game result screen is done. See "Game Settlement Lifecycle" in
43
+ * GAME_DEV_GUIDE.md.
44
+ */
35
45
  isGameOver(state: GameState): boolean;
36
46
  getResult(state: GameState): GameResult;
37
47
  getPlayerView(state: GameState, playerId: string): Partial<GameState>;
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExE,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACtE,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAChF,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;IACtC,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,CAAC;IACxC,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;CAC3B"}
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExE,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACtE,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAChF;;;;;;;;;OASG;IACH,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;IACtC,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,CAAC;IACxC,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;CAC3B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@littlepartytime/sdk",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Game SDK for Little Party Time platform - type definitions and testing utilities",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",