From 2b26bbd94f7a96d051df18b8b4102777f3d052eb Mon Sep 17 00:00:00 2001 From: dal4segno Date: Sat, 3 Oct 2026 15:45:37 +0900 Subject: [PATCH] feat: add browser AI connection guide and calculator instructions --- .gitignore | 1 + UPDATES.md | 10 +- docs/holodori-web-assistant.md | 83 +++++ offline/OfflineApp.svelte | 7 +- package.json | 1 + .../holodori-web-assistant/assets/icon.svg | 6 + plugins/holodori-web-assistant/plugin.json | 23 ++ .../skills/holodori-web-assistant/SKILL.md | 38 +++ .../holodori-web-assistant/agents/openai.yaml | 4 + .../references/browser-workflow.md | 49 +++ .../references/requests.md | 39 +++ scripts/package-holodori-web-skill.ps1 | 76 +++++ src/lib/assistant/service.ts | 161 +++++++++ src/lib/assistant/site-tools.ts | 34 ++ src/lib/assistant/tool-schema.ts | 31 ++ src/lib/assistant/worker-job.ts | 30 ++ src/lib/components/AssistantConnection.svelte | 5 + src/lib/components/ToolsNav.svelte | 5 +- src/lib/engine/assistant-analysis.test.ts | 91 +++++ src/lib/engine/assistant-analysis.ts | 321 ++++++++++++++++++ src/lib/engine/assistant-lifecycle.test.ts | 79 +++++ src/lib/workers/assistant-analysis.worker.ts | 16 + src/routes/+layout.svelte | 2 + src/routes/tools/ai/+page.svelte | 29 ++ static/ai/SKILL.html | 144 ++++++++ static/ai/SKILL.md | 130 +++++++ .../holodori-web-assistant-plugin.zip | Bin 0 -> 9442 bytes .../holodori-web-assistant-skill.zip | Bin 0 -> 8018 bytes 28 files changed, 1411 insertions(+), 4 deletions(-) create mode 100644 docs/holodori-web-assistant.md create mode 100644 plugins/holodori-web-assistant/assets/icon.svg create mode 100644 plugins/holodori-web-assistant/plugin.json create mode 100644 plugins/holodori-web-assistant/skills/holodori-web-assistant/SKILL.md create mode 100644 plugins/holodori-web-assistant/skills/holodori-web-assistant/agents/openai.yaml create mode 100644 plugins/holodori-web-assistant/skills/holodori-web-assistant/references/browser-workflow.md create mode 100644 plugins/holodori-web-assistant/skills/holodori-web-assistant/references/requests.md create mode 100644 scripts/package-holodori-web-skill.ps1 create mode 100644 src/lib/assistant/service.ts create mode 100644 src/lib/assistant/site-tools.ts create mode 100644 src/lib/assistant/tool-schema.ts create mode 100644 src/lib/assistant/worker-job.ts create mode 100644 src/lib/components/AssistantConnection.svelte create mode 100644 src/lib/engine/assistant-analysis.test.ts create mode 100644 src/lib/engine/assistant-analysis.ts create mode 100644 src/lib/engine/assistant-lifecycle.test.ts create mode 100644 src/lib/workers/assistant-analysis.worker.ts create mode 100644 src/routes/tools/ai/+page.svelte create mode 100644 static/ai/SKILL.html create mode 100644 static/ai/SKILL.md create mode 100644 static/downloads/holodori-web-assistant-plugin.zip create mode 100644 static/downloads/holodori-web-assistant-skill.zip diff --git a/.gitignore b/.gitignore index 9129a81..58f1583 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules build offline-dist pages-dist +plugin-dist release .env .env.* diff --git a/UPDATES.md b/UPDATES.md index e352cc6..d647652 100644 --- a/UPDATES.md +++ b/UPDATES.md @@ -1,7 +1,15 @@ --- -updatedAt: 2026-09-30T15:11:08.161Z +updatedAt: 2026-10-03T06:42:48.753Z --- +## 2026년 10월 3일 + +### AI 연결 실험 기능 + +`NEW` 도구에 AI 연결 안내를 추가했습니다. ChatGPT Windows 앱 설치·로그인, 공식 브라우저 확장 설치와 앱 연결 절차를 안내합니다. 평소 계산기를 쓰던 브라우저의 보유·성장 정보를 그대로 사용하며 계산은 계산기에서 수행합니다. 일반 웹·모바일 채팅에서의 사용은 보장하지 않습니다. + +`NEW` AI 연결 안내의 SKILL.md 링크를 GPT에게 먼저 읽게 하면 별도 스킬 설치 없이 계산기 사용 방법을 전달할 수 있습니다. 문서에 화면 조작, 점수 해석과 성장 가정 비교 후 조건 복원 절차를 담았습니다. + ## 2026년 10월 1일 ### 연결노드 개화 추천 범위 선택 diff --git a/docs/holodori-web-assistant.md b/docs/holodori-web-assistant.md new file mode 100644 index 0000000..e201afb --- /dev/null +++ b/docs/holodori-web-assistant.md @@ -0,0 +1,83 @@ +# 웹 계산기 AI 연결 + +ChatGPT 데스크톱 앱 또는 Codex가 연결된 브라우저의 기존 계산 화면을 조작하고 결과를 읽는다. 사이트 도구(WebMCP)가 제공되는 환경에서는 직접 호출도 사용할 수 있다. 계산은 기존 엔진을 사용하는 브라우저 Worker에서 수행한다. AI 대화는 ChatGPT에서 진행하므로 운영자가 추론 API 비용이나 계산 서버를 제공할 필요가 없다. TrueNAS, API 키, Sign in with ChatGPT 파트너 접근 신청도 이 구조에 필요하지 않다. + +## 사용자 흐름 + +1. [공식 Windows 설치 파일](https://get.microsoft.com/installer/download/9PLM9XGG6VKS?cid=website_cta_psi)로 ChatGPT 앱을 설치한 뒤 앱을 열어 계정으로 로그인한다. 주소는 OpenAI Windows 안내에서 연결하는 설치 경로다. +2. 평소 계산기를 사용하던 브라우저 프로필에 [공식 ChatGPT 확장](https://chromewebstore.google.com/detail/chatgpt/hehggadaopoacecdllhhajmbjkdcmajg?hl=ko)을 설치한다. +3. 앱의 설정 → Computer Use에서 해당 브라우저 연결 상태를 확인하고 필요한 연결 설정을 마친다. +4. 계산기 탭에서 확장 아이콘으로 대화창을 연다. 해당 브라우저의 기존 보유·성장 정보를 그대로 사용하므로 데이터 이전은 필요하지 않다. 데이터가 실제로 없을 때만 사용자 데이터 입력이나 백업 가져오기를 안내한다. +5. 도구 → AI 연결의 SKILL.md 링크를 GPT에게 먼저 읽게 한 뒤 현재 편성 분석이나 성장 가정 비교를 요청한다. AI가 배치 분석 등의 화면을 조작하고 읽으며 결과는 해당 계산 화면에 표시된다. 가정 비교 후 변경한 분석 조건을 복원하고 재계산으로 확인한다. 문서는 설치 없이 조작·해석 지침을 전달하며, 브라우저 연결 기능 자체를 제공하지는 않는다. + +AI 연결 페이지는 브라우저 연결 방법, SKILL.md 사용 안내 링크와 요청 예시를 담은 AI 연결 안내만 표시한다. 별도 스킬 소개·플러그인 다운로드 영역, 직접 도구 연결 스위치·계산 확인 버튼, 최근 도구 분석 영역은 제거했다. 화면 조작에서는 연결 스위치가 필요하지 않다. 안내는 기본 조작이나 도구 호출 능력을 추가하지 않는다. 일반 웹·모바일 ChatGPT 채팅의 지원은 보장하지 않는다. 앱·모델·계정 정책과 기능 배포 상태에 따라 지원이 달라진다. [OpenAI 공식 브라우저 확장 안내](https://learn.chatgpt.com/docs/chrome-extension), [사이트 도구 안내](https://learn.chatgpt.com/docs/webmcp) + +## 구현 범위 + +- `holodori_context`: 현재 브라우저의 보유·성장 요약, 활성 보드·연결 배치, 저장 편성 조회. +- `holodori_catalog`: 카드 이름·멤버·ID·보유·개화 또는 공개 악곡·난이도 검색. 최대 50개씩 조회. +- `holodori_analyze`: 편성 분석, 첫 편성 기준 비교, 제한된 조합 탐색. 최대 채보 3개·편성 8개·beamWidth 64·확률 표본 5000. 한 번에 하나의 작업만 허용하고 30초 제한 및 취소를 지원한다. + +사이트 도구 구현은 남아 있으나 현재 안내 페이지는 도구 연결을 켜는 UI를 제공하지 않는다. 스킬은 이미 노출된 도구가 있을 때만 직접 호출하고 나머지는 화면 조작으로 진행한다. 호스트가 도구 이름에 고유 접미사를 붙일 수 있으므로 실제 발견한 이름으로 호출한다. 현재 Document API와 이전 Navigator API를 기능 확인 후 사용한다. 등록 시 AbortSignal을 전달하고 이전 unregisterTool도 지원한다. 연결 종료·페이지 종료 시 등록을 정리하고 오래된 호출도 차단한다. 새로고침 후에는 연결이 꺼진다. 지원하지 않는 브라우저에서도 기존 계산기 화면은 유지된다. + +계산 시작 시 저장 데이터를 복사하며 분석과 가정 비교는 저장 데이터를 변경하지 않는다. 잘못된 ID와 지원하지 않는 쿨타임 값을 거부한다. 쿨타임 감소는 기존 계산기와 동일하게 보드 활성 상태와 무관한 최적화 변수이며 0/4/8/12를 탐색한다. 조합 탐색은 휴리스틱 후보군의 점수를 계산하는 방식이며 전역 최적을 보장하지 않는다. 여러 채보의 순위는 채보별 최적 점수의 합계다. 실시간 이벤트 보너스는 포함하지 않는다. + +엔진은 `src/lib/engine/assistant-analysis.ts`로 공유한다. SvelteKit 레이아웃과 온라인 Pages 공용 진입점 모두 사이트 도구를 초기화한다. 별도 오프라인 산출물은 생성하지 않는다. + +## 스킬 배포 파일 + +`plugins/holodori-web-assistant/skills/holodori-web-assistant`는 독립 스킬 폴더다. SKILL.md, UI 메타데이터, 화면 조작 지침과 직접 도구 요청 예시만 포함한다. 플러그인 형식은 상위 `plugins/holodori-web-assistant`를 사용한다. 엔진·카드·채보·Node 실행 파일은 포함하지 않는다. 화면 안내에는 평균/최고점 구분, 최고점 기준으로 정렬된 빈도 순위, 미사용/0% 차이, 보드 가정과 빈칸/0 덮어쓰기 차이, 새 계산 완료 확인과 조건 복원을 명시한다. + +`npm run package:web-skill`은 ignored `plugin-dist`에 독립 스킬 ZIP과 설치용 플러그인 ZIP을 생성하고 파일 목록과 SHA-256을 검증한다. 검증한 파일을 `static/downloads`에도 복사한다. 현재 패키지는 독립 스킬 4파일/플러그인 6파일이다. 이 두 파일의 고정 다운로드 주소는 유지하며 AI 연결 화면은 설치 대신 사용 안내 문서 읽기를 안내한다. 안내 내용이 바뀔 때만 패키지를 다시 생성한다. 카드·채보 업데이트는 ZIP 변경이 필요하지 않다. 기존 Doricalc 등록을 재사용하거나 계정에 자동 업로드하지 않는다. 스킬 자체는 사이트 도구 미지원 환경을 지원 환경으로 바꾸지 않으며, 연결된 브라우저의 화면 조작을 대체 경로로 안내한다. + +같은 명령은 SKILL.md와 두 참고 문서를 한 파일로 합친 `static/ai/SKILL.md`와 브라우저용 `static/ai/SKILL.html`도 생성한다. AI 연결의 링크는 다운로드가 아닌 HTML 문서 뷰어를 새 탭에서 열며, 뷰어에 전체 Markdown 원문과 파일 다운로드 링크가 들어 있다. 참고 문서 링크는 문서 내부 앵커로 바꿔 단독 읽기를 지원한다. 문서는 공개 자료이며 AI 전용 접근 제한은 없다. 온라인 Pages는 새 탭 링크를 해시 라우트로 바꾸지 않으므로 동일 문서를 열 수 있다. + +## 검증 기록 — 2026-10-03 + +검증용 백업은 서로 다른 멤버 6장의 5성 카드, 모두 0개화, 메모리 10, 멤버 강화 3.2%, 보드 없음이다. 실제 데이터 가져오기 화면으로 격리된 로컬 브라우저 원점에 가져온 뒤 실제 WebMCP 도구를 발견하고 호출했다. API 모의 주입이나 클라우드 코드 변환은 사용하지 않았다. + +| 검증 | 결과 | +| --- | --- | +| 도구 발견 | 데이터 요약·카탈로그·분석 3개 등록 | +| 소라·아키 고정, 소라 리더, SSS 2022 Easy 후보 4개 | complete, Worker 시간 2.414초 | +| 최고 후보 | 소라·아키·미코·AZKi·스이세이, 평균 1,024,124 / 이론 최고 1,155,842 / 종합력 205,159 | +| 소라만 4개화 가정과 현재 상태 비교 | complete, 1.234초, 평균 1,077,758 / 최고 1,217,186 / 종합력 211,854 | +| 평균 점수 증가 | +53,634 (+5.237%) | +| 가정 비교 후 원본 | 저장 시각 동일, 소라 보유 0개화 유지 | +| 존재하지 않는 카드 | error, 빈 results, 점수·순위 없음 | +| 온라인 Pages 빌드에서 같은 후보 탐색 | complete, 2.695초, 개발 환경의 4개 점수·순위와 동일 | +| 연결 해제 | 실제 등록 도구 3개가 모두 제거됨 | + +위 시간은 반환된 준비·Worker 실행 시간이며 AI가 답변을 작성하는 전체 시간은 아니다. 도구 발견과 실행은 실제 내장 브라우저에서 검증했으나 새 스킬 플러그인을 계정에 설치한 뒤 자연어 한 문장으로 자동 호출하는 별도 대화는 아직 검증하지 않았다. 사이트 도구 API를 사용할 수 있기 전에 초기 HTML을 관찰하면 지원 상태 판단이 잘못될 수 있어 초기 로딩 안내를 구분했다. + +자동 검증은 기존 엔진 수치 비교, 가정 적용 및 입력 불변성, 요청 검증과 쿨타임 조건, Worker 완료·오류·취소·시간 제한, 등록 실패 복구와 등록 중 연결 해제를 포함한다. 일반 웹 빌드와 온라인 Pages 빌드를 확인하며 커밋·푸시·공개 배포는 별도 승인 후 Gitea Actions를 통해 진행한다. + +`npm run check`는 오류·경고 0개, 엔진 테스트 255개와 기존 분석 테스트 8개는 모두 통과했다. `npm run build`와 `npm run build:pages` 및 공개 데이터 검사를 통과했다. 패키지 생성 시 5파일 플러그인 ZIP 약 4.5KB와 3파일 스킬 ZIP 약 3.2KB의 목록·해시를 확인했다. 표준 스킬 Python 검증기는 해당 런타임의 PyYAML 누락으로 실행할 수 없었으며 패키지 검증에서 단순 스칼라 frontmatter와 필수 경로를 검사했다. 초기화·등록·조회·계산은 실제 브라우저 호출로 별도 확인했다. + +## 화면 조작 스킬 독립 검증 — 2026-10-03 + +이전 대화·프로젝트 코드를 전달받지 않은 별도 AI에 스킬과 연결된 참고 문서만 제공하고, 사용자 Edge의 localhost 계산기에서 다음 요청을 수행하게 했다. 설치한 새 ChatGPT 대화의 자동 스킬 선택 검증과는 구분한다. + +> 현재 편성으로 Play Dice! Expert 점수를 계산하고, 코보 카나에루를 명함으로 가정했을 때와 비교해줘. 비교 후 원래 분석 조건으로 되돌려줘. + +| 조건 | 종합력 | 예상 점수 | 최고점·밀어치기 적용 | 최고점·미적용 | +| --- | ---: | ---: | ---: | ---: | +| 기준·코보 4개화 | 403,756 | 3,935,062 | 4,224,816 | 4,217,763 | +| 코보 명함 가정 | 396,242 | 3,808,400 | 4,098,013 | 4,091,245 | +| 원래 조건 복원 | 403,756 | 3,935,062 | 4,224,816 | 4,217,763 | + +보탄 5/코보 4/마츠리 4/후부키 4/수영복 아냐 5개화, 하코스 벨즈 리더·레디언트 치즈, 빈도 고정 미사용, 파란 노드 전체 해제, 보너스 덮어쓰기 빈칸, 메모리 35개·멤버 강화 6.01% 조건이다. 명함 가정의 예상 점수는 -126,662점(-3.22%)이다. 매 조건에서 최고점 기준으로 배치를 다시 최적화했으며, 최고점 1위 조합의 평균 비교를 평균 전역 최적화로 설명하지 않았다. 실제 기준 3위 행의 예상 점수 3,935,616점이 1위보다 높다는 점도 구분했다. + +복원 시 코보 인접 개화 표시 4, 나머지 입력, 계산 완료, 이전 조건 결과 경고 소멸, 기준/복원 결과 일치를 확인했다. 보유·성장 데이터, 보드, 프리셋, 저장 분석 기록은 조작하지 않았다. 사용자 Edge에는 WebMCP 직접 호출 기능이 노출되지 않아 화면 조작으로 수행했다. 안내는 이전 결과 경고와 조건별 재최적화 설명을 추가해 보완했다. + +갱신 후 `npm run check` 오류·경고 0개, `npm run build`, `npm run build:pages` 통과. 패키지 생성에서 독립 스킬 4파일/플러그인 6파일의 허용 목록, 소스·ZIP·다운로드 복사본의 해시를 검증한다. 표준 Python 스킬 검증기는 여전히 PyYAML 누락으로 실행 불가하여 기존 패키지 frontmatter 검사와 실제 독립 동작 검증을 사용했다. 이 변경은 로컬 소스와 배포용 ZIP에 반영했으며 공개 배포는 Gitea Actions를 통한 후속 릴리스가 필요하다. + +## 설치 없는 문서 읽기 검증 — 2026-10-03 + +또 다른 독립 AI에 로컬 스킬 파일이나 프로젝트 소스를 제공하지 않고, Edge에 열린 계산기의 AI 연결 페이지에서 SKILL.md 사용 안내를 먼저 읽고 현재 편성을 계산하도록 요청했다. 실제 `/tools/ai`의 링크를 따라 `/ai/SKILL.html` 전체 안내를 읽고 배치 분석에서 계산을 완료했다. 설치와 연결 스위치 없이 예상 점수 3,935,062점, 최고점 4,224,816점(밀어치기 적용)을 확인했으며 입력과 저장 데이터를 변경하지 않았다. 표시된 최고점 3위의 평균이 1위보다 554점 높다는 해석도 안내만으로 수행했다. 새 ChatGPT 대화의 계정별 기능 제공 여부까지 검증한 것은 아니다. + +`npm run check` 오류·경고 0개와 온라인 Pages 빌드를 통과했다. Pages 해시 라우트에서 실제 링크 클릭으로 문서를 새 탭에 열고 전체 내용이 읽히는 것을 확인했다. Markdown/HTML 문서의 소스와 Pages 복사본 SHA-256도 일치했다. 검증용 미리보기 서버는 종료하고 사용자 계산기 탭을 유지했다. 공개 배포는 아직 수행하지 않았다. + +## 최종 소스 검증 — 2026-10-03 + +AI 연결 변경만 포함한 격리 소스에서 기존 엔진·연동 수명주기 테스트 253개와 새 분석 연동 테스트 5개를 통과했다. 분석 연동 테스트는 기존 웹 엔진과 점수·종합력 일치, 성장 가정 및 원본 불변성, 실제 보드 효과, 잘못된 입력 거부와 고정 카드 탐색 조건을 검증한다. `npm run check` 오류·경고 0개, `npm run build`, `npm run build:pages` 통과. Pages 빌드의 기존 큰 번들 경고는 남아 있다. 공개 안내 문서와 ZIP 파일이 Pages 산출물에 포함되며 별도 스킬·플러그인 등록 없이 문서를 읽는 사용자 흐름으로 배포한다. diff --git a/offline/OfflineApp.svelte b/offline/OfflineApp.svelte index 3eaf623..b43ee09 100644 --- a/offline/OfflineApp.svelte +++ b/offline/OfflineApp.svelte @@ -1,6 +1,8 @@ diff --git a/src/lib/components/ToolsNav.svelte b/src/lib/components/ToolsNav.svelte index 0c03ae8..aac2a4f 100644 --- a/src/lib/components/ToolsNav.svelte +++ b/src/lib/components/ToolsNav.svelte @@ -1,15 +1,16 @@ diff --git a/static/ai/SKILL.html b/static/ai/SKILL.html new file mode 100644 index 0000000..f268d58 --- /dev/null +++ b/static/ai/SKILL.html @@ -0,0 +1,144 @@ + + + + + + SKILL.md · HolodoriCalc AI usage guide + + + +

HolodoriCalc · SKILL.md

+

GPT용 사용 안내입니다. 아래 내용을 읽고 원래 계산기 탭에서 작업하세요. SKILL.md 다운로드

+
---
+name: holodori-web-assistant
+description: Use an open HolodoriCalc browser tab to calculate team scores, interpret results, and compare growth scenarios through browser controls or available site tools.
+---
+
+# HolodoriCalc web assistant
+
+Let the website perform all calculations. This skill supplies instructions only: no engine, card/chart database, executable scripts, or calculation server. Answer in Korean unless the user requests another language.
+
+## Connect
+
+Use the user's existing calculator tab in the browser profile where they normally use the calculator, including Edge with the official ChatGPT browser extension. The extension connects to the desktop app and can open side chat from its toolbar icon. Data saved in that browser profile is used directly; do not ask the user to move it to the built-in browser. Respect a supplied localhost or other calculator URL. If no tab is available, open https://doricalc-holo.live/ in that connected browser. If browser control itself is unavailable, explain the required desktop-app/browser connection; reading this guide does not provide that capability. Do not promise ordinary ChatGPT web/mobile support.
+
+For Windows setup guidance, point to the [official ChatGPT Windows installer](https://get.microsoft.com/installer/download/9PLM9XGG6VKS?cid=website_cta_psi), then have the user open the app and sign in. The [official browser extension](https://chromewebstore.google.com/detail/chatgpt/hehggadaopoacecdllhhajmbjkdcmajg?hl=ko) is installed in their usual calculator browser profile. The desktop app's **Settings → Computer Use** shows the browser connection; see the [official connection instructions](https://learn.chatgpt.com/docs/chrome-extension) if setup is incomplete. Do not install software merely to answer a calculator analysis request.
+
+Choose the available route:
+
+- **Browser controls:** Read [Browser calculation workflow](#browser-calculation-workflow) before operating the calculator. This route uses the existing calculation screens and needs no AI connection switch. An unavailable WebMCP capability or disabled **이 탭에서 AI 연결 켜기** switch does not prevent screen interaction. Do not repeatedly attempt an unsupported connection.
+- **Site tools:** Prefer them only when the browser advertises WebMCP and the page already exposes the relevant tools. The current **도구 → AI 연결** page provides browser setup guidance and an AI usage guide, not a tool connection switch. If tools are absent, use browser controls. Discover actual names and schemas; expected names are `holodori_context`, `holodori_catalog`, and `holodori_analyze`, possibly with host-added suffixes. Read [Analysis requests](#analysis-requests) for this route only.
+
+Use data saved in that browser. If required owned/growth data is missing, explain what is missing and guide the user to **사용자 데이터** or their calculator backup. Do not assume all cards are owned, migrate unrelated browser data automatically, ask for Node installation, or run the previous bundled plugin in cloud code execution.
+
+## Calculate and compare
+
+- Keep the user's requested song, difficulty, team, leader/outfit, and goal. If the request says "현재 편성", start from the visible conditions. Distinguish **예상 점수** (expected/average) from theoretical maximum. Default to owned cards and expected score when the goal is unspecified, while respecting the website's actual optimization goal.
+- Read a completed baseline, change only the requested scenario, calculate again, and compare scores using the same remaining conditions. Growth changes can affect parameters and skills; do not estimate score changes from power alone. Delta = scenario minus baseline; relative change uses baseline as denominator.
+- Browser route: record original conditions, use analysis-only inputs, verify each changed value before calculating, and restore changed conditions after comparison. Recalculate the restored baseline to verify the result. Inputs may persist automatically even without a save button.
+- Tool route: start with `holodori_context`; resolve IDs through `holodori_catalog` rather than inventing them. Use `holodori_analyze` and `scenario`, with the first team as baseline. Require `status: complete` before reporting scores.
+- Never edit **사용자 데이터** ownership/growth, board layouts, presets, or saved analysis records merely to model a scenario. Apply or save results only when the user requests it. On failure, restore any changed analysis inputs and state which result or restoration remains unverified.
+- Reuse completed results that answer follow-up questions. Keep additional experiments focused on the user's question instead of automatically running every possible team or growth stage.
+
+## Explain the result
+
+Report the song/difficulty, team and relevant leader/outfit, expected score, maximum score, changed assumption, and delta. State whether each scenario was separately reoptimized; do not describe that as a comparison with the same special order/frequency allocation held fixed. Mention important overrides and any displayed score-calibration warning. **빈도 조합 상위** in **배치 분석** is ordered by maximum score: its first row is not necessarily the best average. An expected-score comparison of visible rows is limited to those rows, not all possible combinations.
+
+The displayed probabilities assume all notes are PERFECT and vary only active-skill activation; report exact versus sampled probabilities as labeled by the current page/result. Search narrows candidates and does not prove global optimality. For site-tool multi-chart rankings, scores are optimized separately for each chart, not a shared fixed order. Do not invent event bonuses or unavailable charts.
+
+Browser results appear on their calculation screen; explain available site-tool results from their returned data. The user's analysis request does not authorize sharing collection data with unrelated services or changing saved user data.
+
+
+# Browser calculation workflow
+
+Use the host's documented browser controls and current visible labels. The labels below describe the calculator; inspect the live page rather than assuming control coordinates, option IDs, or browser APIs. Keep the user's tab open.
+
+## Find the appropriate screen
+
+- **배치 분석**: score an existing five-card team, optimize special-skill order and frequency reductions, and compare individual card awakening scenarios.
+- **조합 탐색**: search team candidates under the visible fixed-card and ownership conditions. Read the screen's stated optimization objective; ordinary search optimizes maximum score, not necessarily average score.
+- **사용자 데이터**: inspect saved ownership, memory count, member upgrades, and growth when needed. Do not change these for hypothetical calculations.
+- **도구 → AI 연결**: browser setup guidance and a link to the AI usage guide. Read the guide in its separate tab and operate the original calculator tab. Use the ordinary analysis screens for calculation; this page has no tool connection switch or result panel.
+
+Use the visible navigation. Preserve the current origin and URL style: online Pages may use hash routes while other deployments use path routes.
+
+## Baseline on 배치 분석
+
+1. Read **분석 조건**. Record the song, difficulty, five selected cards with their slot numbers and awakenings, **리더**, **리더 의상**, frequency locks, **파란 노드 전체** checkboxes, generation overrides, and song-score override. Also note visible memory/member-upgrade context when explaining a comparison.
+2. Cards are five-star cards from five different members. A leading **●** in a card option means owned. A member name alone may identify multiple variants; use the complete displayed card name. The selected awakening is an analysis assumption and may differ from saved ownership growth. **명함** means 0 awakening; maximum is 5. The page calculates parameters at maximum card level.
+3. Use **최적 배치 분석** and observe progress. While **분석 중 …%** is displayed, do not interpret a previous result as the new calculation. A short browser wait timing out does not mean the calculation failed: read progress before retrying, and do not start duplicate jobs.
+4. Verify completion with the ready calculation button, no running progress/error, and a populated **분석 결과** for the chosen song/difficulty and analysis power. If **입력 조건 또는 사용자 데이터가 변경되었습니다** is shown, the displayed result is stale; require a fresh calculation until the warning disappears. After a scenario change, require a new completed calculation even when its score happens to equal the baseline.
+5. Read the concise result summary rather than the entire skill timeline: **예상 점수**, **최고점 · 밀어치기 적용**, **최고점 · 밀어치기 미적용**, **추천 스페셜 발동 순서**, and per-card **쿨타임 …% 감소**. Record the selected frequency row if relevant. A result can remain visible while inputs change: **분석 당시 종합력** belongs to the completed run; **자동 계산 종합력** belongs to current inputs.
+
+## Growth comparison and restoration
+
+For "현재 편성에서 코보를 명함으로 가정하면?" find Kobo's actual selected slot; do not assume a fixed slot across users. Record its original awakening and completed baseline. Use that slot's **N번 카드 개화 단계 낮추기/높이기** buttons until the adjacent numeric value is the requested stage, then read the updated input state before starting calculation. Hold every other condition constant. Read the newly completed scenario result, compute its difference from the baseline, restore the original stage, and calculate once more to verify restoration. Do not change Kobo's ownership record in **사용자 데이터**.
+
+Each **최적 배치 분석** run optimizes special order/frequency allocations for that scenario. Report growth deltas as separately reoptimized outcomes. If the user asks to keep the exact order/allocation fixed, first verify that the available controls or tools support it; do not claim an optimized run holds them fixed.
+
+Use the same approach for another card or outfit comparison. If changing a card alters dependent leader/outfit options, record and restore those too. On an interrupted or failed calculation, restore changed inputs when browser control remains available; otherwise tell the user which condition still needs restoration. Do not save/delete analysis records or presets during a comparison.
+
+## Input meanings that affect conclusions
+
+- **빈도 감소 고정** uses **미사용**, 0%, 4%, 8%, 12%. **미사용** leaves the reduction available for optimization; it does not fix it to 0%. These are optimization variables and are not unlocked solely by the saved blue-board state.
+- **파란 노드 전체** is a hypothetical full-blue-board condition. Leave it off for the user's actual saved board unless that scenario was requested.
+- A blank **기수 보너스 덮어쓰기 (0~900)** or **가창 보너스 덮어쓰기 (0~10%)** uses the actual board value. Entering 0 explicitly overrides it to zero. Preserve blanks versus numeric zeros.
+- **리더 의상** affects team power; selecting a leader alone does not determine the full condition. Read the displayed costume effect.
+- **최고점 · 밀어치기 적용** assumes relevant manually judged notes at a special-skill start boundary benefit from the new special effect. **밀어치기 미적용** excludes that boundary handling. Keep the same maximum-score definition when comparing runs.
+- **예상 점수** incorporates active-skill randomness under the calculation's PERFECT-note assumption. It does not predict a player's actual accuracy.
+- **빈도 조합 상위** is sorted by maximum score. The displayed average and success probabilities can favor another row. Select a row if its detailed result is needed, and distinguish "best among the displayed rows" from a global optimum.
+- Probability labels and sample counts belong to the current page. Do not apply site-tool sample limits to screen calculations. Board/connection recommendations are separate operations; do not apply a recommended board layout as part of ordinary score comparison.
+
+## Team search
+
+On **조합 탐색**, record the visible search conditions before changing them. For owned-only requests, use **내가 보유한 카드만 사용하기**. Record fixed cards/awakenings, leader/outfit, candidate awakening, probability restrictions, overrides, and output count. The page explains how higher saved awakenings override lower automatic-candidate stages; read that guidance before treating every candidate as having the chosen stage.
+
+Run **최적 조합 탐색** and wait through candidate screening, detailed analysis, and probability phases. Summarize the completed visible candidates and their assumptions. For a request to maximize average score, explain an available screen's maximum-score objective; use an advertised expected-score site tool if available, or compare a bounded set of candidates and state the search limitation. Restore search inputs changed for a temporary comparison.
+
+## Continuing the conversation
+
+Keep a compact baseline/scenario comparison in the conversation, including assumptions and values, so questions about existing results do not require another run. For a new experiment, read current page inputs again; the user may have changed them. If a requested growth scenario cannot be modeled by analysis inputs, explain that limitation instead of changing saved growth or making up a numerical result.
+
+
+# Analysis requests
+
+These illustrate request structure. Replace every angle-bracket placeholder with an ID returned by the current page's catalog; they are not executable examples. Use the live tool schema if it differs.
+
+## Bounded search
+
+```json
+{
+  "schemaVersion": 1,
+  "operation": "search",
+  "charts": [{"songId": "<song-id>", "difficulty": "easy"}],
+  "objective": "expected",
+  "ownedOnly": true,
+  "search": {
+    "fixedCardIds": ["<required-card-id>"],
+    "leaderCharacterId": "<member-id>",
+    "leaderOutfitCardId": "<owned-outfit-card-id>",
+    "maxTeams": 4
+  }
+}
+```
+
+Omit candidateCardIds to use owned five-star cards. If more than 200 are eligible, use catalog queries to narrow them. Fixed cards must be included in an explicitly supplied candidate pool. The leader defaults to the first team member; specifying a leader and owned outfit makes the intended comparison clear.
+
+## Compare awakening
+
+```json
+{
+  "schemaVersion": 1,
+  "operation": "compare",
+  "charts": [{"songId": "<song-id>", "difficulty": "easy"}],
+  "teams": [
+    {"name": "현재", "cardIds": ["<c1>", "<c2>", "<c3>", "<c4>", "<c5>"], "leaderCharacterId": "<member-id>", "leaderOutfitCardId": "<outfit-card-id>"},
+    {"name": "4개화 가정", "cardIds": ["<c1>", "<c2>", "<c3>", "<c4>", "<c5>"], "leaderCharacterId": "<member-id>", "leaderOutfitCardId": "<outfit-card-id>", "scenario": {"awakeningByCardId": {"<c1>": 4}}}
+  ]
+}
+```
+
+Each team needs five five-star cards from different members. Limits: three charts, eight teams, beamWidth ≤64, probabilitySamples ≤5000, one active job, 30 seconds. Returned assumptions and provenance explain the calculation's scope. `memberBoards` and `boardCards` scenarios replace complete mappings; do not fabricate node/socket IDs when catalog/context information is insufficient.
+
+ + \ No newline at end of file diff --git a/static/ai/SKILL.md b/static/ai/SKILL.md new file mode 100644 index 0000000..e0d0a9b --- /dev/null +++ b/static/ai/SKILL.md @@ -0,0 +1,130 @@ +--- +name: holodori-web-assistant +description: Use an open HolodoriCalc browser tab to calculate team scores, interpret results, and compare growth scenarios through browser controls or available site tools. +--- + +# HolodoriCalc web assistant + +Let the website perform all calculations. This skill supplies instructions only: no engine, card/chart database, executable scripts, or calculation server. Answer in Korean unless the user requests another language. + +## Connect + +Use the user's existing calculator tab in the browser profile where they normally use the calculator, including Edge with the official ChatGPT browser extension. The extension connects to the desktop app and can open side chat from its toolbar icon. Data saved in that browser profile is used directly; do not ask the user to move it to the built-in browser. Respect a supplied localhost or other calculator URL. If no tab is available, open https://doricalc-holo.live/ in that connected browser. If browser control itself is unavailable, explain the required desktop-app/browser connection; reading this guide does not provide that capability. Do not promise ordinary ChatGPT web/mobile support. + +For Windows setup guidance, point to the [official ChatGPT Windows installer](https://get.microsoft.com/installer/download/9PLM9XGG6VKS?cid=website_cta_psi), then have the user open the app and sign in. The [official browser extension](https://chromewebstore.google.com/detail/chatgpt/hehggadaopoacecdllhhajmbjkdcmajg?hl=ko) is installed in their usual calculator browser profile. The desktop app's **Settings → Computer Use** shows the browser connection; see the [official connection instructions](https://learn.chatgpt.com/docs/chrome-extension) if setup is incomplete. Do not install software merely to answer a calculator analysis request. + +Choose the available route: + +- **Browser controls:** Read [Browser calculation workflow](#browser-calculation-workflow) before operating the calculator. This route uses the existing calculation screens and needs no AI connection switch. An unavailable WebMCP capability or disabled **이 탭에서 AI 연결 켜기** switch does not prevent screen interaction. Do not repeatedly attempt an unsupported connection. +- **Site tools:** Prefer them only when the browser advertises WebMCP and the page already exposes the relevant tools. The current **도구 → AI 연결** page provides browser setup guidance and an AI usage guide, not a tool connection switch. If tools are absent, use browser controls. Discover actual names and schemas; expected names are `holodori_context`, `holodori_catalog`, and `holodori_analyze`, possibly with host-added suffixes. Read [Analysis requests](#analysis-requests) for this route only. + +Use data saved in that browser. If required owned/growth data is missing, explain what is missing and guide the user to **사용자 데이터** or their calculator backup. Do not assume all cards are owned, migrate unrelated browser data automatically, ask for Node installation, or run the previous bundled plugin in cloud code execution. + +## Calculate and compare + +- Keep the user's requested song, difficulty, team, leader/outfit, and goal. If the request says "현재 편성", start from the visible conditions. Distinguish **예상 점수** (expected/average) from theoretical maximum. Default to owned cards and expected score when the goal is unspecified, while respecting the website's actual optimization goal. +- Read a completed baseline, change only the requested scenario, calculate again, and compare scores using the same remaining conditions. Growth changes can affect parameters and skills; do not estimate score changes from power alone. Delta = scenario minus baseline; relative change uses baseline as denominator. +- Browser route: record original conditions, use analysis-only inputs, verify each changed value before calculating, and restore changed conditions after comparison. Recalculate the restored baseline to verify the result. Inputs may persist automatically even without a save button. +- Tool route: start with `holodori_context`; resolve IDs through `holodori_catalog` rather than inventing them. Use `holodori_analyze` and `scenario`, with the first team as baseline. Require `status: complete` before reporting scores. +- Never edit **사용자 데이터** ownership/growth, board layouts, presets, or saved analysis records merely to model a scenario. Apply or save results only when the user requests it. On failure, restore any changed analysis inputs and state which result or restoration remains unverified. +- Reuse completed results that answer follow-up questions. Keep additional experiments focused on the user's question instead of automatically running every possible team or growth stage. + +## Explain the result + +Report the song/difficulty, team and relevant leader/outfit, expected score, maximum score, changed assumption, and delta. State whether each scenario was separately reoptimized; do not describe that as a comparison with the same special order/frequency allocation held fixed. Mention important overrides and any displayed score-calibration warning. **빈도 조합 상위** in **배치 분석** is ordered by maximum score: its first row is not necessarily the best average. An expected-score comparison of visible rows is limited to those rows, not all possible combinations. + +The displayed probabilities assume all notes are PERFECT and vary only active-skill activation; report exact versus sampled probabilities as labeled by the current page/result. Search narrows candidates and does not prove global optimality. For site-tool multi-chart rankings, scores are optimized separately for each chart, not a shared fixed order. Do not invent event bonuses or unavailable charts. + +Browser results appear on their calculation screen; explain available site-tool results from their returned data. The user's analysis request does not authorize sharing collection data with unrelated services or changing saved user data. + + +# Browser calculation workflow + +Use the host's documented browser controls and current visible labels. The labels below describe the calculator; inspect the live page rather than assuming control coordinates, option IDs, or browser APIs. Keep the user's tab open. + +## Find the appropriate screen + +- **배치 분석**: score an existing five-card team, optimize special-skill order and frequency reductions, and compare individual card awakening scenarios. +- **조합 탐색**: search team candidates under the visible fixed-card and ownership conditions. Read the screen's stated optimization objective; ordinary search optimizes maximum score, not necessarily average score. +- **사용자 데이터**: inspect saved ownership, memory count, member upgrades, and growth when needed. Do not change these for hypothetical calculations. +- **도구 → AI 연결**: browser setup guidance and a link to the AI usage guide. Read the guide in its separate tab and operate the original calculator tab. Use the ordinary analysis screens for calculation; this page has no tool connection switch or result panel. + +Use the visible navigation. Preserve the current origin and URL style: online Pages may use hash routes while other deployments use path routes. + +## Baseline on 배치 분석 + +1. Read **분석 조건**. Record the song, difficulty, five selected cards with their slot numbers and awakenings, **리더**, **리더 의상**, frequency locks, **파란 노드 전체** checkboxes, generation overrides, and song-score override. Also note visible memory/member-upgrade context when explaining a comparison. +2. Cards are five-star cards from five different members. A leading **●** in a card option means owned. A member name alone may identify multiple variants; use the complete displayed card name. The selected awakening is an analysis assumption and may differ from saved ownership growth. **명함** means 0 awakening; maximum is 5. The page calculates parameters at maximum card level. +3. Use **최적 배치 분석** and observe progress. While **분석 중 …%** is displayed, do not interpret a previous result as the new calculation. A short browser wait timing out does not mean the calculation failed: read progress before retrying, and do not start duplicate jobs. +4. Verify completion with the ready calculation button, no running progress/error, and a populated **분석 결과** for the chosen song/difficulty and analysis power. If **입력 조건 또는 사용자 데이터가 변경되었습니다** is shown, the displayed result is stale; require a fresh calculation until the warning disappears. After a scenario change, require a new completed calculation even when its score happens to equal the baseline. +5. Read the concise result summary rather than the entire skill timeline: **예상 점수**, **최고점 · 밀어치기 적용**, **최고점 · 밀어치기 미적용**, **추천 스페셜 발동 순서**, and per-card **쿨타임 …% 감소**. Record the selected frequency row if relevant. A result can remain visible while inputs change: **분석 당시 종합력** belongs to the completed run; **자동 계산 종합력** belongs to current inputs. + +## Growth comparison and restoration + +For "현재 편성에서 코보를 명함으로 가정하면?" find Kobo's actual selected slot; do not assume a fixed slot across users. Record its original awakening and completed baseline. Use that slot's **N번 카드 개화 단계 낮추기/높이기** buttons until the adjacent numeric value is the requested stage, then read the updated input state before starting calculation. Hold every other condition constant. Read the newly completed scenario result, compute its difference from the baseline, restore the original stage, and calculate once more to verify restoration. Do not change Kobo's ownership record in **사용자 데이터**. + +Each **최적 배치 분석** run optimizes special order/frequency allocations for that scenario. Report growth deltas as separately reoptimized outcomes. If the user asks to keep the exact order/allocation fixed, first verify that the available controls or tools support it; do not claim an optimized run holds them fixed. + +Use the same approach for another card or outfit comparison. If changing a card alters dependent leader/outfit options, record and restore those too. On an interrupted or failed calculation, restore changed inputs when browser control remains available; otherwise tell the user which condition still needs restoration. Do not save/delete analysis records or presets during a comparison. + +## Input meanings that affect conclusions + +- **빈도 감소 고정** uses **미사용**, 0%, 4%, 8%, 12%. **미사용** leaves the reduction available for optimization; it does not fix it to 0%. These are optimization variables and are not unlocked solely by the saved blue-board state. +- **파란 노드 전체** is a hypothetical full-blue-board condition. Leave it off for the user's actual saved board unless that scenario was requested. +- A blank **기수 보너스 덮어쓰기 (0~900)** or **가창 보너스 덮어쓰기 (0~10%)** uses the actual board value. Entering 0 explicitly overrides it to zero. Preserve blanks versus numeric zeros. +- **리더 의상** affects team power; selecting a leader alone does not determine the full condition. Read the displayed costume effect. +- **최고점 · 밀어치기 적용** assumes relevant manually judged notes at a special-skill start boundary benefit from the new special effect. **밀어치기 미적용** excludes that boundary handling. Keep the same maximum-score definition when comparing runs. +- **예상 점수** incorporates active-skill randomness under the calculation's PERFECT-note assumption. It does not predict a player's actual accuracy. +- **빈도 조합 상위** is sorted by maximum score. The displayed average and success probabilities can favor another row. Select a row if its detailed result is needed, and distinguish "best among the displayed rows" from a global optimum. +- Probability labels and sample counts belong to the current page. Do not apply site-tool sample limits to screen calculations. Board/connection recommendations are separate operations; do not apply a recommended board layout as part of ordinary score comparison. + +## Team search + +On **조합 탐색**, record the visible search conditions before changing them. For owned-only requests, use **내가 보유한 카드만 사용하기**. Record fixed cards/awakenings, leader/outfit, candidate awakening, probability restrictions, overrides, and output count. The page explains how higher saved awakenings override lower automatic-candidate stages; read that guidance before treating every candidate as having the chosen stage. + +Run **최적 조합 탐색** and wait through candidate screening, detailed analysis, and probability phases. Summarize the completed visible candidates and their assumptions. For a request to maximize average score, explain an available screen's maximum-score objective; use an advertised expected-score site tool if available, or compare a bounded set of candidates and state the search limitation. Restore search inputs changed for a temporary comparison. + +## Continuing the conversation + +Keep a compact baseline/scenario comparison in the conversation, including assumptions and values, so questions about existing results do not require another run. For a new experiment, read current page inputs again; the user may have changed them. If a requested growth scenario cannot be modeled by analysis inputs, explain that limitation instead of changing saved growth or making up a numerical result. + + +# Analysis requests + +These illustrate request structure. Replace every angle-bracket placeholder with an ID returned by the current page's catalog; they are not executable examples. Use the live tool schema if it differs. + +## Bounded search + +```json +{ + "schemaVersion": 1, + "operation": "search", + "charts": [{"songId": "", "difficulty": "easy"}], + "objective": "expected", + "ownedOnly": true, + "search": { + "fixedCardIds": [""], + "leaderCharacterId": "", + "leaderOutfitCardId": "", + "maxTeams": 4 + } +} +``` + +Omit candidateCardIds to use owned five-star cards. If more than 200 are eligible, use catalog queries to narrow them. Fixed cards must be included in an explicitly supplied candidate pool. The leader defaults to the first team member; specifying a leader and owned outfit makes the intended comparison clear. + +## Compare awakening + +```json +{ + "schemaVersion": 1, + "operation": "compare", + "charts": [{"songId": "", "difficulty": "easy"}], + "teams": [ + {"name": "현재", "cardIds": ["", "", "", "", ""], "leaderCharacterId": "", "leaderOutfitCardId": ""}, + {"name": "4개화 가정", "cardIds": ["", "", "", "", ""], "leaderCharacterId": "", "leaderOutfitCardId": "", "scenario": {"awakeningByCardId": {"": 4}}} + ] +} +``` + +Each team needs five five-star cards from different members. Limits: three charts, eight teams, beamWidth ≤64, probabilitySamples ≤5000, one active job, 30 seconds. Returned assumptions and provenance explain the calculation's scope. `memberBoards` and `boardCards` scenarios replace complete mappings; do not fabricate node/socket IDs when catalog/context information is insufficient. diff --git a/static/downloads/holodori-web-assistant-plugin.zip b/static/downloads/holodori-web-assistant-plugin.zip new file mode 100644 index 0000000000000000000000000000000000000000..77c21747cc6205d78949176124b58dfffae02da9 GIT binary patch literal 9442 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 atuF)Rp#C;%0N~5tb@%sSR>}V7?tcLEGpD-% literal 0 HcmV?d00001 diff --git a/static/downloads/holodori-web-assistant-skill.zip b/static/downloads/holodori-web-assistant-skill.zip new file mode 100644 index 0000000000000000000000000000000000000000..d299191e095fbbada769e18c32eafcdd2054098b GIT binary patch literal 8018 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 Fe*qAxDp>#k literal 0 HcmV?d00001