feat: viewport modes, configurable server URL, deployment docs

- Add follow mode (F key) to cycle camera between NPCs
- Make server URL configurable via VITE_SERVER_URL env var
- Add comprehensive README with Debian LXC deployment guide
- Add character sprite assets to repo
- Add design docs and implementation plans

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 22:25:53 -05:00
parent d543f8d3f1
commit b94ad32806
5146 changed files with 3422 additions and 55 deletions

View File

@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"Bash(ls:*)",
"Bash(npm:*)",
"Bash(npx:*)",
"Bash(mkdir:*)",
"Bash(netstat:*)",
"Bash(taskkill:*)",
"Bash(node:*)"
]
}
}

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@ node_modules/
dist/
.vite/
client/public/assets/chars/
shared/tsconfig.tsbuildinfo

View File

@@ -17,9 +17,26 @@ Multiplayer NPC simulation game (Dwarf Fortress-inspired). Server-authoritative
- `npm install` -- install all workspace dependencies
- `npm -w server run dev` -- start server (port 3001)
- `npm -w server run test` -- run server tests (22 tests)
- `npm -w server run test:watch` -- run server tests in watch mode
- `npm -w client run dev` -- start client dev server (port 3000)
- `npm -w client run build` -- build client for production
## Tooling
- ESM modules throughout (`"type": "module"`)
- Server: tsx (runtime), vitest (tests)
- Client: vite (bundler), Phaser 3
- Shared: compiled to `shared/dist/` (auto-built)
## Key Entry Points
- `server/src/main.ts` -- server startup
- `server/src/game/GameLoop.ts` -- tick loop orchestration
- `client/src/main.ts` -- Phaser game bootstrap
- `client/src/scenes/GameScene.ts` -- main game scene
- `shared/src/types.ts` -- protocol types
- `shared/src/constants.ts` -- game constants
## Key Conventions
- ECS components are plain data objects, systems are pure functions

244
README.md
View File

@@ -2,33 +2,225 @@
A multiplayer browser game inspired by Dwarf Fortress. Watch autonomous NPCs with needs-driven AI wander, eat, and rest in a shared world. Connect as an observer or take control of an avatar.
## Quick Start
```bash
npm install
# Terminal 1: Start server
npm -w server run dev
# Terminal 2: Start client
npm -w client run dev
```
Open http://localhost:3000. Open multiple tabs for multiplayer.
### Asset Setup
Character assets must be present at `client/public/assets/chars/`. If missing:
```bash
cp -r chars client/public/assets/
```
## Controls
- **WASD / Arrow Keys** -- pan camera (camera mode) or move avatar (avatar mode)
- **TAB** -- toggle between camera and avatar mode
## Architecture
- **Server:** Node.js + Socket.io, server-authoritative ECS simulation at 10 ticks/sec
- **Client:** Phaser 3 + TypeScript, Vite bundler
- **Shared:** TypeScript types and constants
- **Shared:** TypeScript types and constants used by both server and client
```
dflike/
shared/ -- Types and constants (no runtime code)
server/ -- Node.js + Socket.io game server with ECS
client/ -- Phaser 3 + TypeScript renderer
chars/ -- Character sprite assets
docs/ -- Design and implementation docs
```
## Prerequisites
- **Node.js** >= 20 (tested with v22)
- **npm** >= 10
## Quick Start (Development)
```bash
npm install
# Copy character assets into the client public directory
cp -r chars client/public/assets/
# Terminal 1: Start server (port 3001)
npm -w server run dev
# Terminal 2: Start client dev server (port 3000)
npm -w client run dev
```
Open http://localhost:3000. Open multiple tabs for multiplayer.
## Controls
- **WASD / Arrow Keys** -- pan camera (camera mode) or move avatar (avatar mode)
- **TAB** -- toggle between camera, avatar, and follow modes
## Deployment on Debian LXC
These instructions cover running DFlike on a headless Debian (or Ubuntu) LXC container, serving the game on your local network.
### 1. System Setup
```bash
apt update && apt upgrade -y
apt install -y curl git
```
### 2. Install Node.js
Install Node.js 22.x via NodeSource:
```bash
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
node --version # should print v22.x.x
```
### 3. Clone and Install
```bash
cd /opt
git clone <your-repo-url> dflike
cd dflike
npm install
```
### 4. Copy Character Assets
The character sprite assets in `chars/` need to be copied into the client's public directory. They are excluded from the client build via `.gitignore`.
```bash
cp -r chars client/public/assets/
```
### 5. Build the Client
By default the client connects to the game server on the same hostname it was loaded from (port 3001). To override, set the `VITE_SERVER_URL` environment variable before building:
```bash
# Default: auto-detects from browser hostname (works for most setups)
npm -w client run build
# Or specify an explicit server URL
VITE_SERVER_URL=http://192.168.1.50:3001 npm -w client run build
```
For development, you can also create a `client/.env.local` file:
```
VITE_SERVER_URL=http://192.168.1.50:3001
```
### 6. Serve the Client Build
```bash
npm -w client run build
```
This outputs static files to `client/dist/`.
### 6. Serve the Client Build
Install a simple static file server to serve the built client:
```bash
npm install -g serve
```
Or use nginx (recommended for production):
```bash
apt install -y nginx
```
**nginx config** (`/etc/nginx/sites-available/dflike`):
```nginx
server {
listen 80;
server_name _;
root /opt/dflike/client/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
```
Enable and restart:
```bash
ln -s /etc/nginx/sites-available/dflike /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl restart nginx
```
### 7. Run the Game Server
Quick start:
```bash
cd /opt/dflike
npm -w server run start
```
The server listens on port 3001 by default. Override with the `PORT` environment variable:
```bash
PORT=4000 npm -w server run start
```
### 8. Run as a systemd Service
Create `/etc/systemd/system/dflike.service`:
```ini
[Unit]
Description=DFlike Game Server
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/dflike
ExecStart=/usr/bin/npx tsx server/src/main.ts
Restart=on-failure
RestartSec=5
Environment=PORT=3001
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
```
Enable and start:
```bash
systemctl daemon-reload
systemctl enable dflike
systemctl start dflike
systemctl status dflike # verify it's running
journalctl -u dflike -f # view logs
```
### 9. Firewall
If you have a firewall enabled, open the necessary ports:
```bash
# Game server (Socket.io)
ufw allow 3001/tcp
# Web server (nginx)
ufw allow 80/tcp
```
### Summary of Ports
| Service | Port | Protocol |
|---------------|------|----------|
| Game server | 3001 | TCP |
| Client (dev) | 3000 | TCP |
| Client (prod) | 80 | TCP |
## Development Commands
| Command | Description |
|--------------------------------|------------------------------------|
| `npm install` | Install all workspace dependencies |
| `npm -w server run dev` | Start server with hot reload |
| `npm -w server run start` | Start server (production) |
| `npm -w server run test` | Run server tests |
| `npm -w server run test:watch` | Run server tests in watch mode |
| `npm -w client run dev` | Start client dev server |
| `npm -w client run build` | Build client for production |

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Some files were not shown because too many files have changed in this diff Show More