Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions eech-core-ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ TypeScript, compiled to **Lua 5.1** with

The original C in `../aphavoc` and `../modules` is the behavioural authority. The
campaign core runs without DCS World. The environment (physical positions,
replication, and later time, randomness and terrain) reaches it only through
narrow ports in `src/ports`.
replication, the frame clock, and later randomness and terrain) reaches it only
through narrow ports in `src/ports`.

- `docs/architecture.md`: the bootstrap report (kernel boundary, ports, harness,
coverage, Lua semantics, next slices).
- `docs/port-manifest.md`: the authoritative C→TS mapping and status.
- `docs/slices/assess-group-supplies.md`: slice 1, frozen, with its dependency
trace and behaviour matrix.
- `docs/slices/group-update-timing.md`: slice 2, frozen, with its investigation,
boundary and behaviour matrix.

## Requirements

Expand Down Expand Up @@ -42,16 +44,16 @@ npm run verify # every gate below, in order

CI runs the same gates step by step in `.github/workflows/eech-core-ts.yml` for pull requests and pushes to `master` that touch `eech-core-ts/`, `aphavoc/` or `modules/`.

`npm run cref:record` re-records `test/scenarios/generated/c-reference-random.cases.ts`
`npm run cref:record` re-records the fixtures in `test/scenarios/generated/`
from the executed C. `npm run gen:c` regenerates `src/generated` from the C sources.

## Layout

```
src/
core/ ASSERT, C float semantics (toFloat32), maths from modules/maths
core/ ASSERT, C float semantics (toFloat32), maths, time (get_delta_time), configuration
entity/system/ entity runtime: lists (with shared links), value function tables, messages, comms model
entity/special/ session, force, keysite, group, guide: the ported overloads and campaign functions
entity/special/ session, force, keysite, group, guide, update: the ported overloads, campaign functions and the update loop
entity/mobile/ campaign-visible surface of aircraft and vehicles (position comes from a port)
generated/ enums, database columns and constants generated from the EECH C (never hand-edited)
ports/ what the campaign needs from the environment
Expand All @@ -61,7 +63,7 @@ test/
unit/ vitest suites (JavaScript semantics, coverage)
lua/ Lua 5.1 conformance runner and bundle smoke test
c-reference/ C-vs-TS differential tests
c-reference/ extractor, shim and harness that execute the original C
c-reference/ extractor, harness environment and harness that execute the original C
```

## Porting rules (short form)
Expand Down
49 changes: 28 additions & 21 deletions eech-core-ts/c-reference/README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
# C reference harness

This directory runs the **original** EECH functions against the same scenarios
as the TypeScript port.
This directory runs the **original** EECH C against the same scenarios as the
TypeScript port.

- `extract.mjs` copies the enums, structs, macros and functions listed in
`EXTRACTS` verbatim from `../aphavoc` and `../modules` into
`build/c-reference/eech_extracted.c`. Each block carries a `#line` directive to
its origin. The extractor fails if an original definition cannot be found.
- `eech_shim_prelude.h` and `eech_shim_types.h` supply the environment those
functions expect: `ASSERT` with debug-build meaning, entity records, lists
(including the shared `group_link`), and the accessor values of the scenario's
entities. The shim contains no campaign algorithm.
- `harness.c` reads a scenario on stdin (written by
`test/scenarios/campaign-scenario.ts :: serialiseScenario`), runs one operation
and prints the outcome. Floats are printed as bit patterns.
- `build.mjs` extracts and compiles with `-Wall -Werror`, and fails when no C
compiler is available.
- `extract.mjs` generates two files into `build/c-reference/`:
- `project.h`: `eech_harness_env.h`, verbatim fragments and **whole original
headers**, in the order listed in `PROJECT_H`;
- `eech_extracted.c`: verbatim functions from files too large to compile whole
(`EXTRACTED_C`).

The comparison covers the extracted functions. The accessor overloads
(`gp_int.c`, `ks_int.c`, ...) are shim-provided and are verified by source
reading. See `docs/architecture.md`, question 6.
Each fragment carries a `#line` directive to its origin. Extraction fails if
an original definition cannot be found.
- `REAL_TRANSLATION_UNITS` (in `extract.mjs`) lists the original files compiled
**unchanged** against that `project.h`, for example `gp_int.c`, `gp_float.c`
and `gp_updt.c`.
- `eech_harness_env.h` is the environment: the C library, the Windows SDK
`min`/`max`, `ASSERT` with debug-build meaning, debug output declarations, and
opaque engine types. `eech_harness_decls.h` declares the harness's own
globals and extracted functions.
- `harness.c` defines the dispatch tables and fills them through the original
`overload_*_functions ()`. It adds hand-written rows only for entity types
whose files are not compiled yet, and supplies the environment: frame delta,
comms, transport and mobile positions. It then reads a scenario on stdin and
prints the outcome, with floats as bit patterns. NULL dereferences in the
original code are reported as `result null-dereference` (`SIGSEGV`).
- `build.mjs` compiles everything. Our own files build with `-Werror`. Original
files keep their historical warnings, but mismatches with the environment are
errors (implicit declarations, pointer and int conversions). The build fails
when no C compiler is available.

The shim is meant to shrink over time. `docs/architecture.md`, "Shrinking the C
reference shim", lists the current shim entries, the order in which real EECH
translation units replace them, and the rules that stop the shim growing.
The remaining hand-written behaviour is listed, together with the plan to
retire it, in `docs/architecture.md`, "Shrinking the C reference shim".
75 changes: 47 additions & 28 deletions eech-core-ts/c-reference/build.mjs
Original file line number Diff line number Diff line change
@@ -1,50 +1,69 @@
#!/usr/bin/env node
//
// Builds the C reference harness: extracts the original EECH functions and
// compiles them with the shim. Fails (exit 1) when no C compiler is available,
// so the C reference gate can never pass vacuously.
// Builds the C reference harness: generates project.h and the verbatim
// extracts, then compiles the harness, the extracts and the original EECH
// translation units listed in extract.mjs. Fails (exit 1) when no C compiler
// is available, so the C reference gate can never pass vacuously.
//

import { spawnSync } from "node:child_process";
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { generateExtracted } from "./extract.mjs";
import { AP_SOURCE, REAL_TRANSLATION_UNITS, writeGenerated } from "./extract.mjs";

const here = dirname(fileURLToPath(import.meta.url));
const projectRoot = join(here, "..");
const repoRoot = join(projectRoot, "..");
const outDir = join(projectRoot, "build", "c-reference");

export const HARNESS_BINARY = join(outDir, "harness");

export function buildHarness() {
mkdirSync(outDir, { recursive: true });
writeFileSync(join(outDir, "eech_extracted.c"), generateExtracted());
const COMMON = [
"-std=gnu99",
"-O0",
"-ffp-contract=off",
"-fno-fast-math",
// build/c-reference first, so the original files' #include "project.h" finds the reduced one
"-I",
outDir,
"-I",
here,
"-I",
join(repoRoot, AP_SOURCE),
];

const cc = process.env.CC || "cc";
const args = [
"-std=c99",
"-O0",
"-Wall",
"-Werror",
"-Wno-unused-function",
"-Wno-unused-variable",
"-Wno-unused-but-set-variable",
"-ffp-contract=off",
"-fno-fast-math",
"-I",
outDir,
"-I",
here,
join(here, "harness.c"),
"-lm",
"-o",
HARNESS_BINARY,
];
// Our own sources: every warning is an error.
const OWN_FLAGS = ["-Wall", "-Werror", "-Wno-unused-function", "-Wno-unused-variable", "-Wno-unused-but-set-variable"];

// Original EECH sources: their historical warnings are not ours to fix, but the
// ones that would hide a mismatch with the harness environment are errors.
const ORIGINAL_FLAGS = ["-Werror=implicit-function-declaration", "-Werror=incompatible-pointer-types", "-Werror=int-conversion", "-Werror=return-type", "-w"];

function compile(cc, source, flags) {
const object = join(outDir, `${source.replace(/[\\/]/g, "_")}.o`);
const args = [...COMMON, ...flags, "-c", source, "-o", object];
const result = spawnSync(cc, args, { encoding: "utf8" });
if (result.error || result.status !== 0) {
throw new Error(`C reference harness build failed (${cc} ${args.join(" ")}):\n${result.error ?? ""}${result.stdout}${result.stderr}`);
}
return object;
}

export function buildHarness() {
writeGenerated(outDir);

const cc = process.env.CC || "cc";
const objects = [
compile(cc, join(here, "harness.c"), OWN_FLAGS),
// verbatim original code: original-code flags
compile(cc, join(outDir, "eech_extracted.c"), ORIGINAL_FLAGS),
...REAL_TRANSLATION_UNITS.map((unit) => compile(cc, join(repoRoot, unit), ORIGINAL_FLAGS)),
];

const link = spawnSync(cc, [...objects, "-lm", "-o", HARNESS_BINARY], { encoding: "utf8" });
if (link.error || link.status !== 0) {
throw new Error(`C reference harness link failed:\n${link.error ?? ""}${link.stdout}${link.stderr}`);
}
return HARNESS_BINARY;
}

Expand Down
29 changes: 29 additions & 0 deletions eech-core-ts/c-reference/eech_harness_decls.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Declarations the harness adds after the original headers.
*/

#ifndef EECH_HARNESS_DECLS_H
#define EECH_HARNESS_DECLS_H

/* cmndline.h (the EECH.INI "entity update frame rate" setting) */
extern int
command_line_entity_update_frame_rate;

/* functions extracted into eech_extracted.c and called from harness.c */
extern void harness_overload_group_link_parent_responses (void);

extern void harness_default_update_link_responses (void);

extern void (*harness_default_set_entity_int_value) (entity *en, int_types type, int value);

extern void (*harness_default_set_entity_float_value) (entity *en, float_types type, float value);

extern void update_client_server_entities (void);

extern float get_2d_range (const vec3d *v1, const vec3d *v2);

extern float get_approx_2d_range (const vec3d *v1, const vec3d *v2);

extern void assess_group_supplies (entity *en);

#endif
61 changes: 61 additions & 0 deletions eech-core-ts/c-reference/eech_harness_env.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Harness environment.
*
* What the platform, the engine modules (modules/system, modules/maths) and
* the network layer supply to the original EECH translation units. It holds
* no campaign behaviour. The generated build/c-reference/project.h includes
* this first, then verbatim fragments and whole original headers (see
* c-reference/extract.mjs).
*
* Every definition here is either:
* - platform: the C library, and the Windows SDK's min/max macros;
* - engine: types that included headers only name in prototypes
* (matrix3x3, viewpoint), and the debug output functions;
* - debug-build validation that is not ported.
*/

#ifndef EECH_HARNESS_ENV_H
#define EECH_HARNESS_ENV_H

#include <float.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* the TypeScript port models C float arithmetic as IEEE single precision
evaluated at declared type; the reference must do the same */
#if !defined (FLT_EVAL_METHOD) || FLT_EVAL_METHOD != 0
#error "the C reference harness requires FLT_EVAL_METHOD == 0"
#endif

#define TRUE 1
#define FALSE 0

/* Windows SDK windef.h: EECH relies on these platform macros (e.g. gp_updt.c
max (raw->sleep, 0.0f)). Note max (NaN, 0.0f) is 0.0f, unlike fmax. */
#define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))

/* modules/system/assert.h with EECH's debug-build meaning: the check is
performed and a failure is reported to the harness. */
void harness_assert (const char *expression);

#define ASSERT(E) if (!(E)) (harness_assert (#E))
#define debug_assert(E) if (!(E)) (harness_assert (#E))

/* modules/system/debug.h */
extern void debug_fatal (const char *string, ...);
extern void debug_log (const char *string, ...);

/* engine types that included headers only name in prototypes */
typedef float matrix3x3[3][3];
typedef struct VIEWPOINT viewpoint;

/* en_debug/en_valid.h: debug-build checks of the comms dispatch context,
excluded from the port (docs/port-manifest.md) */
#define validate_client_server_local_fn()
#define validate_client_server_remote_fn()

#endif
34 changes: 0 additions & 34 deletions eech-core-ts/c-reference/eech_shim_prelude.h

This file was deleted.

Loading
Loading