diff --git a/index.html b/index.html
index 4d4f02a..0b58ea1 100644
--- a/index.html
+++ b/index.html
@@ -45,7 +45,26 @@
width: 65px;
}
- .reset-btn {
+ #stats {
+ font-family: monospace;
+ color: #777;
+ padding: 8px 12px;
+ font-size: 11px;
+ line-height: 1.8;
+ border-bottom: 1px solid #1a1a1a;
+ }
+
+ #stats .label {
+ display: inline-block;
+ width: 65px;
+ color: #555;
+ }
+
+ #stats .value {
+ color: #c43c3c;
+ }
+
+ .reset-btn {
width: calc(100% - 16px);
margin: 12px 8px;
padding: 6px;
diff --git a/src/App.ts b/src/App.ts
index ec7c290..6aa39ec 100644
--- a/src/App.ts
+++ b/src/App.ts
@@ -1,6 +1,7 @@
import Config from "./Config";
import GUI from "./GUI";
import Renderer from "./Renderer";
+import StatsOverlay from "./StatsOverlay";
import AntsComputeScene from "./scenes/AntsComputeScene";
import AntsDiscretizeScene from "./scenes/AntsDiscretizeScene";
import DrawScene from "./scenes/DrawScene";
@@ -25,6 +26,7 @@ export default new (class App {
);
private scenes!: SceneCollection;
private gui: GUI = new GUI();
+ private statsOverlay: StatsOverlay = new StatsOverlay();
private renderLoop = (time: number): void => this.render(time);
private lastTime: number = 0;
private queuedSimSteps: number = 0;
@@ -82,6 +84,7 @@ export default new (class App {
}
this.renderer.renderSimulation(this.scenes);
+ this.statsOverlay.recordTick();
}
private render(time: number) {
@@ -105,6 +108,11 @@ export default new (class App {
this.renderer.renderToScreen(this.scenes);
+ this.statsOverlay.update(
+ this.scenes.screen.pointerPosition,
+ this.renderer.colonyStatsData,
+ );
+
this.lastTime = time;
}
})();
diff --git a/src/Renderer.ts b/src/Renderer.ts
index 8e09a51..54dbce2 100644
--- a/src/Renderer.ts
+++ b/src/Renderer.ts
@@ -1,7 +1,7 @@
import type { WebGLRenderTarget } from "three";
import * as THREE from "three";
import type { SceneCollection } from "./App";
-import ColonyStats from "./ColonyStats";
+import ColonyStats, { type ColonyStatsData } from "./ColonyStats";
import Config from "./Config";
import {
MAT_AIR,
@@ -406,6 +406,10 @@ export default class Renderer {
}
}
+ public get colonyStatsData(): ColonyStatsData {
+ return this.colonyStats.data;
+ }
+
static convertNumberToFloatString(n: number): string {
return n.toFixed(8);
}
diff --git a/src/StatsOverlay.ts b/src/StatsOverlay.ts
new file mode 100644
index 0000000..0de2898
--- /dev/null
+++ b/src/StatsOverlay.ts
@@ -0,0 +1,69 @@
+import type { ColonyStatsData } from "./ColonyStats";
+import Config from "./Config";
+
+export default class StatsOverlay {
+ private el: HTMLElement;
+ private cursorEl: HTMLElement;
+ private tpsEl: HTMLElement;
+ private antsEl: HTMLElement;
+ private carryingEl: HTMLElement;
+
+ private tickTimestamps: number[] = [];
+
+ constructor() {
+ this.el = document.createElement("div");
+ this.el.id = "stats";
+
+ // insert after #info
+ // biome-ignore lint/style/noNonNullAssertion: #info exists in index.html
+ const info = document.getElementById("info")!;
+ info.after(this.el);
+
+ this.cursorEl = this.addLine("cursor");
+ this.tpsEl = this.addLine("tps");
+ this.antsEl = this.addLine("ants");
+ this.carryingEl = this.addLine("carrying");
+ }
+
+ private addLine(label: string): HTMLElement {
+ const line = document.createElement("div");
+ line.innerHTML = `${label}—`;
+ this.el.appendChild(line);
+ // biome-ignore lint/style/noNonNullAssertion: .value span created by innerHTML above
+ return line.querySelector(".value")!;
+ }
+
+ public recordTick(): void {
+ const now = performance.now();
+ this.tickTimestamps.push(now);
+ // keep last 60 timestamps for averaging
+ if (this.tickTimestamps.length > 60) {
+ this.tickTimestamps.shift();
+ }
+ }
+
+ public update(
+ pointerPosition: { x: number; y: number },
+ colonyStats: ColonyStatsData,
+ ): void {
+ const gridX = Math.floor(pointerPosition.x * Config.worldSize);
+ const gridY = Math.floor(pointerPosition.y * Config.worldSize);
+ this.cursorEl.textContent = `${gridX}, ${gridY}`;
+
+ // tps from tick timestamps
+ if (this.tickTimestamps.length >= 2) {
+ const span =
+ this.tickTimestamps[this.tickTimestamps.length - 1] -
+ this.tickTimestamps[0];
+ const tps =
+ span > 0 ? ((this.tickTimestamps.length - 1) / span) * 1000 : 0;
+ this.tpsEl.textContent = `${Math.round(tps)}`;
+ }
+
+ this.antsEl.textContent = `${colonyStats.totalAnts}`;
+ const carrying = Math.round(
+ colonyStats.foragerRatio * colonyStats.totalAnts,
+ );
+ this.carryingEl.textContent = `${carrying}`;
+ }
+}