feat: Grid-Town 64x64 map rebuild with Scalar API docs#56
Conversation
…rpretation - Add TileInterpreter for parsing map colors to tile types - Add CollisionSystem for client-side movement prediction - Add ZoneBanner UI for zone enter/leave notifications - Add WanderBot for server-side AI wandering with collision respect - Add world.test.ts with 17 test scenarios covering: - Zone enter/exit events - Wall collision rejection - Bot zone traversal - Door passability - Update GameScene with zone tracking and banner display - Extend shared types with TileType, TileInfo, WorldGrid types - Update tileset configuration with door tile support
- Replace 64x52 Work & Life Village with 64x64 Grid-Town layout - Update zone system: plaza, north-block, west-block, east-block, south-block, lake - Add 6 NPCs: greeter, office-pm, meeting-host, barista, arcade-host, ranger - Add 10 interactive facilities across all zones - Integrate Scalar for interactive API documentation at /docs - Add OpenAPI 3.1 spec covering all 8 AIC endpoints - Fix zone.exit event not showing in client notification panel - Update README with current project state and documentation links
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Caution Review failedThe pull request is closed. 워크스루Grid-Town 64x64 맵으로 월드 시스템을 전면 재설계하고, 6개 존으로 구성된 새로운 존 시스템을 도입했습니다. 클라이언트의 TileInterpreter와 ClientCollisionSystem으로 타일 기반 충돌 처리를 구현하고, 서버의 WanderBot과 OpenAPI 문서화를 추가했으며, UI 컴포넌트(ZoneBanner)와 자산 추출 도구들을 신규 추가했습니다. 변경 사항
예상 코드 리뷰 노력🎯 4 (복잡함) | ⏱️ ~45분 관련될 수 있는 PR
시
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @ComBba, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the OpenClawWorld experience by introducing a brand new, larger game map and improving the developer experience with interactive API documentation. The new 'Grid-Town' map offers a more expansive and structured environment for players and AI agents, while the integrated Scalar documentation streamlines understanding and interaction with the AI Agent Interface. Additionally, a client-side bug related to zone exit notifications has been resolved, improving user feedback. Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request is a significant feature update, rebuilding the world map to a new 64x64 "Grid-Town" layout and adding interactive API documentation using Scalar. The changes are extensive, touching client, server, shared code, and documentation. The new map, zone system, and API docs integration are well-implemented. I've found a couple of areas for improvement related to consistency and correctness. One is a minor naming inconsistency in the client's map assets that could affect maintainability. The other is a more significant issue on the server where outdated default zone boundaries could lead to incorrect behavior. Overall, this is a great contribution that modernizes the project's world and developer experience.
| plaza: { x: 192, y: 96, width: 736, height: 416 }, | ||
| 'north-block': { x: 1024, y: 192, width: 448, height: 448 }, | ||
| 'east-block': { x: 96, y: 928, width: 512, height: 576 }, | ||
| 'west-block': { x: 704, y: 928, width: 512, height: 320 }, | ||
| 'south-block': { x: 1344, y: 736, width: 608, height: 416 }, | ||
| lake: { x: 1344, y: 1152, width: 608, height: 416 }, |
There was a problem hiding this comment.
The default zone bounds in this method appear to be from the old 64x52 map layout and do not match the new 64x64 Grid-Town specification. This could lead to incorrect zone detection if a world pack doesn't provide its own bounds. These should be updated to match the new layout defined in docs/reference/map_spec_grid_town.md and packages/server/src/zone/ZoneSystem.ts.
plaza: { x: 768, y: 768, width: 512, height: 512 },
'north-block': { x: 576, y: 64, width: 768, height: 384 },
'east-block': { x: 1472, y: 704, width: 576, height: 640 },
'west-block': { x: 64, y: 704, width: 640, height: 640 },
'south-block': { x: 576, y: 1472, width: 768, height: 384 },
lake: { x: 1408, y: 1408, width: 640, height: 640 },|
|
||
| const ZONE_IDS = ['lobby', 'office', 'meeting-center', 'lounge-cafe', 'arcade', 'plaza'] as const; | ||
| // Single unified map for the entire world | ||
| const ZONE_IDS = ['village'] as const; |
There was a problem hiding this comment.
Using 'village' as the map key is a bit confusing since the new map is "Grid-Town" and its internal mapId is grid_town. For better clarity and consistency, I suggest using 'grid_town' as the key. This would involve:
- Renaming
packages/client/public/assets/maps/village.jsontogrid_town.json. - Updating this line to use
'grid_town'. - Updating
GameScene.tsto usethis.make.tilemap({ key: 'grid_town' }).
| const ZONE_IDS = ['village'] as const; | |
| const ZONE_IDS = ['grid_town'] as const; |
- Update main CI with parallel jobs (typecheck, lint, build, test) - Add E2E testing workflow with Playwright - Add Docker build and push workflow with GHCR - Add CodeQL security scanning (weekly + on PR) - Add Dependabot for automated dependency updates - Add PR auto-labeling by files changed and size
There was a problem hiding this comment.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
tests/unit/worldpack-unified-map.test.ts (3)
7-7:⚠️ Potential issue | 🔴 Critical테스트 설명과 기대값이 새 64×64 맵과 불일치합니다.
테스트 describe 블록이
"64x52"로 명시되어 있지만, 실제grid_town_outdoor.json은 64×64 맵입니다. 이 파일 전반에 걸쳐height: 52기대값(Line 24, 46, 207, 210)이 모두 실패할 것입니다.64x64로 수정해야 합니다.
51-98:⚠️ Potential issue | 🔴 Critical존 경계 기대값이 이전 64×52 맵 기준이며, plaza 경계 테스트가 자기모순입니다.
- Line 57에서
plaza경계를{ x: 192, y: 96, width: 736, height: 416 }로 검증하고, Line 97에서는 같은plaza를{ x: 1344, y: 1152, width: 608, height: 416 }로 검증합니다. 동일한 존에 대해 서로 다른 기대값을 가지므로 둘 중 하나는 반드시 실패합니다.- 모든 경계 기대값이 이전 64×52 맵의 값으로, 새 Grid-Town 맵 스펙(
docs/reference/map_spec_grid_town.md)의 경계와 일치하지 않습니다.존 경계 기대값을 새 64×64 Grid-Town 맵 스펙에 맞춰 전면 업데이트해야 합니다.
215-243:⚠️ Potential issue | 🟠 MajorNPC 및 시설물 이름 기대값이 이전 데이터 기준입니다.
- Line 221-222:
'receptionist','security-guard'는 이전 NPC ID입니다. PR에서 추가된 새 NPC는'greeter','office-pm'등입니다.- Line 225:
'pm'→ 새 ID는'office-pm'입니다.- Line 236:
'reception_desk'가 plaza에 존재하는지 확인하지만, 새 맵 스펙에는 plaza에notice_board와onboarding_signpost만 있습니다.테스트 기대값을 새 Grid-Town NPC 및 시설물 데이터에 맞춰 업데이트해 주세요.
packages/server/src/world/WorldPackLoader.ts (2)
309-310:⚠️ Potential issue | 🟡 Minor기본 높이값
52가 이전 맵 크기의 잔여값입니다.
raw.height ?? 52에서 fallback 값이 이전 64×52 맵 기준입니다. 새로운 64×64 Grid-Town 맵에 맞게64로 변경해야 합니다.🐛 수정 제안
width: raw.width ?? 64, - height: raw.height ?? 52, + height: raw.height ?? 64,
533-544:⚠️ Potential issue | 🔴 Critical
getDefaultZoneBounds값이ZoneSystem.DEFAULT_ZONE_BOUNDS와 완전히 불일치합니다.이 메서드의 존 경계값이
ZoneSystem(테스트에서 검증됨)과Minimap.ts에 정의된 값과 전혀 다릅니다:
Zone WorldPackLoader ZoneSystem/Minimap/Tests plaza {192, 96, 736, 416}{768, 768, 512, 512}north-block {1024, 192, 448, 448}{576, 64, 768, 384}east-block {96, 928, 512, 576}{1472, 704, 576, 640}west-block {704, 928, 512, 320}{64, 704, 640, 640}south-block {1344, 736, 608, 416}{576, 1472, 768, 384}lake {1344, 1152, 608, 416}{1408, 1408, 640, 640}또한
east-block과west-block의 경계가 논리적으로 뒤바뀐 것처럼 보입니다 —east-block이 x=96(왼쪽)에,west-block이 x=704(오른쪽)에 위치합니다.통합 맵 로딩 시 이 경계값으로 NPC/시설물이 잘못된 존에 배치되며,
filterObjectsByZone도 잘못된 결과를 반환합니다.ZoneSystem.DEFAULT_ZONE_BOUNDS의 값과 통일해 주세요.tests/unit/zone-system-plaza.test.ts (1)
105-111:⚠️ Potential issue | 🔴 CriticalWorldPackLoader의 존 경계값이 ZoneSystem 정의와 일치하지 않습니다.
이 테스트가 기대하는 plaza 경계값은
{ x: 768, y: 768, width: 512, height: 512 }이며, 이는packages/server/src/zone/ZoneSystem.ts의DEFAULT_ZONE_BOUNDS와 일치합니다. 그러나packages/server/src/world/WorldPackLoader.ts의getDefaultZoneBounds()메서드는 모든 존에 대해 완전히 다른 좌표를 사용합니다:
- ZoneSystem: plaza
{ x: 768, y: 768, width: 512, height: 512 }- WorldPackLoader: plaza
{ x: 192, y: 96, width: 736, height: 416 }이는 서버 측 존 감지 로직(ZoneSystem)과 맵 로딩(WorldPackLoader)이 서로 다른 좌표계를 사용한다는 뜻입니다. 결과적으로 NPC/시설물 배치와 런타임 존 감지 사이에 심각한 불일치가 발생할 수 있습니다.
🤖 Fix all issues with AI agents
In `@packages/client/src/game/config.ts`:
- Line 4: The import of AssetGalleryScene in config.ts (AssetGalleryScene)
causes CI to fail because the module file is missing; either add the missing
module file packages/client/src/game/scenes/AssetGalleryScene.ts implementing
and exporting the AssetGalleryScene class, or remove the import and any
reference to AssetGalleryScene in the scenes registration array (where scenes
are assembled) until the file is added; update any exports or scene lists (in
config.ts) accordingly so the project typechecks and builds.
In `@packages/client/src/systems/CollisionSystem.ts`:
- Around line 39-53: The server's MovementSystem.setDestination currently only
checks whether the target tile is blocked and lacks the diagonal-corner rule
present in client CollisionSystem.canMoveTo, causing desyncs; update
MovementSystem.setDestination to replicate the client's checks: compute dx/dy
between from and to, reject moves with dx>1 or dy>1, and for dx===1 && dy===1
additionally reject when both adjacent orthogonal tiles (isBlocked(fromTx, toTy)
&& isBlocked(toTx, fromTy)) are blocked, using the same isBlocked helper used on
the client so server-side validation mirrors canMoveTo exactly.
In `@packages/client/src/ui/Minimap.ts`:
- Around line 17-19: The mapHeight constant in Minimap.ts is still 1664 while
mapWidth is updated to 2048, causing scale/clip math and region rendering (e.g.,
south-block and lake) to fall outside the minimap; update mapHeight to 2048 to
match a 64×64 tile (2048×2048px) map, then re-run or adjust any scaling/viewport
calculations in the Minimap component that reference mapHeight so the computed
scales, bounds and clipping for regions like "south-block" and "lake" correctly
fall inside the minimap area.
In `@packages/client/src/world/TileInterpreter.ts`:
- Around line 12-24: COLOR_RANGES contains overlapping RGB intervals causing
ambiguous classification (e.g., floor_plaza vs floor_south, road vs floor_plaza,
floor_north vs floor_west); update the ranges in the COLOR_RANGES array so each
ColorRange is mutually exclusive (adjust endpoints to non-overlapping intervals
or tighten channel bounds for 'floor_plaza', 'floor_south', 'road',
'floor_north', 'floor_west'), or if intentional, add a clear comment near the
COLOR_RANGES declaration documenting the priority ordering and why overlaps are
acceptable.
- Around line 112-134: The tileTypes array in interpretTileId is misordered and
missing a type: reorder the floor entries to match the shared types sequence
(ensure 'floor_north' → 'floor_west' → 'floor_east' at indexes 4–6) and add the
missing 'decoration' entry in the array at the position defined by the shared
types so tileId lookups map correctly; update the tileTypes definition inside
interpretTileId (and keep usage of ZONE_FLOOR_TYPES, isDoor, and collision logic
unchanged).
In `@packages/server/package.json`:
- Line 36: packages/server currently lists "@scalar/express-api-reference" in
devDependencies causing runtime module-not-found when app.config.ts
unconditionally imports it (see import at top of app.config.ts) and always
registers the /docs endpoint; move "@scalar/express-api-reference" from
devDependencies into dependencies in packages/server/package.json so it is
installed in production, then verify app.config.ts still imports the module and
the /docs route registers correctly.
In `@packages/server/src/bots/WanderBot.ts`:
- Around line 143-152: The code currently mixes injected simulation time with
wall-clock time: update(currentTimeMs) uses the injected currentTimeMs but
scheduleNextMove(), tryMove(), and moveTo() call Date.now() directly, causing
inconsistent nextMoveTime semantics; modify scheduleNextMove(nextTimeBaseMs),
tryMove(currentTimeMs) and moveTo(target, currentTimeMs) (or otherwise accept a
currentTimeMs parameter) so all time calculations use the injected
currentTimeMs, update assignments to this.nextMoveTime from the passed-in
currentTimeMs (not Date.now()), and thread currentTimeMs through update ->
tryMove -> moveTo to keep deterministic timing for the RNG and
movementSystem.isMoving(this.entityId) checks.
- Around line 92-102: random() can return exactly 1.0 and randomInt(min,max)
then yields an off-by-one; also the 32-bit LCG multiplication can lose precision
using JS Numbers. Fix by making random() use 32-bit integer arithmetic (e.g. use
Math.imul and >>>0 to keep randomState as uint32 after updating with multiplier
and increment) and divide by 0x80000000 (2**31) or otherwise ensure the returned
value is strictly less than 1.0; update references in the WanderBot class
methods random() and randomInt(min,max) so randomState is updated with
Math.imul(this.randomState, 1103515245) + 12345 masked to 32 bits and random()
returns randomState / 0x80000000 to prevent returning 1.0.
In `@packages/server/src/openapi.ts`:
- Around line 412-429: The OpenAPI schemas ProfileUpdateRequest and
ProfileUpdateResponseData must be made consistent with the TypeScript types in
types.ts: update ProfileUpdateRequest to include the fields used in types.ts
(status, statusMessage, title, department) instead of/alongside name and meta
(or vice‑versa if types.ts is canonical), and update ProfileUpdateResponseData
to use the same result shape (rename updated -> applied if types.ts uses
"applied", and add the missing profile object and any server timestamp field
that types.ts expects). Locate the ProfileUpdateRequest and
ProfileUpdateResponseData components in this file and change their properties
and required arrays to exactly match the field names/types in types.ts so
OpenAPI and TypeScript stay in sync.
- Around line 203-220: The OpenAPI schemas RegisterRequest and
RegisterResponseData are out of sync with the TypeScript types in
packages/shared/src/types.ts (the RegisterRequest type with { name, roomId } and
the response type with { agentId, roomId, sessionToken }). Reconcile them by
either: (A) updating openapi.ts RegisterRequest to remove agentId and only
require name and roomId, and updating RegisterResponseData to use the same field
names (agentId, roomId, sessionToken) and types/descriptions; or (B) updating
the TypeScript types in packages/shared/src/types.ts to include agentId and to
rename sessionToken → token if the OpenAPI shape is the source of truth. Update
examples/descriptions in openapi.ts accordingly and ensure $ref schema names
(RegisterRequest, RegisterResponseData) match the adjusted TypeScript shapes so
clients and runtime agree.
In `@tests/unit/world.test.ts`:
- Around line 53-73: The test uses coordinates (640, 300) which fall into
north-block based on DEFAULT_ZONE_BOUNDS, so update the test expectations or
coordinates: either change the asserted current zone from 'plaza' to
'north-block' for this test and all other specs reusing (640, 300) (Scenario A,
Scenario B beforeEach, Scenario D beforeEach), or replace the coordinate with a
plaza-inside point such as (1024, 1024) and keep expectations as 'plaza'; adjust
the calls to zoneSystem.updateEntityZone and subsequent expects
(result.currentZone, eventLog checks for 'zone.enter', and previousZone
assertions) accordingly.
- Around line 115-136: The test assumes the entity starts in 'plaza' but
beforeEach places it at (640,300) which is already in 'north-block', so
zoneSystem.updateEntityZone('player_1', 1200, 400, ...) will not produce the
expected change; fix by ensuring the entity's initial position is inside 'plaza'
(e.g., set entity.setPosition to plaza coordinates in this test or adjust the
beforeEach setup) before calling zoneSystem.updateEntityZone, so the call
triggers a transition from previousZone 'plaza' to currentZone 'north-block' and
the assertions on result.changed, previousZone, currentZone and emitted
zone.exit/zone.enter events become valid.
- Around line 240-257: The test uses incorrect coordinates for east-block in the
zonePositions array: { zone: 'east-block', x: 300, y: 1100 } actually falls in
west-block; update the test by either changing that entry to the correct
east-block coordinates (e.g., x ~1600, y ~1024) or replace the expectation
expect(visited).toContain('east-block') with
expect(visited).toContain('west-block'); ensure you adjust the matching zone
name in the zonePositions array and keep the calls to entity.setPosition(...)
and wanderBot.update(Date.now()) as-is so visited reflects the corrected
position.
- Around line 10-12: Tests use TILE_SIZE, MAP_WIDTH and MAP_HEIGHT to build the
test map but MAP_HEIGHT is set to 52 which misaligns with the intended 64×64
Grid-Town; update MAP_HEIGHT to 64 (and any derived pixel calculations that use
TILE_SIZE * MAP_HEIGHT) so bottom zones like "south-block" (y:1472-1856) and
"lake" (y:1408-2048) fall inside the test map; also sweep the test assertions in
world.test.ts that reference hard-coded pixel bounds and adjust them to use
TILE_SIZE * MAP_HEIGHT or the updated expected values so collision/movement
checks remain correct.
In `@tests/unit/worldpack-unified-map.test.ts`:
- Around line 33-40: The zones test array contains a duplicated 'plaza' and is
missing 'lake'; update the const zones declaration(s) in
worldpack-unified-map.test.ts (the arrays named zones) to replace the second
'plaza' with 'lake' so the list becomes
['plaza','north-block','east-block','west-block','south-block','lake'] and make
the same change for the other identical zones array later in the file.
🟡 Minor comments (9)
packages/client/public/assets/maps/village.json-11-11 (1)
11-11:⚠️ Potential issue | 🟡 Minor
nextobjectid가 잘못되었습니다 — 객체 ID 충돌 가능성.현재
nextobjectid가1로 설정되어 있지만, objects 레이어에 이미 ID 1~10인 객체가 10개 존재합니다. Tiled에서 이 맵을 다시 편집할 경우 ID 충돌이 발생합니다.11로 수정해야 합니다.🐛 수정 제안
- "nextobjectid": 1, + "nextobjectid": 11,docs/reference/map_spec_grid_town.md-77-81 (1)
77-81:⚠️ Potential issue | 🟡 Minor
lake.viewpoint가 Z_LAKE 존 경계 밖에 위치합니다.
lake.viewpoint의 타일 좌표(42, 42)는 Z_LAKE 존 경계(시작 타일:(44, 44)) 밖에 있습니다. 이 시설물은 플레이어가 lake 존에 진입하기 전에 접근 가능하게 되며, 존 기반 시설물 조회 로직에서 누락될 수 있습니다.타일 좌표를
(44, 44)이상으로 조정하거나, 의도적으로 존 외부에 배치한 것이라면 문서에 명시해 주세요.docs/reference/map_spec_grid_town.md-48-53 (1)
48-53:⚠️ Potential issue | 🟡 Minor
plaza.signpost의 타입이 맵 JSON과 불일치합니다.문서에서는
plaza.signpost의 타입을onboarding_signpost로 명시하고 있지만,grid_town_outdoor.json(Line 328)에서는notice_board로 정의되어 있습니다. 문서와 실제 데이터 중 하나를 수정하여 일관성을 맞춰야 합니다.world/packs/base/maps/grid_town_outdoor.json-10-11 (1)
10-11:⚠️ Potential issue | 🟡 Minor
nextobjectid가 잘못되었습니다.오브젝트 레이어에 ID 1~10까지 10개의 오브젝트가 정의되어 있으므로,
nextobjectid는11이어야 합니다. 현재 값1은 Tiled 에디터에서 이 맵을 다시 열어 편집할 때 오브젝트 ID 충돌을 유발할 수 있습니다.🔧 수정 제안
- "nextobjectid": 1, + "nextobjectid": 11,packages/server/src/openapi.ts-124-127 (1)
124-127:⚠️ Potential issue | 🟡 Minor
ChatChannelenum이 TypeScript 타입의 부분집합입니다.OpenAPI에서는
['proximity', 'global']만 정의하고 있지만,types.ts의ChatChannel은'proximity' | 'global' | 'team' | 'meeting' | 'dm'을 포함합니다. AIC v0.1에서 의도적으로 제한한 것이라면 스키마 설명에 명시해 주세요. 그렇지 않으면team/meeting/dm채널 사용 시 유효성 검증 실패가 발생합니다.world/packs/base/npcs/ranger.json-10-29 (1)
10-29:⚠️ Potential issue | 🟡 Minor대화 트리 구조가 로드 시스템에서 손실됩니다.
JSON 파일의
dialogue필드는 중첩된 객체 구조(greeting/swimming/fishing 노드)로 정의되어 있지만,WorldPackLoader.parseNpcDefinition(라인 630)에서Array.isArray(raw.dialogue)검사 시 배열이 아니므로 빈 배열[]로 변환됩니다. 결과적으로 이 대화 트리 데이터는 로드된NpcDefinition객체에서 사라집니다.테스트는 원본 JSON 파일을 직접 읽어 대화 트리 구조를 검증하지만, 실제 로드되는 NPC 시스템에서는 이 데이터가 사용되지 않습니다. 대화 트리를 실제로 소비하는 별도의 시스템이 있는지, 아니면
NpcDefinition타입을 업데이트해야 하는지 확인해 주세요.packages/client/src/ui/ZoneBanner.ts-60-68 (1)
60-68:⚠️ Potential issue | 🟡 Minor
showLeave와showEnter가 연속 호출되면 leave 배너가 보이지 않습니다.
GameScene.tsLine 498-499에서showLeave와showEnter가 동기적으로 연속 호출됩니다.showBanner는 이전 배너를 즉시 교체하므로 사용자는 leave 배너를 볼 수 없습니다.의도적이라면 무시해도 되지만, 두 알림을 모두 표시하려면 큐 메커니즘이나 결합 메시지(예:
"Plaza → North Block")를 고려해 보세요.packages/client/src/game/scenes/GameScene.ts-91-118 (1)
91-118:⚠️ Potential issue | 🟡 Minor
forEach콜백이 값을 반환하여 린트 경고가 발생합니다.
tile => groundData.push(tile.index)—Array.push()가 배열 길이를 반환하므로forEach콜백에서 값을 반환하는 것으로 간주됩니다. Biome 린터가 이를 경고합니다.🔧 중괄호를 추가하여 암시적 반환 제거
groundLayer.data.forEach(row => { - row.forEach(tile => groundData.push(tile.index)); + row.forEach(tile => { groundData.push(tile.index); }); }); collisionLayer.data.forEach(row => { - row.forEach(tile => collisionData.push(tile.index)); + row.forEach(tile => { collisionData.push(tile.index); }); });packages/client/src/ui/ZoneBanner.ts-123-131 (1)
123-131:⚠️ Potential issue | 🟡 Minor
destroy()에서 resize 리스너가 제거되지 않아 메모리 누수가 발생할 수 있습니다.Line 51에서
scene.scale.on('resize', ...)리스너를 등록하지만,destroy()에서 제거하지 않습니다. Scene이 재시작되거나 ZoneBanner가 교체될 경우 이전 콜백이 계속 유지됩니다.🐛 리스너 정리 제안
+ private resizeHandler: () => void; + constructor(scene: Phaser.Scene) { this.scene = scene; // ... - scene.scale.on('resize', () => this.updatePosition()); + this.resizeHandler = () => this.updatePosition(); + scene.scale.on('resize', this.resizeHandler); } destroy(): void { if (this.hideTimeout) { clearTimeout(this.hideTimeout); } if (this.currentTween) { this.currentTween.stop(); } + this.scene.scale.off('resize', this.resizeHandler); this.container.destroy(); }
🧹 Nitpick comments (11)
packages/server/src/rooms/GameRoom.ts (1)
87-87:mapId매개변수와ZoneId타입 간의 불일치를 해결하세요.기본
mapId값인'village'는 정의된ZoneId타입('plaza'|'north-block'|'west-block'|'east-block'|'south-block'|'lake')에 포함되지 않습니다. 106번 줄에서mapId as ZoneId로 캐스팅되어findAndSetSpawnPoint에 전달되지만,worldPack.maps.get(zoneId)에서undefined를 반환한 후parsedMap.objects에서 spawn 포인트를 찾는 fallback 경로로 처리됩니다. 현재 fallback 로직이 있어 런타임 오류는 발생하지 않지만,as ZoneId캐스트가 타입 안전성을 우회하고 있습니다.
findAndSetSpawnPoint의 매개변수를ZoneId에서string으로 변경하거나,mapId를ZoneId타입과 구분하여 정의하는 것을 권장합니다.package.json (1)
24-24:python명령어의 이식성 문제일부 시스템(특히 macOS, 최신 Linux 배포판)에서는
python이 존재하지 않고python3만 사용 가능합니다.python3으로 변경하거나 프로젝트 규약에 맞게 명시하는 것을 권장합니다.제안
- "extract:assets": "python tools/extract_assets/extract.py --all" + "extract:assets": "python3 tools/extract_assets/extract.py --all"packages/shared/src/schemas.ts (1)
414-427: 섹션 주석이 이전 명칭을 사용하고 있습니다.Line 415의
Work-Life World Schemas주석이 새로운 "Grid-Town" 레이아웃 명칭과 불일치합니다.ZoneIdSchema값 자체는ZoneId타입과 정확히 일치하며 문제 없습니다.제안
-// Work-Life World Schemas +// Grid-Town World Schemasworld/packs/base/npcs/meeting-host.json (1)
15-15: 대화 텍스트에 "Meeting Center" 참조가 남아있습니다.존 이름이
east-block으로 변경되었지만, 대화 텍스트에서 여전히 "Welcome to the Meeting Center!"라고 표시됩니다. 이것이 NPC의 인게임 대화로서 의도된 것이라면 문제없지만, 존 이름과 일치시키려면 업데이트를 고려해 보세요.packages/client/src/ui/Minimap.ts (2)
87-124: 워터/로드 프로시저럴 렌더링의 매직 넘버가 64×52 레이아웃 기준입니다.워터 배경의
y < 20루프(Line 96)와 로드 좌표들(Lines 100-124)이 이전 레이아웃의 하드코딩된 타일 좌표를 사용하고 있습니다.mapHeight가 2048로 수정되면 이 값들도 64×64 맵에 맞게 재검토가 필요합니다.또한 이 매직 넘버들(17, 23, 37, 41 등)을 상수로 추출하거나 주석을 추가하면 향후 맵 변경 시 유지보수가 용이해집니다.
126-133: 존 좌표가 하드코딩되어 있으며 서버DEFAULT_ZONE_BOUNDS와 동기화 필요.이 존 정의는 테스트의 기대값(
plaza: {768,768,512,512})과는 일치하지만,WorldPackLoader.getDefaultZoneBounds와는 불일치합니다. 존 경계가 3곳(ZoneSystem, WorldPackLoader, Minimap)에 개별적으로 하드코딩되어 있어 동기화 관리가 어렵습니다.공유 패키지(
@openclawworld/shared)에 존 경계를 정의하고 재사용하는 것을 권장합니다.packages/server/src/openapi.ts (1)
8-787: OpenAPI 스키마와 TypeScript 타입 간의 동기화를 자동화하는 것을 권장합니다.현재 OpenAPI 스펙이 TypeScript 타입과 수동으로 별도 관리되어 여러 곳에서 스키마 드리프트가 발생하고 있습니다(
RegisterRequest,ProfileUpdateRequest,ChatChannel등).zod-to-openapi또는typescript-json-schema같은 도구를 사용하여 TypeScript 타입으로부터 OpenAPI 스키마를 자동 생성하면 일관성을 보장할 수 있습니다.packages/client/src/game/scenes/GameScene.ts (1)
493-516:entity에서currentZone을 추출할 때as unknown as ...이중 캐스트가 사용됩니다.Line 494의
(entity as unknown as { currentZone?: ZoneId }).currentZone는Entity타입이currentZone을 선언하지 않기 때문에 필요한 것으로 보입니다. 이 패턴은 타입 변경 시 컴파일 오류 없이 런타임 버그로 이어질 수 있습니다.가능하면
Entity타입/인터페이스에currentZone?: ZoneId를 추가하여 타입 안전성을 확보하는 것을 권장합니다.packages/client/src/world/TileInterpreter.ts (2)
95-106:loadFromImage에서getImageData를 타일마다 개별 호출하고 있어 비효율적입니다.64×64 맵 기준 4,096회의
getImageData호출이 발생합니다. 전체 이미지 데이터를 한 번에 가져온 후 오프셋으로 접근하면 성능이 크게 개선됩니다.♻️ 리팩터링 제안
+ const imageData = ctx.getImageData(0, 0, source.width, source.height); + const pixels = imageData.data; + for (let y = 0; y < tilesY; y++) { const row: TileInfo[] = []; for (let x = 0; x < tilesX; x++) { const sampleX = x * this.tileSize + this.tileSize / 2; const sampleY = y * this.tileSize + this.tileSize / 2; - const pixel = ctx.getImageData(sampleX, sampleY, 1, 1).data; + const offset = (sampleY * source.width + sampleX) * 4; - const tileInfo = this.interpretColor(pixel[0], pixel[1], pixel[2]); + const tileInfo = this.interpretColor(pixels[offset], pixels[offset + 1], pixels[offset + 2]); row.push(tileInfo); } grid.push(row); }
155-157:interpretColor의 기본 폴백값에zoneId가 빠져있습니다.매칭되는 색상 범위가 없을 때
{ type: 'grass', collision: false, isDoor: false }를 반환하지만zoneId속성이 누락되어 있습니다.TileInfo타입에서zoneId는 optional이므로 런타임 에러는 아니지만, 명시적으로zoneId: undefined를 추가하면 일관성이 높아집니다.packages/server/src/bots/WanderBot.ts (1)
206-213:DIRECTION_DX/DIRECTION_DY배열을tryMove호출마다 재생성하고 있습니다.이 상수 배열들은 모듈 레벨이나 클래스 static 필드로 한 번만 정의하면 됩니다.
♻️ 리팩터링 제안
클래스 외부 모듈 스코프에 정의:
+const DIRECTION_DX = [0, 1, 1, 1, 0, -1, -1, -1] as const; +const DIRECTION_DY = [-1, -1, 0, 1, 1, 1, 0, -1] as const; + export class WanderBot {
tryMove내부에서 제거:- const DIRECTION_DX = [0, 1, 1, 1, 0, -1, -1, -1]; - const DIRECTION_DY = [-1, -1, 0, 1, 1, 1, 0, -1]; - const dx = DIRECTION_DX[direction]; + const dx = DIRECTION_DX[direction]!;
| canMoveTo(fromTx: number, fromTy: number, toTx: number, toTy: number): boolean { | ||
| if (this.isBlocked(toTx, toTy)) return false; | ||
|
|
||
| const dx = Math.abs(toTx - fromTx); | ||
| const dy = Math.abs(toTy - fromTy); | ||
| if (dx > 1 || dy > 1) return false; | ||
|
|
||
| if (dx === 1 && dy === 1) { | ||
| if (this.isBlocked(fromTx, toTy) && this.isBlocked(toTx, fromTy)) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "*.ts" -not -path "*/client/*" -not -path "*/test/*" -not -path "*/node_modules/*" | head -20Repository: Two-Weeks-Team/openClawWorld
Length of output: 890
🏁 Script executed:
rg -n "canMoveTo|CollisionSystem" --type=ts -g '!**/client/**' -g '!**/node_modules/**' -A 5Repository: Two-Weeks-Team/openClawWorld
Length of output: 9497
🏁 Script executed:
cat -n packages/server/src/collision/CollisionSystem.tsRepository: Two-Weeks-Team/openClawWorld
Length of output: 3110
🏁 Script executed:
cat -n packages/server/src/movement/MovementSystem.tsRepository: Two-Weeks-Team/openClawWorld
Length of output: 4340
서버의 이동 검증 로직에 대각선 이동 정책이 없습니다 — 클라이언트와 서버 간 desync 위험이 있습니다.
클라이언트의 canMoveTo는 대각선 이동 시 양쪽 인접 타일이 모두 막혔을 때만 이동을 차단하는 permissive 정책을 구현합니다. 반면 서버의 MovementSystem.setDestination은 목표 타일이 막혀 있는지만 확인하고, 대각선 이동 검증을 전혀 수행하지 않습니다 (lines 28-34).
서버가 대각선 코너 타일 검사를 구현하지 않으면, 특정 대각선 이동 시나리오에서 클라이언트가 허용하지만 서버가 거부하거나 그 반대의 상황이 발생할 수 있습니다. 서버도 클라이언트와 동일한 대각선 이동 정책을 구현해야 합니다.
🤖 Prompt for AI Agents
In `@packages/client/src/systems/CollisionSystem.ts` around lines 39 - 53, The
server's MovementSystem.setDestination currently only checks whether the target
tile is blocked and lacks the diagonal-corner rule present in client
CollisionSystem.canMoveTo, causing desyncs; update MovementSystem.setDestination
to replicate the client's checks: compute dx/dy between from and to, reject
moves with dx>1 or dy>1, and for dx===1 && dy===1 additionally reject when both
adjacent orthogonal tiles (isBlocked(fromTx, toTy) && isBlocked(toTx, fromTy))
are blocked, using the same isBlocked helper used on the client so server-side
validation mirrors canMoveTo exactly.
| const COLOR_RANGES: ColorRange[] = [ | ||
| { r: [0, 50], g: [100, 200], b: [200, 255], type: 'water', collision: true }, | ||
| { r: [0, 100], g: [150, 255], b: [0, 100], type: 'grass', collision: false }, | ||
| { r: [200, 255], g: [200, 255], b: [0, 100], type: 'door', collision: false, isDoor: true }, | ||
| { r: [60, 100], g: [50, 80], b: [40, 70], type: 'wall', collision: true }, | ||
| { r: [180, 220], g: [180, 220], b: [180, 220], type: 'road', collision: false }, | ||
| { r: [150, 180], g: [150, 180], b: [150, 180], type: 'floor_plaza', collision: false }, | ||
| { r: [170, 200], g: [200, 230], b: [210, 240], type: 'floor_north', collision: false }, | ||
| { r: [190, 220], g: [200, 230], b: [190, 220], type: 'floor_west', collision: false }, | ||
| { r: [200, 230], g: [180, 210], b: [150, 180], type: 'floor_east', collision: false }, | ||
| { r: [140, 170], g: [140, 170], b: [140, 170], type: 'floor_south', collision: false }, | ||
| { r: [100, 130], g: [140, 170], b: [200, 230], type: 'floor_lake', collision: false }, | ||
| ]; |
There was a problem hiding this comment.
COLOR_RANGES에서 floor_plaza와 floor_south의 RGB 범위가 겹칩니다.
floor_plaza: r[150,180], g[150,180], b[150,180]floor_south: r[140,170], g[140,170], b[140,170]
세 채널 모두 [150,170] 구간에서 겹치며, COLOR_RANGES 배열에서 floor_plaza가 먼저 정의되어 있으므로 해당 범위의 색상은 항상 floor_plaza로 분류됩니다. 의도한 floor_south 타일이 floor_plaza로 잘못 해석될 수 있습니다.
마찬가지로 road(r[180,220])와 floor_plaza(r[150,180])도 경계값 180에서 겹치고, floor_north와 floor_west도 일부 겹칩니다. 범위를 상호 배타적으로 조정하거나, 범위 우선순위가 의도된 것이라면 주석으로 명시해주세요.
🤖 Prompt for AI Agents
In `@packages/client/src/world/TileInterpreter.ts` around lines 12 - 24,
COLOR_RANGES contains overlapping RGB intervals causing ambiguous classification
(e.g., floor_plaza vs floor_south, road vs floor_plaza, floor_north vs
floor_west); update the ranges in the COLOR_RANGES array so each ColorRange is
mutually exclusive (adjust endpoints to non-overlapping intervals or tighten
channel bounds for 'floor_plaza', 'floor_south', 'road', 'floor_north',
'floor_west'), or if intentional, add a clear comment near the COLOR_RANGES
declaration documenting the priority ordering and why overlaps are acceptable.
|
This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation. |
Runner configuration: - Mac (primary): [self-hosted, macOS, ARM64] - Linux (Docker only): [self-hosted, Linux, X64]
The AssetGalleryScene module doesn't exist, causing build failures. Remove the import and scene reference from game config.
The Scalar API reference is used at runtime for serving /docs endpoint. Moving from devDependencies ensures it's available in production builds.
Use Math.imul for proper 32-bit multiplication and divide by 0x80000000 to ensure the result is in [0, 1) range. Fixes potential off-by-one errors in randomInt() when random() returned exactly 1.0.
- WorldPackLoader: align default zone bounds with ZoneSystem - Minimap: change mapHeight from 1664 to 2048 (64x64 tiles) - TileInterpreter: fix tileTypes order (west before east) and add decoration
- world.test.ts: update MAP_HEIGHT to 64, fix zone positions to match new plaza (1024,1024), north-block (960,200), west-block (300,1000) - worldpack-unified-map.test.ts: replace duplicate 'plaza' with 'lake', update all zone bounds and height assertions to 64x64 spec
pnpm/action-setup@v4 now auto-detects version from packageManager field in package.json. Specifying both causes version conflict error.
CodeQL autobuild requires Node.js to be available on PATH for TypeScript extraction. Adding pnpm and Node.js setup steps.
codelytv/pr-size-labeler is a container action that only supports Linux runners. Moving size job from macOS self-hosted to ubuntu.
Sync lockfile with previous commit that moved @scalar/express-api-reference from devDependencies to dependencies in server package.
These workflows are resource-intensive and only need to run on main branch pushes. PRs are validated by the main CI workflow (typecheck, lint, test).
These tests were based on old 64x52 map spec and had assertions for 8 NPCs when only 6 exist. Zone bounds and NPC/facility assignments are already validated by zone-system-64x64.test.ts and world-pack.test.ts.
These workflows are not needed for PR validation. Docker builds and E2E tests can be run manually or added back when infrastructure is ready.
Ignore: - .sisyphus/ (work plans and evidence) - assets/extracted/, assets/source/ (generated assets) - packages/client/public/assets/extracted/ - Root-level PNG files (test screenshots)
- convert_to_tiled.py: Convert map data to Tiled JSON format - generate_road_map.py: Generate road network from map image - render_map_with_tiles.py: Render map with tile overlays - visualize_roads.py: Visualize road data - extract_assets/: Asset extraction utilities
Add individual zone maps from previous 64x52 layout for reference: - lobby.json, office.json, meeting-center.json - lounge-cafe.json, arcade.json - packages/server/assets/maps/village.json
Delete tracked PNG files that were used for testing: - game-explore-*.png - game-state-*.png
- typecheck: add self-build step to avoid build dependency bottleneck - test: depend on build job and download artifacts - client: add code splitting for phaser/colyseus chunks - client: adjust chunkSizeWarningLimit for large game engine
Summary
/docsChanges
World Map
API Documentation
/docs/openapi.jsonBug Fixes
zone.exitevent not showing in client notification panelDocumentation
Testing
pnpm typecheck✅pnpm build✅New Endpoints
GET /docsGET /openapi.jsonSummary by CodeRabbit
릴리스 노트
New Features
Documentation
Chores