A RuneScape 3 dashboard for coordinating the Evil Trees Distraction & Diversion across all worlds in real time.
Evil Trees spawn in waves across RS3 worlds. This tool lets you (and a group of players) track the state of every world's tree on one screen:
- World cards displayed in a compact grid — all visible at once on a 1080p monitor (world list is configurable in
shared/worlds.json) - Per-world status tracking: no tree → sapling → mature → alive → dead → (cycle repeats)
- Automatic state transitions based on known game timings (sapling matures at 5 min, tree dies at 30 min, fallen tree despawns at 10 min after death)
- Automatic health caps reflecting lightning strikes mid-fight: health is capped to 50% at 10 minutes and 25% at 20 minutes, with animated lightning bolt effects on the affected card
- Three tools on every card:
- Spawn timer — set a countdown to the next expected spawn, with optional location hint
- Tree info — record tree type, health %, location hint, and exact location
- Mark dead — mark a tree as dead with one click (with confirmation)
- Click any card to open a detail view showing the complete status (tree type, full location, live countdowns). All three tools are accessible directly from the detail view, and a clear world state option lets you instantly reset a world if you recorded information on the wrong one
- Sort & filter bar (collapsible) with multiple options:
- Sort by world number, soonest/latest spawn or end time, health, or favorites
- Filter by favorites, P2P/F2P
- Tree type filters — filter the grid by tree species (Unknown, Tree, Oak, Willow, Maple, Yew, Magic, Elder) so you can focus on the trees you want to cut
- Info filters — tri-state chips (Intel, Hint, Location, Health) to show worlds that need a piece of information or already have it
- Hidden worlds filter — tri-state chip to exclude, include, or show only hidden worlds
- World search — search by world number in the header; auto-opens the detail view when exactly one world matches (sidebar mode)
- Hidden worlds — hide any world from the grid via the EyeOff button on cards or detail views; hidden worlds are excluded by default but can be revealed with the Hidden filter chip
- Map view (PoC) — a Map button in the header opens an interactive RuneScape 3 world map (Leaflet + the community-maintained mejrs RS3 tiles) with a tree pin at every known Evil Tree spawn location; click a pin to see which in-game hint(s) point to that spot
- Visual effects — lightning bolt animations on auto health transitions, ember spark particles on dead trees (can be toggled in settings)
- Sidebar panel (opt-in, available on screens ≥ 640px) — tool views and the detail view can open in a resizable panel beside the grid instead of replacing it, so you never lose sight of all 137 worlds. Dock left or right; drag the handle to resize; width is remembered across sessions and preserved when swapping sides. Enable in Settings (⚙). A toolbar above every view shows four quick-jump buttons (View · Timer · Tree · Dead) to switch tools without returning to the grid, plus dock controls: when in fullscreen use the panel-left/right icons to enter sidebar mode directly; when in sidebar use the expand icon to pop back to fullscreen or Close to return to the grid
- Settings panel (⚙ button) — toggle visual effects, the scrolling tip ticker, sidebar mode, and whether the session browser opens automatically on startup
- Scrolling tip ticker in the footer with gameplay tips and UI hints
- Real-time multi-user sync — create a session (6-character code), share it with friends, and everyone sees updates instantly via WebSocket. Session management lives in a dedicated Session panel (fullscreen or docked in the sidebar) opened from the session bar in the header
- Session browser — browse and join public managed sessions without a code; sessions opt in to visibility by setting a name and toggling Listed in the Session panel; the browser auto-refreshes every 15 seconds and shows each session's name, description, member count, active worlds, and last activity time; opens automatically on startup when you are not in a session (can be disabled in Settings)
- Follow scout's world — when you have an Alt1 scout linked to your dashboard, this toggle (in the Session panel and Settings) auto-opens the detail view for whatever world the scout has hopped to, so spotters and supporters always look at the same world. Re-enables automatically each time you mint a new Alt1 link
- Managed sessions — upgrade any session to invite-only mode: generate named invite links with roles (Owner / Moderator / Scout / Viewer), see a live member list, change roles, kick or ban members. Admins see invite tokens and copy-join-link buttons; world updates show who submitted them. Managed sessions can set a name that appears in the session bar
- Discord intel copy — a copy button in the header formats all currently visible worlds with active intel into a Discord message using
<t:UNIX:R>relative timestamps, respecting your active sort order and filters. Disabled when no intel is visible - State persists in
localStoragebetween sessions; when in a sync session, the server is the source of truth - Installable as a PWA — add to home screen on mobile for a standalone app experience (HTTPS required; see deployment section)
- One player clicks Create Session in the session bar at the top of the grid
- A 6-character session code is generated (e.g.
A3KW7N) — your current local state is shared into the new session automatically - Share the code (or a
#join=A3KW7Nlink) with friends — they click Join Session and type it in, or open the link to join automatically - All connected users see world updates in real time
- Click Leave to disconnect and return to local-only mode (your last-seen state is saved to
localStorage)
The Session panel (open via the session bar) shows full session controls, scout linking, and — when in managed mode — the member list.
Session codes use unambiguous characters only (no 0/O or 1/I). Session limits: max 1000 concurrent sessions, max 1000 clients per session (500 in managed mode). Sessions expire after 10 days of inactivity or 24 hours with no connected clients.
Sessions survive server restarts and redeploys: session state (world states, members, settings) is snapshotted to disk and restored when the server comes back up, so a redeploy mid-wave doesn't lose your tracking data — clients reconnect automatically.
Tip: If you already have local tracking data when you join a session, you'll be prompted to contribute it to the session — only worlds not already tracked in the session will be added.
Any member can fork the current session into a new managed (invite-only) session from the Session panel. Forking creates a fresh managed session and notifies everyone in the current session with an invite link — each person clicks the link to self-register and migrate over. The fork invite window is open for 15 minutes, with a 15-minute cooldown between forks.
- Create invites — give each member a name and a role: Scout (can submit updates) or Viewer (read-only)
- Roles: Owner · Moderator · Scout · Viewer. Moderators can create invites, kick, and ban members
- Member list — see who is connected, their role, and (for admins) their invite token and copy-join-link button
- Admin controls — change a member's role, rename them, kick them (temporary disconnect — token stays valid), ban them (disconnects + revokes token), or transfer ownership
- Open join — enable self-service named joins from the session browser; anyone can enter their name and join as a Viewer without needing an admin-generated invite token
- Session visibility — set a session name, optional description, and toggle Listed to appear in the public session browser. Listing a session also admits anonymous read-only viewers who discover it from the browser
- World updates in managed sessions show the submitter's name and role
An Alt1 Toolkit plugin for scouts — players who hop worlds looking for active trees. It connects to an Ectotrees sync session and lets you submit intel directly from inside RuneScape without switching windows. Three reporting modes cover the full scouting workflow:
- Session: join by entering a 12-character identity token or pasting an identity link (a URL containing
#identity=TOKEN). For unmanaged sessions, the dashboard's Session panel exposes a Copy identity link button that generates a token for your scout to paste; for managed sessions, use the invite link from an admin. Session is remembered and auto-resumed on next launch - Scout linking: once authenticated by identity token, the dashboard shows a live indicator of which world you are on as you hop. In managed sessions your scout is recognized by name
- Auto-world (toggle) — polls Alt1 gamestate; when a world hop is detected, the world field updates automatically and the linked dashboard is notified in real time
-
Timer mode — pre-spawn timer reporting
- Manual dialog scan — reads the in-game Spirit Tree dialog via Alt1 pixel scanning to extract the spawn timer and location hint in one click
- Auto-scan (toggle) — watches for clicks inside RuneScape and automatically triggers a dialog scan 150–800 ms after each click, catching the dialog as soon as it opens
- Auto-submit (toggle) — starts a 10-second countdown once world, time, and hint are all filled in; the submission payload is snapshotted at countdown start so a world hop during the countdown doesn't corrupt it; cancel by clicking the auto-submit button or clearing a field; hint is optional, manual submit is always available
-
Tree mode — post-spawn tree info reporting
- Submit tree type, location hint, and exact location once a tree has spawned
-
Dead mode — dead tree marking
- Mark a tree as dead with optional location info
- Location hint is preserved when transitioning from pre-spawn reporting
- Auto-submit enabled if chat detection identifies a tree death
The plugin is built separately from the main app (alt1-plugin/) and is served at /alt1 in both dev and production. To add it to Alt1 Toolkit, open http://localhost:5173/alt1 inside Alt1 and click Add to Alt1, or use the alt1://addapp/ install link shown on that page.
Edit shared/worlds.json to add or remove worlds:
{ "worlds": [{ "id": 1, "type": "P2P" }, { "id": 2, "type": "F2P" }] }Prerequisites: Node.js 24+ (use nvm use if you have nvm — the project includes a .nvmrc)
# Install dependencies
npm install
# Start the backend server (http://localhost:3001)
npm run server
# In a second terminal, start the frontend dev server (http://localhost:5173)
npm run devThe Vite dev server proxies /api and /ws requests to the backend, so you only need to open http://localhost:5173 in your browser.
To expose the dev server to your local network (e.g. for testing on a phone):
npm run hostThe app works fully offline — if you don't start the server, all tracking features work locally with localStorage. The server is only needed for multi-user sync sessions.
npm run build # production build (output in dist/)
npm run lint # run ESLint
npx tsc --noEmit # type-check client
npm run server:check # type-check server
npm test # unit tests (Vitest — mutations, validation, persistence)
npm run test:e2e # E2E tests (Playwright — auto-starts dev server)
npm run test:e2e:ui # Playwright visual UI for debugging testsUse this when you want to test the app the same way a deploy server would run it.
From the project root:
docker build -t ectotrees:local .What this means:
ectotreesis the image namelocalis the tag (a label). Here it means "my local test build", not a formal release
docker run --name ectotrees-local -p 3001:3001 \
-e APP_URL=http://localhost:3001 \
-e DATA_DIR=/app/data \
-v ectotrees-local-data:/app/data \
-d ectotrees:localThen open http://localhost:3001.
What this means:
- The image runs in production mode, which requires
APP_URLandDATA_DIR— the server refuses to start without them rather than running misconfigured APP_URLis the public base URL of the app (used for the WebSocket origin allowlist and invite links)DATA_DIRis where session state is snapshotted so sessions survive restarts-v ectotrees-local-data:/app/datamounts a named Docker volume at that path, so the snapshot outlives the container itself
If you already have a previous local container, remove it first:
docker rm -f ectotrees-local
docker build -t ectotrees:local .
docker run --name ectotrees-local -p 3001:3001 \
-e APP_URL=http://localhost:3001 \
-e DATA_DIR=/app/data \
-v ectotrees-local-data:/app/data \
-d ectotrees:localRemoving the container does not remove the ectotrees-local-data volume, so your sessions carry over to the new container. Run docker volume rm ectotrees-local-data if you want a clean slate.
docker ps # running containers
docker logs -f ectotrees-local # live app logs
docker stop ectotrees-local # stop containerUse this when running as an installable PWA on mobile devices (iOS requires HTTPS). This project ships with a Caddyfile and a docker-compose.example.yml that set up the app behind Caddy, which provisions TLS certificates automatically via Let's Encrypt.
# 1. Copy the example compose file
cp docker-compose.example.yml docker-compose.yml
# 2. Create the required Caddy directories
mkdir -p caddy/{caddy_data,caddy_config}
# 3. Copy and configure the Caddyfile — replace 'ectotrees.example.com' with your domain
cp Caddyfile caddy/Caddyfile
nano caddy/Caddyfile
# 4. Start
docker compose up -dCaddy routes /api/* and /ws to the Node backend and falls through to the app for all other requests.
The compose file mounts ./data into the container as DATA_DIR, so session state survives redeploys. Keep that directory out of backups and logs — the snapshot contains member identity tokens.
The main app is host-agnostic by design: API calls use the relative path /api and WebSocket connects to ws(s)://<current-host>/ws. Caddy routes both to the Node backend, so no build-time configuration is needed.
The Alt1 plugin bakes the API/WS URL into its bundle at build time. If you self-host the backend at a non-standard URL, override it with a Docker build arg:
docker build --build-arg ECTOTREES_API=https://your-domain.com -t ectotrees:local .When ECTOTREES_API is set, the WebSocket URL is derived from it automatically (https:// → wss://). You can also set ECTOTREES_WS separately if needed.
- React 19 + TypeScript + Vite 8
- Tailwind CSS v4
- Express 5 + ws (WebSocket server)
- GSAP for particle animations
- Leaflet (CRS.Simple) for the interactive RS3 map view, using the community-maintained mejrs tile/icon layers
- Storybook 10 (
@storybook/react-vite) for the component playground and as the OG image source - Shared TypeScript types and mutation logic between client and server (
shared/)