GenvidTechnologies/c3source · wiki
Bundle OKF 0.2 · 15 conceitos · GenvidTechnologies/c3source
Open source Repository Open in the app JSON README (API)
About
<!-- `okf_version` is the ONLY frontmatter key permitted here (§8/§12) — this
file scaffolds the bundle-root index (`wiki/index.md`, the OKF
bundle root per ADR-0022). A `wiki/<subdir>/index.md` carries NO
frontmatter at all. -->
# Wiki Index
This is the wiki's table of contents — every page under `wiki/`,
grouped under section headings, one line each. `/gvt-dev:maintain-wiki`
keeps this list current: a new page is added here when it's created, and
`lint` flags any page listed in **no** index — here, or in a
subdirectory's own `index.md`. Each entry's description is the linked
page's frontmatter `description`, so the index and the page can't drift.
See `docs/wiki-schema.md` for the page format and maintenance rules.
## Library
* [Library Overview](library-overview.md) - c3source is a TypeScript library of typed interfaces and traversal/formatting functions for Construct 3 project source files on disk, consumed by build tools, code generators, and analyzers outside the C3 editor.
*
Details
- Kind
- OKF bundles
- Topic
- Productivity
- Publisher
- genvidtechnologies
- Origin
- okf_github
- Category
- dados
- Version
- 0.2
- Last push
- 2026-08-20T23:33:50Z
- Repository state
- ativo
- Language
- TypeScript
- License
- MIT-0
- Added
- 2026-09-09 12:02:17
- Updated
- 2026-09-09 12:02:17
- Origin id
GenvidTechnologies/c3source:wiki/index.md
README
# c3source
Utilities for reading and traversing Construct 3 project source files: layouts, layers, instances, and event sheets.
## Purpose
`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.
## Compatibility & caveats
> [!IMPORTANT]
> - **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).
> - **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.
> - **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.
## Exported Types
### Layout types
| Type | Description |
|------|-------------|
| `Layout` | A C3 layout file (`name`, `layers`, optional `nonworld-instances`) |
| `Layer` | A layer within a layout (`name`, optional `subLayers`, `instances`, `global`) |
| `Instance` | An object instance (`type`, `uid`, `properties`, optional `instanceVariables`, `effects`) |
| `ObjectType` | An object type definition (`name`, `plugin-id`) |
### Event sheet types
| Type | Description |
|------|-------------|
| `EventSheet` | Root event sheet object (`name`, `events`, `sid`) |
| `EventSheetEvent` | Union of all event types |
| `BlockEvent` | Standard condition/action block |
| `FunctionBlockEvent` | Named function block |
| `CustomAceBlockEvent` | Custom ACE (action/condition/expression) block |
| `GroupEvent` | Named group with children |
| `IncludeEvent` | Include directive referencing another sheet |
| `CommentEvent` | Inline comment |
| `EventSheetVariable` | Sheet-level variable declaration |
| `Condition` | A single condition within a block |
| `ScriptAction` | A TypeScript script action |
| `FunctionParameter` | A parameter on a function-block |
| `ExtractedScript` | A script block extracted by `extractScriptsFromSheet`, with coordinates and scope info |
| `ScopeSegment` | One scope level contributing variables (for typed `localVars` composition) |
## Exported Functions
### File discovery
```ts
find_all_layouts_path(layoutDir: string): string[]
find_all_eventsheets_path(eventSheetsDir: string): string[]
find_all_objectTypes_path(objectTypesDir: string): string[]
```
Recursively collect `.json` files (excluding `.uistate.json`) from a directory tree.
> [!NOTE]
> **Behavior change in 2.0.0.** Before 2.0.0, `find_all_layouts_path` and
> `find_all_objectTypes_path` returned every non-editor-local file regardless
> of extension — the sentence above was already true only of
> `find_all_eventsheets_path`. Both now narrow to `.json` section items like
> their sibling always did, so all three functions match what this section has
> always documented. See [ADR
> 0025](wiki/decisions/0025-section-item-hood-and-stray-files.md).
### Layout traversal
```ts
// Visitor returns the number of mutations made; layout is written back if > 0.
type LayerVisitor = (layer: Layer, fullLayerName: string) => number;
type InstanceVisitor = (instance: Instance, index: number, layer: Layer, fullLayerName: string) => boolean;
visit_layers_in_layouts(layoutsPath: string, visitor: LayerVisitor): number
visit_instances_in_layouts(layoutsPath: string, visitor: InstanceVisitor): number
get_all_global_layers(layoutsPath: string): Set<string>
```
Walk every layer (or instance) across all layouts in a directory. Mutating visitors should return a nonzero/truthy value — the file is written back automatically.
### Event sheet utilities
```ts
extractScriptsFromSheet(sheet: EventSheet): ExtractedScript[]
generateFunctionName(sheetName: string, eventIndex: number, actionIndex: number): string
formatCondition(cond: Condition): string
formatAction(action: ScriptAction | Record<string, unknown>, sheetName: string, eventIndex: number, actionIndex: number): string
normalizeLineEndings(text: string): string
```
## Usage Examples
### List all layout files
```ts
import { find_all_layouts_path } from "@genvidtech/c3source";
const paths = find_all_layouts_path("./layouts");
// ["./layouts/MainMenu.json", "./layouts/Battle/Battle.json", ...]
```
### Walk every instance across all layouts
```ts
import { visit_instances_in_layouts } from "@genvidtech/c3source";
const changed = visit_instances_in_layouts("./layouts", (instance, index, layer, fullLayerName) => {
if (instance.type === "Sprite" && instance.properties.text === "TODO") {
instance.properties.text = "";
return true; // mark as changed — layout will be written back
}
return false;
});
console.log(`Updated ${changed} instances`);
```
### Extract script blocks from an event sheet
```ts
import { readFileSync } from "node:fs";
import { type EventSheet, extractScriptsFromSheet } from "@genvidtech/c3source";
const sheet: EventSheet = JSON.parse(readFileSync("./eventSheets/GamePlay.json", "utf-8"));
const scripts = extractScriptsFromSheet(sheet);
for (const s of scripts) {
console.log(`${s.sheetName} event ${s.eventIndex} action ${s.actionIndex}: ${s.humanPath}`);
console.log(s.lines.join("\n"));
}
```
### Format a condition for display
```ts
import { formatCondition } from "@genvidtech/c3source";
const label = formatCondition({ id: "on-start-of-layout", objectClass: "System", sid: 1 });
// "System.on-start-of-layout()"
```
## Development
### Running the SDK-gated tests locally
Most tests run against synthetic fixtures under `test/fixtures/` and need no extra setup. The
`.c3addon` reader/parser tests (`test/addonReader.test.ts`, `test/addonAcesModel.test.ts`) have a
supplementary tier that reads real, BOM'd samples from the **Scirra Construct Addon SDK**, vendored
as the `SDK/` git submodule. That tier **self-skips** when the submodule is absent, so a plain clone
still passes — but to exercise it, initialize the submodule:
```sh
git clone --recursive git@github.com:GenvidTechnologies/c3source.git # fresh clone
# — or, in an existing clone —
git submodule update --init --recursive
```
With the submodule present, those two files report **25 passing / 0 pending** (absent: 19 passing /
6 pending). CI checks them out recursively (via the shared `node-gate` workflow's `submodules`
input), so the SDK-gated tier runs there unconditionally.
## Further reading
This project's documentation lives in an LLM-wiki under [`wiki/`](wiki/index.md)
— start at [`wiki/index.md`](wiki/index.md), which lists every page with a
one-line description. For usage reference covering SID traversal and
editor-local classification see [`wiki/layout-traversal.md`](wiki/layout-traversal.md);
for project manifest parsing and drift detection see
[`wiki/project-manifest.md`](wiki/project-manifest.md). The architecture
decisions behind the API are recorded in
[`wiki/decisions/`](wiki/decisions/index.md).
(These are repository-relative links: they resolve on GitHub, not from inside
the npm tarball, whose `files` allowlist ships only `dist`, `LICENSE` and this
README.)
## Notes
- Layer visitor full names use the format `LayoutName.LayerName`; global layers use `global.LayerName`.
- `extractScriptsFromSheet` counts events depth-first to match C3's internal event numbering.
- All file writes use tab indentation and no trailing newline, matching C3's serialization format (`serializeC3Json`/`writeC3JsonFile`).
- Line endings in expressions and comments are normalized to LF by `normalizeLineEndings`.