Der Loader zieht jetzt die baseColorTexture jedes Materials aus dem
BIN-Chunk und dekodiert sie (PNG, via png-Crate) → model::Texture mit
Pixeln. Die Namenskonvention "Materialname = Dateiname" und der
assets/textures/-Pfad für Welt-Texturen entfallen; assets/textures/
bleibt nur noch für UI/Fonts/Platzhalter.
- engine::image: neuer, geteilter RGBA8-Image-Typ (aus tga.rs gezogen)
plus PNG-Decoder. tga.rs liefert weiter denselben Typ.
- gltf: material -> texture -> image -> bufferView auflösen, Bild aus
BIN dekodieren; fehlt ein Glied, bleibt image None (Platzhalter).
KHR_texture_transform (Blenders Mapping-Node) wird beim Laden in die
UVs eingebacken.
- model::Model: materials: Vec<String> -> textures: Vec<Texture>.
- render: die Pixel je Schlüssel aus den Modellen sammeln (erstes
Vorkommen gewinnt, Schlüssel ohne Bild -> Platzhalter).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0196AM4UyAJEXVtPvp5DdKr8
Der Kern-KV war HashMap<String, bladeink::ValueType> — jedes Modul, das
eine Spielvariable las, hing damit an der öffentlichen API der Story-Crate.
"Konvertierung gratis" war der Grund, aber der Preis war eine
Dependency-Kante quer durch engine/.
Neuer eigener kv::Value (Bool/Int/Float/Str). Die Umrechnung auf Inks
ValueType passiert jetzt allein in ink.rs (to_ink/from_ink) — die einzige
Stelle im Kern, die bladeink überhaupt kennt. from_ink verwirft
Ink-eigene Typen (Listen, Divert-Referenzen), die im KV nichts verloren
haben.
- format_value → Display für Value
- coerce_to_int → Value::as_int (gleiche Semantik); coerce_to_bool hatte
außer Tests keinen Aufrufer und entfällt, die Tests prüfen jetzt die
gespeicherte Variante direkt
- bladeink bleibt Dependency (ink.rs braucht es), nur nicht mehr im
KV-Vertrag
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0196AM4UyAJEXVtPvp5DdKr8
Die PostChain lag als engine::post im Kern, nur damit die Konsole
`post` durch den exec-Trichter schalten konnte — Zustand, der allein
das Render-Frontend angeht und nichts mit der Simulation zu tun hat.
Jetzt kennt der Kern Post-Effekte nur als deferred Action (wie
play_sound): das `post <name> on|off`-Verb queued Action::SetPost,
die Session reicht sie als take_post()-Queue durch, das Fenster wendet
sie auf seine eigene PostChain an. Damit schalten Trigger und Ink-Tags
Effekte, die vorher nur der Konsolenbefehl erreichte.
- engine/post.rs entfällt; PostChain wandert nach render::post
- Session verliert das post-Feld, bekommt pending_post + take_post
- der `post`-Konsolenbefehl ist ein Shim wie `mode`; Ketten-Listing
und nackter Toggle fallen weg (letzterer bewusst, vgl. 8bee832)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0196AM4UyAJEXVtPvp5DdKr8
Bildweite Effekte gehören nicht in den Szenen-Shader. Cel-Ramp und
Sobel-Outline sollen dazukommen, ohne einander (oder scene.wgsl) zu
kennen — also eine Kette gleichrangiger Fullscreen-Pässe zwischen
Szene und Blit, Ping-Pong über zwei interne Targets.
Der Vertrag ist für alle gleich: Farbtextur rein (group 0/binding 0),
Farbe raus. Die Vertex-Stufe teilen sich alle über fullscreen.wgsl,
damit eine Effekt-Datei nur aus ihrem fs_main besteht. Ein neuer Effekt
ist eine .wgsl plus eine Zeile in EFFECTS. Bei 640×480 kostet ein
eigener Pass je Effekt praktisch nichts — deshalb kein Über-Shader.
Erster Bewohner ist die RGB555-Quantisierung samt Bayer-Dither, aus
scene.wgsl herausgelöst. Das UI zeichnet jetzt in einem eigenen Pass
hinter der Kette, sonst würde der Dither auch den Text quantisieren,
den er bisher nicht anfasst; damit braucht SpritePass kein
Tiefen-Attachment mehr.
Geschaltet wird über den exec-Trichter (`post`, `post <e> on|off`).
Die Namen kennt nur das Frontend: es meldet seine Kette beim Start in
engine::post an, wie render::audio seine Emitter aus engine::audio
zieht. Der Kern bekommt dadurch keine GPU zu sehen.
Das Bild soll unverändert bleiben — gleicher Bayer-Kernel, gleiche
Koordinaten, gleiche Reihenfolge relativ zum UI. Verifiziert sind
Tests, clippy und ein Start ohne Validation-Fehler; ein Pixelvergleich
steht aus.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Befehl war ein Toggle zwischen Play und Free — als getippter
Konsolen-Befehl die falsche Form (man sieht nicht, wohin er schaltet)
und deckungsgleich mit `mode play` / `mode free`. Keine Taste, kein
Menü-Knopf, keine Referenz in assets/ hing daran.
Damit stimmt auch die Doku von set_mode wieder: `menu` und die
SetMode-Action sind jetzt tatsächlich die einzigen Schreibpfade.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UVs laufen jetzt durchgehend in glTF-/tga-Konvention (der Flip-Hin-und-
Zurück stammte vom gelöschten OBJ-Pfad). Kaputte Welt-Texturen fallen auf
den Platzhalter statt zu panicken. Player::eye() statt vierfacher
Augenpunkt-Rechnung. hide_object war ein irl3d-Demo-Relikt ohne
Konsument.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
engine/wav.rs (Decoder) und render/audio.rs (Mixer, 8 SFX-Voices +
Loop-Voice je Emitter) aus irl3d eingebettet; Emitter-Logik headless in
engine/audio.rs. Empties mit sound/radius-Property senden positional,
play_sound-Signale laufen als SFX über Session::take_sounds.
Platzhalter-Sounds + Emitter in der Testszene; artist.md fasst die
Content-Regeln für die Autorin zusammen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
engine/map.rs + render/brush.rs raus, Welt kommt komplett aus .glb.
Spawn via Empty mit role=spawn statt info_player_start; testscene.glb
(Boden, Wände, Spawn) ersetzt test.map.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
col_*-Namenspräfix raus: Rollen steuern nur noch Custom Properties
(collide=1 sichtbar+Collider, "proxy" unsichtbar, signal wie gehabt).
Collider sind die konvexe Hülle der Vertices — konkave Meshes erlaubt.
glb ist der einzige Modell-Pfad; suzanne.obj → suzanne.glb.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Blender wird das Haupt-3D-Tool: zwei Loader (engine/gltf.rs für .glb als
Hauptpfad, engine/obj.rs aus irl3d eingebettet) liefern ein neutrales
engine/model.rs::Model. Loader sind reine Dekoder; das Frontend zieht sie
und schiebt das Ergebnis per Session::load_props in den State, der
Renderer baut sein Mesh (render/props.rs, verschmolzen mit der
Brush-Welt über eine geteilte Texturliste).
glTF trägt Blender-Custom-Properties (node.extras → Object::props) und
Empties (Nodes ohne Mesh) als Entity-Kanal. Namenskonvention: `col_*` =
unsichtbarer Collision-Proxy, aus dem collision::add_model konvexe
Ebenen-Sets baut (konvex-only, daher kein Default-Collider auf
Sichtgeometrie).
Trigger (engine/trigger.rs) hängen an der Custom Property `signal`:
mit Collider = Point-and-Click-Ziel (Session::pick raycastet aus der
Bildmitte, Treffer läuft durch den `use <name>`-Trichter), ohne Collider
= unsichtbare Betretens-Zone, die einmal je Eintritt feuert. Gefeuert
wird nur über Session::exec — der Eingabe-Trichter bleibt der einzige
Weg in den State.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>