Skip to content

Latest commit

 

History

History
1147 lines (681 loc) · 244 KB

File metadata and controls

1147 lines (681 loc) · 244 KB

KLua Codex Goal

Purpose

This document is the main execution brief for implementing KLua with Codex. It converts the architecture and milestone references into practical implementation rules that should guide every change.

Reference documents:

  • docs/KLua_Architecture.md
  • docs/KLua_Implementation_Milestones.md

Reference implementation:

  • Official Lua 5.5 source code is available locally at ~/Downloads/lua-lua-a5522f0.
  • Treat the local source tree as the primary reference for actual Lua 5.5 logic behavior. The manual, local lua5.5 probes, and existing KLua tests are useful evidence, but they do not replace reading the relevant C implementation.

Do not duplicate those documents here. Use them for deeper rationale and feature detail when a milestone needs more context.

Codex Goal Prompt

Use this prompt to bootstrap Codex work sessions for this repository:

/goal Follow docs/KLua_Codex_Goal.md to continue implementing KLua as a pure Kotlin Lua 5.5 runtime for JVM 17+. Use the current repository state as authoritative. For behavior-sensitive parser, VM, coroutine, debug, and standard-library work, inspect the official Lua 5.5 source code at ~/Downloads/lua-lua-a5522f0 before deciding semantics, and use that source as the primary reference for actual logic behavior. Work through bounded milestone-aligned work packages with an observable outcome, explicit exit criteria, and relevant verification. Prefer one to three coherent verified commits per work package; group fixes, tests, and gap-documentation updates for the same Lua semantic rule instead of committing every probe or assertion separately. Use Conventional Commit messages, and keep moving through the prioritized execution plan without redefining the overall goal as complete.

Update this prompt only when the execution rules in this document materially change.

Behavior-sensitive language, VM, coroutine, debug, and standard-library work must refer to the official Lua 5.5 source tree before deciding semantics. The local source checkout is:

~/Downloads/lua-lua-a5522f0

Treat that source tree as part of the execution brief. Inspect the relevant C source file, owning function, and helper chain before implementing or changing observable behavior, and prefer the source logic over assumptions from existing KLua behavior, old Lua-version memory, or manual examples that omit edge-case control flow.

Project Goal

KLua is a greenfield pure Kotlin Lua runtime for JVM 17+. It should provide:

  • A Lua 5.5 source runtime as the only supported language target.
  • A C-Lua-like low-level LuaState stack API.
  • An mlua-style high-level embedding API for Java and Kotlin users.
  • A clean internal bytecode compiler and interpreter before any JVM bytecode generation work.
  • Source-level debugging support as a runtime feature, not as a wrapper around the JVM debugger.

Implementation starts clean. There is no requirement to preserve old project APIs, temporary aliases, transitional shims, or backward behavior before v1.

Non-Negotiable Architecture Rules

  • Keep internal runtime implementation in klua-core.
  • Keep stable Java-friendly public APIs in klua-api.
  • Keep Kotlin convenience APIs in klua-kotlin.
  • Keep standard libraries in klua-stdlib.
  • Keep debugging internals in klua-debug.
  • Keep Debug Adapter Protocol integration in klua-dap.
  • Keep command-line tools in klua-tools.
  • Keep benchmarks in klua-jmh.
  • Keep language, integration, and conformance tests in klua-tests.
  • All production and test packages must start with io.github.realmlabs.klua.
  • Do not expose VM, compiler, bytecode, stack, frame, parser, or AST internals from public API modules.
  • Do not add compatibility aliases for old project APIs.
  • Do not add JVM bytecode generation before the interpreter, bytecode format, APIs, and benchmarks are stable.
  • Do not optimize without a benchmark baseline.

Delivery Rules

  • Define a work package around one observable capability, Lua semantic rule, or tightly related official-source helper chain. A package may cross core, API, and standard-library entry points when they implement the same rule.
  • Before implementation, identify the owning milestone, intended outcome, affected modules, relevant Lua 5.5 source path, verification, and exit criteria.
  • Prefer one to three coherent commits per work package. Commit count is not a progress metric.
  • Keep each commit independently reviewable, revertible, and green, but do not treat one file, function, probe, assertion, or test method as an automatic commit boundary.
  • Put a direct behavior fix and its regression tests in the same commit unless a prerequisite refactor is independently meaningful and verified.
  • Group test-only coverage discovered in the same semantic audit into a case matrix or focused test commit. Use a standalone test commit only when the coverage is independently valuable and the implementation is already correct.
  • When a behavior change resolves or materially narrows a documented conformance gap, update the gap documentation in the same work package and normally in the final behavior commit.
  • Keep unrelated milestone work, independently risky refactors, and public API changes in separate commits.
  • Use no hard line-count or file-count target. The semantic and rollback boundary decides commit size.
  • Every commit should have an obvious verification command, test, golden output, or documentation check.
  • Run the relevant verification before committing. If verification cannot be run, document why and get explicit user approval before committing.
  • Use Conventional Commit messages for all commits.
  • Commit messages should use a clear type and scope, such as feat(core): add token model, test(parser): cover numeric literals, or docs(goal): clarify delivery rules.
  • Do not mark work complete until its verification has been run or the reason it could not be run is documented.

Execution Planning And Milestone Closure

  • Treat docs/KLua_Implementation_Milestones.md as the capability roadmap, not as a live task queue or a commit map.
  • Keep the live work-package order in Next Implementation Focus below. Limit it to a small set of current and immediately following packages.
  • Mark relevant milestone success criteria as Done, Partial, or Gap before declaring a milestone closed or making a later milestone the primary frontier.
  • Finish or explicitly defer confirmed blockers in the earliest active milestone before expanding later surface area. A later task may still be done when it strictly unblocks the earlier milestone.
  • Do not use isolated conformance probes as default filler work. Select related findings into a named, source-backed campaign with a case matrix and an exit condition.
  • A conformance gap document is evidence and scope tracking, not a FIFO backlog. Prioritization belongs in the live execution plan.
  • Update the live plan when a work package closes, milestone reality changes, or new evidence materially changes priority. Do not update it for every commit.

File Structure Rules

  • Production Kotlin files target <=1000 lines of code.
  • Files approaching 1000 lines should be split by responsibility before adding unrelated behavior.
  • Test files may exceed production files when scenarios are cohesive.
  • Large test fixtures, golden outputs, generated files, and bytecode snapshots must be separated into clearly named fixture, golden, generated, or snapshot paths.
  • A file should have one main responsibility. Split by lexer/parser/compiler/VM/API/debug/stdlib behavior instead of creating broad utility files.
  • Prefer small package-private or internal helpers over shared global helpers when behavior belongs to one subsystem.

Language Target Policy

  • Lua 5.5 is the only supported source-language target.
  • Do not add old-version runtime modes, flags, aliases, shims, or source-version selection APIs.
  • Do not support official PUC Lua .luac bytecode in v1.
  • Use one internal KLua bytecode format.
  • Compiled prototypes should not expose a language-version field. Future bytecode packages may carry a fixed Lua 5.5 marker for validation and diagnostics, but this must not become a public version-selection API.
  • Treat Lua 5.5 feature gaps as conformance work, not compatibility-profile work.

Reference Behavior Policy

  • For behavior-sensitive implementation work, inspect the official Lua 5.5 source code in ~/Downloads/lua-lua-a5522f0 before deciding semantics.
  • Start from the source file that owns the behavior, for example parser logic in lparser.c, VM execution in lvm.c, table behavior in ltable.c, coroutine behavior in lcorolib.c, package loading in loadlib.c, and standard-library functions in the matching l*.c library file.
  • Follow the relevant helper functions far enough to understand the real logic. This includes luaL_* argument checks, conversion helpers, metamethod dispatch helpers, string/number formatting helpers, table boundary helpers, stack discipline, and error-message construction.
  • Treat the source implementation as the primary reference for actual logic: argument coercion, integer conversion, stack effects, metamethod lookup order, continuation/yield rules, error construction, boundary cases, and library-specific special cases.
  • When changing behavior copied from or checked against Lua, record the relevant official source file and function in working notes, tests, commit messages, or concise implementation comments when that context would make the change easier to audit.
  • Use the local lua5.5 executable for observable behavior checks, but use the Lua 5.5 source code to understand the actual control flow, coercion rules, error paths, and edge-case logic behind that behavior.
  • The manual and local lua5.5 checks explain observable behavior; the official source explains the logic that KLua should mirror in Kotlin.
  • Do not infer final behavior from existing KLua tests, older Lua manuals, internet examples, or memory when the local official Lua 5.5 source can be inspected.
  • When the manual, local tests, or existing KLua behavior are ambiguous or incomplete, treat the Lua 5.5 source code as the implementation reference and document any intentional KLua deviation as a conformance gap.
  • Prefer focused tests derived from the reference source path being implemented. When helpful, note the relevant Lua source file or function in the test name, commit message, or implementation comment.
  • Existing KLua tests are not authoritative when they conflict with official Lua 5.5 source behavior; update those tests as part of the conformance fix.

Public API Concepts

These concepts should exist as the initial public API direction. Exact method signatures may evolve before v1.

  • LuaState: low-level stack API for Java and Kotlin embedders.
  • Lua: high-level embedding facade.
  • LuaChunk: loaded source or bytecode chunk.
  • LuaConfig: runtime configuration.
  • LuaStatus: non-throwing low-level call result.
  • LuaException: base structured exception type for high-level APIs.
  • LuaReturn: return values from host functions.
  • LuaCallContext: arguments and runtime context for host function calls.

Public APIs may change freely before v1. After v1, compatibility becomes a release concern and breaking changes require explicit migration notes.

Implementation Order

Follow this order unless a later task is strictly necessary to unblock an earlier one:

  1. M0 project foundation.
  2. M1 lexer and parser.
  3. M2 AST and compiler skeleton.
  4. M3 minimal bytecode VM.
  5. M4 expressions, locals, branches, and loops.
  6. M5 functions, calls, returns, and varargs.
  7. M6 tables.
  8. M7 closures and upvalues.
  9. M8 metatables and metamethods.
  10. M9 low-level LuaState API.
  11. M10 high-level embedding API.
  12. M11 userdata and JVM interop.
  13. M12 standard library.
  14. M13 coroutines.
  15. M14 error handling, tracebacks, and debug metadata.
  16. M15 debug hooks and source-level debugger.
  17. M16 DAP adapter and debug tooling.
  18. M17 script packaging and bytecode loading.
  19. M18 sandbox and game-server limits.
  20. M19 benchmark-driven performance pass.
  21. M20 Lua 5.5 conformance hardening.
  22. M21 v1.0 release.
  23. M22 optional JVM bytecode compiler.

The first major proof point is:

Lua source -> lexer/parser -> AST -> compiler -> KLua bytecode -> VM -> observable result

Current Status And Remaining Gaps

This section is a rolling implementation snapshot, not a change log. Update it when milestone reality materially changes; do not record per-commit history here.

Current implemented areas:

  • Multi-module Gradle project with Kotlin/JVM 17 modules and tests.
  • Lexer and parser for current supported Lua syntax.
  • AST model, compiler, internal bytecode, prototype model with a consolidated debug-info view and serialization-friendly snapshot hook covering source IDs, valid breakpoint line metadata, local variable debug ranges, upvalue name metadata, function definition line ranges, Lua-style const and <close> local assignment rejection, for-control assignment rejection, regular and <const> named/wildcard global declaration scopes, initialized global declarations, local-shadowing resolution, lexical _ENV lowering, closure-shared default environment assignment, plain function declarations through resolved bindings including const-global rejection, global function declarations with Lua-style already-defined checks, conservative block-scoped, close-aware goto/label compilation including end-of-block labels and exported pending gotos, close-aware break exits, and generic-for fourth-value closing, plus constant pool, disassembler, initial internal constant-pool, instruction-stream, recursive prototype serialization, prototype bytecode package encoding/decoding with fixed KLua/Lua 5.5 format markers plus payload size/checksum metadata, and core/API runtime bytecode package compile/load helpers.
  • Interpreter VM with core values, stack/frame execution, expressions, locals, branches, loops, functions, calls, returns, varargs, tables, closures, upvalues, metatables, metamethods, globals, native functions, basic userdata bindings, initial instruction-limit enforcement for chunk execution, exported Lua function calls, and coroutine resumes, plus internal thread/yield/resume/dead-state plumbing, full to-be-closed lifecycle unwinding across normal, control-flow, error, yield/resume, and coroutine-close boundaries, and Lua/native continuation for __index/__newindex reads and writes and value-producing arithmetic, bitwise, unary, length, comparison, and concatenation metamethods.
  • Java-friendly LuaState API, high-level Lua facade, Kotlin convenience helpers, single-target runtime configuration, bytecode package resource loading, configurable instruction limits, standard-library whitelisting, a bounded LuaConfig.production() preset that suppresses debugger and unsafe standard-library host access while preserving explicit host bindings, and initial JMH baselines separating parse/compile/package encoding from fresh-coroutine VM numeric-loop execution.
  • Runtime errors preserve structured source-name, line, Lua call-frame metadata including function definition, arity, upvalue, and active-line data, registered global and userdata host/native call-frame metadata, readable traceback strings, and API-visible explicit error objects from VM bytecode positions through core execution results, API runtime exceptions, direct native protected calls, and API coroutine runtime results, and host exceptions can survive as runtime error causes.
  • Partial klua-stdlib support with base, math, string, table, utf8, package, coroutine, initial os, source-backed io, and minimal debug library installers covered by focused Lua-source tests, with config-level standard-library whitelisting and debug-library opt-out for production-style configs, including Lua 5.5-style math random xoshiro state and range projection, Lua warning control handling, Lua-style require searcher diagnostic prefixing, raw searcher iteration, original loaded/preload table binding, package.cpath, pure-Kotlin package.loadlib and C/C-root searcher diagnostics including disabled-library reporting, Lua-style source-prefixing and nil error-object normalization for assert/error through protected calls and coroutine resume/close errors, table-backed load/loadfile environment arguments, Lua-style load reader failure results, Lua-style loadfile mode validation ordering, stateless utf8.codes iterator controls, Lua-backed coroutine yield/resume, protected pcall/xpcall yield continuation, xpcall message-handler result/error behavior, wrap/close/isyieldable behavior, main/normal coroutine status and close reporting, coroutine thread type/string reporting, host/native yield-boundary checks, Lua-style tostring userdata identity and __name handling, deterministic Lua-owned __gc enrollment/finalization with weak-table and userdata-uservalue tracing plus logical full/step/automatic scheduling, Lua-frame-backed debug.traceback, optional coroutine arguments for debug.traceback/debug.getinfo/debug.getlocal/debug.setlocal/debug.sethook/debug.gethook across current/main, fresh, yielded, normal, dead-success, failed-before-close, and reset-after-close thread states, level-based and function-value debug.getinfo source/currentline/function-definition metadata and common call-site name metadata for Lua and host/native functions including Lua 5.5 compile-time <const> substitution for callees and field keys, debug.getinfo option filtering/default metadata including arity/upvalue/active-line/transfer/tail-call fields plus call/return hook transfer metadata, level-based debug.getlocal local name/value inspection with out-of-range level errors, function-value debug.getlocal local-name inspection, debug.setlocal local mutation with out-of-range level errors, debug.getupvalue closure upvalue inspection, debug.setupvalue closure upvalue mutation, debug.upvalueid/debug.upvaluejoin closure upvalue identity and sharing, host-userdata debug.getuservalue/debug.setuservalue slots, table and supported non-table debug.getmetatable/debug.setmetatable including host-userdata metatables, table.concat/table.insert/table.move/table.remove/table.sort support for table-like non-table values with required metamethods, source-like table.unpack length/index behavior for table-like non-table values, userdata index/newindex/call/length/comparison/arithmetic/bitwise/concat/unary metamethods, userdata __name index/call/length/operator errors, os.clock/os.date string and table formats/os.difftime/os.exit embedder-safe exit signaling/os.getenv/os.remove/os.rename/os.setlocale/os.time date-table conversion/os.tmpname, debug.getregistry, host-adapted interactive debug.debug, Lua-style debug.sethook/debug.gethook call/return/line/count hook support including targeted coroutine hooks, and Lua-style argument validation for these debug/package/os entry points plus selected base/table iterator edge cases.
  • Initial klua-debug source-line breakpoint manager for setting, replacing, source-wide replacement, clearing, listing, and enabled-hit lookup by source ID and line, public Lua stack-frame, local-variable, locals/upvalues/globals-scope, and paged table-variable debugger views with stable value summaries, opt-in userdata display adapters, a debug controller for pause, breakpoint, conditional-breakpoint, and step stop decisions, and a transport-independent live session that suspends coroutine-backed VM execution at breakpoint/step line events, exposes stopped source lines plus live locals/upvalues/globals, and evaluates scalar expressions in a fresh read-only snapshot environment.
  • klua-dap typed and wire sessions for the core DAP lifecycle, breakpoint, execution-control, thread, stack, scope, variable, and evaluation requests, including a LiveDapSession that shares breakpoint definitions across independently stepped coroutine threads, resumes real VM suspensions, exposes live frames/scopes/variables/evaluation, and emits stopped/thread/terminated events through the wire bridge.
  • klua-tools CLI debugger support for klua --debug <script.lua> [args...] and its break, run, continue, next, step, out, bt, locals, print, and quit commands over a live source or .kluac top-level coroutine, including stopped-frame inspection and read-only scalar evaluation; bytecode package compilation remains available through klua --compile <script.lua> <output.kluac>.
  • String pattern support covers literals, dot wildcard, anchors, Lua character classes, bracket classes/ranges, bracketed percent classes, optional single-item matches, greedy/minimal single-item repetitions, basic captures for find, match, gsub, and gmatch, backreferences, balanced matches, and frontier matches.
  • string.gsub supports string, function, and table replacements with Lua-style capture arguments and nil/false preservation.
  • string.packsize, string.pack, and string.unpack cover Lua 5.5 format parsing, endian directives, alignment, fixed and variable strings, integer formats, and floating-point formats.
  • Table-library conformance includes table-like primitive receivers for table.concat, table.unpack, table.insert, table.remove, table.move, and table.sort when Lua 5.5 checktab/metamethod requirements are met.
  • IO-library conformance includes source-ordered read-format parsing and write-argument conversion/execution across direct and default-file entry points, including partial progress before later argument errors, read short-circuiting after EOF or file errors, zero-argument writes, and write-failure byte counts.
  • Live debugger runtime registration assigns stable thread IDs to independent coroutine sessions; debug-disabled API configurations reject execution observers, and the normal VM opcode path performs only a null-observer check.
  • Focused parser, compiler, VM, API, Kotlin helper, conformance, and foundation tests.

Remaining major gaps:

  • Broader Lua language and conformance hardening.
  • Broader standard library implementation, including table edge cases, string pattern/format, math edge cases, and utf8 coverage.
  • Broader coroutine runtime hardening and Lua 5.5 conformance coverage beyond the current Lua-backed, protected-call, index/newindex read/write-opcode, and arithmetic/bitwise/unary/length/comparison/concatenation metamethod yield/resume paths.
  • Broader error, traceback, and debug-library conformance beyond the closed M14 foundation criteria.
  • A packaged standalone DAP adapter host and richer debugger scheduling/evaluation policy beyond the closed M16 library-level integration.
  • Broader script packaging workflow, including optional Gradle/plugin integration.
  • Broader sandbox and game-server controls beyond the initial chunk/function/coroutine instruction budget and standard-library whitelist.
  • Benchmark-driven performance pass.
  • Lua 5.5 conformance hardening.

M13-M18 Closure Audit

Done means the milestone success criterion is implemented and covered by committed tests. Partial means reusable pieces exist but the criterion is not yet proven end to end. Gap means the required runtime behavior is not connected yet. Broader conformance hardening that is not required by a criterion remains M20 work.

M13 Coroutines

Success criterion Status Evidence
Yield/resume works across Lua frames. Done Core/API coroutine runners and the coroutine standard library cover repeated Lua-frame yield/resume and completion.
Coroutine errors are reported correctly. Done Runtime, API, and standard-library tests cover source-bearing errors, dead-state resumes, close behavior, and protected-call propagation.
Non-yieldable host calls are detected. Done Core and standard-library tests cover native boundary tracking and the Lua-style attempt to yield across a C-call boundary failure.

M13 is closed against its success criteria. Additional nested-coroutine and cross-thread conformance cases are M20 hardening, not closure blockers.

M14 Error Handling, Tracebacks, And Debug Metadata

Success criterion Status Evidence
Errors identify source file and line. Done Core and API runtime-error tests assert structured source names and bytecode-derived lines.
Stack traces include Lua frames. Done Core, API, coroutine, and debug.traceback tests cover nested Lua frames and readable tracebacks.
Stack traces can include useful Java/Kotlin host call frames. Done Registered global and userdata host-call tests assert [Kotlin] frames and host names.
Function prototypes carry enough metadata for breakpoints and later local inspection. Done Compiler tests cover source IDs, line maps, valid breakpoint lines, function ranges, locals, and upvalue names.
Local variable metadata can be queried in tests. Done Compiler and debug-library tests query local names, slots, lifetimes, and active frame values.
Debug metadata survives compile/load round trips. Done Recursive prototype codec tests round-trip line, local, upvalue, call-site, and breakpoint metadata.
Bytecode packages can optionally include or strip debug metadata. Done Recursive stripped-prototype and string.dump(..., true) tests cover debug removal while retaining executable structure.
Java/Kotlin host exceptions are wrapped clearly. Done API tests preserve the host cause and expose structured Lua and host frames.

M14 is closed against its foundation criteria. Broader debug-library and traceback conformance remains M20 work.

M15 Debug Hooks And Source-Level Debugger

Success criterion Status Evidence
A script can stop at a breakpoint. Done A VM debug observer produces a distinct suspension before the selected line's opcode and LiveDebugSession returns the real stopped context.
A script can step into, over, and out. Done Live integration tests drive line events and call depth through nested Lua frames for all three step modes.
The debugger can show the current source line. Done Stopped frame snapshots preserve the suspended PC and report the exact source line.
Locals, upvalues, and globals are inspectable. Done Live stopped frames populate all three scopes with local/upvalue shadowing information preserved by separate views.
Debug expression evaluation works for simple expressions. Done Selected-frame evaluation copies scalar global/upvalue/local bindings into a fresh library-free Lua state, preserving shadow order without mutating the stopped program.
Coroutines appear as debuggable Lua threads. Done LiveDebugRuntime assigns stable IDs and independent sessions to registered Lua coroutine functions; integration tests suspend multiple registered threads separately.
Debug checks are cheap or disabled in normal fast execution. Done Debug-disabled API configurations reject execution observers, and the opcode loop guards observer dispatch with a single null check when no live debugger is attached.

M15 is closed against its success criteria. Table/function-aware evaluation, mutation-enabled trusted evaluation, and richer multi-thread scheduling policy remain later debugger hardening rather than closure blockers.

M16 DAP Adapter And Debug Tooling

Success criterion Status Evidence
A script can be launched under the CLI debugger. Done CLI command-loop and runner tests launch source and packaged-bytecode chunks as coroutine-capable top-level functions and stop live VM execution.
Breakpoints and stepping work through the debug controller. Done CLI and DAP integration tests set breakpoints, observe exact live stop lines, and resume with step-over through each thread's controller.
A DAP client can set breakpoints and step through Lua code. Done Wire-level tests drive setBreakpoints, start the registered coroutine, issue next/continue, and receive live stopped and lifecycle events.
A DAP client can request stack frames and variables. Done The live wire integration test reads the stopped stack, all three scopes, locals, and selected-frame evaluation from the VM context.
Coroutines appear as debugger threads. Done LiveDapSession registers stable runtime thread IDs, and the wire test lists multiple named coroutine threads while controlling one independently.
Large tables do not freeze the debugger UI. Done Variable expansion is paged and paging behavior is covered by debug/DAP tests.
Debugger can be disabled completely through production runtime configuration. Done Live CLI and DAP registration tests prove that debugEnabled = false rejects debugger observer attachment.

M16 is closed against its library-level success criteria. Packaging a standalone DAP server process remains optional tooling expansion rather than a runtime-integration blocker.

M17 Script Packaging And Bytecode Loading

Success criterion Status Evidence
Runtime can load compiled bytecode. Done Core, API, resource-loading, CLI, and integration tests compile and execute .kluac packages.
Bytecode version mismatch is detected. Done Package/core tests reject unsupported fixed KLua/Lua format markers and checksum/size corruption.
Source mode and bytecode mode produce the same results. Done Cross-module integration tests compare nontrivial source and packaged-bytecode results.

M17 is closed against its success criteria. Gradle/plugin integration remains an optional packaging expansion.

M18 Sandbox And Game-Server Limits

Success criterion Status Evidence
Infinite loops can be stopped. Done Instruction limits are enforced for chunks, exported functions, and coroutine resumes with source-bearing errors.
Scripts cannot access OS/files unless allowed. Done LuaConfig.production() excludes package/I/O/OS, removes base dofile/loadfile, and enforces the unsafe-library gate even if those libraries are accidentally listed or opened directly; an explicit compatibility opt-in restores them.
Host APIs can be selectively exposed. Done Host globals/functions and registered userdata types are exposed explicitly by the embedder; unregistered host members are unavailable.

M18 is closed against its success criteria. Memory accounting, wall-clock timeout integration, deterministic-random policy, and finer-grained permissions remain optional sandbox hardening rather than closure blockers.

Completed Initial Proof Point

The initial implementation slice has already proven a minimal language pipeline:

  • Multi-module Gradle project using Kotlin/JVM 17.
  • Lexer and parser for simple chunks.
  • AST model for literals, expressions, locals, branches, and returns.
  • Prototype and bytecode instruction encoding.
  • Bytecode disassembler for tests and debugging.
  • Simple boxed LuaValue model.
  • Minimal VM stack, call frame, and interpreter loop.
  • Behavior tests that can compile and run return 42.

This proof point should remain covered by tests while later milestones evolve the runtime.

Next Implementation Focus

The closure audit above closes M13 through M18 against their success criteria. The bounded M19 continuation is complete and M20 is the current frontier. Compile, numeric-loop, Lua-call, closure-counter, host-call, table read/write, function-valued __index, JVM-to-Lua call, method-call, recursive Fibonacci, growing string-concatenation, coroutine yield/resume, fixed-arity vararg/multi-return, and table-backed entity-update baselines now exist. Profiling the first selected hotspot showed that eager Lua debug-frame snapshots dominated the host-call bridge; lazy on-access frame materialization reduced profiled allocation by 65.4% and the matched full-suite host-call score by 55.5%. Profiling the second hotspot showed that ordinary table accesses eagerly allocated metamethod cycle-detection sets; matching Lua 5.5's raw-fast-path/finish-operation split reduced table-workload allocation by 59.5% and profiled time by 10.3%. JFR profiling of the JVM-to-Lua control then showed scalar conversions eagerly allocating table-identity caches; lazy graph-cache creation reduced that workload's allocation by 44.3% and profiled time by 16.0%. The same evidence on coroutine transfer showed specialized argument-synchronization caches on every scalar yield/resume; lazy creation reduced allocation by 35.5% and the matched full-suite score by 15.9%. Method-control profiling then showed repeated Lua-byte reconstruction in every string-key hash/equality probe; caching immutable raw bytes and hashes on demand reduced method allocation by 84.9%, matched profiled time by 65.0%, and the full-suite method score by 64.2% without materializing bytes for transient non-key strings. Residual coroutine profiling then selected eager VM native-context service callbacks; consolidating them into one VM-backed context reduced coroutine allocation by 7.4%, matched profiled time by 11.6%, and the post-string-cache full-suite score by 24.5%. A matched host/coroutine comparison next selected API-side service lambdas; passing one core-backed call context directly reduced coroutine allocation by a further 7.0% and host allocation by 10.8%. Timing was too noisy for a supported claim in that package. Matched result profiling then rejected a broad scalar/list rewrite but selected the coroutine-only double copy used to prefix successful resume values; single-pass prefixing reduced coroutine allocation by 3.1% with host and JVM-to-Lua controls unchanged. The first application-kernel follow-up removed an intermediate vararg-range materialization, reducing vararg/multi-return allocation by 10.8%; timing was too noisy for a supported claim. A numeric-loop follow-up rejected a redundant-looking integer-limit fast path after matched GC proved that HotSpot already eliminates its temporary wrapper and JFR showed immutable value boxing is the representation-level residual. Static NEW_TABLE entry hints then reduced entity-kernel allocation by 2.0% and removed the selected resize hotspot without affecting evaluation order. Lazy stack capture storage reduced ordinary Lua-call allocation by another 14.0% while preserving shared and closed upvalue identity. Splitting suspension and debug/hook fields out of ordinary call frames then reduced Lua-call allocation by a further 6.1%. Retaining one shared call-site metadata reference instead of two expanded fields reduced it by another 2.2%. Array-backed stack slots then removed another 24 bytes per created Lua frame, shared empty vararg views removed a further 24 bytes from fixed-arity frames, direct Lua-to-Lua argument transfer removed the remaining intermediate argument collection, singleton return snapshots removed another 24 bytes per one-result call, co-locating stack state with its frame removed the separate wrapper object, removing obsolete shared-stack offsets reduced frames by another 16 bytes, deriving prototype/upvalue metadata from each frame's exact closure removed another 8 bytes per frame, direct post-hook Lua-to-Lua result transfer removed the remaining per-call return wrapper and snapshot, reusing fixed metamethod call-site records removed repeated diagnostic name construction, and direct fixed-arity Lua metamethod entry removed the remaining two- and three-argument snapshot wrappers. The proposed single-result metamethod specialization was then rejected when the Lua 5.5 continuation audit exposed missing yield completion; correctness work interrupted the performance sequence and completed read-opcode __index, write-opcode __newindex, value-producing arithmetic/bitwise/unary/length, comparison, and concatenation continuation slices. Direct core-backed protected calls and a shared live metatable provider then removed the latest API/VM bridge residuals. All retained packages pass the full correctness suite.

M19 Application-Kernel Coverage Checkpoint

The fixed-arity control rotates three values through a vararg function and three-result assignment 10,000 times, with setup requiring checksum 312. Its behavior follows Lua 5.5's OP_VARARG handling in lvm.c, result movement in ldo.c:moveresults, and parser emission of OP_VARARG/OP_VARARGPREP in lparser.c. The table-backed control creates 1,000 entity records, runs ten position-update passes, and requires checksum 1,511,500. Compilation remains outside both measured operations.

Focused GC measurement records 3,886.439 us/op and 5,201,811.110 B/op for vararg/multi-return, versus 5,477.484 us/op and 1,563,492.372 B/op for the entity kernel. The all-benchmark 14-control run records compile 8.040, numeric loop 1,108.178, Lua calls 2,475.068, closure counter 2,236.065, host calls 2,762.559, table read/write 2,469.676, function-valued __index 3,747.251, JVM-to-Lua calls 3,540.765, method calls 5,052.486, recursive Fibonacci 6,350.335, string concatenation 3,634.071, coroutine yield/resume 14,935.196, vararg/multi-return 3,800.991, and entity update 5,297.183 us/op. JFR selects the vararg workload for the next package: CollectionsKt.toMutableList contributes 48.46% of sampled allocation pressure, followed by array copying at 10.84%, call-frame push at 10.44%, and stack slicing at 10.20%.

M19 Vararg Copy Checkpoint

KLua previously formed each stored vararg sequence with arguments.drop(numParams).toMutableList(), allocating an intermediate range before the required mutable snapshot. Lua 5.5's ltm.c:luaT_adjustvarargs and luaT_getvarargs require independent extra-argument storage, nil preservation, all-result expansion, and nil padding for fixed-result reads, but do not require that intermediate collection. One exact-capacity range copy preserves those semantics; a focused ownership test proves later caller-list mutation cannot alter a frame's varargs and frame mutation cannot alter the caller list, including an embedded nil.

Matched GC measurement reduces vararg/multi-return allocation from 5,201,811.110 to 4,641,739.572 B/op, a reduction of 560,071.538 B/op or 10.8% (about 56 bytes per rotated call). Entity-update allocation remains effectively unchanged at 1,563,477.874 B/op. Post-change JFR no longer contains CollectionsKt.toMutableList; the necessary single-copy copyVarargs snapshot accounts for 8.16% of sampled pressure. Focused and full-suite timing remains too noisy to support a speed claim; the final 14-control run completes with vararg/multi-return at 3,983.178 us/op. The same JFR now selects LuaVm.advanceForLoop at 51.80% of sampled allocation pressure for a separate numeric-loop audit.

M19 Numeric-Loop Rejection Checkpoint

Lua 5.5's lvm.c:forprep, floatforloop, and OP_FORLOOP keep integer loop count, step, and visible control value in inline tagged stack slots; unsigned iteration counts preserve termination across integer overflow. KLua already preserves the corresponding visible-value and overflow behavior, but its immutable LuaInteger value model boxes each updated control value and arithmetic result. A proposed fast path avoided constructing ForIntegerLimit.Run after FOR_TEST had normalized the limit, but matched GC was unchanged: numeric-loop allocation moved only from 961,433.021 to 961,433.523 B/op, and vararg/multi-return from 4,641,739.277 to 4,641,744.162 B/op. The change was removed.

Allocation-by-class JFR attributes 98.49% of numeric-loop pressure to LuaInteger, with 42.56% sampled at loop advancement, 39.04% in body arithmetic, and 16.89% in frame/return execution. This makes further improvement a value/stack representation package rather than a safe local loop change, so it is deferred. Profiling the entity-update control instead selects a bounded next target: HashMap.resize accounts for 67.22% of sampled allocation pressure while each four-field record grows its initially empty hash storage.

M19 Table-Literal Capacity Checkpoint

Lua 5.5's lvm.c:OP_NEWTABLE decodes separate array and hash size operands and calls luaH_resize before constructor field writes. KLua uses one insertion-ordered map for both key classes, so its compiler now records one conservative total-entry hint in the unused NEW_TABLE B operand, capped at 255, and the VM converts that hint to a load-factor-safe LinkedHashMap capacity. Empty and dynamically grown tables retain their prior zero-hint path. Compiler disassembly/cap tests and a mixed keyed, named, and list-field VM test cover the encoding and evaluation order.

Matched GC reduces the 1,000-record entity kernel from 1,563,477.874 to 1,531,429.602 B/op, a reduction of 32,048.272 B/op or 2.0% (about 32 bytes per record). The focused entity score is effectively flat at 5,439.685 us/op, and a machine-wide shift during the final 14-control run prevents a supported suite timing claim. Post-change JFR reduces sampled HashMap.resize pressure from 67.22% to 0.90%; map entries and boxed arithmetic are now the representation-level entity residual. A fresh Lua-call profile selects the next local cost: eagerly empty LuaStack.captures maps account for 56.45% of sampled allocation pressure.

M19 Lazy Stack-Capture Checkpoint

Lua 5.5's lfunc.c:luaF_findupval searches the open-upvalue list and creates storage only when a closure first captures a stack slot; repeated capture of that slot reuses the same UpVal, and luaF_closeupval unlinks it while preserving its value. KLua now follows the same lifetime more closely: LuaStack creates its slot-to-upvalue map on first capture, reuses entries, drops the map after its last open capture closes, and otherwise performs only a nullable check. A focused stack test proves open identity, value synchronization, close detachment, and independent reopening; the existing closure/upvalue suite remains green.

Matched GC reduces the 10,000-call Lua control from 4,561,530.081 to 3,921,465.806 B/op, a reduction of 640,064.275 B/op or 14.0% (about 64 bytes per call). The capture-bearing closure control completes at 3,602,193.695 B/op, and the final 14-control run records Lua calls at 2,432.232 us/op and closure calls at 2,354.365 us/op; focused timing is directionally lower but too noisy for a supported claim. Post-change JFR removes the empty LinkedHashMap and dominant LuaStack construction sites. CallFrame now accounts for 72.05% of sampled Lua-call allocation pressure and is the next bounded audit target.

M19 Call-Frame Cold-State Checkpoint

Lua 5.5's lstate.h:CallInfo uses unions and status bits so Lua-only, C-continuation, yield, and close state share storage rather than expanding every active call. KLua's ordinary frame previously carried two pending-result integers, a continuation reference, and six hook/debugger integers even when execution could neither suspend nor be observed. Pending-call data is now one atomically installed lazy state used only across yield or debugger suspension, while hook-transfer and debugger-PC data share a second lazy state. Core suspension tests plus the complete coroutine, debug-hook, live-debugger, and DAP suites cover the cold paths.

Matched GC reduces Lua-call allocation from 3,921,465.806 to 3,681,441.811 B/op, a reduction of 240,023.995 B/op or 6.1% (about 24 bytes per call). The closure control falls from 3,602,193.695 to 3,362,145.779 B/op, the same per-call effect. Coroutine yield/resume remains green with lazy pending state and measures 22,878,495.070 B/op. Timing remains too variable for a supported claim; the final 14-control run completes with Lua calls at 3,396.977 us/op. Post-change JFR still selects CallFrame at 57.12% of sampled runtime allocation, but pooling is excluded because frames can remain suspended and debugger-visible. The next audit instead targets the two call-site metadata references already represented by one shared immutable CallSiteInfo in the prototype.

M19 Call-Site Metadata Checkpoint

Lua 5.5's ldebug.c:funcnamefromcode, funcnamefromcall, and getfuncname derive a callee's contextual name from the caller prototype and calling program counter instead of expanding separate name strings into every CallInfo. KLua's compiler already records one immutable CallSiteInfo per relevant prototype instruction. Each frame now retains that shared object as one reference, and computed accessors preserve the existing traceback, runtime-error, and debug-name behavior.

Matched GC reduces Lua-call allocation from 3,681,441.811 to 3,601,433.679 B/op, a reduction of 80,008.132 B/op or 2.2% (about 8 bytes per call). Recursive Fibonacci falls from 9,195,942.593 to 9,020,804.720 B/op, a reduction of 175,137.873 B/op across its 21,891 calls, again about 8 bytes per call. Timing is too noisy for a supported claim; the final 14-control run completes with Lua calls at 2,423.077 us/op and recursive Fibonacci at 6,033.997 us/op. The complete correctness suite, including bytecode round trips and call-site name/traceback assertions, remains green. Post-change runtime JFR distributes the residual across call-frame push (14.01%), integer arithmetic (13.76%), loop advancement (12.99%), and stack initialization (10.83%), so the next bounded package targets stack storage rather than another metadata field.

M19 Array-Backed Stack Checkpoint

Lua 5.5 represents thread stack slots as one contiguous StackValue vector. ldo.c:luaD_growstack grows that vector geometrically while respecting the requested minimum, and luaD_reallocstack nil-initializes its new segment and repairs stack references through offsets. KLua now stores each frame's slots directly in a LuaValue array, grows it by 1.5x or to the requested index, and fills new positions with LuaNil. KLua's open captures already synchronize through stable slot indices and independent LuaUpvalue cells, so array relocation requires no externally visible repair. Focused tests cover skipped-slot nil initialization, copying and slicing after growth, and open-capture synchronization across growth.

In an immediate array/list control comparison, matched GC reduces 10,000 ordinary Lua calls from 3,601,437.479 to 3,361,409.783 B/op, a reduction of 240,027.696 B/op or 6.7% (about 24 bytes per created stack). Recursive Fibonacci falls from 9,020,807.412 to 8,495,397.761 B/op, a reduction of 525,409.651 B/op or 5.8% across 21,891 calls, again about 24 bytes per stack. The closure control falls from 3,282,131.589 to 3,042,049.840 B/op, while coroutine yield/resume is effectively unchanged at 22,878,435.052 B/op. Timing is too machine-noisy for a supported claim. The complete correctness suite and final 14-control run pass. Post-change JFR removes the collection wrapper from stack construction and selects the remaining eager empty ArrayList created for fixed-arity frame varargs as the next bounded audit.

M19 Fixed-Arity Empty-Vararg Checkpoint

Lua 5.5 runs ltm.c:luaT_adjustvarargs only for prototypes marked vararg; luaT_getvarargs then reads those hidden extra arguments. Fixed-arity CallInfo records do not own a separate mutable empty collection. KLua now mirrors that split with nullable mutable backing on the frame: fixed-arity calls expose the shared read-only empty list, while real vararg calls retain their exact owned mutable snapshot. Debugger vararg writes go through a bounds-checked frame method, preserving debug.setlocal behavior without allowing writes into fixed frames. Focused tests prove shared fixed empties, rejected fixed writes, and mutable vararg ownership independent of the caller list.

The immediate eager/shared control comparison reduces 10,000 fixed-arity Lua calls from 3,361,410.401 to 3,121,410.785 B/op, a reduction of 239,999.616 B/op or 7.1% (about 24 bytes per call). The actual vararg/multi-return control remains effectively unchanged at 3,441,618.883 B/op versus 3,441,620.700 B/op in the eager control. Timing is too noisy for a supported claim. The complete correctness suite and final 14-control run pass, with Lua calls at 3,145.241 us/op and vararg/multi-return at 4,699.574 us/op. Post-change JFR selects LuaStack.slice at 55.13% of sampled Lua-call allocation pressure, primarily from materializing Lua-to-Lua argument lists.

M19 Lua-to-Lua Argument Transfer Checkpoint

Lua 5.5's ldo.c:luaD_precall receives a Lua closure and its arguments in adjacent stack slots, prepares the callee CallInfo, and pads missing fixed parameters directly; native functions retain their separate C-call path. KLua now specializes the same boundary: a Lua-closure call copies the caller's evaluated stack range directly into the new frame, including an exact owned copy of any extra varargs. Native calls and __call chains keep their existing snapshot-list API. Focused range tests plus the function-call suite cover fixed parameters, nil fill, vararg ownership, open-result expansion, evaluation order, call-site metadata, and suspension behavior.

An immediate feature-toggle comparison reduces 10,000 one-argument Lua calls from 3,121,409.907 to 2,641,409.476 B/op, a reduction of 480,000.431 B/op or 15.4% (about 48 bytes per call). The three-argument vararg/multi-return control falls from 3,441,618.529 to 2,881,619.867 B/op, a reduction of 559,998.662 B/op or 16.3% (about 56 bytes per call). Host-call allocation is unchanged at 7,278,266.447 B/op versus 7,278,266.023 B/op in the list-materializing control. Timing remains too noisy for a supported claim. The complete correctness suite and final 14-control run pass, with Lua calls at 2,333.785 us/op, vararg/multi-return at 3,227.189 us/op, and host calls at 2,747.453 us/op. Post-change JFR reduces LuaStack.slice from 55.13% to 0.58% of sampled Lua-call pressure and selects the remaining small snapshot collection used for return and native-call ranges.

M19 Small Return-Snapshot Checkpoint

Lua 5.5's ldo.c:moveresults handles zero and one wanted results separately before falling back to general result movement. KLua likewise now uses shared empty and singleton immutable snapshots for zero- and one-value returns, while retaining exact copied lists for multiple results. This representation change left the then-existing pre-hook snapshot order unchanged; the later direct-transfer checkpoint corrects that order to Lua 5.5's hook-before-move behavior. A proposed global LuaStack.slice specialization was rejected because matched GC showed a 24-byte regression on every native call; native argument snapshots therefore retain their prior representation. Focused tests prove empty reuse and single/multiple snapshot independence after stack mutation.

Matched GC reduces 10,000 one-result Lua calls from 2,641,409.476 to 2,401,380.281 B/op, a reduction of 240,029.195 B/op or 9.1% (about 24 bytes per call plus the outer result). Host calls remain effectively unchanged at 7,278,234.487 B/op, only about 32 bytes below the 7,278,266.447 B/op control due to the outer chunk return; the three-result vararg/multi-return kernel is likewise unchanged at 2,881,586.316 B/op. Timing remains too noisy for a supported claim. The complete correctness suite and final 14-control run pass, with Lua calls at 1,811.663 us/op, host calls at 2,534.190 us/op, and vararg/multi-return at 2,987.333 us/op. Post-change JFR attributes only 2.73% of sampled pressure to singleton return lists and selects the separate per-call LuaStack wrapper alongside CallFrame as the next structural audit.

M19 Call-Frame Stack Co-Location Checkpoint

Lua 5.5 keeps one contiguous value stack in lua_State; CallInfo points into that storage, and ldo.c:luaD_reallocstack repairs stack and upvalue locations after growth rather than allocating a separate stack wrapper for every call. KLua retains independent arrays per frame, but CallFrame now inherits the existing LuaStack storage and exposes its legacy stack property as a computed self-reference. This removes the wrapper object and stored wrapper reference while preserving the tested stack implementation, lazy capture map, dynamic growth, and stable frame identity. Mutable frames are now ordinary identity classes instead of data classes; no generated equality, copy, or destructuring behavior was used. Focused tests prove frame/stack identity, growth, and capture synchronization, while the complete coroutine, hook, debugger, and DAP suites cover suspended visibility and mutation.

Matched GC reduces 10,000 ordinary Lua calls from 2,401,377.429 to 2,241,362.534 B/op, a reduction of 160,014.895 B/op or 6.7% (about 16 bytes per frame). The closure control falls from 2,321,945.610 to 2,161,945.316 B/op, the same per-call effect. Coroutine yield/resume is effectively unchanged at 22,878,311.396 B/op versus 22,878,341.958 B/op, consistent with its few Lua frames. The matched closure score improves from 2,000.554 to 1,629.448 us/op (18.5%) with non-overlapping intervals, and the final full run confirms 1,628.765 us/op; other timing controls remain too noisy for a supported claim. The full correctness suite and 14-control matrix pass. Post-change JFR removes LuaStack as an allocated class; CallFrame now accounts for 65.14% and boxed integers for 27.81% of sampled Lua-call pressure.

M19 Zero-Based Frame-Metadata Checkpoint

Lua 5.5 needs CallInfo.func and top offsets because all calls share the thread's value stack. KLua's co-located design gives every frame its own zero-based storage, and repository-wide inspection proves no CallFrame constructor supplies a nonzero base or either of the unused returnBase and frame-level expectedResults values. The latter result count already belongs to PendingCallState only while a call is suspended. Those three stored integers are removed; register translation is explicitly identity-based and debug return transfer starts at the zero-based return register plus one. Focused opcode tests and the exact debug ftransfer/ntransfer hook test cover the affected calculations.

Matched GC reduces 10,000 Lua calls from 2,241,361.355 to 2,081,345.412 B/op, a reduction of 160,015.943 B/op or 7.1% (about 16 bytes per frame after JVM alignment). The closure control falls from 2,161,913.904 to 2,001,913.823 B/op, the same per-call effect. Coroutine yield/resume changes only from 22,878,308.089 to 22,878,277.367 B/op, consistent with its few frames. Timing is too noisy for a supported claim; the final full run completes with Lua calls at 2,081.649 us/op, closure calls at 1,965.621 us/op, and coroutine transfer at 24,332.924 us/op. The full correctness suite and 14-control matrix pass. Post-change JFR attributes 60.06% of sampled Lua-call pressure to boxed loop integers and 37.23% to the remaining frame object.

M19 Closure-Owned Frame-Metadata Checkpoint

Lua 5.5's ci_func(ci) identifies the exact closure in a CallInfo; VM and debug paths then reach the prototype through cl->p rather than storing independent prototype and upvalue references on each call record. KLua now follows that ownership rule: every Lua frame retains its exact LuaClosure, and computed accessors derive the prototype and upvalue list from it. The separately resolved environment remains frame-owned because KLua's nullable closure environment and globals fallback can intentionally differ from the closure's stored field. Focused tests prove closure, prototype, and underlying upvalue-list identity, while the full debug and VM suites preserve function identity and call behavior.

Matched GC reduces 10,000 Lua calls from 2,081,345.207 to 2,001,341.308 B/op, a reduction of about 80,004 B/op or 3.8% (about 8 bytes per frame). The closure control falls from 2,001,913.361 to 1,921,899.126 B/op, a reduction of about 80,014 B/op or 4.0%. Coroutine yield/resume remains effectively unchanged at 22,878,278.040 B/op versus 22,878,276.441 B/op, consistent with its few Lua frames. Timing is too system-noisy for a supported claim. The full correctness suite and 14-control matrix pass. Post-change JFR attributes 60.97% of sampled Lua-call pressure to the per-frame LuaExecutionResult.Returned, 14.21% to its singleton result list, 9.40% to boxed arithmetic, 7.09% to stack arrays, and 6.81% to the now-smaller frame; the internal return-path representation is the next bounded audit.

M19 Direct Lua-Return Transfer Checkpoint

Lua 5.5's lvm.c:OP_RETURN leaves results in the callee stack, and ldo.c:luaD_poscall runs rethook before moveresults copies only the wanted values into the caller. KLua now follows the same boundary for ordinary unsuspended Lua-to-Lua calls: after the return hook, the VM copies zero, one, fixed, or open results directly from the callee frame into the caller frame and uses nil padding where required. This removes the internal LuaExecutionResult.Returned and result-list allocation without changing native, public, coroutine, or suspended-call result objects. A new conformance test proves debug.setlocal in a return hook can alter the register value transferred to the caller, correcting KLua's former pre-hook snapshot order; a focused result matrix covers discarded, singleton, fixed multiple, nil-padded, and chained open results.

Matched GC reduces 10,000 Lua calls from 2,001,341.308 to 1,601,337.812 B/op, a reduction of 400,003.496 B/op or 20.0% (about 40 bytes per one-result return). The closure control falls from 1,921,899.126 to 1,521,858.038 B/op, a reduction of 400,041.088 B/op or 20.8%. The three-result vararg/multi-return control falls from 2,481,548.876 to 1,761,546.533 B/op, a reduction of 720,002.343 B/op or 29.0% (about 72 bytes per return). Host calls and coroutine yield/resume remain allocation-flat, and timing is too system-noisy for a supported claim. The full correctness suite and 14-control matrix pass. Post-change Lua-call JFR removes LuaExecutionResult.Returned and result-list classes; frame creation accounts for 70.26%, stack arrays 13.51%, and boxed arithmetic 14.69% of sampled pressure. A function-valued __index profile selects a separate local target: repeated immutable metamethod CallSiteInfo construction plus name substring/string storage contributes about 11% of sampled allocation, while numeric boxing remains deferred as a representation-level change.

M19 Metamethod Call-Site Metadata Checkpoint

Lua 5.5's ldebug.c:funcnamefromcode maps metamethod-capable opcodes to an interned tmname entry, strips the fixed __ prefix by pointer offset, and reports namewhat = "metamethod"; it does not build a new diagnostic record or name string per call. KLua now likewise returns prebuilt immutable CallSiteInfo records for its fixed __index, __newindex, length, comparison, concatenation, unary, bitwise, and arithmetic keys. Referential checks make canonical hot keys constant-time, while a fallback preserves behavior for any future noncanonical private key. Existing exact debug tests cover index, newindex, all supported operator names, and their metamethod classification.

Matched GC reduces 10,000 function-valued __index calls from 2,726,247.184 to 2,006,243.085 B/op, a reduction of 720,004.099 B/op or 26.4% (about 72 bytes per call). Ordinary Lua calls remain effectively unchanged at 1,601,337.691 B/op, as does raw table read/write at 1,252,593.552 B/op. Timing intervals overlap, so no speed claim is supported. The full correctness suite and 14-control matrix pass. Post-change JFR removes the selected CallSiteInfo, substring byte-array, and name-string sites. The remaining local entry cost is the two-value listOf(receiver, key) snapshot: its object array and Arrays$ArrayList wrapper are the next bounded target; frame pooling and numeric unboxing remain excluded.

M19 Fixed-Arity Lua-Metamethod Argument Checkpoint

Lua 5.5's ltm.c:luaT_callTMres and luaT_callTM place the metamethod function and its two or three arguments directly into stack slots before entering the call machinery. KLua now follows that boundary for Lua-closure metamethods: fixed parameters are placed directly into a new frame, missing parameters are nil-filled, and a vararg closure receives an exact owned copy of only its extra arguments. Native functions and callable values retain their existing list snapshots, while call-site metadata, argument order, __newindex's third value, and the existing error/yield boundary remain unchanged. A focused frame test covers two-argument nil fill and three-argument vararg ownership; the index, newindex, length, operator, callable-value, native-function, and exact debug-name suites cover the public paths.

Matched GC reduces 10,000 function-valued __index calls from 2,006,243.085 to 1,766,183.055 B/op, a reduction of 240,060.030 B/op or 12.0% (about 24 bytes per call). Host calls remain effectively unchanged at 7,278,194.391 B/op, ordinary Lua calls at 1,601,313.734 B/op, and raw table read/write at 1,252,594.007 B/op. Timing is too noisy for a supported claim. The full correctness suite and 14-control matrix pass. Post-change JFR removes Arrays$ArrayList from the workload; the residual profile is dominated by LuaExecutionResult.Returned at 64.17%, the stack array at 15.21%, the frame at 12.34%, and the singleton result list at 1.43%. The next bounded audit therefore targets single-result Lua metamethod completion without changing public, native, callable, coroutine, or general multi-result transport.

M19/M20 Metamethod-Completion Audit And Yieldable-Index Checkpoint

The proposed single-result Lua-metamethod specialization is rejected. Lua 5.5's ltm.c:luaT_callTMres permits a metamethod to yield when its caller is Lua code, ldo.c retains the interrupted call, and lvm.c:luaV_finishOp moves the resumed first result into GETTABLE, GETFIELD, and environment-read destinations, using nil when no value is returned. KLua's synchronous result helper instead treated the same yield as an outside-coroutine error; bypassing its result object first would have optimized and entrenched that conformance gap.

The first bounded continuation slice now covers KLua's GET_TABLE, GET_FIELD, and GET_GLOBAL opcodes. Read helpers explicitly enable suspension only for Lua instruction dispatch, table-valued __index chains carry that permission to the final function, and a suspended Lua or explicitly yieldable native metamethod reuses the caller's pending-result machinery to install exactly one resumed result. Calls made through native standard-library code retain the non-yieldable C boundary. Focused Lua-source tests cover receiver/key transfer, first-of-many selection, nil padding, computed keys, table chains, environment reads, native continuations, and the native table.concat boundary.

Matched GC remains effectively flat: function-valued __index measures 1,766,224.765 B/op versus the prior 1,766,183.055, ordinary Lua calls 1,601,337.877, raw table read/write 1,252,594.677, host calls 7,278,194.264, and the existing coroutine control 22,878,262.260 B/op. Timing is too noisy for a supported claim. The full correctness suite and all 14 benchmark controls pass. Remaining metamethod yield completion is explicitly queued by operation family rather than hidden behind the rejected performance work.

M20 Yieldable-Newindex Checkpoint

Lua 5.5's lvm.c:luaV_finishset calls ltm.c:luaT_callTM with receiver, key, and value, requests no results, and leaves SETTABLE, SETFIELD, and environment-write opcodes with no post-yield value movement in luaV_finishOp. KLua now carries the same Lua-only suspension permission through its table, userdata-metatable, primitive-metatable, and table-chain assignment helpers. A suspended Lua closure or explicitly yieldable native __newindex reuses pending-call continuation state with zero expected results, so repeated yields resume the interrupted assignment and discard every metamethod return value. Native standard-library entry retains its non-yieldable boundary.

Focused Lua-source tests cover field and computed assignments, global/environment writes, exact receiver/key/value transfer, table-valued chains, repeated Lua yields, native continuation, discarded multiple results, raw-table nonmutation, and table.move boundary rejection. The full correctness suite and all 14 benchmark controls pass. Non-suspending allocation remains flat: table read/write is 1,252,596.553 B/op, function-valued __index 1,766,340.191, host calls 7,278,194.595, and the existing coroutine control 22,878,282.971 B/op. Timing is system-noisy and supports no speed claim.

M20 Yieldable Value-Operator Checkpoint

Lua 5.5's lvm.c:luaV_finishOp completes binary arithmetic and bitwise metamethod opcodes plus OP_UNM, OP_BNOT, and OP_LEN by moving the metamethod's first result into register A; ldo.c:moveresults supplies nil when the call returns no value. KLua now gives the same Lua-only suspension permission to all 15 corresponding opcode families, including Lua closures, explicitly yieldable native functions, and callable metamethod values. Pending-call continuation installs exactly one result after each final resume, repeated yields retain the interrupted operation, and unary/length calls preserve Lua's duplicated operand arguments. Native standard-library entry remains a non-yieldable boundary.

Focused Lua-source tests cover all seven binary arithmetic operations, five binary bitwise operations, unary minus, bitwise not, and length; first-of-many selection; no-result nil padding; exact operand transfer; repeated yields; native and callable metamethod continuations; and table.unpack boundary rejection. The full correctness suite and all 14 benchmark controls pass. A first implementation changed a hot arithmetic helper's JVM shape and regressed JVM-to-Lua allocation from the 9,673,883 B/op baseline to 9,993,882.289 B/op, about 32 bytes per call; that version was rejected. Restoring the original helper boundary while limiting suspension to its metamethod branch brings the final control back to 9,673,884.690 B/op. Other final controls remain allocation-flat, including function-valued __index at 1,766,341.046, Lua calls at 1,601,339.694, host calls at 7,278,196.610, coroutine yield/resume at 22,878,286.945, and numeric loops at 961,240.614 B/op. Timing remains system-noisy and supports no speed claim.

M20 Yieldable Comparison Checkpoint

Lua 5.5's lvm.c:luaV_finishOp completes yielding OP_EQ, OP_LT, and OP_LE by applying l_isfalse to the first metamethod result and then honoring the comparison instruction's conditional sense. ltm.c:callbinTM selects the first operand's metamethod and falls back to the second without reordering the arguments. KLua now preserves the corresponding rule in its value-producing bytecode: a comparison continuation writes one canonical LuaBoolean from the resumed first result or nil, ~= applies its following NOT, and >/>= retain the compiler's reversed-operand lowering. Lua closures, explicitly yieldable native functions, and callable metamethod values can suspend repeatedly from Lua opcodes; native API and standard-library comparison helpers retain a non-yieldable C boundary.

Focused Lua-source tests cover all six source comparison operators, first- and second-operand metamethod selection, exact argument order, repeated yields, zero/one/multiple return behavior, Lua truthiness including numeric zero, closure/native/callable paths, resumed errors, and math.min/table.move boundary rejection. An initial explicit result-mode field enlarged every coroutine pending-call record by 8 bytes, moving its control from the 22,878,287 B/op baseline to 22,958,263.716 B/op; a triple-valued catch representation also moved JVM-to-Lua calls from the 9,673,884 B/op baseline to 9,993,882.718 B/op. Both representations were rejected. Encoding the comparison completion in the existing pending result-count field keeps the final controls flat: coroutine yield/resume is 22,878,281.000, JVM-to-Lua calls 9,673,883.344, function-valued __index 1,766,339.507, Lua calls 1,601,338.368, host calls 7,278,195.163, and numeric loops 961,240.823 B/op. The full correctness suite and all 14 controls pass; timing remains system-noisy and supports no speed claim.

M20 Yieldable Concatenation Checkpoint

Lua 5.5's lvm.c:luaV_concat reduces concatenation operands from right to left and calls luaT_tryconcatTM when a pair cannot be converted directly; after a yield, luaV_finishOp installs the first metamethod result in the reduced operand position and re-enters luaV_concat with the remaining count. KLua's compiler represents the same progress as a right-associated sequence of binary CONCAT instructions, so the suspended program counter and destination register already preserve the partially reduced value and remaining operands. Resume now writes the first result, or nil for no results, into that instruction's destination and lets the next outer CONCAT continue without reevaluating operands or allocating a separate progress object.

Focused Lua-source tests cover four-operand right-to-left reduction, repeated yields at multiple reductions, exact first- and second-operand selection and argument order, raw string/number reduction before an outer metamethod, once-only operand evaluation, zero/one/multiple result behavior, closure/native/callable paths, resumed errors, and table.sort comparator boundary rejection. A direct yieldable call through the shared operator helper reproduced the established HotSpot escape-analysis regression, moving JVM-to-Lua calls from 9,673,883 to 9,993,883 B/op, about 32 bytes per call; that code shape was rejected. A dedicated concat metamethod path restores the final control to 9,673,882.682 B/op while string concatenation remains flat at 16,649,068.272 B/op and coroutine yield/resume at 22,878,261.175 B/op. The complete correctness suite and all 14 allocation controls pass; timing remains system-noisy and supports no speed claim.

M19 Direct Core-Backed Protected-Call Checkpoint

Lua 5.5's conventional lapi.c:lua_pcallk path enters ldo.c:luaD_pcall and luaD_callnoyield, keeping the function, arguments, and results in the Lua stack while enforcing a non-yieldable protected-call boundary. KLua now follows that boundary when LuaState.pcall invokes a source-backed Lua closure that is already represented by a core function: pcallNativeFunction builds core arguments once, calls KLuaCoreRuntime.callFunction with isYieldable = false, and pushes the core results directly. Arbitrary host functions and callable values retain the generic DefaultLuaCallContext/LuaReturn adapter path, and protected error objects, exit propagation, fixed/open result handling, nil padding, and unsupported API-value diagnostics remain unchanged.

Focused API tests cover reuse of a returned closure, argument order, fixed-result truncation and nil padding, open results, runtime errors, and protected argument-conversion failures. Matched GC reduces 10,000 JVM-to-Lua protected calls from 9,673,884.563 to 6,560,001.808 B/op, a reduction of 3,113,882.755 B/op or 32.19% (about 311 bytes per call). Post-change JFR removes the selected DefaultLuaCallContext, stack subList, duplicate core-argument list, and LuaReturn allocation sites. A separate direct metamethod-result experiment reduced the index control by about 400,000 B/op but added 320,000 B/op to JVM-to-Lua calls through a HotSpot escape-analysis shape, so it was rejected and fully reverted. The complete correctness suite and all 14 allocation controls pass; adjacent controls remain flat, including host calls at 7,278,182.254, function-valued __index at 1,766,312.743, Lua calls at 1,601,339.480, coroutine yield/resume at 22,878,260.956, and numeric loops at 961,240.593 B/op. Timing remains system-noisy and supports no speed claim.

M20 Compile-Time-Const Call-Site Name Checkpoint

Lua 5.5's lparser.c:localstat promotes only the final local in an exactly matched declaration when its <const> initializer passes lcode.c:luaK_exp2const; singlevaraux carries that VCONST value through nested functions without creating an upvalue. Safe unary, arithmetic, bitwise, and the no-jump logical cases are folded by luaK_prefix, luaK_posfix, and constfolding. Consequently, ldebug.c:basicgetobjname/getobjname sees the substituted load or indexed opcode: a string callee is reported by value as constant, a non-string constant callee has no inferred name, and constant string or small unsigned integer keys become named field calls. KLua retains its ordinary execution slots but now tracks the same compile-time value environment solely for call-site metadata, including scope shadowing/restoration and nested-function lookup.

Focused compiler metadata and Lua-source debug tests cover direct string and numeric constants, the final-declarator and exact-value-count rule, adjusted and dynamic const initializers, safe numeric/logical folding and its zero/division failure boundaries, propagated nested constants, shadowing, string callees, unnamed numeric callees, constant string keys, and optimized integer-index keys. The complete correctness suite passes. A matched compile-allocation negative control measures 20,497.264 B/op versus 20,543.538 B/op in the prior 14-control matrix, so the lazy constant-binding map remains within the established allocation band; timing is system-noisy and supports no speed claim. The rolling gap snapshot now records this closed family while retaining broader indirect-call name inference as M20 work.

M19 Shared Metatable-Service Checkpoint

Lua 5.5 stores primitive metatables once in global_State.mt (lstate.h) and reads the current shared entry in ltm.c:luaT_gettmbyobj and lapi.c:lua_getmetatable; lua_setmetatable updates that same shared state, while userdata uses its object metatable. KLua previously represented the equivalent live lookups with four capturing callbacks every time KLuaCoreRuntime created a VM. A single provider now belongs to each KLuaCoreGlobals instance and supplies current string, primitive-type, and host-userdata metatables to all of its VMs. This preserves mutation visibility and removes the repeated callback objects without snapshotting metatable state.

Fresh JFR selected those callback objects in the JVM-to-Lua protected-call control and confirms they disappear after consolidation. The immediate matched GC control reduces 10,000 calls from 6,560,001.754 to 5,760,002.191 B/op, a reduction of 799,999.563 B/op or 12.20% (about 80 bytes per call). The complete correctness suite passes, including the metatable-sensitive standard-library coverage. The final 14-control allocation matrix records compile 20,522.994, numeric loop 961,160.816, Lua calls 1,601,257.495, closure counter 1,521,782.783, host calls 7,278,115.012, table read/write 1,252,516.927, function-valued __index 1,766,258.374, JVM-to-Lua calls 5,760,005.259, method calls 1,681,520.626, recursive Fibonacci 4,642,401.469, string concatenation 16,648,988.833, coroutine yield/resume 22,878,110.950, vararg/multi-return 1,761,468.118, and entity update 1,531,375.238 B/op. Timing remains system-noisy and supports no speed claim.

M20 To-Be-Closed Lifecycle Checkpoint

Lua 5.5's lparser.c:localstat, checktoclose, marktobeclosed, leaveblock, closegoto, and forlist establish readonly close bindings, mark them only after adjusted initialization, close scopes on all exits, and swap generic-for's fourth expression result into the hidden closing slot. lfunc.c:checkclosemth, luaF_newtbcupval, callclosemethod, and luaF_close, together with lvm.c:OP_TBC/OP_CLOSE/OP_RETURN, require declaration-time raw-method validation, close-time method lookup, reverse order, captured-upvalue closure before close calls, normal one-argument calls, active-error propagation and replacement, and yieldable normal closing. ldo.c:lua_resume retains an unprotected failed coroutine stack, while lstate.c:luaE_resetthread and lua_closethread later reset it, pass the active error through pending close handlers, and abandon yielded continuations through protected non-yieldable closing. KLua now implements that complete lifecycle in compiler bytecode, call-frame close-slot state, VM continuations and error handling, retained failed-coroutine frames, core coroutine closure, and generic-for's hidden fourth value. Registered userdata types can expose raw metamethod names, and io file handles map __close to the Lua liolib.c:f_gc-style idempotent closer, so io.lines(filename) closes owned files on early loop exit as well as EOF.

Focused compiler, VM, core-coroutine, and Lua-source standard-library tests cover nil/false no-ops, declaration rejection, readonly direct and captured bindings, reverse closure across fallthrough, break, goto, and return, preservation of open and fixed return values, callable and dynamically replaced close methods, normal and error argument shapes, replacement errors while earlier closers still run, normal close yields and non-yieldable top-level rejection, generic-for fourth-value early exit, suspended-coroutine abandonment/unwinding, and named io.lines file closure. The complete core suite and focused stdlib integration pass; the full repository verification is the package exit criterion.

M20 Indirect Call-Name Inference Audit

Lua 5.5's ldebug.c:findsetreg, basicgetobjname, rname, isEnv, getobjname, and funcnamefromcode infer ordinary call names from an active local, a backward MOVE, an upvalue load, a string constant load, or a table-access opcode; they additionally name method, generic-for, hook, finalizer, read/write/operator, and close metamethod calls directly from the calling opcode or call state. KLua records the equivalent source identity when it lowers each call instead of replaying bytecode symbolically at inspection time. The audit maps every then-supported source family to that reference chain: direct and moved source variables retain local/upvalue/global identity, string constants and Lua 5.5 compile-time const substitution retain constant/key identity, table accesses retain environment/field/integer-index/method classification, and fixed VM call sites cover generic-for, hooks, __call, reads, writes, operators, and __close. At this checkpoint Lua __gc finalization was not yet implemented and was deliberately selected as a separate VM campaign; the later lifecycle and finalizer-debug campaigns close that historical exclusion.

Existing compiler metadata and Lua-source tests already cover every ordinary symbolic-origin and supported fixed-call family except the newly reachable close path. Focused close-name coverage now exercises both block CLOSE and return-time closing and confirms name = "close", namewhat = "metamethod", matching funcnamefromcode's OP_CLOSE/OP_RETURN branch. The broader indirect-name gap was stale and has been removed from the conformance snapshot.

M20 Cross-Thread Debug State Audit

Lua 5.5's ldblib.c:getthread accepts an optional thread without restricting its status. ldebug.c:lua_getstack walks whatever CallInfo chain that target still owns; ldo.c:lua_resume leaves the chain intact after an unprotected error, and lstate.c:luaE_resetthread removes it only when lua_closethread resets the coroutine. The resulting reachable-state matrix is now explicit:

Thread state Expected stack KLua behavior
Current/main Live caller frames Existing explicit-current offset handling
Fresh suspended Empty getinfo is nil, local levels are invalid, traceback has only its header
Yielded suspended Live suspended frames Existing cross-thread inspection and mutation
Normal Live frames while it resumes another coroutine Existing cross-thread inspection and mutation
Dead after success Empty Existing empty-stack behavior
Dead after error Live until coroutine.close, then empty Failed frames are retained and mutable; close handlers and reset run at close time

The failed state was the one missing family. Coroutine VMs now retain unhandled frames and open captures instead of closing and popping them during resume; debug.getinfo, debug.traceback, debug.getlocal, and debug.setlocal therefore operate on the failed stack, while hook state remains independently targetable as in the other states. coroutine.close passes the active error through pending close handlers, resets the frames, reports the final error once, and leaves subsequent debug inspection with the same empty-stack behavior as a successfully dead coroutine. Focused core and Lua-source tests cover fresh and failed targets, live local mutation, deferred <close> timing and error arguments, reset-after-close, and the existing full state families. The vague broader cross-thread debug gap was stale and has been removed.

M20 Nested Coroutine State Audit

Lua 5.5's lcorolib.c:auxstatus derives running, normal, suspended, and dead from thread identity, lua_status, live stack frames, and the initial function slot. luaB_coresume returns resume errors without resetting the target, and luaB_close only resets dead or suspended threads; current main and normal resumer states reject close, while a non-main coroutine may close itself. Existing KLua coverage already exercises those current/main, nested-normal, fresh, yielded, dead-success, dead-error, explicit-close, self-close, and protected resume-error transitions.

The missing transition was lcorolib.c:luaB_auxwrap's special failed-thread path. A wrapper owns a hidden coroutine that callers cannot close explicitly, so after an execution error Lua distinguishes the target's error status from ordinary dead/normal resume failures, calls lua_closethread, and only then propagates the final error. KLua now records genuine resume failures separately from state-validation errors. A failed wrapper runs its hidden thread's pending close handlers with the original error, accepts a replacement close error, resets the thread, and then preserves the final error object's identity through pcall; attempts to call an already dead or recursively running wrapper retain their existing messages without an inappropriate reset. Focused Lua-source tests cover both preserved and replaced table error objects and the active error passed to __close; the complete coroutine/stdlib and repository suites pass. The vague additional nested-coroutine edge-case wording has been retired from the gap snapshot.

M20 table.move Boundary Audit

Lua 5.5's ltablib.c:tmove validates integer positions before checktab, checks readable source and writable destination capabilities even for an empty range, skips range arithmetic for that empty range, rejects source-count and destination overflow, and copies backward only when the destination start lies strictly inside the source range and the two receivers compare equal. Its forward-copy condition deliberately tests t > e and t <= f before lua_compare, so non-overlapping moves cannot invoke an __eq metamethod.

KLua already matched argument/checktab ordering, nil-default destinations, empty-range behavior, both overflow guards, nil-hole transfer, source/destination metamethod requirements, Lua equality for distinct overlapping receivers, and forward/backward copy order. The audit found that it nevertheless computed receiver equality before deciding whether the range overlapped. Equality is now evaluated only in the possible-overlap branch; moves before or after the source range copy forward without calling __eq, while a genuinely overlapping distinct receiver still uses Lua equality and retains the standard-library non-yieldable boundary. Focused tests cover both short-circuited forward directions, exact read/write order, the equality-driven backward path, and an adjusted overlapping yield-boundary case. The complete stdlib and repository suites pass.

M20 table.sort Partition Audit

Lua 5.5's ltablib.c:sort, sort_comp, partition, and auxsort use median-of-three quicksort, recurse into the smaller partition, tail-process the larger partition, detect comparator contradictions at the partition sentinels, and keep all custom/default comparisons inside the native library boundary. After a severely imbalanced split, auxsort stops reusing the fixed middle pivot and calls choosePivot; l_randomizePivot may supply ~0 when a build wants deterministic rather than entropy-backed pivot variation.

KLua already matched short-list comparator-validation timing, array-size limits, median-of-three and partition comparison order, invalid-order errors, exact numeric/string/default comparison, custom comparator truthiness, table-like reads/writes, and non-yieldable comparison boundaries. It did not carry auxsort's imbalance state, leaving adversarial layouts on the same fixed pivot for every large tail partition. The range sorter now propagates pivot state through smaller recursive partitions, applies the / 128 imbalance threshold to the remaining range, and uses Lua's choosePivot formula with the source-sanctioned deterministic ~0 randomizer. A 512-element organ-pipe regression crosses that fallback, remains correctly ordered, and bounds comparisons below the fixed-pivot path; focused invalid-order cases and the complete stdlib suite pass.

M20 utf8.offset Boundary Audit

Lua 5.5's lutf8lib.c:byteoffset translates negative byte positions through u_posrelat, uses byte len + 1 as a valid end sentinel, rejects out-of-range explicit positions, walks backward or forward by continuation-byte layout, and treats n = 0 as “find the containing sequence start.” It intentionally does not call utf8_decode: a truncated or otherwise invalid lead byte is still a start, and its following continuation run determines the reported end. A continuation byte cannot remain the resolved start; an orphan continuation after ASCII is skipped when moving forward, and the sentinel is returned once before a later offset becomes nil.

KLua's raw-byte offset implementation already matches that control flow for positive, negative, zero, default, explicit, empty-string, and extreme offsets. Existing tests covered valid multibyte characters, continuation starts, relative positions, range errors, nil results, and sentinel positions. New raw-byte cases cover truncated leads, extended invalid leads with continuation runs, containing-sequence lookup, orphan continuations after ASCII, both continuation-start error paths, and the sentinel-before-nil transition. No runtime change was required; utf8.offset is now explicitly retired from the broader malformed-byte uncertainty, while decoder and general JVM-string byte fidelity remain separate gaps. Focused and complete stdlib tests pass.

M20 UTF-8 Strict/Lax Decoder Audit

Lua 5.5's lutf8lib.c:utf8_decode accepts one through six byte sequences up to 0x7fffffff, rejects continuation bytes as leads, more than five continuation bytes, missing or invalid continuations, and overlong encodings in both modes, and in strict mode additionally rejects surrogates and values above 0x10ffff. utflen returns nil plus the one-based start of the first invalid sequence; codepoint raises invalid UTF-8 code. Both entry points decode every sequence whose start lies in the selected interval even when its continuation bytes extend beyond the interval endpoint.

KLua's shared decoder and the utf8.len/utf8.codepoint loops already match those limits, strict/lax distinction, interval rules, and error positions. Existing tests covered surrogate and maximum-UTF boundaries plus sequences crossing the interval endpoint. New raw-byte cases cover a continuation lead, two- and three-byte overlong forms, truncation, a non-continuation interior byte, and an overlong lead-width, and prove that lax mode does not relax structural validity. No runtime change was required. The strict/lax decoder family is now retired from the broader malformed-byte uncertainty; utf8.codes iterator behavior and general JVM-string byte fidelity remain separate gaps. Focused and complete stdlib tests pass.

M20 utf8.codes Iterator Audit

Lua 5.5's lutf8lib.c:iter_codes rejects a state string whose first byte is a continuation byte before returning the iterator triple, independently of strict/lax decoding. The stateless iter_aux casts its control to an unsigned byte offset, ends for negative or past-end controls, skips any continuation run at the next offset, decodes one sequence, and rejects both an invalid sequence and an otherwise valid sequence immediately followed by another continuation byte. Because the iterator can be called directly with an alternate state, that continuation skipping remains observable even for a state that utf8.codes itself would reject.

KLua's strict/lax iterator closures already match that state machine and the lua_tointeger fallback-to-zero control behavior. Existing tests covered the iterator triple, repeated and arbitrary controls, continuation controls inside a valid sequence, state coercion, strict/lax surrogate handling, and termination. New raw-byte cases cover entry-time leading-continuation rejection, direct-iterator continuation-run skipping, overlong and truncated sequences, and the post-decode trailing-continuation guard in both modes. No runtime change was required. All raw-byte UTF-8 decode/offset/iterator entry points are now source-backed for representable malformed layouts; general JVM-string byte fidelity remains the broader gap. Focused and complete stdlib tests pass.

M20 IO Handle Close/Flush Audit

Lua 5.5's liolib.c:io_type first uses luaL_checkany, so an absent value is an argument error while an explicit nil or non-file value returns nil. aux_close marks a stream closed before invoking its close callback; f_close rejects an already closed stream, io_close substitutes the default output only when its argument is absent, and f_gc/__close ignore close-result values and are idempotent for closed streams. io_noclose restores the standard stream's close callback and returns nil plus cannot close standard file, while io_pclose delegates to luaL_execresult for a three-value success/failure, termination kind, and code tuple. aux_flush, f_flush, and io_flush use the usual one-value success or three-value file-error result after their open/default checks.

KLua already matched regular/default/standard/process close transitions, closed-handle errors, one-value flush success, direction-limited flush failure tuples, non-closing standard handles, and successful process exit tuples. The audit found that io.type() without arguments returned nil instead of raising the source-required value error; it now distinguishes absence from explicit nil. New lifecycle coverage adds that boundary, a nonzero process close returning nil, "exit", 7 while leaving the handle closed, result suppression for nonzero process and standard-stream <close> calls, standard-stream preservation, flush result arity, and idempotent __close on an explicitly closed handle. Focused IO tests and the complete repository suite pass.

M20 IO Open/Default Routing Audit

Lua 5.5's liolib.c:l_checkmode accepts [rwa]%+?b*; io_open coerces the filename before its optional mode, validates the mode before touching the filesystem, and returns a file handle or the three-value luaL_fileresult failure tuple. opencheck instead raises on failure. g_iofile treats absent and explicit nil arguments as getters, coerces string/number filenames and opens them before replacing the registry default, or validates an open file handle before replacement. Consequently, failed filename replacement leaves the prior default installed. getiofile reports a closed default only when an operation later tries to use it; the getter itself still returns the closed handle.

KLua's io.open, mode parser, default-file argument selection, and delayed default assignment already match that ordering. Existing tests covered source mode extensions, read/write/append direction, filename coercion, handle selection, module-level routing, and later use of closed defaults. New coverage proves that explicit nil getters preserve identity, failed io.input and io.output filename replacements raise without replacing their usable handles, the old defaults still read/write afterward, equivalent io.open failure remains a nil/string/number tuple, invalid mode wins over filesystem failure, and filename validation wins over mode validation. No runtime change was required. Focused routing and complete repository tests pass.

M20 IO Seek Boundary Audit

Lua 5.5's liolib.c:f_seek first validates an open file handle, then resolves the optional set/cur/end option with default cur, then validates the optional integer offset with default zero and its platform seek-number range, and only afterward calls fseek. A seek failure returns the three-value luaL_fileresult tuple; success returns the resulting absolute position from ftell. Thus invalid options and offsets remain argument errors even for a valid but non-seekable stream, and a failed seek does not move the file position.

KLua already matched open-handle precedence, optional/nil defaults, integer coercion, negative-position failure tuples, beyond-end seeks, successful absolute position reporting, closed handles, and non-seekable result shapes. It checked stream seekability before classifying the already-coerced option, however, so io.stdin:seek("bad") returned an Illegal seek tuple instead of the source-required invalid-option error. Option membership is now validated before the capability check. Focused tests cover both invalid-option and non-integral-offset precedence on stream handles plus regular-file defaults, every base, backward and beyond-end positions, failed-position preservation, numeric-string offsets, and existing argument diagnostics. Focused IO and complete repository tests pass.

M20 IO setvbuf Argument Audit

Lua 5.5's liolib.c:f_setvbuf first validates an open file, then requires one of no, full, or line, then reads an optional integer size defaulting to LUAL_BUFFERSIZE, casts it to the host size_t, and returns luaL_fileresult for the platform setvbuf call. luaL_checkoption with no default treats an absent or nil option as a string-argument error; option validation precedes size validation, while the open-file check precedes both. Negative and extreme sizes are deliberately passed to the C library after the unsigned cast, so their success or failure is platform behavior rather than a portable Lua rule.

KLua retains an explicit limitation here: setvbuf validates arguments but does not reconfigure buffering for RandomAccessFile or JVM process/standard streams, and therefore returns a one-value success for validated calls instead of exposing platform C buffering failures. Within that boundary, its validation order already matches the source. Expanded focused coverage proves all three options, nil/default and numeric-string sizes, one-result success, missing/nil option errors, invalid-option-before-size precedence, fractional and non-number size errors, and closed-handle precedence. No runtime change was required; the conformance snapshot continues to name actual buffer reconfiguration as the remaining gap. Focused and complete repository tests pass.

M20 IO Lines Ownership Audit

Lua 5.5's liolib.c:f_lines validates an open caller-owned handle before capturing up to 250 formats. io_lines treats absent/nil as the caller-owned default input, validates that handle before aux_lines, and returns only the iterator; for a filename it opens an owned file first and returns iterator, nil state, nil control, and that file as the fourth generic-for close value. io_readline ignores call-time iterator arguments, validates the captured handle, applies captured formats when invoked, returns no values at EOF, closes only an owned named file at clean EOF, raises read errors without the EOF close path, and relies on the fourth value for early-loop closing.

KLua already matched delayed format validation, format limits and source positions, manual/stateless iteration, named/default/file-handle ownership, EOF closure, already-closed iterator errors, named open errors, read errors, and early generic-for closing. The audit found one ordering mismatch in the default branch: KLua enforced the format cap before validating the default handle, whereas Lua's tofile check happens first. Default-handle lookup/open validation now precedes the count check, so a closed default plus 251 formats reports attempt to use a closed file; file-handle and named-file branches retain their source order. Focused ownership tests and the complete repository suite pass.

M20 IO Ordered Read-Format Audit

Lua 5.5's liolib.c:g_read parses and executes one format per loop iteration and stops as soon as a read fails. Therefore a later invalid format is diagnosed only after every earlier successful read has advanced the file, but is never inspected after EOF or a file error. The same helper drives f_read, io_read, and io_readline; the iterator returns no values at clean EOF and raises only when the failed read supplied error details.

KLua previously converted the entire format list before performing any input, so a later invalid format could prevent earlier valid reads and could surface even when EOF or a direction error should have ended processing. Direct reads and captured line iterators now retain raw format arguments and parse each immediately before its read. For a non-readable handle, only the first format is validated before the file-error result, matching the source's first attempted operation while leaving later formats unobserved. Focused coverage proves partial line consumption before a later format error, zero-byte success before later validation, EOF arity and short-circuiting, iterator reuse after partial progress, iterator no-result EOF, and first-versus-later invalid formats on a write-only handle. Focused IO and complete repository tests pass; numeric scanner token grammar remains a separate campaign.

M20 IO Ordered Write/Error Audit

Lua 5.5's liolib.c:g_write converts one current argument with lua_numbertocstring or luaL_checklstring, immediately passes its bytes to fwrite, and stops without inspecting later arguments when that write is short. Successful calls return the file handle already left on the stack by f_write or getiofile; this includes a call with no values, because the write loop never touches the stream. A short write instead returns the four-value luaL_fileresult tuple extended with the cumulative number of bytes accepted, including any partial bytes from the failing fwrite.

KLua previously rejected a non-writable handle before converting any value and rejected even a zero-value write. It now converts each current value immediately before checking/attempting that write, leaves later values unobserved after a direction or IO failure, returns the handle for an empty write, and appends the cumulative completed-byte count to write-error results. Existing per-argument writes already preserve earlier output before a later type error. Focused direct/default coverage proves method-versus-module argument positions, empty read-only writes, invalid-first precedence, valid-first direction failure with skipped later validation, four-result failure arity and zero byte count, retained output before a later invalid value, and closed-default precedence. Java's OutputStream and RandomAccessFile bulk writes do not expose the number of bytes accepted within an argument that throws, so KLua's failure count is exact through completed arguments but may omit an intra-argument partial write; that host-API limitation remains documented. Focused IO and complete repository tests pass.

M20 IO Numeric Scanner Audit

Lua 5.5's liolib.c:read_number, nextc, test2, and readdigits skip leading whitespace, scan one decimal or hexadecimal numeral prefix with optional sign, fraction, and exponent, retain one look-ahead byte, and invalidate a token that attempts to exceed 200 bytes while leaving byte 201 unread. lua_stringtonumber then reaches lobject.c:luaO_str2num, which first tries l_str2int and only produces an integer when the complete token satisfies that integer grammar; otherwise l_str2d produces a float. A decimal point or exponent therefore keeps an integral-valued result and signed zero in the float subtype, as do hexadecimal fractions/exponents and decimal integers that overflow the integer range.

KLua's raw-byte scanner already matched the source's sign/prefix/fraction/exponent consumption, locale and dot decimal-point acceptance, malformed-token look-ahead, ASCII digit boundaries, hexadecimal integer wrapping, and overlength failure position. Its post-scan conversion incorrectly narrowed every exactly integral Double back to Long, however. Successful decimal and hexadecimal floating conversions now retain Double; only syntactic decimal and hexadecimal integers take the integer path. New direct and captured-iterator coverage proves subtype selection for signed integers, decimal/exponent/hex floats, negative floating zero, decimal integer overflow, floating overflow/underflow, and iterator reuse. The cap matrix now proves both a successful 200-byte float with preserved look-ahead and a failed 201-byte token whose final digit remains unread; existing malformed-prefix, locale, and raw-byte cases complete the scanner family. KLua deliberately uses stable ASCII whitespace/digit classification plus the JVM locale decimal separator instead of reproducing platform-specific extended-byte C ctype classifications. Focused IO and complete repository tests pass.

M20 IO Process/Temporary-File Audit

Lua 5.5's liolib.c:io_popen validates the command before its optional mode, accepts only r/w by default and a single optional b/t suffix on Windows, and connects exactly the requested standard stream while the child's other streams remain inherited. io_pclose delegates termination reporting to luaL_execresult, producing true|nil, "exit"|"signal", code where the host exposes that distinction. io_tmpfile ignores all arguments, returns exactly one auto-removing read/write handle on success, and otherwise uses the standard three-value file result.

KLua previously accepted Windows pipe-mode suffixes on every host and discarded unconnected child output/error while leaving read-mode child input as an unwritten JVM pipe. Pipe mode validation is now host-conditional, read-mode children inherit stdin/stderr, and write-mode children inherit stdout/stderr. Expanded coverage proves command-before-mode validation, explicit-nil/default mode with ignored extras, method direction, valid and invalid platform modes, successful and nonzero close tuples, close-metamethod result suppression, tmpfile one-result arity, ignored tmpfile arguments, read/write/seek lifecycle, closed state, and deletion from the host temporary directory. JVM process APIs expose a normalized exit value rather than POSIX wait-status signal metadata, and Java pipe streams do not implement Windows C text/binary translation, so those two platform distinctions remain explicit limitations. Focused IO and complete repository tests pass.

M20 OS Filesystem-Result Audit

Lua 5.5's loslib.c:os_remove and os_rename coerce their filename arguments in order and return luaL_fileresult: one true value on success or nil, message, and platform errno on failure. Remove qualifies its failure message with the requested filename, while rename deliberately passes no filename and therefore reports an unqualified reason. The underlying C remove accepts files and empty directories. C rename replaces an existing target on POSIX but fails that collision on Windows. os_tmpname ignores arguments, returns one unique filename without creating the final file, and raises if generation fails.

KLua already matched argument order, qualified versus unqualified error messages, result shapes, missing-path failures, basic rename/remove operations, and unique non-created writable temporary names. It forced REPLACE_EXISTING on every host, however, masking the Windows C-runtime collision rule. Rename now uses replacement only on non-Windows hosts. Expanded test-owned filesystem coverage proves one-result success arity, file and empty-directory removal, conditional target-collision behavior with source/target content preservation, tmpname ignored arguments and one-result arity, uniqueness, noncreation, and later writability. JVM filesystem exceptions do not expose portable C errno values, so KLua continues to return a numeric placeholder code for these failures while preserving the portable message and arity contract. Focused OS and complete repository tests pass.

M20 OS Execute-Result Audit

Lua 5.5's loslib.c:os_execute uses luaL_optstring: an absent or nil command calls system(NULL) and returns one boolean indicating whether a command processor exists, while a present command is converted before system runs and extra arguments are ignored. luaL_execresult returns true|nil, "exit"|"signal", code for a completed command, but if invocation itself fails with errno it delegates to the ordinary nil, message, numeric-code luaL_fileresult tuple. The command inherits the host process's standard streams.

KLua previously returned true for every shell probe without checking availability and converted ProcessBuilder.start failures into nil, "exit", 1, conflating invocation errors with completed nonzero commands. The nil/absent path now starts a side-effect-free shell exit probe with closed input and discarded probe output, returning false when the shell cannot be launched. Command launch exceptions now return a file-error-shaped tuple, while completed commands retain exit tuples and inherited IO. Expanded coverage proves one-result nil probing with ignored extras, three-result zero/nonzero arity, ignored command extras, nonexistent-command completion through the shell, and argument-type precedence. JVM exit values still cannot reconstruct POSIX signal wait status, and launch exceptions still use a numeric placeholder because Java exposes no errno. Focused OS and complete repository tests pass.

M20 OS String-Coercion Audit

Lua 5.5's OS entry points use luaL_checkstring/luaL_optstring, which accept strings and numbers and convert numbers in place with Lua's locale-aware integer/float printer. KLua's shared OS helper instead delegated numeric values to JVM toString, producing spellings such as 1.0E15 instead of 1e+15 and 1.0E-5 instead of 1e-05. The helper now routes every numeric OS string argument through the same Lua number formatter used by IO and string libraries. Test-owned numeric filenames prove exact formatting through os.remove and both ordered os.rename arguments, while a numeric os.date format provides a non-filesystem observation. Existing non-number rejection and nil-default coverage remains green. Focused OS and complete repository tests pass.

M20 OS Date-Format Boundary Audit

Lua 5.5's loslib.c:os_date obtains the format with its byte length, validates the optional time before inspecting conversions, consumes one leading !, converts through gmtime or localtime, and then uses two deliberately different string boundaries. Its strcmp(s, "*t") table selector stops at the first NUL, while its formatter loop runs to the saved Lua-string end and therefore copies literals and interprets conversions after embedded NULs. checkoption applies the build-selected LUA_STRFTIMEOPTIONS whitelist and reports the remaining invalid suffix through C %s, which again stops at NUL. Each valid conversion is delegated to strftime with a 250-byte item buffer; the surrounding Lua buffer itself grows without a fixed total limit.

KLua previously compared the complete JVM string for *t, included bytes after NUL in invalid-conversion diagnostics, hardcoded English %p, and returned generic zone labels such as ET for %Z. It now emulates the NUL-terminated table selector and diagnostic suffix while retaining full-length formatting, obtains AM/PM names from the active locale, and formats date-specific timezone abbreviations such as EST versus EDT. The matrix covers empty and !-only formats, ignored extra arguments and one-result arity, table selection and valid conversion scanning across NUL, NUL-truncated errors, valid and invalid E/O modifiers, trailing specifiers, format-before-time and time-before-conversion validation, and unrepresentable extreme timestamps. Locale and timezone tests restore their host defaults. KLua deliberately uses the source's C99/POSIX conversion set on every JVM host, does not expose the Windows-only # variants, maps %c/%x/%X and locale text through Java, and has neither native strftime's 250-byte per-item cap nor its zero-length failure signal. Focused date and complete repository tests pass.

M20 OS Time-Table Normalization Audit

Lua 5.5's loslib.c:os_time accepts absent or nil input as the current time, otherwise truncates the call to its first table argument, reads year, month, day, optional hour, min, sec, and tri-state isdst in that order, applies getfield's exact-integer and adjusted C-int bounds, and passes the resulting struct tm to mktime. It then calls setallfields before checking the -1 sentinel, so normalized table mutations remain observable even when the call ultimately errors. A positive or zero tm_isdst is an instruction to mktime, not merely an overlap preference; contradictory seasonal or gap hints can shift the normalized wall clock and write back the actual resulting DST state.

KLua already covered required/default fields, Lua numeric-string conversion, adjusted bounds, broad calendar carry normalization, index/newindex chains, and ambiguous-overlap selection. It previously ignored an explicit isdst value when no currently valid offset matched and lost argument-table mutations when a nested host/core call returned an error. Time conversion now selects the nearest Java timezone-transition offset for the requested daylight state and converts that assumed local offset back through the active zone, reproducing mktime-style seasonal and spring-gap correction. Mutable table arguments are now synchronized through the stack/core and public/Lua bridges on runtime errors as well as success and yield, preserving setallfields write-back before the sentinel error and protected-call visibility in general. The expanded matrix proves absent/true/false overlap behavior, contradictory winter/summer hints, both spring-gap choices, complete ordered reads, nil/table ignored extras, one integer result, and normalized sentinel write-back. KLua still uses Java LocalDateTime, Instant, and ZoneRules; fixed zones or installations without a matching transition fall back to Java's inferred local time, and historical C-library normalization can remain platform-specific. Focused time and complete repository tests pass.

M20 OS Locale-Category Audit

Lua 5.5's loslib.c:os_setlocale converts its optional locale-name argument first, validates the optional category second against all, collate, ctype, monetary, numeric, and time, delegates both query and mutation to C setlocale, and always returns exactly one string-or-nil result. Locale availability and the spelling returned by a successful call are installation-defined; an empty name requests the host environment's default locale, while C selects the portable C locale. Extra arguments are ignored.

KLua already matched ordered argument validation, defaults, C/empty/query behavior, invalid-category errors, and global restoration. Its parser accepted any syntactically plausible language tag, however, so unavailable names such as zz_ZZ incorrectly succeeded. Non-special names now must match a Java-installed locale by language and any requested script/country/variant. The expanded test proves one-result query/mutation/failure arity, ignored extras, numeric name coercion, unsupported-name nil without mutation, and category queries after controlled changes. The deliberate JVM policy is explicit: all six Lua category names are aliases for Java's single process-wide default locale, so a numeric mutation is also visible through a time query; tests restore the prior host locale. Java locale names and category coupling remain installation-specific substitutions for C locale state. Focused locale and complete repository tests pass.

M20 OS Clock/Exit Audit

Lua 5.5's loslib.c:os_clock ignores all arguments and returns one float equal to C clock() divided by CLOCKS_PER_SEC, measuring process CPU consumption rather than elapsed wall time or time since the OS library opened. os_exit maps boolean true/false to EXIT_SUCCESS/EXIT_FAILURE; otherwise it takes an optional exact integer defaulting to success and narrows it to C int. It evaluates the second argument only for Lua truthiness, optionally closes the state, then terminates without returning; later arguments are ignored.

KLua previously started a System.nanoTime wall timer on every openOs, so clocks reset with library/state creation and included sleep time. It now reads JVM process CPU duration, with process-MXBean and finally current-thread CPU fallbacks when the standard process value is unavailable. Cross-state coverage proves the value does not reset, remains finite/nonnegative/nondecreasing, returns exactly one float, and ignores arguments. Existing embedder-safe exit propagation was already uncatchable through Lua pcall; the expanded matrix proves absent, true, false, numeric-string, narrowed large-integer, nil, and truthy-zero/table close forms plus ignored extras, handler ordering, invalid status rejection, and no Lua return. KLua deliberately reports the close request to LuaExitHandler and throws LuaExitException instead of directly closing the state or terminating the JVM process; the embedder owns those irreversible actions. Focused lifecycle and complete repository tests pass.

M20 OS Environment-Lookup Audit

Lua 5.5's loslib.c:os_getenv first applies luaL_checkstring, accepting strings and Lua-formatted numbers while rejecting absent, nil, and other types, then passes the resulting NUL-terminated pointer to C getenv. Embedded NUL bytes therefore terminate the host name even though the Lua string retains a longer byte length. getenv returns either the exact environment value, including an empty string, or NULL, which Lua exposes as exactly one nil; extra arguments are ignored.

KLua previously passed the complete JVM string to System.getenv, so embedded NUL and Java-invalid names leaked IllegalArgumentException. It now truncates the checked name at the first NUL and maps Java-invalid or security-denied lookups to the ordinary one-nil result. Focused coverage proves an existing variable through the NUL prefix rule, unavailable and invalid-name nil arity, numeric coercion, ignored extras, and nil/type errors. Java's immutable process environment prevents test-owned empty-value injection, and security-denied lookups are deliberately indistinguishable from absence as the closest pure-JVM equivalent to a failed C lookup. Focused environment and complete repository tests pass.

M20 OS Difftime Boundary Audit

Lua 5.5's loslib.c:os_difftime validates its two configured l_timet arguments in order, calls C difftime(time_t, time_t), and pushes exactly one Lua float. The C helper computes the mathematical time difference without overflowing time_t; converting each operand to a floating value before subtraction is not equivalent near the limits because adjacent integers can round to the same float.

KLua previously evaluated first.toDouble() - second.toDouble(). It happened to produce the expected rounded full signed-range magnitude but returned zero for maxinteger - (maxinteger - 1) and the corresponding minimum-edge case. It now subtracts the validated integers exactly with BigInteger and converts only the difference to the Lua float. Coverage proves both full-range directions, positive and negative unit differences at the extremes, numeric-string coercion, ignored extras, one-result arity, float subtype, and existing left-before-right validation errors. KLua's fixed 64-bit integer time representation is the deliberate JVM substitute for installation-dependent time_t width. Focused difftime and complete repository tests pass.

M20 Package Searchpath Template Audit

Lua 5.5's loadlib.c:ll_searchpath checks its name and path followed by optional separator and replacement strings, accepting Lua-formatted numbers, and passes their NUL-terminated C representations to searchpath. luaL_gsub replaces the complete separator in the name, luaL_addgsub replaces every ? in the complete template list, and getnextfilename preserves leading, repeated, and trailing empty semicolon templates. Each candidate is tested by actually opening it with fopen("r"); success returns exactly the first filename, while failure returns nil plus one diagnostic containing every substituted candidate.

KLua already matched default/custom and empty separators, all-mark replacement, first-match selection, empty-template diagnostics, ordered argument errors, and readable directory behavior on POSIX-like hosts. It previously processed bytes after embedded NULs and used metadata-only Files.isReadable, which accepts directories on Windows even though the host C open fails. All four string arguments now truncate at NUL before substitution, and candidates are probed by opening and immediately closing a JVM input stream. IO, invalid-path, and security failures continue to the next template. Expanded test-owned coverage proves name/path/separator/replacement boundaries, ignored extras, one-result success, numeric coercion, invalid path diagnostics, and conditional Windows versus POSIX directory selection; existing missing-result and empty-template matrices prove two-result diagnostic shape. Java stream openability is the explicit substitute for C fopen. Focused searchpath and complete repository tests pass.

M20 Package Loadlib-Disabled Boundary Audit

Lua 5.5's loadlib.c:ll_loadlib checks its path and initialization-symbol arguments in order as NUL-terminated strings, then calls lookforfunc. In the no-dynamic-loader fallback, lsys_load always fails before symbol lookup with the fixed DLMSG, LIB_FAIL is redefined to "absent", and ll_loadlib returns exactly nil, message, and that location tag. Extra arguments are ignored; numbers are accepted through luaL_checkstring.

KLua's intentional pure-JVM fallback already returned the exact source triple and propagated its message through the C and C-root searchers. Both checked arguments now explicitly normalize to their C-string prefixes before the fallback, keeping the boundary correct if the implementation later gains path-sensitive diagnostics. Expanded direct coverage proves NUL-bearing strings, numeric arguments, ignored extras, exact three-result arity/content, and path-before-init errors. Existing overridden-loader and fallback searcher matrices prove dotted/hyphenated open-symbol construction, missing paths, load failures, C-root compatibility fallback, and package.cpath type errors. Native loader creation remains intentionally unavailable. Focused loadlib/searcher and complete repository tests pass.

M20 Package Require-Name Boundary Audit

Lua 5.5's loadlib.c:ll_require first applies luaL_checkstring to stack slot 1. The returned C pointer terminates at an embedded NUL and is used for package.loaded keys, every searcher call, and missing-module diagnostics. The slot itself retains the complete Lua string, however, and ll_require later passes that original value to the selected loader. Each built-in searcher independently checks its argument as a C string; consequently direct searcher calls also coerce numbers and ignore bytes after NUL for preload keys, path substitution, C-root detection, and open-symbol construction.

KLua previously coerced numeric require names but used the complete JVM string throughout. It now captures a hidden Kotlin-backed C-string normalizer during package initialization. require retains separate lookup and loader names, while all four built-in searchers normalize their direct arguments before lookup or substitution; the hidden helper and existing module-root helper remain inaccessible from Lua. Expanded coverage proves prefix-only preload/cache identity, a full NUL-bearing loader argument, cached one-result behavior, prefix-only failure diagnostics, numeric direct preload lookup, Lua and C filename substitution, C open-symbol construction, and suppression of C-root selection when the first dot follows NUL. Existing matrices continue to prove numeric require, hyphen compatibility symbols, result arity, searcher diagnostics, and C-root behavior. Focused require/searcher and complete repository tests pass.

M20 Package Initialization-Path Audit

Lua 5.5's loadlib.c:setpath checks the versioned LUA_PATH_5_5/LUA_CPATH_5_5 environment names before their unversioned forms, treats an empty value as present, and uses the compiled platform default when neither is available or registry LUA_NOENV is truthy. Only the first ;; in an environment value is replaced with that default, with separators added only when a prefix or suffix exists. On Windows, setprogdir replaces every ! with the running executable's directory. luaopen_package exports the platform separator/configuration string and registry-backed loaded/preload tables.

KLua previously hardcoded only ?.lua;?/init.lua and ?.so. It now carries the official Lua 5.5 Windows and POSIX default template sets, applies versioned-first process-environment lookup and exact first-marker expansion, and on Windows substitutes the Java process executable directory obtained from ProcessHandle; failure to discover a directory when substitution is required raises an initialization error. LuaConfig.packagePathEnvironmentEnabled is the explicit JVM substitute for setting registry LUA_NOENV, defaults to true, and is false in the production/sandbox preset. Focused coverage proves precedence including empty versioned values, opt-out behavior, leading/interior/trailing and first-only default insertion, Windows executable substitution/failure, exact path/cpath installation, and exported configuration. Existing tests prove that require captures the initial loaded/preload identities even if public package fields are replaced. At this checkpoint a shared low-level registry connecting package storage, debug.getregistry, and LUA_NOENV remained a separate cross-module gap; the integration and field-access campaigns below close it. Focused initialization and complete repository tests pass.

M20 Shared Registry-Table Integration

Lua 5.5's lstate.c:init_registry creates one state-owned table with false at reserved slot 1, the globals table at LUA_RIDX_GLOBALS (2), and the main thread at LUA_RIDX_MAINTHREAD (3). luaL_getsubtable reuses a named registry table or replaces any non-table value. luaopen_package obtains _LOADED and _PRELOAD through that helper, setpath:noenv reads LUA_NOENV from the same table, and ldblib.c:db_getregistry returns the registry identity directly.

KLua previously returned a private debug-only table and created package loaded/preload tables per open. LuaState now owns one source-backed core registry with low-level stack methods for the table and named subtables. It refreshes Lua-side mutations before API reads and synchronizes newly created subtables back to the core identity. Slot 1 is false and slot 2 refreshes to the live globals table immediately before exposure, avoiding stale bridge snapshots. Package _LOADED/_PRELOAD, registry LUA_NOENV, repeated package opens, and debug.getregistry all use this identity; the temporary debug bridge global is removed during installation. Cross-library coverage proves stable registry identity/mutation, hidden bridge cleanup, slots 1 and 2, package subtable identity, cached/preloaded mutation visibility, survival across global package replacement and re-open, and registry-controlled default paths. Focused registry/package/debug and complete repository tests pass.

The follow-up audit found that the storage was shared but its host helpers used raw snapshot fields. Lua's lua_getfield/lua_setfield and luaL_getsubtable are ordinary operations: an existing table supplied through registry __index is reused, and a replacement table can be intercepted by __newindex. LuaState.getField/setField now route through the same metamethod-aware stack operations used by host call contexts, while pushRegistrySubtable preserves the newly created table as its result even when assignment is intercepted. Package opening also creates/reuses the observable _CLIBS registry table before other work. Each path/cpath environment lookup now short-circuits without touching LUA_NOENV when absent and otherwise performs its own ordinary registry read, matching loadlib.c:setpath. Java API and Lua-source coverage prove raw-value bypass, __index fallback, __newindex interception, _CLIBS creation, provided versus created registry subtables, conditional per-path LUA_NOENV reads, and reopened package identities.

M20 State-Born Registry Main-Thread Checkpoint

Lua 5.5's lapi.c:lua_pushthread pushes the current thread identity and reports whether it is the main thread; lcorolib.c:luaB_corunning returns that identity plus the boolean in exactly two results. The registry's LUA_RIDX_MAINTHREAD value is the same object, is created during lstate.c:init_registry, and is used by coroutine lifecycle checks.

LuaState now creates one real main-thread handle with its registry, stores it in slot 3 before any library is selected, and keeps it isolated per state. The coroutine library binds its runtime tracking to that exact handle instead of creating or installing a synthetic coroutine; coroutine.running, status, resume, close, yieldability, nested current-thread restoration, and debug targeting retain their established behavior. Reopening the coroutine library cannot replace the registry identity. Focused coverage proves the low-level pre-library slot and stability, debug-registry exposure without coroutine support, identity with coroutine.running, thread type, main boolean, exact two-result arity, lifecycle behavior, reopen persistence, nested restoration, and cross-state isolation. Focused registry/coroutine tests and the complete repository suite pass.

M20 IO Buffering-Mode Checkpoint

Lua 5.5's liolib.c:f_setvbuf checks the file, mode, and optional integer size in that order, accepts the C-string options "no", "full", and "line", defaults to LUAL_BUFFERSIZE, delegates buffering to the host C stream, and returns luaL_fileresult. Writes feed that stream buffer, while flush, seek, and close establish the relevant output synchronization boundaries.

KLua previously validated file:setvbuf and always returned true without changing output behavior. Each IO handle now owns deterministic JVM pending-output state: no mode writes directly, full mode drains at its configured capacity, and line mode drains through the last newline while retaining any suffix. Explicit flush, seek, mode changes, and close drain pending output; this also makes buffered io.popen(..., "w") input reach the process before waiting for its result. The 64-bit Lua build default is represented as 1024 bytes. Zero-sized full/line modes write directly, sizes beyond the nonnegative JVM Int range return a three-value failure, and no mode ignores its size as the C mode does. Handles remain direct-write until explicitly configured instead of inheriting opaque platform stdio defaults. Coverage proves immediate versus deferred file visibility, capacity and newline boundaries, retained line suffixes, flush/seek/close draining, buffered process close, read-only handles, numeric/default/zero/negative/oversized sizes, C-string mode boundaries, ordered errors, result arity, and closed-handle behavior. Focused IO tests and the complete repository suite pass.

M20 IO Seek-Boundary Checkpoint

Lua 5.5's liolib.c:f_seek checks the file, optional C-string whence, and optional integer offset in order. It converts the Lua integer to the configured l_seeknum, rejects a narrowing conversion, delegates set/current/end positioning to l_fseek, returns a luaL_fileresult failure, or returns exactly the resulting l_ftell position. The common POSIX and modern Windows configurations use signed 64-bit offsets.

KLua already covered default/nil current positioning, set/current/end bases, numeric-string offsets, beyond-end success, negative-position failure, argument ordering, process/standard-stream failures, and position retention after ordinary failures. Whence selection now follows the source C-string boundary, and base-plus-offset calculation uses checked signed addition so current/end requests cannot wrap a JVM Long into an unrelated position before RandomAccessFile.seek. Overflow produces the same three-value failure shape as other host seek failures and leaves the post-buffer-drain position unchanged. Expanded coverage proves embedded-NUL whence selection, maximum signed set positioning, current and end overflow, minimum signed negative positioning, unchanged positions, pending-buffer draining, and exact one-versus-three result arity. Focused seek tests and the complete repository suite pass.

M20 IO Open-Boundary Checkpoint

Lua 5.5's liolib.c:io_open, opencheck, g_iofile, io_lines, and io_popen obtain filenames, commands, and modes through luaL_checkstring, luaL_optstring, or lua_tostring, so each native operation observes the NUL-terminated C prefix. l_checkmode accepts [rwa]%+?b*; l_checkmodep accepts only read/write on POSIX and permits one binary/text suffix on Windows. Direct open failures use luaL_fileresult, default and named-line opens raise filename-based errors, and named io.lines owns its fourth-result handle.

KLua now normalizes all of those native-bound strings before path lookup, diagnostics, shell launch, or mode validation. Direct io.open, filename-driven io.input/io.output, named io.lines, and both io.popen directions therefore use the same prefix and never expose bytes after NUL in missing-file messages. Existing repeated-b, update/append/truncate, nil/default/numeric argument, atomic default replacement, iterator ownership, ignored-extra, and host-conditional process-mode behavior is preserved. JVM InvalidPathException is converted to the normal three-value open failure rather than escaping as a host exception, and process launch failures now include the normalized command prefix like luaL_fileresult. Expanded coverage proves direct/default/iterator file access through NUL-bearing names, NUL-bearing valid and repeated-binary modes, truncated failure diagnostics, exact open arity, and NUL-bearing process commands and read/write modes. Focused IO tests and the complete repository suite pass.

M20 IO Partial-Write Result Checkpoint

Lua 5.5's liolib.c:g_write converts each argument immediately before writing it, calls fwrite once for that argument, adds the returned byte count even when it is shorter than the requested length, and then returns nil, the luaL_fileresult message/code, and the cumulative count as exactly four results. A successful call returns the original handle already on the stack. This preserves earlier completed arguments plus the accepted prefix of the failing argument.

KLua previously treated Java bulk writes as all-or-throw and therefore reported only earlier complete arguments. Handle writes now return a byte-counted attempt. Regular files retain bulk transfer and derive a partial count from file-pointer movement on failure; stream handles issue byte-granular writes because Java's bulk OutputStream contract exposes no accepted-prefix count. g_write-equivalent logic adds that attempt count before constructing the failure tuple. Full and line buffering map drain progress past previously pending bytes back to the current argument, retain only unwritten bytes that earlier calls had already accepted, and discard the unreported suffix of the failing argument. A reusable internal output-handle bridge enables deterministic stream conformance tests without changing the public API. Coverage proves zero-byte, partial, and full stream writes; multiple string/NUL/integer/float/empty arguments; file and default-output routing; exact four-result failures and cumulative counts; success identity; read-only and validation ordering; flush/close aftermath; and full-buffer, line-prefix, and line-suffix drain failures. Focused write/buffering tests and the complete repository suite pass.

M20 IO Close-Finalization Checkpoint

Lua 5.5's liolib.c:aux_close saves the close function and clears it before invoking io_fclose or io_pclose, so every owned handle is observably closed even when the host close returns a failure. f_gc and the __close metamethod ignore close results, an explicit repeated close fails through tofile, and io_noclose deliberately restores the standard handle's close function before returning nil plus its message.

KLua previously drained pending output before setting closed; a drain exception could therefore skip the underlying close and leave a retryable open handle. Nonstandard handles now mark themselves closed first, attempt pending-buffer drain and every owned JVM resource close exactly once, clear retained pending bytes, and still invoke the process close/wait callback. The first IO failure is returned while later failures are retained as suppressed host diagnostics. Delete-on-close cleanup is attempted by the outer close path even after a resource failure. Standard handles retain their existing non-closing path and remain open. Coverage proves combined drain/underlying-close failure, close-only failure, exact three-result failures, io.type, repeated close/use errors, closed-default-output errors, underlying close-attempt counts, and <close> suppression; existing process exit, iterator exhaustion/early-exit, temporary-file deletion, already-closed metamethod, and standard-handle tests remain green. Focused close/write/buffering/iterator tests and the complete repository suite pass.

M20 String-Format Byte-Boundary Checkpoint

Lua 5.5's lstrlib.c:str_format traverses the format with an explicit byte length, so literal NULs and bytes above 0x7f remain output data. getformat builds a NUL-terminated temporary C specifier; conversion errors print only its C prefix. Unmodified %s adds the complete length-aware value, modified %s rejects embedded zeros before calling sprintf, and its width/precision count bytes. addquoted also walks an explicit byte length, escaping quotes, backslashes, newlines, and control bytes while padding a decimal control escape when the following byte is an ASCII digit.

KLua's existing formatter already used luaRawBytes for modified %s, byte-sized precision/width padding, %c, and raw quoting, while preserving the complete unmodified string. The audit adds a consolidated matrix proving raw high-byte and literal-NUL format data, truncated high-byte values, byte padding, long unmodified NUL-bearing values, zero precision, modified-zero rejection, and %q control-digit escaping. The remaining mismatch was diagnostic-only: invalid temporary specifiers containing NUL embedded that JVM character in the error. Invalid conversion and conversion-specification messages now truncate at the source C-string boundary, while missing-argument validation still occurs first. Existing numeric coercion/extremes, repeated flags, quoting literals, UTF-8 byte splits, pointers, ignored extras, modifier rejection, and maximum-length tests remain green. Focused format tests and the complete repository suite pass.

M20 String Case-Byte Checkpoint

Lua 5.5's lstrlib.c:str_lower and str_upper obtain an explicit string byte length, allocate exactly that length, and pass each unsigned byte independently through the active C locale's tolower or toupper. The result can never expand or change length. In the default C locale and common UTF-8 C locales, ASCII letters map and all other individual bytes remain unchanged; installation-specific single-byte locales may provide additional byte-to-byte mappings.

KLua previously applied the same ASCII policy while iterating its JVM string representation. It now converts through luaRawBytes, mutates only ASCII byte ranges, and reconstructs the Lua byte string, making byte-for-byte length preservation explicit for NULs, malformed bytes, and valid multibyte sequences. Java locale mappings are deliberately not used: they are Unicode transformations that can expand (ß) or produce code points outside one byte (Turkish dotless/dotted I), and the JVM does not expose the host C locale's code-page table. Coverage proves the complete 0..255 input domain and expected output arrays, embedded NUL/high bytes, numeric coercion, ignored extras, missing/table errors, exact byte lengths, and deterministic ASCII behavior after selecting a Turkish Java locale through os.setlocale; the non-ASCII C-locale limitation is recorded in the gap snapshot. Focused case/locale tests and the complete repository suite pass.

M20 String Pattern-Class Byte Checkpoint

Lua 5.5's lstrlib.c:match_class applies one C ctype predicate to one unsigned subject byte, uses lowercase class names for positive predicates and uppercase names for their complements, treats %z as the zero byte, and falls back to literal byte equality for unknown escapes. matchbracketclass combines those predicates with unsigned literal ranges and optional set negation. The %f branch of match applies the same bracket-set predicate to the previous and current bytes, using zero-byte sentinels at both string boundaries.

KLua already converted pattern subjects to one unsigned byte per JVM character and used the portable ASCII C-locale predicates, but represented the source rule as separate character lambdas. All percent escapes now route through one explicit byte classifier mirroring match_class, including complement selection and unknown-escape literal fallback; bracket classes, repetitions, and frontiers therefore consume the same predicate. Coverage partitions the complete 0..255 domain for all eleven classes and all complements, including control/high/NUL bytes, and composes classes with unsigned 128..255 ranges, negated sets, boundary frontiers, captures, find, match, gmatch, and gsub. Valid multibyte and malformed byte sequences remain byte-oriented, and selecting a Turkish Java locale cannot trigger JVM Unicode classification. Existing malformed-pattern and unknown-escape matrices remain green. As with case conversion and IO scanning, installation-specific non-ASCII single-byte C ctype tables are unavailable on the JVM; the deterministic ASCII policy matches the default C locale and common UTF-8 C locales. Focused pattern tests and the complete repository suite pass.

M20 String Pattern Repetition/Progress Audit

Lua 5.5's lstrlib.c:match gives ? a greedy one-then-zero choice, sends * and the remainder of + through max_expand, and sends - through min_expand. Each expansion retries the remaining pattern at successively smaller or larger endpoints, with capture state rolled back by the surrounding recursive match. gmatch_aux and str_gsub remember the end of the last accepted match, reject a second empty match at that same pointer, and then advance exactly one source byte before trying again; this permits empty matches at every distinct boundary, including the end sentinel, without looping.

KLua's immutable capture maps, repetition candidate ordering, and consumer end-index suppression already implement that behavior. A consolidated audit now proves zero/one/many optional, greedy, and minimal choices; rollback into following literals, classes, NUL items, capture boundaries, and backreferences; search start positions and end-sentinel matches; anchored replacement; and once-only empty progress through find, match, gmatch, and gsub. The matrix includes cases where a greedy candidate must relinquish bytes, a minimal candidate must acquire bytes, and a consuming match is immediately followed by a suppressed empty match at the same endpoint. No runtime change was required. Focused repetition/progress tests and the complete repository suite pass.

M20 string.gsub Argument/Replacement Checkpoint

Lua 5.5's lstrlib.c:str_gsub checks the subject and pattern, records the replacement's type, converts the optional maximum count, and only then rejects a replacement that is not a number, string, function, or table. add_s scans a length-aware replacement byte string for %0, %%, and %1 through %9; other or dangling percent escapes fail only when a match actually requests the replacement. add_value calls a function with all captures, indexes a table by the first capture, preserves the original match for nil/false results, accepts string-convertible numbers, and rejects other truthy result types. Both callback paths execute inside the native library boundary.

KLua previously rejected an invalid replacement type before converting an invalid fourth argument. The limit conversion now precedes the replacement-type error, matching the source's combined-invalid-argument behavior. Existing replacement conversion was already raw-byte faithful and source ordered. Expanded coverage proves direct, function, and table results containing NUL, malformed high bytes, and valid UTF-8; %0 expansion; numeric position captures as table keys and function arguments; exact result counts; nil/false and invalid result behavior through the existing matrices; and non-yieldable function and __index callbacks. Focused gsub tests and the complete repository suite pass.

M20 String Pattern Capture-State Audit

Lua 5.5's lstrlib.c:start_capture appends string or position capture state, rolls the new level back when its recursive suffix fails, and enforces the 32-slot limit only when execution reaches a new capture. end_capture closes the innermost unfinished string capture and reopens it if the remaining pattern fails. check_capture rejects missing or unfinished backreferences but permits a position capture, which then cannot satisfy the byte-length comparison. get_onecapture and push_captures preserve declaration order, expose positions as one-based byte offsets, report unfinished captures only when results are requested, and fall back to the whole match only when no explicit capture exists.

KLua's immutable start/capture maps, LIFO close tokens, and sorted result projection already match that lifecycle. Expanded coverage proves nested raw-byte captures containing NUL and high bytes, capture declaration/result order across find, match, gmatch, and gsub, nested position captures, raw backreference equality, position capture keys/arguments, and reversed capture replacement. A cross-consumer reachability matrix proves that excess captures, invalid backreferences, and unfinished captures are ignored on failed paths but raise once execution reaches them; plain find and out-of-range starts retain their source bypasses. Existing 32-capture, invalid-close, replacement-index, and backtracking matrices complete the family. No runtime change was required. Focused capture tests and the complete repository suite pass.

M20 String Pattern Structural-Error Reachability Checkpoint

Lua 5.5's lstrlib.c:match reaches classend, matchbalance, and the frontier parser only after every preceding item has matched at the current candidate position. A dangling %, missing bracket terminator, incomplete %b, or malformed %f therefore raises only on a path that executes that item. str_find_aux bypasses pattern parsing for plain searches and starts beyond the subject, str_gsub does not enter matching for a nonpositive limit, and gmatch_aux parses lazily on iterator calls. Within a bracket class, a terminal % is consumed as an attempted escape before the end is detected, so [% reports malformed pattern (missing ']'), not the standalone dangling-percent diagnostic.

KLua previously raised these errors while tokenizing the complete pattern, even when an earlier literal could never match. Malformed structural items now compile to deferred error tokens. The matcher raises those tokens before checking source exhaustion, while valid prefix tokens, anchors, captures, repetition, and byte predicates retain their existing flow. Bracket and frontier-set scanning return a deferred missing-bracket token, including the corrected [% diagnostic. Coverage proves skipped and reached variants of all four error families, later candidate positions, exact messages, find/match/gmatch/gsub, plain search, out-of-range starts, nonpositive replacement limits, and lazy iterator creation/exhaustion. Focused structural-error tests and the complete repository suite pass.

M20 Math Integer/IEEE Boundary Audit

Lua 5.5's lmathlib.c:math_abs keeps integer inputs in the integer subtype and deliberately wraps mininteger; math_floor and math_ceil convert a rounded float to integer only when it fits. math_fmod uses integer remainder only when both inputs are integer, rejects integer zero, returns integer zero for divisor -1 to avoid minimum-integer overflow, and otherwise delegates to floating fmod. math_modf truncates toward zero, uses the same fit conversion for its integer part, and forces a positive floating zero fractional part for exact values and infinities. math_toint returns one integer-or-nil value, while math_ult compares the unsigned representations of two checked integers.

KLua's existing subtype branches and 2^63-exclusive floating boundary already match those rules. A consolidated audit now proves positive-zero abs, integerized negative-zero floor/ceil, NaN and infinity rounding, both modf outputs for signed zero, NaN, infinities, and the -2^63/2^63 edges, integer fmod at both signed limits, negative-zero floating remainder, floating zero-divisor and infinite-dividend NaNs, tointeger conversion failures, unsigned ordering around the sign bit, ignored extras, exact result arity, and left-to-right argument diagnostics. No runtime change was required. Focused math-boundary tests and the complete repository suite pass.

Lua 5.5's lmathlib.c:math_min and math_max retain the first argument and replace it only after a strict lua_compare(..., LUA_OPLT) succeeds; lapi.c:lua_compare routes that operation through luaV_lessthan, whose nonprimitive path selects the first operand's __lt before the second operand's while preserving the original comparison argument order. KLua's existing implementation matches those rules. A focused audit now covers first-value retention for numeric ties and NaN, exact mixed integer/float decisions at both 2^63 boundaries, raw unsigned string ordering, table and userdata result identity, subtype retention, first- and second-operand metamethod selection, exact argument order, first-error termination, and the existing non-yieldable native-library callback boundary. No runtime change was required; the focused matrix and complete repository suite pass.

Lua 5.5's lmathlib.c:math_atan always calls atan2, defaulting its checked second argument to one, while math_log uses distinct natural-log, log2, log10, and general-base branches. KLua's argument and IEEE behavior already matches the source for signed-zero quadrants, finite axes, infinities, NaN, optional nil, coercion, ignored extras, result arity/subtype, and left-to-right diagnostics. The base-2 implementation did not: dividing natural logarithms produced nonintegral answers for 441 exact powers of two on the JVM. A dedicated base-2 helper now preserves exact exponents for every normal and subnormal power of two and retains the prior IEEE/general-number behavior. Focused tests and the complete repository suite pass.

Lua 5.5's lmathlib.c:math_asin, math_acos, and math_sqrt are direct one-result wrappers around the configured double-precision math operations after one checked-number conversion. KLua's existing wrappers match those rules. A focused audit now proves both signed zeros, exact ±1 inverse-trigonometric boundaries, adjacent representable inside/outside values, zero-adjacent minimum subnormal square roots, infinities, NaN, numeric-string coercion, ignored extras, float subtype, exact arity, and argument diagnostics. No runtime change was required; focused and complete repository tests pass.

Lua 5.5's lmathlib.c:math_sin, math_cos, and math_tan use the same direct checked-number wrapper shape. KLua's existing JVM wrappers match it across both signed zeros, representative quadrants, minimum subnormal and maximum finite inputs, infinities, NaN, numeric-string coercion, ignored extras, float subtype, exact arity, and argument diagnostics. The matrix records host operation results for nontrivial finite approximation cases instead of claiming bit identity with every C libm. No runtime change was required; focused and complete repository tests pass.

Lua 5.5's lmathlib.c:math_exp is another direct checked-number wrapper, while math_deg and math_rad multiply by the already-divided ratios 180/PI and PI/180. KLua's exponential wrapper already matches the source across both signed zeros, underflow and overflow neighborhoods, infinities, NaN, coercion, result projection, and diagnostics. Its angle conversions did not preserve the source operation order: multiplying the input by 180 or π before dividing produced infinity for large values whose source result remains finite. Parenthesizing the ratios fixes deg(2^1018) and rad(max-finite) while retaining signed-zero, infinity, NaN, round-trip, subtype, arity, and argument behavior. Focused and complete repository tests pass.

Lua 5.5's lmathlib.c:math_frexp returns the configured frexp mantissa plus an integer exponent, and math_ldexp explicitly narrows a checked Lua integer to C int before scaling. KLua's existing normal/subnormal decomposition and JVM 32-bit narrowing match those rules. A consolidated audit now covers both signed zeros, minimum subnormal and normal values, maximum finite reconstruction, infinities, NaN, exact finite round trips, float/integer result subtypes, two-versus-one result arity, underflow/overflow, special-value scaling, 32-bit and full-width Lua-integer wraparound, numeric strings, ignored extras, and left-to-right diagnostics. No runtime change was required; focused and complete repository tests pass.

Lua 5.5's luaopen_math installs exactly 29 default fields. Zero-upvalue entries from mathlib are light C function values whose identity is their function pointer, while setrandfunc creates fresh random and randomseed closures sharing one new userdata state on each open. KLua's surface and constants already match, and reopening already replaced the global table with independently seeded random closures while leaving old references mutable and live. Stateless functions incorrectly acquired new identities because both bridge layers rebuilt native wrappers. LuaState now interns each repeated host LuaFunction object to one core function per state, KLuaCoreGlobals interns that core value to one VM-native function per globals context, and the math library reuses stable host objects only for its stateless entries. Coverage proves the exact default field set, absent compatibility names, constant values/subtypes, new table identity, restored fields, preserved light-function identity, fresh random-closure identity, deterministic independent RNG progress, and the general host-function identity contract. Focused API/math and complete repository tests pass. The math gap now names only installation-dependent JVM-versus-C libm approximation parity.

Lua 5.5's lstrlib.c:matchbalance validates its two delimiter bytes only when %b execution is reached, requires the current subject byte to equal the opener, increments nesting only for a distinct opener, and returns at the first closer that reduces depth to zero. KLua's existing byte-normalized pattern engine matches those rules. A consolidated cross-consumer audit now proves nested and adjacent ordinary balances, identical-delimiter first-close behavior, NUL/control/high-byte delimiters and subjects, capture/backreference and suffix composition, the literal treatment of a quantifier-looking byte after %b, later-candidate search after suffix failure, find positions, gmatch exhaustion, gsub replacement counts, plain-find and out-of-range bypasses, nonpositive replacement limits, failed-prefix reachability, and lazy iterator errors for missing delimiters. No runtime change was required; focused and complete repository tests pass.

M20 String Format Numeric Conversion Campaign

Lua 5.5's lstrlib.c:getformat admits only the five format flags, two width digits, and two precision digits into a bounded conversion string; checkformat then applies distinct flag sets to signed, unsigned, alternate-base, character/string/pointer, and floating conversions. The integer branches convert their argument before validating the collected specification. Decimal floating branches also convert first, whereas hexadecimal floating branches validate first. Integers are passed to printf through the configured unsigned argument carrier, preserving the full two's-complement projection; decimal and C99 hexadecimal rendering otherwise inherit the host formatter's rounding and spelling.

KLua's scanner, integer projection, per-conversion flag validation, locale-radix normalization, numeric-string coercion, and missing-argument order already matched those rules. Its Java formatter backend did not reproduce numeric printf: %g retained insignificant zeros, exact halfway values rounded away from even, binary endpoints were formatted from shortened decimal values, hexadecimal precision zero truncated instead of rounding, and non-finite zero padding/sign flags diverged. Decimal conversions now construct the exact binary value as BigDecimal, round ties to even, implement the f/e/g precision and style rules directly, and apply width after rendering. Hexadecimal conversions round the 53-bit significand by nibble precision, retain C99 subnormal layout, preserve alternate radix points, and handle carry without changing the source exponent field. Decimal validation now follows the source's argument-before-specification order; non-finite zero padding becomes spaces and explicit positive/space signs apply consistently while KLua deliberately ignores the JVM's incidental NaN sign bit. Coverage consolidates signed limits and two's-complement d/i/u/o/x/X, flag/width/precision/alternate interactions, exact minimum-subnormal and maximum-finite decimal rendering, %g thresholds and trimming, hexadecimal tie/carry/subnormal cases, signed zero/infinities/NaN, numeric strings, extras, arity, locale normalization, and diagnostic ordering. Focused string tests and the complete repository suite pass.

M20 String Pack/Unpack Integer Layout Campaign

Lua 5.5's lstrlib.c:getoption maps each fixed or sized integer directive to a byte width and carries endian and maximum-alignment state through the format. getdetails computes padding from the absolute accumulated byte position; X consumes the following option only to obtain that alignment. packint writes the low Lua-integer bytes in the selected order and extends signed values with 0xff beyond eight bytes. unpackint reconstructs at most eight low bytes, sign-extends narrower signed inputs, and requires every wider high byte to be the correct zero or sign extension. Signed and unsigned packing checks overflow only below the Lua-integer width. All three format entry points scan a C-string pointer, and str_unpack uses posrelatI, which maps zero and every overly negative initial position to byte one.

KLua's integer encoding, two's-complement unsigned projection, 1-through-16 size limits, narrow overflow checks, wide-extension validation, endian reversal, absolute alignment, X consumption, padding bytes, result positions, and diagnostic sequencing already match those rules. Three surrounding details did not: the default ! cap was 16 instead of the configured scalar union's 8-byte alignment, embedded NUL continued into the format instead of ending it, and unpack rejected zero or positions before -#data instead of clipping them to the first byte. The scanner now uses the C-string-bounded prefix, the default cap is eight while explicit !16 remains available, and position normalization mirrors the source's overflow-safe branch order. Coverage exercises every fixed integer option, every i[n]/I[n] width from 1 through 16, exact narrow signed/unsigned boundaries, little/big order, positive/negative wide extension, malformed high bytes, short-data precedence, all alignment caps, X look-ahead from zero and nonzero starting positions, NUL suffix bypass, clipped zero/minimum positions, ignored extras, result arity/positions, and combined diagnostic order. Focused string tests and the complete repository suite pass.

M20 Compiler Long-Branch Campaign

Lua 5.5's lopcodes.h gives OP_JMP the wide signed sJ field, while lcode.c:condjump emits a test/comparison followed by that separate jump and fixjump patches jump-list destinations. Numeric and generic loop opcodes likewise use separate control-flow fields large enough for substantial function bodies. KLua instead fused each conditional or loop operation with a signed one-byte displacement, so otherwise valid source failed once a forward or backward span left -128 through 127 instructions.

KLua now follows the source's test-plus-jump structure: TEST, FOR_TEST, and FOR_LOOP are paired with a separate JMP, and JMP stores an absolute unsigned 24-bit instruction target. The VM implements the paired skip/execute behavior, the compiler patches one common wide representation for forward and backward edges, and bytecode decoding rejects out-of-stream targets or unpaired control instructions. Because instruction semantics changed, the internal KLua bytecode package format is version 3. Coverage crosses the old limit for if/elseif, logical short circuiting, while, repeat, numeric and generic for, break, and goto; it also round-trips packages and string.dump, preserves runtime debug lines, checks disassembly lowering, and rejects malformed streams. Focused compiler/VM/package tests and the complete repository suite pass.

M20 String Pack/Unpack Floating Layout Campaign

Lua 5.5's lstrlib.c:getoption maps f, n, and d to C float, the configured lua_Number, and C double. str_pack checks a Lua number, narrows only the Kfloat branch through a C cast, then copywithendian copies or reverses the in-memory representation; str_unpack performs the inverse copy and promotes float back to lua_Number. The default luaconf.h configuration makes lua_Number a double. Alignment and X still flow through the common getdetails path, and the argument is converted before a later format option is scanned. The shared string-to-number path in lobject.c:l_str2d accepts decimal and hexadecimal Lua numeral grammar, rejects named non-finite spellings, and does not reject strtod overflow results.

KLua's existing binary32 narrowing, binary64 d/n storage, raw byte order, alignment, unpack promotion, and result projection already match those rules. The audit exposed a surrounding shared-coercion defect: Java parsing admitted Java-only f/d suffixes, while API/native and VM paths discarded exponent overflow instead of producing signed infinity. VM, API/native-call, and base tonumber conversion now validate the Lua decimal/hexadecimal grammar before using the JVM parser and retain overflow/underflow results. Coverage consolidates exact ties-to-even binary32 narrowing, signed zeros, normal/subnormal endpoints, overflow to infinity, quiet-NaN payload/sign round trips, f/d/n native and opposite endian layouts, alignment and X, pack sizes, result subtype/arity/position, ignored extras, decimal/hexadecimal/locale numeric strings, rejected Java suffixes and named values, short data, and source-ordered errors. Focused core/API/string tests and the complete repository suite pass.

M20 String Pack/Unpack Variable-String Layout Campaign

Lua 5.5's lstrlib.c treats Kchar, Kstring, and Kzstr as three distinct byte layouts. c[n] copies at most n bytes and pads the remainder with LUAL_PACKPADBYTE, accepts c0, and never aligns by its apparent size. s[n] writes an unsigned length prefix in the selected byte order, rejects lengths that do not fit when the prefix is narrower than lua_Unsigned, and aligns by the prefix width; unpacking validates wide prefix extension before checking that the declared payload remains. z accepts no embedded zero when packing and stops at the first zero when unpacking. X rejects c and z targets but can consume s[n] solely for its alignment. Fixed-size capacity is checked before the option argument, while value-specific errors occur before any later format option is scanned.

KLua's existing fixed padding, unsigned prefix encoding/decoding, raw payload copies, zero-termination logic, absolute-position alignment, and diagnostic sequencing match those rules; no runtime change was required. Coverage now exercises raw NUL/control/high bytes, c0 and padded c[n], every little- and big-endian s[n] width from 1 through 16, exact one- and two-byte length limits, malformed wide prefixes, zero and unterminated z values, numeric-to-string coercion, ordinary and X alignment, nondefault absolute positions, pack sizes, ignored extras, result arity/next positions, JVM result limits, short prefix/payload data, and successful-versus-failing later-format precedence. Focused string tests and the complete repository suite pass.

M20 String Byte-Primitives Campaign

Lua 5.5's lstrlib.c:str_len, str_sub, str_reverse, str_rep, str_byte, and str_char operate on raw string bytes. posrelatI maps zero and positions before -len to byte one, while getendpos maps zero to the empty end position and positions before -len to byte zero. Consequently, string.byte(s, 0) and string.byte(s, -#s - 1) return no values when their end defaults from the same original position. Repetition validates its optional separator before handling a nonpositive count, checks the combined copy size before allocation, and omits the final separator. Character conversion checks every argument left to right and accepts only the unsigned-byte range.

KLua's existing shared raw-byte range, repetition-size, and integer/string conversion helpers already match those rules, so no runtime change was required. A consolidated matrix now proves the complete 0-through-255 byte/char round trip, reversal and slicing of NUL/control/high bytes, UTF-8 byte length behavior through existing coverage, positive/zero/negative/full-width extreme positions, empty and reversed intervals, exact zero-versus-multiple result arity, numeric subject and separator coercion, empty and oversized repetition results, ignored trailing arguments through existing tests, and source-ordered sub, byte, rep, and char diagnostics. Focused tests, the complete standard-library suite, and the complete repository suite pass. Result allocation remains subject to the documented JVM indexed-array/string limit.

M20 string.find Plain-Search/Index Campaign

Lua 5.5's lstrlib.c:str_find_aux converts the subject, pattern, and initial position before rejecting starts beyond the end. For string.find, either a truthy fourth argument or nospecials selects the length-bounded lmemfind path. nospecials scans every NUL-delimited segment of the explicit pattern length for the exact ^$*+?.([%- byte set; notably, an unmatched ) is not in that set and is therefore literal for automatic find search even though it remains an invalid capture close in the pattern engine. lmemfind returns the first byte match, treats an empty needle as present at every allowed start, and never depends on C-string termination.

KLua previously delegated the automatic decision to its pattern tokenizer. That agreed for ordinary text but classified ) as a capture close and raised where source string.find takes the plain branch. string.find now tests the exact source special-byte set after raw-byte normalization, including bytes beyond embedded NULs, while explicit plain search and all true pattern cases keep their existing paths. Coverage consolidates NUL/control/high/UTF-8 needles and subjects, continuation-byte starts, first and overlapping matches, empty needles at the first, last, and end-sentinel positions, zero/negative/full-width extreme starts, exact byte-index and nil result arity, Lua truthiness, numeric subject/pattern/start coercion, ignored extras, parser bypass past the end, special reachability after NUL, literal unmatched ), and source-ordered argument errors. Focused tests and the complete repository suite pass.

M20 Table Sequence-Mutation Campaign

Lua 5.5's ltablib.c:aux_getn first verifies the read, write, and length capabilities required by table.insert and table.remove, then evaluates length before either function validates its remaining arguments. tinsert wraps length + 1 with Lua-integer arithmetic, validates an explicit position through unsigned projection, and shifts from the tail toward the insertion point by interleaving each indexed read with its indexed write. tremove reads the result before shifting toward the head, accepts the source's special empty/after-end and negative-length positions, clears the last visited slot, and ignores arguments after its optional position. Neither operation rolls earlier writes back when a later metamethod call fails.

KLua's existing length, unsigned-boundary, indexed metamethod, and interleaved shift logic already matches those rules, so no runtime change was required. Existing coverage already spans ordinary and table-valued elements, wrapped full-width lengths and positions, empty and negative-length removals, primitive/table-like receivers, chained and failing __newindex routes, numeric-string/fractional positions, and exact diagnostics. New matrices add sparse sequences under a fixed custom length, zero-result insert versus one-result remove arity, ignored trailing removal arguments, __len execution before wrong-arity rejection, exact read/write callback order, and retained partial mutations when a later insert write or removal read fails. Focused tests and the complete repository suite pass.

M20 table.concat Range/Coercion Campaign

Lua 5.5's ltablib.c:tconcat evaluates aux_getn before its separator and range arguments, then iterates while i < last, appending one field and one separator per iteration. It handles i == last separately, so the inclusive endpoint is read exactly once without incrementing the maximum Lua integer. addfield accepts only Lua strings and numbers, converts numbers through Lua's ordinary string representation, and reports the first invalid indexed value. Reversed ranges still return one empty string after all option conversions, and indexed reads use the ordinary metamethod path.

KLua previously used an inclusive while (index <= end) loop and incremented after every field. A range ending at math.maxinteger therefore wrapped to math.mininteger after its final field and could continue indefinitely. The implementation now mirrors the source's less-than loop plus separate final field, preserving separator placement and eliminating the overflow. Existing coverage already proves Lua numeric spellings, sequence and explicit lengths, table-like primitive/nested-index routing, length conversion, non-yieldable callbacks, invalid fields, option diagnostics, and reversed ranges. New matrices cover one- and two-element ranges at both full-width endpoints, exact empty result arity, raw NUL/high/UTF-8 values and separators, numeric separator coercion, and ignored extras. Focused tests and the complete repository suite pass; aggregate results retain the general JVM indexed-string limit.

M20 table.unpack Range/Result Campaign

Lua 5.5's ltablib.c:tunpack converts the optional start before lazily evaluating either an explicit end or luaL_len as its default. A reversed range returns no values before any count or indexed-read work. For a nonempty range it computes the span through unsigned Lua-integer projection, rejects counts that cannot fit the C result count or current Lua stack, pushes every index strictly before the endpoint, and then pushes the endpoint once. That separate final read avoids incrementing LUA_MAXINTEGER while preserving nil slots in the exact result count.

KLua's signed exact-arithmetic and Int.MAX_VALUE count checks already reject wrapped/full-width spans before any indexed read, and existing tests cover ordinary, sparse, table-like primitive, nested-index, and string receivers; byte-length defaults; nil preservation; explicit versus default length behavior; noninteger lengths; numeric-string/fractional ranges; empty primitive bypasses; non-yieldable callbacks; and exact diagnostics. Its inclusive loop still incremented the final index, so a valid one-element range at math.maxinteger wrapped and could continue indefinitely. The loop now follows the source's half-open prefix plus one final read. New coverage proves both full-width endpoints, adjacent maximum indices, one nil result versus zero reversed results, pre-index oversized-span rejection, and exact read counts. Focused tests and the complete repository suite pass. KLua materializes results in a JVM list, so its aggregate capacity follows that model rather than Lua's dynamically available C-stack slots.

M20 Table Construction Campaign

Lua 5.5's ltablib.c:tcreate converts both allocation hints before checking either range, casts them to lua_Unsigned, rejects values above INT_MAX in argument order, and ignores later arguments. tpack creates a fresh table, moves every original argument into its integer field without changing identity, and records the exact argument count in n, including arguments whose nil values leave no stored field.

KLua's creation conversion, validation, empty result, and ordered diagnostics already match those rules; the nonnegative hints are deliberately validation-only because eager JVM allocation would make otherwise valid large hints observably fail. table.pack previously read function arguments through the host-oriented context getter, replacing a Lua closure with a new host wrapper when the result table crossed back into the VM and thereby breaking raw identity. It now reads each argument through the Lua-value path, which retains the original core function reference while preserving tables, threads, userdata, primitives, and nil. Consolidated coverage proves zero, negative, oversized, and full-width hints; optional and ignored arguments; numeric-string coercion; conversion-before-range failure order; empty tables and exact result arity; nil holes; every Lua value kind; exact n; no metatable; and independent packed-table identity. Focused tests and the complete repository suite pass.

M20 IO Read-Format Campaign

Lua 5.5's liolib.c:g_read validates and executes each format inside one loop, stopping after the first failed read; a later invalid format therefore cannot prevent earlier successful reads. read_line treats only LF as the terminator and retains CR, read_all always succeeds with a string, zero-character reads probe EOF without consuming input, and read_chars returns any nonempty partial block. Numeric formats first use luaL_checkinteger, while read_number consumes its bounded candidate prefix, leaves one lookahead byte, and returns nil when conversion fails. The lines constructors copy format values without converting them and defer validation to iterator execution.

KLua previously snapshotted every format through the host-oriented getter. A table or Lua function in any later position consequently failed before the first format executed, used an internal host-conversion diagnostic, and made lines reject such values at iterator construction. Read formats now capture lossless Lua values and retain their type tags, so conversion occurs in source order. KLua's floating count conversion also previously saturated values beyond the Lua-integer domain and misclassified them as valid integers outside JVM capacity; it now rejects them at the Lua conversion stage before applying the intentional Int.MAX_VALUE read-buffer limit. Existing coverage already spans ordinary/default reads, empty and partial EOF shapes, zero probes, selector parsing, numeric token/subtype/locale/length behavior, and incremental string-format failures. New matrices add table/function delayed validation across direct, default, and iterator reads; fractional/out-of-domain/full-width/capacity count separation; no-consumption failures; embedded NUL/high bytes; CRLF retention and LF chopping; and exact diagnostics. Focused tests and the complete repository suite pass.

M20 IO Write/Flush Campaign

Lua 5.5's liolib.c:g_write converts and writes arguments one at a time, formats actual numbers through lua_numbertocstring, accumulates every byte accepted before the first short write, and returns either the file handle or nil, message, code, bytecount. luaO_tostringbuff first formats floats with 15 significant digits, reparses that candidate, and retries with 17 digits when necessary to preserve the exact binary value. Direct and default flush both delegate to fflush through luaL_fileresult, ignore extra arguments, and return exactly one truthy value on success.

KLua's incremental conversion, raw-byte writes, handle identity, direction handling, buffered failure attribution, partial byte counts, and success/failure shapes already follow those rules. Its shared float-to-string helper previously stopped after the 15-digit candidate, so values requiring the source's precision retry—such as 0x1.0000000000001p0—were written as 1.0 and lost their exact value. The helper now reparses the locale-aware ordinary candidate and emits the 17-digit form when it does not compare equal. Existing matrices already cover empty and multiple writes, raw NUL/high bytes, integer/ordinary/special floats, direct/default routing, invalid later arguments after earlier writes, unwritable and closed handles, partial stream failures, all buffer modes, close-time drains, and flush failure tuples. New coverage adds the precision-retry boundary on both entry points plus ignored flush extras, exact one-result success arity, and buffered visibility after direct/default flush. Focused tests and the complete repository suite pass.

M20 IO Open/Default-Handle Campaign

Lua 5.5's liolib.c:l_checkmode accepts one of r, w, or a, an optional immediately following +, and then zero or more configured extension bytes (b by default). The C fopen mode controls creation: r and r+ require an existing file, while w/w+ create and truncate and a/a+ create and force writes to the end. g_iofile treats strings and numbers as filenames, validates an open file handle otherwise, replaces the registry default only after a successful open/check, and returns the selected handle. io.close defaults only when its argument is absent, while io.type distinguishes open, closed, and non-file values; these entry points ignore later arguments.

KLua's mode grammar, C-string boundaries, direction and append behavior, default identity/atomic replacement, close/type distinctions, temporary files, standard handles, and error ordering already follow those rules. Its writable update modes used Java RandomAccessFile("rw"), which creates missing files; consequently r+ incorrectly succeeded and created a path that source fopen("r+") rejects. Parsed read modes now require the path to exist before opening through the JVM read/write handle, while w+ and a+ retain their source creation behavior. Existing tests cover mode extensions and invalid placements, read/write/append direction, C-string and numeric filenames, open failures, default getters/setters and closed defaults, temporary/standard/process handle lifecycles, close results, and io.type. New coverage isolates missing-file r+ versus w+/a+, verifies no unwanted path creation, and adds ignored-extra probes for nil default getters, close, and type. Focused tests and the complete repository suite pass.

M20 IO Seek/Buffering Campaign

Lua 5.5's liolib.c:f_seek checks the file handle, validates its optional origin through luaL_checkoption, and only then converts the optional offset. It narrows through the host l_seeknum, rejects lossy narrowing, calls l_fseek, and returns either the resulting position or a luaL_fileresult failure. f_setvbuf likewise validates its required mode before converting the optional size, casts that Lua integer to host size_t, and delegates success or failure to the C library. Extra arguments are ignored. The source relies on host stdio for buffer allocation, flushing, and seek width.

KLua's defaults, C-string options, full signed position arithmetic, position preservation on failures, stream-handle tuples, buffer modes, deterministic capacity policy, and drain-before-seek/reconfigure/flush/close behavior already match the applicable rules. It previously converted the offset before rejecting an invalid origin, so seek("bad", {}) incorrectly reported argument 3 instead of the source's argument-2 option error. Origin validation now occurs immediately after conversion of the origin string and before any offset access. Existing matrices already cover all origins, negative/beyond-end/full-width positions, overflow and invalid-position failures, numeric-string/fractional offsets, nonseekable handles, C-string origins, buffer visibility and thresholds, all modes, read-only handles, reconfiguration and lifecycle drains, partial failures, invalid modes/sizes, and JVM size limits. New coverage adds the combined origin/offset failure, full-width setvbuf boundaries, and ignored trailing arguments for successful seek and unbuffered configuration. Focused tests and the complete repository suite pass.

M20 IO Line-Iterator Lifecycle Campaign

Lua 5.5's liolib.c:aux_lines captures the file, format count, ownership flag, and up to 250 raw format values in the iterator closure. file:lines returns only that iterator and never owns the file. Default io.lines also returns one iterator over the current input, whereas named io.lines opens an owned file and returns the iterator, nil state, nil control, and file close value. io_readline ignores generic-for call arguments, replays the captured formats through g_read, returns every result when the first is truthy, raises read errors, closes owned files only on ordinary false-first-result EOF, and then returns no values. Consequently, "a" yields a truthy empty string forever at EOF unless the fourth close value or caller closes the file.

KLua's captured raw formats, delayed validation, result projection, ownership flag, EOF/error distinction, automatic and generic-for close paths, closed-call diagnostic, and format-count enforcement already match those rules, so no runtime change was required. Existing tests cover ordinary/blank lines, file/default/named iterators, early generic-for exit, write-only read errors, delayed invalid string and unsupported-value formats, incremental multiple-format failures, named open failures, closed defaults, and the exact 250-format limit. A consolidated matrix now proves exact one-versus-four constructor arity, two-result successful iterations, zero-result EOF, caller-owned handles remaining open, named handles closing at EOF, repeated closed calls, nil-default routing, zero-count EOF closure, and the source-specific live empty-string "a" result after consuming the file. Focused tests and the complete repository suite pass.

M20 IO Process-Stream Campaign

Lua 5.5's liolib.c:io_popen converts the command and optional mode before validating the platform mode grammar. POSIX builds accept only r or w; Windows additionally accepts one trailing b or t. The command and mode are C strings, later arguments are ignored, and success returns one file handle. Closing delegates to pclose and luaL_execresult, producing true, "exit", 0 for success or nil with exit/signal metadata for failure; the file __close path suppresses those results.

KLua's host-dependent mode grammar, C-string command/mode routing, shell launch, stream direction, buffering and close ordering, one-result open, three-result successful/nonzero close, automatic-close suppression, and closed/type behavior already match those applicable rules, so no runtime change was required. Existing tests cover default/ignored arguments, POSIX-versus-Windows binary/text modes, invalid placement and type ordering, read and write processes, raw line handling, buffered writes, flush and close, zero/nonzero status, explicit and automatic closure, and C-string suffixes. The C-string matrix now also asserts exact one-result open and three-result successful close arity. KLua can report only JVM numeric process exits; it cannot recover POSIX wait-status signal metadata, and Java pipe streams do not reproduce Windows C text-mode translation. Focused tests and the complete repository suite pass.

M20 OS Filesystem-Result Campaign

Lua 5.5's loslib.c:os_remove converts one filename through luaL_checkstring, calls C remove, and passes that same C string to luaL_fileresult for failure diagnostics. os_rename converts both names left to right before calling C rename, but requests an unprefixed failure message. Both return one truthy result on success or nil, message, code on failure and ignore later arguments. os_tmpname ignores all arguments, generates a unique filename without creating the file, raises if generation fails, and returns exactly one string.

KLua's conversion order, numeric formatting, file/directory removal, host-conditional replacement behavior, result shapes, ignored arguments, and create-then-delete temporary-name emulation already match those rules. Filesystem names previously retained bytes after embedded NULs and were passed whole to Path.of, so valid source prefixes failed as JVM invalid paths instead of naming the prefix. Remove and rename now truncate both converted names at the first NUL before path construction and diagnostics; JVM InvalidPathException is also mapped to a three-result file failure rather than escaping as a runtime error. Existing tests cover successful rename/remove, empty directories, replacement differences, numeric filenames, exact success arity, validation order, and unique writable-but-not-created temporary names. A consolidated matrix adds NUL-suffixed source/target/remove names, ignored extras, missing remove and rename failures, exact failure arity, and diagnostics that exclude discarded suffix bytes. Focused tests and the complete repository suite pass.

M20 OS Command/Exit Campaign

Lua 5.5's loslib.c:os_execute obtains an optional C string, clears errno, and calls the platform l_system. A nil or absent command returns exactly one boolean describing shell availability. An actual command delegates to luaL_execresult, returning true, "exit", 0 for success, nil plus exit/signal metadata for a completed failure, or a luaL_fileresult triple when system itself fails. os_exit maps exact booleans to EXIT_SUCCESS/EXIT_FAILURE, otherwise converts an optional Lua integer and casts it to host int; it evaluates the second argument only for Lua truthiness, optionally closes the state, and terminates without returning.

KLua's real host-shell routing, shell probe, completed-command and launch-failure result shapes, boolean/integer status mapping, full-width status projection, close flag, embedder handler, non-catchable exit signal, ignored arguments, and validation order already follow the applicable rules. Command strings previously retained bytes after an embedded NUL, so the JVM rejected or interpreted a suffix that source system never sees; os.execute now truncates the converted command at the first NUL before shell launch. The consolidated matrix covers nil probes, zero/nonzero and missing commands, C-string and numeric commands, exact result arity, forced argument-vector launch failure, type errors, full-width exit statuses, close-state truthiness, ignored extras, handler calls, and handler suppression before conversion errors. JVM process APIs expose numeric exits but not POSIX wait-status signal metadata, and launch diagnostics/codes remain Java substitutes for C errno. Focused tests and the complete repository suite pass.

M20 OS Date/Time Campaign

Lua 5.5's loslib.c:os_date converts the optional length-bearing format and optional checked integer time in argument order, selects UTC only from a leading !, recognizes the table form through the C-string comparison with "*t", and otherwise scans the complete format length while validating each configured strftime item. os_time reads year, month, day, optional hour, min, sec, and truth-valued isdst in that order, normalizes through mktime, then writes year, month, day, hour, min, sec, yday, wday, and defined isdst back before rejecting an unrepresentable result. os_difftime converts both checked times left to right and returns one Lua float without signed-integer subtraction overflow.

KLua's C-string table-form boundary, length-aware literal/format processing, UTC/local tables and strings, format validation order, locale/timezone projection, input defaults and coercion, field/metamethod reads, normalization, DST overlap/gap hints, sentinel failure, full-width field/time boundaries, exact wide differences, result arity, ignored extras, and diagnostics already follow those applicable rules. Normalized write-back previously placed wday before yday; that differed from setallfields and was observable through __newindex. Both normalized table construction and os.time write-back now place yday first, and a proxy-table regression records the complete nine-field source order. Existing matrices cover the portable and configured C99 format sets, modifiers, malformed and NUL-bearing formats, local/UTC round trips, locale names, timezone abbreviations, ambiguous/contradictory DST hints, numeric-string and metamethod fields, adjusted C-int bounds, unrepresentable instants, signed extremes, exact result types, and argument ordering. KLua deliberately uses the JVM timezone database and stable Java-backed locale layouts, exposes the configured C99 conversion set on every host, and has no native 250-byte strftime item limit or installation-specific mktime range. Focused tests and the complete repository suite pass.

M20 OS Locale/Environment/Clock Campaign

Lua 5.5's loslib.c:os_setlocale converts the optional locale string first, then validates the optional category C string against all, collate, ctype, monetary, numeric, and time, calls C setlocale, and returns exactly the resulting name or nil. os_getenv checks one C string and returns exactly the environment value or nil. os_clock ignores all arguments and returns exactly one float computed from the process CPU clock and CLOCKS_PER_SEC; reopening the library does not reset that process clock.

KLua's locale-name parsing, C/reset/query forms, category validation and source order, one-result success/failure shapes, environment coercion and lookup results, ignored extras, and continuous process-CPU clock already follow those applicable rules. os.setlocale previously retained bytes after embedded NULs in both checked arguments, so a valid locale or category prefix was rejected instead of reaching the host API. Both strings now truncate at the first NUL before validation and selection; a focused matrix proves locale selection, category queries, ignored extras, exact arity, and suffix-free invalid-option diagnostics. Existing coverage proves all ordinary category routes, unavailable and numeric locales, restoration, existing/missing/invalid/numeric environment names, C-string environment lookup, type errors, CPU monotonicity, result finiteness, exact clock arity, and continuity across independent states/library opens. The JVM exposes one global Java locale rather than independent C categories, accepts only Java-installed locale names, has no mutable process environment API, and may fall back from process CPU accounting to current-thread CPU time. Focused tests and the complete repository suite pass.

M20 Package Path-Resolution Campaign

Lua 5.5's loadlib.c:searchpath first replaces the configured separator in the checked module name, then substitutes that complete name for every ? in the complete checked path, and only afterward lets getnextfilename split the expanded buffer on semicolons. Thus a semicolon introduced by the module name or replacement creates additional candidates. ll_searchpath returns the first host-openable filename or nil plus the complete candidate diagnostic; findfile applies the same routine to package.path/cpath, and the Lua, C, and C-root searchers return the selected filename as loader data. setpath chooses versioned then unversioned environment paths, expands only the first ;;, applies platform defaults and Windows executable-directory substitution, and honors registry LUA_NOENV.

KLua's C-string and numeric conversions, separator/name and all-marker substitution, empty/repeated templates, host-openability probing, exact search results and diagnostics, Lua/C/C-root searcher names and loader data, platform defaults, environment precedence, first-marker insertion, executable substitution, and registry/config opt-out already follow those applicable rules. Direct search previously split the original path into templates before substituting the normalized name, so a semicolon introduced by that name remained part of one literal filename. Search now expands the complete path first and splits the resulting candidate list afterward. A regression proves the first inserted candidate is selected by both package.searchpath and the built-in Lua searcher, whose returned loader data is that exact extensionless filename and whose loader executes successfully. Existing matrices cover all four checked strings and C-string boundaries, numeric coercion, empty/default/custom separators, multiple markers, invalid paths, directories, first matches, miss arity/diagnostics, Lua load errors, C/C-root fallback names and failures, path/cpath types, environment defaults, ignored extras, and platform-specific openability. Native loader creation remains intentionally absent, and Java stream openability is the pure-JVM substitute for C fopen. Focused tests and the complete repository suite pass.

M20 Package Require/Cache Campaign

Lua 5.5's loadlib.c:findloader reads package.searchers with raw integer access, stops at the first nil entry, calls each searcher for exactly two results, accepts a function as the loader, and otherwise appends the first result when lua_isstring succeeds. That predicate includes numbers through cvt2str; secondary results are discarded. ll_require uses the checked C-string name for registry cache and searcher lookup, but passes the original stack value and selected loader data to the loader. A truthy cache returns immediately with one result; false/nil invokes the searchers, captures only the loader's first result, preserves a loader-written cache value when that result is nil, defaults a still-nil cache to true, and returns the fresh module plus loader data. It installs no implicit recursion sentinel.

KLua's C-string lookup versus original loader argument, captured registry loaded/preload identities, truthy/false/nil cache behavior, raw sparse-searcher termination, loader arguments/data/results, default-true rule, preload data, C/C-root compatibility names, failure propagation, result arity, and loader-controlled recursion already follow those rules. Its diagnostic loop previously accepted only values already typed as strings, so numeric first results were silently discarded even though source lua_isstring converts them. Numeric and string first results now share the accumulation path; a focused matrix proves exact full-width integer and float conversion, ordering, and secondary-result suppression. A second regression proves no pre-loader sentinel is installed: the loader observes nil, installs its own truthy cache value, recursively requires it with one-result cached arity, and then replaces it with the final loader result. Existing coverage spans returned-value and default-true caching, false reloads, original table capture, numeric and NUL-bearing names, direct searchers, custom diagnostic protocols, raw metatable bypass, loader errors, path/searcher type errors, and exact fresh-versus-cached result counts. Native C loader creation remains intentionally unavailable. Focused tests and the complete repository suite pass.

M20 Binary Dump/Load Campaign

Lua 5.5's lstrlib.c:str_dump accepts only a Lua function, reads strip through Lua truthiness, ignores later arguments, and returns exactly one binary string. lbaselib.c:luaB_load converts a direct string/number source before checking the optional mode C string, otherwise repeatedly calls the reader and accepts its string-convertible results; it applies the optional fourth argument as the first upvalue even when that argument is nil. luaB_loadfile converts the optional filename C string before validating mode and likewise treats an existing third slot as the environment. Both loaders return exactly the compiled function or nil plus the load diagnostic. luaL_loadfilex uses the C-string filename for opening and the @filename source name, while dofile uses the same filename boundary.

KLua's function validation, raw package bytes, strip metadata, reader conversion/progression, text/binary/default mode gates, explicit/default source names, environment replacement, file and standard-input loading, success/failure arity, and malformed-package diagnostics already follow those applicable rules. Mode, explicit chunk-name, and filename controls previously retained suffixes after embedded NULs, and loadfile validated mode before converting its filename. Those controls now stop at the first NUL, loadfile restores filename-first validation, and JVM invalid-path/security failures are mapped to ordinary nil, message load failures instead of escaping the Lua result protocol. Focused matrices prove text and dumped inputs, numeric-zero strip truthiness, exact dump arity, stripped debug source metadata, reader and environment behavior, C-string controls, source-validation order, file/dofile routing, and exact truncated-package failure shape. KLua deliberately serializes its own KLua package rather than Lua's native host-dependent binary-chunk layout, so dumps round-trip inside KLua but are not portable to the reference runtime or other Lua implementations. Focused tests and the complete repository suite pass.

M20 Base Iteration/Metatable Campaign

Lua 5.5's lbaselib.c:luaB_next checks a table, adjusts to two arguments, and returns the next raw key/value or exactly one nil. luaB_pairs reads the raw __pairs metafield and calls it for exactly four adjusted results, or returns the light C function luaB_next, the original state, nil control, and nil closing value. luaB_ipairs accepts any present value and returns the stable light C function ipairsaux, the original state, and integer zero; the auxiliary checks and wrap-increments an integer control before metamethod-aware integer indexing, returning the new index alone at nil. luaB_getmetatable exposes a non-nil raw __metatable value instead of the metatable, while luaB_setmetatable accepts only a table receiver and nil/table replacement and rejects every non-nil protection value. The raw helpers validate in source order, bypass metamethods, preserve raw identity, and return exactly one result.

KLua's iterator tuples and termination, yielding/callable/padded __pairs results, primitive metatables and index fallback chains, protected table and userdata metatables, nil/NaN raw boundaries, validation order, ignored arguments, exact arity, and diagnostics already follow those rules. Its stdlib-facing table bridge previously compared integer and exactly integral float keys by wrapper type, so next(t, 1.0) rejected an existing integer key and raw access could miss or transiently duplicate it despite Lua's canonical numeric-key rule. All stack-table lookup, mutation, fallback, host-map conversion, and traversal controls now share integral-float canonicalization. Default pairs and ipairs also created fresh host function wrappers on each call; registered host functions are now cached through the same core identity and both source light C iterators are stable singletons, making the default pairs iterator equal to global next and repeated ipairs iterators equal to each other. Focused matrices prove canonical zero/integer keys, non-duplication, stable iterator identity, false-valued table/userdata protection, false __pairs calls, terminal nil shape, raw helper arity, and ignored extras alongside the existing broad iterator/metatable suite. Focused tests and the complete repository suite pass.

M20 Base Protected-Call/Error Campaign

Lua 5.5's lbaselib.c:luaB_error narrows its optional Lua-integer level through a C int before deciding whether to add a location, while luaB_assert delegates its false path to that same level-one rule. luaB_pcall, luaB_xpcall, and finishpcall preserve target yields and return exactly the success flag plus adjusted results. The xpcall message handler is different: ldebug.c:luaG_errormsg invokes it through luaD_callnoyield, then replaces a nil final error object with "<no error object>". ldo.c:luaD_pcall keeps that handler active before luaD_closeprotected unwinds to-be-closed variables, so closers see the transformed object; a closer that fails re-enters the same handler, and a handler failure or attempted yield becomes "error in error handling".

KLua now installs the xpcall handler in the VM error path instead of invoking it in the base-library catch after stack unwinding. The handler remains attached across protected target yields, runs non-yieldably at the original error site, marks transformed errors so outer frames do not handle them twice, and runs again for replacement close errors before later closers execute. Nil remains visible to the handler and is normalized only afterward; the same final normalization now covers plain pcall, coroutine resume/close/wrap failures, and direct native LuaState.pcall. error levels use the source-compatible 32-bit narrowing rule. Focused matrices cover exact result counts, non-string identity, full-width levels, nil handling, target versus handler yields, handler failures, handler/close ordering, repeated handling after close failure, normalized close arguments, and API/coroutine propagation alongside the existing validation, location, callable-value, ignored-argument, and success/failure coverage. Focused API/stdlib tests and the complete repository suite pass.

M20 Base Garbage-Collector Control Campaign

Lua 5.5's lbaselib.c:luaB_collectgarbage selects both operation and parameter names through luaL_checkoption, so numeric conversion and C-string boundaries apply before dispatch. step reads an optional Lua integer and casts it through size_t. The param branch reads its optional value as a Lua integer, narrows it through a C int, returns the previous setting, and passes nonnegative values to lapi.c:lua_gc. That API stores each setting through lobject.c:luaO_codeparam's saturating floating-byte encoding and reports it through luaO_applyparam, so values such as 201 and 8192 read back as 200 and 8000. Collector running state, mode, and parameters belong to the Lua state rather than a particular base-library closure.

KLua now applies C-string boundaries to collector operation and parameter names, performs the source-compatible 32-bit value narrowing, and stores parameter settings in the same encoded form with the same rounding and 396800 saturation. Logical running/mode/parameter state and one stable collector function are weakly associated with each LuaState, survive reopening the base library, and remain isolated between states. Focused matrices cover defaults, exact result arity/types, stop/restart/collect/count/step/mode controls, optional and ignored arguments, numeric and invalid options, embedded-NUL dispatch and diagnostics, all six parameters, rounding, saturation, full-width wrapping, negative-query behavior, reopen function identity and state persistence, state isolation, and validation order. At this checkpoint KLua could not control HotSpot's automatic collector: collect and step only requested System.gc, count observed JVM heap usage rather than Lua-owned bytes, mode/parameter controls were logical compatibility state, and Lua __gc finalization remained a separate VM campaign. The later lifecycle campaigns below supersede the collect/step and __gc portions of that limitation. Focused tests and the complete repository suite pass.

M20 Base Warning-Control Campaign

Lua 5.5's lbaselib.c:luaB_warn first converts and validates every argument through luaL_checkstring, then sends each C string to lua_warning, marking all but the last as continuations. lauxlib.c maintains three state-owned handlers: off, ready for a new message, and continuing a message. Only a non-continuation part beginning with @ is a control; @on and @off switch handlers and unknown controls are suppressed. Continuation parts beginning with @ are ordinary text. luaL_newstate installs the enabled handler; lua.c explicitly sends @off only as standalone interpreter policy. Embedded NULs truncate each part at the callback boundary, and reopening the base library neither changes handler state nor changes the light C function's identity.

KLua now starts its embeddable runtime warning state enabled, applies C-string boundaries independently to every validated part, and retains one stable warning function and control state per LuaState across base-library reopen. Reopening with KLua's output-consumer overload updates that host routing without resetting the Lua warning state; separate states remain isolated. Focused matrices cover default output, numeric conversion, empty and NUL-bearing parts, on/off/unknown controls, multipart continuation controls, validation-before-output atomicity, exact zero-result shape, reopen identity/state, updated output routing, and state isolation. KLua emits each complete warning line to its configured Consumer<String> instead of writing fragments to the C stderr macros. Focused tests and the complete repository suite pass.

M20 Base Print-Output Campaign

Lua 5.5's lbaselib.c:luaB_print processes arguments strictly left to right. For each argument it first calls luaL_tolstring; after that conversion succeeds, it writes a tab when needed, writes the returned string with its explicit byte length, and pops only the temporary result. A later conversion failure therefore leaves every earlier argument already written, but writes neither the separator nor any bytes for the failing argument and never reaches later conversions. With no arguments it still writes one empty line and returns no values. luaL_tolstring accepts string-convertible numeric __tostring results, preserves embedded NULs through the returned length, and otherwise applies the standard primitive or identity formatting rules.

KLua now performs print conversion in that same left-to-right sequence and retains one stable print function per LuaState across base-library reopen. Successful calls deliver one complete tab-separated line to the configured Consumer<String>; if a later conversion fails, the already-converted prefix is delivered once before the original Lua failure is rethrown. Reopening with the output-consumer overload updates host routing without changing function identity. Focused matrices cover ordinary primitive formatting, primitive and table __tostring, numeric metamethod results, non-yieldable metamethod boundaries, zero arguments, empty and embedded-NUL strings, exact zero-result shape, partial prefixes, failed-argument separator suppression, later-conversion suppression, reopen identity, and output updates. The Consumer adapter represents a C partial write as one prefix event because its host contract has no fragment/newline channel. Focused tests and the complete repository suite pass.

M20 Base Registration-Identity Campaign

Lua 5.5's luaopen_base passes all 23 base entries to luaL_setfuncs with zero upvalues. lauxlib.c:luaL_setfuncs therefore reaches lapi.c:lua_pushcclosure with n == 0, which stores each entry as a light C function through setfvalue; lvm.c:luaV_rawequalobj compares those values by their lua_CFunction pointer. Reopening the base library consequently replaces globals with values equal to their previous functions, including yieldable protected/file helpers and every stateless conversion, raw, iterator, and metatable helper.

KLua now registers every stateless base entry from one stable LuaFunction or LuaYieldableFunction singleton. The already stateful collector, print, and warning functions retain their stable per-state objects. dofile and loadfile share one weakly state-associated holder that captures only the immutable configured standard-input supplier, preserving identity without retaining the LuaState key. A complete source-list matrix snapshots and compares all 23 functions across reopen while also proving _G identity and _VERSION; existing safe/unsafe configuration and file/stdin tests retain conditional availability and behavior. Focused identity coverage and the complete repository suite pass.

M20 Coroutine Registration/Runtime-Ownership Campaign

Lua 5.5's lcorolib.c:luaopen_coroutine calls luaL_newlib, which creates a fresh library table and registers all eight co_funcs entries through luaL_setfuncs with zero upvalues. Each reopen therefore returns a distinct table containing the same light C functions. Those functions operate on the shared Lua state/thread graph rather than state owned by a particular library-table generation, so saved functions, newly registered functions, and coroutines created through either generation interoperate.

KLua previously allocated a new CoroutineRuntime and eight new host functions for every open. Besides changing every function's identity, mixing a saved resume with the reopened global yield split the current-coroutine state and rejected a valid yield as occurring outside a coroutine. KLua now retains one weakly state-associated coroutine library holder containing the runtime and all eight functions. Its main-thread update callback holds only a WeakReference<LuaState>, so the map value does not retain its weak key. A focused source-list matrix proves fresh table identity, stable function identity, stable main-thread identity, bidirectional old/new create/resume/yield, a saved wrap across reopen, cross-generation close, and final statuses; the existing lifecycle suite retains state isolation and broader behavior coverage. Focused tests and the complete repository suite pass.

M20 Coroutine Resume/Wrap Transfer Campaign

Lua 5.5's lcorolib.c:auxresume checks capacity on the target thread before moving every argument, calls lua_resume, then checks caller capacity before moving all yielded or returned results. luaB_coresume prepends exactly one success boolean, or returns false plus the single final error object. luaB_cocreate accepts only an actual function and ignores later arguments; luaB_cowrap applies the same rule and captures the new thread. On a wrap failure, luaB_auxwrap closes an actually failed coroutine before propagating its final error, adds a level-one caller location only when that final object is a string and not a memory error, and otherwise preserves the error object's identity and type.

KLua's existing transfer and wrapper paths already follow those portable rules, so no runtime change was required. Consolidated coverage proves actual-function rather than callable-table validation without invoking __call, ignored create/wrap extras, all resume arguments, zero-result yields/returns, multiple results and nil holes, repeated Lua and host-continuation yields, cyclic table identity, dead/normal target errors, string and embedded-NUL location prefixes, unprefixed numeric and false errors, table identity, close-before-propagation ordering, string/numeric/table close replacements, and exact ordinary result counts. KLua uses JVM lists and heap allocation rather than Lua's queryable C-stack capacity, so otherwise valid transfers are practically bounded by JVM indexed collections and memory and cannot reproduce the source's pre-transfer "too many arguments to resume" or "too many results to resume" branches at a deterministic Lua stack limit. Focused tests and the complete repository suite pass.

M20 Coroutine Status/Yieldability/Close Lifecycle Campaign

Lua 5.5's lcorolib.c:auxstatus derives running, normal, suspended, and dead from current-thread identity, lua_status, live frames, and the initial function slot. getoptco defaults only an absent thread argument; luaB_yieldable delegates to lua_isyieldable for the selected thread, and luaB_corunning always returns the current thread plus its main-thread flag. luaB_close resets dead or suspended targets, rejects the main and normal states, and lets a running non-main coroutine close itself. lstate.c:lua_closethread runs protected non-yieldable closing on the target state, so close handlers observe that target as running, receive nil or the active/replacement error in reverse order, and restore the caller only after reset. A self-close exits the running body without returning from coroutine.close; a close failure becomes the resume error and remains available to one subsequent close reset.

KLua's self-close sentinel previously marked the coroutine dead without closing its suspended VM handle, skipping active <close> variables and their errors. It now routes through the handle reset, preserves a self-close failure for one later coroutine.close, and lets coroutine.wrap perform its required failed-thread cleanup. Handle closing now temporarily installs the target as the running coroutine and marks it running while close handlers execute, then marks it dead and restores the main or nested caller in finally. Focused matrices prove successful reverse-order self-close with nil error arguments, replacement errors seen by earlier closers, forbidden close-handler yields, table identity, wrapper propagation, one-time error replay, target running/non-main/non-yieldable observations, caller restoration, optional-versus-explicit-nil thread arguments, ignored extras, exact arity, and current/main/fresh/yielded/normal/dead-success/dead-failure statuses and yieldability. Focused tests and the complete repository suite pass.

M20 Debug Registration/State-Ownership Campaign

Lua 5.5's ldblib.c:luaopen_debug calls luaL_newlib, which creates a fresh table and registers all 16 dblib entries through luaL_setfuncs with zero upvalues. Reopening therefore changes the table but preserves every light-C-function identity. db_getregistry returns the live registry directly, while db_sethook stores the hook function in a registry-owned thread-keyed table and the hook mask/count on the selected thread; neither belongs to a particular debug-table generation.

KLua previously reused the public table, compiled 16 new Lua wrapper closures, and exposed 14 native bridge globals on every open. It now creates a fresh table from 15 stable stateless native functions plus one stable state-owned core registry getter, with no generated wrappers or helper globals. The registry getter returns the live core registry without round-tripping its cyclic globals graph through a stale host snapshot. Current-thread introspection models the native debug call as level zero so established caller levels and local writes remain unchanged. Main-thread hook state now belongs to LuaState and seeds each top-level VM execution; coroutine hooks remain owned by their coroutine VMs. No weak state map is needed. A complete source-list matrix proves all 16 stable zero-upvalue C-style identities, fresh tables, registry identity and state isolation, saved/new hook interoperation and persistence across reopen and separate top-level calls, helper absence, and unchanged representative traceback/getinfo/setlocal behavior. Focused tests and the complete repository suite pass.

M20 Debug Traceback/Getinfo Boundary Campaign

Lua 5.5's ldblib.c:db_getinfo treats its option as a C string, rejects only a leading public >, distinguishes function subjects from C-int stack levels, and delegates the selected SlnurtfL fields to lua_getinfo. db_traceback similarly converts only strings and numbers, returns other message objects unchanged, uses different current/other-thread default levels, and passes a C-string message to luaL_traceback. lauxlib.c:luaL_traceback formats source, line, contextual or global function identity, Lua-definition/C fallbacks, ten-head/eleven-tail elision, and one marker for each reachable tail-call frame. ldebug.c:auxgetinfo exposes tail state and the four-bit count of receiver arguments inserted by chained __call resolution; ldo.c:tryfuncTM rejects the sixteenth insertion. A function subject has no active-frame name, transfer, tail, or extra-argument state.

KLua now truncates getinfo options and traceback messages at the first NUL, preserves the source's shifted diagnostics, ignored extras, and exact one-result arity, and renders traceback frame descriptions instead of bare locations. Native debug calls carry their source call-site name through the core/API context bridge, so level-zero C metadata and explicit level-zero tracebacks identify local, field, method, and other supported call families without injecting a native frame into ordinary error-location stacks. Lua call frames record tail position from the compiler's open CALL/CLOSE/RETURN shape, excluding live <close> slots; the debug projection collapses logically replaced callers while retaining raw VM frames for execution, local mutation mapping, and non-debug native libraries. Tail frames suppress contextual names, expose istailcall, retain extraargs, use a VM-resolved global path or definition fallback, and emit the source-style tail marker. Callable-chain limits are now 15 in both core and API-mediated protected-call paths, with the exact source diagnostic at the next insertion.

Consolidated Lua-source coverage proves NUL option/message boundaries, leading-private-option ordering, default/explicit native fields, native identity and call-site names, exact arity and ignored extras, contextual/global/main/Lua/C traceback descriptions, deep elision with tail markers, logical multi-tail collapse, suppression of tail contextual names, one- and three-receiver extraargs, the live-<close> non-tail boundary, and the 15/16 callable-chain edge. Existing matrices continue to cover current/explicit-current, fresh, suspended, normal, dead, failed, and reset thread stacks; function subjects; transfer metadata including hooks; active lines; shifted diagnostics; level wrapping; and non-coercible traceback messages. Focused tests and the complete repository suite pass.

M20 Debug Local Inspection/Mutation Boundary Campaign

Lua 5.5's ldblib.c:db_getlocal parses and narrows the local index through a C int before distinguishing a function subject from a stack level. Function subjects expose only names live at entry, while active frames delegate positive and negative indices to lua_getlocal. db_setlocal validates the selected level and index before requiring the replacement, trims ignored extras, moves that value to the target thread, and returns exactly the selected name or nil. In ldebug.c:luaG_findlocal, negative indices address hidden Lua varargs; positive indices first use luaF_getlocalname, then expose otherwise valid Lua and C stack slots as "(temporary)" and "(C temporary)", bounded by the next callee function slot or the selected thread's current top.

KLua now projects Lua locals by physical register slot rather than by filtering named debug records. An active caller's CALL A function slot and a suspended caller's pending-result base provide the same exclusive temporary boundary used by Lua's next-frame function slot; retained frames without a trustworthy dynamic boundary continue to expose their named locals without leaking the prototype's full allocated register array. Current-thread level zero carries the native debug call's arguments as C temporaries, and debug.setlocal applies the source's post-trim argument extent before resolving that native slot. Raw frame mutation still uses the tail-projection map, so logically replaced tail callers remain hidden without losing writes to a surviving caller.

Consolidated Lua-source coverage proves live and suspended Lua temporary reads and writes, current and explicit-current C temporaries, C setlocal extra trimming, positive named locals, negative varargs, function subjects, C-int level/index wrapping, logical tail replacement, mutation visibility, validation order, missing locals and levels, fresh/dead/failed thread behavior, ignored extras, and exact result arity. Focused tests and the complete repository suite pass.

M20 Debug Upvalue Identity/Mutation Campaign

Lua 5.5's ldblib.c:auxupvalue requires a replacement before setupvalue parses anything, then narrows the index through a C int before checking the function. checkupval validates each function and cell completely in target-then-source order, so upvaluejoin rejects an invalid target before parsing the source index and only afterward rejects otherwise valid C closures as non-Lua functions. lapi.c:aux_upvalue returns an empty name for C-closure cells, the prototype name for Lua cells, or "(no name)" when stripped debug data removed that name. lua_upvalueid identifies the cell rather than its current value, and lua_upvaluejoin redirects only the target closure's cell reference. ldo.c:f_parser initializes every loaded closure cell, while lua_load assigns the selected environment to the first cell.

KLua now initializes all declared cells when a compiled or dumped function becomes a standalone loaded closure, seeds the first from the selected environment, and bases upvalue existence on those cells rather than on the optional debug-name array. Missing or empty Lua names therefore project as "(no name)" for both reads and writes. upvaluejoin validates the target cell before touching source controls. Function-valued coroutine transfers and API-to-core native calls now retain their internal function wrappers instead of degrading Lua closures into host-callable wrappers; this preserves cell metadata and identity across yield/resume and through protected native calls.

Consolidated Lua-source coverage proves named and stripped cells, explicit load environments, nil initialization, C-int index wrapping, open suspended cells, closed cells, sibling sharing, target-only join redirection, mutation visibility before and after close, stable/distinct identities, target-before-source validation, native functions without cells, nil replacement, ignored extras, exact result arity, and function identity through pcall. Existing validation and table-identity matrices remain green. Focused upvalue/API tests and the complete repository suite pass.

M20 Debug Metatable/Uservalue Boundary Campaign

Lua 5.5's ldblib.c:db_getmetatable requires any first value and returns exactly its raw table or nil, bypassing the public __metatable guard. db_setmetatable validates only nil-or-table at argument two before truncating extras, applies that raw table to tables, userdata, or the selected primitive type, and returns exactly the original first value. db_getuservalue and db_setuservalue narrow their optional slot through a C int before target/value validation. A valid uservalue slot returns value, true even when the value is nil; an invalid slot or non-userdata get returns one nil, while a valid set returns the userdata and an invalid set returns one nil.

KLua's existing raw metatable service already follows those applicable rules for tables, host userdata, nil, booleans, numbers, strings, functions, and threads, including shared primitive-type state, object-specific userdata state, clearing, identity, and protection bypass. Its host-userdata uservalue bridge likewise preserves target and stored table/function identity, nil presence, C-int wrapping, source validation order, ignored extras, and exact result shapes. No runtime correction was required. Unlike Lua's lua_newuserdatauv, however, KLua's embedding API does not allocate a fixed nuvalue count; every positive host-userdata slot is dynamically available, while zero and wrapped-negative slots are invalid.

Consolidated Lua-source coverage proves locked/raw/replaced/cleared metatables, exact get/set arity, ignored extras, every supported value kind, shared-versus-object identity, wrapped valid/zero uservalue slots, valid nil presence, invalid and non-userdata one-result failures, userdata return identity, stored Lua-closure identity and cell visibility, validation order, and diagnostics. Existing metamethod integration matrices remain green. Focused tests and the complete repository suite pass.

M20 Debug Hook Event/Boundary Campaign

Lua 5.5's ldblib.c:db_sethook, db_gethook, hookf, makemask, and unmakemask apply optional thread selection, parse the mask as a C string, narrow the count to a C int, preserve only c/r/l in canonical order, and install count tracing only for a positive narrowed interval. ldebug.c:luaG_traceexec decrements and resets the count before dispatch, emits count before line when both are due, and retriggers a line event on a backward jump even when the source line is unchanged. ldo.c:luaD_hookcall, precallC, and rethook distinguish Lua "tail call" from ordinary Lua/C calls, expose argument/result transfer ranges, reuse a Lua caller frame across a tail call, and finish a yielded C boundary only when its continuation returns. The Lua wrapper is entered through a non-yieldable C call.

KLua now follows that chain. Hook masks stop at the first NUL and counts retain 32-bit C narrowing. Count events precede line events and a replacement performed by the count callback receives the still-pending line event; line history tracks every traced PC so same-line loops retrigger at backward edges. Lua closures and Lua __call targets emit "tail call", suppress the logically replaced caller's synthetic RETURN hooks, and preserve that state across yields without replaying call events on resume. Native call/return events insert a synthetic C frame beneath the hook invocation with the actual function identity, C temporary values, call-site name, and transfer fields; yield-without-continuation and host-continuation paths emit their native return event on eventual completion. Hook yields fail with the source's C-call-boundary diagnostic.

Consolidated coverage proves current and other-thread installation across fresh, suspended, failed, and dead states; reopen persistence from the registration campaign; C-string masks; unknown/duplicate mask characters; positive, negative, inert, and wrapped counts; count/line ordering and replacement/removal; backward same-line loops; Lua, tail-Lua, Lua-__call, native, and yielded-native call/return events; call/return transfer fields and C temporaries; yielded tail targets; non-yieldability; validation order; ignored extras; and exact zero/one/three-result shapes. Focused hook tests and the complete repository suite pass. The "external hook" result branch is not constructible because KLua's public host API installs Lua hook functions rather than raw C hook pointers.

M20 Native Hook Local-Mutation Campaign

Lua 5.5's luaD_hook keeps the selected CallInfo and its live stack range available while the hook runs. precallC invokes the call hook after arguments occupy the C frame, luaG_findlocal exposes the positive slots up to the current top as "(C temporary)", and lua_setlocal writes directly into those slots. rethook computes the returned range from top - nres and runs before moveresults, so return-hook writes change the values ultimately transferred to the caller. A yielded C function follows resume or finishCcall to luaD_poscall; its return slots do not exist until that eventual completion.

KLua's native hook activation now owns one mutable value list instead of an immutable inspection snapshot. Its synthetic frame rebuilds C temporary projections from that live list for each inspection, and the debug frame-selection map distinguishes the synthetic C level from underlying raw Lua levels even through tail projection. A native call hook passes its mutated list into the actual LuaNativeFunction; a native return hook passes its mutated list back into Lua result adjustment. The same path wraps yield-without-continuation and repeated host continuations, so call writes affect yielded values and eventual return writes affect resumed results. Ordinary current-native inspection and Lua-frame mutation retain their previous mappings.

Focused coverage proves read-after-write behavior, positive C temporary names and bounds, multi-result return mutation, nil/out-of-range lookup behavior, default-current and explicit-current coroutine selection, an ordinary native function, target failure without a return event, coroutine.yield's resume completion, and a host continuation's final completion. The complete stdlib and repository suites pass. The follow-up protected-call continuity gap is closed by the campaign below.

M20 Protected-Call Hook-Continuity Campaign

Lua 5.5's lbaselib.c:luaB_pcall, luaB_xpcall, and finishpcall use lua_pcallk on the current state. The yieldable branch in lapi.c:lua_pcallk marks the active C CallInfo as CIST_YPCALL, saves its error function and continuation, and calls the target without changing lua_State. On resume, ldo.c:findpcall, precover, unroll, finishpcallk, and finishCcall unwind errors to that recovery point, restore the protected handler, and continue the same C boundary. Consequently the selected thread's hook mask, count, line history, Lua/C frames, and live transfer slots remain authoritative for the target and the xpcall handler across success, yield, and recovery.

KLua now uses that same-state model for the standard-library protected-call entries. LuaCallContext.protectedCall identifies pcall without changing ordinary host callback boundaries, while callWithErrorHandler identifies xpcall; the core-backed context sends only those calls back into the active LuaVm. A lazy protected frame state carries a recovery completion and optional handler down the real Lua frame chain. A suspended Lua target completes physically on coroutine resume and then feeds its results into the pending native pcall continuation; a resumed error closes the protected frames, records the final error, and drives that same continuation with a protected failure. Native target continuations resume directly. Error handlers run non-yieldably on the selected state, yielded handler frames are unwound before producing "error in error handling", and a nested protected call shadows an outer handler even when its own handler is nil. Argument-table mutations are synchronized back after returns, yields, and errors, preventing the public bridge snapshot from overwriting changes made by a protected target.

The continuity matrix is now explicit:

Protected path Verified observable
Synchronous Lua target calling a native function Target and native call/return events occur on the installed hook; native argument/result debug.setlocal writes become the protected results.
Target line/count execution A line hook can replace itself with a count hook inside the target, and the replacement can remove itself without leaking or reverting hook state.
Lua target yield and successful resume The selected coroutine retains its hook; yielded native call/return transfers and final target results are delivered exactly once.
Lua target yield followed by error xpcall recovers on the same coroutine; the handler and its native callees emit hook events, live native argument mutation affects the handler result, and the target has no synthetic return event.
Nested pcall/xpcall Inner nil/explicit handlers shadow the outer recovery handler and preserve exact boolean/error-result shapes.

Focused protected-hook tests pass together with the complete standard-library and repository suites. Because protected native failures now see the real Lua caller, source-producing native errors and coroutine.wrap diagnostics retain the caller locations that the previous nested VM lost. The only remaining hook-registration deviation is the unconstructible raw "external hook" branch described in the gap snapshot.

M20 debug.debug Interaction/Policy Campaign

Lua 5.5's ldblib.c:db_debug writes "lua_debug> " to the error stream before every fgets, exits on EOF or "cont\n", compiles each other buffer as "=(debug command)", runs it through a zero-result lua_pcall, reports compile/runtime error objects through luaL_tolstring, and resets the stack to zero after every iteration. The command call is protected and non-yieldable but remains on the current lua_State, so a command can inspect the stack that invoked debug.debug.

KLua now exposes that loop through an explicit pure-JVM host policy. The default openDebug(state) adapter reads one UTF-8 logical line at a time from the current stdin and writes prompt/error fragments to stderr. The overload openDebug(state, LuaDebugInput, Consumer<String>) lets embedders provide deterministic nonblocking input and route output without global streams; the Java functional interface returns null for EOF. Commands compile in the normal globals and use the same-VM protected-call bridge, so their frames can inspect the original caller, their yields are rejected at the native boundary, their errors are contained, and their results cannot leak into the next command or the zero-result debug.debug return. Reopening creates a fresh debug table while retaining the per-state function identity; saved functions use the newly supplied adapters. Ignored arguments remain unread, and production configs still omit the entire debug library.

Focused Kotlin and Java coverage proves default stdin/stderr routing, injected prompt ordering, cont, EOF after a command, compile and runtime error continuation, same-state caller inspection, discarded multi-results, exact zero-result arity, ignored arguments, saved/reopened function identity and adapter replacement, and production exclusion. The JVM adapter intentionally consumes logical lines rather than reproducing C fgets's 249-byte chunking, so an overlong input line remains one command and a logical "cont" is accepted without retaining a newline byte. Primitive error objects use their direct textual form; for opaque table/userdata objects KLua reports the bridged runtime diagnostic instead of invoking luaL_tolstring on a raw C-stack value that the public exception adapter does not expose.

M20 __gc Lifecycle Foundation Campaign

Lua 5.5's lapi.c:lua_setmetatable calls luaC_checkfinalizer only when a table or full userdata receives a metatable that already contains __gc. lgc.c moves newly marked objects to the head of finobj, separates unreachable objects without reversing that list, and therefore schedules finalizers newest-first. GCTM makes the object ordinary before its protected, non-yieldable call, disables debug hooks and collector steps during that call, turns failures into warnings, and continues with later objects. A finalizer can resurrect its argument; an explicit metatable assignment during that call can mark it again for a later cycle. luaC_freeallobjects drains every already-marked object during state close, rejects new marking while closing, and runs the same protected warning path.

KLua now has a deterministic pure-JVM lifecycle registry keyed by table identity or host-userdata identity. Explicit full collection traces the core globals, registry, API stack, active VM frames, closure cells/environments, table keys/values/metatables, userdata metatables, and weakly registered suspended coroutine VMs without treating the lifecycle registry itself as a root. Unreachable marked objects run newest-first on the active VM with hooks suppressed and yields forbidden. Marking is idempotent while pending, removal is at-most-once after a call, explicit re-arming from inside __gc creates a later pending entry, resurrection remains reachable, and protected failures route through the base warning policy without aborting the collection. LuaState is now AutoCloseable; close drains every marked object regardless of reachability, releases state-owned roots, is idempotent, supports Java try-with-resources, and rejects later load/call execution.

Focused coverage proves initial-versus-late __gc discovery, table and host-userdata targets, newest-first ordering, global/live/API-stack/suspended-coroutine roots, table resurrection, at-most-once execution, explicit re-arming, protected failure continuation and warning text, hook suppression, reachable-object close finalization, close idempotence, root release, and Kotlin/Java close APIs. The complete API and standard-library suites pass. This is intentionally a lifecycle foundation rather than a replacement JVM collector: at this checkpoint automatic allocation-debt collection and step finalizer scheduling remained absent, and nested collectgarbage calls inside a finalizer were inert but still returned the ordinary logical compatibility result instead of Lua's invalid-call results. The later scheduling campaign closes those lifecycle gaps and corrects the suspended-thread close assumption through a direct close_state/luaE_freethread audit.

M20 __gc Weak-Edge Tracer Checkpoint

Lua 5.5's lgc.c:getmode reads __mode as a C string and distinguishes strong, weak-value, ephemeron weak-key, and all-weak tables. traverseweakvalue keeps keys strong, traverseephemeron marks a value only after its collectable key is marked, and convergeephemerons repeats until chained keys stop exposing new values. During atomic, original weak values are cleared before finalizable objects are separated; all pending finalizer targets are then marked and propagated, ephemerons reconverge, weak keys are cleared, and weak values in tables reached through those finalizer targets are cleared. Strings behave as non-clearable values. traverseudata marks both the userdata metatable and every uservalue slot.

KLua's deterministic tracer now implements that four-mode graph and atomic ordering. It reads only the pre-NUL mode prefix, ignores non-string/unknown modes, preserves primitive/string entries, converges arbitrarily chained ephemerons, clears weak entries directly from live Lua tables, retains weak-key associations while a key is pending finalization, and exposes host-userdata uservalue slots as strong child edges. Focused Lua-source coverage proves all four modes, string retention, C-string and invalid modes, dead/live weak entries, ephemeron convergence and release, weak-value versus weak-key visibility inside __gc, second-cycle weak-key cleanup, and uservalue-root retention/release. The complete repository suite passes. The scheduling campaign below completes this continuation.

M20 __gc Scheduling And Close-Order Campaign

Lua 5.5's lgc.c:singlestep advances through pause, propagation, atomic, sweep, and finalizer states; GCScallfin calls at most one finalizer per basic step and returns to pause after the queue drains. lapi.c:lua_gc(LUA_GCSTEP) temporarily clears a user stop while performing explicit work and then restores it. Every operation returns -1 while GCSTPGC/GCSTPCLS blocks collector reentry, but lbaselib.c:luaB_collectgarbage projects that value differently: collect/stop/restart/count/step/isrunning yield one nil, mode switches use pushmode and also yield nil, while the param branch directly returns integer -1; option-specific argument validation occurs before those invalid results. lstate.c:close_state closes the main state's slots before luaC_freeallobjects, whereas a suspended thread freed through luaE_freethread closes open upvalues but does not invoke its pending <close> methods. An explicit lua_closethread still runs those close methods before the now-unreachable values can later reach __gc.

KLua now keeps a logical cycle queue independently of HotSpot. The first slice performs the deterministic atomic reachability/separation pass, later slices run at most one queued finalizer, and the slice that drains the queue reports cycle completion; a full collection discards a partial logical cycle and completes a fresh one. Bytecode table and closure allocations each accrue a fixed 64 logical bytes. Crossing the state-local stepsize threshold, whose default remains 9600, runs one automatic slice; stop pauses that debt policy, restart resets its debt, and explicit step still advances while stopped. Automatic finalizer failures use the same warning adapter as explicit collection and close.

Nested collector calls now preserve the source's one-result shapes, do not mutate running/mode/parameter state, and retain validation-before-availability ordering. Focused coverage proves empty and finalizer-bearing cycle completion, newest-first one-per-slice scheduling, automatic allocation slices and warning routing, stop/restart behavior, explicit stopped stepping, all nested option results including the param == -1 exception, nonrecursive finalizer ordering, invalid step-size validation, explicit coroutine-close-before-finalizer ordering, and the source-compatible absence of suspended <close> calls during whole-state close. The complete repository suite passes.

This remains a logical Lua-object lifecycle rather than byte-accurate memory control. count still observes JVM heap use; automatic debt currently charges VM-created tables and closures rather than every allocation category; the explicit step size is validated but one call always performs one logical slice; and pause, stepmul, generational mode, and HotSpot scheduling cannot reproduce Lua's allocator-byte and generation policy. Those residuals are isolated from reachability, weak-edge clearing, finalizer ordering, reentry, warning, and close behavior.

M20 __gc Debug-Name Campaign

Lua 5.5's lgc.c:GCTM disables hooks, marks the caller CallInfo with CIST_FIN, and invokes the finalizer through a protected non-yieldable call. ldebug.c:funcnamefromcall treats that call-state bit specially: unlike opcode-derived metamethod names, it returns the literal name = "__gc" with namewhat = "metamethod". The same state also gives luaG_callerror the suffix (metamethod '__gc') when the registered finalizer is not callable, while GCTM converts the failure into the ordinary error in __gc warning.

KLua now carries one immutable finalizer call-site record through its protected-call boundary. Explicit collection, logical automatic slices, and whole-state close all use the same non-yieldable, hook-suppressed helper, so debug.getinfo and debug.traceback observe the source-compatible finalizer identity without exposing call/return/line/count hook events. Non-callable finalizers retain that identity in the protected warning diagnostic. Focused coverage proves direct and automatic debug.getinfo, explicit and close-time traceback formatting, close-time hook suppression, and the exact non-callable warning shape; the finalizer-focused and complete repository suites pass.

M20 Named Call-Error Campaign

Lua 5.5's ldebug.c:luaG_callerror first asks funcnamefromcall how the failing value was invoked and formats (<kind> '<name>') when the active Lua opcode or call state supplies one. OP_CALL/OP_TAILCALL reuse getobjname for local, upvalue, global, field, method, and string-constant origins; OP_TFORCALL reports for iterator; metamethod-capable opcodes report the interned name without its __ prefix; and CIST_FIN retains the literal __gc. When neither the call site nor varinfo can name the value, the diagnostic has no suffix. Names pass through C %s formatting, so embedded NUL terminates a constant or field name in the error text.

KLua now projects its existing CallSiteInfo through the failing __call resolution path instead of using that metadata only for debug inspection. The same helper covers ordinary tables, primitives, named host userdata, callable chains, fixed VM metamethod calls, close calls, finalizers, and tail-call sites, while native/API calls without Lua call-site metadata retain the plain diagnostic. Focused coverage proves local/upvalue/global/field/method/constant/iterator/metamethod names, NUL truncation, the unnamed computed-result fallback, host-userdata type names, __close, and __gc; the complete repository suite passes.

Use this work-package order:

Order Work package Outcome and exit criteria Expected commit shape
1 M20 __gc weak-edge and scheduling continuation Closed: weak/ephemeron/uservalue tracing, logical full/step/automatic scheduling, nested-finalizer results, close ordering, residual isolation, and full verification are recorded above. e4e4077c and 6bb176a6.
2 M20 registry field-access completion Closed after the shared-registry re-audit: ordinary field API semantics, luaL_getsubtable metamethod behavior, _CLIBS, conditional per-path LUA_NOENV, Java/Lua coverage, and full verification are recorded above. One coherent API/package/test/documentation commit.
3 M20 __gc debug-name completion Closed: CIST_FIN-equivalent metadata reaches explicit, automatic, and close finalizers; traceback, hook suppression, non-callable failure shape, focused coverage, and full verification are recorded above. One coherent VM/test/documentation commit.
4 M20 named call-error completion Closed: Lua call-site metadata now reaches non-callable diagnostics across ordinary, iterator, metamethod, close, finalizer, host-userdata, C-string, and unnamed fallback cases with full verification. One coherent VM/test/documentation commit.

M20 conformance remains important throughout development, but broad hardening should run as an explicitly selected campaign rather than an open-ended stream of unrelated probes. A campaign should name one subsystem or semantic invariant, list the affected entry points and reference-source functions, define its case matrix, and finish by updating the gap snapshot. Regression, data-integrity, security, or active-milestone blocking fixes may interrupt the order above; incidental edge cases should be queued for the next campaign.

Testing Requirements

Every implemented feature should include the relevant tests:

  • Parser tests where syntax changes.
  • Compiler bytecode or golden tests where lowering changes.
  • VM behavior tests where runtime behavior changes.
  • Error and traceback tests where source locations matter.
  • Debug metadata tests where line, local, upvalue, or breakpoint data changes.
  • Java API tests for klua-api.
  • Kotlin API tests for klua-kotlin.
  • Lua 5.5 conformance tests for language and standard-library behavior.
  • JMH benchmarks only after correctness exists for hot paths.

Minimum CI target after M0:

./gradlew test

Do not mark a milestone complete if it lacks tests for its core behavior.

Definition of Done

A feature is done only when:

  • It is implemented in the correct module.
  • It does not leak internal types into public APIs.
  • It has focused tests at the parser, compiler, VM, API, or integration level as appropriate.
  • Errors include useful source context when the feature can fail at compile time or runtime.
  • The implementation follows the file-size and responsibility rules.
  • Behavior is implemented as Lua 5.5 semantics; deviations and incomplete Lua 5.5 features are documented as conformance gaps.
  • Behavior-sensitive changes have been checked against the relevant official Lua 5.5 source file under ~/Downloads/lua-lua-a5522f0.

Engineering Defaults

  • Prefer correctness and clear semantics before speed.
  • Prefer explicit runtime structures over clever Kotlin abstractions in VM hot paths.
  • Avoid allocation per opcode in the interpreter loop.
  • Avoid Kotlin lambdas inside the VM dispatch loop.
  • Avoid exceptions for normal control flow.
  • Keep debug checks disabled or isolated from fast execution when debugging is off.
  • Use Java-friendly public API shapes: factories, explicit result types, Java functional interfaces, and no Kotlin-only public types in klua-api.
  • Use reflection only at registration time for host bindings; runtime calls should use cached adapters.
  • Use VM-managed coroutine state, not JVM threads.

Maintaining This Document

  • Keep this document aligned with the current committed repository state.
  • Update the status and gap snapshot when milestone reality materially changes.
  • Update the work-package table when a package closes or priority materially changes, not for every commit inside the package.
  • Do not record per-commit history, dates, release notes, or detailed change logs here.
  • Keep detailed roadmap content in docs/KLua_Implementation_Milestones.md.
  • Keep user-facing project status in README.md.

Assumptions

  • The repository already contains working compiler, VM, API, Kotlin helper, userdata, and test slices.
  • This document is the operational execution brief and must stay aligned with the current repo state.
  • "No backward capability" means no legacy project API preservation and no old Lua-version compatibility profile preservation.
  • Lua 5.5 is the only runtime target.
  • Interpreter-first architecture remains the required path before any JVM bytecode compiler.
  • Clean module structure is more important than minimizing module count.
  • Performance work starts after correctness and benchmark baselines exist.