feat: comprehensive AI-oriented data model with cell-level map API and knowledge base

- Add cell/tile map APIs: /api/state/cell, /api/state/cells, /api/state/cells/slice, /api/state/gas
- Add entity registry APIs: /api/registry/buildings, /api/registry/elements, /api/registry/techs
- Add plants, rooms, mop, harvest endpoints
- Rich semantic metadata: element state/category, building category/power, duplicant chore/cell
- AI-friendly coordinate system with (x,y) + cell index in all responses
- Build AI_KNOWLEDGE_BASE.md with building IDs, element IDs, tech trees, game mechanics
- Rewrite SKILL.md with data model explanation, coordinate guide, operation patterns
- Update Python tools: explore, cell, cells, slice, gas, registry subcommands
- Update MOD_DEV_GUIDE.md with AI data design principles
This commit is contained in:
root
2026-05-22 08:54:03 +08:00
parent 0e17e8a6ac
commit 6a7ac87c1b
6 changed files with 1775 additions and 341 deletions

View File

@ -1,6 +1,6 @@
import json
import sys
from oni_api import api_get, api_post
from oni_api import api_get
def get_game_state():
@ -13,6 +13,8 @@ def get_game_state():
'geysers': api_get('/api/state/geysers'),
'alerts': api_get('/api/state/alert'),
'critters': api_get('/api/state/critters'),
'plants': api_get('/api/state/plants'),
'rooms': api_get('/api/state/rooms'),
}
@ -22,22 +24,36 @@ def as_dict(resources):
return {r.get('name'): r for r in resources}
def analyze_o2(resources):
def buildings_by_cat(buildings):
cats = {}
for b in (buildings or []):
cat = b.get('category', 'Other')
cats.setdefault(cat, []).append(b)
return cats
def analyze_o2(resources, buildings):
r = as_dict(resources)
o2 = r.get('Oxygen', {}).get('amount', 0)
algae = r.get('Algae', {}).get('amount', 0)
pw = r.get('PollutedWater', {}).get('amount', 0)
has_electrolyzer = any(b.get('id') == 'Electrolyzer' for b in (buildings or []))
has_diffuser = any(b.get('id') == 'OxygenDiffuser' for b in (buildings or []))
warnings = []
if o2 < 100:
warnings.append(("CRITICAL", f"Oxygen critically low ({o2:.0f} kg)"))
warnings.append(("CRITICAL", f"Oxygen critically low ({o2:.0f} kg)!"))
elif o2 < 500:
warnings.append(("WARN", f"Oxygen declining ({o2:.0f} kg)"))
if o2 < 1000 and not has_electrolyzer and not has_diffuser:
warnings.append(("CRITICAL", "No oxygen production buildings found! Build OxygenDiffuser or Electrolyzer"))
if algae < 1000:
warnings.append(("WARN", f"Algae running out ({algae:.0f} kg) — build electrolyzer"))
elif algae < 5000:
warnings.append(("INFO", f"Algae moderate ({algae:.0f} kg) — plan SPOM"))
elif algae < 5000 and not has_electrolyzer:
warnings.append(("INFO", f"Algae moderate ({algae:.0f} kg) — plan SPOM transition"))
if pw > 50000:
warnings.append(("INFO", f"Polluted water abundant ({pw:.0f} kg) — use for reed fiber / pincha pepper"))
@ -45,33 +61,54 @@ def analyze_o2(resources):
return warnings
def analyze_food(resources):
def analyze_food(resources, buildings):
r = as_dict(resources)
cal = r.get('Calories', {}).get('amount', 0)
has_farm = any(b.get('id') in ('PlanterBox', 'FarmTile') for b in (buildings or []))
has_grill = any(b.get('id') == 'ElectricGrill' for b in (buildings or []))
warnings = []
if cal < 100000:
warnings.append(("CRITICAL", f"Food shortage ({cal:.0f} kcal)"))
warnings.append(("CRITICAL", f"Food shortage ({cal:.0f} kcal)!"))
elif cal < 500000:
warnings.append(("WARN", f"Food declining ({cal:.0f} kcal)"))
elif cal > 2000000:
warnings.append(("INFO", f"Food surplus ({cal:.0f} kcal) — consider more dupes"))
warnings.append(("INFO", f"Food surplus ({cal:.0f} kcal)"))
if cal < 500000 and not has_farm:
warnings.append(("WARN", "No farm plots found. Build PlanterBox and plant Mealwood"))
if cal < 500000 and not has_grill:
warnings.append(("INFO", "Build ElectricGrill to improve food quality"))
return warnings
def analyze_power(resources):
def analyze_power(resources, buildings):
r = as_dict(resources)
coal = r.get('Coal', {}).get('amount', 0)
hydrogen = r.get('Hydrogen', {}).get('amount', 0)
natgas = r.get('NaturalGas', {}).get('amount', 0)
generators = [b for b in (buildings or []) if b.get('id') in (
'CoalGenerator', 'HydrogenGenerator', 'NaturalGasGenerator',
'ManualGenerator', 'PetroleumGenerator', 'WoodBurner'
)]
batteries = [b for b in (buildings or []) if b.get('id') in ('Battery', 'JumboBattery', 'SmartBattery')]
warnings = []
if coal < 5000:
if not generators:
warnings.append(("CRITICAL", "No power generators found! Build ManualGenerator or CoalGenerator"))
else:
powered_on = sum(1 for g in generators if g.get('isOperational'))
warnings.append(("INFO", f"Power: {len(generators)} generators ({powered_on} operational), {len(batteries)} batteries"))
if coal < 5000 and any(g.get('id') == 'CoalGenerator' for g in generators):
warnings.append(("WARN", f"Coal low ({coal:.0f} kg) — diversify power"))
if hydrogen > 20000:
warnings.append(("INFO", f"Hydrogen stockpiled ({hydrogen:.0f} kg) — add generators"))
if natgas > 10000:
warnings.append(("INFO", f"Natural gas abundant ({natgas:.0f} kg) — tap for power"))
warnings.append(("INFO", f"Natural gas abundant ({natgas:.0f} kg)"))
return warnings
@ -94,52 +131,65 @@ def analyze_water(resources):
r = as_dict(resources)
water = r.get('Water', {}).get('amount', 0)
pw = r.get('PollutedWater', {}).get('amount', 0)
sw = r.get('SaltWater', {}).get('amount', 0)
total_water = water + pw + sw
warnings = []
if water < 10000:
warnings.append(("WARN", f"Clean water low ({water:.0f} kg) — conserve / filter PW"))
if pw > water * 2 and water > 0:
warnings.append(("INFO", f"More polluted water than clean — build water purifier"))
if total_water < 10000:
warnings.append(("WARN", f"Total water low ({total_water:.0f} kg across all sources)"))
elif total_water < 50000:
warnings.append(("INFO", f"Water reserves moderate ({total_water:.0f} kg)"))
if water < 10000 and pw > 10000:
warnings.append(("INFO", f"Filter polluted water ({pw:.0f} kg available)"))
return warnings
def suggest_actions(warnings, alerts):
def analyze_research(research):
if not isinstance(research, list):
return []
warnings = []
done = sum(1 for t in research if t.get('isComplete'))
total = len(research)
if done == 0 and total > 0:
warnings.append(("WARN", "No research completed! Start with Research Station"))
elif done < total * 0.3:
warnings.append(("INFO", f"Research progress: {done}/{total} ({done*100//total}%)"))
return warnings
def suggest_actions(warnings, alerts, buildings):
suggestions = []
for sev, msg in warnings:
if 'Oxygen' in msg or 'oxygen' in msg:
if 'CRITICAL' in sev:
suggestions.append("URGENT: Build algae deoxidizer or electrolyzer immediately")
else:
suggestions.append("Build or expand SPOM (Self-Powered Oxygen Module)")
elif 'Food' in msg or 'food' in msg:
if 'CRITICAL' in sev:
suggestions.append("URGENT: Harvest wild plants or cook mush fry")
else:
suggestions.append("Expand mealwood farm or start hatch ranching")
elif 'Coal' in msg:
suggestions.append("Diversify power: hydrogen generator, natural gas, or solar")
elif 'Hydrogen' in msg:
suggestions.append("Build more hydrogen generators and battery bank")
elif 'NaturalGas' in msg:
suggestions.append("Build natural gas generator + gas pipe system")
elif 'temperature' in msg.lower() or 'overheat' in msg.lower():
suggestions.append("Check cooling loop; add liquid pipe thermo sensor")
elif 'water' in msg.lower() and 'low' in msg.lower():
suggestions.append("Dig more water sources or filter polluted water")
has_electrolyzer = any(b.get('id') == 'Electrolyzer' for b in (buildings or []))
has_lavatory = any(b.get('id') == 'Lavatory' for b in (buildings or []))
has_sieve = any(b.get('id') == 'WaterSiever' for b in (buildings or []))
if isinstance(alerts, list):
for a in alerts:
msg = a.get('message', '') or a.get('title', '')
ml = msg.lower()
if 'oxygen' in ml or 'breathable' in ml:
suggestions.append("Build or expand electrolyzer setup (SPOM)")
elif 'food' in ml or 'starving' in ml:
suggestions.append("Expand mealwood farm or start ranching")
elif 'heat' in ml or 'temperature' in ml:
suggestions.append("Check cooling system, expand steam turbine setup")
elif 'power' in ml or 'wattage' in ml:
suggestions.append("Add power generation (hydrogen/natural gas)")
for sev, msg in warnings:
ml = msg.lower()
if 'oxygen' in ml:
suggestions.append("Build SPOM: Electrolyzer + Hydrogen Generator at a water source")
elif 'food' in ml and 'shortage' in ml:
suggestions.append("Build PlanterBoxes x5, plant Mealwood (no irrigation needed)")
elif 'food' in ml and 'declining' in ml:
suggestions.append("Expand farm or start hatch ranching (Hatch eats Sedimentary Rock)")
elif 'power' in ml and 'generator' in ml:
suggestions.append("Build ManualGenerator (early) or CoalGenerator (durable)")
elif 'coal' in ml:
suggestions.append("Diversify power: build HydrogenGenerator + SmartBattery")
elif 'hydrogen' in ml:
suggestions.append("Connect HydrogenGenerator to your hydrogen vent/SPOM")
elif 'water' in ml and 'low' in ml:
suggestions.append("Dig to find water geyser or filter polluted water")
elif 'heat' in ml or 'overheat' in ml:
suggestions.append("Build insulated tiles around heat sources; add cooling loop")
if not has_lavatory:
suggestions.append("Build Lavatory + Water Sieve for renewable water loop")
if not has_electrolyzer:
suggestions.append("Plan SPOM once Algae < 5t or you have renewable water")
if not has_sieve and has_lavatory:
suggestions.append("Build Water Sieve to close the bathroom loop")
return list(dict.fromkeys(suggestions))
@ -151,35 +201,40 @@ def print_report(state):
return False
resources = state.get('resources', [])
buildings = state.get('buildings', [])
alerts = state.get('alerts', [])
all_warnings = []
all_warnings += analyze_o2(resources)
all_warnings += analyze_food(resources)
all_warnings += analyze_power(resources)
all_warnings += analyze_o2(resources, buildings)
all_warnings += analyze_food(resources, buildings)
all_warnings += analyze_power(resources, buildings)
all_warnings += analyze_temp(resources)
all_warnings += analyze_water(resources)
all_warnings += analyze_research(state.get('research', []))
critical = [w for w in all_warnings if w[0] == 'CRITICAL']
warns = [w for w in all_warnings if w[0] == 'WARN']
infos = [w for w in all_warnings if w[0] == 'INFO']
suggestions = suggest_actions(all_warnings, alerts)
suggestions = suggest_actions(all_warnings, alerts, buildings)
print("=" * 52)
print("=" * 56)
print(" ONI Analysis Report")
print("=" * 52)
print("=" * 56)
print(f" Cycle: {g.get('cycle', '?')}")
print(f" Duplicants: {g.get('duplicantCount', '?')}")
print(f" World: {g.get('worldName', '?')}")
print(f" Buildings: {len(state.get('buildings', []) or [])}")
print(f" Grid: {g.get('gridWidth', '?')} x {g.get('gridHeight', '?')}")
print(f" Buildings: {len(buildings or [])}")
print(f" Critters: {len(state.get('critters', []) or [])}")
print(f" Geysers: {len(state.get('geysers', []) or [])}")
print(f" Research done: {sum(1 for t in (state.get('research') or []) if t.get('isComplete'))}")
print(f" Plants: {len(state.get('plants', []) or [])}")
print(f" Rooms: {len(state.get('rooms', []) or [])}")
print(f" Research done: {sum(1 for t in (state.get('research') or []) if t.get('isComplete'))}/{len(state.get('research', []) or [])}")
print()
if critical:
print(f" [CRITICAL] {len(critical)} issues")
print(f" [CRITICAL] {len(critical)} issues — act immediately!")
for _, msg in critical:
print(f" ! {msg}")
print()
@ -206,11 +261,11 @@ def print_report(state):
print(f" -> {s}")
print()
print(f" Alerts in-game: {len(alerts) if isinstance(alerts, list) else 0}")
print(f" In-game alerts: {len(alerts) if isinstance(alerts, list) else 0}")
if isinstance(alerts, list):
for a in alerts:
print(f" [{a.get('severity', '?')}] {a.get('title', '?')}: {a.get('message', '')}")
print("=" * 52)
print(f" [{a.get('severity', '?')}] {a.get('title', '?')}")
print("=" * 56)
return True