diff --git a/compiler/backend/emit.ts b/compiler/backend/emit.ts index 30e688d..55e6ca5 100644 --- a/compiler/backend/emit.ts +++ b/compiler/backend/emit.ts @@ -21,9 +21,9 @@ import { STACK_DEPTH, type ArrayDecl, type BoolExpr, type Call, type FuncDecl, type NumExpr, type Program, type Stmt, type TextPart, type VarDecl, type At } from "../ir"; import { PACK_FROM } from "../eud"; import type { Range } from "../ranges"; -import { PlayerGroup } from "../../vendor/triggers"; +import { PlayerGroup, type ActionRecord, type ConditionRecord } from "../../vendor/triggers"; import { actionDef, conditionDef } from "../../vendor/triggerDefs"; -import { END, MOST_ACTS, MOST_CONDS, STACK, SWITCHES, TRIGGER_BYTES, checkGraph, nextOf, type Act, type Compare, type Cond, type Dword, type FixedFn, type Graph, type Num, type Region, type Trigger } from "./graph"; +import { END, MOST_ACTS, MOST_CONDS, STACK, SWITCHES, TRIGGER_BYTES, checkGraph, nextOf, type Act, type ActPart, type Compare, type Cond, type Dword, type FixedFn, type Graph, type Num, type Region, type Trigger } from "./graph"; /** The game's tick (frames since the game started), and the fields of the trigger a map of our own wakes by. */ export const GAME_TICK = 0x57f23c; @@ -218,11 +218,7 @@ export function unlowered(s: Stmt, decls: Map, arrays: Map LAST_GROUP) return "a read of memory (memory())"; - return `the condition ${conditionDef(r.type)?.name ?? r.type}`; - } + case "cond": return undefined; case "tableRead": return `a read of ${(o.cell as { name: string }).name}`; case "input": return `the players' input (${(o.input as { source: string }).source})`; case "length": return array(o.array as string) ? "the length of an array that grows" : undefined; @@ -268,6 +264,7 @@ export function unlowered(s: Stmt, decls: Map, arrays: Map { switch (t.kind) { case "action": { + if (gameAction(t)) return undefined; if (t.record.type === SET_DEATHS_ACTION && t.record.player > LAST_GROUP) return "a write of memory (setMemory())"; const name = `the action ${actionDef(t.record.type)?.name ?? t.record.type}`; return t.variables?.length ? `${name}, its fields from values` : t.text ? `${name}, its text the program's` : name; @@ -323,8 +320,92 @@ export function unlowered(s: Stmt, decls: Map, arrays: Map): boolean => + (!s.text || (s.text.kind === "template" && MADE_TEXT.has(s.record.type))) && s.record.type !== WAIT_ACTION && fieldsTaken(s); +/** + * The actions whose text may be made while the game runs (a template filled before it, as a print's): Transmission, + * Display Text, the mission objectives and the leaderboards' labels — the game reads such a text again whenever it + * draws, from the template the action names, one for each place in the script it is written. + */ +const MADE_TEXT = new Set([7, 9, 12, 17, 18, 19, 20, 21, 33, 34, 35, 36, 37, 40]); +/** + * Where an action's field from a value goes (`Emitter.action`): the dword of the record its place is, by the part of + * an action `graph.ts` names it by. The unit type, the action's kind and the count share one; a value in it is the + * type, or the count times 2^24, or (a 32-bit unit type, `Rewriter.action`'s) both. + */ +const FIELD_PART: Partial> = { location: "mask", text: "text", wav: "wav", time: "time", player: "dest", target: "value", unitId: "modifier", modifier: "modifier" }; +/** Every field from a value is one a place can be, each one variable times a constant plus a constant, one a dword. */ +function fieldsTaken(s: Extract): boolean { + const vs = (s.variables ?? []).filter((v) => linear(v.expr)?.terms.length !== 0); + if (!(s.variables ?? []).every((v) => FIELD_PART[v.field] && (linear(v.expr)?.terms.length ?? 2) <= 1)) return false; + return new Set(vs.map((v) => FIELD_PART[v.field])).size === vs.length; +} +/** + * The actions after which Bring and Command read the counts they found before: the game keeps a table of them while the + * queries stay of one kind (and, for Bring, one location), throwing it away at the start of a pass, at a query of the + * other kind, and in the actions that kill or remove units — not in these (Create Unit with Properties, Move Unit, + * Create Unit, Give Units). The Python side's `MOVES_UNITS`; its `forget_counts` covers Bring alone. + */ +const MOVES_UNITS = new Set([11, 39, 44, 48]); +/** The conditions that read that table: Command, Bring, Command the Most (at), Command the Least (at). */ +const COUNTING = new Set([2, 3, 6, 7, 16, 17]); +/** + * What throws the table away after such an action, in its own trigger: Kill Unit of Player 12's units of type 228, + * which no unit is (played in the lifted game, 2026-10-20: Command and Bring both count again after it, and Player 12's + * minerals stay). + */ +const FORGET_COUNTS: ActionRecord = { location: 0, text: 0, wav: 0, time: 0, player: 11, target: 0, unitId: 228, type: 22, modifier: 0, flags: 0x10, padding: 0, mask: 0 }; +/** The condition types whose comparison is a number's at least / at most, which one condition turns around. */ +const COUNTED = new Set([1, 2, 3, 4, 5, 12, 14, 15, 21]); + +/** + * A `max` or `min` against a constant that the value ranges already settle, as its other side: the front end stops an + * action's amount and count at 0 (`max(n, 0)`), which a counter never below it does not need. + */ +function settled(e: NumExpr): NumExpr { + if (e.kind !== "intrinsic" || (e.name !== "max" && e.name !== "min") || e.args.length !== 2 || e.unsigned) return e; + const [x, c] = e.args; + const r = (x as NumExpr & { range?: readonly [number, number] }).range; + if (c.kind !== "const" || !r) return e; + return (e.name === "max" ? r[0] >= c.value : r[1] <= c.value) ? settled(x) : e; +} + +/** Statements with the blocks around them taken away, and the remarks. */ +function flatten(list: Stmt[]): Stmt[] { + return list.flatMap((t) => (t.kind === "block" ? flatten(t.body) : t.kind === "remark" ? [] : [t])); +} + +/** A condition of the game's in a trigger (`Cond.game`). */ +const gameCond = (r: ConditionRecord): Cond => ({ dword: { game: 0 }, compare: "atLeast", value: 0, game: r }); + +/** + * The conditions that hold exactly when a condition of the game's is `when`: itself; turned around, the one condition + * that holds when it does not — at least n against at most n − 1, a switch set against cleared — or null when there is + * none (exactly n, Command the Most, …). Always and Never are known either way. + */ +function gameConds(r: ConditionRecord, when: boolean): Cond[] | "true" | "false" | null { + // (A disabled one as it is: the game decides what it makes of it.) + if (r.flags & 0x02) return when ? [gameCond(r)] : null; + if (r.type === ALWAYS_CONDITION) return when ? "true" : "false"; + if (r.type === NEVER_CONDITION) return when ? "false" : "true"; + if (when) return [gameCond(r)]; + if (r.mask) return null; + if (r.type === SWITCH_CONDITION && (r.comparison === 2 || r.comparison === 3)) return [gameCond({ ...r, comparison: 5 - r.comparison })]; + if (!COUNTED.has(r.type)) return null; + const n = r.amount >>> 0; + if (r.comparison === 0) return n === 0 ? "false" : [gameCond({ ...r, comparison: 1, amount: n - 1 })]; + if (r.comparison === 1) return n === U32 - 1 ? "false" : [gameCond({ ...r, comparison: 0, amount: n + 1 })]; + return null; +} /** The kinds of statement, told apart from the expressions of the same name (`pop`, `call`). */ const STATEMENT_KINDS = new Set(["declare", "assignText", "storeText", "releaseText", "textLoop", "assign", "declareArray", "store", "push", "setLength", "assignBool", "assignUnit", "unitLoop", "unitWrite", "unitDo", "tableWrite", "if", "while", "do", "for", "unrolled", "switch", "break", "continue", "return", "sleep", "parallel", "start", "cancel", "action", "syncSend", "select", "centerLocation", "print", "buildText", "block", "try", "throw", "uncaught", "remark", "graph"]); @@ -342,6 +423,8 @@ class Scope { whyOf = new Map(); /** A map of our own: the slot of the program's owner, the current player while it runs. */ owner: number | undefined; + /** Whether a condition taken reads the game's counts, which an action that makes or moves units leaves behind (`MOVES_UNITS`). */ + counts = false; readonly decls: Map; constructor(decls: Map) { this.decls = decls; @@ -387,6 +470,10 @@ class Scope { case "var": return this.bool(notZero({ kind: "var", id: e.id })); case "and": case "or": return e.items.every((x) => this.bool(x)); case "not": return this.bool(e.expr); + case "cond": + if (!this.native) return this.no(`a ${e.kind}`); + if (COUNTING.has(e.record.type)) this.counts = true; + return true; default: return this.no(`a ${e.kind}`); } } @@ -818,6 +905,15 @@ class Scope { if (this.native && s.position === "chat" && s.parts.every((p) => this.printed(p))) return true; return this.island(s); case "call": return (this.native && this.call(s.call)) || this.island(s); + case "action": { + if (!this.native || !gameAction(s)) return this.island(s); + if (s.text?.kind === "template" && !s.text.parts.every((p) => this.printed(p))) return this.island(s); + for (const v of s.variables ?? []) for (const t of linear(v.expr)!.terms) { + if (!this.ok(t.id)) return this.island(s); + this.read.add(t.id); + } + return true; + } case "declareArray": { // A map of our own: the cells set from the list or the one value (`fill`), each a number the emitter lowers. const a = this.arrays.get(s.array); @@ -907,8 +1003,12 @@ interface Store { array: ArrayDecl; index: Linear; reads: CellRead[]; value: Lin /** (`label`: every place of `target` set to where `to` is — where a function that calls itself goes back to.) */ /** (`route`: a variable's cell into these dwords, and nothing else of its places.) */ type Item = { act: Act } | { write: Write } | { fill: { temp: string; target: string; apart?: boolean; top?: number } } | { copy: string; at?: At } | { island: Stmt; writes: Set } | { bump: { target: string; add: number } } | { call: { fn: FixedFn; target: string }; at?: At } | { label: { target: string; to: string } } | { route: { from: string; into: Dword[] } }; -/** `keep`: an empty trigger that stays (a jump whose next pointer a place sets). */ -interface Proto { label: string; conds: Cond[]; items: Item[]; next?: string; at?: At; depth?: number; keep?: boolean } +/** + * `keep`: an empty trigger that stays (a jump whose next pointer a place sets). `guarded`: an `if` in one trigger (its + * conditions, its statements' actions), which may be followed by the copies of what it writes — a copy whether or not + * the conditions held, which writes back what the places hold when they did not. + */ +interface Proto { label: string; conds: Cond[]; items: Item[]; next?: string; at?: At; depth?: number; keep?: boolean; guarded?: boolean } /** * A place of a variable: a dword that holds it times `scale` plus `add` (wrapping) — known (its cell, a home, a @@ -1003,7 +1103,7 @@ class Emitter { /** A jump into an EUDVArray's cell: its trigger's next pointer, a place of the index. */ jumpReads = new Map(); /** A store's action: its destination a place of the index, its number (when not a constant) a place of the value. */ - actPlaces = new Map(); + actPlaces = new Map>>(); /** The variables of the emitter's own (a table's index), which have no cell on the Python side. */ own = new Set(); /** The arrays whose cells are read where they are, and the EPD from one cell's value to the next's. */ @@ -1097,7 +1197,7 @@ class Emitter { } push(p: Omit & { label?: string }): Proto { - const t: Proto = { label: p.label ?? this.label("t"), conds: p.conds, items: p.items, next: p.next, at: p.at ?? this.at, depth: this.depthBase + this.loops.length, ...(p.keep ? { keep: true } : {}) }; + const t: Proto = { label: p.label ?? this.label("t"), conds: p.conds, items: p.items, next: p.next, at: p.at ?? this.at, depth: this.depthBase + this.loops.length, ...(p.keep ? { keep: true } : {}), ...(p.guarded ? { guarded: true } : {}) }; this.protos.push(t); return t; } @@ -1118,10 +1218,11 @@ class Emitter { return this.push({ conds: [], items: [] }); } - write(target: string, value: Linear, copy = true): void { + write(target: string, value: Linear, copy = true, into: Item[] = this.open().items, copies = into): void { const w: Write = { target, value, ...(this.at ? { at: this.at } : {}) }; // (The copy is there for a variable that turns out to need one: the rest leave it out.) - this.open().items.push({ write: w }, ...(copy ? [{ copy: target, ...(this.at ? { at: this.at } : {}) }] : [])); + into.push({ write: w }); + if (copy) copies.push({ copy: target, ...(this.at ? { at: this.at } : {}) }); let list = this.writes.get(target); if (!list) this.writes.set(target, (list = [])); list.push(w); @@ -1242,6 +1343,7 @@ class Emitter { if (e.kind === "not") return this.single(e.expr, !when); if (e.kind === "test") return this.single(notZero(e.expr), when); if (e.kind === "var") return this.single(notZero({ kind: "var", id: e.id }), when); + if (e.kind === "cond") return gameConds(e.record, when); if ((e.kind === "and" && when) || (e.kind === "or" && !when)) { const parts = e.items.map((x) => this.single(x, when)); if (parts.some((p) => p === null)) return null; @@ -1268,6 +1370,21 @@ class Emitter { case "var": return this.jump(notZero({ kind: "var", id: e.id }), when, to, at); case "and": case "or": { if ((e.kind === "and") !== when) { + // Any item not so goes to `to`: when every item's conditions for being so fit in one trigger, that trigger + // skips past a goto (one trigger where a branch an item would be more); else an item at a time. + const parts = e.items.map((x) => this.single(x, !when)); + if (e.items.length > 1 && parts.every((p) => p !== null)) { + if (parts.includes("false")) return this.goto(to); + const conds = parts.filter((p): p is Cond[] => Array.isArray(p)).flat(); + if (conds.length === 0) return; + if (conds.length <= MOST_CONDS) { + const past = this.label("p"); + this.branch(conds, past, at); + this.goto(to); + this.land(past); + return; + } + } for (const x of e.items) this.jump(x, when, to, at); return; } @@ -1298,6 +1415,18 @@ class Emitter { this.jump({ ...e, op: ">", unsigned: true }, true, to, at); return; } + case "cond": { + const c = gameConds(e.record, when); + if (c === "false") return; + if (c === "true") return this.goto(to); + if (c) return this.branch(c, to, at); + // (Not so, of a condition no other one turns around: past `to` when it holds.) + const past = this.label("p"); + this.branch(gameConds(e.record, true) as Cond[], past, at); + this.goto(to); + this.land(past); + return; + } default: throw new Error(`emitter: ${e.kind}`); } } @@ -1328,6 +1457,7 @@ class Emitter { if (s.kind === "declare" && s.decl.kind === "boolean") return this.setBool(s.decl.id, s.init as BoolExpr); if (s.kind === "assignBool") return this.setBool(s.target, s.value); if (s.kind === "call" && !this.scope.islands.has(s)) return this.call(s.call); + if (s.kind === "action" && !this.scope.islands.has(s)) return this.action(s); if (s.kind === "declareArray" && this.scope.native && !this.scope.islands.has(s)) return this.declareArray(s); if (s.kind === "return") return this.ret(s); const call = this.scope.calls.get(s); @@ -1379,6 +1509,24 @@ class Emitter { case "declare": return this.write(s.decl.id, linear(s.init as NumExpr)!); case "assign": return this.write(s.target, linear(s.value)!); case "if": { + // A map of our own: actions of the game's and writes that read nothing but their target, under conditions + // that fit in one trigger — that trigger, its actions run when they hold (no branch, no puts-back); the copies + // of what it writes after it. + const then = flatten(s.then); + const guard = this.scope.native && !s.else?.length && then.length && then.length <= MOST_ACTS / 4 && then.every((t) => this.guards(t)) ? this.single(s.cond, true) : null; + if (guard === "false") return; + if (Array.isArray(guard)) { + const items: Item[] = [], copies: Item[] = []; + for (const t of then) { + const outer = this.at; + if (t.at) this.at = t.at; + if (t.kind === "action") items.push({ act: { dword: { game: 0 }, modifier: "set", value: 0, record: t.record, ...(t.at ? { at: t.at } : {}) } }, ...this.forgets(t.record)); + else if (t.kind === "assign") this.write(t.target, linear(t.value)!, true, items, copies); + this.at = outer; + } + this.push({ conds: guard, items: [...items, ...copies], at: s.at, guarded: true }); + return; + } const other = this.label("else"), done = this.label("fi"); this.jump(s.cond, false, s.else?.length ? other : done, s.at); this.stmts(s.then); @@ -1575,6 +1723,73 @@ class Emitter { } /** A boolean set from a test: 1 when it holds, 0 when not. */ + /** A statement an `if` may guard in its one trigger: an action of the game's, or a write that reads only its target. */ + guards(t: Stmt): boolean { + if (this.scope.islands.has(t) || this.scope.tables.has(t) || this.scope.calls.has(t)) return false; + if (t.kind === "action") return gameAction(t) && !t.variables?.length && !t.text && t.record.type !== TRANSMISSION_ACTION; + if (t.kind !== "assign" || isTemp(t.target) || this.own.has(t.target) || this.results.has(t.target) || this.params.has(t.target)) return false; + const l = linear(t.value); + return !!l && l.terms.every((x) => x.id === t.target && x.k === 1); + } + + /** + * An action of the game's, as the script wrote it, among the trigger's others. After one that makes or moves units, + * in a program that asks Bring or Command, Kill Unit of no unit (`FORGET_COUNTS`), in the same trigger. + */ + action(s: Extract): void { + // A field from a value is a place of its variable, kept by its writes: the action reads it where it is, and costs + // nothing more when it runs. The record holds the rest; in the dword the type, the kind and the count share, the + // place's constant is what is not the value. Its trigger is one of its own, the action first, so the place's index + // holds. + const record: ActionRecord = { ...s.record }; + const vars = (s.variables ?? []).map((v) => ({ ...v, l: linear(v.expr)! })); + for (const v of vars) if (!v.l.terms.length) (record as unknown as Record)[v.field] = v.l.c & (v.bits === 32 ? -1 : 2 ** v.bits - 1); + const varied = vars.filter((v) => v.l.terms.length); + const word = (f: string) => varied.some((v) => v.field === f); + const places: Partial> = {}; + for (const v of varied) { + const [t] = v.l.terms; + let scale = 1, add = 0; + if (v.field === "unitId" || v.field === "modifier") { + add = u((word("unitId") ? 0 : record.unitId) + record.type * 2 ** 16 + (word("modifier") ? 0 : record.modifier) * 2 ** 24); + if (v.field === "modifier") scale = 2 ** 24; + } + (record as unknown as Record)[v.field] = 0; + places[FIELD_PART[v.field]!] = { id: t.id, scale: mul(scale, t.k), add: u(mul(scale, v.l.c) + add) }; + } + // (A text made while the game runs: its template filled first, the action's text field naming it.) + const textOf = s.text?.kind === "template" ? this.template(s.text.parts) : undefined; + const act: Act = { dword: { game: 0 }, modifier: "set", value: 0, record, ...(textOf !== undefined ? { textOf } : {}) }; + const items = [{ act }, ...this.forgets(record)]; + if (record.type === TRANSMISSION_ACTION) return this.transmission(act, varied.length ? places : undefined); + if (varied.length) { + this.actPlaces.set(act, places); + this.push({ conds: [], items }); + } else this.open().items.push(...items); + } + + /** + * A Transmission holds the rest of its trigger until its time is over — the walk goes on to the next trigger — and + * marks itself started, so that the game steps over it the next time its trigger is reached, going on from it: every + * other Transmission of a loop never showed (eudplib's do not). So it is the first action of a trigger of its own, + * and the trigger after it puts its flags and the trigger's current action back, as they were before it ran: each + * time the program reaches it, it shows. (A Transmission while another one's time runs is still held, as the game + * holds it.) + */ + transmission(act: Act, places?: Partial>): void { + if (places) this.actPlaces.set(act, places); + const tx = this.push({ conds: [], items: [{ act }] }); + this.push({ conds: [], items: [ + { act: { dword: { at: tx.label, field: { act: 0, part: "flags" } }, modifier: "set", value: act.record!.flags, mask: 0xff } }, + { act: { dword: { at: tx.label, field: "current" }, modifier: "set", value: 0, mask: 0xff000000 } }, + ] }); + } + + /** After an action that makes or moves units, in a program that asks Bring or Command: the counts thrown away. */ + forgets(r: ActionRecord): Item[] { + return MOVES_UNITS.has(r.type) && this.scope.counts ? [{ act: { dword: { game: 0 }, modifier: "set", value: 0, record: FORGET_COUNTS } }] : []; + } + setBool(target: string, cond: BoolExpr): void { const no = this.label("bno"), done = this.label("bdone"); this.jump(cond, false, no, this.at); @@ -1747,12 +1962,18 @@ class Emitter { * JavaScript works them out.) */ print(s: Extract): void { + const id = this.template(s.parts); + this.open().items.push({ act: { dword: { game: 0 }, modifier: "set", value: 0, show: id } }); + } + + /** A text with values in it written into a template of its own (`Graph.templates`), each value as it is now: its id. */ + template(parts: TextPart[]): string { const id = `${this.key}.text${this.n++}`; const bytes: number[] = []; const enc = new TextEncoder(); const values: { part: TextPart; at: number }[] = []; const fixedAt = new Map(); - for (const p of s.parts) { + for (const p of parts) { if (p.kind === "text") bytes.push(...enc.encode(p.text)); else if (p.kind === "number" && p.fixed) { // A number with a fraction: its sign's byte, its whole part's field, and eight bytes for the point and four places. @@ -1797,7 +2018,7 @@ class Emitter { this.push({ conds: [], items: [{ act: set }] }); this.push({ conds: [{ dword: word, compare: "exactly", value: 0, mask }], items: [{ act: { dword: word, modifier: "set", value: u(PAD * shift), mask } }] }); } - this.open().items.push({ act: { dword: { game: 0 }, modifier: "set", value: 0, show: id } }); + return id; } /** @@ -2951,7 +3172,7 @@ class Emitter { this.writes.delete(own); const rename = (r: R | undefined): R | undefined => (r && r.id === own ? { ...r, id: by } : r); for (const [label, r] of this.jumpReads) this.jumpReads.set(label, rename(r)!); - for (const [act, ap] of this.actPlaces) this.actPlaces.set(act, { ...(ap.dest ? { dest: rename(ap.dest) } : {}), ...(ap.value ? { value: rename(ap.value) } : {}) }); + for (const [act, ap] of this.actPlaces) this.actPlaces.set(act, Object.fromEntries(Object.entries(ap).map(([part, r]) => [part, rename(r)]))); for (const [c, r] of this.condReads) this.condReads.set(c, rename(r)!); for (const [c, r] of this.condPlayers) this.condPlayers.set(c, rename(r)!); this.own.delete(own); @@ -3201,8 +3422,7 @@ class Emitter { if (!("act" in it)) continue; const ap = this.actPlaces.get(it.act); if (ap) { - for (const [part, r] of [["dest", ap.dest], ["value", ap.value]] as const) { - if (!r) continue; + for (const [part, r] of Object.entries(ap) as [ActPart, Read][]) { this.placesOf(r.id).push({ dword: { at: p.label, field: { act: k, part } }, scale: r.scale, add: r.add, ...(r.base ? { base: r.base } : {}) }); pinned.add(r.id); } @@ -3343,7 +3563,7 @@ class Emitter { for (const id of it.writes) if ((this.places.get(id)?.filter((p) => !p.synced).length ?? 0) > 1 && !routed.has(id)) pieces.push({ copy: id, island: true, ...(top !== undefined ? { top } : {}), ...(it.island.at ? { at: it.island.at as At } : {}) }); } } - if (p.conds.length && (pieces.length > 1 || (pieces[0] as { acts: unknown[] }).acts.length > MOST_ACTS)) return this.crowded.size ? "crowded" : "a branch with too many actions"; + if (p.conds.length && (pieces.slice(1).some((x) => !(p.guarded && "copy" in x)) || !("acts" in pieces[0]) || pieces[0].acts.length > MOST_ACTS)) return this.crowded.size ? "crowded" : "a branch with too many actions"; // Routed variables: an action that reads one has its number refreshed from the cell just before its trigger // (again after a write of the variable earlier in the same run of actions); conditions likewise. const cut: Piece[] = []; @@ -3983,6 +4203,58 @@ class Rewriter { return out; } + /** + * An action of the game's with fields from values (a map of our own), as `Emitter.action` takes it: each value, in + * JavaScript's order, one variable times a constant plus a constant — a field holds that — or into a variable of the + * emitter's own first. A unit type stopped at 228, as the Python side's: past the table an action reads what is not + * a unit. A unit count is that many units, 0 or below none: the action done 255 at a time while more are left, then + * once with the rest — each half only where the count's range reaches it. A unit type and a count both from values + * share their dword, through one variable of the emitter's own: the type plus the count times 2^24. + */ + action(s: Extract, where: { at: At; label: string }): Stmt[] { + const pre: Stmt[] = []; + // (A text made while the game runs: its values as a print's — in JavaScript's order, the text before the fields.) + if (s.text?.kind === "template") { + const parts = s.text.parts; + const valued = parts.filter((p): p is Extract => p.kind === "number" || p.kind === "char"); + const exprs = this.each(valued.map((p) => p.expr), pre, where, (x) => (linear(x) ? x : this.sum(x, pre, where))); + let i = 0; + s = { ...s, text: { ...s.text, parts: parts.map((p) => (p.kind === "number" || p.kind === "char" ? { ...p, expr: exprs[i++] } : p)) } }; + if (!s.variables?.length) return [...pre, s]; + } + const one = (x: NumExpr) => (linear(x)?.terms.length ?? 2) <= 1; + const vars = s.variables!; + const exprs = this.each(vars.map((v) => (v.field === "unitId" ? this.top(settled(v.expr), 228, where) : settled(v.expr))), pre, where, (x) => (one(x) ? x : this.sum(x, pre, where))); + let fields = vars.map((v, i) => ({ ...v, expr: exprs[i] })); + const k = fields.findIndex((v) => v.field === "modifier"); + if (k < 0) return [...pre, { ...s, variables: fields }]; + const range = (vars[k].expr as NumExpr & { range?: readonly [number, number] }).range; + const many = !range || range[1] > 255, none = !range || range[0] < 1; + let count = fields[k].expr; + if (many || none || !one(count)) { + const t = this.temp(); + pre.push({ kind: "assign", target: t, value: count, ...where }); + count = { kind: "var", id: t }; + } + fields = fields.filter((_, i) => i !== k); + const v = (id: string): NumExpr => ({ kind: "var", id }); + const made = (n: NumExpr | null): Stmt[] => { + const type = fields.find((f) => f.field === "unitId"); + if (!n) return [{ ...s, record: { ...s.record, modifier: 255 }, variables: fields }]; + if (!type) return [{ ...s, variables: [...fields, { ...vars[k], expr: n }] }]; + const w = this.temp(); + const word: NumExpr = { kind: "binary", op: "+", left: type.expr, right: { kind: "binary", op: "*", left: n, right: { kind: "const", value: 2 ** 24 }, at: where.at, label: where.label }, at: where.at, label: where.label } as NumExpr; + return [{ kind: "assign", target: w, value: word, ...where }, { ...s, record: { ...s.record, modifier: 0 }, variables: fields.map((f) => (f === type ? { ...f, bits: 32 as const, expr: v(w) } : f)) }]; + }; + const at = { at: where.at, label: where.label }; + const c = count.kind === "var" ? count.id : ""; + if (many) { + pre.push({ kind: "while", cond: { kind: "compare", op: ">", left: count, right: { kind: "const", value: 255 }, ...at }, body: [...made(null), { kind: "assign", target: c, value: { kind: "binary", op: "-", left: count, right: { kind: "const", value: 255 }, ...at } as NumExpr, ...where }], ...where } as Stmt); + } + if (none) return [...pre, { kind: "if", cond: { kind: "compare", op: ">=", left: count, right: { kind: "const", value: 1 }, ...at }, then: made(count), ...where } as Stmt]; + return [...pre, ...made(count)]; + } + /** The statements that set `target` to `e`. */ set(target: string, e: NumExpr, where: { at: At; label: string }): Stmt[] { const assign = (value: NumExpr): Stmt => ({ kind: "assign", target, value, ...where }); @@ -4211,6 +4483,20 @@ class Rewriter { return so; } case "not": return { ...c, expr: this.cond(c.expr, pre, where) }; + // (A ?: of booleans, on a map of our own: the side its test picks worked out under an `if` into a variable of the + // emitter's own — the other side is never read, as in the script.) + case "ternary": { + if (!this.own) return c; + const t = this.temp(); + const test = this.cond(c.cond, pre, where); + const side = (x: BoolExpr): Stmt[] => { + const inner: Stmt[] = []; + const value = this.cond(x, inner, where); + return [...inner, { kind: "assignBool", target: t, value, ...where }]; + }; + pre.push({ kind: "if", cond: test, then: side(c.whenTrue), else: side(c.whenFalse), ...where }); + return { kind: "var", id: t }; + } case "call": { if (!this.own || !c.call.result) return c; this.call(c.call, pre, where); @@ -4374,6 +4660,7 @@ class Rewriter { const value = this.cond(s.value, pre, where); return pre.length ? keep([...pre, { ...s, value }]) : [this.own ? { ...s, value } : s]; } + case "action": return this.own && (s.variables?.length || s.text?.kind === "template") ? this.action(s, where) : [s]; default: return [s]; } } diff --git a/compiler/backend/graph.ts b/compiler/backend/graph.ts index 7af4251..9145050 100644 --- a/compiler/backend/graph.ts +++ b/compiler/backend/graph.ts @@ -10,6 +10,8 @@ * where every value lives by writing such fields, which is the point of having one. */ import type { At, Stmt } from "../ir"; +import type { ScriptString } from "../runtime"; +import type { ActionRecord, ConditionRecord } from "../../vendor/triggers"; /** A dword the graph reads or writes. */ export type Dword = @@ -57,7 +59,18 @@ export interface FixedFn { fn: "fmul_vars" | "fdiv_vars" | "fmul_by" | "fdiv_by" * destination or modifier, the mask of a masked one (a condition's or an action's, which it reads as it runs: what the * mask is can be written like any number), the next pointer. */ -export type Field = { cond: number; part?: "value" | "player" | "mask" } | { act: number; part: "value" | "dest" | "modifier" | "mask" } | "next"; +/** + * (An action of the game's, `Act.record`, names its other fields by these too: its location is where the mask is, its + * first player the destination, its second player or amount the number, its unit type, kind and count the modifier's + * dword; `text`, `wav` and `time` are its own.) + */ +/** + * `current`: the dword whose top byte is the trigger's current action (where a trigger held by a Wait or a + * Transmission goes on from); an action's `flags` dword has its flags in the low byte (bit 0: a Wait or a Transmission + * started, which the game steps over the next time it is reached). + */ +export type Field = { cond: number; part?: "value" | "player" | "mask" } | { act: number; part: ActPart } | "next" | "current"; +export type ActPart = "value" | "dest" | "modifier" | "mask" | "text" | "wav" | "time" | "flags"; export type Compare = "atLeast" | "atMost" | "exactly"; export type Modifier = "set" | "add" | "subtract"; @@ -74,8 +87,12 @@ export type Num = number | { epdOf: string; plus: number } | { addrOf: string; p */ export type Value = Num | { label: string } | { epd: Dword }; -/** The dword compared with `value` (read from 0 up); with `mask`, the dword's bits of the mask (`value` as it is). */ -export interface Cond { dword: Dword; compare: Compare; value: number; mask?: number } +/** + * The dword compared with `value` (read from 0 up); with `mask`, the dword's bits of the mask (`value` as it is). + * `game`: instead, a condition of the game's as the script wrote it (a map of our own: TrigScript 4, step 5 of the way + * off eudplib) — its record laid out as it is, the dword not read. + */ +export interface Cond { dword: Dword; compare: Compare; value: number; mask?: number; game?: ConditionRecord } /** * `value` written into the dword, added to it or taken from it; with `mask`, only the mask's bits change. An add wraps; @@ -93,7 +110,13 @@ export interface Cond { dword: Dword; compare: Compare; value: number; mask?: nu * game's own random numbers, the same on every player's machine. A map of our own reads the switch where the game keeps * it (`SWITCHES`). */ -export interface Act { dword: Dword; modifier: Modifier; value: Value; mask?: number; at?: At; display?: string; show?: string; randomize?: number } +/** + * `record`: instead of a write, an action of the game's as the script wrote it (a map of our own), laid out as it is but + * for its text and its sound file, which are the script's strings (`strings`: a record's `text` / `wav` of `k > 0` is the + * script's `k`-th) and are put in the map's strings when the map is made. + */ +/** `textOf`: an action of the game's whose text is a printed text's template (`Graph.templates`), filled before it. */ +export interface Act { dword: Dword; modifier: Modifier; value: Value; mask?: number; at?: At; display?: string; show?: string; randomize?: number; record?: ActionRecord; strings?: { text?: ScriptString; wav?: ScriptString }; textOf?: string } /** Where the game keeps its 256 switches: a bit each, 32 to a dword. */ export const SWITCHES = 0x58dc40; diff --git a/compiler/backend/layout.ts b/compiler/backend/layout.ts index 60f1880..9215be4 100644 --- a/compiler/backend/layout.ts +++ b/compiler/backend/layout.ts @@ -17,6 +17,7 @@ */ import { END, TRIGGER_BYTES, type Act, type Cond, type Dword, type Graph, type Value } from "./graph"; import { GAME_TICK, WAKE_AT, WAKE_NEXT, WAKE_TICK } from "./emit"; +import type { ScriptString } from "../runtime"; export const STRINGS = 0x191943c8; const DEATHS = 0x58a364; @@ -53,7 +54,7 @@ export interface LayoutInput { strings: number; /** The program's player, 0-based. */ owner: number; - /** A text's index in the map's string table (1-based), for Display Text. */ + /** A text's index in the map's string table (1-based), for Display Text and an action's text or sound file. */ text: (t: string) => number; /** The cells of the lists the graph reads where they are, by array id. */ arrays: Map; @@ -188,8 +189,9 @@ export function layout(input: LayoutInput): OwnMap { if (t === undefined) throw new Error(`no trigger ${d.at}`); const f = d.field; if (f === "next") return t + 4; + if (f === "current") return t + 2404; if ("cond" in f) return t + 8 + 20 * f.cond + (f.part === "player" ? 4 : f.part === "mask" ? 0 : 8); - return t + 328 + 32 * f.act + { value: 20, dest: 16, modifier: 24, mask: 0 }[f.part]; + return t + 328 + 32 * f.act + { value: 20, dest: 16, modifier: 24, mask: 0, text: 4, wav: 8, time: 12, flags: 28 }[f.part]; }; const address = (d: Dword): number => { if ("game" in d) return d.game; @@ -240,8 +242,11 @@ export function layout(input: LayoutInput): OwnMap { // (Every address first: the cells are allocated as the triggers name them, so the size is known after.) const encoded = triggers.map((t) => ({ t, - conds: t.conds.map((c) => ({ c, player: epd(address(c.dword)) })), - acts: t.acts.map((a) => ({ a, player: a.display !== undefined || a.show !== undefined || a.randomize !== undefined ? 0 : epd(address(a.dword)), number: a.display !== undefined || a.show !== undefined || a.randomize !== undefined ? 0 : value(a.value) })), + conds: t.conds.map((c) => ({ c, player: c.game ? 0 : epd(address(c.dword)) })), + acts: t.acts.map((a) => { + const none = a.display !== undefined || a.show !== undefined || a.randomize !== undefined || a.record !== undefined; + return { a, player: none ? 0 : epd(address(a.dword)), number: none ? 0 : value(a.value) }; + }), })); const size = next - base; const out = new Uint8Array(pad + size); @@ -254,6 +259,17 @@ export function layout(input: LayoutInput): OwnMap { if (conds.length > 16 || acts.length > 64) throw new Error(`${t.label}: too many conditions or actions`); conds.forEach(({ c, player }, k) => { const o = n + 8 + 20 * k; + if (c.game) { + // A condition of the game's, as the script wrote it. + const r = c.game; + w32(o, r.location); + w32(o + 4, r.player); + w32(o + 8, r.amount); + dv.setUint16(pad + o + 12 - base, r.unitId, true); + for (const [at, b] of [[14, r.comparison], [15, r.type], [16, r.resource], [17, r.flags]]) dv.setUint8(pad + o + at - base, b); + dv.setUint16(pad + o + 18 - base, r.mask, true); + return; + } w32(o, c.mask ?? 0); w32(o + 4, player); w32(o + 8, c.value); @@ -263,6 +279,26 @@ export function layout(input: LayoutInput): OwnMap { }); acts.forEach(({ a, player, number }, k) => { const o = n + 328 + 32 * k; + if (a.record) { + // An action of the game's, as the script wrote it: its text and its sound file put among the map's strings. + const r = a.record; + const str = (s: ScriptString | undefined, k: number) => { + if (!k) return 0; + if (!s) throw new Error(`${t.label}: an action's string ${k} with no text given for it`); + return "index" in s ? s.index : input.text(s.text); + }; + w32(o, r.location); + if (a.textOf !== undefined && !input.template) throw new Error(`an action's text (${a.textOf}) with no string to show it by`); + w32(o + 4, a.textOf !== undefined ? input.template!(a.textOf) : str(a.strings?.text, r.text)); + w32(o + 8, str(a.strings?.wav, r.wav)); + w32(o + 12, r.time); + w32(o + 16, r.player); + w32(o + 20, r.target); + dv.setUint16(pad + o + 24 - base, r.unitId, true); + for (const [at, b] of [[26, r.type], [27, r.modifier], [28, r.flags], [29, r.padding]]) dv.setUint8(pad + o + at - base, b); + dv.setUint16(pad + o + 30 - base, r.mask, true); + return; + } if (a.randomize !== undefined) { // Set Switch (13), its switch in the record's number, Randomize (11). w32(o + 20, a.randomize); diff --git a/compiler/backend/own.ts b/compiler/backend/own.ts index b12dd29..22fa38b 100644 --- a/compiler/backend/own.ts +++ b/compiler/backend/own.ts @@ -8,7 +8,7 @@ import type { ArrayDecl, FuncDecl, Program, VarDecl } from "../ir"; import { emitBody } from "./emit"; import type { Graph } from "./graph"; -export function emitOwn(r: Pick): { program: Program; graph: Graph } | string { +export function emitOwn(r: Pick): { program: Program; graph: Graph } | string { if (r.ir.length !== 1) return `a map of our own has one program; this script has ${r.ir.length}`; const program = r.ir[0]; if (program.perPlayer) return "a map of our own has no per-player program yet"; @@ -29,5 +29,9 @@ export function emitOwn(r: Pick): { program: P const functions = new Map((program.functions ?? []).map((f) => [f.id, f])); const arrays = new Map((program.arrays ?? []).map((a) => [a.id, a])); const graph = emitBody(program.body, decls, "own", functions, arrays, undefined, program.owner); - return typeof graph === "string" ? graph : { program, graph }; + if (typeof graph === "string") return graph; + // (An action of the game's names its text and its sound file by the script's strings: given here, for the layout.) + const of = (k: number) => (k ? r.strings[k - 1] : undefined); + for (const t of graph.triggers) for (const a of t.acts) if (a.record && (a.record.text || a.record.wav)) a.strings = { text: of(a.record.text), wav: of(a.record.wav) }; + return { program, graph }; } diff --git a/docs/v4-plan.md b/docs/v4-plan.md index 7f43569..0c90d62 100644 --- a/docs/v4-plan.md +++ b/docs/v4-plan.md @@ -3529,8 +3529,11 @@ found what they left out; where a step moved, its old number is given. `bits`). Random numbers: `random()`, `randomInt`, seeded as `f_randomize` seeds them (the Randomize switch), each draw a product (`f_dwrand`). A fixed number printed. eudplib's `f_mul`, `f_div`, the bit functions and `f_dwrand`, each beaten. -5. **The game's conditions and actions** (new). A trigger record of any condition and any action in the graph and the - layout — they encode three today: the Memory condition, Set Deaths and Display Text. Fields filled from values +5. **The game's conditions and actions** (new). Part 1 ✓: every condition and action with its fields known when the map + is built, laid out as the game's record — "The game's conditions and actions on our own backend"; `memory()` and + `setMemory()` at an address known when built came with it (from 6). Part 2 ✓: fields from values and texts made + while the game runs — "Fields from values on our own backend". A trigger record of any condition and any action in the graph and the + layout — they encoded three before: the Memory condition, Set Deaths and Display Text. Fields filled from values (`action.variables`), action texts (transmissions, objectives, leaderboards). Every condition in a test and as a value (ore, deaths, score, kills, Bring, Command, the countdown; the Python side's bit search), the players' facts (race, slot, human, left) and supply; `rose` and `once`. A print to a group — each player in turn, as the Python @@ -3901,13 +3904,13 @@ lists what no script reaches. A step is done when its scripts say so. | Step | Taken today | | --- | ---: | -| 0 The start | 157 of 158 | +| 0 The start | 158 of 158 | | 1 Calls and recursion | 33 of 33 | | 2 Texts with numbers | 6 of 6 | | 3 Arrays of a fixed length | 54 of 54 | | 4 Numbers | 59 of 59 | -| 5 The game's conditions and actions | 0 of 193 | -| 6 The game's memory | 0 of 49 | +| 5 The game's conditions and actions | 154 of 195 | +| 6 The game's memory | 0 of 47 | | 7 Programs, owners and players | 1 of 22 | | 8 Texts as values | 0 of 43 | | 9 Arrays that grow, the heap | 0 of 28 | @@ -3916,8 +3919,10 @@ lists what no script reaches. A step is done when its scripts say so. | 12 Input | 0 of 31 | | 13 The map's own triggers | 0 of 13 | -One step called done is not quite: a `?:` of two booleans (step 0). (A cell read at a shifted index, `xs[a >> 1]`, the -other one, is taken since step 4's part 4.) Every script a map of our own takes was also run +(Step 5 has 195 rows since two of step 6's — `memory()` and `setMemory()` at an address known when built — came with +its part 1.) Every step called done is: a `?:` of two booleans (step 0), the last, is taken since step 5's part 1 — the +side its test picks worked out under an `if` (`Rewriter.cond`). (A cell read at a shifted index, `xs[a >> 1]`, the other +one, is taken since step 4's part 4.) Every script a map of our own takes was also run from its laid-out bytes against the simulator — the numbers, arrays, texts and language scripts, and about forty edge cases besides (signs of `>>`, `u32` printed and compared, wrap-around, reads past the ends, negative indexes): all printed the same. The README's reference to the language predates TrigScript 4 (it says `try` and `async` are missing): @@ -3968,8 +3973,13 @@ the ledger is the list it can be written again from at 4.0. - Not in a program, against JavaScript: `~a`, `**`, `Math.pow`, `Math.sign`, `Math.log2`, `fill(v, start, end)`, `reduce` with no first value. A `const HALF = 7 / 2` worked out when built is 3.5, a fixed number; the same `7 / 2` in the game is 3. -- A map of our own: `displayText("known text")` is refused where `print("known text")` is taken (step 5's first); - JSX inside a program is "made while the game runs" even when it holds nothing of the program's (step 8); +- A condition kept in a variable: `const both = command(…) && bring(…)` is "Variables hold numbers, booleans, … before + is Condition", where `switchIs(…) && !switchIs(…)` is taken as a boolean, and either in an `if` is taken + (found writing `probes/own-game.ts`, 2026-10-20). +- `command()` straight after `killUnit()` or `removeUnit()` still counts the units (the game counts them until the next + frame) where the simulator does not; and two Transmissions, the second while the first one's time runs, hold the + second until the program reaches it again (both builds; found writing `probes/own-fields.ts`, 2026-10-20). +- A map of our own: JSX inside a program is "made while the game runs" even when it holds nothing of the program's (step 8); (`Math.tan` and `Math.hypot`, refused as what they become, are taken since step 4's part 5; `a >> 0`, refused where `a >> 1` to `a >> 31` were taken, is the number itself since its part 2.) @@ -4182,6 +4192,104 @@ variables and arrays, as graphs and from the laid-out bytes (3,000 of 3,000 take simulator; `probes/own-math.ts` built both ways, every line ok — its edges: roots of 0, below zero, the largest, a whole number from 2^30 up; angles on the axes and of (0, 0); sines at a turn. +### The game's conditions and actions on our own backend (2026-10-20) + +Step 5, part 1: every condition and every action of the game's whose fields are known when the map is built — Bring, +Command, Accumulate, Switch, the clocks, Score, Opponents, Always and Never and the rest; Create Unit, Kill and Remove, +Give, Set Resources, Set Switch, the leaderboards, Transmission, Play WAV, Center View, Victory and the rest — taken by a +map of our own and laid out as the game's own record (`Cond.game`, `Act.record` in `compiler/backend/graph.ts`; the +layout writes the record as it is, the action's text and sound file put among the map's strings by `build-own`, and the +script's clips into the archive). `memory()` and `setMemory()` at an address known when built are a Deaths and a Set +Deaths whose player is the address's EPD: they came with it, from step 6. Wait is refused (it would hold the owner's +whole trigger list; `sleep` is the program's wait). Ledger: step 5 118 of 195 taken; the 77 left are fields from values +(34), the game read as a value (18), `centerLocation` (6), texts made or shown to others (7), the players' facts and +supply (7), Wait, `rose`, `once`. + +- **Turned around in one condition** (`gameConds`): `!` of at least n is at most n − 1 (at least 0: never), at most n + is at least n + 1, a switch set is cleared; Always and Never are known. Exactly, the Most and the Least have no + opposite in one condition: the trigger that asks them sends the walk past the goto. eudplib's `EUDNot` is a trigger + setting a flag and one testing it. +- **`&&` in one trigger.** A test whose every part fits in one trigger — up to 16 conditions — is one trigger that skips + past the goto, where it was a branch a part (`Emitter.jump`); `||` turned around the same. This is general, as is the + next: with the two, the numbers probes are 2–8% faster. +- **An `if` in one trigger** (`Emitter.guards`, `Proto.guarded`): no `else`, a body of actions of the game's and writes + that read nothing but their own target (`n++`, `n = 3`), conditions that fit in one trigger — that trigger is the + `if`: its conditions, then its actions, run when they hold. No branch and no put-back. The copies of a variable it + writes, when the plan copies it, come after it whether the conditions held or not — they write back what the places + hold when they did not. +- **Bring counted again.** Bring keeps the counts it found for a location until a query of another kind is asked: + after an action that makes or moves units (Create Unit, with properties, Move Unit, Give Units), a program that asks + Bring gets a trigger asking Command, as the Python side's `forget_counts`. + +| `probes/own-game.ts` (lifted game, Remastered's weights) | Own | eudplib | +| --- | ---: | ---: | +| The start and every check once (frame 2) | **0.003 ms** | 1.074 ms | +| 200 turns: Set Resources, `if` of 3 conditions → `n++`, Set Deaths (frame 4) | **0.020 ms** | 0.028 ms | +| 200 turns: `if (!deaths(…, "==", 5)) n++`, Set Deaths (frame 6) | **0.021 ms** | 0.038 ms | +| A frame with nothing to do | **0** | 0.004 ms | + +Frame 4 was 0.029 ms (a tie) before `&&` in one trigger and the `if` in one trigger; the 200 turns are four triggers +each now — the loop's test, Set Resources, the guarded `if`, Set Deaths with `j++`. What is left there is the loop's +own test at its head, which every loop pays: a loop tested at its foot would save a trigger a turn. + +Checked: `tests/ownGame.test.ts` (the records byte for byte, the conditions turned around, the `if` in one trigger, +Bring counted again); the emitter's random programs; every earlier `probes/own-*.ts` built and played again, every +line ok; `probes/own-game.ts` built both ways, the same lines at the same frames. + +### Fields from values on our own backend (2026-10-20) + +Step 5, part 2: an action of the game's whose fields come from values — a unit type, a count, an amount, a time, a +player or a location — and one whose text is made while the game runs (a Transmission, the objectives, a +leaderboard's label). Ledger: step 5 154 of 195; the 41 left are the game read as a value (19), `centerLocation` (6), +texts shown to other players than the owner (4) or at the centre (2), the players' facts and supply (7), Wait, +`rose`, `once`. + +- **A field from a value is a place of its variable** (`Emitter.action`; `actPlaces` takes every part of an action + now): the variable's writes keep it, so the action reads the field where it is and costs nothing more when it runs. + eudplib copies the variable into the field before every run. The unit type, the action's kind and its count share a + dword: the place's constant is what is not the value (the kind times 2^16, the count times 2^24). A value more than + one variable times a constant plus a constant goes into a variable of the emitter's own first (`Rewriter.action`); + the front end's `max(n, 0)` around an amount is dropped where the value ranges already know it (`settled`). +- **A unit count** is that many units, 0 or below none: the action done 255 at a time while more are left, then once + with the rest, each half only where the count's range reaches it. A type and a count both from values are one + variable of the emitter's own, the type plus the count times 2^24. A unit type is stopped at 228, as the Python + side's is. 258 Scourges in one place: ours makes about 253 (two Create Units, the game spreading each one's units), + eudplib's 50 to 100 (Create Unit of one, 258 times). +- **A text made while the game runs** fills a template of its own, as a print's does (`Emitter.template`, split out of + `print`), and the action's text field names the template's string (`Act.textOf`): one for each place in the script, + where the Python side keeps one string for each kind of field, so two Transmissions written apart share it. +- **The game's counts thrown away.** The game keeps one table of counts for Command and for Bring while the queries + stay of one kind, throwing it away at the start of a pass, at a query of the other kind and in the actions that kill + or remove units — not after Create Unit, Move Unit or Give Units. So `command()` after making units read the old + count, in both builds (the Python side's `forget_counts` covers Bring alone). Now, in a program that asks Command, + Bring or the Most/Least queries, such an action is followed in its own trigger by Kill Unit of Player 12's units of + type 228, which no unit is (played in the lifted game: both count again after it, and Player 12's minerals stay). + Part 1's Command trigger after it is gone. +- **A Transmission** holds the rest of its trigger until its time is over and marks itself started, so the game + steps over it the next time the trigger is reached: in a loop, every other Transmission never showed (eudplib's + too). Ours is the first action of a trigger of its own, and the trigger after it puts its flags and the trigger's + current action back (`Emitter.transmission`, the fields `flags` and `current`): each one shows. A Transmission while + another one's time runs is still held, as the game holds it — to undo that needs the players' wait timers, which no + probe has found yet. +- **Found on the way** (`probes/own-fields.ts`): the game counts a unit killed or removed until the next frame — a + `command()` straight after `killUnit()` still sees it, in both builds and the simulator does not; and the eleven text + lines of a frame: a line printed past them is lost to `testGame`. + +| `probes/own-fields.ts` (lifted game, Remastered's weights) | Own | eudplib | +| --- | ---: | ---: | +| The start and every check (frame 2) | **0.004 ms** | 1.074 ms | +| 200 Set Deaths adding a counter (frame 4) | **0.014 ms** | 0.021 ms | +| 258 Scourges made and removed (frame 6) | **0.001 ms** | 0.059 ms | +| A frame with nothing to do | **0** | 0.004 ms | + +The 200 adds are two triggers a turn: the loop's test, and the Set Deaths whose amount is a place of the counter, which +`j++` keeps. Lines C and D (a count of 3, 0 and −2; a type and a count, then a kill) and H (258 made) are WRONG in +eudplib's build: the old counts, and the Create Units of one. + +Checked: `tests/ownGame.test.ts` (the places and what shares their dword, the count's halves by its range, the +Transmission's puts-back, the template); `probes/own-fields.ts` and `probes/own-game.ts` built both ways; a loop of +four Transmissions in the lifted game (ours shows each, eudplib's every other). + ## Part C — the game, pushed Each is a library over Part B, offered only after its probe. "Known" means a played probe diff --git a/ledger/game/action-raw-record.ts b/ledger/game/action-raw-record.ts index 4efdbfa..50c4b5f 100644 --- a/ledger/game/action-raw-record.ts +++ b/ledger/game/action-raw-record.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Center View +// ledger: step 5, takes // An action by its raw type number and fields, `action(type, …)`: what StarEdit stores, for a type the // editor does not know (here 10, Center View). program(() => { diff --git a/ledger/game/center-view.ts b/ledger/game/center-view.ts index b0ea166..1bbee45 100644 --- a/ledger/game/center-view.ts +++ b/ledger/game/center-view.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Center View +// ledger: step 5, takes // The action Center View: the player's screen moved to a location. program(() => { centerView(locations.Anywhere); diff --git a/ledger/game/condition-accumulate.ts b/ledger/game/condition-accumulate.ts index 226b38e..1f1583c 100644 --- a/ledger/game/condition-accumulate.ts +++ b/ledger/game/condition-accumulate.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Accumulate +// ledger: step 5, takes // The condition Accumulate as a test in an `if`: a player has at least so many minerals. program(() => { if (accumulate(P1, ">=", 500, "ore")) print("yes"); diff --git a/ledger/game/condition-always.ts b/ledger/game/condition-always.ts index 8114848..8ff8de4 100644 --- a/ledger/game/condition-always.ts +++ b/ledger/game/condition-always.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Always +// ledger: step 5, takes // The condition Always as a test in an `if`. program(() => { if (always()) print("yes"); diff --git a/ledger/game/condition-bring.ts b/ledger/game/condition-bring.ts index 99d5ee8..4011794 100644 --- a/ledger/game/condition-bring.ts +++ b/ledger/game/condition-bring.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Bring +// ledger: step 5, takes // The condition Bring as a test in an `if`: a player has units of a type at a location. program(() => { if (bring(P1, units.TerranMarine, locations.Anywhere, ">=", 5)) print("yes"); diff --git a/ledger/game/condition-command-the-least-at.ts b/ledger/game/condition-command-the-least-at.ts index 9fa929e..a48da5c 100644 --- a/ledger/game/condition-command-the-least-at.ts +++ b/ledger/game/condition-command-the-least-at.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Command the Least At +// ledger: step 5, takes // The condition Command the Least At as a test in an `if`: the fewest units of a type at a location. program(() => { if (commandTheLeastAt(units.TerranMarine, locations.Anywhere)) print("yes"); diff --git a/ledger/game/condition-command-the-least.ts b/ledger/game/condition-command-the-least.ts index 523e29f..de7e1a5 100644 --- a/ledger/game/condition-command-the-least.ts +++ b/ledger/game/condition-command-the-least.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Command the Least +// ledger: step 5, takes // The condition Command the Least as a test in an `if`: the current player has the fewest units of a type. program(() => { if (commandTheLeast(units.TerranMarine)) print("yes"); diff --git a/ledger/game/condition-command-the-most-at.ts b/ledger/game/condition-command-the-most-at.ts index e516e21..3944bb1 100644 --- a/ledger/game/condition-command-the-most-at.ts +++ b/ledger/game/condition-command-the-most-at.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Command the Most At +// ledger: step 5, takes // The condition Command the Most At as a test in an `if`: the most units of a type at a location. program(() => { if (commandTheMostAt(units.TerranMarine, locations.Anywhere)) print("yes"); diff --git a/ledger/game/condition-command-the-most.ts b/ledger/game/condition-command-the-most.ts index 11933b6..99ff6dd 100644 --- a/ledger/game/condition-command-the-most.ts +++ b/ledger/game/condition-command-the-most.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Command the Most +// ledger: step 5, takes // The condition Command the Most as a test in an `if`: the current player has the most units of a type. program(() => { if (commandTheMost(units.TerranMarine)) print("yes"); diff --git a/ledger/game/condition-command.ts b/ledger/game/condition-command.ts index 0df012e..e4489c2 100644 --- a/ledger/game/condition-command.ts +++ b/ledger/game/condition-command.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Command +// ledger: step 5, takes // The condition Command as a test in an `if`: a player has so many units of a type anywhere. program(() => { if (command(P1, units.TerranMarine, ">=", 5)) print("yes"); diff --git a/ledger/game/condition-countdown-timer.ts b/ledger/game/condition-countdown-timer.ts index 208e951..f0a83b3 100644 --- a/ledger/game/condition-countdown-timer.ts +++ b/ledger/game/condition-countdown-timer.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Countdown Timer +// ledger: step 5, takes // The condition Countdown Timer as a test in an `if`: the timer is at most so many seconds. program(() => { if (countdownTimer("<=", 10)) print("yes"); diff --git a/ledger/game/condition-deaths.ts b/ledger/game/condition-deaths.ts index 7536ad8..79f22d0 100644 --- a/ledger/game/condition-deaths.ts +++ b/ledger/game/condition-deaths.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Deaths +// ledger: step 5, takes // The condition Deaths as a test in an `if`: the counter a map keeps in the death table. program(() => { if (deaths(P1, units.TerranMarine, ">=", 3)) print("yes"); diff --git a/ledger/game/condition-elapsed-time.ts b/ledger/game/condition-elapsed-time.ts index ed84806..8438afb 100644 --- a/ledger/game/condition-elapsed-time.ts +++ b/ledger/game/condition-elapsed-time.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Elapsed Time +// ledger: step 5, takes // The condition Elapsed Time as a test in an `if`: so many game seconds have gone by. program(() => { if (elapsedTime(">=", 60)) print("yes"); diff --git a/ledger/game/condition-highest-score.ts b/ledger/game/condition-highest-score.ts index a226cc5..2063f84 100644 --- a/ledger/game/condition-highest-score.ts +++ b/ledger/game/condition-highest-score.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Highest Score +// ledger: step 5, takes // The condition Highest Score as a test in an `if`: the current player has the highest score of a kind. program(() => { if (highestScore("kills")) print("yes"); diff --git a/ledger/game/condition-in-a-while.ts b/ledger/game/condition-in-a-while.ts index e7b49d0..d466b32 100644 --- a/ledger/game/condition-in-a-while.ts +++ b/ledger/game/condition-in-a-while.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Switch +// ledger: step 5, takes // A condition as the test of a `while`: the loop goes on, a frame at a time, until the switch is set. program(() => { while (!switchIs(switches.Switch1, "set")) sleep(frames(1)); diff --git a/ledger/game/condition-kill.ts b/ledger/game/condition-kill.ts index 9813ab4..18ae8b8 100644 --- a/ledger/game/condition-kill.ts +++ b/ledger/game/condition-kill.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Kill +// ledger: step 5, takes // The condition Kill as a test in an `if`: a player has killed so many units of a type. program(() => { if (kill(P1, units.ZergZergling, ">=", 10)) print("yes"); diff --git a/ledger/game/condition-least-kills.ts b/ledger/game/condition-least-kills.ts index c3c0fd3..49dfbaf 100644 --- a/ledger/game/condition-least-kills.ts +++ b/ledger/game/condition-least-kills.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Least Kills +// ledger: step 5, takes // The condition Least Kills as a test in an `if`. program(() => { if (leastKills(units.ZergZergling)) print("yes"); diff --git a/ledger/game/condition-least-resources.ts b/ledger/game/condition-least-resources.ts index 73d26b2..72d547c 100644 --- a/ledger/game/condition-least-resources.ts +++ b/ledger/game/condition-least-resources.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Least Resources +// ledger: step 5, takes // The condition Least Resources as a test in an `if`. program(() => { if (leastResources("ore")) print("yes"); diff --git a/ledger/game/condition-lowest-score.ts b/ledger/game/condition-lowest-score.ts index be20015..aef61a3 100644 --- a/ledger/game/condition-lowest-score.ts +++ b/ledger/game/condition-lowest-score.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Lowest Score +// ledger: step 5, takes // The condition Lowest Score as a test in an `if`. program(() => { if (lowestScore("total")) print("yes"); diff --git a/ledger/game/condition-most-kills.ts b/ledger/game/condition-most-kills.ts index 8e833a8..c058c89 100644 --- a/ledger/game/condition-most-kills.ts +++ b/ledger/game/condition-most-kills.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Most Kills +// ledger: step 5, takes // The condition Most Kills as a test in an `if`. program(() => { if (mostKills(units.ZergZergling)) print("yes"); diff --git a/ledger/game/condition-most-resources.ts b/ledger/game/condition-most-resources.ts index 83c5f60..227a00e 100644 --- a/ledger/game/condition-most-resources.ts +++ b/ledger/game/condition-most-resources.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Most Resources +// ledger: step 5, takes // The condition Most Resources as a test in an `if`. program(() => { if (mostResources("oreAndGas")) print("yes"); diff --git a/ledger/game/condition-negated.ts b/ledger/game/condition-negated.ts index 94b735d..2dfb8a6 100644 --- a/ledger/game/condition-negated.ts +++ b/ledger/game/condition-negated.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Bring +// ledger: step 5, takes // A condition turned around with `!` in an `if`: `if (!bring(…))`, which works for every condition. program(() => { if (!bring(P1, units.AnyUnit, locations.Anywhere, ">=", 1)) print("yes"); diff --git a/ledger/game/condition-never.ts b/ledger/game/condition-never.ts index 117f4af..bc33d39 100644 --- a/ledger/game/condition-never.ts +++ b/ledger/game/condition-never.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Never +// ledger: step 5, takes // The condition Never as a test in an `if`. program(() => { if (never()) print("yes"); diff --git a/ledger/game/condition-opponents.ts b/ledger/game/condition-opponents.ts index 1208abb..90ff6ab 100644 --- a/ledger/game/condition-opponents.ts +++ b/ledger/game/condition-opponents.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Opponents +// ledger: step 5, takes // The condition Opponents as a test in an `if`: how many opponents a player has left. program(() => { if (opponents(P1, "==", 0)) print("yes"); diff --git a/ledger/game/condition-raw-record.ts b/ledger/game/condition-raw-record.ts index d9ea21b..a5793ca 100644 --- a/ledger/game/condition-raw-record.ts +++ b/ledger/game/condition-raw-record.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Deaths +// ledger: step 5, takes // A condition by its raw type number and fields, `condition(type, …)`: what StarEdit stores, for a type the // editor does not know (here 15, Deaths). program(() => { diff --git a/ledger/game/condition-score.ts b/ledger/game/condition-score.ts index d2f76b8..b68eb63 100644 --- a/ledger/game/condition-score.ts +++ b/ledger/game/condition-score.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Score +// ledger: step 5, takes // The condition Score as a test in an `if`: a player has so many points of a kind. program(() => { if (score(P1, "kills", ">=", 1000)) print("yes"); diff --git a/ledger/game/condition-switch.ts b/ledger/game/condition-switch.ts index 2c44be8..668a1de 100644 --- a/ledger/game/condition-switch.ts +++ b/ledger/game/condition-switch.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Switch +// ledger: step 5, takes // The condition Switch as a test in an `if`: one of the map's switches (`switches.*`) is set. program(() => { if (switchIs(switches.Switch1, "set")) print("yes"); diff --git a/ledger/game/create-unit-count-from-a-value.ts b/ledger/game/create-unit-count-from-a-value.ts index 6d39c64..f2b6c3e 100644 --- a/ledger/game/create-unit-count-from-a-value.ts +++ b/ledger/game/create-unit-count-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Create Unit, its fields from values +// ledger: step 5, takes // The action Create Unit, how many from a value of the program. program(() => { let n = 2; diff --git a/ledger/game/create-unit-type-from-a-value.ts b/ledger/game/create-unit-type-from-a-value.ts index be7f763..0083cd0 100644 --- a/ledger/game/create-unit-type-from-a-value.ts +++ b/ledger/game/create-unit-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Create Unit, its fields from values +// ledger: step 5, takes // The action Create Unit, the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/create-unit-with-properties-count-from-a-value.ts b/ledger/game/create-unit-with-properties-count-from-a-value.ts index 488e691..95da2f4 100644 --- a/ledger/game/create-unit-with-properties-count-from-a-value.ts +++ b/ledger/game/create-unit-with-properties-count-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Create Unit with Properties, its fields from values +// ledger: step 5, takes // The action Create Unit with Properties, how many from a value of the program. program(() => { let n = 1; diff --git a/ledger/game/create-unit-with-properties-type-from-a-value.ts b/ledger/game/create-unit-with-properties-type-from-a-value.ts index 0b6a031..17bf543 100644 --- a/ledger/game/create-unit-with-properties-type-from-a-value.ts +++ b/ledger/game/create-unit-with-properties-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Create Unit with Properties, its fields from values +// ledger: step 5, takes // The action Create Unit with Properties, the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/create-unit-with-properties.ts b/ledger/game/create-unit-with-properties.ts index 2346523..f1ec8cb 100644 --- a/ledger/game/create-unit-with-properties.ts +++ b/ledger/game/create-unit-with-properties.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Create Unit with Properties +// ledger: step 5, takes // The action Create Unit with Properties, all its fields known when the map is built: units made with // one of the map's unit property sets (hit points, energy, …). program(() => { diff --git a/ledger/game/create-unit.ts b/ledger/game/create-unit.ts index 8f9fd83..d3d4520 100644 --- a/ledger/game/create-unit.ts +++ b/ledger/game/create-unit.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Create Unit +// ledger: step 5, takes // The action Create Unit, all its fields known when the map is built. program(() => { createUnit(P1, units.TerranMarine, 1, locations.Anywhere); diff --git a/ledger/game/defeat.ts b/ledger/game/defeat.ts index 7071811..a342f9f 100644 --- a/ledger/game/defeat.ts +++ b/ledger/game/defeat.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Defeat +// ledger: step 5, takes // The action Defeat: the player loses the game. program(() => { defeat(); diff --git a/ledger/game/display-text.ts b/ledger/game/display-text.ts index 3f43e97..7f5663d 100644 --- a/ledger/game/display-text.ts +++ b/ledger/game/display-text.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Display Text Message +// ledger: step 5, takes // The action Display Text Message, its text written in the script: `displayText("…")`. program(() => { displayText("The gates are open."); diff --git a/ledger/game/draw.ts b/ledger/game/draw.ts index 5a0e973..3dc8464 100644 --- a/ledger/game/draw.ts +++ b/ledger/game/draw.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Draw +// ledger: step 5, takes // The action Draw: the game ends in a draw. program(() => { draw(); diff --git a/ledger/game/give-units-count-from-a-value.ts b/ledger/game/give-units-count-from-a-value.ts index 9cccc20..f86fa48 100644 --- a/ledger/game/give-units-count-from-a-value.ts +++ b/ledger/game/give-units-count-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Give Units to Player, its fields from values +// ledger: step 5, takes // The action Give Units to Player, how many from a value of the program. program(() => { let n = 1; diff --git a/ledger/game/give-units-type-from-a-value.ts b/ledger/game/give-units-type-from-a-value.ts index b143ed5..f4ba457 100644 --- a/ledger/game/give-units-type-from-a-value.ts +++ b/ledger/game/give-units-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Give Units to Player, its fields from values +// ledger: step 5, takes // The action Give Units to Player, the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/give-units.ts b/ledger/game/give-units.ts index 155cd33..5700687 100644 --- a/ledger/game/give-units.ts +++ b/ledger/game/give-units.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Give Units to Player +// ledger: step 5, takes // The action Give Units to Player, all its fields known when the map is built. program(() => { giveUnits(P1, P2, units.TerranMarine, 1, locations.Anywhere); diff --git a/ledger/game/kill-unit-at-count-from-a-value.ts b/ledger/game/kill-unit-at-count-from-a-value.ts index 6f66fbe..3c2d4bd 100644 --- a/ledger/game/kill-unit-at-count-from-a-value.ts +++ b/ledger/game/kill-unit-at-count-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Kill Unit At Location, its fields from values +// ledger: step 5, takes // The action Kill Unit at Location, how many from a value of the program. program(() => { let n = 1; diff --git a/ledger/game/kill-unit-at-type-from-a-value.ts b/ledger/game/kill-unit-at-type-from-a-value.ts index 441e7be..2e997ab 100644 --- a/ledger/game/kill-unit-at-type-from-a-value.ts +++ b/ledger/game/kill-unit-at-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Kill Unit At Location, its fields from values +// ledger: step 5, takes // The action Kill Unit at Location, the unit type from a value of the program. program(() => { let kind: UnitType = units.ZergZergling; diff --git a/ledger/game/kill-unit-at.ts b/ledger/game/kill-unit-at.ts index 4384372..aa95802 100644 --- a/ledger/game/kill-unit-at.ts +++ b/ledger/game/kill-unit-at.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Kill Unit At Location +// ledger: step 5, takes // The action Kill Unit at Location, all its fields known when the map is built. program(() => { killUnitAt(P2, units.ZergZergling, "All", locations.Anywhere); diff --git a/ledger/game/kill-unit-type-from-a-value.ts b/ledger/game/kill-unit-type-from-a-value.ts index 30af66f..9a1f8d0 100644 --- a/ledger/game/kill-unit-type-from-a-value.ts +++ b/ledger/game/kill-unit-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Kill Unit, its fields from values +// ledger: step 5, takes // The action Kill Unit, the unit type from a value of the program. program(() => { let kind: UnitType = units.ZergZergling; diff --git a/ledger/game/kill-unit.ts b/ledger/game/kill-unit.ts index d170aa7..fb067ee 100644 --- a/ledger/game/kill-unit.ts +++ b/ledger/game/kill-unit.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Kill Unit +// ledger: step 5, takes // The action Kill Unit: every unit of a type a player has dies. program(() => { killUnit(P2, units.ZergZergling); diff --git a/ledger/game/leaderboard-computer-players.ts b/ledger/game/leaderboard-computer-players.ts index bcd8ca4..905fc11 100644 --- a/ledger/game/leaderboard-computer-players.ts +++ b/ledger/game/leaderboard-computer-players.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leaderboard Computer Players +// ledger: step 5, takes // The action Leaderboard Computer Players: whether the computers show on the leader board. program(() => { leaderboardComputerPlayers("disable"); diff --git a/ledger/game/leaderboard-control-at-type-from-a-value.ts b/ledger/game/leaderboard-control-at-type-from-a-value.ts index 175aa89..0874751 100644 --- a/ledger/game/leaderboard-control-at-type-from-a-value.ts +++ b/ledger/game/leaderboard-control-at-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leader Board Control At Location, its fields from values +// ledger: step 5, takes // The action Leader Board (Control at Location), the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/leaderboard-control-at.ts b/ledger/game/leaderboard-control-at.ts index 5417c0d..229dfc7 100644 --- a/ledger/game/leaderboard-control-at.ts +++ b/ledger/game/leaderboard-control-at.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leader Board Control At Location +// ledger: step 5, takes // The action Leader Board (Control at Location): how many units of a type each player has at a location. program(() => { leaderboardControlAt("Marines on the hill", units.TerranMarine, locations.Anywhere); diff --git a/ledger/game/leaderboard-control-type-from-a-value.ts b/ledger/game/leaderboard-control-type-from-a-value.ts index ed0b0bf..12afea1 100644 --- a/ledger/game/leaderboard-control-type-from-a-value.ts +++ b/ledger/game/leaderboard-control-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leader Board Control, its fields from values +// ledger: step 5, takes // The action Leader Board (Control), the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/leaderboard-control.ts b/ledger/game/leaderboard-control.ts index 4401730..574f7e6 100644 --- a/ledger/game/leaderboard-control.ts +++ b/ledger/game/leaderboard-control.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leader Board Control +// ledger: step 5, takes // The action Leader Board (Control): how many units of a type each player has, under a label. program(() => { leaderboardControl("Marines", units.TerranMarine); diff --git a/ledger/game/leaderboard-goal-control-at-type-from-a-value.ts b/ledger/game/leaderboard-goal-control-at-type-from-a-value.ts index 3bade63..a36f9a3 100644 --- a/ledger/game/leaderboard-goal-control-at-type-from-a-value.ts +++ b/ledger/game/leaderboard-goal-control-at-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leaderboard Goal Control At Location, its fields from values +// ledger: step 5, takes // The action Leader Board Goal (Control at Location), the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/leaderboard-goal-control-at.ts b/ledger/game/leaderboard-goal-control-at.ts index be893b6..931ff18 100644 --- a/ledger/game/leaderboard-goal-control-at.ts +++ b/ledger/game/leaderboard-goal-control-at.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leaderboard Goal Control At Location +// ledger: step 5, takes // The action Leader Board Goal (Control at Location): units of a type at a location, against a goal. program(() => { leaderboardGoalControlAt("Marines on the hill", units.TerranMarine, 10, locations.Anywhere); diff --git a/ledger/game/leaderboard-goal-control-type-from-a-value.ts b/ledger/game/leaderboard-goal-control-type-from-a-value.ts index 4157661..ad06368 100644 --- a/ledger/game/leaderboard-goal-control-type-from-a-value.ts +++ b/ledger/game/leaderboard-goal-control-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leaderboard Goal Control, its fields from values +// ledger: step 5, takes // The action Leader Board Goal (Control), the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/leaderboard-goal-control.ts b/ledger/game/leaderboard-goal-control.ts index f1b98a0..6339194 100644 --- a/ledger/game/leaderboard-goal-control.ts +++ b/ledger/game/leaderboard-goal-control.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leaderboard Goal Control +// ledger: step 5, takes // The action Leader Board Goal (Control): units of a type each player has, against a goal. program(() => { leaderboardGoalControl("Marines to 20", units.TerranMarine, 20); diff --git a/ledger/game/leaderboard-goal-kills-type-from-a-value.ts b/ledger/game/leaderboard-goal-kills-type-from-a-value.ts index 75fb080..c604316 100644 --- a/ledger/game/leaderboard-goal-kills-type-from-a-value.ts +++ b/ledger/game/leaderboard-goal-kills-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leaderboard Goal Kills, its fields from values +// ledger: step 5, takes // The action Leader Board Goal (Kills), the unit type from a value of the program. program(() => { let kind: UnitType = units.ZergZergling; diff --git a/ledger/game/leaderboard-goal-kills.ts b/ledger/game/leaderboard-goal-kills.ts index 409dfe9..9e90f59 100644 --- a/ledger/game/leaderboard-goal-kills.ts +++ b/ledger/game/leaderboard-goal-kills.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leaderboard Goal Kills +// ledger: step 5, takes // The action Leader Board Goal (Kills): units of a type each player has killed, against a goal. program(() => { leaderboardGoalKills("Kill 50 Zerglings", units.ZergZergling, 50); diff --git a/ledger/game/leaderboard-goal-points.ts b/ledger/game/leaderboard-goal-points.ts index 230a361..e9f3972 100644 --- a/ledger/game/leaderboard-goal-points.ts +++ b/ledger/game/leaderboard-goal-points.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leaderboard Goal Points +// ledger: step 5, takes // The action Leader Board Goal (Points): a score of each player, against a goal. program(() => { leaderboardGoalPoints("Kills to 1000", "kills", 1000); diff --git a/ledger/game/leaderboard-goal-resources.ts b/ledger/game/leaderboard-goal-resources.ts index 19d7c1e..114a25a 100644 --- a/ledger/game/leaderboard-goal-resources.ts +++ b/ledger/game/leaderboard-goal-resources.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leaderboard Goal Resources +// ledger: step 5, takes // The action Leader Board Goal (Resources): each player's resources, against a goal. program(() => { leaderboardGoalResources("Gas to 800", 800, "gas"); diff --git a/ledger/game/leaderboard-greed.ts b/ledger/game/leaderboard-greed.ts index f329807..dee1661 100644 --- a/ledger/game/leaderboard-greed.ts +++ b/ledger/game/leaderboard-greed.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leader Board Greed +// ledger: step 5, takes // The action Leader Board Greed: who first gathers so many minerals and gas. program(() => { leaderboardGreed(5000); diff --git a/ledger/game/leaderboard-kills-type-from-a-value.ts b/ledger/game/leaderboard-kills-type-from-a-value.ts index dff43bd..7605ee9 100644 --- a/ledger/game/leaderboard-kills-type-from-a-value.ts +++ b/ledger/game/leaderboard-kills-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leader Board Kills, its fields from values +// ledger: step 5, takes // The action Leader Board (Kills), the unit type from a value of the program. program(() => { let kind: UnitType = units.ZergZergling; diff --git a/ledger/game/leaderboard-kills.ts b/ledger/game/leaderboard-kills.ts index 7f5da8e..c2b3a84 100644 --- a/ledger/game/leaderboard-kills.ts +++ b/ledger/game/leaderboard-kills.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leader Board Kills +// ledger: step 5, takes // The action Leader Board (Kills): how many units of a type each player has killed. program(() => { leaderboardKills("Zerglings killed", units.ZergZergling); diff --git a/ledger/game/leaderboard-label-from-the-program.ts b/ledger/game/leaderboard-label-from-the-program.ts index 963cf3e..a5fbdba 100644 --- a/ledger/game/leaderboard-label-from-the-program.ts +++ b/ledger/game/leaderboard-label-from-the-program.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leader Board Control, its text the program's +// ledger: step 5, takes // A leader board's label made while the game runs: a number of the program in it. program(() => { let goal = 10; diff --git a/ledger/game/leaderboard-points.ts b/ledger/game/leaderboard-points.ts index cbe13d5..7e90236 100644 --- a/ledger/game/leaderboard-points.ts +++ b/ledger/game/leaderboard-points.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leader Board Points +// ledger: step 5, takes // The action Leader Board (Points): a score of each player. program(() => { leaderboardPoints("Score", "total"); diff --git a/ledger/game/leaderboard-resources.ts b/ledger/game/leaderboard-resources.ts index 98198eb..4861518 100644 --- a/ledger/game/leaderboard-resources.ts +++ b/ledger/game/leaderboard-resources.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Leader Board Resources +// ledger: step 5, takes // The action Leader Board (Resources): each player's minerals, gas or both. program(() => { leaderboardResources("Minerals", "ore"); diff --git a/ledger/game/minimap-ping.ts b/ledger/game/minimap-ping.ts index 78ee02e..07bdc2a 100644 --- a/ledger/game/minimap-ping.ts +++ b/ledger/game/minimap-ping.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Minimap Ping +// ledger: step 5, takes // The action Minimap Ping: a flash on the minimap at a location. program(() => { minimapPing(locations.Anywhere); diff --git a/ledger/game/modify-energy-type-from-a-value.ts b/ledger/game/modify-energy-type-from-a-value.ts index 065f201..6c27c96 100644 --- a/ledger/game/modify-energy-type-from-a-value.ts +++ b/ledger/game/modify-energy-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Modify Unit Energy, its fields from values +// ledger: step 5, takes // The action Modify Unit Energy, the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranGhost; diff --git a/ledger/game/modify-energy.ts b/ledger/game/modify-energy.ts index 81c13e5..48a3463 100644 --- a/ledger/game/modify-energy.ts +++ b/ledger/game/modify-energy.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Modify Unit Energy +// ledger: step 5, takes // The action Modify Unit Energy: units at a location get a percentage of their energy. program(() => { modifyEnergy(P1, units.TerranGhost, 100, "All", locations.Anywhere); diff --git a/ledger/game/modify-hangar-count-type-from-a-value.ts b/ledger/game/modify-hangar-count-type-from-a-value.ts index c3596b8..c0652db 100644 --- a/ledger/game/modify-hangar-count-type-from-a-value.ts +++ b/ledger/game/modify-hangar-count-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Modify Unit Hanger Count, its fields from values +// ledger: step 5, takes // The action Modify Unit Hanger Count, the unit type from a value of the program. program(() => { let kind: UnitType = units.ProtossCarrier; diff --git a/ledger/game/modify-hangar-count.ts b/ledger/game/modify-hangar-count.ts index 3f68335..dafafe6 100644 --- a/ledger/game/modify-hangar-count.ts +++ b/ledger/game/modify-hangar-count.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Modify Unit Hanger Count +// ledger: step 5, takes // The action Modify Unit Hanger Count: Interceptors or Scarabs added to the units at a location. program(() => { modifyHangarCount(P1, units.ProtossCarrier, 4, "All", locations.Anywhere); diff --git a/ledger/game/modify-hit-points-type-from-a-value.ts b/ledger/game/modify-hit-points-type-from-a-value.ts index 64ba22c..6ba50da 100644 --- a/ledger/game/modify-hit-points-type-from-a-value.ts +++ b/ledger/game/modify-hit-points-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Modify Unit Hit Points, its fields from values +// ledger: step 5, takes // The action Modify Unit Hit Points, the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/modify-hit-points.ts b/ledger/game/modify-hit-points.ts index 085e767..16b61d1 100644 --- a/ledger/game/modify-hit-points.ts +++ b/ledger/game/modify-hit-points.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Modify Unit Hit Points +// ledger: step 5, takes // The action Modify Unit Hit Points: units at a location get a percentage of their hit points. program(() => { modifyHitPoints(P2, units.ZergZergling, 50, "All", locations.Anywhere); diff --git a/ledger/game/modify-resource-amount.ts b/ledger/game/modify-resource-amount.ts index a0989ae..46103a4 100644 --- a/ledger/game/modify-resource-amount.ts +++ b/ledger/game/modify-resource-amount.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Modify Unit Resource Amount +// ledger: step 5, takes // The action Modify Unit Resource Amount: what the mineral fields and geysers at a location hold. program(() => { modifyResourceAmount(players.Neutral, 500, "All", locations.Anywhere); diff --git a/ledger/game/modify-shields-type-from-a-value.ts b/ledger/game/modify-shields-type-from-a-value.ts index d9da344..c071041 100644 --- a/ledger/game/modify-shields-type-from-a-value.ts +++ b/ledger/game/modify-shields-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Modify Unit Shield Points, its fields from values +// ledger: step 5, takes // The action Modify Unit Shield Points, the unit type from a value of the program. program(() => { let kind: UnitType = units.ProtossZealot; diff --git a/ledger/game/modify-shields.ts b/ledger/game/modify-shields.ts index 84ab4a0..f15b142 100644 --- a/ledger/game/modify-shields.ts +++ b/ledger/game/modify-shields.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Modify Unit Shield Points +// ledger: step 5, takes // The action Modify Unit Shield Points: units at a location get a percentage of their shields. program(() => { modifyShields(P1, units.ProtossZealot, 100, "All", locations.Anywhere); diff --git a/ledger/game/move-location-type-from-a-value.ts b/ledger/game/move-location-type-from-a-value.ts index 0f2188b..70e644d 100644 --- a/ledger/game/move-location-type-from-a-value.ts +++ b/ledger/game/move-location-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Move Location, its fields from values +// ledger: step 5, takes // The action Move Location, the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/move-location.ts b/ledger/game/move-location.ts index bce13ac..fba7095 100644 --- a/ledger/game/move-location.ts +++ b/ledger/game/move-location.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Move Location +// ledger: step 5, takes // The action Move Location: a location centred on a unit of a type at another location. program(() => { moveLocation(P1, units.TerranMarine, locations.Anywhere, locations.Anywhere); diff --git a/ledger/game/move-unit-type-from-a-value.ts b/ledger/game/move-unit-type-from-a-value.ts index ce36558..cfdc765 100644 --- a/ledger/game/move-unit-type-from-a-value.ts +++ b/ledger/game/move-unit-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Move Unit, its fields from values +// ledger: step 5, takes // The action Move Unit, the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/move-unit.ts b/ledger/game/move-unit.ts index 2620546..69fecf6 100644 --- a/ledger/game/move-unit.ts +++ b/ledger/game/move-unit.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Move Unit +// ledger: step 5, takes // The action Move Unit: units taken from one location to another at once. program(() => { moveUnit(P1, units.TerranMarine, 4, locations.Anywhere, locations.Anywhere); diff --git a/ledger/game/mute-unit-speech.ts b/ledger/game/mute-unit-speech.ts index 48e10c4..fde5351 100644 --- a/ledger/game/mute-unit-speech.ts +++ b/ledger/game/mute-unit-speech.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Mute Unit Speech +// ledger: step 5, takes // The action Mute Unit Speech: the units' voices quiet, for a cinematic. program(() => { muteUnitSpeech(); diff --git a/ledger/game/not-of-a-condition.ts b/ledger/game/not-of-a-condition.ts index fb89937..2368b71 100644 --- a/ledger/game/not-of-a-condition.ts +++ b/ledger/game/not-of-a-condition.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Bring +// ledger: step 5, takes // A condition turned around by `not(…)`, worked out when the map is built: a program tests the // flipped condition ("at least 1" becomes "at most 0"). program(() => { diff --git a/ledger/game/order.ts b/ledger/game/order.ts index 3bd056b..0ccbf30 100644 --- a/ledger/game/order.ts +++ b/ledger/game/order.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Order +// ledger: step 5, takes // The action Order: units at a location sent to another, to move, patrol or attack — "attack" or orders.AttackMove, // one of the three orders the action gives (any other of orders.* is a compile error). program(() => { diff --git a/ledger/game/pause-game.ts b/ledger/game/pause-game.ts index 0639bfa..78bb98e 100644 --- a/ledger/game/pause-game.ts +++ b/ledger/game/pause-game.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Pause Game +// ledger: step 5, takes // The action Pause Game (single player only). program(() => { pauseGame(); diff --git a/ledger/game/pause-timer.ts b/ledger/game/pause-timer.ts index a312727..97a4a7f 100644 --- a/ledger/game/pause-timer.ts +++ b/ledger/game/pause-timer.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Pause Timer +// ledger: step 5, takes // The action Pause Timer: the countdown timer stops. program(() => { pauseTimer(); diff --git a/ledger/game/remove-unit-at-count-from-a-value.ts b/ledger/game/remove-unit-at-count-from-a-value.ts index 54a102b..5e30cd9 100644 --- a/ledger/game/remove-unit-at-count-from-a-value.ts +++ b/ledger/game/remove-unit-at-count-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Remove Unit At Location, its fields from values +// ledger: step 5, takes // The action Remove Unit at Location, how many from a value of the program. program(() => { let n = 1; diff --git a/ledger/game/remove-unit-at-type-from-a-value.ts b/ledger/game/remove-unit-at-type-from-a-value.ts index 1c0bc52..21ebb09 100644 --- a/ledger/game/remove-unit-at-type-from-a-value.ts +++ b/ledger/game/remove-unit-at-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Remove Unit At Location, its fields from values +// ledger: step 5, takes // The action Remove Unit at Location, the unit type from a value of the program. program(() => { let kind: UnitType = units.ZergZergling; diff --git a/ledger/game/remove-unit-at.ts b/ledger/game/remove-unit-at.ts index 2f558db..b1631aa 100644 --- a/ledger/game/remove-unit-at.ts +++ b/ledger/game/remove-unit-at.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Remove Unit At Location +// ledger: step 5, takes // The action Remove Unit at Location, all its fields known when the map is built. program(() => { removeUnitAt(P2, units.ZergZergling, 2, locations.Anywhere); diff --git a/ledger/game/remove-unit-type-from-a-value.ts b/ledger/game/remove-unit-type-from-a-value.ts index 48f8364..536214a 100644 --- a/ledger/game/remove-unit-type-from-a-value.ts +++ b/ledger/game/remove-unit-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Remove Unit, its fields from values +// ledger: step 5, takes // The action Remove Unit, the unit type from a value of the program. program(() => { let kind: UnitType = units.ZergZergling; diff --git a/ledger/game/remove-unit.ts b/ledger/game/remove-unit.ts index 332dc31..ca74e16 100644 --- a/ledger/game/remove-unit.ts +++ b/ledger/game/remove-unit.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Remove Unit +// ledger: step 5, takes // The action Remove Unit: every unit of a type a player has taken off the map, without a death. program(() => { removeUnit(P2, units.ZergZergling); diff --git a/ledger/game/run-ai-script-at.ts b/ledger/game/run-ai-script-at.ts index ef211aa..0604000 100644 --- a/ledger/game/run-ai-script-at.ts +++ b/ledger/game/run-ai-script-at.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Run AI Script At Location +// ledger: step 5, takes // The action Run AI Script at Location. program(() => { runAiScriptAt(aiScripts.ZergCustomLevel, locations.Anywhere); diff --git a/ledger/game/run-ai-script.ts b/ledger/game/run-ai-script.ts index d908645..9656eab 100644 --- a/ledger/game/run-ai-script.ts +++ b/ledger/game/run-ai-script.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Run AI Script +// ledger: step 5, takes // The action Run AI Script: one of the game's AI scripts, by name (`aiScripts.*`). program(() => { runAiScript(aiScripts.TerranCustomLevel); diff --git a/ledger/game/set-alliance-status.ts b/ledger/game/set-alliance-status.ts index ba56a4b..0b6c14e 100644 --- a/ledger/game/set-alliance-status.ts +++ b/ledger/game/set-alliance-status.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Alliance Status +// ledger: step 5, takes // The action Set Alliance Status: the current player's stance toward another, ally or enemy. program(() => { setAllianceStatus(P2, "ally"); diff --git a/ledger/game/set-countdown-timer-from-a-value.ts b/ledger/game/set-countdown-timer-from-a-value.ts index 5959fb7..2d4ad53 100644 --- a/ledger/game/set-countdown-timer-from-a-value.ts +++ b/ledger/game/set-countdown-timer-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Countdown Timer, its fields from values +// ledger: step 5, takes // The action Set Countdown Timer, the seconds from a value of the program. program(() => { let seconds = 60; diff --git a/ledger/game/set-countdown-timer.ts b/ledger/game/set-countdown-timer.ts index 614b059..3deab58 100644 --- a/ledger/game/set-countdown-timer.ts +++ b/ledger/game/set-countdown-timer.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Countdown Timer +// ledger: step 5, takes // The action Set Countdown Timer, its seconds known when the map is built. program(() => { setCountdownTimer("set", 90); diff --git a/ledger/game/set-deaths-amount-from-a-value.ts b/ledger/game/set-deaths-amount-from-a-value.ts index 389ea60..9cbbdb7 100644 --- a/ledger/game/set-deaths-amount-from-a-value.ts +++ b/ledger/game/set-deaths-amount-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Deaths, its fields from values +// ledger: step 5, takes // The action Set Deaths, the amount from a value of the program. program(() => { let n = 4; diff --git a/ledger/game/set-deaths-unit-from-a-value.ts b/ledger/game/set-deaths-unit-from-a-value.ts index 2405a94..bf89806 100644 --- a/ledger/game/set-deaths-unit-from-a-value.ts +++ b/ledger/game/set-deaths-unit-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Deaths, its fields from values +// ledger: step 5, takes // The action Set Deaths, the unit type (which counter) from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/set-deaths.ts b/ledger/game/set-deaths.ts index c4c40fa..473a618 100644 --- a/ledger/game/set-deaths.ts +++ b/ledger/game/set-deaths.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Deaths +// ledger: step 5, takes // The action Set Deaths, all its fields known when the map is built: the counter a map keeps in the death table. program(() => { setDeaths(P1, units.TerranMarine, "add", 1); diff --git a/ledger/game/set-doodad-state-type-from-a-value.ts b/ledger/game/set-doodad-state-type-from-a-value.ts index 2d7e642..8db8872 100644 --- a/ledger/game/set-doodad-state-type-from-a-value.ts +++ b/ledger/game/set-doodad-state-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Doodad State, its fields from values +// ledger: step 5, takes // The action Set Doodad State, the unit type from a value of the program. program(() => { let kind: UnitType = units.FloorGunTrap; diff --git a/ledger/game/set-doodad-state.ts b/ledger/game/set-doodad-state.ts index 9eacdb9..7bbbd10 100644 --- a/ledger/game/set-doodad-state.ts +++ b/ledger/game/set-doodad-state.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Doodad State +// ledger: step 5, takes // The action Set Doodad State: a trap or a door at a location turned on or off. program(() => { setDoodadState(players.Neutral, units.FloorGunTrap, locations.Anywhere, "disable"); diff --git a/ledger/game/set-invincibility-type-from-a-value.ts b/ledger/game/set-invincibility-type-from-a-value.ts index a3bf66c..485c398 100644 --- a/ledger/game/set-invincibility-type-from-a-value.ts +++ b/ledger/game/set-invincibility-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Invincibility, its fields from values +// ledger: step 5, takes // The action Set Invincibility, the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/set-invincibility.ts b/ledger/game/set-invincibility.ts index e72aee9..f34b2c2 100644 --- a/ledger/game/set-invincibility.ts +++ b/ledger/game/set-invincibility.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Invincibility +// ledger: step 5, takes // The action Set Invincibility: units at a location cannot be hurt, or can again. program(() => { setInvincibility(P1, units.TerranMarine, locations.Anywhere, "enable"); diff --git a/ledger/game/set-mission-objectives-text-from-the-program.ts b/ledger/game/set-mission-objectives-text-from-the-program.ts index 956e4fd..c291a2d 100644 --- a/ledger/game/set-mission-objectives-text-from-the-program.ts +++ b/ledger/game/set-mission-objectives-text-from-the-program.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Mission Objectives, its text the program's +// ledger: step 5, takes // The action Set Mission Objectives, its text made while the game runs: a number of the program in it. program(() => { let wave = 2; diff --git a/ledger/game/set-mission-objectives.ts b/ledger/game/set-mission-objectives.ts index 266402a..9de91f4 100644 --- a/ledger/game/set-mission-objectives.ts +++ b/ledger/game/set-mission-objectives.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Mission Objectives +// ledger: step 5, takes // The action Set Mission Objectives, its text written in the script. program(() => { setMissionObjectives("Hold the bridge."); diff --git a/ledger/game/set-next-scenario.ts b/ledger/game/set-next-scenario.ts index 79c2170..e0cbad9 100644 --- a/ledger/game/set-next-scenario.ts +++ b/ledger/game/set-next-scenario.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Next Scenario +// ledger: step 5, takes // The action Set Next Scenario: the map a campaign goes on to. program(() => { setNextScenario("Chapter 2"); diff --git a/ledger/game/set-resources-amount-from-a-read.ts b/ledger/game/set-resources-amount-from-a-read.ts index 622bc34..9935612 100644 --- a/ledger/game/set-resources-amount-from-a-read.ts +++ b/ledger/game/set-resources-amount-from-a-read.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Resources, its fields from values +// ledger: step 5, refused: a read of the game (Accumulate) // The action Set Resources, the amount read from the game when the line runs: `setResources(P2, "set", minerals(P1), "ore")`. program(() => { setResources(P2, "set", minerals(P1), "ore"); diff --git a/ledger/game/set-resources-amount-from-a-value.ts b/ledger/game/set-resources-amount-from-a-value.ts index 1aac56f..e9212ef 100644 --- a/ledger/game/set-resources-amount-from-a-value.ts +++ b/ledger/game/set-resources-amount-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Resources, its fields from values +// ledger: step 5, takes // The action Set Resources, the amount from a value of the program. program(() => { let bounty = 50; diff --git a/ledger/game/set-resources.ts b/ledger/game/set-resources.ts index 9d77969..81a77c8 100644 --- a/ledger/game/set-resources.ts +++ b/ledger/game/set-resources.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Resources +// ledger: step 5, takes // The action Set Resources, all its fields known when the map is built. program(() => { setResources(P1, "add", 500, "ore"); diff --git a/ledger/game/set-score-amount-from-a-value.ts b/ledger/game/set-score-amount-from-a-value.ts index da7a8f6..e50c375 100644 --- a/ledger/game/set-score-amount-from-a-value.ts +++ b/ledger/game/set-score-amount-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Score, its fields from values +// ledger: step 5, takes // The action Set Score, the amount from a value of the program. program(() => { let points = 10; diff --git a/ledger/game/set-score.ts b/ledger/game/set-score.ts index 1684123..4654a7c 100644 --- a/ledger/game/set-score.ts +++ b/ledger/game/set-score.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Score +// ledger: step 5, takes // The action Set Score, all its fields known when the map is built. program(() => { setScore(P1, "add", 10, "custom"); diff --git a/ledger/game/set-switch.ts b/ledger/game/set-switch.ts index ee5088b..021b6dc 100644 --- a/ledger/game/set-switch.ts +++ b/ledger/game/set-switch.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Set Switch +// ledger: step 5, takes // The action Set Switch: one of the map's switches (`switches.*`) set, cleared, toggled or randomized. program(() => { setSwitch(switches.Switch1, "set"); diff --git a/ledger/game/talking-portrait-type-from-a-value.ts b/ledger/game/talking-portrait-type-from-a-value.ts index b4f56fa..5a0027c 100644 --- a/ledger/game/talking-portrait-type-from-a-value.ts +++ b/ledger/game/talking-portrait-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Talking Portrait, its fields from values +// ledger: step 5, takes // The action Talking Portrait, the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/talking-portrait.ts b/ledger/game/talking-portrait.ts index 8a36888..465f6a4 100644 --- a/ledger/game/talking-portrait.ts +++ b/ledger/game/talking-portrait.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Talking Portrait +// ledger: step 5, takes // The action Talking Portrait: a unit's portrait talks for so many milliseconds. program(() => { talkingPortrait(units.TerranMarine, 2000); diff --git a/ledger/game/transmission-text-from-the-program.ts b/ledger/game/transmission-text-from-the-program.ts index 40ec024..9f69639 100644 --- a/ledger/game/transmission-text-from-the-program.ts +++ b/ledger/game/transmission-text-from-the-program.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Transmission, its text the program's +// ledger: step 5, takes // The action Transmission, its text made while the game runs: a number of the program in it. program(() => { let left = 4; diff --git a/ledger/game/transmission-type-from-a-value.ts b/ledger/game/transmission-type-from-a-value.ts index d287d16..50f192d 100644 --- a/ledger/game/transmission-type-from-a-value.ts +++ b/ledger/game/transmission-type-from-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Transmission, its fields from values +// ledger: step 5, takes // The action Transmission, the unit type from a value of the program. program(() => { let kind: UnitType = units.TerranMarine; diff --git a/ledger/game/transmission.ts b/ledger/game/transmission.ts index 8d9e5c0..985674c 100644 --- a/ledger/game/transmission.ts +++ b/ledger/game/transmission.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Transmission +// ledger: step 5, takes // The action Transmission: a text, a sound and a talking portrait, the unit pinged on the minimap. program(() => { transmission("Report in.", units.TerranMarine, locations.Anywhere, "add", 0, "sound\\glue\\mousedown2.wav", 500); diff --git a/ledger/game/unmute-unit-speech.ts b/ledger/game/unmute-unit-speech.ts index 24b547f..63633b9 100644 --- a/ledger/game/unmute-unit-speech.ts +++ b/ledger/game/unmute-unit-speech.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Unmute Unit Speech +// ledger: step 5, takes // The action Unmute Unit Speech: the units' voices back. program(() => { unmuteUnitSpeech(); diff --git a/ledger/game/unpause-game.ts b/ledger/game/unpause-game.ts index 7336f61..69710ac 100644 --- a/ledger/game/unpause-game.ts +++ b/ledger/game/unpause-game.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Unpause Game +// ledger: step 5, takes // The action Unpause Game. program(() => { unpauseGame(); diff --git a/ledger/game/unpause-timer.ts b/ledger/game/unpause-timer.ts index f7a1331..9671c56 100644 --- a/ledger/game/unpause-timer.ts +++ b/ledger/game/unpause-timer.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Unpause Timer +// ledger: step 5, takes // The action Unpause Timer: the countdown timer runs on. program(() => { unpauseTimer(); diff --git a/ledger/game/victory.ts b/ledger/game/victory.ts index 71d2665..1ff8908 100644 --- a/ledger/game/victory.ts +++ b/ledger/game/victory.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Victory +// ledger: step 5, takes // The action Victory: the player wins the game. program(() => { victory(); diff --git a/ledger/language/ternary-boolean.ts b/ledger/language/ternary-boolean.ts index a3b112b..61def18 100644 --- a/ledger/language/ternary-boolean.ts +++ b/ledger/language/ternary-boolean.ts @@ -1,4 +1,4 @@ -// ledger: step 0, refused: a ?: of booleans +// ledger: step 0, takes // `?:` choosing between two booleans, its test asked while the game runs. program(() => { let n = 0; diff --git a/ledger/memory/memory-condition.ts b/ledger/memory/memory-condition.ts index e397bc9..addfd75 100644 --- a/ledger/memory/memory-condition.ts +++ b/ledger/memory/memory-condition.ts @@ -1,4 +1,4 @@ -// ledger: step 6, refused: the condition Deaths +// ledger: step 5, takes // A cell of the game's memory compared, by its address (EUD): `memory(address, ">=", value)`. program(() => { if (memory(0x58A364, ">=", 1)) print("set"); diff --git a/ledger/memory/set-memory.ts b/ledger/memory/set-memory.ts index 93340dc..2dd60d5 100644 --- a/ledger/memory/set-memory.ts +++ b/ledger/memory/set-memory.ts @@ -1,4 +1,4 @@ -// ledger: step 6, refused: the action Set Deaths +// ledger: step 5, takes // A cell of the game's memory written, by its address (EUD): `setMemory(address, "add", value)`. program(() => { setMemory(0x58A364, "add", 1); diff --git a/ledger/sounds/music-arrange.ts b/ledger/sounds/music-arrange.ts index ff9eff1..304f789 100644 --- a/ledger/sounds/music-arrange.ts +++ b/ledger/sounds/music-arrange.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A song in sections: parts named once (`music.part`), each section saying what they play // (`music.section`), put in order by `music.arrange`. import { music } from "trigscript/music"; diff --git a/ledger/sounds/music-bars.ts b/ledger/sounds/music-bars.ts index c243253..dd40fe6 100644 --- a/ledger/sounds/music-bars.ts +++ b/ledger/sounds/music-bars.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // How long bars last at a tempo, as a song plays them: `music.bars(120, 1)`, the length of a riser. import { music } from "trigscript/music"; const rise = music.riser(music.bars(240, 1), { volume: 0.3 }); diff --git a/ledger/sounds/music-beat.ts b/ledger/sounds/music-beat.ts index d69f9c0..8768342 100644 --- a/ledger/sounds/music-beat.ts +++ b/ledger/sounds/music-beat.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A drum pattern from the steps each drum hits, when the map is built: `music.beat({ k: [0, 4], h: […] }, 8)`. import { music } from "trigscript/music"; const drums = music.kit({ k: tone([[0, 150], [0.08, 45]], 0.2, { volume: 0.6 }), h: noise(0.03, { volume: 0.2 }) }); diff --git a/ledger/sounds/music-chord.ts b/ledger/sounds/music-chord.ts index 26b4e1c..3030bde 100644 --- a/ledger/sounds/music-chord.ts +++ b/ledger/sounds/music-chord.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A chord by its name, its notes sounding together: `music.chord("Am")`. import { music } from "trigscript/music"; const keys = music.synth({ volume: 0.1, release: 0.05 }); diff --git a/ledger/sounds/music-chords.ts b/ledger/sounds/music-chords.ts index 710bd77..24e321f 100644 --- a/ledger/sounds/music-chords.ts +++ b/ledger/sounds/music-chords.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A chord progression by name, each drawn by a template: `music.chords("Am F", "1+2+3 - . .")`. import { music } from "trigscript/music"; const keys = music.synth({ volume: 0.1, release: 0.05 }); diff --git a/ledger/sounds/music-drums.ts b/ledger/sounds/music-drums.ts index c355f73..d489dc1 100644 --- a/ledger/sounds/music-drums.ts +++ b/ledger/sounds/music-drums.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A drum kit made from nothing, a letter a drum: `music.drums()`. import { music } from "trigscript/music"; const kit = music.drums(); diff --git a/ledger/sounds/music-fm-synth.ts b/ledger/sounds/music-fm-synth.ts index 616c46b..37f4b31 100644 --- a/ledger/sounds/music-fm-synth.ts +++ b/ledger/sounds/music-fm-synth.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // An FM instrument: `music.fmSynth({ ratio, index })`. import { music } from "trigscript/music"; const keys = music.fmSynth({ ratio: 1, index: 2, release: 0.1 }); diff --git a/ledger/sounds/music-impact.ts b/ledger/sounds/music-impact.ts index 39a9d3f..c3377e1 100644 --- a/ledger/sounds/music-impact.ts +++ b/ledger/sounds/music-impact.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A boom to land on, made when the map is built: `music.impact()`. import { music } from "trigscript/music"; const boom = music.impact({ seconds: 0.5 }); diff --git a/ledger/sounds/music-kit.ts b/ledger/sounds/music-kit.ts index 468dfee..1e1ff2b 100644 --- a/ledger/sounds/music-kit.ts +++ b/ledger/sounds/music-kit.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A drum kit of the script's own clips, a letter each: `music.kit({ k: kick, h: hat })`. import { music } from "trigscript/music"; const drums = music.kit({ k: tone([[0, 150], [0.08, 45]], 0.2, { volume: 0.6 }), h: noise(0.03, { volume: 0.2 }) }); diff --git a/ledger/sounds/music-midi.ts b/ledger/sounds/music-midi.ts index fc1728a..b6b01c7 100644 --- a/ledger/sounds/music-midi.ts +++ b/ledger/sounds/music-midi.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A song of notes (as a MIDI file imports them), each track played by an instrument: `music.midi(notes, { Piano: keys })`. import { music } from "trigscript/music"; const notes: Notes = { diff --git a/ledger/sounds/music-over.ts b/ledger/sounds/music-over.ts index ef2b840..c4e67e2 100644 --- a/ledger/sounds/music-over.ts +++ b/ledger/sounds/music-over.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A chord's notes drawn by a template, when the map is built: `music.over("C4+E4+G4", "1 2 3 2")`. import { music } from "trigscript/music"; const lead = music.synth({ release: 0.05 }); diff --git a/ledger/sounds/music-play.ts b/ledger/sounds/music-play.ts index 49bef7c..047017b 100644 --- a/ledger/sounds/music-play.ts +++ b/ledger/sounds/music-play.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A song played note by note from the music library: `music.synth`, `music.track`, `music.song`, and // `await music.play(song)` in an async program. import { music } from "trigscript/music"; diff --git a/ledger/sounds/music-presets.ts b/ledger/sounds/music-presets.ts index 75a22ca..642452b 100644 --- a/ledger/sounds/music-presets.ts +++ b/ledger/sounds/music-presets.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // An instrument to start from: `music.presets.chip()`. import { music } from "trigscript/music"; const chip = music.presets.chip(); diff --git a/ledger/sounds/music-pump.ts b/ledger/sounds/music-pump.ts index afc71ff..814765b 100644 --- a/ledger/sounds/music-pump.ts +++ b/ledger/sounds/music-pump.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A part that breathes with the beat, each note ducked on it: `{ fx: music.pump(beat) }`. import { music } from "trigscript/music"; const pad = music.synth({ volume: 0.1, release: 0.05 }); diff --git a/ledger/sounds/music-render.ts b/ledger/sounds/music-render.ts index 9801fa9..5d642ab 100644 --- a/ledger/sounds/music-render.ts +++ b/ledger/sounds/music-render.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A song mixed into one clip when the map is built, every note in time: `music.render(song)`. import { music } from "trigscript/music"; const lead = music.synth({ volume: 0.2, release: 0.05 }); diff --git a/ledger/sounds/music-repeat.ts b/ledger/sounds/music-repeat.ts index 5f5129a..856d82c 100644 --- a/ledger/sounds/music-repeat.ts +++ b/ledger/sounds/music-repeat.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A pattern played several times over, when the map is built: `music.repeat(pattern, 2)`. import { music } from "trigscript/music"; const lead = music.synth({ release: 0.05 }); diff --git a/ledger/sounds/music-riser.ts b/ledger/sounds/music-riser.ts index ba458c7..1e454bf 100644 --- a/ledger/sounds/music-riser.ts +++ b/ledger/sounds/music-riser.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // Noise rising through an opening filter, made when the map is built: `music.riser(seconds)`. import { music } from "trigscript/music"; const rise = music.riser(1, { volume: 0.3 }); diff --git a/ledger/sounds/music-sampler.ts b/ledger/sounds/music-sampler.ts index 34a2a33..b1d3b39 100644 --- a/ledger/sounds/music-sampler.ts +++ b/ledger/sounds/music-sampler.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // An instrument from a clip, played at each note's pitch: `music.sampler(clip, root)`. import { music } from "trigscript/music"; const harp = music.sampler(pluck("C4", 0.5), "C4"); diff --git a/ledger/sounds/music-stretch.ts b/ledger/sounds/music-stretch.ts index 09f5963..d389212 100644 --- a/ledger/sounds/music-stretch.ts +++ b/ledger/sounds/music-stretch.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A pattern made slower, each step held longer, when the map is built: `music.stretch(pattern, 2)`. import { music } from "trigscript/music"; const lead = music.synth({ release: 0.05 }); diff --git a/ledger/sounds/music-strings.ts b/ledger/sounds/music-strings.ts index ccd55eb..c95d8d0 100644 --- a/ledger/sounds/music-strings.ts +++ b/ledger/sounds/music-strings.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A plucked-string instrument: `music.strings()`. import { music } from "trigscript/music"; const guitar = music.strings({ brightness: 0.5, decay: 0.6 }); diff --git a/ledger/sounds/music-transpose.ts b/ledger/sounds/music-transpose.ts index 3595383..8627f0a 100644 --- a/ledger/sounds/music-transpose.ts +++ b/ledger/sounds/music-transpose.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // A pattern moved up or down by semitones when the map is built: `music.transpose(pattern, 12)`. import { music } from "trigscript/music"; const lead = music.synth({ release: 0.05 }); diff --git a/ledger/sounds/play-wav-chosen-by-a-value.ts b/ledger/sounds/play-wav-chosen-by-a-value.ts index aecc362..ef40742 100644 --- a/ledger/sounds/play-wav-chosen-by-a-value.ts +++ b/ledger/sounds/play-wav-chosen-by-a-value.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of one of two clips, chosen by a value of the program. const low = tone("C4", 0.1, { volume: 0.3 }); const high = tone("C5", 0.1, { volume: 0.3 }); diff --git a/ledger/sounds/play-wav-clip-effects.ts b/ledger/sounds/play-wav-clip-effects.ts index 335af64..2d882a3 100644 --- a/ledger/sounds/play-wav-clip-effects.ts +++ b/ledger/sounds/play-wav-clip-effects.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of a clip put through effects when the map is built: a filter, an echo, a reverb, a limiter. const zap = tone("A3", 0.4, { wave: "saw", volume: 0.4 }).filter("lowpass", [[0, 300], [0.4, 3000]]).delay(0.1).reverb({ decay: 0.3 }).limit(); program(() => { diff --git a/ledger/sounds/play-wav-fm.ts b/ledger/sounds/play-wav-fm.ts index fb613e2..659f512 100644 --- a/ledger/sounds/play-wav-fm.ts +++ b/ledger/sounds/play-wav-fm.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of a clip made by `fm()` when the map is built: a bell. const bell = fm("E5", 1, { ratio: 3.5, index: [[0, 6], [1, 0.3]], volume: 0.3 }).fade(0, 0.5); program(() => { diff --git a/ledger/sounds/play-wav-game-file.ts b/ledger/sounds/play-wav-game-file.ts index 8e77f74..5294585 100644 --- a/ledger/sounds/play-wav-game-file.ts +++ b/ledger/sounds/play-wav-game-file.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of one of the game's own sound files, by its path. program(() => { playWav("sound\\glue\\mousedown2.wav", 0); diff --git a/ledger/sounds/play-wav-map-sound.ts b/ledger/sounds/play-wav-map-sound.ts index 837fb46..5df4bf1 100644 --- a/ledger/sounds/play-wav-map-sound.ts +++ b/ledger/sounds/play-wav-map-sound.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of one of the map's own sounds (the Sound Editor's list) by its name, `sounds.Name`. // The ledger's map has none, so one of the game's own files, chosen when the map is built. const own = Object.values(sounds) as Sound[]; diff --git a/ledger/sounds/play-wav-mixdown.ts b/ledger/sounds/play-wav-mixdown.ts index f6acdc6..ac01aaa 100644 --- a/ledger/sounds/play-wav-mixdown.ts +++ b/ledger/sounds/play-wav-mixdown.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of clips laid over one another by `mixdown()` when the map is built: one clip. const kick = tone([[0, 150], [0.08, 45]], 0.2, { volume: 0.6 }); const hat = noise(0.03, { volume: 0.2 }); diff --git a/ledger/sounds/play-wav-noise.ts b/ledger/sounds/play-wav-noise.ts index ec0203c..7cda92f 100644 --- a/ledger/sounds/play-wav-noise.ts +++ b/ledger/sounds/play-wav-noise.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of a clip made by `noise()` when the map is built: a burst. const burst = noise(0.15, { volume: 0.4 }).fade(0, 0.1); program(() => { diff --git a/ledger/sounds/play-wav-pluck.ts b/ledger/sounds/play-wav-pluck.ts index e2f113c..f0fcc8d 100644 --- a/ledger/sounds/play-wav-pluck.ts +++ b/ledger/sounds/play-wav-pluck.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of a clip made by `pluck()` when the map is built: a plucked string. const harp = pluck("G3", 0.8, { brightness: 0.6 }); program(() => { diff --git a/ledger/sounds/play-wav-together.ts b/ledger/sounds/play-wav-together.ts index cb8d9ef..3dde4dd 100644 --- a/ledger/sounds/play-wav-together.ts +++ b/ledger/sounds/play-wav-together.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of clips the game plays together, turned down by `together()` when the map is built // so that they never add up past the peak. const [low, high] = together([{ clip: tone("C4", 0.5, { volume: 0.8 }), at: 0 }, { clip: tone("G4", 0.5, { volume: 0.8 }), at: 0.25 }]); diff --git a/ledger/sounds/play-wav-tone.ts b/ledger/sounds/play-wav-tone.ts index 1987294..61626c1 100644 --- a/ledger/sounds/play-wav-tone.ts +++ b/ledger/sounds/play-wav-tone.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of a clip made by `tone()` when the map is built: a beep. const beep = tone("A5", 0.1, { wave: "square", volume: 0.3 }).fade(0, 0.05); program(() => { diff --git a/ledger/sounds/play-wav-with-silence.ts b/ledger/sounds/play-wav-with-silence.ts index 51c8615..9c14886 100644 --- a/ledger/sounds/play-wav-with-silence.ts +++ b/ledger/sounds/play-wav-with-silence.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Play WAV +// ledger: step 5, takes // The action Play WAV of two beeps a gap apart, the gap made by `silence()` when the map is built. const beep = tone("C5", 0.08, { volume: 0.3 }); const twice = beep.then(silence(0.1), beep); diff --git a/ledger/sounds/transmission-with-a-clip.ts b/ledger/sounds/transmission-with-a-clip.ts index 6b323e9..6f28902 100644 --- a/ledger/sounds/transmission-with-a-clip.ts +++ b/ledger/sounds/transmission-with-a-clip.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Transmission +// ledger: step 5, takes // The action Transmission with a clip made when the map is built as its sound. const chirp = tone([[0, 800], [0.2, 1600]], 0.2, { volume: 0.3 }); program(() => { diff --git a/ledger/tasks/cutscene-line.ts b/ledger/tasks/cutscene-line.ts index 8b8e62f..844c36a 100644 --- a/ledger/tasks/cutscene-line.ts +++ b/ledger/tasks/cutscene-line.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Talking Portrait +// ledger: step 5, refused: a text at the centre of the screen // trigscript/cutscene: a line of a scene, the speaker's portrait talking and the words in the middle of the screen: `await line(speaker, text)`. import { line, SECOND } from "trigscript/cutscene"; program(async () => { diff --git a/ledger/tasks/until-condition.ts b/ledger/tasks/until-condition.ts index 966a66e..9d32d52 100644 --- a/ledger/tasks/until-condition.ts +++ b/ledger/tasks/until-condition.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the condition Elapsed Time +// ledger: step 5, takes // A wait until a condition of the game holds, asked every frame: `await until(elapsedTime(">=", 5))`. program(async () => { await until(elapsedTime(">=", 5)); diff --git a/ledger/triggers/disable-debug-mode.ts b/ledger/triggers/disable-debug-mode.ts index 19499e5..42b621e 100644 --- a/ledger/triggers/disable-debug-mode.ts +++ b/ledger/triggers/disable-debug-mode.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Disable Debug Mode +// ledger: step 5, takes // The action Disable Debug Mode, inside a program. program(() => { disableDebugMode(); diff --git a/ledger/triggers/enable-debug-mode.ts b/ledger/triggers/enable-debug-mode.ts index 95998ae..720c7c6 100644 --- a/ledger/triggers/enable-debug-mode.ts +++ b/ledger/triggers/enable-debug-mode.ts @@ -1,4 +1,4 @@ -// ledger: step 5, refused: the action Enable Debug Mode +// ledger: step 5, takes // The action Enable Debug Mode, inside a program. program(() => { enableDebugMode(); diff --git a/probes/own-fields.ts b/probes/own-fields.ts new file mode 100644 index 0000000..d796cb5 --- /dev/null +++ b/probes/own-fields.ts @@ -0,0 +1,82 @@ +// TrigScript 4, the backend: the game's actions with fields from values in a map of our own (scripts/build-own.mts) — +// step 5 of the way off eudplib, part 2. A field from a value is a place of its variable, kept by the variable's +// writes, so the action reads it where it is; a unit count is the action done 255 at a time while more are left, then +// once with the rest, 0 or below doing nothing. Built both ways (through eudplib, and as a map of our own), the lifted +// game should print the same lines at the same frames; every line should end "ok", and a line ending "WRONG" names +// the check that failed; line I is a Transmission's own text. Frame 2 (the lifted game's 4) is 200 actions with an amount from a value, to weigh. +// npx tsx scripts/build-own.mts probes/own-fields.ts /tmp/own-fields.scx +// npx tsx scripts/build-fixture.mts probes/own-fields.ts /tmp/own-fields-eud.scx +// The map build-own makes: Player 1 a person with a Command Center and 4 Marines, Player 2 a computer with a Hatchery +// and 4 Zerglings. +import { program, print, sleep, frames } from "trigscript"; + +program(() => { + // A: an amount from a value, and one from a sum. + let n = 4; + n += 1; + setDeaths(P1, units.TerranMarine, "set", n); + let bounty = 50; + bounty += 25; + setResources(P1, "set", bounty + 25, "ore"); + if (deaths(P1, units.TerranMarine, "==", 5) && accumulate(P1, "==", 100, "ore")) print("A: amounts ok"); else print("A: amounts WRONG"); + // B: a unit type from a value: the deaths of the type it holds, made and counted. + let kind: UnitType = units.TerranMarine; + if (n > 2) kind = units.TerranFirebat; + setDeaths(P1, kind, "add", 7); + createUnit(P1, kind, 2, locations.Anywhere); + if (deaths(P1, units.TerranFirebat, "==", 7) && deaths(P1, units.TerranMarine, "==", 5) && command(P1, units.TerranFirebat, "==", 2)) print("B: a unit type ok"); + else print("B: a unit type WRONG"); + // C: a unit count from a value — 3, 0, below zero — of Wraiths (air units: they stack where ground units would not + // all fit). + let count = 3; + createUnit(P1, units.TerranWraith, count, locations.Anywhere); + count = 0; + createUnit(P1, units.TerranWraith, count, locations.Anywhere); + count = -2; + createUnit(P1, units.TerranWraith, count, locations.Anywhere); + if (command(P1, units.TerranWraith, "==", 3)) print("C: a unit count ok"); else print("C: a unit count WRONG"); + // D: the type and the count both from values; a kill of the type a value holds (counted a frame later: the game + // counts a unit killed or removed until the next frame). + let many = 2; + many += 1; + kind = units.TerranValkyrie; + createUnit(P1, kind, many, locations.Anywhere); + let three = false; + if (command(P1, units.TerranValkyrie, "==", 3)) three = true; + killUnit(P1, kind); + // F: the countdown from a value. + let seconds = 60; + seconds += 30; + setCountdownTimer("set", seconds); + if (countdownTimer("==", 90)) print("F: the countdown ok"); else print("F: the countdown WRONG"); + sleep(frames(2)); + // Frame 2: 200 actions, each adding a value that changes every turn. + let j = 0; + setDeaths(P1, units.TerranMarine, "set", 0); + while (j < 200) { + setDeaths(P1, units.TerranMarine, "add", j); + j++; + } + if (deaths(P1, units.TerranMarine, "==", 19900)) print("G: 200 amounts from a value ok"); else print("G: 200 amounts from a value WRONG"); + if (three && command(P1, units.TerranValkyrie, "==", 0)) print("D: a type and a count ok"); else print("D: a type and a count WRONG"); + // H (frames 4 and 6, the lifted game's 6 and 8): a count past what a byte holds — 258 Scourges for Player 2 (air + // units), then 258 removed. The game places about 253 at one point before it says "Unit unplaceable", up to eleven + // times, which fills the screen: the lines come two frames later. A count cut to a byte would be 2 either way. + sleep(frames(2)); + count = 258; + createUnit(P2, units.ZergScourge, count, locations.Anywhere); + let made = false, removed = false; + if (command(P2, units.ZergScourge, ">=", 250)) made = true; + removeUnitAt(P2, units.ZergScourge, count, locations.Anywhere); + sleep(frames(2)); + if (command(P2, units.ZergScourge, "==", 0)) removed = true; + if (made) print("H: 258 made ok"); else print("H: 258 made WRONG"); + if (removed) print("H: 258 removed ok"); else print("H: 258 removed WRONG"); + // I: a Transmission whose text is made while the game runs (it shows among the text lines), and the program going on + // after it (a Transmission waits as Wait does). + let left = 4; + left -= 1; + transmission(`I: ${left} bunkers left ok`, units.TerranMarine, locations.Anywhere, "add", 0, "sound\\glue\\mousedown2.wav", 500); + setMissionObjectives(`Survive wave ${left}`); + print("Own fields: done."); +}); diff --git a/probes/own-game.ts b/probes/own-game.ts new file mode 100644 index 0000000..95e86a2 --- /dev/null +++ b/probes/own-game.ts @@ -0,0 +1,75 @@ +// TrigScript 4, the backend: the game's own conditions and actions in a map of our own (scripts/build-own.mts) — step +// 5 of the way off eudplib, part 1. A condition or an action the script writes with every field known when the map is +// built is laid out as the game's record, as it is; a condition turned around with `!` is one condition where one +// says the opposite (at least n against at most n − 1, set against cleared), else a trigger that skips past. Built both +// ways (through eudplib, and as a map of our own), the lifted game should print the same lines at the same frames; +// every line should end "ok", and a line ending "WRONG" names the check that failed. Frames 2 and 4 (the lifted game's +// 4 and 6) are each one kind of work, 200 times, to weigh. +// npx tsx scripts/build-own.mts probes/own-game.ts /tmp/own-game.scx +// npx tsx scripts/build-fixture.mts probes/own-game.ts /tmp/own-game-eud.scx +// The map build-own makes: Player 1 a person with a Command Center and 4 Marines, Player 2 a computer with a Hatchery +// and 4 Zerglings. +import { program, print, sleep, frames } from "trigscript"; + +// A cell of the deaths table nobody else uses: Player 8's deaths of unit 227. +const CELL = 0x58a364 + 4 * (227 * 12 + 7); + +program(() => { + // A: a switch set and cleared, asked both ways. + setSwitch(switches.Switch1, "set"); + const setA = switchIs(switches.Switch1, "set") && !switchIs(switches.Switch1, "cleared"); + setSwitch(switches.Switch1, "clear"); + if (setA && !switchIs(switches.Switch1, "set") && switchIs(switches.Switch1, "cleared")) print("A: switches ok"); else print("A: switches WRONG"); + // B: ore set, then asked at least, at most and exactly — each turned around too. + setResources(P1, "set", 500, "ore"); + if (accumulate(P1, ">=", 500, "ore") && !accumulate(P1, ">=", 501, "ore") && accumulate(P1, "<=", 500, "ore") + && !accumulate(P1, "<=", 499, "ore") && accumulate(P1, "==", 500, "ore") && !accumulate(P1, "==", 7, "ore")) print("B: ore ok"); + else print("B: ore WRONG"); + // C: deaths set and added to. + setDeaths(P1, units.TerranMarine, "set", 3); + const three = deaths(P1, units.TerranMarine, "==", 3) && !deaths(P1, units.TerranMarine, ">=", 4); + setDeaths(P1, units.TerranMarine, "add", 2); + if (three && deaths(P1, units.TerranMarine, "==", 5)) print("C: deaths ok"); else print("C: deaths WRONG"); + // D: units made, counted by Command and by Bring — asked before and after, which Bring would answer from what it + // counted before unless a query of another kind is asked in between. + let before = false; + if (command(P1, units.TerranMarine, "==", 4) && bring(P1, units.TerranMarine, locations.Anywhere, "==", 4)) before = true; + createUnit(P1, units.TerranMarine, 2, locations.Anywhere); + if (before && command(P1, units.TerranMarine, "==", 6) && bring(P1, units.TerranMarine, locations.Anywhere, "==", 6)) print("D: units made ok"); + else print("D: units made WRONG"); + // E: the game's clock and the countdown. + setCountdownTimer("set", 90); + if (elapsedTime(">=", 0) && !elapsedTime(">=", 100000) && countdownTimer(">=", 90) && countdownTimer("<=", 90)) print("E: clocks ok"); + else print("E: clocks WRONG"); + // F: always, never, a score, the opponents. + setScore(P1, "set", 10, "custom"); + if (always() && !never() && score(P1, "custom", "==", 10) && !score(P1, "custom", ">=", 11) && opponents(P1, "==", 1)) print("F: always, never, score, opponents ok"); + else print("F: always, never, score, opponents WRONG"); + // G: memory at an address known when the map is built. + setMemory(CELL, "set", 77); + setMemory(CELL, "add", 3); + if (memory(CELL, "==", 80) && !memory(CELL, ">=", 81)) print("G: memory ok"); else print("G: memory WRONG"); + // H: an action's own text. + displayText("H: display text ok"); + sleep(frames(2)); + // Frame 2: 200 turns, each asking three conditions and doing two actions. + let j = 0, n = 0; + setResources(P1, "set", 0, "ore"); + while (j < 200) { + setResources(P1, "add", 1, "ore"); + if (accumulate(P1, ">=", 100, "ore") && !switchIs(switches.Switch1, "set") && deaths(P1, units.TerranMarine, "==", 5)) n++; + setDeaths(P1, units.TerranMarine, "add", 0); + j++; + } + if (n == 101) print("I: 200 turns of conditions and actions ok"); else print("I: 200 turns of conditions and actions WRONG"); + sleep(frames(2)); + // Frame 4: 200 turns of a condition turned around that no other one says (exactly). + j = 0; n = 0; + while (j < 200) { + if (!deaths(P1, units.TerranMarine, "==", 5)) n++; + setDeaths(P1, units.TerranMarine, "add", 1); + j++; + } + if (n == 199) print("J: 200 turns of not exactly ok"); else print("J: 200 turns of not exactly WRONG"); + print("Own game: done."); +}); diff --git a/scripts/build-own.mts b/scripts/build-own.mts index 3ecc3ea..c6256de 100644 --- a/scripts/build-own.mts +++ b/scripts/build-own.mts @@ -18,6 +18,7 @@ import { STRINGS, layout } from "../compiler/backend/layout"; import type { Graph } from "../compiler/backend/graph"; import { defaultScriptNames } from "../compiler/names"; import { defaultLib } from "../bundle/lib.mjs"; +import { finishSounds, vorbisEncoder } from "../compiler/vorbis"; import { scriptFiles } from "./script-files.mts"; const started = performance.now(); @@ -59,7 +60,12 @@ for (const [owner, base, troop] of [[0, COMMAND_CENTER, MARINE], [1, HATCHERY, Z applyUnitChanges(scn, addUnits(scn, placed)); // The texts the graph shows, in the strings first: the payload goes after them. const texts = new Map(); -for (const t of (graph as Graph).triggers) for (const a of t.acts) if (a.display !== undefined && !texts.has(a.display)) texts.set(a.display, internString(scn, a.display)); +const intern = (text: string) => { if (!texts.has(text)) texts.set(text, internString(scn, text)); }; +for (const t of (graph as Graph).triggers) for (const a of t.acts) { + if (a.display !== undefined) intern(a.display); + // (An action of the game's: its text and its sound file's name.) + for (const s of [a.strings?.text, a.strings?.wav]) if (s && "text" in s) intern(s.text); +} // A string for each text printed with values in it, as long as its template and three bytes more: once the strings are // laid out, the template goes in it from its first dword's start on, and the string's offset is moved there (the // game's own table of strings names only its first 64 KB in 1.16.1, so the template stays among them, as eudplib's @@ -116,6 +122,9 @@ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0)); let at = 0; for (const p of parts) { out.set(p, at); at += p.length; } const extras = loaded.archive ? await readExtras(loaded.archive, loaded.files) : new Map(); +// The clips the script plays, in the archive (an Ogg one encoded first), as build-fixture puts them. +if (r.sounds.some((s) => s.vorbis)) finishSounds(r.sounds, vorbisEncoder(readFileSync(join(root, "node_modules", "wasm-media-encoders", "wasm", "ogg.wasm")))); +for (const s of r.sounds) extras.set(s.path, s.bytes!); writeFileSync(resolve(outPath), await saveMap(out, { extras, compress: "pkware", listfile: true })); const done = performance.now(); console.log(`${resolve(outPath)}: ${graph.triggers.length} triggers of the graph, payload ${own.payload.length} bytes at 0x${own.address.toString(16)}, start-up ${own.trig.length / 2400} triggers`); diff --git a/tests/emitter.test.ts b/tests/emitter.test.ts index 0323d75..b184509 100644 --- a/tests/emitter.test.ts +++ b/tests/emitter.test.ts @@ -290,7 +290,7 @@ function key(d: Dword): string { if ("template" in d) return `template:${d.template}:${d.word}`; if ("cell" in d) return `epd:${TABLE_EPD(d.cell) + 18 * d.index}`; const f = d.field; - return f === "next" ? `${d.at}|next` : "cond" in f ? `${d.at}|cond:${f.cond}${f.part === "player" ? ":player" : f.part === "mask" ? ":mask" : ""}` : `${d.at}|act:${f.act}:${f.part}`; + return f === "next" || f === "current" ? `${d.at}|${f}` : "cond" in f ? `${d.at}|cond:${f.cond}${f.part === "player" ? ":player" : f.part === "mask" ? ":mask" : ""}` : `${d.at}|act:${f.act}:${f.part}`; } function exec(g: Graph, env: Env): Env { diff --git a/tests/ownGame.test.ts b/tests/ownGame.test.ts new file mode 100644 index 0000000..3dae8bd --- /dev/null +++ b/tests/ownGame.test.ts @@ -0,0 +1,110 @@ +/** + * The game's own conditions and actions in a map of our own (TrigScript 4, step 5 of the way off eudplib): laid out as + * the game's records, byte for byte as the map's own triggers would hold them; a condition turned around in one + * condition where one says it; an `if` of actions under conditions that fit in one trigger, that one trigger. + */ +import ts from "typescript"; +import { describe, expect, it } from "vitest"; +import { compileScript } from "../compiler/compiler"; +import { defaultScriptNames } from "../compiler/names"; +import { emitOwn } from "../compiler/backend/own"; +import type { Graph } from "../compiler/backend/graph"; +import { layout } from "../compiler/backend/layout"; +import { encodeTriggers, emptyTrigger, type ActionRecord, type ConditionRecord } from "../vendor/triggers"; +import { defaultLib } from "../bundle/lib.mjs"; + +let lib: ReturnType | undefined; +function own(body: string) { + lib ??= defaultLib(); + const r = compileScript(ts, { "main.ts": `program(() => {\n${body}\n});\n` }, defaultScriptNames(), { lib, emitter: false }); + expect(r.diagnostics).toEqual([]); + const o = emitOwn(r); + if (typeof o === "string") throw new Error(o); + return { ...o, strings: r.strings }; +} +const games = (g: Graph) => g.triggers.flatMap((t) => t.conds.filter((c) => c.game).map((c) => c.game!)); + +describe("a map of our own, the game's conditions and actions", () => { + it("lays a condition and an action out as the game's records, the action's texts among the map's strings", () => { + const { graph } = own(`if (bring(P1, units.TerranMarine, locations.Anywhere, ">=", 5)) transmission("Report in.", units.TerranMarine, locations.Anywhere, "add", 0, "sound\\\\glue\\\\mousedown2.wav", 500);`); + // (A Transmission is the first action of a trigger of its own: the condition is the branch's before it.) + const c = graph.triggers.find((t) => t.conds.some((c) => c.game))!, a = graph.triggers.find((t) => t.acts[0]?.record)!; + const texts = new Map([["Report in.", 7], ["sound\\glue\\mousedown2.wav", 8]]); + const out = layout({ graph, strings: 0, owner: 0, text: (s) => texts.get(s)!, arrays: new Map() }); + const cond: ConditionRecord = c.conds[0].game!, act: ActionRecord = { ...a.acts[0].record!, text: 7, wav: 8 }; + const record = encodeTriggers([{ ...emptyTrigger(), conditions: [cond], actions: [act] }]); + // (The payload's triggers in order: `@wake` and `@init` first, then the graph's, a node of 2408 bytes each, its + // record 8 bytes in.) + const node = (t: typeof c) => 8 + 2408 * (2 + graph.triggers.indexOf(t)); + expect([...out.payload.subarray(node(c), node(c) + 20)]).toEqual([...record.subarray(0, 20)]); + expect([...out.payload.subarray(node(a) + 320, node(a) + 352)]).toEqual([...record.subarray(320, 352)]); + }); + + it("turns a condition around in one condition where one says the opposite, and skips past one where none does", () => { + const flipped = (cond: string) => games(own(`if (!${cond}) setDeaths(P1, units.TerranMarine, "add", 1);`).graph); + expect(flipped(`accumulate(P1, ">=", 500, "ore")`)).toMatchObject([{ type: 4, comparison: 1, amount: 499 }]); + expect(flipped(`accumulate(P1, "<=", 500, "ore")`)).toMatchObject([{ type: 4, comparison: 0, amount: 501 }]); + expect(flipped(`switchIs(switches.Switch1, "set")`)).toMatchObject([{ type: 11, comparison: 3 }]); + // (Exactly: the condition as it is, its trigger sending the walk past what it guards.) + expect(flipped(`deaths(P1, units.TerranMarine, "==", 5)`)).toMatchObject([{ type: 15, comparison: 10, amount: 5 }]); + // (At least 0 always holds: turned around, the `if` is gone.) + expect(flipped(`accumulate(P1, ">=", 0, "ore")`)).toEqual([]); + }); + + it("lays an `if` of actions and counts under conditions that fit in one trigger out as that trigger, with no branch", () => { + const { graph } = own(`let n = 0;\nif (switchIs(switches.Switch1, "set") && accumulate(P1, ">=", 100, "ore")) { n += 1; setDeaths(P1, units.TerranMarine, "add", 1); }\nprint(\`\${n}\`);`); + const t = graph.triggers.find((t) => t.conds.some((c) => c.game))!; + expect(t.conds.map((c) => c.game?.type)).toEqual([11, 4]); + expect(t.acts.some((a) => a.record?.type === 45)).toBe(true); + // (No action of it moves a next pointer: it branches nowhere.) + expect(t.acts.some((a) => "at" in a.dword && a.dword.field === "next")).toBe(false); + }); + + it("throws the game's counts away after an action that makes units, in a program that asks Bring or Command", () => { + // (The game keeps Command's and Bring's counts until a query of the other kind or an action that kills or removes + // units: Kill Unit of Player 12's units of type 228, which no unit is, in the same trigger.) + for (const ask of [`bring(P1, units.TerranMarine, locations.Anywhere, "==", 6)`, `command(P1, units.TerranMarine, "==", 6)`]) { + const { graph } = own(`createUnit(P1, units.TerranMarine, 2, locations.Anywhere);\nif (${ask}) print("six");`); + const t = graph.triggers.find((t) => t.acts.some((a) => a.record?.type === 44))!; + expect(t.acts.map((a) => a.record?.type)).toEqual([44, 22]); + expect(t.acts[1].record).toMatchObject({ player: 11, unitId: 228 }); + } + const alone = own(`createUnit(P1, units.TerranMarine, 2, locations.Anywhere);`).graph; + expect(alone.triggers.flatMap((t) => t.acts).filter((a) => a.record).map((a) => a.record!.type)).toEqual([44]); + }); +}); + +describe("a map of our own, the game's actions with fields from values", () => { + it("makes a field from a value a place of its variable, written with what else shares its dword", () => { + const { graph } = own(`let kind: UnitType = units.TerranMarine;\nlet n = 3;\nif (n > 2) kind = units.TerranFirebat;\nsetDeaths(P1, kind, "add", 1);`); + const t = graph.triggers.find((t) => t.acts.some((a) => a.record?.type === 45))!; + const k = t.acts.findIndex((a) => a.record?.type === 45); + expect(t.acts[k].record!.unitId).toBe(0); + // (The unit type, the action's kind and its modifier share a dword: a write of the variable is the three.) + const writes = graph.triggers.flatMap((x) => x.acts).filter((a) => "at" in a.dword && a.dword.at === t.label && typeof a.dword.field === "object" && "act" in a.dword.field && a.dword.field.part === "modifier"); + expect(writes.map((a) => a.value)).toContain(32 + 45 * 2 ** 16 + 8 * 2 ** 24); + }); + + it("does a unit count 255 at a time only where its range goes past 255, and nothing at 0 or below only where it may be", () => { + const loops = (count: string) => { + const { graph } = own(`${count}\ncreateUnit(P1, units.TerranMarine, count, locations.Anywhere);`); + return graph.triggers.filter((t) => t.acts.some((a) => a.record?.type === 44)).length; + }; + // (Known to be 3: one Create Unit, its count in its field. Anything: one in the loop of 255 and one for the rest.) + expect(loops(`let count = 3;`)).toBe(1); + expect(loops(`let count = 3;\nif (elapsedTime(">=", 1)) count = 400;`)).toBe(2); + }); + + it("puts a Transmission's started mark and its trigger's current action back after it, so that it shows each time", () => { + const { graph } = own(`transmission("Report in.", units.TerranMarine, locations.Anywhere, "add", 0, "sound\\\\glue\\\\mousedown2.wav", 500);\nprint("after");`); + const k = graph.triggers.findIndex((t) => t.acts[0]?.record?.type === 7); + expect(graph.triggers[k].acts).toHaveLength(1); + expect(graph.triggers[k + 1].acts.slice(0, 2).map((a) => ("at" in a.dword ? a.dword.field : null))).toEqual([{ act: 0, part: "flags" }, "current"]); + }); + + it("fills a template for a text made while the game runs, the action's text field naming it", () => { + const { graph } = own(`let wave = 2;\nwave += 1;\nsetMissionObjectives(\`Survive wave \${wave}\`);`); + const a = graph.triggers.flatMap((t) => t.acts).find((a) => a.record?.type === 12)!; + expect(graph.templates?.map((t) => t.id)).toContain(a.textOf); + }); +});