S1MAPI is a mesh and building construction library for Schedule 1 mods.
The core principle: no compile-time dependencies on ScheduleOne types to remain resilient across game updates.
S1MAPI uses Unity primitives and FishNet only—no Assembly-CSharp imports. Where game-type
interaction is unavoidable, use reflection with graceful fallbacks.
- Review the codebase thoroughly before submitting a PR.
- Keep S1MAPI free of ScheduleOne type dependencies.
- S1MAPI handles meshes and buildings; S1API handles game component integration.
- Mods typically use both: S1MAPI for construction, S1API for game logic.
- All classes must exist in a logical namespace matching the folder structure.
- Core initialization and resource management in
S1MAPI.Core. - Mesh generation in
S1MAPI.ProceduralMesh. - Building construction in
S1MAPI.Building. - GLTF loading in
S1MAPI.Gltf. - Utilities in
S1MAPI.Utils.
namespace S1MAPI.ProceduralMesh { ... }
namespace S1MAPI.Building { ... }- In general, naming follows standard C# conventions and Jetbrains Rider suggestions.
- PascalCase for class names, methods, properties, and non-private fields.
- camelCase for local variables and parameters.
- Prefix private and internal fields with
_.
private readonly List<Vector3> _vertices;
private Material? _material;
internal GameObject _rootObject;- Static readonly fields use PascalCase.
private static readonly HashSet<UnityEngine.Object> _trackedResources;
public static readonly string DefaultMeshName = "ProceduralMesh";- Use nested static classes for constants instead of flat constant lists.
public static class Constants
{
public static class Mesh
{
public const int MaxVerticesPerMesh = 65535;
public const int DefaultCylinderSegments = 12;
}
}- Enums do not need to be prefixed with
E. - Prefer descriptive names without redundant suffixes:
MeshBuildernotMeshBuilderManagerBuildingUtilitiesnotBuildingUtilitySystem
- Public API classes, methods, and properties should use
publicwith XML documentation. - Internal implementation details must be marked as
internal. - Use
sealedon classes when inheritance is not intended.
public sealed class ProceduralMeshBuilder { ... }- Arrow functions (
=>) are placed below the declaration, indented once.
public static int TrackedResourceCount =>
_trackedResources.Count;
public GameObject GetRoot() =>
_root;- Use
readonlyorconstfor immutable values. - Nullable variables should be declared using
?.
private Material? _material;
public Material? DefaultMaterial { get; set; }S1MAPI uses fluent builders as its primary API style. Builders should:
- Return
thisfrom configuration methods for chaining. - Place configuration methods before terminal
Build()methods. - Provide sensible defaults so minimal configuration is required.
GameObject building = new BuildingBuilder("MyShop")
.SetFootprint(6f, 9f)
.SetHeight(6f)
.AddFloor()
.AddWalls()
.AddRoof()
.Build(position, rotation);Unity automatically handles resource cleanup when GameObjects are destroyed. Use S1MAPI.Utils.DebugLog for logging:
DebugLog.Info($"Built mesh: {_name} ({_vertices.Count} vertices)");
DebugLog.Warning("Attempted to register null resource");
DebugLog.Error($"Failed to load GLTF: {path}");S1MAPI uses FishNet for networked prefabs (e.g., doors that sync across clients).
- NetworkBehaviour components should follow FishNet conventions.
- Prefabs intended for network spawning must be registered appropriately.
- Keep networking logic separate from mesh generation logic.
- All public API declarations must have XML documentation summaries.
/// <summary>
/// Add a box (cube) to the mesh.
/// </summary>
/// <param name="center">Center position of the box</param>
/// <param name="size">Size of the box</param>
/// <returns>This builder for method chaining</returns>
public ProceduralMeshBuilder AddBox(Vector3 center, Vector3 size) { ... }- Internal members should have brief documentation explaining their purpose.
- Use
#if IL2CPPand#if MONOfor platform-specific logic when necessary.
#if IL2CPP
using Il2CppUnityEngine;
#elif MONO
using UnityEngine;
#endif- Use
#regionand#endregionto organize code sections.
#region Fields
private readonly string _name;
private readonly List<Vector3> _vertices;
#endregion
#region Public API
public ProceduralMeshBuilder AddBox(...) { ... }
#endregion-
Organize class members in this order:
- Fields (private, internal, public)
- Constructors
- Properties
- Public API methods
- Private/internal helper methods
- Event handlers
-
Group related members together using regions.
- Do not add compile-time references to ScheduleOne types (
Assembly-CSharp.dll). S1MAPI must remain update-resilient—nousing ScheduleOne.*imports or direct type usage. When interaction with game types is unavoidable (e.g. NPC navigation), use reflection with graceful fallbacks so the code degrades safely if the game changes. - Do not use magic strings—prefer enums or constants.
- Do not ignore compiler warnings.
- Do not leave commented-out code in commits.
- Do not use
varwhen the type is not immediately obvious.
// Bad
var x = GetSomething();
// Good
MeshData x = GetSomething();
// Also good (type is obvious)
var builder = new ProceduralMeshBuilder("Test");- Follow Conventional Commits format with module scope.
feat(ProceduralMesh): add cylinder mesh generator
fix(Building): null reference in BuildingBuilder.Build
docs(Core): update core documentation
refactor(Gltf): extract node processing logic