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:
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user