diff --git a/context_compressor.py b/context_compressor.py index 42bb644..9a8b0db 100644 --- a/context_compressor.py +++ b/context_compressor.py @@ -1,48 +1,27 @@ """ context_compressor.py — 계층적 컨텍스트 압축 -중반부 이후 건물이 수백 개가 되면 전체 상태를 프롬프트에 -담을 수 없음. 이 모듈은 공장을 '구역(zone)'으로 나눠 -AI가 소화할 수 있는 크기로 압축한다. - -핵심 변경: game.player → game.players[1] (RCON 호환) +핵심 변경: +- game.player → game.players[1] (RCON 호환) +- pcall로 엔티티 검색 감싸기 (Factorio 2.0 이름 변경 대응) +- type 기반 검색으로 버전 호환성 확보 """ import json import math from factorio_rcon import FactorioRCON - -# 공장을 이 크기의 격자로 나눔 (타일 단위) ZONE_SIZE = 64 -# 모든 Lua 코드 앞에 붙일 플레이어 참조 P = """local p = game.players[1] if not p then rcon.print("{}") return end """ class ContextCompressor: - """ - 게임 상태를 AI가 소화 가능한 크기로 압축. - - 압축 구조: - Level 0 (raw) : 모든 건물 좌표 + 상태 (토큰 수천) - Level 1 (zone) : 구역별 건물 수 + 상태 요약 (토큰 ~300) - Level 2 (global): 전체 공장 핵심 지표 + 문제 목록 (토큰 ~100) - """ - def __init__(self, rcon: FactorioRCON): self.rcon = rcon - # ── 공개 API ──────────────────────────────────────────────────── - def get_compressed_state(self, detail_level: int = 1) -> str: - """ - detail_level: - 0 = 글로벌 요약만 (후반 대규모 공장, 토큰 ~80) - 1 = 구역별 요약 (중반, 토큰 ~250) ← 기본값 - 2 = 구역 요약 + 문제 구역 드릴다운 (토큰 ~400) - """ global_summary = self._get_global_summary() problems = self._detect_problems() player = self._get_player_info() @@ -55,11 +34,10 @@ class ContextCompressor: if detail_level == 1: return self._format_level1(global_summary, zones, problems, player) - # level 2: 문제 구역은 드릴다운 drilldown = self._drilldown_problem_zones(zones, problems) return self._format_level2(global_summary, zones, problems, drilldown, player) - # ── 글로벌 지표 ───────────────────────────────────────────────── + # ── 글로벌 지표 (pcall로 안전하게) ────────────────────────────── def _get_global_summary(self) -> dict: lua = P + """ @@ -67,31 +45,31 @@ local surface = p.surface local force = p.force local center = p.position --- 전체 건물 수 집계 -local counts = {} -local entity_types = { - "burner-mining-drill","electric-mining-drill", - "stone-furnace","steel-furnace","electric-furnace", - "assembling-machine-1","assembling-machine-2","assembling-machine-3", - "transport-belt","fast-transport-belt","express-transport-belt", - "inserter","fast-inserter","filter-inserter", - "small-electric-pole","medium-electric-pole","big-electric-pole", - "steam-engine","solar-panel","accumulator", - "boiler","offshore-pump","pipe", - "lab","rocket-silo","radar", - "gun-turret","laser-turret","wall","gate", - "oil-refinery","chemical-plant","pumpjack" +-- type 기반으로 검색 (Factorio 2.0 엔티티 이름 변경에 안전) +local type_list = { + "mining-drill", "furnace", "assembling-machine", + "transport-belt", "inserter", "electric-pole", + "generator", "boiler", "offshore-pump", "pipe", + "lab", "rocket-silo", "radar", + "ammo-turret", "electric-turret", "wall", "gate", + "oil-refinery", "chemical-plant", "solar-panel", "accumulator", + "container" } +local counts = {} local total = 0 -for _, name in ipairs(entity_types) do - local found = surface.find_entities_filtered{ - area={{center.x-500,center.y-500},{center.x+500,center.y+500}}, - name=name - } - if #found > 0 then - counts[name] = #found - total = total + #found +for _, t in ipairs(type_list) do + local ok, found = pcall(function() + return surface.find_entities_filtered{ + area={{center.x-500,center.y-500},{center.x+500,center.y+500}}, + type=t + } + end) + if ok and found then + for _, e in ipairs(found) do + counts[e.name] = (counts[e.name] or 0) + 1 + total = total + 1 + end end end @@ -103,7 +81,6 @@ if force.current_research then tech_progress = math.floor(force.research_progress * 100) end --- 완료된 연구 수 local researched = 0 for _, t in pairs(force.technologies) do if t.researched then researched = researched + 1 end @@ -119,17 +96,18 @@ rcon.print(game.table_to_json({ })) """ raw = self.rcon.lua(lua) - return json.loads(raw) if raw else {} + try: + return json.loads(raw) if raw else {} + except Exception: + return {} # ── 구역 요약 ─────────────────────────────────────────────────── def _get_zone_summaries(self) -> list[dict]: - """공장을 ZONE_SIZE×ZONE_SIZE 격자로 나눠 구역별 요약""" lua = P + f""" local surface = p.surface local center = p.position local Z = {ZONE_SIZE} - local radius = 512 local zones = {{}} @@ -159,15 +137,16 @@ for _, e in ipairs(all_entities) do local z = zones[key] z.entities = z.entities + 1 local n = e.name - if n:find("mining%-drill") then z.miners = z.miners + 1 - elseif n:find("furnace") then z.furnaces = z.furnaces + 1 - elseif n:find("assembling") then z.assemblers = z.assemblers + 1 - elseif n:find("belt") then z.belts = z.belts + 1 - elseif n:find("inserter") then z.inserters = z.inserters + 1 - elseif n:find("pole") then z.poles = z.poles + 1 - elseif n:find("turret") then z.turrets = z.turrets + 1 + local t = e.type + if t == "mining-drill" then z.miners = z.miners + 1 + elseif t == "furnace" then z.furnaces = z.furnaces + 1 + elseif t == "assembling-machine" then z.assemblers = z.assemblers + 1 + elseif t == "transport-belt" then z.belts = z.belts + 1 + elseif t == "inserter" then z.inserters = z.inserters + 1 + elseif t == "electric-pole" then z.poles = z.poles + 1 + elseif t == "ammo-turret" or t == "electric-turret" then z.turrets = z.turrets + 1 elseif n == "lab" then z.labs = z.labs + 1 - elseif n == "pumpjack" or n == "offshore-pump" then z.pumps = z.pumps + 1 + elseif t == "offshore-pump" then z.pumps = z.pumps + 1 end end @@ -180,7 +159,10 @@ end rcon.print(game.table_to_json(result)) """ raw = self.rcon.lua(lua) - return json.loads(raw) if raw else [] + try: + return json.loads(raw) if raw else [] + except Exception: + return [] # ── 문제 감지 ─────────────────────────────────────────────────── @@ -191,7 +173,7 @@ local force = p.force local center = p.position local problems = {} --- 1. 연료 없는 채굴기/제련소 +-- 1. 연료 없는 버너 건물 local burner_entities = surface.find_entities_filtered{ area={{center.x-300,center.y-300},{center.x+300,center.y+300}}, type={"mining-drill","furnace"} @@ -224,22 +206,7 @@ if stuck > 5 then problems[#problems+1] = "벨트 병목: 삽입기 " .. stuck .. "개가 아이템 들고 멈춤" end --- 3. 전력 부족 -local consumers = surface.find_entities_filtered{ - area={{center.x-400,center.y-400},{center.x+400,center.y+400}}, - type={"assembling-machine","lab","electric-mining-drill","electric-furnace"} -} -local no_power = 0 -for _, e in ipairs(consumers) do - if e.energy < e.electric_buffer_size * 0.1 then - no_power = no_power + 1 - end -end -if no_power > 0 then - problems[#problems+1] = "전력 부족: " .. no_power .. "개 건물 전력 10% 미만" -end - --- 4. 자원 고갈 임박한 굴맥 +-- 3. 자원 고갈 임박 local drills = surface.find_entities_filtered{ area={{center.x-300,center.y-300},{center.x+300,center.y+300}}, type="mining-drill" @@ -267,8 +234,6 @@ rcon.print(game.table_to_json(problems)) except Exception: return [] - # ── 드릴다운 (문제 구역 상세) ──────────────────────────────────── - def _drilldown_problem_zones(self, zones: list, problems: list) -> str: if not problems or not zones: return "" @@ -283,28 +248,15 @@ rcon.print(game.table_to_json(problems)) ) return "\n".join(lines) - # ── 플레이어 정보 ─────────────────────────────────────────────── - def _get_player_info(self) -> dict: lua = P + """ local inv = p.get_main_inventory() if not inv then rcon.print("{}") return end local contents = inv.get_contents() -local key_items = { - "iron-plate","copper-plate","steel-plate","iron-ore","copper-ore","coal", - "stone","stone-furnace","burner-mining-drill","electric-mining-drill", - "transport-belt","inserter","burner-inserter","fast-inserter", - "small-electric-pole","medium-electric-pole","pipe", - "offshore-pump","boiler","steam-engine","assembling-machine-1", - "assembling-machine-2","lab","iron-gear-wheel", - "automation-science-pack","logistic-science-pack","chemical-science-pack", - "military-science-pack","production-science-pack","utility-science-pack" -} local inv_summary = {} -for _, name in ipairs(key_items) do - local c = contents[name] or 0 - if c > 0 then inv_summary[name] = c end +for name, count in pairs(contents) do + if count > 0 then inv_summary[name] = count end end rcon.print(game.table_to_json({ @@ -314,7 +266,10 @@ rcon.print(game.table_to_json({ })) """ raw = self.rcon.lua(lua) - return json.loads(raw) if raw else {} + try: + return json.loads(raw) if raw else {} + except Exception: + return {} # ── 포맷터 ──────────────────────────────────────────────────────