ソースコード入力をAST分析に渡すフロー
作成日: 2026年6月26日
目的
この文書は docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md の前段を補強する。
基準ロードマップではすでに次のフローを整理した。
project source
-> AST chunks or direct chunks
-> official docs JSONL retrieval
-> on-demand LLM verification
-> score DB
-> classified filesystemここではユーザーがプロンプトとソースコードを入力したとき、その入力を AST Parser にどのような形で渡すかを整理します。
重要な原則
プロンプトとソースコードは同じ入力束に入りますが、役割は異なります。
| 入力 | 役割 |
|---|---|
| ユーザープロンプト | 分析意図、質問タイプ、期待出力の方向 |
| ソースコード | AST Parser が実際にパースする対象 |
| ファイルパス/プロジェクト情報 | 分析単位の識別、プロジェクト単位の分類のためのメタデータ |
AST Parser はソースコードをパースします。プロンプトは AST 文法解析の対象ではありません。
ただし、プロンプトはその後の段階でどの断片を優先的に見るか、どの Retriever を使用するか、Qwen 3.6 にどのような検証リクエストを作成するかを決定するために使用されます。
入力パッケージ
ユーザー入力はまず 1 つの分析リクエストとしてまとめられます。
analysis_request
prompt
source
source_kind
project_id
source_file
source_sha256
source_origin各項目の意味は以下の通りです。
| 項目 | 説明 |
|---|---|
prompt |
ユーザーが入力した質問。例: “このコードは何の意味ですか?”、“Godot 4に変換が必要ですか?” |
source |
元のソースコードテキスト。できるだけそのまま保存します。 |
source_kind |
gdscript, scene, resource, project_config, markdown, unknown など入力の種類 |
project_id |
GitHub リポジトリまたはローカルプロジェクトを区別するための ID |
source_file |
プロジェクト内のファイルパス。単一コードの貼り付けの場合は一時的なパスを使用します。 |
source_sha256 |
元のソーステキストのハッシュ。貼り付けやファイル入力の両方で、リクエスト作成時に計算されます。 |
source_origin |
github, local_directory, uploaded_file, pasted_snippet など出所 |
この段階では出力 JSONL スキーマや score DB カラムは確定しません。目的は AST パーサーとその後の Retriever/LLM 段階が同じ入力を同じ基準で追跡できるようにすることです。
単一ソースコードの貼り付け
ユーザーが Web UI または CLI にプロンプトとソースコードを直接入力する場合です。
例:
これを Godot 4 に変換する必要がありますか?
extends KinematicBody2D
func _physics_process(delta):
move_and_slide()処理フロー:
- 入力を
analysis_requestでまとめる。 source_originはpasted_snippetとする。source_fileは実際のファイルパスがないため、pasted://snippet-<id>.gdのような一時識別子にする。source_sha256は貼り付けた元コードを基準に即座に計算する。source_kindは拡張子のヒント、コード内容、ユーザーが指定した言語を基に推測する。- AST Parser には
source、source_kind、source_file、source_sha256を渡す。 - プロンプトは Parser に混ぜずに
analysis_request.promptとして保存する。
プロジェクトディレクトリ入力
GitHub リポジトリまたはローカルフォルダを分析する場合です。
処理フロー:
- プロジェクトルートを
project_idでまとめる。 - ファイルシステムを相対パス基準で走査する。
- 各ファイルについて、含めるか除外するかをまず記録する。
- 除外されたファイルは Web UI やログで確認できるように
excluded_filesとして残す。 - 含めたファイルは
# <relative/path>ヘッダーが付いたプレーンテキストブロックとして展開する。 .gdファイルは AST Parser に送って、関数/コード片を元の順序で作成する。- AST Parser の対象でない含めたファイルは、ファイルの性質に合わせた直接的な片化ルールを適用する。
- 各片は元のパス、元の順序、ハッシュを持ったまま Retriever 要求へ渡される。
初期対象ファイルは基準ロードマップと同じです。
| ファイル | AST/片化方向 |
|---|---|
.gd |
GDScript AST、関数、クラス、signal、変数、API 呼び出し候補 |
.tscn |
シーンノードタイプ、スクリプト参照、リソース参照、エクスポートプロパティ |
.tres |
リソースタイプ、スクリプトクラス、マテリアル/シェーダー/リソース手がかり |
project.godot |
Godot バージョンヒント、autoload、renderer、feature、input map |
| README/文書 | 基本的にソースコード AST 分析では除外候補。除外の有無と理由を UI/ログに残す。 |
プロジェクトソースプレーンテキスト展開
プロジェクト単位の入力はまずファイル別プレーンテキストの塊として展開する。このプレーンテキストは LLM にそのまま一度に入れるための最終プロンプトではなく、ファイル境界と相対パスを失わないための中間表現である。
基本形:
# project.godot
; original project.godot content
# scripts/player.gd
extends KinematicBody2D
func _physics_process(delta):
move_and_slide(velocity)
# scenes/player.tscn
[gd_scene load_steps=2 format=3]
[node name="Player" type="CharacterBody2D"]規則:
- ファイルヘッダーは
# <relative/path>形式で記述する。 - 相対パスはプロジェクトルート基準である。
- ファイル本文は可能な限り原文のまま残す。
- この平文ブロックはファイル境界の確認とデバッグに使用する。
- 実際の処理ではこの全文平文を一度に LLM や AST パーサに渡さない。
- ヘッダーで区切られた各ファイルはまず含める/除外する判定を受ける。
- 除外されたファイルは
excluded_filesに残す。 - 含めた
.gdファイルはanalysis_requestを経て AST パーサに入る。 - 含めた non-
.gdテキスト/設定ファイルは直接チャンク化ツールに入る。
このようにすれば、ユーザーが提供した「プロジェクト全体のソース」を人が読める形で残しつつ、以降の段階で除外ファイル、AST チャンク、direct chunk、Retriever ペイロードをファイル/チャンク単位で追跡できる。
クローンプロジェクト基準確認計画
AST パーサから Retriever へ渡すフローは抽象的な例だけでは確認しにくい。したがって実際に Godot プロジェクトをひとつクローンし、そのプロジェクトのファイルツリー、平文展開、AST チャンク/direct チャンク分割、Retriever 要求の繰り返しフローを基準として確認する。
基準プロジェクトは次のように設定する。
| 項目 | 値 |
|---|---|
| repository | godotengine/godot-demo-projects |
| project path | 2d/dodge_the_creeps |
| 選択理由 | Godot 公式デモで、.gd、.tscn、project.godot、README、アセット参照が一緒にあり、プロジェクト入力フローを小規模に確認しやすい。 |
| 保存基準 | クローンしたソースはリポジトリにコミットしない。ドキュメントにはツリーと処理計画だけを残す。 |
クローン基準:
git clone --depth 1 --filter=blob:none --sparse https://github.com/godotengine/godot-demo-projects.git /tmp/idea_world_godot_demo_projects
cd /tmp/idea_world_godot_demo_projects
git sparse-checkout set 2d/dodge_the_creepsこのクローンはフロー確認用です。リポジトリ内にクローンしたプロジェクトのソースやアセットをコピーしません。
確認対象はクローンしたプロジェクトの全ファイルパスです。まず全パスをソートされた順序で展開し、各ファイルを LLM が読み取れる断片に分割します。ここで別途中間 DB を作成しません。重要なのは「リポジトリ全体を一度に読み込むこと」ではなく「パス順に断片を作成し、断片ごとに何度も呼び出すこと」です。
2d/dodge_the_creeps
.gitignore
LICENSE
README.md
art/House In a Forest Loop.ogg
art/House In a Forest Loop.ogg.import
art/enemyFlyingAlt_1.png
art/enemyFlyingAlt_1.png.import
art/enemyFlyingAlt_2.png
art/enemyFlyingAlt_2.png.import
art/enemySwimming_1.png
art/enemySwimming_1.png.import
art/enemySwimming_2.png
art/enemySwimming_2.png.import
art/enemyWalking_1.png
art/enemyWalking_1.png.import
art/enemyWalking_2.png
art/enemyWalking_2.png.import
art/gameover.wav
art/gameover.wav.import
art/playerGrey_up1.png
art/playerGrey_up1.png.import
art/playerGrey_up2.png
art/playerGrey_up2.png.import
art/playerGrey_walk1.png
art/playerGrey_walk1.png.import
art/playerGrey_walk2.png
art/playerGrey_walk2.png.import
fonts/FONTLOG.txt
fonts/LICENSE.txt
fonts/Xolonium-Regular.ttf
fonts/Xolonium-Regular.ttf.import
hud.gd
hud.gd.uid
hud.tscn
icon.webp
icon.webp.import
main.gd
main.gd.uid
main.tscn
mob.gd
mob.gd.uid
mob.tscn
player.gd
player.gd.uid
player.tscn
project.godot
screenshots/.gdignore
screenshots/dodge.pngファイル別断片化基準:
| ファイル種別 | 断片化方式 | 以後の呼び出し方式 |
|---|---|---|
.gd |
AST パーサが関数、シグナル、変数、クラス継承、API 呼び出しの断片を元の順序で作成する。 | 各 AST 断片ごとに Retriever を呼び出し、プロンプト + AST 断片 + 検索結果 を LLM に送る。 |
project.godot |
AST パーサに入れない。対象に含める場合は section/key-value、メインシーン、機能、入力マップの断片を作成する。 | 断片を直接 Retriever に送るか、.gd の AST 判定の周辺コンテキストとして付加する。 |
.tscn, .tres |
AST パーサに入れない。対象に含める場合は node ブロック、ext_resource、sub_resource、connection の断片を作成する。 | 断片を直接 Retriever に送るか、関連付けられた .gd ファイルの AST 判定の周辺コンテキストとして付加する。 |
.md, .txt, LICENSE |
ソースコード AST 分析では基本的に除外候補とする。 | 除外の有無と理由を UI/ログに残す。別途文書分析モードでない限り Retriever には送らない。 |
.import, .uid, .gitignore, .gdignore |
AST パーサに入れない。対象に含める場合は行または key-value の断片を作成する。 | 断片を直接 Retriever に送るか、ファイルパス/リソース関係を確認する周辺コンテキストとしてのみ使用する。 |
| 画像、オーディオ、フォント等のバイナリ | AST パーサにも入れず、LLM に元のバイト列も入れない。パスと除外理由だけを残す。 | 該当パスが .tscn、.import などのテキスト断片に出現したとき、関係情報としてのみ使用する。 |
つまり AST パーサに入る対象は .gd ファイルである。リポジトリ全体をパス順に展開するが、その結果は「一度に LLM に送る大きな本文」ではない。# <relative/path> ヘッダーはファイルがどのパスから来たか、以後どの関数/コード/設定断片に分割され Retriever に入ったかを追跡するための表示である。.md のように除外することにしたファイルは除外リストに残し、含まれた .gd ファイルは AST パーサが関数またはコード断片単位で順番に分割する。その後の LLM 呼び出しは常に プロンプト + 現在の断片 + Retriever 検索結果 で複数回実行される。
for each file in repository path order:
record file path under "# <relative/path>"
if file is excluded by source-analysis policy:
save excluded_files entry with reason
continue
if file.path endswith ".gd":
ast_chunks = ast_parse(full_file_text)
for each ast_chunk in ast_chunks:
retrieved = retrieve(build_query_from_chunk(prompt, ast_chunk))
response = call_llm(prompt + ast_chunk + retrieved)
validate(response)
save_chunk_result(response)
else:
text_chunks = split_without_ast(file)
for each text_chunk in text_chunks:
retrieved = retrieve(build_query_from_chunk(prompt, text_chunk))
response = call_llm(prompt + text_chunk + retrieved)
validate(response)
save_chunk_result(response)最終 LLM 入力単位は常に次の形です。
llm_judgment_request
user_prompt
project_id
source_file
chunk_id
chunk_order
chunk_kind
chunk_text
retrieved_evidenceRetriever 検索入力
Retriever は私が提供したコード片を DB で検索する役割だけを担います。ファイルパス、チャンク ID、チャンク順序は検索条件ではなく追跡情報です。したがって実装では、これら二つのオブジェクトを分離します。
chunk_trace
project_id
source_file
source_sha256
chunk_id
chunk_order
chunk_kindretriever_query
query_text
query_terms
raw_chunk_text
symbol_candidates
api_call_candidates
reference_candidates
prompt_termschunk_traceは UI、ログ、再起動、デバッグ、LLM 応答接続にのみ使用します。Retriever 検索関数には retriever_queryだけを渡します。
AST/direct chunk
-> build_retriever_query(chunk_text, extracted_candidates, user_prompt)
-> retriever.search(retriever_query)
-> orchestrator attaches chunk_trace to returned hits
-> prompt + chunk_text + retrieved_hits -> LLM judgmentすなわち、検索は次の値で行う。
| 検索に使用 | 検索に使用しない |
|---|---|
chunk_text |
source_file |
symbol_candidates |
chunk_id |
api_call_candidates |
chunk_order |
reference_candidates |
source_sha256 |
prompt_terms |
project_id |
ファイルパスが必要なのは検索ではなく追跡です。例えば player.gd:function:_process という trace が必要で、ウェブ UI で「この応答が player.gd の4番目の断片から来た」ことを示すことができます。しかし Retriever が DB で根拠を探すときは player.gd というパスではなく、_process 関数内のコードと API 候補で検索する必要があります。
例えば player.gd の _process 関数のコードは、まず追跡情報と検索情報を分離します。
chunk_trace
project_id: "github:godotengine/godot-demo-projects/2d/dodge_the_creeps"
source_file: "player.gd"
source_sha256: "87f4fcf7481dba031f74a363475cee75b81c3d42eb1347b318f6b824e37329a6"
chunk_id: "player.gd:function:_process"
chunk_order: 4
chunk_kind: "function"retriever_query
raw_chunk_text: |
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if Input.is_action_pressed(&"move_down"):
velocity.y += 1
if Input.is_action_pressed(&"move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite2D.play()
else:
$AnimatedSprite2D.stop()
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
if velocity.x != 0:
$AnimatedSprite2D.animation = &"right"
$AnimatedSprite2D.flip_v = false
$Trail.rotation = 0
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite2D.animation = &"up"
rotation = PI if velocity.y > 0 else 0
symbol_candidates:
- "Vector2"
- "AnimatedSprite2D"
api_call_candidates:
- "Input.is_action_pressed"
- "Vector2.ZERO"
- "velocity.normalized"
- "position.clamp"
reference_candidates: []
prompt_terms:
- "Godot 3"
- "Godot 4"
- "migration"
query_terms:
- "Input.is_action_pressed"
- "Vector2.ZERO"
- "velocity.normalized"
- "position.clamp"
- "Vector2"
- "AnimatedSprite2D"
- "_process"
- "Godot 3"
- "Godot 4"
- "migration"
query_text: "Input.is_action_pressed Vector2.ZERO velocity.normalized position.clamp Vector2 AnimatedSprite2D _process Godot 3 Godot 4 migration"Retriever 検索コード基準
以下のコードは実装方向を固定するための基準です。Retriever には RetrieverQuery のみを渡します。ChunkTrace は Retriever の外で orchestrator が保持し、検索結果と再度結び付けます。
from dataclasses import dataclass, field
import re
@dataclass(frozen=True)
class ChunkTrace:
project_id: str
source_file: str
source_sha256: str
chunk_id: str
chunk_order: int
chunk_kind: str
@dataclass(frozen=True)
class RetrieverQuery:
query_text: str
query_terms: list[str]
raw_chunk_text: str
symbol_candidates: list[str] = field(default_factory=list)
api_call_candidates: list[str] = field(default_factory=list)
reference_candidates: list[str] = field(default_factory=list)
prompt_terms: list[str] = field(default_factory=list)
@dataclass(frozen=True)
class RetrievalHit:
table_name: str
record_id: str
score: float
title: str
content: str
metadata: dict
IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)?")
def unique_keep_order(values: list[str]) -> list[str]:
seen: set[str] = set()
result: list[str] = []
for value in values:
normalized = value.strip()
if normalized and normalized not in seen:
seen.add(normalized)
result.append(normalized)
return result
def extract_identifiers(chunk_text: str) -> list[str]:
return unique_keep_order(IDENTIFIER_RE.findall(chunk_text))
def extract_prompt_terms(user_prompt: str) -> list[str]:
terms: list[str] = []
prompt = user_prompt.lower()
if "godot 3" in prompt or "godot3" in prompt:
terms.append("Godot 3")
if "godot 4" in prompt or "godot4" in prompt:
terms.append("Godot 4")
if "변환" in user_prompt or "마이그레이션" in user_prompt or "migration" in prompt:
terms.extend(["migration", "deprecated", "renamed"])
if "설명" in user_prompt or "뜻" in user_prompt or "explain" in prompt:
terms.extend(["description", "usage"])
return terms
def build_retriever_query(
*,
user_prompt: str,
chunk_text: str,
symbol_candidates: list[str],
api_call_candidates: list[str],
reference_candidates: list[str],
) -> RetrieverQuery:
prompt_terms = extract_prompt_terms(user_prompt)
query_terms = unique_keep_order(
api_call_candidates
+ symbol_candidates
+ reference_candidates
+ extract_identifiers(chunk_text)
+ prompt_terms
)
return RetrieverQuery(
query_text=" ".join(query_terms),
query_terms=query_terms,
raw_chunk_text=chunk_text,
symbol_candidates=symbol_candidates,
api_call_candidates=api_call_candidates,
reference_candidates=reference_candidates,
prompt_terms=prompt_terms,
)
def build_embedding_text(query: RetrieverQuery) -> str:
return "\n".join(
[
query.raw_chunk_text,
" ".join(query.api_call_candidates),
" ".join(query.symbol_candidates),
" ".join(query.reference_candidates),
" ".join(query.prompt_terms),
]
)
def retrieve_for_query(db, embedder, query: RetrieverQuery, limit_per_table: int = 5) -> list[RetrievalHit]:
query_embedding = embedder.embed_query(build_embedding_text(query))
tables = ["docs_chunks", "api_mapping", "label_prototypes"]
hits: list[RetrievalHit] = []
for table_name in tables:
hits.extend(search_table(db, table_name, query, query_embedding, limit_per_table))
hits.sort(key=lambda hit: hit.score, reverse=True)
return hits
def search_table(db, table_name: str, query: RetrieverQuery, query_embedding: list[float], limit: int) -> list[RetrievalHit]:
sql = """
select
%(table_name)s as table_name,
id::text as record_id,
title,
content,
metadata,
1 - (embedding <=> %(query_embedding)s::vector) as vector_score,
ts_rank(search_tsv, websearch_to_tsquery('simple', %(query_text)s)) as text_score,
similarity(search_text, %(query_text)s) as trigram_score
from godot_rag.dynamic_retrieval_view
where table_name = %(table_name)s
and (
embedding is not null
or
search_tsv @@ websearch_to_tsquery('simple', %(query_text)s)
or search_text %% %(query_text)s
)
order by
embedding <=> %(query_embedding)s::vector asc,
ts_rank(search_tsv, websearch_to_tsquery('simple', %(query_text)s)) desc,
similarity(search_text, %(query_text)s) desc
limit %(limit)s
"""
rows = db.fetch_all(
sql,
{
"table_name": table_name,
"query_text": query.query_text,
"query_embedding": query_embedding,
"limit": limit,
},
)
return [
RetrievalHit(
table_name=row["table_name"],
record_id=row["record_id"],
score=float(row["vector_score"] or 0) + float(row["text_score"] or 0) + float(row["trigram_score"] or 0),
title=row["title"],
content=row["content"],
metadata={
**row["metadata"],
"query_text": query.query_text,
"query_terms": query.query_terms,
},
)
for row in rows
]
def retrieve_with_trace(db, embedder, trace: ChunkTrace, query: RetrieverQuery) -> dict:
hits = retrieve_for_query(db, embedder, query)
return {
"trace": trace,
"retriever_query": query,
"hits": hits,
}上記のコードで重要な点は次のとおりです。
- Retriever 検索関数は
source_file、chunk_id、chunk_orderを検索条件として受け取らない。 - Retriever はコード片で作成した
query_embeddingでベクトル検索を行い、query.query_textはキーワード/トライグラムによる補助検索に使用する。 query_embeddingはraw_chunk_text、API 候補、シンボル候補、プロンプト意図語を組み合わせて作成する。query.query_textはファイル内部のコード片から抽出した API、シンボル、識別子、プロンプト意図語で作成する。- ファイルパスとチャンク順序は
ChunkTraceにのみ残す。 retrieve_with_traceは検索後の結果とトレースを結合するオーケストレータ関数である。検索自体はretrieve_for_queryが行う。docs_chunks、api_mapping、label_prototypesは同じRetrieverQueryで検索する。
godot_rag.dynamic_retrieval_view は新しい判断ロジックではなく、検索用の平坦化ビューです。3 つのテーブルのペイロード構造が異なっていても、Retriever 側では同じカラムが見えるようにする。
dynamic_retrieval_view
table_name
id
title
content
embedding
search_text
search_tsv
metadata検索対象は次のように展開します。ここでも source_file、source_url のような出典フィールドは metadata として保持しますが、基本検索文字列には入れません。検索はコード片の API/シンボル/パターンで行う必要があります。
| テーブル | title |
content |
search_text に入る値 |
|---|---|---|---|
docs_chunks |
文書タイトル、セクションパス、シンボル | 文書本文チャンク | doc_type、symbol、section_path、content、api_symbols |
api_mapping |
変更された API または以前の API 名 | 移行説明、変更理由、例示 | old_symbol、new_symbol、change_type、description、before_code、after_code |
label_prototypes |
プロトタイプ名またはラベル | 関数の使用方法、引数構成、呼び出しパターン変更例 | label、input_pattern、prompt_pattern、before_code、after_code、expected_response、explanation |
例示 SQL view 形式:
create or replace view godot_rag.dynamic_retrieval_view as
select
'docs_chunks' as table_name,
id,
coalesce(payload->>'title', payload->>'symbol', source_file) as title,
coalesce(payload->>'content', '') as content,
embedding,
concat_ws(
' ',
payload->>'doc_type',
payload->>'symbol',
payload->>'section_path',
payload->>'content',
payload->>'api_symbols'
) as search_text,
search_tsv,
payload || jsonb_build_object('source_file', source_file, 'source_url', payload->>'source_url') as metadata
from godot_rag.docs_chunks
union all
select
'api_mapping' as table_name,
id,
concat_ws(' -> ', payload->>'old_symbol', payload->>'new_symbol') as title,
concat_ws(' ', payload->>'description', payload->>'rationale') as content,
embedding,
concat_ws(
' ',
payload->>'old_symbol',
payload->>'new_symbol',
payload->>'change_type',
payload->>'description',
payload->>'before_code',
payload->>'after_code'
) as search_text,
search_tsv,
payload || jsonb_build_object('source_file', source_file, 'source_url', payload->>'source_url') as metadata
from godot_rag.api_mapping
union all
select
'label_prototypes' as table_name,
id,
coalesce(payload->>'label', payload->>'name', source_file) as title,
concat_ws(' ', payload->>'expected_response', payload->>'explanation') as content,
embedding,
concat_ws(
' ',
payload->>'label',
payload->>'input_pattern',
payload->>'prompt_pattern',
payload->>'before_code',
payload->>'after_code',
payload->>'expected_response',
payload->>'explanation'
) as search_text,
search_tsv,
payload || jsonb_build_object('source_file', source_file) as metadata
from godot_rag.label_prototypes;このビュー上で Retriever が実際に検索する値は query_text です。例えば _process チャンクの場合、次のように検索します。
query_text =
"Input.is_action_pressed Vector2.ZERO velocity.normalized position.clamp Vector2 AnimatedSprite2D _process Godot 3 Godot 4 migration"検索結果は LLM にそのまま信じて入れる正解ではなく、候補となる根拠です。LLM の判断リクエストには chunk と検索候補を一緒に入れ、Qwen 3.6 が「この根拠が現在の chunk と関連しているか」を再検証します。
送信デバッグ基準
AI が .gd ファイルから「主要な関数だけ」を任意に選んで送信したり、逆にファイル全体を一つの LLM リクエストとして送ることを防ぐため、送信前後を diff で確認できる必要があります。確認対象は「原文ファイル全体が LLM に送られたか」ではなく、「原文ファイルから作成された関数/コード片が元の順序通りに漏れなく追跡され、各片が Retriever リクエストに渡されたか」なのです。
ファイル展開段階ではまずパス別に原文ブロックを作成します。
# player.gd
extends Area2D
signal hit
@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
func _ready():
screen_size = get_viewport_rect().size
hide()
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if Input.is_action_pressed(&"move_down"):
velocity.y += 1
if Input.is_action_pressed(&"move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite2D.play()
else:
$AnimatedSprite2D.stop()
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
if velocity.x != 0:
$AnimatedSprite2D.animation = &"right"
$AnimatedSprite2D.flip_v = false
$Trail.rotation = 0
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite2D.animation = &"up"
rotation = PI if velocity.y > 0 else 0
func start(pos):
position = pos
rotation = 0
show()
$CollisionShape2D.disabled = false
func _on_body_entered(_body):
hide() # Player disappears after being hit.
hit.emit()
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred(&"disabled", true)次に AST Parser が作成した断片リストを残す。
ast_chunk_trace
source_file: "player.gd"
source_kind: "gdscript"
source_sha256: "87f4fcf7481dba031f74a363475cee75b81c3d42eb1347b318f6b824e37329a6"
chunks:
- chunk_order: 1
chunk_id: "player.gd:class_extends:1"
node_kind: "class_extends"
code_text: "extends Area2D"
- chunk_order: 2
chunk_id: "player.gd:signal:hit"
node_kind: "signal"
code_text: "signal hit"
- chunk_order: 3
chunk_id: "player.gd:function:_ready"
node_kind: "function"
code_text: |
func _ready():
screen_size = get_viewport_rect().size
hide()
- chunk_order: 4
chunk_id: "player.gd:function:_process"
node_kind: "function"
code_text: |
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if Input.is_action_pressed(&"move_down"):
velocity.y += 1
if Input.is_action_pressed(&"move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite2D.play()
else:
$AnimatedSprite2D.stop()
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
if velocity.x != 0:
$AnimatedSprite2D.animation = &"right"
$AnimatedSprite2D.flip_v = false
$Trail.rotation = 0
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite2D.animation = &"up"
rotation = PI if velocity.y > 0 else 0
- chunk_order: 5
chunk_id: "player.gd:function:start"
node_kind: "function"
code_text: |
func start(pos):
position = pos
rotation = 0
show()
$CollisionShape2D.disabled = false
- chunk_order: 6
chunk_id: "player.gd:function:_on_body_entered"
node_kind: "function"
code_text: |
func _on_body_entered(_body):
hide() # Player disappears after being hit.
hit.emit()
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred(&"disabled", true)Retriever送信直前のペイロードもチャンク単位で残す。
retrieval_payload
source_file: "player.gd"
chunk_order: 4
chunk_id: "player.gd:function:_process"
chunk_text: |
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if Input.is_action_pressed(&"move_down"):
velocity.y += 1
if Input.is_action_pressed(&"move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite2D.play()
else:
$AnimatedSprite2D.stop()
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
if velocity.x != 0:
$AnimatedSprite2D.animation = &"right"
$AnimatedSprite2D.flip_v = false
$Trail.rotation = 0
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite2D.animation = &"up"
rotation = PI if velocity.y > 0 else 0検証規則:
expanded_source_block["player.gd"].sha256 == ast_chunk_trace.source_sha256
ast_chunk_trace.chunks[].chunk_order is strictly increasing
retrieval_payload.chunk_id exists in ast_chunk_trace.chunks[].chunk_id
retrieval_payload.chunk_order matches ast_chunk_trace.chunks[].chunk_orderこの検証が失敗した場合、送信しません。この規則は .gd ファイルで特に重要です。意図は .gd ファイル全体を LLM に入れるのではなく、.gd から出た関数/コード片が元のパスと元の順序を失わずに Retriever に渡るかを確認することです。
このプロジェクトを入力として受け取ると、まず人が確認可能な平文の束に展開します。以下のブロックはバイナリアセットを除いたテキストファイルの原文全体を # <relative/path> 単位で示します。ここには .md、LICENSE のようにソースコード解析では除外候補となるファイルも含まれています。文書の例では相対パス内の内容を一部省略せずに残して追跡基準を確認します。ただし、実装で除外ポリシーが適用される場合、除外されたファイルは Retriever に送らず、excluded_files に理由とともに表示します。
# .gitignore
.import
logs/
# LICENSE
MIT License
Copyright (c) 2017 KidsCanCode
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# README.md
# Dodge the Creeps
This is a simple game where your character must move
and avoid the enemies for as long as possible.
This is a finished version of the game featured in the
["Your first 2D game"](https://docs.godotengine.org/en/latest/getting_started/first_2d_game/index.html)
tutorial in the documentation. For more details,
consider following the tutorial in the documentation.
Language: GDScript
Renderer: Compatibility
> [!NOTE]
>
> There is a C# version available [here](https://github.com/godotengine/godot-demo-projects/tree/master/mono/dodge_the_creeps).
Check out this demo on the asset library: https://godotengine.org/asset-library/asset/2712
## Screenshots


## Copying
`art/House In a Forest Loop.ogg` Copyright © 2012 [HorrorPen](https://opengameart.org/users/horrorpen), [CC-BY 3.0: Attribution](https://creativecommons.org/licenses/by/3.0/). Source: https://opengameart.org/content/loop-house-in-a-forest
Images are from "Abstract Platformer". Created in 2016 by kenney.nl, [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/). Source: https://www.kenney.nl/assets/abstract-platformer
Font is "Xolonium". Copyright © 2011-2016 Severin Meyer <sev.ch@web.de>, with Reserved Font Name Xolonium, SIL open font license version 1.1. Details are in `fonts/LICENSE.txt`.
# art/House In a Forest Loop.ogg.import
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://sgfduhhw4pno"
path="res://.godot/imported/House In a Forest Loop.ogg-1a6a72ae843ad792b7039931227e8d50.oggvorbisstr"
[deps]
source_file="res://art/House In a Forest Loop.ogg"
dest_files=["res://.godot/imported/House In a Forest Loop.ogg-1a6a72ae843ad792b7039931227e8d50.oggvorbisstr"]
[params]
loop=true
loop_offset=0.0
bpm=0.0
beat_count=0
bar_beats=4
# art/enemyFlyingAlt_1.png.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dun74wipekpfq"
path="res://.godot/imported/enemyFlyingAlt_1.png-559f599b16c69b112c1b53f6332e9489.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/enemyFlyingAlt_1.png"
dest_files=["res://.godot/imported/enemyFlyingAlt_1.png-559f599b16c69b112c1b53f6332e9489.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# art/enemyFlyingAlt_2.png.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://vusf51hepduk"
path="res://.godot/imported/enemyFlyingAlt_2.png-31dc7310eda6e1b721224f3cd932c076.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/enemyFlyingAlt_2.png"
dest_files=["res://.godot/imported/enemyFlyingAlt_2.png-31dc7310eda6e1b721224f3cd932c076.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# art/enemySwimming_1.png.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d182mv7y80xqy"
path="res://.godot/imported/enemySwimming_1.png-dd0e11759dc3d624c8a704f6e98a3d80.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/enemySwimming_1.png"
dest_files=["res://.godot/imported/enemySwimming_1.png-dd0e11759dc3d624c8a704f6e98a3d80.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# art/enemySwimming_2.png.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dmgglhdyowipd"
path="res://.godot/imported/enemySwimming_2.png-4c0cbc0732264c4ea3290340bd4a0a62.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/enemySwimming_2.png"
dest_files=["res://.godot/imported/enemySwimming_2.png-4c0cbc0732264c4ea3290340bd4a0a62.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# art/enemyWalking_1.png.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dgwhuvn7qb4iy"
path="res://.godot/imported/enemyWalking_1.png-5af6eedbe61b701677d490ffdc1e6471.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/enemyWalking_1.png"
dest_files=["res://.godot/imported/enemyWalking_1.png-5af6eedbe61b701677d490ffdc1e6471.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# art/enemyWalking_2.png.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dyw702efe6meu"
path="res://.godot/imported/enemyWalking_2.png-67c480ed60c35e95f5acb0436246b935.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/enemyWalking_2.png"
dest_files=["res://.godot/imported/enemyWalking_2.png-67c480ed60c35e95f5acb0436246b935.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# art/gameover.wav.import
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://td2mgko63p61"
path="res://.godot/imported/gameover.wav-98c95c744b35280048c2bd093cf8a356.sample"
[deps]
source_file="res://art/gameover.wav"
dest_files=["res://.godot/imported/gameover.wav-98c95c744b35280048c2bd093cf8a356.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=true
edit/normalize=true
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
# art/playerGrey_up1.png.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bcow5c46vixno"
path="res://.godot/imported/playerGrey_up1.png-6bd114d0a6beac91f48e3a7314d44564.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/playerGrey_up1.png"
dest_files=["res://.godot/imported/playerGrey_up1.png-6bd114d0a6beac91f48e3a7314d44564.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# art/playerGrey_up2.png.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dw3lwgwhpbfx8"
path="res://.godot/imported/playerGrey_up2.png-d6aba85f5f2675ebc7045efa7552ee79.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/playerGrey_up2.png"
dest_files=["res://.godot/imported/playerGrey_up2.png-d6aba85f5f2675ebc7045efa7552ee79.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# art/playerGrey_walk1.png.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b2aofu01vxvea"
path="res://.godot/imported/playerGrey_walk1.png-c4773fe7a7bf85d7ab732eb4458c2742.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/playerGrey_walk1.png"
dest_files=["res://.godot/imported/playerGrey_walk1.png-c4773fe7a7bf85d7ab732eb4458c2742.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# art/playerGrey_walk2.png.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ddjou2q6gxlfr"
path="res://.godot/imported/playerGrey_walk2.png-34d2d916366100182d08037c51884043.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://art/playerGrey_walk2.png"
dest_files=["res://.godot/imported/playerGrey_walk2.png-34d2d916366100182d08037c51884043.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# fonts/FONTLOG.txt
Please distribute this file along with the Xolonium fonts when possible.
Source
Find the sourcefiles of Xolonium at
<gitlab.com/sev/xolonium>
Credits
Xolonium is created with FontForge <fontforge.org>,
Inkscape <inkscape.org>, Python <python.org>, and
FontTools <github.com/fonttools>.
It originated as a custom font for the open-source
game Xonotic <xonotic.org>. With many thanks to the
Xonotic community for your support.
Supported OpenType features
case Provides case sensitive placement of punctuation,
brackets, and math symbols for uppercase text.
frac Replaces number/number sequences with diagonal fractions.
Numbers that touch a slash should not exceed 10 digits.
kern Provides kerning for Latin, Greek, and Cyrillic scripts.
locl Dutch: Replaces j with a stressed version if it follows í.
Sami: Replaces n-form Eng with the preferred N-form version.
Romanian and Moldovan: Replaces ŞşŢţ with the preferred ȘșȚț.
pnum Replaces monospaced digits with proportional versions.
sinf Replaces digits with scientific inferiors below the baseline.
subs Replaces digits with subscript versions on the baseline.
sups Replaces digits with superscript versions.
zero Replaces zero with a slashed version.
Supported glyph sets
Adobe Latin 3
OpenType W1G
ISO 8859-1 Western European
ISO 8859-2 Central European
ISO 8859-3 South European
ISO 8859-4 North European
ISO 8859-5 Cyrillic
ISO 8859-7 Greek
ISO 8859-9 Turkish
ISO 8859-10 Nordic
ISO 8859-13 Baltic Rim
ISO 8859-14 Celtic
ISO 8859-15 Western European
ISO 8859-16 South-Eastern European
Available glyphs
!"#$%&'()*+,-./0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
`abcdefghijklmnopqrstuvwxyz{|}~
¡¢£¤¥¦§¨©ª«¬ ®¯°±²³´µ¶·¸¹º»¼½¾¿
ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß
àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğ
ĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľ
ĿŀŁłŃńŅņŇňŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞş
ŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽž
ƒǺǻǼǽǾǿȘșȚțȷ
ˆˇˉ˘˙˚˛˜˝
ͺ;΄΅Ά·ΈΉΊΌΎΏΐ
ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩΪΫάέήίΰ
αβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ
ЀЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОП
РСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмноп
рстуфхцчшщъыьэюяѐёђѓєѕіїјљњћќѝўџ
ѢѣѲѳѴѵҐґҒғҔҕҖҗҘҙҚқҜҝҞҟҠҡҢңҤҥҦҧҨҩ
ҪҫҬҭҮүҰұҲҳҴҵҶҷҸҹҺһҼҽӀӁӂӇӈӋӌӏӐӑӒӓ
ӔӕӖӗӘәӜӝӞӟӠӡӢӣӤӥӦӧӨөӮӯӰӱӲӳӴӵӶӷӸӹ
Ԥԥ
ḂḃḊḋḞḟṀṁṖṗṠṡṪṫẀẁẂẃẄẅẞỲỳ
‒–—―‘’‚‛“”„‟†‡•…‰′″‹›‽‾⁄
⁰⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ⁿ₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎
₤₦₩₫€₯₱₹₺₽₿
℅ℓ№℗™Ω℮
⅛⅜⅝⅞
←↑→↓
∂∆∏∑−∕∙√∞∟∫≈≠≤≥
⌖
■▬▮▰▲▶▼◀◆◊●◢◣◤◥
☄★☠☢☣⚙⚛⚠⚡⛔
❇❈❌❤❰❱❲❳
fffiflffiffl
🌌🌍🌎🌏👽💣🔥🔫
😁😃😄😆😇😈😉😊😎😐😒😕😘
😛😝😞😟😠😣😭😮😲😴😵
🚀
Debugging glyphs
U+EFFD Font version
U+F000 Font hinting indicator
Changelog
Xolonium 4.1 2016-11-22 Severin Meyer <sev.ch@web.de>
Reverted frac OpenType feature to a more stable implementation
Xolonium 4.0 2016-10-08 Severin Meyer <sev.ch@web.de>
Decreased width of most glyphs
Thinner vertical stems in Xolonium-Regular
Thicker horizontal stems in Xolonium-Bold
Revised diagonal stems
Lowered middle bars
Revised diacritical bars
Added glyphs:
ӏẞ₿
U+2007 U+2008 U+2009 U+200A U+202F
U+EFFD U+F000
Revised glyphs:
$&,JKQRXkwxy~¢¤ßǻ˜ζκλμξφЖУжћѴѵ∕₱₺₦₩€ℓ№≈ffffiffl
❤🌍🌎🌏😁😄😇😈😉😊😘😭😮😴🚀
Removed uncommon glyphs:
ʼnſʼҌҍҎҏҾҿӃӄӇӈӚӛӪӫӬӭ
U+0312 U+0313 U+0326
Simplified OpenType features pnum, zero, and case
Removed OpenType feature dlig
Revised vertical metrics
Merged outlines of composite glyphs in otf version
Added ttf version with custom outlines and instructions
Added woff and woff2 version
Xolonium 3.1 2015-06-10 Severin Meyer <sev.ch@web.de>
Added currency glyphs:
₦₩₫₱₹₺₽
Revised glyph:
₯
Relicensed public release under the SIL Open Font License 1.1
Xolonium 3.0 2015-05-04 Severin Meyer <sev.ch@web.de>
Decreased width of glyphs
Decreased descender height
Increased height of super/subscript glyphs
Revised width of dashes, underscore, and overscore
Sharper bends with more circular proportions
Decreased stroke thickness of mathematical glyphs
Revised diacritical marks
Revised diacritical bars
Revised Cyrillic hooks
Revised glyphs:
GQRYjmuwßŊŒſƒǻfffiffiffl
ΞΨΩδζιξπςστυφω
ЉЄДЛУЭЯбдлэяєљђєћѢѣҨҩҼҽӃӄӘә
#$&'()*,/69?@[]{}~¡£¤¥§©®¿
‹›₤€₯ℓ№℗℮←↑→↓∂∏∑∞≈▰☄❈❰❱❲❳😝
Raised vertical position of mathematical glyphs
Unified advance width of numeral and monetary glyphs
Unified advance width of mathematical glyphs
Revised bearings
Rewrote kern feature
Bolder Xolonium-Bold with improved proportions
Updated glyph names to conform to the AGLFN 1.7
Revised hints and PS Private Dictionary
Added glyphs:
ӶӷԤԥ
Added OpenType features:
case frac liga locl pnum sinf subs sups zero
Xolonium 2.4 2014-12-23 Severin Meyer <sev.ch@web.de>
Added dingbats:
⛔💣🔥
Revised size and design of emoticons
Revised dingbats:
⌖☄☠☣⚙⚛⚠⚡❇❈🌌🌍🌎🌏🔫
Removed dingbat:
💥
Xolonium 2.3 2014-08-14 Severin Meyer <sev.ch@web.de>
Bugfixed ε and έ, thanks to bowzee for the feedback
Xolonium 2.2 2014-03-01 Severin Meyer <sev.ch@web.de>
Added dingbats:
⌖◆●❌💥
Revised dingbats:
•←↑→↓◊☄★☠☣⚙⚛⚠⚡❇❈❤🌌🌍🌎🌏👽🔫🚀
Removed dingbats:
♻✪💡📡🔋🔧🔭
Xolonium 2.1 2013-10-20 Severin Meyer <sev.ch@web.de>
Added dingbats:
←↑→↓❰❱❲❳■▬▮▰▲▶▼◀◢◣◤◥
☄★☠☢☣♻⚙⚛⚠⚡✪❇❈❤
🌌🌍🌎🌏👽💡📡🔋🔧🔫🔭🚀
😁😃😄😆😇😈😉😊😎😐😒😕
😘😛😝😞😟😠😣😭😮😲😴😵
Xolonium 2.0.1 2013-07-12 Severin Meyer <sev.ch@web.de>
Reorganised and simplified files
Xolonium 2.0 2012-08-11 Severin Meyer <sev.ch@web.de>
Revised bends
Revised thickness of uppercase diagonal stems
Revised diacritical marks
Revised hints and PS Private Dictionary
Revised glyphs:
*1469@DPRly{}§©®¶ÐÞƒΘΞαεζνξνυЄЉЊ
ЏБЗЛУЧЪЫЬЭЯбзлчъыьэяєљњџ•€∂∙√∞∫≠
Completed glyph sets:
Adobe Latin 3
OpenType World Glyph Set 1 (W1G)
Ghostscript Standard (ghostscript-fonts-std-8.11)
Added OpenType kern feature
Added Xolonium-Bold
Xolonium 1.2 2011-02-12 Severin Meyer <sev.ch@web.de>
Revised glyphs:
D·Ðı
Completed glyph sets:
ISO 8859-7 (Greek)
Unicode Latin Extended-A block
Added glyphs:
†‡•…‰⁄™∂∑−√∞≠≤≥
Xolonium 1.1 2011-01-17 Severin Meyer <sev.ch@web.de>
Revised placement of cedilla and ogonek in accented glyphs
Revised glyphs:
,;DKTjkvwxy¥§Ð˛€
Completed glyph sets:
ISO 8859-2 (Central European)
ISO 8859-3 (South European, Esperanto)
ISO 8859-4 (North European)
ISO 8859-5 (Cyrillic)
ISO 8859-9 (Turkish)
ISO 8859-10 (Nordic)
ISO 8859-13 (Baltic Rim)
ISO 8859-14 (Celtic)
ISO 8859-16 (South-Eastern European)
Added glyphs:
ȷʼ̒ ЀЍѐѝ‒–—‘’‚‛“”„‟‹›
Xolonium 1.0 2011-01-04 Severin Meyer <sev.ch@web.de>
Completed glyph sets:
ISO 8859-1 (Western European)
ISO 8859-15 (Western European)
Added glyphs:
ĄĆĘŁŃŚŹŻąćęłńśźżıˆˇ˙˚˛˜
# fonts/LICENSE.txt
Copyright 2011-2016 Severin Meyer <sev.ch@web.de>,
with Reserved Font Name Xolonium.
This Font Software is licensed under the SIL Open Font License,
Version 1.1. This license is copied below, and is also available
with a FAQ at <http://scripts.sil.org/OFL>
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
# fonts/Xolonium-Regular.ttf.import
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://bgv586r20ps8e"
path="res://.godot/imported/Xolonium-Regular.ttf-bc2981e3069cff4c34dd7c8e2bb73fba.fontdata"
[deps]
source_file="res://fonts/Xolonium-Regular.ttf"
dest_files=["res://.godot/imported/Xolonium-Regular.ttf-bc2981e3069cff4c34dd7c8e2bb73fba.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
modulate_color_glyphs=false
hinting=1
subpixel_positioning=4
keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}
# hud.gd
extends CanvasLayer
signal start_game
func show_message(text):
$MessageLabel.text = text
$MessageLabel.show()
$MessageTimer.start()
func show_game_over():
show_message("Game Over")
await $MessageTimer.timeout
$MessageLabel.text = "Dodge the\nCreeps"
$MessageLabel.show()
await get_tree().create_timer(1).timeout
$StartButton.show()
func update_score(score):
$ScoreLabel.text = str(score)
func _on_StartButton_pressed():
$StartButton.hide()
start_game.emit()
func _on_MessageTimer_timeout():
$MessageLabel.hide()
# hud.gd.uid
uid://c1g57034r2c0
# hud.tscn
[gd_scene format=3 uid="uid://b0efehuavobda"]
[ext_resource type="Script" uid="uid://c1g57034r2c0" path="res://hud.gd" id="1"]
[ext_resource type="FontFile" uid="uid://bgv586r20ps8e" path="res://fonts/Xolonium-Regular.ttf" id="2_2jm3i"]
[sub_resource type="InputEventAction" id="InputEventAction_fopy7"]
action = &"start_game"
[sub_resource type="Shortcut" id="4"]
events = [SubResource("InputEventAction_fopy7")]
[node name="HUD" type="CanvasLayer" unique_id=126421993]
script = ExtResource("1")
[node name="ScoreLabel" type="Label" parent="." unique_id=1314826100]
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 78.0
grow_horizontal = 2
theme_override_fonts/font = ExtResource("2_2jm3i")
theme_override_font_sizes/font_size = 60
text = "0"
horizontal_alignment = 1
[node name="MessageLabel" type="Label" parent="." unique_id=1611528703]
anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_top = -79.5
offset_bottom = 79.5
grow_horizontal = 2
grow_vertical = 2
theme_override_fonts/font = ExtResource("2_2jm3i")
theme_override_font_sizes/font_size = 60
text = "Dodge the
Creeps"
horizontal_alignment = 1
[node name="StartButton" type="Button" parent="." unique_id=1561548516]
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -90.0
offset_top = -200.0
offset_right = 90.0
offset_bottom = -100.0
grow_horizontal = 2
grow_vertical = 0
theme_override_fonts/font = ExtResource("2_2jm3i")
theme_override_font_sizes/font_size = 60
shortcut = SubResource("4")
text = "Start"
[node name="MessageTimer" type="Timer" parent="." unique_id=1675980570]
one_shot = true
[connection signal="pressed" from="StartButton" to="." method="_on_StartButton_pressed"]
[connection signal="timeout" from="MessageTimer" to="." method="_on_MessageTimer_timeout"]
# icon.webp.import
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dfklrdtaun0xt"
path="res://.godot/imported/icon.webp-e94f9a68b0f625a567a797079e4d325f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.webp"
dest_files=["res://.godot/imported/icon.webp-e94f9a68b0f625a567a797079e4d325f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
# main.gd
extends Node
@export var mob_scene: PackedScene
var score
func game_over():
$ScoreTimer.stop()
$MobTimer.stop()
$HUD.show_game_over()
$Music.stop()
$DeathSound.play()
func new_game():
get_tree().call_group(&"mobs", &"queue_free")
score = 0
$Player.start($StartPosition.position)
$StartTimer.start()
$HUD.update_score(score)
$HUD.show_message("Get Ready")
$Music.play()
func _on_MobTimer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instantiate()
# Choose a random location on Path2D.
var mob_spawn_location = get_node(^"MobPath/MobSpawnLocation")
mob_spawn_location.progress_ratio = randf()
# Set the mob's position to a random location.
mob.position = mob_spawn_location.position
# Set the mob's direction perpendicular to the path direction.
var direction = mob_spawn_location.rotation + PI / 2
# Add some randomness to the direction.
direction += randf_range(-PI / 4, PI / 4)
mob.rotation = direction
# Choose the velocity for the mob.
var velocity = Vector2(randf_range(150.0, 250.0), 0.0)
mob.linear_velocity = velocity.rotated(direction)
# Spawn the mob by adding it to the Main scene.
add_child(mob)
func _on_ScoreTimer_timeout():
score += 1
$HUD.update_score(score)
func _on_StartTimer_timeout():
$MobTimer.start()
$ScoreTimer.start()
# main.gd.uid
uid://c4wt6ace7hycd
# main.tscn
[gd_scene format=3 uid="uid://bggkaprn62fwm"]
[ext_resource type="Script" uid="uid://c4wt6ace7hycd" path="res://main.gd" id="1_0r6n5"]
[ext_resource type="PackedScene" uid="uid://cao351pllxqpa" path="res://mob.tscn" id="2_50pww"]
[ext_resource type="PackedScene" uid="uid://bwhlkliwp13p4" path="res://player.tscn" id="3_veqnc"]
[ext_resource type="PackedScene" uid="uid://b0efehuavobda" path="res://hud.tscn" id="4_0qnje"]
[ext_resource type="AudioStream" uid="uid://sgfduhhw4pno" path="res://art/House In a Forest Loop.ogg" id="5_55d8h"]
[ext_resource type="AudioStream" uid="uid://td2mgko63p61" path="res://art/gameover.wav" id="6_hp1r0"]
[sub_resource type="Curve2D" id="1"]
_data = {
"points": PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480, 0, 0, 0, 0, 0, 480, 720, 0, 0, 0, 0, 0, 720, 0, 0, 0, 0, 0, 0)
}
point_count = 5
[node name="Main" type="Node" unique_id=1975992027]
script = ExtResource("1_0r6n5")
mob_scene = ExtResource("2_50pww")
[node name="ColorRect" type="ColorRect" parent="." unique_id=569320965]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.219608, 0.372549, 0.380392, 1)
[node name="Player" parent="." unique_id=927660131 instance=ExtResource("3_veqnc")]
[node name="MobTimer" type="Timer" parent="." unique_id=228987391]
wait_time = 0.5
[node name="ScoreTimer" type="Timer" parent="." unique_id=451982858]
[node name="StartTimer" type="Timer" parent="." unique_id=238316384]
wait_time = 2.0
one_shot = true
[node name="StartPosition" type="Marker2D" parent="." unique_id=568887530]
position = Vector2(240, 450)
[node name="MobPath" type="Path2D" parent="." unique_id=694323229]
curve = SubResource("1")
[node name="MobSpawnLocation" type="PathFollow2D" parent="MobPath" unique_id=1080274542]
[node name="HUD" parent="." unique_id=1879130737 instance=ExtResource("4_0qnje")]
[node name="Music" type="AudioStreamPlayer" parent="." unique_id=267371681]
stream = ExtResource("5_55d8h")
[node name="DeathSound" type="AudioStreamPlayer" parent="." unique_id=1715684712]
stream = ExtResource("6_hp1r0")
[connection signal="hit" from="Player" to="." method="game_over"]
[connection signal="timeout" from="MobTimer" to="." method="_on_MobTimer_timeout"]
[connection signal="timeout" from="ScoreTimer" to="." method="_on_ScoreTimer_timeout"]
[connection signal="timeout" from="StartTimer" to="." method="_on_StartTimer_timeout"]
[connection signal="start_game" from="HUD" to="." method="new_game"]
# mob.gd
extends RigidBody2D
func _ready():
var mob_types = Array($AnimatedSprite2D.sprite_frames.get_animation_names())
$AnimatedSprite2D.animation = mob_types.pick_random()
$AnimatedSprite2D.play()
func _on_VisibilityNotifier2D_screen_exited():
queue_free()
# mob.gd.uid
uid://cypxpb8arjrqt
# mob.tscn
[gd_scene format=3 uid="uid://cao351pllxqpa"]
[ext_resource type="Script" uid="uid://cypxpb8arjrqt" path="res://mob.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://dun74wipekpfq" path="res://art/enemyFlyingAlt_1.png" id="2"]
[ext_resource type="Texture2D" uid="uid://vusf51hepduk" path="res://art/enemyFlyingAlt_2.png" id="3"]
[ext_resource type="Texture2D" uid="uid://dgwhuvn7qb4iy" path="res://art/enemyWalking_1.png" id="4"]
[ext_resource type="Texture2D" uid="uid://dyw702efe6meu" path="res://art/enemyWalking_2.png" id="5"]
[ext_resource type="Texture2D" uid="uid://d182mv7y80xqy" path="res://art/enemySwimming_1.png" id="6"]
[ext_resource type="Texture2D" uid="uid://dmgglhdyowipd" path="res://art/enemySwimming_2.png" id="7"]
[sub_resource type="SpriteFrames" id="1"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": ExtResource("2")
}, {
"duration": 1.0,
"texture": ExtResource("3")
}],
"loop": true,
"name": &"fly",
"speed": 3.0
}, {
"frames": [{
"duration": 1.0,
"texture": ExtResource("6")
}, {
"duration": 1.0,
"texture": ExtResource("7")
}],
"loop": true,
"name": &"swim",
"speed": 4.0
}, {
"frames": [{
"duration": 1.0,
"texture": ExtResource("4")
}, {
"duration": 1.0,
"texture": ExtResource("5")
}],
"loop": true,
"name": &"walk",
"speed": 4.0
}]
[sub_resource type="CapsuleShape2D" id="2"]
radius = 37.0
height = 100.0
[node name="Mob" type="RigidBody2D" unique_id=371809901 groups=["mobs"]]
collision_mask = 0
gravity_scale = 0.0
script = ExtResource("1")
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=1998522389]
scale = Vector2(0.75, 0.75)
sprite_frames = SubResource("1")
animation = &"walk"
[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=1880423722]
rotation = 1.5708
shape = SubResource("2")
[node name="VisibleOnScreenNotifier2D" type="VisibleOnScreenNotifier2D" parent="." unique_id=959995349]
[connection signal="screen_exited" from="VisibleOnScreenNotifier2D" to="." method="_on_VisibilityNotifier2D_screen_exited"]
# player.gd
extends Area2D
signal hit
@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
func _ready():
screen_size = get_viewport_rect().size
hide()
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if Input.is_action_pressed(&"move_down"):
velocity.y += 1
if Input.is_action_pressed(&"move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite2D.play()
else:
$AnimatedSprite2D.stop()
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
if velocity.x != 0:
$AnimatedSprite2D.animation = &"right"
$AnimatedSprite2D.flip_v = false
$Trail.rotation = 0
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite2D.animation = &"up"
rotation = PI if velocity.y > 0 else 0
func start(pos):
position = pos
rotation = 0
show()
$CollisionShape2D.disabled = false
func _on_body_entered(_body):
hide() # Player disappears after being hit.
hit.emit()
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred(&"disabled", true)
# player.gd.uid
uid://6s0lxctks3qn
# player.tscn
[gd_scene format=3 uid="uid://bwhlkliwp13p4"]
[ext_resource type="Script" uid="uid://6s0lxctks3qn" path="res://player.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://b2aofu01vxvea" path="res://art/playerGrey_walk1.png" id="2"]
[ext_resource type="Texture2D" uid="uid://ddjou2q6gxlfr" path="res://art/playerGrey_walk2.png" id="3"]
[ext_resource type="Texture2D" uid="uid://bcow5c46vixno" path="res://art/playerGrey_up1.png" id="4"]
[ext_resource type="Texture2D" uid="uid://dw3lwgwhpbfx8" path="res://art/playerGrey_up2.png" id="5"]
[sub_resource type="SpriteFrames" id="1"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": ExtResource("2")
}, {
"duration": 1.0,
"texture": ExtResource("3")
}],
"loop": true,
"name": &"right",
"speed": 5.0
}, {
"frames": [{
"duration": 1.0,
"texture": ExtResource("4")
}, {
"duration": 1.0,
"texture": ExtResource("5")
}],
"loop": true,
"name": &"up",
"speed": 5.0
}]
[sub_resource type="CapsuleShape2D" id="2"]
radius = 27.0
height = 68.0
[sub_resource type="Gradient" id="3"]
colors = PackedColorArray(1, 1, 1, 0.501961, 1, 1, 1, 0)
[sub_resource type="GradientTexture1D" id="4"]
gradient = SubResource("3")
[sub_resource type="Curve" id="5"]
_data = [Vector2(0.00501098, 0.5), 0.0, 0.0, 0, 0, Vector2(0.994989, 0.324), 0.0, 0.0, 0, 0]
point_count = 2
[sub_resource type="CurveTexture" id="6"]
curve = SubResource("5")
[sub_resource type="ParticleProcessMaterial" id="7"]
gravity = Vector3(0, 0, 0)
scale_curve = SubResource("6")
color_ramp = SubResource("4")
[node name="Player" type="Area2D" unique_id=2141725708]
z_index = 10
script = ExtResource("1")
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=1437394421]
scale = Vector2(0.5, 0.5)
sprite_frames = SubResource("1")
animation = &"right"
[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=1954506745]
shape = SubResource("2")
[node name="Trail" type="GPUParticles2D" parent="." unique_id=1747300857]
z_index = -1
amount = 10
texture = ExtResource("2")
speed_scale = 2.0
process_material = SubResource("7")
[connection signal="body_entered" from="." to="." method="_on_body_entered"]
# project.godot
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Dodge the Creeps"
config/description="This is a simple game where your character must move
and avoid the enemies for as long as possible.
This is a finished version of the game featured in the 'Your first 2D game'
tutorial in the documentation. For more details, consider
following the tutorial in the documentation."
config/tags=PackedStringArray("2d", "demo", "official")
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.6")
config/icon="res://icon.webp"
[display]
window/size/viewport_width=480
window/size/viewport_height=720
window/size/window_width_override=480
window/size/window_height_override=720
window/stretch/mode="canvas_items"
[input]
move_left={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
]
}
move_right={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":15,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
]
}
move_up={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
]
}
move_down={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
]
}
start_game={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
[rendering]
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"
# screenshots/.gdignore
バイナリ アセットは元の bytes を上記の平文本文には入れませんが、repository path order と除外/参照リストには必ず残します。例えば main.tscn が res://art/gameover.wav を指している場合、LLM 判定入力には main.tscn から出たチャンク、art/gameover.wav が参照されたパス メタデータ、Retriever 検索結果、ユーザー プロンプトが同じリクエスト オブジェクト内に入ります。バイナリ 元の bytes 自体は Retriever や LLM に送られません。
ファイル単位 伝達 例
平文 の束から # player.gd ブロックを取り除くと、次のように 1 つの analysis_request になります。
extends Area2D
signal hit
@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
func _ready():
screen_size = get_viewport_rect().size
hide()
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if Input.is_action_pressed(&"move_down"):
velocity.y += 1
if Input.is_action_pressed(&"move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite2D.play()
else:
$AnimatedSprite2D.stop()
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
if velocity.x != 0:
$AnimatedSprite2D.animation = &"right"
$AnimatedSprite2D.flip_v = false
$Trail.rotation = 0
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite2D.animation = &"up"
rotation = PI if velocity.y > 0 else 0
func start(pos):
position = pos
rotation = 0
show()
$CollisionShape2D.disabled = false
func _on_body_entered(_body):
hide() # Player disappears after being hit.
hit.emit()
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred(&"disabled", true)AST Parserには、上記のリクエスト全体ではなく、ソース解析に必要なフィールドだけを渡す。
ast_parse_input
project_id: "github:godotengine/godot-demo-projects/2d/dodge_the_creeps"
source_file: "player.gd"
source_kind: "gdscript"
source_text: |
extends Area2D
signal hit
@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
func _ready():
screen_size = get_viewport_rect().size
hide()
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if Input.is_action_pressed(&"move_down"):
velocity.y += 1
if Input.is_action_pressed(&"move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite2D.play()
else:
$AnimatedSprite2D.stop()
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
if velocity.x != 0:
$AnimatedSprite2D.animation = &"right"
$AnimatedSprite2D.flip_v = false
$Trail.rotation = 0
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite2D.animation = &"up"
rotation = PI if velocity.y > 0 else 0
func start(pos):
position = pos
rotation = 0
show()
$CollisionShape2D.disabled = false
func _on_body_entered(_body):
hide() # Player disappears after being hit.
hit.emit()
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred(&"disabled", true)
source_sha256: "87f4fcf7481dba031f74a363475cee75b81c3d42eb1347b318f6b824e37329a6"Parserはファイルをチャンクに分割します。実際の実装が確定するまで、次のレベルのチャンクを期待します。
ast_parse_output
source_file: "player.gd"
chunks:
- node_kind: "class_extends"
code_text: "extends Area2D"
symbol_candidates: ["Area2D"]
- node_kind: "signal"
code_text: "signal hit"
symbol_candidates: ["hit"]
- node_kind: "export_variable"
code_text: "@export var speed = 400"
symbol_candidates: ["@export", "speed"]
- node_kind: "function"
code_text: |
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if Input.is_action_pressed(&"move_down"):
velocity.y += 1
if Input.is_action_pressed(&"move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite2D.play()
else:
$AnimatedSprite2D.stop()
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
if velocity.x != 0:
$AnimatedSprite2D.animation = &"right"
$AnimatedSprite2D.flip_v = false
$Trail.rotation = 0
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite2D.animation = &"up"
rotation = PI if velocity.y > 0 else 0
api_call_candidates: ["Input.is_action_pressed", "Vector2.ZERO", "position.clamp"]
- node_kind: "function"
code_text: |
func _on_body_entered(_body):
hide() # Player disappears after being hit.
hit.emit()
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred(&"disabled", true)
api_call_candidates: ["hit.emit", "set_deferred"]各チャンクは再びRetrieverリクエストに渡されます。
retrieval_request
project_id: "github:godotengine/godot-demo-projects/2d/dodge_the_creeps"
source_file: "player.gd"
chunk_id: "player.gd:function:_process"
prompt_intent: "version_check_and_explain"
query_terms:
- "Input.is_action_pressed"
- "Vector2.ZERO"
- "position.clamp"
- "@export"
candidate_tables:
- "docs_chunks"
- "api_mapping"
- "label_prototypes"このフローは player.gd 固有の特別な処理ではありません。ただし、ファイルの種類に応じてチャンク生成段階が異なります。 main.gd、mob.gd、hud.gd は analysis_request -> ast_parse_input -> ast_parse_output.chunks[] -> retrieval_request を繰り返します。 project.godot、main.tscn のように AST Parser の対象でないファイルは analysis_request -> direct_chunk_output.chunks[] -> retrieval_request を繰り返します。
このサンプルプロジェクトで確認すべき繰り返し送信フローは次のとおりです。
- プロジェクトのルートを 1 つの
project_idとして扱う。 - Godot 関連のテキストファイルを相対パス基準で収集する。
- 各ファイルを
# <relative/path>ヘッダーが付いたプレーンテキストブロックとして展開する。 - 各プレーンテキストブロックを再びファイル単位の
analysis_requestに変換する。 - 各
analysis_requestでsource_sha256を計算する。 .gdファイルは AST Parser がast_parse_output.chunks[]を生成する。- AST Parser の対象でないインクルードファイルは直接チャンク化ツールが
direct_chunk_output.chunks[]を生成する。 - 各チャンクを基準に Retriever リクエストを作成する。
- Retriever の結果とチャンクを組み合わせて LLM 判定リクエストを繰り返す。
- すべての必要なファイル/チャンクリクエストが正常に応答したら、プロジェクト単位の判定候補として提出する。
つまり、1 つのプロジェクトは次のように複数のリクエストに分割されます。
dodge_the_creeps/project.godot
-> analysis_request
-> direct_chunk_output.chunks[]
-> retrieval_request per chunk
dodge_the_creeps/main.gd
-> analysis_request
-> ast_parse_output.chunks[]
-> retrieval_request per chunk
dodge_the_creeps/player.gd
-> analysis_request
-> ast_parse_output.chunks[]
-> retrieval_request per chunk
dodge_the_creeps/player.tscn
-> analysis_request
-> direct_chunk_output.chunks[]
-> retrieval_request per chunk
dodge_the_creeps/mob.gd
-> analysis_request
-> ast_parse_output.chunks[]
-> retrieval_request per chunk
dodge_the_creeps/mob.tscn
-> analysis_request
-> direct_chunk_output.chunks[]
-> retrieval_request per chunk
dodge_the_creeps/hud.gd
-> analysis_request
-> ast_parse_output.chunks[]
-> retrieval_request per chunk
dodge_the_creeps/hud.tscn
-> analysis_request
-> direct_chunk_output.chunks[]
-> retrieval_request per chunkこのとき、1つのファイルや1つのチャンクが失敗したからといって、プロジェクト全体をすぐに確定しません。失敗したリクエストは pending または retry の対象として残し、正常に応答したチャンクだけを蓄積します。プロジェクト単位の判断は、必要な繰り返しリクエストが十分に応答した後にのみ実行されます。
繰り返しリクエストの流れ
プロジェクトは1回のLLMリクエストで判定しません。
プロジェクトの入力は以下のように複数のリクエストに分割されます。
project
-> file requests
-> AST chunks or direct chunks
-> retrieval requests
-> LLM judgment requests
-> project-level aggregation各段階の繰り返し単位は次のとおりです。
| 段階 | 繰り返し単位 |
|---|---|
| ファイルスキャン | プロジェクト内のファイル |
| AST パーサー | 含まれる .gd ファイル |
| 直接断片化 | AST パーサーの対象でない含むファイル |
| 除外記録 | .md、バイナリ等の除外対象ファイル |
| Retriever | AST チャンクまたは direct チャンク |
| Qwen 3.6 判定 | AST/direct チャンク + 検索根拠 |
| プロジェクト分類 | チャンク判定結果全体 |
したがって、プロジェクト全体が真/偽で終わるのではなく、プロジェクト内の複数のファイルと複数の断片がそれぞれ正常に処理される必要があります。最終的なプロジェクト判定は、すべての必要な断片の Retriever/LLM/Validator 結果が揃って初めて行うことができます。
Qwen 3.6 はオンデマンドで動作する前提なので、1 回の大きなリクエストで完了させるよりも、AST チャンクまたは direct チャンク単位で複数回呼び出す方が適しています。このとき、すでに完了したチャンクは再利用し、失敗したり中断されたチャンクだけを再度リクエストします。
AST パーサーに渡すデータ
AST パーサーにはプロンプトを直接入れません。
パーサーへの入力は次のように最小化します。
ast_parse_input
project_id
source_file
source_kind
source_text
source_sha256このように分割する理由は次のとおりです。
- 同じソースコードを複数のプロンプトで再分析できる。
- プロンプトが変わっても AST パース結果は再利用できる。
- パーサが「質問意図」ゆえにソース構造を異なる解釈をしないようにする。
- その後の Retriever/LLM 段階でのみプロンプトを使用して判断方向を変えることができる。
AST パーサ出力
パーサはソース全体を直接 LLM に渡すよりも、追跡可能な断片リストを作成する。
ast_parse_output
project_id
source_file
source_sha256
parse_status
chunks[]各チャンクは次の情報を持っています。
chunk
chunk_id
source_file
span
node_kind
symbol_candidates
api_call_candidates
code_text
surrounding_context| 項目 | 説明 |
|---|---|
chunk_id |
プロジェクト/ファイル/範囲ベースの決定的 ID |
span |
line/column または byte offset の範囲 |
node_kind |
function、class、call、signal、property、scene_node など |
symbol_candidates |
クラス名、型名、メソッド名の候補 |
api_call_candidates |
Godot API 呼び出しの候補 |
code_text |
該当部分の元コード |
surrounding_context |
判断に必要な周辺コード。全ファイルではなく必要な範囲だけを保持する。 |
直接チャンク出力
AST パーサーの対象でないインクルードファイルは direct_chunk_output を作成する。この出力は AST ではなく、テキスト/設定/シーン構造を元の順序で分割した結果である。
direct_chunk_output
project_id
source_file
source_sha256
chunk_status
chunks[]各チャンクは次の情報を持っています。
direct_chunk
chunk_id
source_file
chunk_order
span
chunk_kind
chunk_text
reference_candidates例:
direct_chunk
source_file: "main.tscn"
chunk_order: 3
chunk_kind: "ext_resource"
chunk_text: "[ext_resource type=\"Script\" uid=\"uid://c4wt6ace7hycd\" path=\"res://main.gd\" id=\"1_0r6n5\"]"
reference_candidates:
- "res://main.gd".md のように除外することにしたファイルは direct_chunk_output を作成せず、excluded_files に残す。
プロンプトとチャンクの結合
プロンプトは Parser の後で再結合される。
analysis_request.prompt
+ ast chunk または direct chunk
+ project/file metadata
-> retrieval_request| プロンプトの意図 | 優先 Retriever |
|---|---|
| “このコードは何を意味していますか?” | docs_chunks |
| “Godot 4への変換が必要ですか?” | api_mapping, label_prototypes |
| “このコードは Godot 3 ですか 4 ですか?” | docs_chunks, api_mapping, label_prototypes のすべてが候補 |
この段階でも特定のテーブルだけを特別扱いしません。プロンプトと AST 断片の性格に応じて、必要な公式ドキュメント JSONL の根拠を探します。
LLM に渡す単位
Qwen 3.6 では、元のプロジェクト全体を一度に渡しません。
LLM 呼び出し単位は次の束です。
llm_judgment_request
prompt
project_id
source_file
chunk_id
chunk_order
chunk_kind
chunk_text
surrounding_context
retrieved_evidence
judgment_contractここで judgment_contract はまだ最終的な JSONL スキーマではありません。現在の段階では、LLM が何を判断すべきかを示すリクエスト契約です。
retrieved_evidence はまだ最終的な保存スキーマではなく、検索結果の束を指す抽象フィールドです。保存段階では、根拠の種類に応じて docs_chunks の根拠 ID、api_mapping の根拠 ID、label_prototypes の根拠 ID のように分割される可能性があります。この文書では「検索された公式ドキュメント JSONL 根拠束」という意味でのみ使用します。
例:
判断すべきこと:
- このコード片が Godot 3 API を使用しているか
- Godot 4 基準で有効か
- 移行の根拠があるか
- 検索された公式ドキュメントの根拠が実際のコードと関連しているか再利用境界
同じソースコードでもプロンプトが異なれば Retriever/LLM の判断は変わる可能性がある。
逆に同じソースコードであれば AST パーサの結果は再利用できる。
| 段階 | 再利用基準 |
|---|---|
| AST parse | source_sha256, source_kind, Parser version |
| chunk 生成 | source_sha256, chunking version |
| Retriever 検索 | prompt intent, chunk symbols, retrieval version |
| LLM 判断 | prompt, chunk, evidence, model, prompt version |
この境界を守れば「ソース構造分析」と「質問意図に基づく判断」が混ざらない。
中断/再開の観点
分析要求が長くなる可能性があるため、各段階は再開可能であるべきだ。
最小保存単位:
- ファイルスキャン完了の有無
- ファイル別 AST parse 状態
- chunk 生成状態
- chunk 別 Retriever 検索状態
- chunk 別 LLM 判断状態
- Validator 通過の有無
RunPod またはローカルアプリが途中で停止した場合、処理中だった chunk だけを再び pending に戻し、完了したファイルと chunk は再利用する。
まだ確定していないこと
この文書では以下を確定しない。
ただし、依存順序は記録する。
| 順序 | 項目 | 理由 | 現在の追跡位置 |
|---|---|---|---|
| 1 | AST パーサ実装体 | どのパーサを使用するかにより、チャンク構造と失敗復旧方式が変わる。 | この文書の AST Parserに渡すデータ、AST Parser 出力 |
| 2 | 最終 JSONL 応答スキーマ | LLM の判断結果をどの JSONL で検証するか決めなければ Validator を付けられる。 | docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md |
| 3 | score DB カラム | JSONL 応答と Retriever の根拠 ID が整理された後、保存カラムを決められる。 | docs/retrospectives/2026-06-25-source-analysis-scoring.md |
| 4 | ファイルシステム分類ラベル | score DB にどの判断結果が蓄積されるかを確認した上でプロジェクトラベルを決める。 | docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md |
| 5 | SFT/DPO 作成規則 | 分類されたファイルシステムが蓄積された後、別途設計する。 | docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md |
現在確定していることは一つだけです。
プロンプトとソースコードは1つの分析リクエストとしてまとめ、ASTパーサーにはソースコードとファイルメタデータのみを渡し、プロンプトはパーサー以降のRetriever/LLM判断段階で使用する。