- Date
- 0y 1m
- - Active groups
- 0
+ - Field population
- 0
- Urban population
- 0
- Ethnicities
- 0
- Cities
- 0
@@ -66,7 +66,7 @@
- Trade routes
- 0
- Farming knowledge
- 0.000
- Metallurgy knowledge
- 0.000
- - Collapse deaths
- 0
+ - Deaths
- 0
- Frame cost
- 0ms
diff --git a/render.js b/render.js
index c86688d..105ccbe 100644
--- a/render.js
+++ b/render.js
@@ -79,7 +79,13 @@ if (graphState.hoverPolityId !== null && !polityColor.isGraphHover) color = mix(
}
}
} else if (mode === "technology") {
-color = mix(terrainInfo[w.terrain[i]].color, [44, 42, 48], 0.35);
+const farming = w.farmingKnowledge?.[i] || 0;
+const metallurgy = w.metallurgyKnowledge?.[i] || 0;
+const tech = clamp(Math.max(farming, metallurgy), 0, 1);
+const techColor = mix([95, 171, 91], [202, 169, 102], metallurgy / Math.max(0.001, farming + metallurgy));
+color = tech > 0.01
+? mix(terrainInfo[w.terrain[i]].color, techColor, clamp(0.12 + tech * 0.62, 0.12, 0.74))
+: mix(terrainInfo[w.terrain[i]].color, [44, 42, 48], 0.35);
} else if (mode === "pheromone") {
const v = clamp(w.pheromone[i] / SimConfig.route.maxPheromone, 0, 1);
color = mix(mix(terrainInfo[w.terrain[i]].color, [18, 20, 18], 0.58), [216, 177, 86], v);
@@ -95,8 +101,9 @@ data[p + 3] = 255;
ctx.putImageData(image, 0, 0);
renderDisasters();
drawTradeLinks();
+drawNomadBands();
drawCampaigns();
-drawAgentsAndCities(mode);
+drawPopulationAndCities(mode);
drawGraphPolityHighlight();
maybeRenderStateGraph();
els.frameCost.textContent = `${Math.round(performance.now() - start)}ms`;
@@ -205,7 +212,39 @@ fillSquare(ctx, x, y, 1);
}
ctx.restore();
}
-function drawAgentsAndCities(mode) {
+function drawNomadBands() {
+if (!sim?.nomadBands?.length) return;
+const w = sim.world;
+ctx.save();
+for (const band of sim.nomadBands) {
+const ethnicityColor = sim.ethnicities.get(band.ethnicityId)?.color || [216, 177, 86];
+ctx.globalAlpha = 0.07;
+ctx.fillStyle = rgba(ethnicityColor, 0.45);
+for (const tile of band.influenceTiles || []) {
+ctx.fillRect(tile % w.size, Math.floor(tile / w.size), 1, 1);
+}
+ctx.globalAlpha = 0.34;
+ctx.strokeStyle = band.mode === "raiding" || band.mode === "invading"
+? "rgba(232, 101, 76, 0.95)"
+: band.mode === "settling"
+? "rgba(202, 169, 102, 0.9)"
+: rgba(ethnicityColor, 0.9, 18);
+ctx.lineWidth = 1;
+strokeCircle(ctx, band.x + 0.5, band.y + 0.5, Math.max(2.5, band.radius * 0.45));
+if (band.mode === "confederating") {
+ctx.globalAlpha = 0.26;
+strokeCircle(ctx, band.x + 0.5, band.y + 0.5, Math.max(4, band.radius * 0.68));
+}
+ctx.globalAlpha = 0.22;
+ctx.fillStyle = rgba(ethnicityColor, 0.9, 22);
+for (const tile of band.trailTiles || []) ctx.fillRect(tile % w.size, Math.floor(tile / w.size), 1, 1);
+ctx.globalAlpha = 0.82;
+ctx.fillStyle = rgba(ethnicityColor, 0.95, 36);
+fillSquare(ctx, Math.round(band.x), Math.round(band.y), 1);
+}
+ctx.restore();
+}
+function drawPopulationAndCities(mode) {
ctx.save();
if (mode !== "ethnicity") {
for (const city of sim.cities) {
@@ -218,23 +257,18 @@ fillSquare(ctx, city.x, city.y, radius);
}
ctx.globalAlpha = 1;
if (mode === "technology") {
-for (const a of sim.agents) {
-sim.ensureAgentTech(a);
-const farming = techLevel(a, "farming");
-const metallurgy = techLevel(a, "metallurgy");
-const tech = clamp(Math.max(farming, metallurgy), 0, 1);
-if (tech <= 0.01) continue;
-const color = mix([95, 171, 91], [202, 169, 102], metallurgy / Math.max(0.001, farming + metallurgy));
-ctx.fillStyle = rgba(color, clamp(0.32 + tech * 0.68, 0.32, 1));
-ctx.fillRect(a.x, a.y, 1, 1);
-}
+// Tile-level technology is rendered in the base pass.
} else if (mode === "ethnicity") {
-// Phase 2: ethnicity view is driven by tile-level population/culture fields.
-// Individual agents remain in the simulation but no longer define this layer.
+// Ethnicity view is driven by tile-level population/culture fields.
} else {
-ctx.fillStyle = "#eeeccf";
-for (const tile of sim.tileAgents.keys()) {
-ctx.fillRect(tile % sim.world.size, Math.floor(tile / sim.world.size), 1, 1);
+const w = sim.world;
+ctx.fillStyle = "rgba(238, 236, 207, 0.16)";
+for (let tile = 0; tile < w.count; tile++) {
+const pop = w.population?.[tile] || 0;
+if (pop < 6) continue;
+const alpha = clamp(Math.log1p(pop) / Math.log(160), 0.04, 0.18);
+ctx.fillStyle = `rgba(238, 236, 207, ${alpha})`;
+ctx.fillRect(tile % w.size, Math.floor(tile / w.size), 1, 1);
}
}
for (const city of sim.cities) {
@@ -329,23 +363,23 @@ return;
}
const w = sim.world;
const i = w.idx(x, y);
-const agent = findAgentAt(x, y);
const city = w.city[i] >= 0 ? sim.getCityById(w.city[i]) : null;
const terrain = terrainInfo[w.terrain[i]];
-const ethnicity = agent ? sim.ethnicities.get(agent.ethnicity) : null;
const waterInfluence = waterInfluenceAt(w, x, y).toFixed(2);
const cityEthnicity = city ? dominantComposition(city.ethnicityComposition) : null;
-const mismatch = agent ? sim.climateMismatch(agent.ethnicity, i) : 0;
const polity = cityPolity(city);
const territoryId = w.polity?.[i] ?? -1;
const territoryPolity = territoryId >= 0 ? sim.getPolityById(territoryId) : null;
const ethnicMix = sim.tileEthnicMix?.get(i);
const tileDominantEthnicity = ethnicMix?.size ? dominantComposition(ethnicMix) : w.dominantEthnicity[i];
const regionalEthnicity = tileDominantEthnicity != null && tileDominantEthnicity >= 0 ? `E${tileDominantEthnicity}` : "-";
+const regionalLineage = tileDominantEthnicity != null && tileDominantEthnicity >= 0 ? sim.ethnicities.get(tileDominantEthnicity) : null;
+const mismatch = tileDominantEthnicity != null && tileDominantEthnicity >= 0 ? sim.climateMismatch(tileDominantEthnicity, i) : 0;
const tilePopulation = w.population?.[i] || 0;
const nearbyCampaigns = activeCampaignsNear(i);
+const nearbyNomad = activeNomadNear(i);
els.tooltip.innerHTML = `
-