{
  "markdown": "# c3source\n\nUtilities for reading and traversing Construct 3 project source files: layouts, layers, instances, and event sheets.\n\n## Purpose\n\n`c3source` provides typed interfaces and traversal functions for working with C3 JSON source files on disk. It is used by build tools, code generators, and analyzers that need to inspect or mutate project files outside the C3 editor.\n\n## Compatibility & caveats\n\n> [!IMPORTANT]\n> - **Folder-based projects only.** This library reads and writes the JSON files of a C3 project saved as a **folder** (the \"Save as project folder\" layout, with separate `layouts/`, `eventSheets/`, `objectTypes/` files). It does **not** handle the single-file `.c3p`/`.c3proj` archive export. The folder project's `project.c3proj` **manifest** (a JSON file in the project root, distinct from the archive) is modeled by `C3ProjectManifest`, parsed strictly by `parseProjectManifest`/`readProjectManifest`, drift-checked by `detectManifestDrift`, and can now be written back in canonical form via `serializeProjectManifest`/`writeProjectManifest`. A tolerant opt-in — `parseProjectManifestTolerant`/`readProjectManifestTolerant`, paired with the never-throwing `validateProjectManifest` — reads a manifest that fails the strict shape check without losing the document, for repair workflows; see [wiki/project-manifest.md](wiki/project-manifest.md).\n> - **Pinned to a specific C3 version.** The types and traversal logic were derived from Construct 3 **r487** (`savedWithRelease: 48700`, `projectFormatVersion: 1`) and are validated against the canonical `construct3-sample` reference fixture, now at **r495** (`savedWithRelease: 49500`, `projectFormatVersion: 1`; materialized to `test/fixtures/canonical/` from the `construct3-sample` submodule). Other releases may serialize differently.\n> - **Built on undocumented internals.** Construct 3's on-disk format is **not a documented or stable public interface**. These interfaces were reverse-engineered from project output, so a future C3 release can change the shape without notice and **break this library**. Pin your C3 version, and re-validate the fixtures against any new C3 release before upgrading.\n\n## Exported Types\n\n### Layout types\n\n| Type | Description |\n|------|-------------|\n| `Layout` | A C3 layout file (`name`, `layers`, optional `nonworld-instances`) |\n| `Layer` | A layer within a layout (`name`, optional `subLayers`, `instances`, `global`) |\n| `Instance` | An object instance (`type`, `uid`, `properties`, optional `instanceVariables`, `effects`) |\n| `ObjectType` | An object type definition (`name`, `plugin-id`) |\n\n### Event sheet types\n\n| Type | Description |\n|------|-------------|\n| `EventSheet` | Root event sheet object (`name`, `events`, `sid`) |\n| `EventSheetEvent` | Union of all event types |\n| `BlockEvent` | Standard condition/action block |\n| `FunctionBlockEvent` | Named function block |\n| `CustomAceBlockEvent` | Custom ACE (action/condition/expression) block |\n| `GroupEvent` | Named group with children |\n| `IncludeEvent` | Include directive referencing another sheet |\n| `CommentEvent` | Inline comment |\n| `EventSheetVariable` | Sheet-level variable declaration |\n| `Condition` | A single condition within a block |\n| `ScriptAction` | A TypeScript script action |\n| `FunctionParameter` | A parameter on a function-block |\n| `ExtractedScript` | A script block extracted by `extractScriptsFromSheet`, with coordinates and scope info |\n| `ScopeSegment` | One scope level contributing variables (for typed `localVars` composition) |\n\n## Exported Functions\n\n### File discovery\n\n```ts\nfind_all_layouts_path(layoutDir: string): string[]\nfind_all_eventsheets_path(eventSheetsDir: string): string[]\nfind_all_objectTypes_path(objectTypesDir: string): string[]\n```\n\nRecursively collect `.json` files (excluding `.uistate.json`) from a directory tree.\n\n> [!NOTE]\n> **Behavior change in 2.0.0.** Before 2.0.0, `find_all_layouts_path` and\n> `find_all_objectTypes_path` returned every non-editor-local file regardless\n> of extension — the sentence above was already true only of\n> `find_all_eventsheets_path`. Both now narrow to `.json` section items like\n> their sibling always did, so all three functions match what this section has\n> always documented. See [ADR\n> 0025](wiki/decisions/0025-section-item-hood-and-stray-files.md).\n\n### Layout traversal\n\n```ts\n// Visitor returns the number of mutations made; layout is written back if > 0.\ntype LayerVisitor = (layer: Layer, fullLayerName: string) => number;\ntype InstanceVisitor = (instance: Instance, index: number, layer: Layer, fullLayerName: string) => boolean;\n\nvisit_layers_in_layouts(layoutsPath: string, visitor: LayerVisitor): number\nvisit_instances_in_layouts(layoutsPath: string, visitor: InstanceVisitor): number\nget_all_global_layers(layoutsPath: string): Set<string>\n```\n\nWalk every layer (or instance) across all layouts in a directory. Mutating visitors should return a nonzero/truthy value — the file is written back automatically.\n\n### Event sheet utilities\n\n```ts\nextractScriptsFromSheet(sheet: EventSheet): ExtractedScript[]\ngenerateFunctionName(sheetName: string, eventIndex: number, actionIndex: number): string\nformatCondition(cond: Condition): string\nformatAction(action: ScriptAction | Record<string, unknown>, sheetName: string, eventIndex: number, actionIndex: number): string\nnormalizeLineEndings(text: string): string\n```\n\n## Usage Examples\n\n### List all layout files\n\n```ts\nimport { find_all_layouts_path } from \"@genvidtech/c3source\";\n\nconst paths = find_all_layouts_path(\"./layouts\");\n// [\"./layouts/MainMenu.json\", \"./layouts/Battle/Battle.json\", ...]\n```\n\n### Walk every instance across all layouts\n\n```ts\nimport { visit_instances_in_layouts } from \"@genvidtech/c3source\";\n\nconst changed = visit_instances_in_layouts(\"./layouts\", (instance, index, layer, fullLayerName) => {\n  if (instance.type === \"Sprite\" && instance.properties.text === \"TODO\") {\n    instance.properties.text = \"\";\n    return true; // mark as changed — layout will be written back\n  }\n  return false;\n});\nconsole.log(`Updated ${changed} instances`);\n```\n\n### Extract script blocks from an event sheet\n\n```ts\nimport { readFileSync } from \"node:fs\";\nimport { type EventSheet, extractScriptsFromSheet } from \"@genvidtech/c3source\";\n\nconst sheet: EventSheet = JSON.parse(readFileSync(\"./eventSheets/GamePlay.json\", \"utf-8\"));\nconst scripts = extractScriptsFromSheet(sheet);\n\nfor (const s of scripts) {\n  console.log(`${s.sheetName} event ${s.eventIndex} action ${s.actionIndex}: ${s.humanPath}`);\n  console.log(s.lines.join(\"\\n\"));\n}\n```\n\n### Format a condition for display\n\n```ts\nimport { formatCondition } from \"@genvidtech/c3source\";\n\nconst label = formatCondition({ id: \"on-start-of-layout\", objectClass: \"System\", sid: 1 });\n// \"System.on-start-of-layout()\"\n```\n\n## Development\n\n### Running the SDK-gated tests locally\n\nMost tests run against synthetic fixtures under `test/fixtures/` and need no extra setup. The\n`.c3addon` reader/parser tests (`test/addonReader.test.ts`, `test/addonAcesModel.test.ts`) have a\nsupplementary tier that reads real, BOM'd samples from the **Scirra Construct Addon SDK**, vendored\nas the `SDK/` git submodule. That tier **self-skips** when the submodule is absent, so a plain clone\nstill passes — but to exercise it, initialize the submodule:\n\n```sh\ngit clone --recursive git@github.com:GenvidTechnologies/c3source.git   # fresh clone\n# — or, in an existing clone —\ngit submodule update --init --recursive\n```\n\nWith the submodule present, those two files report **25 passing / 0 pending** (absent: 19 passing /\n6 pending). CI checks them out recursively (via the shared `node-gate` workflow's `submodules`\ninput), so the SDK-gated tier runs there unconditionally.\n\n## Further reading\n\nThis project's documentation lives in an LLM-wiki under [`wiki/`](wiki/index.md)\n— start at [`wiki/index.md`](wiki/index.md), which lists every page with a\none-line description. For usage reference covering SID traversal and\neditor-local classification see [`wiki/layout-traversal.md`](wiki/layout-traversal.md);\nfor project manifest parsing and drift detection see\n[`wiki/project-manifest.md`](wiki/project-manifest.md). The architecture\ndecisions behind the API are recorded in\n[`wiki/decisions/`](wiki/decisions/index.md).\n\n(These are repository-relative links: they resolve on GitHub, not from inside\nthe npm tarball, whose `files` allowlist ships only `dist`, `LICENSE` and this\nREADME.)\n\n## Notes\n\n- Layer visitor full names use the format `LayoutName.LayerName`; global layers use `global.LayerName`.\n- `extractScriptsFromSheet` counts events depth-first to match C3's internal event numbering.\n- All file writes use tab indentation and no trailing newline, matching C3's serialization format (`serializeC3Json`/`writeC3JsonFile`).\n- Line endings in expressions and comments are normalized to LF by `normalizeLineEndings`.\n",
  "bytes": 8899,
  "sha": "4276fba1d4a1bcfa5acfd63b7df1acb046d9fe101626df24a925d132ac6e1688",
  "repo_slug": "genvidtechnologies/c3source",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_genvidtechnologies_c3source_wiki_index_m_4a22ed86/readme"
}