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>
This commit is contained in:
Денис Шкабатур
2026-02-12 17:27:15 +03:00
parent 3c24205e72
commit 6ba0746bb9
16 changed files with 2176 additions and 39 deletions

View File

@@ -8,8 +8,8 @@ import { ReactionEngine } from '../src/chemistry/engine';
// =============================================================================
describe('ElementRegistry', () => {
it('should load all 20 elements', () => {
expect(ElementRegistry.count()).toBe(20);
it('should load all 40 elements', () => {
expect(ElementRegistry.count()).toBe(40);
});
it('should look up elements by symbol', () => {
@@ -59,6 +59,39 @@ describe('ElementRegistry', () => {
expect(ElementRegistry.isElement('NaCl')).toBe(false);
expect(ElementRegistry.isElement('H2O')).toBe(false);
});
it('should have all 20 new Phase 9 elements', () => {
const newSymbols = ['Li', 'B', 'F', 'Ne', 'Ar', 'Ti', 'Cr', 'Mn', 'Co', 'Ni', 'As', 'Br', 'Ag', 'I', 'Ba', 'W', 'Pt', 'Pb', 'Bi', 'U'];
for (const sym of newSymbols) {
expect(ElementRegistry.has(sym), `Element ${sym} not found`).toBe(true);
}
});
it('should have correct data for new elements (real periodic table)', () => {
const li = ElementRegistry.getBySymbol('Li')!;
expect(li.atomicNumber).toBe(3);
expect(li.category).toBe('alkali-metal');
const f = ElementRegistry.getBySymbol('F')!;
expect(f.atomicNumber).toBe(9);
expect(f.category).toBe('halogen');
expect(f.state).toBe('gas');
expect(f.electronegativity).toBeCloseTo(3.98, 1); // Most electronegative
const br = ElementRegistry.getBySymbol('Br')!;
expect(br.state).toBe('liquid'); // Only liquid non-metal at room temp
const w = ElementRegistry.getBySymbol('W')!;
expect(w.atomicNumber).toBe(74);
expect(w.name).toBe('Tungsten');
const u = ElementRegistry.getBySymbol('U')!;
expect(u.atomicNumber).toBe(92);
expect(u.category).toBe('actinide');
const pt = ElementRegistry.getBySymbol('Pt')!;
expect(pt.category).toBe('transition-metal');
});
});
// =============================================================================
@@ -106,6 +139,32 @@ describe('CompoundRegistry', () => {
expect(CompoundRegistry.isCompound('H2O')).toBe(true);
expect(CompoundRegistry.isCompound('Na')).toBe(false);
});
it('should load all 64 compounds', () => {
expect(CompoundRegistry.count()).toBe(64);
});
it('should have new Phase 9 compounds', () => {
const newIds = ['NH3', 'HF', 'HBr', 'TiO2', 'MnO2', 'As2O3', 'H2SO4', 'HNO3', 'CuO', 'FeCl3', 'CaCl2', 'NH4Cl', 'C6H12O6', 'CH3COOH'];
for (const id of newIds) {
expect(CompoundRegistry.has(id), `Compound ${id} not found`).toBe(true);
}
});
it('should correctly flag new dangerous compounds', () => {
const hf = CompoundRegistry.getById('HF')!;
expect(hf.properties.acidic).toBe(true);
expect(hf.properties.corrosive).toBe(true);
expect(hf.properties.toxic).toBe(true);
const h2so4 = CompoundRegistry.getById('H2SO4')!;
expect(h2so4.properties.acidic).toBe(true);
expect(h2so4.properties.oxidizer).toBe(true);
const as2o3 = CompoundRegistry.getById('As2O3')!;
expect(as2o3.properties.toxic).toBe(true);
expect(as2o3.name).toContain('Arsenic');
});
});
// =============================================================================
@@ -204,6 +263,104 @@ describe('ReactionEngine — success', () => {
expect(result.reaction!.energyChange).toBeGreaterThan(0); // Endothermic
});
it('should produce HF from H + F', () => {
const result = ReactionEngine.react([
{ id: 'H', count: 1 },
{ id: 'F', count: 1 },
]);
expect(result.success).toBe(true);
expect(result.products).toEqual([{ id: 'HF', count: 1 }]);
});
it('should produce NH3 via Haber process (N + 3H with Fe catalyst + heat)', () => {
const result = ReactionEngine.react(
[
{ id: 'N', count: 1 },
{ id: 'H', count: 3 },
],
{ minTemp: 500, catalyst: 'Fe' },
);
expect(result.success).toBe(true);
expect(result.products).toEqual([{ id: 'NH3', count: 1 }]);
});
it('should produce TiO2 from Ti + 2O with extreme heat', () => {
const result = ReactionEngine.react(
[
{ id: 'Ti', count: 1 },
{ id: 'O', count: 2 },
],
{ minTemp: 1000 },
);
expect(result.success).toBe(true);
expect(result.products).toEqual([{ id: 'TiO2', count: 1 }]);
});
it('should produce tungsten via WO3 + Al redox', () => {
const result = ReactionEngine.react(
[
{ id: 'WO3', count: 1 },
{ id: 'Al', count: 2 },
],
{ minTemp: 1000 },
);
expect(result.success).toBe(true);
expect(result.products).toContainEqual({ id: 'W', count: 1 });
expect(result.products).toContainEqual({ id: 'Al2O3', count: 1 });
});
it('should neutralize HF with NaOH (acid-base)', () => {
const result = ReactionEngine.react([
{ id: 'NaOH', count: 1 },
{ id: 'HF', count: 1 },
]);
expect(result.success).toBe(true);
expect(result.products).toContainEqual({ id: 'NaF', count: 1 });
expect(result.products).toContainEqual({ id: 'H2O', count: 1 });
});
it('should dissolve Zn in HCl (single-replacement)', () => {
const result = ReactionEngine.react([
{ id: 'Zn', count: 1 },
{ id: 'HCl', count: 2 },
]);
expect(result.success).toBe(true);
expect(result.products).toContainEqual({ id: 'ZnCl2', count: 1 });
expect(result.products).toContainEqual({ id: 'H', count: 2 });
});
it('should displace Cu with Fe from CuCl2', () => {
const result = ReactionEngine.react([
{ id: 'Fe', count: 1 },
{ id: 'CuCl2', count: 1 },
]);
expect(result.success).toBe(true);
expect(result.products).toContainEqual({ id: 'Cu', count: 1 });
expect(result.products).toContainEqual({ id: 'FeCl2', count: 1 });
});
it('should ferment glucose into ethanol + CO2', () => {
const result = ReactionEngine.react([
{ id: 'C6H12O6', count: 1 },
]);
expect(result.success).toBe(true);
expect(result.products).toContainEqual({ id: 'C2H5OH', count: 2 });
expect(result.products).toContainEqual({ id: 'CO2', count: 2 });
});
it('should produce H2SO4 via Contact process (Pt catalyst)', () => {
const result = ReactionEngine.react(
[
{ id: 'S', count: 1 },
{ id: 'O', count: 3 },
{ id: 'H', count: 2 },
],
{ catalyst: 'Pt' },
);
expect(result.success).toBe(true);
expect(result.products).toEqual([{ id: 'H2SO4', count: 1 }]);
});
it('reactant order should not matter (key is sorted)', () => {
const r1 = ReactionEngine.react([
{ id: 'Cl', count: 1 },
@@ -234,6 +391,16 @@ describe('ReactionEngine — failures', () => {
expect(result.failureReasonRu).toContain('благородный газ');
});
it('should reject reactions with new noble gases (Ne, Ar)', () => {
const ne = ReactionEngine.react([{ id: 'Ne', count: 1 }, { id: 'F', count: 1 }]);
expect(ne.success).toBe(false);
expect(ne.failureReason).toContain('noble gas');
const ar = ReactionEngine.react([{ id: 'Ar', count: 1 }, { id: 'Cl', count: 1 }]);
expect(ar.success).toBe(false);
expect(ar.failureReason).toContain('noble gas');
});
it('should reject gold reactions with explanation', () => {
const result = ReactionEngine.react([
{ id: 'Au', count: 1 },
@@ -309,8 +476,8 @@ describe('ReactionEngine — failures', () => {
// =============================================================================
describe('ReactionEngine — metadata', () => {
it('should have 30+ registered reactions', () => {
expect(ReactionEngine.count()).toBeGreaterThanOrEqual(30);
it('should have 100+ registered reactions', () => {
expect(ReactionEngine.count()).toBeGreaterThanOrEqual(100);
});
it('should look up reactions by id', () => {