diff --git a/Editor/Core/SceneMemoryReport.cs b/Editor/Core/SceneMemoryReport.cs
index 81ab27e..1a88e2c 100644
--- a/Editor/Core/SceneMemoryReport.cs
+++ b/Editor/Core/SceneMemoryReport.cs
@@ -43,9 +43,9 @@ namespace SplashEdit.EditorCode
public const long SPU_RESERVED = 0x1010;
public const long USABLE_SPU = TOTAL_SPU - SPU_RESERVED;
- // Fixed runtime overhead from C++ (renderer.hh constants)
- public const long BUMP_ALLOC_TOTAL = 2L * 8096 * 24; // ~380KB
- public const long OT_TOTAL = 2L * 16384 * 4; // ~128KB
+ // Fixed runtime overhead from C++ (renderer.hh constants, now configurable)
+ public static long BUMP_ALLOC_TOTAL => 2L * SplashSettings.BumpSize;
+ public static long OT_TOTAL => 2L * SplashSettings.OtSize * 4;
public const long VIS_REFS = 4096 * 4; // 16KB
public const long STACK_ESTIMATE = 32 * 1024; // 32KB
public const long LUA_OVERHEAD = 16 * 1024; // 16KB approximate
@@ -53,6 +53,11 @@ namespace SplashEdit.EditorCode
public long FixedOverhead => BUMP_ALLOC_TOTAL + OT_TOTAL + VIS_REFS + STACK_ESTIMATE + LUA_OVERHEAD;
+ // Heap estimate and warnings
+ public long EstimatedHeapFree => USABLE_RAM - TotalRamUsage;
+ public bool IsHeapWarning => EstimatedHeapFree < 128 * 1024; // < 128KB free
+ public bool IsHeapCritical => EstimatedHeapFree < 64 * 1024; // < 64KB free
+
/// RAM used by scene data (live portion of splashpack).
public long SceneRamUsage => splashpackLiveSize > 0 ? splashpackLiveSize : splashpackFileSize;
diff --git a/Editor/Core/SplashBuildPaths.cs b/Editor/Core/SplashBuildPaths.cs
index b666720..1f09cd1 100644
--- a/Editor/Core/SplashBuildPaths.cs
+++ b/Editor/Core/SplashBuildPaths.cs
@@ -166,6 +166,45 @@ namespace SplashEdit.EditorCode
EnsureGitIgnore();
}
+ // ───── Lua bytecode compilation paths ─────
+
+ ///
+ /// Directory for Lua source files extracted during export.
+ ///
+ public static string LuaSrcDir =>
+ Path.Combine(BuildOutputDir, "lua_src");
+
+ ///
+ /// Directory for compiled Lua bytecode files.
+ ///
+ public static string LuaCompiledDir =>
+ Path.Combine(BuildOutputDir, "lua_compiled");
+
+ ///
+ /// Manifest file listing input/output pairs for the PS1 Lua compiler.
+ ///
+ public static string LuaManifestPath =>
+ Path.Combine(LuaSrcDir, "manifest.txt");
+
+ ///
+ /// Sentinel file written by luac_psx when compilation is complete.
+ /// Contains "OK" on success or "ERROR" on failure.
+ ///
+ public static string LuaDoneSentinel =>
+ Path.Combine(LuaSrcDir, "__done__");
+
+ ///
+ /// Path to the luac_psx PS1 compiler executable (built from tools/luac_psx/).
+ ///
+ public static string LuacPsxExePath =>
+ Path.Combine(NativeSourceDir, "tools", "luac_psx", "luac_psx.ps-exe");
+
+ ///
+ /// Path to the luac_psx tools directory (for building the compiler).
+ ///
+ public static string LuacPsxDir =>
+ Path.Combine(NativeSourceDir, "tools", "luac_psx");
+
///
/// Checks if PCSX-Redux is installed in the tools directory.
///
diff --git a/Editor/Core/SplashControlPanel.cs b/Editor/Core/SplashControlPanel.cs
index 92945b5..a5e2afc 100644
--- a/Editor/Core/SplashControlPanel.cs
+++ b/Editor/Core/SplashControlPanel.cs
@@ -37,6 +37,7 @@ namespace SplashEdit.EditorCode
// ───── Build State ─────
private static bool _isBuilding;
private static bool _isRunning;
+ private static bool _luaBytecodeCompiled;
private static Process _emulatorProcess;
// ───── Scene List ─────
@@ -669,6 +670,13 @@ namespace SplashEdit.EditorCode
new GUIContent("FPS Overlay", "Show an FPS counter at top-left during gameplay"),
SplashSettings.FpsOverlay);
+ SplashSettings.OtSize = EditorGUILayout.IntField(
+ new GUIContent("OT Size", "Ordering table entries. Lower = less RAM, shallower Z-sorting."),
+ SplashSettings.OtSize);
+ SplashSettings.BumpSize = EditorGUILayout.IntField(
+ new GUIContent("Bump Alloc Size", "Per-frame primitive buffer (bytes). Lower = less RAM, fewer triangles per frame."),
+ SplashSettings.BumpSize);
+
// Serial port (only for Real Hardware)
if (SplashSettings.Target == BuildTarget.RealHardware)
{
@@ -789,14 +797,18 @@ namespace SplashEdit.EditorCode
GUILayout.Label($"Scene: {report.sceneName}", PSXEditorStyles.SectionHeader);
EditorGUILayout.Space(4);
- // Main RAM bar
- DrawMemoryBar("Main RAM",
- report.TotalRamUsage, SceneMemoryReport.USABLE_RAM,
+ // Main RAM bar — segmented: OT | Bump | Scene | Heap
+ DrawSegmentedMemoryBar("Main RAM",
+ SceneMemoryReport.USABLE_RAM,
report.RamPercent,
- new Color(0.3f, 0.6f, 1f),
- $"Scene: {FormatBytes(report.SceneRamUsage)} | " +
- $"Fixed: {FormatBytes(report.FixedOverhead)} | " +
- $"Free: {FormatBytes(report.RamFree)}");
+ new (string label, long size, Color color)[] {
+ ("OT", SceneMemoryReport.OT_TOTAL, new Color(0.9f, 0.4f, 0.2f)),
+ ("Bump", SceneMemoryReport.BUMP_ALLOC_TOTAL, new Color(0.8f, 0.6f, 0.1f)),
+ ("Scene", report.SceneRamUsage, new Color(0.3f, 0.6f, 1.0f)),
+ ("Other", SceneMemoryReport.VIS_REFS + SceneMemoryReport.STACK_ESTIMATE + SceneMemoryReport.LUA_OVERHEAD,
+ new Color(0.5f, 0.5f, 0.5f)),
+ },
+ report.EstimatedHeapFree);
EditorGUILayout.Space(4);
@@ -842,6 +854,15 @@ namespace SplashEdit.EditorCode
$"{report.clutCount} CLUTs",
PSXEditorStyles.RichLabel);
+ if (report.IsHeapCritical)
+ EditorGUILayout.HelpBox(
+ $"Estimated heap: {report.EstimatedHeapFree / 1024}KB free. Lua scripts may OOM!",
+ MessageType.Error);
+ else if (report.IsHeapWarning)
+ EditorGUILayout.HelpBox(
+ $"Estimated heap: {report.EstimatedHeapFree / 1024}KB free. Consider reducing OT/Bump sizes.",
+ MessageType.Warning);
+
EditorGUILayout.EndVertical();
EditorGUILayout.Space(4);
}
@@ -889,6 +910,62 @@ namespace SplashEdit.EditorCode
GUILayout.Label(details, EditorStyles.miniLabel);
}
+ private void DrawSegmentedMemoryBar(
+ string label, long total, float percent,
+ (string label, long size, Color color)[] segments,
+ long heapFree)
+ {
+ long used = 0;
+ foreach (var seg in segments) used += seg.size;
+
+ // Label row
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.Label(label, EditorStyles.boldLabel, GUILayout.Width(70));
+ GUILayout.Label($"{FormatBytes(used)} / {FormatBytes(total)} ({percent:F1}%) | Heap free: {FormatBytes(heapFree)}",
+ EditorStyles.miniLabel);
+ GUILayout.FlexibleSpace();
+ EditorGUILayout.EndHorizontal();
+
+ // Bar
+ Rect barRect = GUILayoutUtility.GetRect(0, 16, GUILayout.ExpandWidth(true));
+ EditorGUI.DrawRect(barRect, new Color(0.15f, 0.15f, 0.17f));
+
+ // Draw each segment
+ float x = barRect.x;
+ foreach (var seg in segments)
+ {
+ float w = barRect.width * Mathf.Clamp01((float)seg.size / total);
+ if (w > 0)
+ EditorGUI.DrawRect(new Rect(x, barRect.y, w, barRect.height), seg.color);
+ x += w;
+ }
+
+ // Border
+ DrawRectOutline(barRect, new Color(0.3f, 0.3f, 0.35f));
+
+ // Percent overlay
+ var style = new GUIStyle(EditorStyles.miniLabel)
+ {
+ alignment = TextAnchor.MiddleCenter,
+ normal = { textColor = Color.white }
+ };
+ GUI.Label(barRect, $"{percent:F1}%", style);
+
+ // Legend row
+ EditorGUILayout.BeginHorizontal();
+ foreach (var seg in segments)
+ {
+ // Color swatch + label
+ Rect swatchRect = GUILayoutUtility.GetRect(10, 10, GUILayout.Width(10));
+ swatchRect.y += 2;
+ EditorGUI.DrawRect(swatchRect, seg.color);
+ GUILayout.Label($"{seg.label}: {FormatBytes(seg.size)}", EditorStyles.miniLabel);
+ GUILayout.Space(6);
+ }
+ GUILayout.FlexibleSpace();
+ EditorGUILayout.EndHorizontal();
+ }
+
private static void DrawRectOutline(Rect rect, Color color)
{
EditorGUI.DrawRect(new Rect(rect.x, rect.y, rect.width, 1), color);
@@ -930,6 +1007,7 @@ namespace SplashEdit.EditorCode
}
_isBuilding = true;
+ _luaBytecodeCompiled = false;
var console = EditorWindow.GetWindow();
console.titleContent = new GUIContent("PSX Console", EditorGUIUtility.IconContent("d_UnityEditor.ConsoleWindow").image);
@@ -946,7 +1024,32 @@ namespace SplashEdit.EditorCode
}
Log("Toolchain OK.", LogType.Log);
+ // Collect Lua files from all scenes and compile to bytecode
+ Log("Collecting Lua scripts...", LogType.Log);
+ EditorUtility.DisplayProgressBar("SplashEdit", "Collecting Lua scripts...", 0.2f);
+ var luaFiles = CollectLuaSources();
+ if (luaFiles != null && luaFiles.Count > 0)
+ {
+ Log($"Found {luaFiles.Count} Lua script(s). Compiling...", LogType.Log);
+ EditorUtility.DisplayProgressBar("SplashEdit", "Compiling Lua scripts...", 0.3f);
+ var bytecodeMap = await CompileLuaAsync(luaFiles);
+ if (bytecodeMap == null)
+ {
+ Log("Lua compilation failed.", LogType.Error);
+ return;
+ }
+ PSXSceneWriter.CompiledLuaBytecode = bytecodeMap;
+ _luaBytecodeCompiled = true;
+ Log($"Compiled {bytecodeMap.Count} Lua script(s) to bytecode.", LogType.Log);
+ }
+ else
+ {
+ Log("No Lua scripts found.", LogType.Log);
+ PSXSceneWriter.CompiledLuaBytecode = null;
+ }
+
Log("Exporting scenes...", LogType.Log);
+ EditorUtility.DisplayProgressBar("SplashEdit", "Exporting scenes...", 0.4f);
if (!ExportAllScenes())
{
Log("Export failed.", LogType.Error);
@@ -954,6 +1057,9 @@ namespace SplashEdit.EditorCode
}
Log($"Exported {_sceneList.Count} scene(s).", LogType.Log);
+ // Clear bytecode cache after export
+ PSXSceneWriter.CompiledLuaBytecode = null;
+
Log("Compiling native code...", LogType.Log);
EditorUtility.DisplayProgressBar("SplashEdit", "Compiling native code...", 0.6f);
if (!await CompileNativeAsync())
@@ -1222,6 +1328,345 @@ namespace SplashEdit.EditorCode
Log("Wrote scene manifest.", LogType.Log);
}
+ // ───── Lua bytecode compilation ─────
+
+ ///
+ /// Scan all scenes in the scene list and collect unique Lua source files.
+ /// Writes each source to PSXBuild/lua_src/ for compilation.
+ ///
+ private Dictionary CollectLuaSources()
+ {
+ SplashBuildPaths.EnsureDirectories();
+ string luaSrcDir = SplashBuildPaths.LuaSrcDir;
+ if (Directory.Exists(luaSrcDir))
+ Directory.Delete(luaSrcDir, true);
+ Directory.CreateDirectory(luaSrcDir);
+
+ string currentScenePath = SceneManager.GetActiveScene().path;
+ var luaFileMap = new Dictionary(); // LuaFile -> filename (no extension)
+
+ for (int i = 0; i < _sceneList.Count; i++)
+ {
+ var scene = _sceneList[i];
+ if (scene.asset == null) continue;
+
+ try
+ {
+ EditorSceneManager.OpenScene(scene.path, OpenSceneMode.Single);
+ // Collect from object exporters
+ var objExporters = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None);
+ foreach (var objExporter in objExporters)
+ {
+ if (objExporter.LuaFile != null && !luaFileMap.ContainsKey(objExporter.LuaFile))
+ {
+ string name = $"script_{luaFileMap.Count}";
+ luaFileMap[objExporter.LuaFile] = name;
+ }
+ }
+
+ // Collect scene-level Lua file
+ var sceneExporter = UnityEngine.Object.FindFirstObjectByType();
+ if (sceneExporter != null && sceneExporter.SceneLuaFile != null &&
+ !luaFileMap.ContainsKey(sceneExporter.SceneLuaFile))
+ {
+ string name = $"script_{luaFileMap.Count}";
+ luaFileMap[sceneExporter.SceneLuaFile] = name;
+ }
+
+ // Collect from trigger boxes
+ var triggerBoxes = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None);
+ foreach (var tb in triggerBoxes)
+ {
+ if (tb.LuaFile != null && !luaFileMap.ContainsKey(tb.LuaFile))
+ {
+ string name = $"script_{luaFileMap.Count}";
+ luaFileMap[tb.LuaFile] = name;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log($"Error scanning '{scene.name}' for Lua files: {ex.Message}", LogType.Warning);
+ }
+ }
+
+ // Write source files to disk
+ foreach (var kvp in luaFileMap)
+ {
+ string srcPath = Path.Combine(luaSrcDir, kvp.Value + ".lua");
+ File.WriteAllText(srcPath, kvp.Key.LuaScript, new System.Text.UTF8Encoding(false));
+ }
+
+ // Restore original scene
+ if (!string.IsNullOrEmpty(currentScenePath))
+ EditorSceneManager.OpenScene(currentScenePath, OpenSceneMode.Single);
+
+ return luaFileMap;
+ }
+
+ ///
+ /// Compile Lua source files to PS1 bytecode using luac_psx inside PCSX-Redux.
+ /// Returns a dictionary mapping LuaFile assets to their compiled bytecode,
+ /// or null on failure.
+ ///
+ private async Task> CompileLuaAsync(Dictionary luaFileMap)
+ {
+ string luaSrcDir = SplashBuildPaths.LuaSrcDir;
+ string nativeDir = SplashBuildPaths.NativeSourceDir;
+
+ // Build luac_psx if needed
+ string luacDir = SplashBuildPaths.LuacPsxDir;
+ string luacExe = SplashBuildPaths.LuacPsxExePath;
+
+ if (!File.Exists(luacExe))
+ {
+ Log("Building Lua compiler (luac_psx)...", LogType.Log);
+ if (!await BuildLuacPsxAsync(luacDir))
+ {
+ Log("Failed to build luac_psx.", LogType.Error);
+ return null;
+ }
+ }
+
+ // Generate manifest.txt
+ string manifestPath = SplashBuildPaths.LuaManifestPath;
+ using (var sw = new StreamWriter(manifestPath, false, new System.Text.UTF8Encoding(false)))
+ {
+ foreach (var kvp in luaFileMap)
+ {
+ sw.WriteLine(kvp.Value + ".lua");
+ sw.WriteLine(kvp.Value + ".luac");
+ }
+ }
+
+ // Clean up previous sentinel
+ string sentinelPath = SplashBuildPaths.LuaDoneSentinel;
+ if (File.Exists(sentinelPath))
+ File.Delete(sentinelPath);
+
+ // Launch PCSX-Redux headless with luac_psx
+ string reduxBinary = SplashBuildPaths.PCSXReduxBinary;
+ if (!File.Exists(reduxBinary))
+ {
+ Log("PCSX-Redux not found. Install it via the Toolchain section.", LogType.Error);
+ return null;
+ }
+
+ string args = $"--no-ui --run --fastboot --pcdrv --stdout --pcdrvbase \"{luaSrcDir}\" --loadexe \"{luacExe}\"";
+ Log($"luac_psx: {luacExe}", LogType.Log);
+ Log($"pcdrvbase: {luaSrcDir}", LogType.Log);
+ Log($"manifest: {File.ReadAllText(manifestPath).Trim()}", LogType.Log);
+
+ var psi = new ProcessStartInfo
+ {
+ FileName = reduxBinary,
+ Arguments = args,
+ WorkingDirectory = luaSrcDir,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ Process process = null;
+ try
+ {
+ process = new Process { StartInfo = psi, EnableRaisingEvents = true };
+ var stdoutBuf = new System.Text.StringBuilder();
+
+ var stderrBuf = new System.Text.StringBuilder();
+ process.OutputDataReceived += (s, e) =>
+ {
+ if (e.Data != null)
+ {
+ stdoutBuf.AppendLine(e.Data);
+ }
+ };
+ process.ErrorDataReceived += (s, e) =>
+ {
+ if (e.Data != null)
+ {
+ stderrBuf.AppendLine(e.Data);
+ }
+ };
+
+ process.Start();
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ // Poll for sentinel file (100ms interval, 30s timeout)
+ bool done = false;
+ int elapsed = 0;
+ const int timeoutMs = 30000;
+ const int pollMs = 100;
+
+ while (elapsed < timeoutMs)
+ {
+ await Task.Delay(pollMs);
+ elapsed += pollMs;
+
+ if (File.Exists(sentinelPath))
+ {
+ done = true;
+ break;
+ }
+
+ // Check if process died unexpectedly
+ if (process.HasExited)
+ {
+ Log("PCSX-Redux exited unexpectedly during Lua compilation.", LogType.Error);
+ break;
+ }
+ }
+
+ // Kill emulator
+ if (!process.HasExited)
+ {
+ try { process.Kill(); } catch { }
+ }
+
+ if (!done)
+ {
+ Log("Lua compilation timed out (30s).", LogType.Error);
+ return null;
+ }
+
+ // Check sentinel content
+ // Give a tiny delay for file to flush on Windows
+ await Task.Delay(200);
+ string sentinelContent = File.ReadAllText(sentinelPath).Trim();
+ if (string.IsNullOrEmpty(sentinelContent))
+ sentinelContent = "(empty)";
+ if (sentinelContent != "OK")
+ {
+ // Sentinel contains error details from luac_psx
+ foreach (string errLine in sentinelContent.Split('\n'))
+ {
+ if (!string.IsNullOrWhiteSpace(errLine))
+ Log(errLine.TrimEnd(), LogType.Error);
+ }
+
+ // Dump full stdout and stderr for diagnosis
+ string fullStdout = stdoutBuf.ToString().Trim();
+ string fullStderr = stderrBuf.ToString().Trim();
+
+ if (!string.IsNullOrEmpty(fullStdout))
+ {
+ Log("--- PCSX-Redux stdout ---", LogType.Log);
+ foreach (string line in fullStdout.Split('\n'))
+ {
+ if (!string.IsNullOrWhiteSpace(line))
+ LogToPanel(line.TrimEnd(), LogType.Log);
+ }
+ }
+ else
+ {
+ Log("No stdout captured from PCSX-Redux.", LogType.Warning);
+ }
+
+ if (!string.IsNullOrEmpty(fullStderr))
+ {
+ Log("--- PCSX-Redux stderr ---", LogType.Log);
+ foreach (string line in fullStderr.Split('\n'))
+ {
+ if (!string.IsNullOrWhiteSpace(line))
+ LogToPanel(line.TrimEnd(), LogType.Log);
+ }
+ }
+ return null;
+ }
+
+ // Read compiled bytecode files, keyed by source text
+ var result = new Dictionary();
+ foreach (var kvp in luaFileMap)
+ {
+ string luacPath = Path.Combine(luaSrcDir, kvp.Value + ".luac");
+ if (!File.Exists(luacPath))
+ {
+ Log($"Missing compiled bytecode: {kvp.Value}.luac", LogType.Error);
+ return null;
+ }
+ result[kvp.Key.LuaScript] = File.ReadAllBytes(luacPath);
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ Log($"Lua compilation error: {ex.Message}", LogType.Error);
+ return null;
+ }
+ finally
+ {
+ if (process != null)
+ {
+ if (!process.HasExited)
+ try { process.Kill(); } catch { }
+ process.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Build the luac_psx PS1 compiler executable.
+ ///
+ private async Task BuildLuacPsxAsync(string luacDir)
+ {
+ int jobCount = Math.Max(1, SystemInfo.processorCount - 1);
+ string makeCmd = $"make -j{jobCount}";
+
+ var psi = new ProcessStartInfo
+ {
+ FileName = Application.platform == RuntimePlatform.WindowsEditor ? "cmd.exe" : "/bin/bash",
+ Arguments = Application.platform == RuntimePlatform.WindowsEditor
+ ? $"/c \"cd /d \"{luacDir}\" && {makeCmd}\""
+ : $"-c \"cd \\\"{luacDir}\\\" && {makeCmd}\"",
+ WorkingDirectory = luacDir,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ try
+ {
+ var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
+ var stderrBuf = new System.Text.StringBuilder();
+ process.OutputDataReceived += (s, e) => { };
+ process.ErrorDataReceived += (s, e) =>
+ {
+ if (e.Data != null) stderrBuf.AppendLine(e.Data);
+ };
+
+ var tcs = new TaskCompletionSource();
+ process.Exited += (s, e) => tcs.TrySetResult(process.ExitCode);
+
+ process.Start();
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ int exitCode = await tcs.Task;
+ process.Dispose();
+
+ if (exitCode != 0)
+ {
+ foreach (string line in stderrBuf.ToString().Split('\n'))
+ {
+ if (!string.IsNullOrWhiteSpace(line))
+ LogToPanel(line.Trim(), LogType.Error);
+ }
+ return false;
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Log($"Failed to build luac_psx: {ex.Message}", LogType.Error);
+ return false;
+ }
+ }
+
// ───── Step 3: Compile ─────
private async void CompileOnly()
@@ -1266,9 +1711,20 @@ namespace SplashEdit.EditorCode
if (SplashSettings.FpsOverlay)
buildArg += " FPSOVERLAY=1";
+ buildArg += $" OT_SIZE={SplashSettings.OtSize} BUMP_SIZE={SplashSettings.BumpSize}";
+
+ // Use noparser Lua library when bytecode was pre-compiled
+ string noparserPrefix = "";
+ if (_luaBytecodeCompiled)
+ {
+ buildArg += " NOPARSER=1";
+ // Build liblua-noparser.a first
+ noparserPrefix = "make -C third_party/nugget/third_party/psxlua psx-noparser && ";
+ }
+
int jobCount = Math.Max(1, SystemInfo.processorCount - 1);
string cleanPrefix = SplashSettings.CleanBuild ? "make clean && " : "";
- string makeCmd = $"{cleanPrefix}make all -j{jobCount} {buildArg}".Trim();
+ string makeCmd = $"{cleanPrefix}{noparserPrefix}make all -j{jobCount} {buildArg}".Trim();
Log($"Running: {makeCmd}", LogType.Log);
var psi = new ProcessStartInfo
diff --git a/Editor/Core/SplashSettings.cs b/Editor/Core/SplashSettings.cs
index 53779f8..5ad784e 100644
--- a/Editor/Core/SplashSettings.cs
+++ b/Editor/Core/SplashSettings.cs
@@ -149,6 +149,19 @@ namespace SplashEdit.EditorCode
set => EditorPrefs.SetBool(Prefix + "FpsOverlay", value);
}
+ // --- Renderer sizes ---
+ public static int OtSize
+ {
+ get => EditorPrefs.GetInt(Prefix + "OtSize", 2048 * 4);
+ set => EditorPrefs.SetInt(Prefix + "OtSize", value);
+ }
+
+ public static int BumpSize
+ {
+ get => EditorPrefs.GetInt(Prefix + "BumpSize", 8096 * 16);
+ set => EditorPrefs.SetInt(Prefix + "BumpSize", value);
+ }
+
// --- Export settings ---
public static float DefaultGTEScaling
{
@@ -188,7 +201,8 @@ namespace SplashEdit.EditorCode
"PCSXReduxPath", "PCSXReduxPCdrvBase", "SerialPort", "SerialBaudRate",
"ResWidth", "ResHeight", "DualBuffering", "VerticalLayout",
"GTEScaling", "AutoValidate",
- "LicenseFilePath", "ISOVolumeLabel"
+ "LicenseFilePath", "ISOVolumeLabel",
+ "OtSize", "BumpSize"
};
foreach (string key in keys)
diff --git a/Runtime/PSXSceneWriter.cs b/Runtime/PSXSceneWriter.cs
index 4355184..ef71657 100644
--- a/Runtime/PSXSceneWriter.cs
+++ b/Runtime/PSXSceneWriter.cs
@@ -12,6 +12,13 @@ namespace SplashEdit.RuntimeCode
///
public static class PSXSceneWriter
{
+ ///
+ /// Pre-compiled Lua bytecode, keyed by Lua source text.
+ /// When populated, Write() packs bytecode instead of source text.
+ /// Set by the build pipeline after running luac_psx, cleared after export.
+ ///
+ public static Dictionary CompiledLuaBytecode { get; set; }
+
///
/// All scene data needed to produce a .bin file.
/// Populated by PSXSceneExporter before calling .
@@ -239,7 +246,12 @@ namespace SplashEdit.RuntimeCode
{
luaOffset.PlaceholderPositions.Add(writer.BaseStream.Position);
writer.Write((int)0); // placeholder
- writer.Write((uint)Encoding.UTF8.GetByteCount(luaFile.LuaScript));
+
+ // Use compiled bytecode length if available, otherwise source length
+ if (CompiledLuaBytecode != null && CompiledLuaBytecode.TryGetValue(luaFile.LuaScript, out byte[] bytecode))
+ writer.Write((uint)bytecode.Length);
+ else
+ writer.Write((uint)Encoding.UTF8.GetByteCount(luaFile.LuaScript));
}
// ──────────────────────────────────────────────────────
@@ -429,12 +441,26 @@ namespace SplashEdit.RuntimeCode
// Data sections
// ══════════════════════════════════════════════════════
- // Lua data
+ // Lua data (bytecode if compiled, source text otherwise)
+ int luaIdx = 0;
+ log?.Invoke($"CompiledLuaBytecode: {(CompiledLuaBytecode != null ? CompiledLuaBytecode.Count + " entries" : "null")}", LogType.Log);
foreach (LuaFile luaFile in luaFiles)
{
AlignToFourBytes(writer);
luaOffset.DataOffsets.Add(writer.BaseStream.Position);
- writer.Write(Encoding.UTF8.GetBytes(luaFile.LuaScript));
+
+ if (CompiledLuaBytecode != null && CompiledLuaBytecode.TryGetValue(luaFile.LuaScript, out byte[] bytecode))
+ {
+ log?.Invoke($" Lua [{luaIdx}]: using bytecode ({bytecode.Length} bytes)", LogType.Log);
+ writer.Write(bytecode);
+ }
+ else
+ {
+ byte[] srcBytes = Encoding.UTF8.GetBytes(luaFile.LuaScript);
+ log?.Invoke($" Lua [{luaIdx}]: FALLBACK to source ({srcBytes.Length} bytes, script hash={luaFile.LuaScript.GetHashCode()})", LogType.Warning);
+ writer.Write(srcBytes);
+ }
+ luaIdx++;
}
// Mesh data