docs: add day counter + 2x indicator implementation plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
# Day Counter + 2x Indicator Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Add a "Day N" counter to the left of the day/night indicator and double the indicator's size.
|
||||
|
||||
**Architecture:** Server computes `dayNumber` centrally in GameLoop, broadcasts it in StateUpdate. Client TimeIndicator renders it. Existing inline day calculation in inventionSystem replaced with centralized method.
|
||||
|
||||
**Tech Stack:** TypeScript, Socket.io, HTML/CSS DOM (TimeIndicator), shared types
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add `dayNumber` to StateUpdate type
|
||||
|
||||
**Files:**
|
||||
- Modify: `shared/src/types.ts:178-182`
|
||||
|
||||
**Step 1: Add dayNumber field to StateUpdate**
|
||||
|
||||
In `shared/src/types.ts`, add `dayNumber` to the `StateUpdate` interface:
|
||||
|
||||
```typescript
|
||||
export interface StateUpdate {
|
||||
entities: EntityState[];
|
||||
tick: number;
|
||||
gameTime: number; // 0.0-1.0 normalized position in day/night cycle
|
||||
dayNumber: number; // 1-indexed day counter
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Rebuild shared types**
|
||||
|
||||
Run: `npx -w shared tsc`
|
||||
Expected: Clean compile, no errors
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add shared/src/types.ts
|
||||
git commit -m "feat: add dayNumber to StateUpdate type"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Add getCycleTicks() and getDayNumber() to GameLoop
|
||||
|
||||
**Files:**
|
||||
- Modify: `server/src/game/GameLoop.ts:342-347`
|
||||
|
||||
**Step 1: Add getCycleTicks() private helper and getDayNumber() method**
|
||||
|
||||
Replace the existing `getGameTime()` method and add new methods after it in `server/src/game/GameLoop.ts`:
|
||||
|
||||
```typescript
|
||||
private getCycleTicks(): number {
|
||||
const dayTicks = 100 / this.runtimeConstants.get('ENERGY_DECAY_PER_TICK');
|
||||
const nightTicks = dayTicks / this.runtimeConstants.get('DAY_NIGHT_RATIO');
|
||||
return Math.round(dayTicks + nightTicks);
|
||||
}
|
||||
|
||||
getGameTime(): number {
|
||||
const cycleTicks = this.getCycleTicks();
|
||||
return (this.tick % cycleTicks) / cycleTicks;
|
||||
}
|
||||
|
||||
getDayNumber(): number {
|
||||
return Math.floor(this.tick / this.getCycleTicks()) + 1;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Verify server compiles**
|
||||
|
||||
Run: `npx -w server tsc --noEmit`
|
||||
Expected: Clean compile
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add server/src/game/GameLoop.ts
|
||||
git commit -m "feat: add getCycleTicks() and getDayNumber() to GameLoop"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Include dayNumber in state broadcast
|
||||
|
||||
**Files:**
|
||||
- Modify: `server/src/network/stateSerializer.ts:100-102`
|
||||
- Modify: `server/src/network/SocketServer.ts:36`
|
||||
|
||||
**Step 1: Update serializeStateUpdate to accept and include dayNumber**
|
||||
|
||||
In `server/src/network/stateSerializer.ts`, change the function signature and return value:
|
||||
|
||||
```typescript
|
||||
export function serializeStateUpdate(world: World, tick: number, gameTime: number, dayNumber: number): StateUpdate {
|
||||
const entities = world.query('position').map(id => serializeEntity(world, id));
|
||||
return { entities, tick, gameTime, dayNumber };
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Update SocketServer broadcast call**
|
||||
|
||||
In `server/src/network/SocketServer.ts` line 36, add `getDayNumber()`:
|
||||
|
||||
```typescript
|
||||
const update = serializeStateUpdate(this.gameLoop.world, this.gameLoop.getTick(), this.gameLoop.getGameTime(), this.gameLoop.getDayNumber());
|
||||
```
|
||||
|
||||
**Step 3: Verify server compiles**
|
||||
|
||||
Run: `npx -w server tsc --noEmit`
|
||||
Expected: Clean compile
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add server/src/network/stateSerializer.ts server/src/network/SocketServer.ts
|
||||
git commit -m "feat: broadcast dayNumber in state updates"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Replace inline day calculation in inventionSystem
|
||||
|
||||
**Files:**
|
||||
- Modify: `server/src/systems/inventionSystem.ts:14,21-26,148-152`
|
||||
|
||||
**Step 1: Pass getDayNumber callback into createInventionSystem**
|
||||
|
||||
Change the factory function signature to accept a `getDayNumber` callback:
|
||||
|
||||
```typescript
|
||||
export function createInventionSystem(
|
||||
llmService: LlmService,
|
||||
narrationService: NarrationService,
|
||||
eventMemoryService: EventMemoryService,
|
||||
onInventionCreated?: (summary: InventionSummary) => void,
|
||||
getDayNumber?: () => number,
|
||||
): InventionSystem {
|
||||
```
|
||||
|
||||
**Step 2: Replace inline day calculation**
|
||||
|
||||
Replace lines 148-152 (the inline day calculation):
|
||||
|
||||
```typescript
|
||||
// Compute current day
|
||||
const dayTicks = 100 / ENERGY_DECAY_PER_TICK;
|
||||
const nightTicks = dayTicks / DAY_NIGHT_RATIO;
|
||||
const cycleTicks = Math.round(dayTicks + nightTicks);
|
||||
const day = Math.floor(tick / cycleTicks) + 1;
|
||||
```
|
||||
|
||||
With:
|
||||
|
||||
```typescript
|
||||
const day = getDayNumber ? getDayNumber() : 1;
|
||||
```
|
||||
|
||||
**Step 3: Remove unused imports**
|
||||
|
||||
Remove `ENERGY_DECAY_PER_TICK` and `DAY_NIGHT_RATIO` from the import on line 14 (if no longer used elsewhere in file):
|
||||
|
||||
```typescript
|
||||
// Remove this line:
|
||||
import { ENERGY_DECAY_PER_TICK, DAY_NIGHT_RATIO } from '@dflike/shared';
|
||||
```
|
||||
|
||||
**Step 4: Update GameLoop to pass getDayNumber**
|
||||
|
||||
In `server/src/game/GameLoop.ts` around line 75, update the `createInventionSystem` call:
|
||||
|
||||
```typescript
|
||||
this.inventionSystem = createInventionSystem(
|
||||
this.llmService,
|
||||
this.narrationService,
|
||||
this.eventMemoryService,
|
||||
(summary) => this.onInventionCreated?.(summary),
|
||||
() => this.getDayNumber(),
|
||||
);
|
||||
```
|
||||
|
||||
**Step 5: Run tests**
|
||||
|
||||
Run: `npm -w server run test`
|
||||
Expected: All 408 tests pass
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add server/src/systems/inventionSystem.ts server/src/game/GameLoop.ts
|
||||
git commit -m "refactor: use centralized getDayNumber() in inventionSystem"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Double TimeIndicator dimensions and add day counter
|
||||
|
||||
**Files:**
|
||||
- Modify: `client/src/ui/TimeIndicator.ts` (full file)
|
||||
|
||||
**Step 1: Add dayLabel field and update constructor**
|
||||
|
||||
Add a `dayLabel` property and a new DOM element for it. Update all dimensions to 2x. The full updated constructor and fields:
|
||||
|
||||
Add new field:
|
||||
```typescript
|
||||
private dayLabel: HTMLDivElement;
|
||||
private lastDayNumber = 0;
|
||||
```
|
||||
|
||||
In the constructor, update these CSS values:
|
||||
- Container gap: `5px` → `10px`
|
||||
- Sun icon font-size: `10px` → `20px`, text-shadow blur `4px` → `8px`
|
||||
- Track height: `8px` → `16px`, border: `2px` → `4px`, box-shadow blur `6px` → `12px`
|
||||
- Day section width: `dayFraction * 120` → `dayFraction * 240`
|
||||
- Divider width: `1px` → `2px`
|
||||
- Night section width: `(1 - dayFraction) * 120` → `(1 - dayFraction) * 240`
|
||||
- Marker width: `3px` → `6px`, top: `-1px` → `-2px`, height calc: `calc(100% + 2px)` → `calc(100% + 4px)`, box-shadow blur `4px` → `8px`
|
||||
- Moon icon font-size: `9px` → `18px`, text-shadow blur `4px` → `8px`
|
||||
|
||||
Add day label element before appending children:
|
||||
|
||||
```typescript
|
||||
// Day counter label
|
||||
this.dayLabel = document.createElement('div');
|
||||
this.dayLabel.style.cssText = `
|
||||
font-size: 10px;
|
||||
color: #e0d0b0;
|
||||
text-shadow: 0 0 4px #00000088;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
this.dayLabel.textContent = 'Day 1';
|
||||
```
|
||||
|
||||
Update container children order — day label first:
|
||||
|
||||
```typescript
|
||||
this.container.appendChild(this.dayLabel);
|
||||
this.container.appendChild(this.sunIcon);
|
||||
this.container.appendChild(track);
|
||||
this.container.appendChild(this.moonIcon);
|
||||
```
|
||||
|
||||
**Step 2: Update the update() method**
|
||||
|
||||
Change signature to accept dayNumber and update the track width constant:
|
||||
|
||||
```typescript
|
||||
update(gameTime: number, dayNumber: number): void {
|
||||
// Update day label when day changes
|
||||
if (dayNumber !== this.lastDayNumber) {
|
||||
this.lastDayNumber = dayNumber;
|
||||
this.dayLabel.textContent = `Day ${dayNumber}`;
|
||||
}
|
||||
|
||||
const key = Math.round(gameTime * 200).toString();
|
||||
if (key === this.lastTimeKey) return;
|
||||
this.lastTimeKey = key;
|
||||
|
||||
const totalWidth = 240 + 2; // 240px sections + 2px divider
|
||||
const px = gameTime * totalWidth;
|
||||
// ... rest unchanged
|
||||
```
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add client/src/ui/TimeIndicator.ts
|
||||
git commit -m "feat: double TimeIndicator size and add day counter"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Pass dayNumber from GameScene to TimeIndicator
|
||||
|
||||
**Files:**
|
||||
- Modify: `client/src/scenes/GameScene.ts`
|
||||
|
||||
**Step 1: Add dayNumber tracking field**
|
||||
|
||||
Find where `targetGameTime` and `currentGameTime` are declared and add:
|
||||
|
||||
```typescript
|
||||
private currentDayNumber = 1;
|
||||
```
|
||||
|
||||
**Step 2: Store dayNumber from state updates**
|
||||
|
||||
In `handleStateUpdate()` (around line 674-675), add:
|
||||
|
||||
```typescript
|
||||
private handleStateUpdate(update: StateUpdate): void {
|
||||
this.targetGameTime = update.gameTime;
|
||||
this.currentDayNumber = update.dayNumber;
|
||||
```
|
||||
|
||||
**Step 3: Pass dayNumber to TimeIndicator.update()**
|
||||
|
||||
In the `update()` method (around line 799), change:
|
||||
|
||||
```typescript
|
||||
this.timeIndicator.update(this.currentGameTime, this.currentDayNumber);
|
||||
```
|
||||
|
||||
**Step 4: Verify client builds**
|
||||
|
||||
Run: `npm -w client run build`
|
||||
Expected: Clean build
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add client/src/scenes/GameScene.ts
|
||||
git commit -m "feat: pass dayNumber from state updates to TimeIndicator"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Manual verification
|
||||
|
||||
**Step 1: Start server and client**
|
||||
|
||||
Run server: `npm -w server run dev`
|
||||
Run client: `npm -w client run dev`
|
||||
|
||||
**Step 2: Verify in browser**
|
||||
|
||||
- Day/night indicator should be approximately 2x larger
|
||||
- "Day N" text should appear to the left of the sun icon
|
||||
- Day counter should increment when the day/night cycle completes
|
||||
- Marker should still move smoothly across the track
|
||||
|
||||
**Step 3: Run all server tests**
|
||||
|
||||
Run: `npm -w server run test`
|
||||
Expected: All tests pass
|
||||
Reference in New Issue
Block a user