Translate the whole repository to English

The kit is going public, so everything a reader sees is now English:
27 model sources, the core vocabulary, meta.ts (titles, descriptions and
every slider label), the viewer UI, all script output, REFERENCE.md and
the READMEs.

Rules applied: only comments, doc comments and human-facing strings were
touched. Identifiers, numbers, logic, registry item ids, part names and
material slot names are unchanged — those are a published contract and
were already English.

Verified rather than assumed: for all 53 changed .ts files the code
skeleton (source with comments stripped and string contents replaced by a
placeholder) is byte-identical before and after. Full gate is green —
typecheck, registry conformance, ~510 model checks, GLB round trip and a
production build.

Also fixed here:
- index.html and viewer.html were missed by the translation pass because
  the file list only covered .ts/.md/.css. Both had lang="tr" and Turkish
  titles.
- The viewer formatted triangle counts with toLocaleString('tr-TR'), which
  prints 15.425 rather than 15,425.
- REFERENCE.md's model table header did not match what
  scripts/catalog-table.ts emits, so the next regeneration would have
  silently changed it. Aligned to the generator.
- Removed scripts/probe.ts and scripts/zdiag.ts; they were throwaway
  diagnostics that got committed by accident.
This commit is contained in:
CRTkafa 2026-08-21 14:35:13 +07:00
parent 72f219d087
commit 50c7fc60b9
97 changed files with 4572 additions and 4270 deletions

View file

@ -1,23 +1,26 @@
/**
* @medieval-kit/pitch-torch
*
* Ziftli meşale: budaklı bir sopa, ucuna sarılmış zift emdirilmiş bez, üstünde
* alev. Dönemin aydınlatması tam olarak bu — mum pahalıydı, meşale bedavaydı.
* Pitch torch: a knotty stick, pitch-soaked cloth wound onto its end, a flame
* above. This was exactly the period's lighting — candles were expensive,
* torches were free.
*
* Kitin ilk ANİMASYONLU modeli. Alev `update()` ile titriyor ve titremenin
* kaynağı rastgelelik DEĞİL, geçen sürenin sinüs toplamı. Bunun üç sebebi var:
* The kit's first ANIMATED model. The flame flickers via `update()` and the
* source of that flicker is NOT randomness but a sum of sines of elapsed time.
* There are three reasons for that:
*
* - Rastgelelik determinizmi bozar. Kitin her yerinde `Math.random()` yasak;
* alev de istisna olamaz, yoksa aynı tohumlu iki meşale ayrışır.
* - Uyumsuz frekanslı iki sinüs gözle "tekrar etmeyen" bir salınım veriyor.
* Tek sinüs metronom gibi atardı.
* - Tüketici `update()` çağırmazsa model tamamen durur. Kendi kendine dönen
* bir zamanlayıcı kurmak protokolün "tüketici döngüye sahiptir" ilkesini
* çiğnerdi.
* - Randomness breaks determinism. `Math.random()` is banned everywhere in
* the kit; the flame can be no exception, otherwise two torches with the
* same seed diverge.
* - Two sines at incommensurate frequencies give an oscillation that reads as
* "non-repeating" to the eye. A single sine would tick like a metronome.
* - If the consumer never calls `update()`, the model stops completely.
* Setting up a self-driving timer would violate the protocol's principle
* that "the consumer owns the loop".
*
* Alev ayrıca ışık YAYMAZ. Meşalenin sahneyi aydınlatması isteniyorsa tüketici
* `parts.flame.anchor`'a bir PointLight takar — modelin sahnenin ışık bütçesi
* hakkında varsayım yapmaya hakkı yok.
* The flame also EMITS NO LIGHT. If the torch is meant to light the scene, the
* consumer attaches a PointLight to `parts.flame.anchor` — the model has no
* right to make assumptions about the scene's light budget.
*/
import {
createKitModel,
@ -31,15 +34,15 @@ import {
} from '../core/index.ts'
export interface PitchTorchConfig {
/** Toplam sap uzunluğu (metre). */
/** Total shaft length (metres). */
readonly length: number
/** Sap yarıçapı (metre). */
/** Shaft radius (metres). */
readonly radius: number
/** Bez sargının uzunluğu, sapın oranı olarak. */
/** Length of the cloth wrap, as a fraction of the shaft. */
readonly wrapLength: number
/** Alev yüksekliği, sargı uzunluğunun oranı olarak. */
/** Flame height, as a fraction of the wrap length. */
readonly flameHeight: number
/** Titremenin genliği. 0 = sabit alev. */
/** Amplitude of the flicker. 0 = steady flame. */
readonly flicker: number
readonly seed: number
}
@ -56,13 +59,13 @@ export const pitchTorchDefaults: PitchTorchConfig = {
export type PitchTorchParts = 'shaft' | 'wrap' | 'flame'
export interface PitchTorchActions {
/** Alevi yakar/söndürür. Sönükken `flame` parçası tamamen gizlenir. */
/** Lights/extinguishes the flame. When out, the `flame` part is fully hidden. */
setLit(lit: boolean): void
isLit(): boolean
}
export function createModel(overrides: Partial<PitchTorchConfig> = {}) {
// Durum inşanın DIŞINDA: `configure()` meşaleyi söndürmemeli.
// State lives OUTSIDE the build: `configure()` must not put the torch out.
let lit = true
let elapsed = 0
@ -77,9 +80,10 @@ export function createModel(overrides: Partial<PitchTorchConfig> = {}) {
const wrapLength = config.length * config.wrapLength
const wrapBase = half - wrapLength
// --- Sap -----------------------------------------------------------
// Budaklı bir dal: yarıçapı boyunca dalgalanıyor. Düz bir silindir
// fabrikasyon görünürdü, oysa meşale ormandan kesilmiş bir çubuktur.
// --- Shaft -----------------------------------------------------------
// A knotty branch: its radius wavers along its length. A straight
// cylinder would look manufactured, whereas a torch is a stick cut in
// the forest.
const knots = 6
const shaftProfile: Level[] = Array.from({ length: knots + 1 }, (_, i) => {
const t = i / knots
@ -93,10 +97,10 @@ export function createModel(overrides: Partial<PitchTorchConfig> = {}) {
{ colourTop: tint('oak', -0.02) },
)])
// --- Sargı ---------------------------------------------------------
// Zift emdirilmiş bez: sapa göre kalın, uca doğru şişkin, tepesi düz.
// Kömür yuvası kullanılıyor çünkü zift kurumla kaplanır — meşe rengi
// burada yalan olurdu.
// --- Wrap ------------------------------------------------------------
// Pitch-soaked cloth: thick relative to the shaft, swollen towards the
// end, flat on top. The char slot is used because pitch gets coated in
// soot — an oak colour would be a lie here.
const wrapProfile: Level[] = [
{ y: wrapBase - wrapLength * 0.12, radius: config.radius * 1.15 },
{ y: wrapBase + wrapLength * 0.22, radius: config.radius * 2.35 },
@ -108,16 +112,18 @@ export function createModel(overrides: Partial<PitchTorchConfig> = {}) {
{ colourTop: tint('charHot', -0.12) },
)])
// --- Alev ----------------------------------------------------------
// Alev geometrisi KENDİ orijininde üretiliyor ve anchor sargının ucuna
// taşınıyor. Titreme anchor'ın ölçeğini oynattığı için bu şart: orijini
// dipte olmayan bir alev, ölçeklenince sargının içine gömülürdü.
// --- Flame -----------------------------------------------------------
// The flame geometry is built at ITS OWN origin and the anchor is moved
// to the end of the wrap. This is required because the flicker drives the
// anchor's scale: a flame whose origin is not at its base would sink into
// the wrap when scaled.
const flameHeight = wrapLength * config.flameHeight
// Alev profili ikinci hâli. İlki tek eğriyle dipten uca inceliyordu ve
// render'da ROKET BURNU gibi duruyordu — pürüzsüz, simetrik, sivri.
// Alev öyle değil: dibi geniş ve şişkin, ortasında bir boğum, ucu ise
// sivri değil YIRTIK. Aşağıdaki profil o boğumu veriyor, `roughen` de
// simetriyi bozuyor.
// This is the flame profile's second version. The first tapered from base
// to tip on a single curve and in render it looked like a ROCKET NOSE —
// smooth, symmetric, pointed. A flame is not like that: its base is wide
// and swollen, it has a waist in the middle, and its tip is not pointed
// but TORN. The profile below gives that waist, and `roughen` breaks the
// symmetry.
const flameProfile: Level[] = [
{ y: 0, radius: config.radius * 2.05 },
{ y: flameHeight * 0.14, radius: config.radius * 2.75 },
@ -133,12 +139,12 @@ export function createModel(overrides: Partial<PitchTorchConfig> = {}) {
const flame = mergeColoured([
outer,
// İç çekirdek: dıştakinden daha küçük ve daha beyaz. İki katman
// alevin derinliği olduğu izlenimini veriyor — tek koni yassı durur.
// Inner core: smaller and whiter than the outer one. Two layers give
// the impression that the flame has depth — a single cone reads flat.
//
// Dibi KAPATILMIYOR: dış koninin tabanıyla aynı düzleme oturup
// titriyordu. Zaten dışın içinde kaldığı için görünmez, dolayısıyla
// kapak hem gereksiz hem zararlıydı.
// Its base is NOT CAPPED: it was coplanar with the base of the outer
// cone and z-fought with it. It is invisible anyway, being inside the
// outer shell, so the cap was both unnecessary and harmful.
prismGeometry(
config.radius * 1.25, config.radius * 0.1, flameHeight * 0.48, 5,
[0, flameHeight * 0.3, 0], tint('ember', 0.22, 0.3),
@ -166,23 +172,25 @@ export function createModel(overrides: Partial<PitchTorchConfig> = {}) {
},
update: (dt, { parts, getConfig }) => {
// Sönük meşale ilerlemez: yeniden yakıldığında alev kaldığı yerden
// değil, aynı faz noktasından devam etsin diye. Aksi hâlde uzun süre
// sönük kalan bir meşale yanınca rastgele bir boyda başlardı.
// An extinguished torch does not advance: so that when it is relit the
// flame resumes from the same phase point rather than wherever it drifted
// to. Otherwise a torch left out for a long time would start at an
// arbitrary size when lit.
if (!lit) return
const config = getConfig()
const amount = config.flicker
if (amount === 0) return
elapsed += Math.max(0, dt)
// Uyumsuz frekanslar: 11.3 ve 19.7 birbirinin katı değil, dolayısıyla
// toplamın periyodu gözle yakalanamayacak kadar uzun.
// Incommensurate frequencies: 11.3 and 19.7 are not multiples of each
// other, so the period of the sum is too long for the eye to catch.
const pulse = Math.sin(elapsed * 11.3) * 0.09 + Math.sin(elapsed * 19.7 + 1.4) * 0.055
const sway = Math.sin(elapsed * 7.1 + 0.6) * 0.05 + Math.sin(elapsed * 13.9) * 0.028
const anchor = parts.flame.anchor
// Boy ve en TERS yönde oynuyor: alev uzarken incelir. Aynı yönde
// ölçeklemek alevi nefes alan bir balon gibi gösteriyordu.
// Height and width move in OPPOSITE directions: a flame narrows as it
// stretches. Scaling them the same way made the flame look like a
// breathing balloon.
anchor.scale.set(1 - pulse * 0.55 * amount, 1 + pulse * amount, 1 - pulse * 0.55 * amount)
anchor.rotation.z = sway * amount
anchor.rotation.x = Math.sin(elapsed * 9.4 + 2.1) * 0.038 * amount