@littlepartytime/sdk 2.0.1 → 2.1.0

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
@@ -22,11 +22,16 @@ my-game/
22
22
  ├── vite.config.ts # Build configuration
23
23
  ├── vitest.config.ts # Test configuration
24
24
  ├── tsconfig.json
25
- ├── assets/ # Required game images
25
+ ├── assets/ # Game images and custom assets
26
26
  │ ├── icon.png # 1:1 (256x256+) - game list icon
27
27
  │ ├── banner.png # 16:9 (640x360+) - lobby banner
28
28
  │ ├── cover.png # 21:9 (840x360+) - store/featured cover
29
- └── splash.png # 9:21 (360x840+) - loading screen
29
+ ├── splash.png # 9:21 (360x840+) - loading screen
30
+ │ ├── cards/ # Custom game assets (optional)
31
+ │ │ ├── king.png
32
+ │ │ └── queen.png
33
+ │ └── sounds/
34
+ │ └── flip.mp3
30
35
  ├── src/
31
36
  │ ├── config.ts # GameConfig - metadata
32
37
  │ ├── engine.ts # GameEngine - server-side logic
@@ -592,7 +597,12 @@ The `.zip` upload package contains:
592
597
  ├── icon.png # 1:1 game icon
593
598
  ├── banner.png # 16:9 banner
594
599
  ├── cover.png # 21:9 cover
595
- └── splash.png # 9:21 splash screen
600
+ ├── splash.png # 9:21 splash screen
601
+ └── assets/ # Custom game assets (if any)
602
+ ├── cards/
603
+ │ └── king.png
604
+ └── sounds/
605
+ └── flip.mp3
596
606
  ```
597
607
 
598
608
  ### Configuration
@@ -663,18 +673,61 @@ Every game must include 4 images in the `assets/` directory. These are validated
663
673
 
664
674
  The `pack` command reads these images, validates them, and includes them in the zip with canonical names (`icon.png`, `banner.png`, etc.).
665
675
 
666
- ### In-Game Assets (Audio, Fonts, etc.)
676
+ ### Custom Game Assets
667
677
 
668
- For assets used inside your game UI (not the 4 required images above), use one of these approaches:
678
+ For assets used inside your game UI (card images, sound effects, fonts, etc.), place them in subdirectories under `assets/`:
679
+
680
+ ```
681
+ assets/
682
+ ├── icon.png # Platform display images (root level, required)
683
+ ├── banner.png
684
+ ├── cover.png
685
+ ├── splash.png
686
+ ├── cards/ # Custom game assets (subdirectories)
687
+ │ ├── king.png
688
+ │ └── queen.png
689
+ └── sounds/
690
+ └── flip.mp3
691
+ ```
692
+
693
+ Access custom assets in your renderer via `platform.getAssetUrl()`:
694
+
695
+ ```tsx
696
+ export default function GameRenderer({ platform, state }: GameRendererProps) {
697
+ const cardImg = platform.getAssetUrl('cards/king.png');
698
+ const flipSound = platform.getAssetUrl('sounds/flip.mp3');
699
+
700
+ return (
701
+ <div>
702
+ <img src={cardImg} alt="King" />
703
+ <audio src={flipSound} />
704
+ </div>
705
+ );
706
+ }
707
+ ```
708
+
709
+ **How it works:**
710
+ - During `lpt-dev-kit dev`: returns a local URL (e.g., `http://localhost:4000/assets/cards/king.png`)
711
+ - In production: returns a CDN URL (the platform uploads assets to OSS automatically)
712
+
713
+ **Validation rules (enforced by `pack` command):**
714
+
715
+ | Rule | Limit |
716
+ |------|-------|
717
+ | Single file size | ≤ 10MB |
718
+ | Total assets size | ≤ 50MB |
719
+ | Allowed file types | `.png`, `.jpg`, `.jpeg`, `.webp`, `.svg`, `.gif`, `.mp3`, `.wav`, `.ogg`, `.json`, `.woff2`, `.woff` |
720
+ | Path rules | No `..`, no spaces |
721
+
722
+ **Alternative approaches for small assets:**
669
723
 
670
724
  | Approach | When to Use | How |
671
725
  |----------|-------------|-----|
672
- | **Inline in bundle** | Small assets (icons, sounds < 100KB) | Vite automatically inlines assets below `assetsInlineLimit` (default 4KB) as data URLs. Increase the limit in `vite.config.ts` if needed: `build: { assetsInlineLimit: 100000 }` |
673
- | **External URL** | Large assets (images, audio, video) | Host on a CDN or external server, reference by absolute URL in your code |
674
- | **CSS/SVG** | UI elements, icons | Use Tailwind CSS utilities, inline SVG components, or CSS gradients/shapes |
675
- | **Emoji/Unicode** | Simple visual indicators | Use Unicode characters directly in JSX |
726
+ | **Inline in bundle** | Tiny assets (< 4KB) | Vite automatically inlines as data URLs |
727
+ | **CSS/SVG** | UI elements, icons | Tailwind CSS utilities or inline SVG components |
728
+ | **Emoji/Unicode** | Simple visual indicators | Unicode characters directly in JSX |
676
729
 
677
- > **Tip:** Keep your bundle under 5MB. The `pack` command warns if `bundle.js` exceeds this limit.
730
+ > **Tip:** Use `platform.getAssetUrl()` for assets 100KB+ instead of inlining them into the bundle.
678
731
 
679
732
  ### Submitting Your Game
680
733
 
@@ -695,6 +748,7 @@ For assets used inside your game UI (not the 4 required images above), use one o
695
748
  | `on(event, handler)` | Listens for events from the server |
696
749
  | `off(event, handler)` | Removes an event listener |
697
750
  | `reportResult(result)` | Reports game results (called by platform, not games) |
751
+ | `getAssetUrl(path)` | Returns the runtime URL for a custom asset (e.g., `"cards/king.png"` → CDN URL) |
698
752
 
699
753
  ### Events the renderer should listen for
700
754
 
@@ -753,6 +807,113 @@ Use these CSS variables / Tailwind classes for consistent styling:
753
807
  | Display font | `--font-display` | `font-display` |
754
808
  | Body font | `--font-body` | `font-body` |
755
809
 
810
+ ## Platform Runtime Constraints
811
+
812
+ The production platform runs game engines inside a Node.js `vm` sandbox. The dev-kit (`npm run dev`) automatically enforces key constraints so issues surface during local development, not after deployment.
813
+
814
+ ### Timer APIs Are Disabled
815
+
816
+ The sandbox replaces `setTimeout`, `setInterval`, `clearTimeout`, and `clearInterval` with no-ops. Calling them inside engine code will:
817
+
818
+ - **In production**: silently do nothing (the callback is never executed)
819
+ - **In dev-kit**: print a warning to the console and return `0`
820
+
821
+ ```typescript
822
+ // BAD - will not work in production
823
+ handleAction(state, playerId, action) {
824
+ setTimeout(() => {
825
+ // This callback will NEVER run in the sandbox
826
+ }, 2000);
827
+ return { ...state, phase: 'animating' };
828
+ }
829
+
830
+ // GOOD - let the client handle timing
831
+ handleAction(state, playerId, action) {
832
+ return { ...state, phase: 'animating' };
833
+ }
834
+ // In renderer: after animation completes, send a follow-up action:
835
+ // platform.send({ type: 'ANIMATION_DONE' })
836
+ // Engine handles ANIMATION_DONE to advance to the next phase.
837
+ ```
838
+
839
+ ### State Must Be JSON-Serializable
840
+
841
+ The platform stores game state in Redis via `JSON.stringify` / `JSON.parse` round-trips. Non-serializable types will silently lose data:
842
+
843
+ | Type | After JSON Round-Trip | Result |
844
+ |------|----------------------|--------|
845
+ | `Map` | `{}` | Data lost |
846
+ | `Set` | `{}` | Data lost |
847
+ | `Date` | `"2026-02-10T..."` (string) | Type changed |
848
+ | `undefined` | Removed | Field disappears |
849
+ | `RegExp` | `{}` | Data lost |
850
+ | Function | Removed | Lost |
851
+
852
+ The dev-kit checks your state after every `init()` and `handleAction()` call and prints warnings if non-serializable types are detected.
853
+
854
+ ```typescript
855
+ // BAD
856
+ data: {
857
+ players: new Map([['p1', { score: 0 }]]),
858
+ seen: new Set(['card-1']),
859
+ createdAt: new Date(),
860
+ }
861
+
862
+ // GOOD
863
+ data: {
864
+ players: { p1: { score: 0 } },
865
+ seen: ['card-1'],
866
+ createdAt: '2026-02-10T00:00:00.000Z',
867
+ }
868
+ ```
869
+
870
+ ### Engine Instance Is Shared
871
+
872
+ The platform loads your engine bundle once and reuses it across all game rooms. This means **module-level mutable variables are shared between games**:
873
+
874
+ ```typescript
875
+ // BAD - shared across all rooms!
876
+ let gameCounter = 0;
877
+
878
+ const engine: GameEngine = {
879
+ init(players) {
880
+ gameCounter++; // Will increment across different rooms
881
+ // ...
882
+ },
883
+ };
884
+
885
+ // GOOD - all state lives in GameState
886
+ const engine: GameEngine = {
887
+ init(players) {
888
+ return {
889
+ phase: 'playing',
890
+ players: players.map(p => ({ id: p.id })),
891
+ data: { roundNumber: 1 }, // State is per-room
892
+ };
893
+ },
894
+ };
895
+ ```
896
+
897
+ ### Restricted Global Variables
898
+
899
+ The following globals are `undefined` in the sandbox:
900
+
901
+ | Global | Alternative |
902
+ |--------|-------------|
903
+ | `fetch` | Not available. Engines cannot make network calls. |
904
+ | `process` | Not available. |
905
+ | `globalThis` | Not available. |
906
+ | `global` | Not available. |
907
+
908
+ Available built-ins: `Math`, `JSON`, `Date`, `Array`, `Object`, `Map`, `Set`, `Number`, `String`, `Boolean`, `Error`, `TypeError`, `RangeError`, `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `console` (log/error/warn).
909
+
910
+ ### `require()` Whitelist
911
+
912
+ Only the following modules can be required in engine code (all are stubs for compatibility):
913
+ `react`, `react/jsx-runtime`, `react-dom`, `react-dom/client`
914
+
915
+ Any other `require()` call will throw an error.
916
+
756
917
  ## Data Flow Diagram
757
918
 
758
919
  ```
package/dist/types.d.ts CHANGED
@@ -24,5 +24,11 @@ export interface Platform {
24
24
  on(event: string, handler: (...args: unknown[]) => void): void;
25
25
  off(event: string, handler: (...args: unknown[]) => void): void;
26
26
  reportResult(result: GameResult): void;
27
+ /**
28
+ * 获取游戏资产的运行时 URL。
29
+ * @param path - 相对于游戏 assets/ 目录的路径,如 "cards/king.png"、"sounds/flip.mp3"
30
+ * @returns 可直接用于 <img src> / <audio src> / fetch() 的完整 URL
31
+ */
32
+ getAssetUrl(path: string): string;
27
33
  }
28
34
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE;QACR,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC;KACnB,EAAE,CAAC;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,IAAI,MAAM,EAAE,CAAC;IACvB,cAAc,IAAI,MAAM,CAAC;IACzB,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAChE,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CACxC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE;QACR,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC;KACnB,EAAE,CAAC;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,IAAI,MAAM,EAAE,CAAC;IACvB,cAAc,IAAI,MAAM,CAAC;IACzB,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAChE,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@littlepartytime/sdk",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
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",