feat(types): IVec2 + Rect 2D value types (#1)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 20s
ci / build-and-test (push) Successful in 1m26s
commit-lint / conventional-commits (push) Successful in 6s
docs / build-and-deploy (push) Successful in 21s

Phase 1 of the fuller type-system proposal: two by-value spatial types
that lower to packed integers (no heap, copy like scalars).

- IVec2 — integer 2D vector, a pair of int packed into one i64, for tile
  and grid coordinates: make/zero/x/y/add/sub/scale/dot, the grid distance
  manhattan, equal, and to_vector (widen into the fixed-point Vector).
- Rect — axis-aligned rectangle, four Q16.16 fixed components packed into
  one i128, for HUD boxes and hitboxes: make/x/y/w/h, the derived
  right/bottom/center, and the contains (point) / intersects (overlap) tests.

Both are exact and deterministic, bit-identical on every platform. Vector
and Color already cover phase 1's other 2D primitives.

Wired end to end: emit_core llty (IVec2->i64, Rect->i128), emit_call
dispatch, the FRAGS list + reseeded seed, a selfhost test (types2d),
per-symbol docs + type pages + inventory, and the vocabulary/editor sync
(header, JetBrains, TextMate, LSP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-31 18:29:41 +03:00
parent 3df6640fa5
commit 2c9f9ac549
40 changed files with 26731 additions and 24058 deletions

View file

@ -319,7 +319,9 @@
"type-string",
"type-void",
"type-words",
"type-vector"
"type-vector",
"type-ivec2",
"type-rect"
],
"world": [
"world-get",
@ -356,6 +358,31 @@
"vector-angle",
"vector-from_angle"
],
"ivec2": [
"ivec2-make",
"ivec2-zero",
"ivec2-x",
"ivec2-y",
"ivec2-add",
"ivec2-sub",
"ivec2-scale",
"ivec2-dot",
"ivec2-equal",
"ivec2-manhattan",
"ivec2-to_vector"
],
"rect": [
"rect-make",
"rect-x",
"rect-y",
"rect-w",
"rect-h",
"rect-right",
"rect-bottom",
"rect-center",
"rect-contains",
"rect-intersects"
],
"duration": [
"duration-seconds",
"duration-minutes",

View file

@ -52,7 +52,7 @@ object LudicVocabulary {
"enable", "disable", "match", "machine", "state", "become", "where",
"and", "or", "not", "break", "continue", "new", "emit", "cancel", "try"
)
val PRIMITIVES = setOf("int", "long", "fixed", "bool", "entity", "string", "pointer", "byte", "words", "fixeds", "pointers", "Vector", "void")
val PRIMITIVES = setOf("int", "long", "fixed", "bool", "entity", "string", "pointer", "byte", "words", "fixeds", "pointers", "Vector", "IVec2", "Rect", "void")
val PHASES = setOf("Start", "Input", "FixedUpdate", "Update", "LateUpdate", "Render")
val WIDGETS = setOf("panel", "col", "row", "label", "button", "image", "spacer")

View file

@ -177,7 +177,7 @@
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on)\\b" },
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
{ "name": "storage.type.ludic", "match": "\\b(program|property|model|enum|ui|const|var|let|function|handler|entry|state|test)\\b" },
{ "name": "support.type.primitive.ludic", "match": "\\b(int|long|fixed|bool|entity|string|pointer|byte|words|fixeds|pointers|Vector|void)\\b" },
{ "name": "support.type.primitive.ludic", "match": "\\b(int|long|fixed|bool|entity|string|pointer|byte|words|fixeds|pointers|Vector|IVec2|Rect|void)\\b" },
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false|null)\\b" },
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }
]

View file

@ -177,7 +177,7 @@
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on)\\b" },
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
{ "name": "storage.type.ludic", "match": "\\b(program|property|model|enum|ui|const|var|let|function|handler|entry|state|test)\\b" },
{ "name": "support.type.primitive.ludic", "match": "\\b(int|long|fixed|bool|entity|string|pointer|byte|words|fixeds|pointers|Vector|void)\\b" },
{ "name": "support.type.primitive.ludic", "match": "\\b(int|long|fixed|bool|entity|string|pointer|byte|words|fixeds|pointers|Vector|IVec2|Rect|void)\\b" },
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false|null)\\b" },
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }
]

View file

@ -204,7 +204,7 @@ program LudicLsp {
return c == 43 or c == 45 or c == 42 or c == 47 or c == 37 or c == 60 or c == 62 or c == 61 or c == 40 or c == 41 or c == 123 or c == 125 or c == 91 or c == 93 or c == 44 or c == 58 or c == 46 or c == 33 or c == 64 or c == 59
}
function is_type_word(w: pointer) -> bool {
return (w == "int") or (w == "long") or (w == "fixed") or (w == "bool") or (w == "entity") or (w == "string") or (w == "pointer") or (w == "byte") or (w == "words") or (w == "fixeds") or (w == "pointers") or (w == "Vector") or (w == "void")
return (w == "int") or (w == "long") or (w == "fixed") or (w == "bool") or (w == "entity") or (w == "string") or (w == "pointer") or (w == "byte") or (w == "words") or (w == "fixeds") or (w == "pointers") or (w == "Vector") or (w == "IVec2") or (w == "Rect") or (w == "void")
}
function is_phase_word(w: pointer) -> bool {
return (w == "Start") or (w == "Input") or (w == "FixedUpdate") or (w == "Update") or (w == "LateUpdate") or (w == "Render")
@ -1318,6 +1318,31 @@ program LudicLsp {
if (meth == "angle") { return "Vector.angle(v) -> fixed" }
if (meth == "from_angle") { return "Vector.from_angle(a) -> Vector" }
}
if (ns == "IVec2") {
if (meth == "make") { return "IVec2.make(x, y) -> IVec2" }
if (meth == "zero") { return "IVec2.zero() -> IVec2" }
if (meth == "x") { return "IVec2.x(v) -> int" }
if (meth == "y") { return "IVec2.y(v) -> int" }
if (meth == "add") { return "IVec2.add(a, b) -> IVec2" }
if (meth == "sub") { return "IVec2.sub(a, b) -> IVec2" }
if (meth == "scale") { return "IVec2.scale(v, s) -> IVec2" }
if (meth == "dot") { return "IVec2.dot(a, b) -> int" }
if (meth == "equal") { return "IVec2.equal(a, b) -> bool" }
if (meth == "manhattan") { return "IVec2.manhattan(a, b) -> int" }
if (meth == "to_vector") { return "IVec2.to_vector(v) -> Vector" }
}
if (ns == "Rect") {
if (meth == "make") { return "Rect.make(x, y, w, h) -> Rect" }
if (meth == "x") { return "Rect.x(r) -> fixed" }
if (meth == "y") { return "Rect.y(r) -> fixed" }
if (meth == "w") { return "Rect.w(r) -> fixed" }
if (meth == "h") { return "Rect.h(r) -> fixed" }
if (meth == "right") { return "Rect.right(r) -> fixed" }
if (meth == "bottom") { return "Rect.bottom(r) -> fixed" }
if (meth == "center") { return "Rect.center(r) -> Vector" }
if (meth == "contains") { return "Rect.contains(r, px, py) -> bool" }
if (meth == "intersects") { return "Rect.intersects(a, b) -> bool" }
}
if (ns == "List") {
if (meth == "len") { return "List.len(s) -> int" }
if (meth == "push") { return "List.push(s, v)" }
@ -2341,7 +2366,7 @@ program LudicLsp {
}
function comp_types(o: Buf) -> void {
comp_item(o, "int", 14, "built-in type"); comp_item(o, "long", 14, "built-in type"); comp_item(o, "fixed", 14, "built-in type"); comp_item(o, "bool", 14, "built-in type")
comp_item(o, "entity", 14, "built-in type"); comp_item(o, "string", 14, "built-in type"); comp_item(o, "pointer", 14, "built-in type"); comp_item(o, "Vector", 14, "built-in type"); comp_item(o, "void", 14, "built-in type")
comp_item(o, "entity", 14, "built-in type"); comp_item(o, "string", 14, "built-in type"); comp_item(o, "pointer", 14, "built-in type"); comp_item(o, "Vector", 14, "built-in type"); comp_item(o, "IVec2", 14, "built-in type"); comp_item(o, "Rect", 14, "built-in type"); comp_item(o, "void", 14, "built-in type")
}
function comp_builtins(o: Buf) -> void { var i = 0; while i < len(g_builtins) { comp_item(o, g_builtins[i], 3, g_bsigs[i]); i = i + 1 } }
function on_completion(msg: JVal, id: JVal) -> void {

View file

@ -71,7 +71,7 @@ static const char* LUDIC_KW_STMT[] = {
"and","or","not","break","continue","new","emit","cancel","try", 0
};
static const char* LUDIC_TYPES[] = {
"int","long","fixed","bool","entity","string","pointer","byte","words","fixeds","pointers","Vector","void", 0
"int","long","fixed","bool","entity","string","pointer","byte","words","fixeds","pointers","Vector","IVec2","Rect","void", 0
};
static const char* LUDIC_PHASES[] = {
"Start","Input","FixedUpdate","Update","LateUpdate","Render", 0

View file

@ -26,6 +26,8 @@ function selfhost_frags() -> []pointer {
push(f, "selfhost/backend/emit_intrin2.ludic")
push(f, "selfhost/backend/stdlib/emit_math.ludic")
push(f, "selfhost/backend/stdlib/emit_vector.ludic")
push(f, "selfhost/backend/stdlib/emit_ivec.ludic")
push(f, "selfhost/backend/stdlib/emit_rect.ludic")
push(f, "selfhost/backend/stdlib/emit_text.ludic")
push(f, "selfhost/backend/stdlib/emit_text_prelude.ludic")
push(f, "selfhost/backend/stdlib/emit_hash.ludic")

View file

@ -96,6 +96,7 @@ function cmd_selfhost_test() -> int {
sh_case("math3", "45 90 180 -135 30 60 90")
sh_case("transcend", "2718 1 148 1000 0 2303 1024 1414 3 250 0 1000 1016")
sh_case("vector", "3 4 5 4 6 2 2 11 2 6 999 999 999 1570 5 10")
sh_case("types2d", "3 4 4 6 2 2 9 12 11 4 0 1 0 4 10 20 30 40 40 60 25 40 1 0 0 1 0")
sh_case("datetime", "10800 330 172800 3 2 0 10957 2026 8 30 4 6 1 0 1 29 28 31 2 1 365 946729815 2000 1 1 12 30 15 6 10957 1")
sh_case("datetime2", "2026-08-30 07:05:09 30/08/26 0 -1 3600 5400 30 0")
sh_case("textsplit", "1 1 1 3 1 1 1 1 1 1 1")