Files
synthesis/src/world/resources.ts
Денис Шкабатур 6ba0746bb9 phase 9: biome expansion — 3 biomes, 40 elements, 119 reactions, 9 species
Expand beyond vertical slice with two new biomes and massive chemistry expansion:

Chemistry: +20 real elements (Li→U), +39 compounds (acids/salts/oxides/organics),
+85 reactions (Haber process, thermite variants, smelting, fermentation, etc.)

Biomes: Kinetic Mountains (physics/mechanics themed) and Verdant Forests
(biology/ecology themed), each with 8 tile types and unique generation rules.

Creatures: 6 new species — Pendulums/Mechanoids/Resonators (mountains),
Symbiotes/Mimics/Spore-bearers (forests). Species filtered by biome.

Infrastructure: CradleScene biome selector UI, generic world generator
(tile lookup by property instead of hardcoded names), actinide element category.

487 tests passing (32 new).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 17:27:15 +03:00

107 lines
3.0 KiB
TypeScript

/**
* Resource Spawner — creates ECS entities for harvestable world objects
*
* Scans the generated tile grid for mineral veins and geysers,
* creates an entity at each with a randomly assigned element.
*/
import { addEntity, addComponent } from 'bitecs';
import type { World } from '../ecs/world';
import { Position, Resource, SpriteRef } from '../ecs/components';
import type { TileGrid, BiomeData } from './types';
import {
pickResourceElement,
MINERAL_ELEMENTS,
GEYSER_ELEMENTS,
type ResourceInfo,
} from '../player/interaction';
/** Resource spawn configuration per tile type */
interface ResourceTileConfig {
tileId: number;
elements: readonly string[];
minQuantity: number;
maxQuantity: number;
interactRange: number;
spriteColor: number;
spriteRadius: number;
}
/**
* Spawn resource entities for all resource tiles in the grid.
* @returns Map of entity ID → ResourceInfo for string data
*/
export function spawnResources(
world: World,
grid: TileGrid,
biome: BiomeData,
seed: number,
): Map<number, ResourceInfo> {
const resourceData = new Map<number, ResourceInfo>();
// Find tile IDs for resource types (generic: resource + interactive tiles)
const mineralTile = biome.tiles.find(t => t.resource);
const geyserTile = biome.tiles.find(t => t.interactive);
const configs: ResourceTileConfig[] = [];
if (mineralTile) {
configs.push({
tileId: mineralTile.id,
elements: MINERAL_ELEMENTS,
minQuantity: 3,
maxQuantity: 5,
interactRange: 40,
spriteColor: 0xffd700, // gold
spriteRadius: 4,
});
}
if (geyserTile) {
configs.push({
tileId: geyserTile.id,
elements: GEYSER_ELEMENTS,
minQuantity: 2,
maxQuantity: 4,
interactRange: 48,
spriteColor: 0xff6600, // orange
spriteRadius: 5,
});
}
const tileSize = biome.tileSize;
for (let y = 0; y < grid.length; y++) {
for (let x = 0; x < grid[y].length; x++) {
const tileId = grid[y][x];
const config = configs.find(c => c.tileId === tileId);
if (!config) continue;
// Pick element deterministically
const itemId = pickResourceElement(x, y, seed, config.elements);
// Quantity from deterministic hash
const qHash = ((x * 48611) ^ (y * 29423) ^ (seed * 61379)) >>> 0;
const range = config.maxQuantity - config.minQuantity + 1;
const quantity = config.minQuantity + (qHash % range);
// Create entity at tile center
const eid = addEntity(world);
addComponent(world, eid, Position);
addComponent(world, eid, Resource);
addComponent(world, eid, SpriteRef);
Position.x[eid] = x * tileSize + tileSize / 2;
Position.y[eid] = y * tileSize + tileSize / 2;
Resource.quantity[eid] = quantity;
Resource.interactRange[eid] = config.interactRange;
SpriteRef.color[eid] = config.spriteColor;
SpriteRef.radius[eid] = config.spriteRadius;
resourceData.set(eid, { itemId, tileX: x, tileY: y });
}
}
return resourceData;
}