Skip to content

anywherepilot/squirrel_transpiler

Repository files navigation

SquirrelCompiler — Brunel

A toolchain for writing an OpenTTD AI in C# and transpiling it to Squirrel, the language OpenTTD runs AIs in. You author the AI ("Brunel"; AJ in the code) with full IDE support, static typing, and unit tests; the build emits .nut files that drop into OpenTTD's ai/ folder.

Every file in AJ is dual-target: it compiles and runs as ordinary C# (against the NoAi/Squirrel stand-in libraries, with mocks for tests) and transpiles to valid Squirrel. The C# side exists for tooling, type-checking, and tests; the Squirrel side is what ships.

See CLAUDE.md for architecture and conventions, and PLAN.md for the modernization roadmap.

Build and test

dotnet build SquirrelCompiler.slnx
dotnet test

(Requires the .NET 10 SDK.)

Uptaking a new OpenTTD NoAI API version

OpenTTD's script API drifts between releases — classes, methods, enums, and constants come and go. The NoAi stubs are generated from OpenTTD's C++ headers by the NoAiConverter tool, so retargeting to a newer API version means regenerating and reconciling.

  1. Get the OpenTTD source for the target version. Check out the release tag into a local working copy, e.g. git checkout 15.3 under D:\Source\OpenTTD. The script API headers are in src/script/api/script_*.hpp.

  2. Regenerate the stubs into the committed Generated/ folder:

    dotnet run --project NoAiConverter -- --headers <OpenTTD>\src\script\api --out NoAi\Stubs\Generated
    

    The generator owns that folder and rewrites it wholesale, so a class dropped upstream leaves no stale file. It writes one partial C# stub per AI-exposed class (events to Generated\Events\): it resolves the DOXYGEN_API view of each header, filters by the @api doc tag (only ai / ai game surface), and renders summaries, parameters, returns, remarks, and folded @code examples into C# XML docs. The hand-written layer lives in the sibling NoAi\Stubs\Manual\, which the generator never touches.

  3. Build NoAi. This is the compile-check — the generated stubs are committed and compiled directly. Blockers surface as build errors: a missing NoAi.Types type, a list with no element-type mapping, a std::/Squirrel-builtin type the generator doesn't handle yet. Fix them in the layers in step 5, then rebuild.

  4. Read what moved. git diff -- NoAi/Stubs/Generated shows new/removed classes, changed signatures, new enums, renamed members. Read the version's script-API changelog (src/script/api/*_changelog.hpp) and the release's game-change notes alongside the diff — both inform later AI design, so don't skip the reading.

  5. Reconcile what the generator can't decide:

    • NoAi/Types — add a readonly record struct for each new opaque ID (int- or long-backed, explicit (int) conversions). A type the generator emits but NoAi.Types lacks shows up as a "type not found" build error — that's the signal to add it.
    • NoAiConverter/ListElementTypes — map each new AIList-derived class to its element type so the generator emits the typed base (AITileList : AIList<TileIndex>); the headers don't carry it. A list left out of the map stays a bare AIList.
    • NoAiConverter mappersTypeMapper for new C++ types (the std::, integer, and Squirrel-builtin families), ConstantValue for new sentinel constants (real values, each cited to its core header).
    • NoAi/Stubs/Manual — the partial companions the headers can't express: AIMode and the : AIMode partials for the mode classes; the AIList enumerator plumbing and the generic AIList<TItem>. AIMap/AIGameSettings are hand-written here (mock delegation) with their generated copies excluded in NoAi.csproj; HandWrittenStubSyncTest fails if their method surface drifts from the generated reference — reconcile them and their mocks when it does.
    • CSharpToSquirrelConverter — update any transpiler special-cases the API moved out from under.
  6. Build AJ and fix the fallout. Renamed/removed/re-typed API surfaces as compile errors. Change AJ freely — keeping its current behavior is not a goal.

  7. Bump info.cs — set GetAPIVersion() to the new version string (e.g. "15"), before re-baselining so the golden captures it.

  8. Re-baseline the golden fixtures. The golden test is [Explicit] — it pins the whole transpiled-AJ output, so it would break on every deliberate AJ change and is therefore kept out of the everyday run, used only here. Regenerate the fixtures (delete UnitTestSuite.AJ\Golden\AJ first if AJ files were added or removed — the transpiler doesn't prune):

    dotnet run --project SquirrelCompiler -- --source AJ --out UnitTestSuite.AJ\Golden\AJ
    

    then review the git diff of Golden/AJ. It is a reviewed re-baseline, never silent. (To see the failure as NUnit's diff instead, run the test by name: dotnet test --filter FullyQualifiedName~Transpiler_ReproducesGoldenOutput.)

  9. Load-test in OpenTTD. Drop the emitted .nut set into OpenTTD's ai/ folder and confirm the AI loads. Squirrel compiles function bodies lazily, so loading only really exercises info.nut/main.nut parsing and RegisterAI — a bad body won't error until that code path runs.

If you're jumping several releases at once, step through them one version at a time — the point is to read each version's API and game changes, not just to reach the latest headers.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors