Initial commit

This commit is contained in:
Crimsofall 2026-06-15 12:56:29 -05:00
commit ed87277ef8
64 changed files with 4639 additions and 0 deletions

View file

@ -0,0 +1,34 @@
# AI Context
The primary AI-ready file is:
`Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.md`
This file is intended to be pasted into or read by AI/Codex before deeper work on a customer Unity project.
It summarizes:
- project name
- Unity version
- scenes
- likely entry scene
- GameObjects
- managers/controllers
- MonoBehaviour scripts
- ScriptableObjects
- lifecycle methods
- public methods
- serialized fields
- UnityEvents
- scene transitions
- prefabs
- missing scripts and references
- imported Git report status
- suggested first files to inspect
- risks and unknowns
The JSON companion file is:
`Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.json`
Use cautious interpretation. The report is designed to accelerate understanding, not replace manual review.

View file

@ -0,0 +1,30 @@
# Codex Instructions For Unity Analyzer
## Purpose
This Unity package gives Runlevel Systems DevPlus1 a safe Editor-only analyzer for customer Unity projects.
## Rules
- Read generated reports before changing customer Unity code.
- Treat findings as best-effort and confirm manually.
- Do not modify customer scenes, prefabs, scripts, assets, or project settings unless explicitly asked.
- Do not generate C# files into `Assets/RL_DevPlus1/Reports/`.
- Keep analyzer Editor scripts under `Assets/RL_DevPlus1/Editor/`.
- Keep docs under `Assets/RL_DevPlus1/Docs/`.
- Keep report outputs under `Assets/RL_DevPlus1/Reports/`.
## Recommended AI Reading Order
1. `Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.md`
2. `Assets/RL_DevPlus1/Reports/RL_Unity_Report_Index.md`
3. `Assets/RL_DevPlus1/Reports/RL_Unity_Project_Summary.md`
4. `reports/api-reference.md`
5. `reports/call-map.md`
6. `Assets/RL_DevPlus1/Reports/RL_Unity_Scene_Map.md`
7. `Assets/RL_DevPlus1/Reports/RL_Unity_Script_Map.md`
8. `Assets/RL_DevPlus1/Reports/RL_Unity_Event_Map.md`
## Maintenance
Update this file when analyzer behavior, report locations, or recommended reading order changes.

View file

@ -0,0 +1,26 @@
# Runlevel DevPlus1 Unity Analyzer
This folder contains the Unity-side Runlevel Systems DevPlus1 project analyzer.
The analyzer is an Editor-only tool. It reads Unity project structure, scenes, prefabs, scripts, UnityEvents, and existing Git-generated reports, then writes safe markdown and JSON reports under:
`Assets/RL_DevPlus1/Reports/`
It does not modify customer scenes, prefabs, scripts, project settings, or assets.
## Menu Location
Unity menu:
`Runlevel Systems/DevPlus1/Analyze Unity Project`
## Main Reports
- `RL_AI_Project_Context.md`
- `RL_Unity_Report_Index.md`
- `RL_Unity_Project_Summary.md`
- `RL_Unity_Scene_Map.md`
- `RL_Unity_Script_Map.md`
- `RL_Unity_Event_Map.md`
Future Codex sessions should read `RL_AI_Project_Context.md` first after reports are generated.

View file

@ -0,0 +1,28 @@
# Unity Analyzer Reports
Reports are written under:
`Assets/RL_DevPlus1/Reports/`
## Human-Readable Reports
- `RL_Unity_Project_Summary.md`: high-level counts and project status.
- `RL_Unity_Scene_Map.md`: scene paths, root objects, counts, and missing reference totals.
- `RL_Unity_GameObject_Map.md`: GameObject hierarchy, tags, layers, and components.
- `RL_Unity_Script_Map.md`: script classes, lifecycle methods, fields, methods, and Unity API usage.
- `RL_Unity_Event_Map.md`: UnityEvent and Button callback bindings where detectable.
- `RL_Unity_Prefab_Map.md`: prefab components, scripts, nested prefabs, and references.
- `RL_Unity_Execution_Order.md`: custom script execution order and probable lifecycle order.
- `RL_Unity_Missing_References.md`: missing scripts and missing serialized references.
- `RL_Unity_Git_Report_Summary.md`: summary of repository-root Git reports if present.
- `RL_Unity_Report_Index.md`: index of generated reports.
## AI-Ready Reports
- `RL_AI_Project_Context.md`: primary Codex/AI context report.
- `RL_AI_Project_Context.json`: machine-readable AI context.
- `RL_Unity_Project_Data.json`: full machine-readable project analysis data.
## Limitations
Reports are best-effort. Runtime-created objects, reflection, dependency injection, generated content, addressable content, and custom serialization may require manual confirmation.

View file

@ -0,0 +1,47 @@
# Unity Analyzer
## What It Does
The Runlevel DevPlus1 Unity Analyzer creates project-understanding reports for customer Unity projects.
It can inspect:
- open scenes
- build settings scenes
- all asset scenes
- prefabs
- C# scripts
- Unity lifecycle methods
- UnityEvents and Button callbacks where serialized data is visible
- missing scripts
- missing serialized object references where detectable
- root Git-generated reports under `reports/`
## Menu Items
- `Runlevel Systems/DevPlus1/Analyze Unity Project`
- `Runlevel Systems/DevPlus1/Open Report Folder`
- `Runlevel Systems/DevPlus1/Refresh Git Reports`
## Safety
The analyzer is read-only against customer source files and Unity assets.
It does not:
- save scenes
- modify prefabs
- edit scripts
- write generated C# files
- modify project settings
- create asmdef files
If open scenes have unsaved changes, broad scene-scan operations skip scene switching rather than saving or discarding changes.
## How To Run
1. Open the Unity project.
2. Wait for Unity to compile Editor scripts.
3. Open `Runlevel Systems/DevPlus1/Analyze Unity Project`.
4. Click `Generate All Reports`.
5. Open `Assets/RL_DevPlus1/Reports/`.

View file

@ -0,0 +1,69 @@
using System.Collections.Generic;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLAIContextExporter
{
public static void EnrichProjectContext(RLProjectAnalysisData data)
{
data.suggestedFirstFiles.Clear();
data.risksOrUnknowns.Clear();
foreach (RLScriptInfo script in data.scripts)
{
string lower = (script.className + " " + script.possibleRole).ToLowerInvariant();
if (lower.Contains("manager") || lower.Contains("controller") || script.lifecycleMethods.Count > 0)
{
AddUnique(data.suggestedFirstFiles, script.scriptPath);
}
}
foreach (string file in data.gitReports.importantFiles)
{
AddUnique(data.suggestedFirstFiles, file);
}
if (data.scenes.Count == 0)
{
data.risksOrUnknowns.Add("No scenes were analyzed. Scene flow needs manual confirmation.");
}
if (data.gitReports.validationFoundErrors)
{
data.risksOrUnknowns.Add("Git validation reports customer code errors. Review reports/errors.md before feature work.");
}
int missingScripts = 0;
int missingReferences = 0;
foreach (RLSceneInfo scene in data.scenes)
{
missingScripts += scene.missingScriptCount;
missingReferences += scene.missingReferenceCount;
}
foreach (RLPrefabInfo prefab in data.prefabs)
{
missingScripts += prefab.missingScriptCount;
missingReferences += prefab.missingReferenceCount;
}
if (missingScripts > 0)
{
data.risksOrUnknowns.Add("Missing scripts were detected. Inspect RL_Unity_Missing_References.md.");
}
if (missingReferences > 0)
{
data.risksOrUnknowns.Add("Missing serialized object references were detected. Inspect RL_Unity_Missing_References.md.");
}
data.risksOrUnknowns.Add("Analysis is best-effort and may miss runtime-created objects, reflection, addressables, or dependency injection.");
}
private static void AddUnique(List<string> values, string value)
{
if (!string.IsNullOrEmpty(value) && !values.Contains(value))
{
values.Add(value);
}
}
}
}

View file

@ -0,0 +1,114 @@
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEngine;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLGitReportImporter
{
private static readonly string[] ExpectedReports =
{
"errors.md",
"api-reference.md",
"file-purpose.md",
"call-map.md",
"unity-scripts.md",
"code-docs-summary.md",
"project-flow.md",
"summary.md",
"generated-files.md"
};
public static RLGitReportSummary ImportGitReports()
{
RLGitReportSummary summary = new RLGitReportSummary();
string repoRoot = Directory.GetParent(Application.dataPath).FullName;
string reportsFolder = Path.Combine(repoRoot, "reports");
summary.reportsFolderFound = Directory.Exists(reportsFolder);
if (!summary.reportsFolderFound)
{
summary.notes = "Repository-root reports folder was not found. Run Forgejo reports or local scripts first if Git analysis is needed.";
foreach (string report in ExpectedReports)
{
summary.missingReports.Add("reports/" + report);
}
return summary;
}
foreach (string report in ExpectedReports)
{
string path = Path.Combine(reportsFolder, report);
if (File.Exists(path))
{
summary.foundReports.Add("reports/" + report);
}
else
{
summary.missingReports.Add("reports/" + report);
}
}
string errorsPath = Path.Combine(reportsFolder, "errors.md");
summary.validationReportFound = File.Exists(errorsPath);
if (summary.validationReportFound)
{
string text = File.ReadAllText(errorsPath);
summary.validationFoundErrors = text.Contains("CUSTOMER CODE ERRORS FOUND");
summary.validationWarnings = ExtractCount(text, @"Warnings:\s*(\d+)");
summary.validationErrors = ExtractCount(text, @"Errors:\s*(\d+)");
}
summary.apiReferenceFound = File.Exists(Path.Combine(reportsFolder, "api-reference.md"));
summary.filePurposeFound = File.Exists(Path.Combine(reportsFolder, "file-purpose.md"));
summary.staticCallMapFound = File.Exists(Path.Combine(reportsFolder, "call-map.md"));
summary.unityScriptReportFound = File.Exists(Path.Combine(reportsFolder, "unity-scripts.md"));
ReadProjectFlow(Path.Combine(reportsFolder, "project-flow.md"), summary);
summary.notes = "Imported repository-root Git reports with best-effort parsing.";
return summary;
}
private static int ExtractCount(string text, string pattern)
{
Match match = Regex.Match(text, pattern);
if (match.Success && int.TryParse(match.Groups[1].Value, out int value))
{
return value;
}
return -1;
}
private static void ReadProjectFlow(string path, RLGitReportSummary summary)
{
if (!File.Exists(path))
{
return;
}
string[] lines = File.ReadAllLines(path);
bool inEntry = false;
bool inImportant = false;
foreach (string line in lines)
{
if (line.StartsWith("## "))
{
inEntry = line.Contains("Likely Entry Point") || line.Contains("Possible Startup");
inImportant = line.Contains("Important Files");
continue;
}
if (line.StartsWith("- ") && inEntry)
{
summary.likelyEntryPoints.Add(line.Substring(2));
}
else if (line.StartsWith("- ") && inImportant)
{
summary.importantFiles.Add(line.Substring(2));
}
}
}
}
}

View file

@ -0,0 +1,98 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLPrefabAnalyzer
{
public static List<RLPrefabInfo> AnalyzePrefabs()
{
List<RLPrefabInfo> results = new List<RLPrefabInfo>();
string[] guids = AssetDatabase.FindAssets("t:Prefab");
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab == null)
{
continue;
}
RLPrefabInfo info = new RLPrefabInfo();
info.prefabPath = path;
TraversePrefab(prefab, info);
results.Add(info);
}
return results;
}
private static void TraversePrefab(GameObject gameObject, RLPrefabInfo info)
{
info.missingScriptCount += GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(gameObject);
Component[] components = gameObject.GetComponents<Component>();
foreach (Component component in components)
{
if (component == null)
{
AddUnique(info.components, "Missing Script");
continue;
}
AddUnique(info.components, component.GetType().Name);
if (component is MonoBehaviour)
{
AddUnique(info.attachedScripts, component.GetType().Name);
}
CollectReferences(component, info);
}
string prefabSource = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(gameObject);
if (!string.IsNullOrEmpty(prefabSource) && prefabSource != info.prefabPath)
{
AddUnique(info.nestedPrefabs, prefabSource);
}
foreach (Transform child in gameObject.transform)
{
TraversePrefab(child.gameObject, info);
}
}
private static void CollectReferences(Component component, RLPrefabInfo info)
{
SerializedObject serializedObject = new SerializedObject(component);
SerializedProperty property = serializedObject.GetIterator();
bool enterChildren = true;
while (property.NextVisible(enterChildren))
{
enterChildren = false;
if (property.propertyType != SerializedPropertyType.ObjectReference)
{
continue;
}
if (property.objectReferenceValue != null)
{
AddUnique(info.importantReferences, component.GetType().Name + "." + property.displayName + " -> " + property.objectReferenceValue.name);
}
else if (property.objectReferenceInstanceIDValue != 0)
{
info.missingReferenceCount++;
}
}
}
private static void AddUnique(List<string> values, string value)
{
if (!values.Contains(value))
{
values.Add(value);
}
}
}
}

View file

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public class RLProjectAnalyzerWindow : EditorWindow
{
private RLProjectAnalysisData data = new RLProjectAnalysisData();
private string lastRunTime = "Not run yet";
private Vector2 scroll;
[MenuItem("Runlevel Systems/DevPlus1/Analyze Unity Project")]
public static void ShowWindow()
{
RLProjectAnalyzerWindow window = GetWindow<RLProjectAnalyzerWindow>("Runlevel DevPlus1 Project Analyzer");
window.minSize = new Vector2(520f, 520f);
}
[MenuItem("Runlevel Systems/DevPlus1/Open Report Folder")]
public static void OpenReportFolder()
{
RLReportExporter.RevealReportFolder();
}
[MenuItem("Runlevel Systems/DevPlus1/Refresh Git Reports")]
public static void RefreshGitReportsMenu()
{
RLGitReportSummary git = RLGitReportImporter.ImportGitReports();
RLReportExporter.WriteGitSummary(git);
AssetDatabase.Refresh();
Debug.Log("Runlevel DevPlus1 Git report summary refreshed.");
}
private void OnEnable()
{
RLReportExporter.EnsureFolders();
data.projectName = Application.productName;
data.unityVersion = Application.unityVersion;
}
private void OnGUI()
{
scroll = EditorGUILayout.BeginScrollView(scroll);
EditorGUILayout.LabelField("Runlevel DevPlus1 Project Analyzer", EditorStyles.boldLabel);
EditorGUILayout.Space();
if (GUILayout.Button("Import Git Reports")) ImportGitReports();
if (GUILayout.Button("Analyze Open Scene")) AnalyzeOpenScenes();
if (GUILayout.Button("Analyze Build Settings Scenes")) AnalyzeBuildSettingsScenes();
if (GUILayout.Button("Analyze All Asset Scenes")) AnalyzeAllAssetScenes();
if (GUILayout.Button("Analyze Prefabs")) AnalyzePrefabs();
if (GUILayout.Button("Analyze Scripts")) AnalyzeScripts();
if (GUILayout.Button("Generate All Reports")) GenerateAllReports();
if (GUILayout.Button("Open Reports Folder")) RLReportExporter.RevealReportFolder();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Last run time", lastRunTime);
EditorGUILayout.LabelField("Scenes analyzed", data.scenes.Count.ToString());
EditorGUILayout.LabelField("Scripts analyzed", data.scripts.Count.ToString());
EditorGUILayout.LabelField("Prefabs analyzed", data.prefabs.Count.ToString());
EditorGUILayout.LabelField("Missing script count", CountMissingScripts().ToString());
EditorGUILayout.LabelField("Missing reference count", CountMissingReferences().ToString());
EditorGUILayout.LabelField("Report folder path", RLReportExporter.AbsoluteReportFolder);
EditorGUILayout.EndScrollView();
}
private void ImportGitReports()
{
data.gitReports = RLGitReportImporter.ImportGitReports();
RLReportExporter.WriteGitSummary(data.gitReports);
MarkRun();
}
private void AnalyzeOpenScenes()
{
data.scenes = RLSceneAnalyzer.AnalyzeOpenScenes();
MarkRun();
}
private void AnalyzeBuildSettingsScenes()
{
data.scenes = RLSceneAnalyzer.AnalyzeBuildSettingsScenes();
MarkRun();
}
private void AnalyzeAllAssetScenes()
{
data.scenes = RLSceneAnalyzer.AnalyzeAllAssetScenes();
MarkRun();
}
private void AnalyzePrefabs()
{
data.prefabs = RLPrefabAnalyzer.AnalyzePrefabs();
MarkRun();
}
private void AnalyzeScripts()
{
data.scripts = RLScriptAnalyzer.AnalyzeScripts();
MarkRun();
}
private void GenerateAllReports()
{
data.projectName = Application.productName;
data.unityVersion = Application.unityVersion;
data.generatedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
data.gitReports = RLGitReportImporter.ImportGitReports();
data.scenes = RLSceneAnalyzer.AnalyzeOpenScenes();
data.prefabs = RLPrefabAnalyzer.AnalyzePrefabs();
data.scripts = RLScriptAnalyzer.AnalyzeScripts();
RLAIContextExporter.EnrichProjectContext(data);
RLReportExporter.ExportAll(data);
MarkRun();
Debug.Log("Runlevel DevPlus1 Unity reports generated.");
}
private void MarkRun()
{
lastRunTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
Repaint();
}
private int CountMissingScripts()
{
int count = 0;
foreach (RLSceneInfo scene in data.scenes) count += scene.missingScriptCount;
foreach (RLPrefabInfo prefab in data.prefabs) count += prefab.missingScriptCount;
return count;
}
private int CountMissingReferences()
{
int count = 0;
foreach (RLSceneInfo scene in data.scenes) count += scene.missingReferenceCount;
foreach (RLPrefabInfo prefab in data.prefabs) count += prefab.missingReferenceCount;
return count;
}
}
}

View file

@ -0,0 +1,567 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace RunlevelSystems.DevPlus1.EditorTools
{
[Serializable]
public class RLProjectAnalysisData
{
public string projectName;
public string unityVersion;
public string generatedAt;
public List<RLSceneInfo> scenes = new List<RLSceneInfo>();
public List<RLScriptInfo> scripts = new List<RLScriptInfo>();
public List<RLPrefabInfo> prefabs = new List<RLPrefabInfo>();
public RLGitReportSummary gitReports = new RLGitReportSummary();
public List<string> suggestedFirstFiles = new List<string>();
public List<string> risksOrUnknowns = new List<string>();
}
[Serializable]
public class RLSceneInfo
{
public string scenePath;
public string sceneName;
public int buildIndex = -1;
public int rootGameObjectCount;
public int totalGameObjects;
public int activeGameObjects;
public int inactiveGameObjects;
public int missingScriptCount;
public int missingReferenceCount;
public List<string> rootGameObjects = new List<string>();
public List<RLGameObjectInfo> gameObjects = new List<RLGameObjectInfo>();
public List<RLUnityEventInfo> unityEvents = new List<RLUnityEventInfo>();
public string notes;
}
[Serializable]
public class RLGameObjectInfo
{
public string hierarchyPath;
public bool activeSelf;
public string tag;
public int layer;
public List<string> components = new List<string>();
public List<string> attachedScripts = new List<string>();
public List<string> referencedObjects = new List<string>();
public int missingScriptCount;
public int missingReferenceCount;
}
[Serializable]
public class RLScriptInfo
{
public string scriptPath;
public string className;
public string baseClass;
public bool isMonoBehaviour;
public bool isScriptableObject;
public List<string> lifecycleMethods = new List<string>();
public List<string> publicMethods = new List<string>();
public List<string> serializedFields = new List<string>();
public List<string> publicFields = new List<string>();
public List<string> unityApiUsage = new List<string>();
public string possibleRole;
}
[Serializable]
public class RLPrefabInfo
{
public string prefabPath;
public List<string> components = new List<string>();
public List<string> attachedScripts = new List<string>();
public List<string> nestedPrefabs = new List<string>();
public List<string> importantReferences = new List<string>();
public int missingScriptCount;
public int missingReferenceCount;
}
[Serializable]
public class RLUnityEventInfo
{
public string sourcePath;
public string gameObjectPath;
public string componentName;
public string eventName;
public string targetObject;
public string targetComponent;
public string targetMethod;
}
[Serializable]
public class RLGitReportSummary
{
public bool reportsFolderFound;
public bool validationReportFound;
public bool validationFoundErrors;
public int validationWarnings = -1;
public int validationErrors = -1;
public bool apiReferenceFound;
public bool filePurposeFound;
public bool staticCallMapFound;
public bool unityScriptReportFound;
public List<string> likelyEntryPoints = new List<string>();
public List<string> importantFiles = new List<string>();
public List<string> foundReports = new List<string>();
public List<string> missingReports = new List<string>();
public string notes;
}
public static class RLReportExporter
{
public const string RootFolder = "Assets/RL_DevPlus1";
public const string ReportFolder = RootFolder + "/Reports";
public static void EnsureFolders()
{
EnsureAssetFolder("Assets", "RL_DevPlus1");
EnsureAssetFolder(RootFolder, "Reports");
EnsureAssetFolder(RootFolder, "Docs");
EnsureAssetFolder(RootFolder, "Runtime");
}
public static string AbsoluteReportFolder
{
get { return Path.GetFullPath(Path.Combine(Application.dataPath, "RL_DevPlus1/Reports")); }
}
public static void ExportAll(RLProjectAnalysisData data)
{
EnsureFolders();
WriteProjectSummary(data);
WriteSceneMap(data);
WriteGameObjectMap(data);
WriteScriptMap(data);
WriteEventMap(data);
WritePrefabMap(data);
WriteExecutionOrder(data);
WriteMissingReferences(data);
WriteGitSummary(data.gitReports);
WriteProjectDataJson(data);
WriteAIContext(data);
WriteReportIndex();
AssetDatabase.Refresh();
}
public static void WriteGitSummary(RLGitReportSummary git)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel Unity Git Report Summary");
sb.AppendLine();
sb.AppendLine("- Git reports folder found: " + BoolText(git.reportsFolderFound));
sb.AppendLine("- Validation report found: " + BoolText(git.validationReportFound));
sb.AppendLine("- Validation found errors: " + BoolText(git.validationFoundErrors));
sb.AppendLine("- Validation warnings: " + ValueOrUnknown(git.validationWarnings));
sb.AppendLine("- Validation errors: " + ValueOrUnknown(git.validationErrors));
sb.AppendLine("- API reference found: " + BoolText(git.apiReferenceFound));
sb.AppendLine("- File purpose report found: " + BoolText(git.filePurposeFound));
sb.AppendLine("- Static call map found: " + BoolText(git.staticCallMapFound));
sb.AppendLine("- Unity script report found: " + BoolText(git.unityScriptReportFound));
AppendList(sb, "Likely Entry Points From Git Reports", git.likelyEntryPoints);
AppendList(sb, "Important Files From Git Reports", git.importantFiles);
AppendList(sb, "Found Reports", git.foundReports);
AppendList(sb, "Missing Reports", git.missingReports);
sb.AppendLine("## Notes");
sb.AppendLine();
sb.AppendLine(string.IsNullOrEmpty(git.notes) ? "Best-effort import only. Missing Git reports are allowed." : git.notes);
WriteReport("RL_Unity_Git_Report_Summary.md", sb.ToString());
}
private static void WriteProjectSummary(RLProjectAnalysisData data)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel Unity Project Summary");
sb.AppendLine();
sb.AppendLine("- Project name: " + data.projectName);
sb.AppendLine("- Unity version: " + data.unityVersion);
sb.AppendLine("- Generated at: " + data.generatedAt);
sb.AppendLine("- Scenes analyzed: " + data.scenes.Count);
sb.AppendLine("- Scripts analyzed: " + data.scripts.Count);
sb.AppendLine("- Prefabs analyzed: " + data.prefabs.Count);
sb.AppendLine("- Missing scripts detected: " + CountMissingScripts(data));
sb.AppendLine("- Missing references detected: " + CountMissingReferences(data));
sb.AppendLine();
sb.AppendLine("This report is best-effort and needs manual confirmation before project changes are made.");
WriteReport("RL_Unity_Project_Summary.md", sb.ToString());
}
private static void WriteSceneMap(RLProjectAnalysisData data)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel Unity Scene Map");
sb.AppendLine();
foreach (RLSceneInfo scene in data.scenes)
{
sb.AppendLine("## " + scene.sceneName);
sb.AppendLine();
sb.AppendLine("- Scene path: `" + scene.scenePath + "`");
sb.AppendLine("- Build index: " + scene.buildIndex);
sb.AppendLine("- Root GameObjects: " + scene.rootGameObjectCount);
sb.AppendLine("- Total GameObjects: " + scene.totalGameObjects);
sb.AppendLine("- Active/Inactive: " + scene.activeGameObjects + "/" + scene.inactiveGameObjects);
sb.AppendLine("- Missing scripts: " + scene.missingScriptCount);
sb.AppendLine("- Missing references: " + scene.missingReferenceCount);
AppendList(sb, "Root GameObjects", scene.rootGameObjects);
if (!string.IsNullOrEmpty(scene.notes))
{
sb.AppendLine("Notes: " + scene.notes);
sb.AppendLine();
}
}
WriteReport("RL_Unity_Scene_Map.md", sb.ToString());
}
private static void WriteGameObjectMap(RLProjectAnalysisData data)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel Unity GameObject Map");
sb.AppendLine();
foreach (RLSceneInfo scene in data.scenes)
{
sb.AppendLine("## Scene: " + scene.sceneName);
sb.AppendLine();
foreach (RLGameObjectInfo go in scene.gameObjects)
{
sb.AppendLine("- `" + go.hierarchyPath + "` | active=" + go.activeSelf + " | tag=" + go.tag + " | layer=" + go.layer + " | components=" + string.Join(", ", go.components.ToArray()));
}
sb.AppendLine();
}
WriteReport("RL_Unity_GameObject_Map.md", sb.ToString());
}
private static void WriteScriptMap(RLProjectAnalysisData data)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel Unity Script Map");
sb.AppendLine();
foreach (RLScriptInfo script in data.scripts)
{
sb.AppendLine("## `" + script.scriptPath + "`");
sb.AppendLine();
sb.AppendLine("- Class: " + script.className);
sb.AppendLine("- Base class: " + script.baseClass);
sb.AppendLine("- MonoBehaviour: " + BoolText(script.isMonoBehaviour));
sb.AppendLine("- ScriptableObject: " + BoolText(script.isScriptableObject));
sb.AppendLine("- Possible role: " + script.possibleRole);
AppendList(sb, "Lifecycle Methods", script.lifecycleMethods);
AppendList(sb, "Public Methods", script.publicMethods);
AppendList(sb, "Serialized Fields", script.serializedFields);
AppendList(sb, "Public Fields", script.publicFields);
AppendList(sb, "Unity API Usage", script.unityApiUsage);
}
WriteReport("RL_Unity_Script_Map.md", sb.ToString());
}
private static void WriteEventMap(RLProjectAnalysisData data)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel Unity Event Map");
sb.AppendLine();
bool any = false;
foreach (RLSceneInfo scene in data.scenes)
{
foreach (RLUnityEventInfo evt in scene.unityEvents)
{
any = true;
sb.AppendLine("- Scene `" + scene.scenePath + "` GameObject `" + evt.gameObjectPath + "` component `" + evt.componentName + "` event `" + evt.eventName + "` targets `" + evt.targetObject + "." + evt.targetMethod + "`");
}
}
if (!any)
{
sb.AppendLine("No UnityEvent or Button callback bindings detected in analyzed scenes.");
}
WriteReport("RL_Unity_Event_Map.md", sb.ToString());
}
private static void WritePrefabMap(RLProjectAnalysisData data)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel Unity Prefab Map");
sb.AppendLine();
foreach (RLPrefabInfo prefab in data.prefabs)
{
sb.AppendLine("## `" + prefab.prefabPath + "`");
sb.AppendLine();
AppendList(sb, "Components", prefab.components);
AppendList(sb, "Attached Scripts", prefab.attachedScripts);
AppendList(sb, "Nested Prefabs", prefab.nestedPrefabs);
AppendList(sb, "Important References", prefab.importantReferences);
sb.AppendLine("- Missing scripts: " + prefab.missingScriptCount);
sb.AppendLine("- Missing references: " + prefab.missingReferenceCount);
sb.AppendLine();
}
WriteReport("RL_Unity_Prefab_Map.md", sb.ToString());
}
private static void WriteExecutionOrder(RLProjectAnalysisData data)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel Unity Execution Order");
sb.AppendLine();
sb.AppendLine("## Script Execution Order Settings");
sb.AppendLine();
bool any = false;
string[] scriptGuids = AssetDatabase.FindAssets("t:MonoScript");
foreach (string guid in scriptGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
if (script == null)
{
continue;
}
int order = MonoImporter.GetExecutionOrder(script);
if (order != 0)
{
any = true;
sb.AppendLine("- " + script.name + ": " + order);
}
}
if (!any)
{
sb.AppendLine("- No custom script execution order detected.");
}
sb.AppendLine();
sb.AppendLine("## Probable Runtime Order");
sb.AppendLine();
sb.AppendLine("- Scene load");
sb.AppendLine("- Awake");
sb.AppendLine("- OnEnable");
sb.AppendLine("- Start");
sb.AppendLine("- Update / FixedUpdate / LateUpdate");
sb.AppendLine("- UnityEvents / UI callbacks");
sb.AppendLine("- Scene transitions");
AppendList(sb, "Lifecycle Methods By Script", BuildLifecycleLines(data.scripts));
WriteReport("RL_Unity_Execution_Order.md", sb.ToString());
}
private static void WriteMissingReferences(RLProjectAnalysisData data)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel Unity Missing References");
sb.AppendLine();
foreach (RLSceneInfo scene in data.scenes)
{
foreach (RLGameObjectInfo go in scene.gameObjects)
{
if (go.missingScriptCount > 0 || go.missingReferenceCount > 0)
{
sb.AppendLine("- Scene `" + scene.scenePath + "` GameObject `" + go.hierarchyPath + "` missing scripts=" + go.missingScriptCount + " missing references=" + go.missingReferenceCount);
}
}
}
foreach (RLPrefabInfo prefab in data.prefabs)
{
if (prefab.missingScriptCount > 0 || prefab.missingReferenceCount > 0)
{
sb.AppendLine("- Prefab `" + prefab.prefabPath + "` missing scripts=" + prefab.missingScriptCount + " missing references=" + prefab.missingReferenceCount);
}
}
if (sb.ToString().TrimEnd().EndsWith("References", StringComparison.Ordinal))
{
sb.AppendLine("No missing scripts or missing serialized object references detected in analyzed assets.");
}
WriteReport("RL_Unity_Missing_References.md", sb.ToString());
}
private static void WriteProjectDataJson(RLProjectAnalysisData data)
{
WriteReport("RL_Unity_Project_Data.json", JsonUtility.ToJson(data, true));
}
private static void WriteAIContext(RLProjectAnalysisData data)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel AI Project Context");
sb.AppendLine();
sb.AppendLine("This report is intended to be fed into AI/Codex before deeper Unity project work.");
sb.AppendLine();
sb.AppendLine("- Project name: " + data.projectName);
sb.AppendLine("- Unity version: " + data.unityVersion);
sb.AppendLine("- Main likely entry scene: " + GetLikelyEntryScene(data));
sb.AppendLine("- Git validation status: " + (data.gitReports.validationFoundErrors ? "Detected customer code errors" : "No customer code errors detected or report missing"));
AppendList(sb, "Scenes", SceneLines(data.scenes));
AppendList(sb, "Important Managers/Controllers", ManagerLines(data.scripts));
AppendList(sb, "MonoBehaviour Scripts", ScriptLines(data.scripts, true));
AppendList(sb, "ScriptableObjects", ScriptLines(data.scripts, false));
AppendList(sb, "Prefabs", PrefabLines(data.prefabs));
AppendList(sb, "Scene Transitions", UsageLines(data.scripts, "SceneManager"));
AppendList(sb, "Suggested First Files For AI To Inspect", data.suggestedFirstFiles);
AppendList(sb, "Suggested Risks Or Unknowns", data.risksOrUnknowns);
sb.AppendLine("## Limitations");
sb.AppendLine();
sb.AppendLine("- Best-effort static and editor inspection only.");
sb.AppendLine("- Runtime-created objects, reflection, dependency injection, addressable content, and generated code may be incomplete.");
sb.AppendLine("- Findings need manual confirmation before changes are made.");
WriteReport("RL_AI_Project_Context.md", sb.ToString());
WriteReport("RL_AI_Project_Context.json", JsonUtility.ToJson(data, true));
}
private static void WriteReportIndex()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("# Runlevel Unity Report Index");
sb.AppendLine();
sb.AppendLine("- `RL_Unity_Project_Summary.md`: high-level project counts and status.");
sb.AppendLine("- `RL_Unity_Scene_Map.md`: scene paths, roots, counts, and missing reference totals.");
sb.AppendLine("- `RL_Unity_GameObject_Map.md`: GameObject hierarchy and component overview.");
sb.AppendLine("- `RL_Unity_Script_Map.md`: C# script lifecycle, fields, methods, and Unity API usage.");
sb.AppendLine("- `RL_Unity_Event_Map.md`: detected UnityEvent and Button callback bindings.");
sb.AppendLine("- `RL_Unity_Prefab_Map.md`: prefab components, scripts, nested prefabs, and missing references.");
sb.AppendLine("- `RL_Unity_Execution_Order.md`: script execution order settings and probable lifecycle order.");
sb.AppendLine("- `RL_Unity_Missing_References.md`: missing scripts and serialized reference issues.");
sb.AppendLine("- `RL_Unity_Git_Report_Summary.md`: imported root `reports/` summary if available.");
sb.AppendLine("- `RL_Unity_Project_Data.json`: AI-readable Unity analysis data.");
sb.AppendLine("- `RL_AI_Project_Context.md`: primary AI/Codex context report.");
sb.AppendLine("- `RL_AI_Project_Context.json`: machine-readable AI context data.");
WriteReport("RL_Unity_Report_Index.md", sb.ToString());
}
public static void WriteReport(string fileName, string contents)
{
EnsureFolders();
File.WriteAllText(Path.Combine(AbsoluteReportFolder, fileName), contents, Encoding.UTF8);
}
public static void RevealReportFolder()
{
EnsureFolders();
EditorUtility.RevealInFinder(AbsoluteReportFolder);
}
private static void EnsureAssetFolder(string parent, string child)
{
string full = parent + "/" + child;
if (!AssetDatabase.IsValidFolder(full))
{
AssetDatabase.CreateFolder(parent, child);
}
}
private static string BoolText(bool value)
{
return value ? "yes" : "no";
}
private static string ValueOrUnknown(int value)
{
return value >= 0 ? value.ToString() : "unknown";
}
private static void AppendList(StringBuilder sb, string title, List<string> values)
{
sb.AppendLine("## " + title);
sb.AppendLine();
if (values == null || values.Count == 0)
{
sb.AppendLine("- None detected.");
}
else
{
foreach (string value in values)
{
sb.AppendLine("- " + value);
}
}
sb.AppendLine();
}
private static int CountMissingScripts(RLProjectAnalysisData data)
{
int count = 0;
foreach (RLSceneInfo scene in data.scenes) count += scene.missingScriptCount;
foreach (RLPrefabInfo prefab in data.prefabs) count += prefab.missingScriptCount;
return count;
}
private static int CountMissingReferences(RLProjectAnalysisData data)
{
int count = 0;
foreach (RLSceneInfo scene in data.scenes) count += scene.missingReferenceCount;
foreach (RLPrefabInfo prefab in data.prefabs) count += prefab.missingReferenceCount;
return count;
}
private static List<string> BuildLifecycleLines(List<RLScriptInfo> scripts)
{
List<string> lines = new List<string>();
foreach (RLScriptInfo script in scripts)
{
if (script.lifecycleMethods.Count > 0)
{
lines.Add(script.className + ": " + string.Join(", ", script.lifecycleMethods.ToArray()));
}
}
return lines;
}
private static string GetLikelyEntryScene(RLProjectAnalysisData data)
{
foreach (RLSceneInfo scene in data.scenes)
{
if (scene.buildIndex == 0) return scene.scenePath;
}
return data.scenes.Count > 0 ? data.scenes[0].scenePath : "No scene analyzed";
}
private static List<string> SceneLines(List<RLSceneInfo> scenes)
{
List<string> lines = new List<string>();
foreach (RLSceneInfo scene in scenes) lines.Add(scene.scenePath + " (" + scene.totalGameObjects + " GameObjects)");
return lines;
}
private static List<string> ManagerLines(List<RLScriptInfo> scripts)
{
List<string> lines = new List<string>();
foreach (RLScriptInfo script in scripts)
{
string lower = (script.className + " " + script.possibleRole).ToLowerInvariant();
if (lower.Contains("manager") || lower.Contains("controller"))
{
lines.Add(script.scriptPath + " - " + script.possibleRole);
}
}
return lines;
}
private static List<string> ScriptLines(List<RLScriptInfo> scripts, bool monoBehaviour)
{
List<string> lines = new List<string>();
foreach (RLScriptInfo script in scripts)
{
if ((monoBehaviour && script.isMonoBehaviour) || (!monoBehaviour && script.isScriptableObject))
{
lines.Add(script.scriptPath + " - " + script.className);
}
}
return lines;
}
private static List<string> PrefabLines(List<RLPrefabInfo> prefabs)
{
List<string> lines = new List<string>();
foreach (RLPrefabInfo prefab in prefabs) lines.Add(prefab.prefabPath);
return lines;
}
private static List<string> UsageLines(List<RLScriptInfo> scripts, string usage)
{
List<string> lines = new List<string>();
foreach (RLScriptInfo script in scripts)
{
if (script.unityApiUsage.Contains(usage))
{
lines.Add(script.scriptPath);
}
}
return lines;
}
}
}

View file

@ -0,0 +1,193 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLSceneAnalyzer
{
public static List<RLSceneInfo> AnalyzeOpenScenes()
{
List<RLSceneInfo> results = new List<RLSceneInfo>();
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scene.IsValid() && scene.isLoaded)
{
results.Add(AnalyzeLoadedScene(scene));
}
}
return results;
}
public static List<RLSceneInfo> AnalyzeBuildSettingsScenes()
{
List<string> paths = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
if (scene.enabled)
{
paths.Add(scene.path);
}
}
return AnalyzeScenePaths(paths);
}
public static List<RLSceneInfo> AnalyzeAllAssetScenes()
{
List<string> paths = new List<string>();
foreach (string guid in AssetDatabase.FindAssets("t:Scene"))
{
paths.Add(AssetDatabase.GUIDToAssetPath(guid));
}
return AnalyzeScenePaths(paths);
}
private static List<RLSceneInfo> AnalyzeScenePaths(List<string> paths)
{
List<RLSceneInfo> results = new List<RLSceneInfo>();
if (HasDirtyOpenScenes())
{
RLSceneInfo skipped = new RLSceneInfo();
skipped.sceneName = "Scene analysis skipped";
skipped.scenePath = "Open scenes contain unsaved changes";
skipped.notes = "The analyzer does not save scenes. Save or discard changes manually, then run scene analysis again.";
results.Add(skipped);
return results;
}
SceneSetup[] setup = EditorSceneManager.GetSceneManagerSetup();
try
{
foreach (string path in paths)
{
if (string.IsNullOrEmpty(path))
{
continue;
}
Scene scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Single);
results.Add(AnalyzeLoadedScene(scene));
}
}
finally
{
if (setup != null && setup.Length > 0)
{
EditorSceneManager.RestoreSceneManagerSetup(setup);
}
}
return results;
}
private static bool HasDirtyOpenScenes()
{
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scene.isDirty)
{
return true;
}
}
return false;
}
private static RLSceneInfo AnalyzeLoadedScene(Scene scene)
{
RLSceneInfo info = new RLSceneInfo();
info.scenePath = scene.path;
info.sceneName = scene.name;
info.buildIndex = scene.buildIndex;
GameObject[] roots = scene.GetRootGameObjects();
info.rootGameObjectCount = roots.Length;
foreach (GameObject root in roots)
{
info.rootGameObjects.Add(root.name);
AnalyzeGameObjectRecursive(root, root.name, scene.path, info);
}
return info;
}
public static void AnalyzeGameObjectRecursive(GameObject gameObject, string hierarchyPath, string sourcePath, RLSceneInfo sceneInfo)
{
RLGameObjectInfo goInfo = AnalyzeGameObject(gameObject, hierarchyPath);
sceneInfo.gameObjects.Add(goInfo);
sceneInfo.totalGameObjects++;
if (gameObject.activeSelf) sceneInfo.activeGameObjects++;
else sceneInfo.inactiveGameObjects++;
sceneInfo.missingScriptCount += goInfo.missingScriptCount;
sceneInfo.missingReferenceCount += goInfo.missingReferenceCount;
sceneInfo.unityEvents.AddRange(RLUnityEventAnalyzer.ExtractUnityEvents(gameObject, sourcePath, hierarchyPath));
foreach (Transform child in gameObject.transform)
{
AnalyzeGameObjectRecursive(child.gameObject, hierarchyPath + "/" + child.name, sourcePath, sceneInfo);
}
}
public static RLGameObjectInfo AnalyzeGameObject(GameObject gameObject, string hierarchyPath)
{
RLGameObjectInfo info = new RLGameObjectInfo();
info.hierarchyPath = hierarchyPath;
info.activeSelf = gameObject.activeSelf;
info.tag = gameObject.tag;
info.layer = gameObject.layer;
info.missingScriptCount = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(gameObject);
Component[] components = gameObject.GetComponents<Component>();
foreach (Component component in components)
{
if (component == null)
{
info.components.Add("Missing Script");
continue;
}
info.components.Add(component.GetType().Name);
MonoBehaviour monoBehaviour = component as MonoBehaviour;
if (monoBehaviour != null)
{
info.attachedScripts.Add(component.GetType().Name);
}
CollectSerializedReferences(component, info);
}
return info;
}
private static void CollectSerializedReferences(Component component, RLGameObjectInfo info)
{
SerializedObject serializedObject = new SerializedObject(component);
SerializedProperty property = serializedObject.GetIterator();
bool enterChildren = true;
while (property.NextVisible(enterChildren))
{
enterChildren = false;
if (property.propertyType != SerializedPropertyType.ObjectReference)
{
continue;
}
if (property.objectReferenceValue != null)
{
string referenceName = property.displayName + " -> " + property.objectReferenceValue.name;
if (!info.referencedObjects.Contains(referenceName))
{
info.referencedObjects.Add(referenceName);
}
}
else if (property.objectReferenceInstanceIDValue != 0)
{
info.missingReferenceCount++;
}
}
}
}
}

View file

@ -0,0 +1,147 @@
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLScriptAnalyzer
{
private static readonly string[] LifecycleMethods =
{
"Awake", "OnEnable", "Start", "Update", "FixedUpdate", "LateUpdate",
"OnDisable", "OnDestroy", "OnTriggerEnter", "OnTriggerExit",
"OnCollisionEnter", "OnCollisionExit"
};
private static readonly string[] UnityApiMarkers =
{
"SceneManager", "Instantiate", "Destroy", "GetComponent",
"FindObjectOfType", "FindFirstObjectByType", "Resources.Load",
"Addressables", "Input", "UnityEvent"
};
public static List<RLScriptInfo> AnalyzeScripts()
{
List<RLScriptInfo> results = new List<RLScriptInfo>();
string[] guids = AssetDatabase.FindAssets("t:MonoScript");
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.StartsWith("Assets/RL_DevPlus1/Editor/"))
{
continue;
}
MonoScript monoScript = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
if (monoScript == null)
{
continue;
}
string text = File.Exists(path) ? File.ReadAllText(path) : string.Empty;
System.Type scriptClass = monoScript.GetClass();
RLScriptInfo info = new RLScriptInfo();
info.scriptPath = path;
info.className = scriptClass != null ? scriptClass.Name : GuessClassName(text);
info.baseClass = scriptClass != null && scriptClass.BaseType != null ? scriptClass.BaseType.Name : GuessBaseClass(text);
info.isMonoBehaviour = scriptClass != null && typeof(UnityEngine.MonoBehaviour).IsAssignableFrom(scriptClass);
info.isScriptableObject = scriptClass != null && typeof(UnityEngine.ScriptableObject).IsAssignableFrom(scriptClass);
info.lifecycleMethods.AddRange(FindLifecycleMethods(text));
info.publicMethods.AddRange(FindPublicMethods(text));
info.serializedFields.AddRange(FindSerializedFields(text));
info.publicFields.AddRange(FindPublicFields(text));
info.unityApiUsage.AddRange(FindUnityApiUsage(text));
info.possibleRole = GuessRole(path, info);
results.Add(info);
}
return results;
}
private static string GuessClassName(string text)
{
Match match = Regex.Match(text, @"class\s+([A-Za-z_][A-Za-z0-9_]*)");
return match.Success ? match.Groups[1].Value : "not detected";
}
private static string GuessBaseClass(string text)
{
Match match = Regex.Match(text, @"class\s+[A-Za-z_][A-Za-z0-9_]*\s*:\s*([A-Za-z_][A-Za-z0-9_]*)");
return match.Success ? match.Groups[1].Value : "not detected";
}
private static List<string> FindLifecycleMethods(string text)
{
List<string> results = new List<string>();
foreach (string method in LifecycleMethods)
{
if (Regex.IsMatch(text, @"\b" + method + @"\s*\("))
{
results.Add(method);
}
}
return results;
}
private static List<string> FindPublicMethods(string text)
{
return UniqueMatches(text, @"public\s+[A-Za-z_][A-Za-z0-9_<>,\[\]\?]*\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(");
}
private static List<string> FindSerializedFields(string text)
{
return UniqueMatches(text, @"\[SerializeField\]\s*(private|protected|public)?\s*[A-Za-z_][A-Za-z0-9_<>,\[\]]*\s+([A-Za-z_][A-Za-z0-9_]*)", 2);
}
private static List<string> FindPublicFields(string text)
{
return UniqueMatches(text, @"public\s+[A-Za-z_][A-Za-z0-9_<>,\[\]]*\s+([A-Za-z_][A-Za-z0-9_]*)\s*(=|;)");
}
private static List<string> FindUnityApiUsage(string text)
{
List<string> results = new List<string>();
foreach (string marker in UnityApiMarkers)
{
if (text.Contains(marker))
{
results.Add(marker);
}
}
return results;
}
private static List<string> UniqueMatches(string text, string pattern, int group = 1)
{
List<string> results = new List<string>();
foreach (Match match in Regex.Matches(text, pattern))
{
string value = match.Groups[group].Value;
if (!string.IsNullOrEmpty(value) && !results.Contains(value))
{
results.Add(value);
}
}
return results;
}
private static string GuessRole(string path, RLScriptInfo info)
{
string value = (path + " " + info.className).ToLowerInvariant();
if (value.Contains("player")) return "Possible player controller";
if (value.Contains("enemy") || value.Contains("ai")) return "Possible enemy AI";
if (value.Contains("ui") || value.Contains("menu") || value.Contains("hud")) return "Possible UI controller";
if (value.Contains("game") && value.Contains("manager")) return "Possible game manager";
if (value.Contains("spawner") || value.Contains("spawn")) return "Possible spawner";
if (value.Contains("inventory")) return "Possible inventory system";
if (value.Contains("health") || value.Contains("damage")) return "Possible health/damage system";
if (value.Contains("camera")) return "Possible camera controller";
if (value.Contains("audio") || value.Contains("music")) return "Possible audio manager";
if (value.Contains("scene")) return "Possible scene loader";
if (info.isScriptableObject) return "Possible data/config asset";
return "Possible utility or gameplay script";
}
}
}

View file

@ -0,0 +1,77 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLUnityEventAnalyzer
{
public static List<RLUnityEventInfo> ExtractUnityEvents(GameObject gameObject, string sourcePath, string hierarchyPath)
{
List<RLUnityEventInfo> events = new List<RLUnityEventInfo>();
Component[] components = gameObject.GetComponents<Component>();
foreach (Component component in components)
{
if (component == null)
{
continue;
}
SerializedObject serializedObject = new SerializedObject(component);
SerializedProperty iterator = serializedObject.GetIterator();
bool enterChildren = true;
while (iterator.NextVisible(enterChildren))
{
enterChildren = false;
if (iterator.propertyType == SerializedPropertyType.Generic &&
(iterator.type.Contains("UnityEvent") || iterator.displayName.Contains("On Click") || iterator.name.Contains("m_OnClick")))
{
ExtractPersistentCalls(events, iterator.Copy(), sourcePath, hierarchyPath, component);
}
}
}
return events;
}
private static void ExtractPersistentCalls(List<RLUnityEventInfo> events, SerializedProperty eventProperty, string sourcePath, string hierarchyPath, Component component)
{
SerializedProperty calls = eventProperty.FindPropertyRelative("m_PersistentCalls.m_Calls");
if (calls == null || !calls.isArray)
{
return;
}
for (int i = 0; i < calls.arraySize; i++)
{
SerializedProperty call = calls.GetArrayElementAtIndex(i);
Object target = ReadObject(call, "m_Target");
string method = ReadString(call, "m_MethodName");
RLUnityEventInfo info = new RLUnityEventInfo();
info.sourcePath = sourcePath;
info.gameObjectPath = hierarchyPath;
info.componentName = component.GetType().Name;
info.eventName = eventProperty.displayName;
info.targetObject = target != null ? target.name : "missing or none";
info.targetComponent = target != null ? target.GetType().Name : "not detected";
info.targetMethod = string.IsNullOrEmpty(method) ? "not detected" : method;
events.Add(info);
}
}
private static Object ReadObject(SerializedProperty parent, string relativePath)
{
SerializedProperty property = parent.FindPropertyRelative(relativePath);
return property != null ? property.objectReferenceValue : null;
}
private static string ReadString(SerializedProperty parent, string relativePath)
{
SerializedProperty property = parent.FindPropertyRelative(relativePath);
return property != null ? property.stringValue : string.Empty;
}
}
}

View file

@ -0,0 +1,5 @@
# Runlevel DevPlus1 Reports
Unity analyzer reports are generated here.
This folder must only contain safe report files such as `.md`, `.txt`, and `.json`.

View file

@ -0,0 +1,3 @@
{
"status": "not generated yet"
}

View file

@ -0,0 +1,3 @@
# Runlevel AI Project Context
Not generated yet. Run `Generate All Reports` from the Unity analyzer. This is the first file future AI/Codex sessions should read after generation.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Event Map
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Execution Order
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity GameObject Map
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Git Report Summary
Not generated yet. Use `Runlevel Systems/DevPlus1/Refresh Git Reports` or generate all reports.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Missing References
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Prefab Map
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
{
"status": "not generated yet"
}

View file

@ -0,0 +1,3 @@
# Runlevel Unity Project Summary
Not generated yet. Run `Runlevel Systems/DevPlus1/Analyze Unity Project` in Unity, then click `Generate All Reports`.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Report Index
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Scene Map
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Script Map
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,4 @@
Runtime scripts are not required for the DevPlus1 analyzer.
The current analyzer is Editor-only and lives under:
Assets/RL_DevPlus1/Editor/