geometry-tower-defense/AGENTS.md

6.2 KiB

Project Philosophy

This project prioritizes clarity, correctness, and maintainability over defensive boilerplate or unnecessary abstraction.

AI-generated code should:

  • follow existing architecture
  • keep logic simple and explicit
  • avoid hiding bugs
  • avoid speculative abstractions

If uncertain, prefer simple direct code over generalized frameworks.

Repository Guidelines

Project Structure & Module Organization

  • Assets/ contains all Unity assets and code. Game-specific content lives under Assets/GameMain/.
  • Assets/GameMain/Scripts/ holds gameplay code organized by domain (Procedure/, Entity/, UI/, Scene/, Sound/, Utility/).
  • Assets/GameMain/Scenes/ stores Unity scenes (start from Assets/Launcher.unity).
  • Assets/GameMain/Configs/ and Assets/GameMain/DataTables/ store runtime configuration and data tables.
  • StreamingAssets/ is for runtime-loaded files that must be preserved on build.
  • docs/ contains design notes (see docs/GameDesign.md).
  • 数据表/ is a top-level data table workspace; keep it in sync with Assets/GameMain/DataTables/ when exporting.

Build, Test, and Development Commands

  • Open the project with Unity Hub and load GeometryTD as a Unity project.
  • Play locally via the Unity Editor Play button (no custom CLI runner is defined in this repo).
  • Build via Unity: File > Build Settings... then choose target and build.
  • IDE support: open GeometryTD.sln or Assembly-CSharp.csproj for C# navigation and tooling.

Coding Style & Naming Conventions

  • C# uses 4-space indentation and Allman braces (see Assets/GameMain/Scripts/Procedure/ProcedureLaunch.cs).
  • Types, methods, and public members use PascalCase; locals and parameters use camelCase.
  • Namespaces follow GeometryTD.* by feature area (example: GeometryTD.Procedure).
  • Keep Unity .meta files with their assets; do not delete or regenerate them manually.

Code Design Principles

1. Prefer explicit logic over abstraction

Do not introduce abstractions unless they are clearly justified.

Avoid creating layers such as:

Manager
Service
Provider
Repository
Coordinator
Bridge

unless the architecture explicitly requires them.

Simple gameplay logic should remain simple.

Bad example:

EnemyService -> EnemyManager -> EnemyRepository

Good example:

EnemySpawner
Enemy
EnemyAI

2. Do not create abstractions for a single implementation

Avoid introducing interfaces unless multiple implementations are expected.

Bad:

IEnemyService
EnemyService

Good:

EnemySystem

Interfaces are only appropriate when:

  • multiple implementations are required
  • plugin/mod systems are involved
  • testing requires mocking
  • architecture explicitly defines extension points

3. Avoid speculative generalization

Do not design systems for hypothetical future use.

Implement only what the current feature requires.

Avoid:

  • generic service layers
  • premature plugin systems
  • unnecessary configuration frameworks

Defensive Programming Policy

Boundary validation only

Validation is appropriate only at true system boundaries, such as:

  • user input
  • network input
  • file/config loading
  • inspector/external data
  • public API boundaries

Internal gameplay logic should assume that invariants are already satisfied.

Do not add redundant validation inside internal code.


Do not hide errors

Do not silently ignore invalid states.

Avoid code such as:

if (enemy == null)
    return;

or

if (config == null)
    continue;

unless the behavior is intentionally designed.

Unexpected states should be visible, not hidden.


Prefer fail-fast over silent failure

If a condition should never happen in correct execution:

Use assertions.

Example:

Debug.Assert(config != null);

Do not convert invariant violations into no-op behavior.

Bad:

if (config == null)
    return;

Good:

Debug.Assert(config != null);

Do not weaken required dependencies

Required references must remain required.

Do not turn required dependencies into optional ones by adding defensive checks.

Bad:

if (enemy.Config == null)
    return;

If a dependency is required by design, assume it exists.


Error Handling

Do not introduce try/catch blocks unless there is a clear recovery strategy.

Avoid:

try
{
    DoSomething();
}
catch (Exception e)
{
    Debug.LogError(e);
}

Exceptions should generally propagate during development so bugs are visible.


Early Return Policy

Early returns are acceptable for normal control flow simplification.

However, do not use early returns to hide invariant violations or missing required state.

Bad:

if (enemy == null)
    return;

Good:

if (!enemy.IsAlive)
    return;

Logging

Avoid excessive logging.

Do not log inside per-frame loops unless necessary.

Avoid logs like:

Debug.Log("Updating enemy");

Use logging only when it provides meaningful debugging value.


Utility Classes

Avoid creating generic utility classes such as:

GameUtils
CommonHelper
MathHelper

Prefer placing behavior in the domain object responsible for it.

Example:

Bad:

DamageHelper.CalculateDamage(...)

Good:

enemy.CalculateDamage(...)

Data Structures

Avoid large context objects with many loosely related fields.

Bad:

EffectContext
{
    int value;
    int count;
    float rate;
    object source;
    object target;
}

Prefer explicit parameters or strongly typed structures.


Code Generation Rules for AI

Before adding any of the following:

  • null check
  • range check
  • fallback default
  • try/catch
  • abstraction layer

First determine:

  1. Is this a boundary input?
  2. Is the value optional by design?
  3. Is there a real recovery strategy?

If the answer is no, do not add defensive code.


Preferred Coding Style

Prefer:

  • simple classes
  • explicit logic
  • minimal indirection
  • clear ownership of behavior

Avoid:

  • unnecessary patterns
  • speculative architecture
  • defensive boilerplate

Guiding Principle

Prefer:

clear failure over hidden corruption

A bug should surface near its source rather than being silently ignored.