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:
parent
72f219d087
commit
50c7fc60b9
97 changed files with 4572 additions and 4270 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue