OpenHP1 is an open-source Rust reimplementation of the original Harry Potter and the Philosopher's/Sorcerer's Stone PC game. It must load and run files from a legally obtained original installation rather than replacing them with committed copies.
- Treat the workspace manifests and current code as authoritative for the crate list and implemented features.
- Keep this file updated in the same logical change set whenever its architecture, workflow, or durable constraints change.
- Keep focused documentation under
docs/up to date with the implementation. Record important discoveries about formats, runtime behavior, coordinates, compatibility, or validation there instead of leaving them only in chat, commit messages, or temporary notes. - Keep the top-level
SETTINGS.mduser reference aligned withOpenHP1.ini. Update it in the same logical change whenever a section, key, default, accepted value, validation rule, or settings-file location changes. - Keep progress reports, completed milestones, corpus counts, and dependency version snapshots out of this file.
res/contains the local original game installation and is gitignored.- Never commit original files, extracted assets, embedded script source, generated asset dumps, or binary fixtures copied from the game.
- Public tests must construct small synthetic packages or data.
- Local integration tests may read
res/, but must never modify it or be required by public CI. - Do not use leaked proprietary source code. Reverse engineering must be based on behavior, documented formats, legally obtained binaries/packages, and properly licensed reference projects.
- Keep a clear non-affiliation/trademark notice for public releases.
- Use the local SurrealEngine clone at
/Users/splitty/Developer/SurrealEnginerather than downloading temporary copies. UE Viewer is also an acceptable licensed reference.
- Reuse existing crates, modules, helpers, and data flow before adding another abstraction. Add a crate only for a real independent responsibility or dependency boundary.
- Keep the dependency graph acyclic. Package decoding is the foundation; typed map, mesh, texture, and script decoding sits above it; scene/runtime assembly sits above those; renderers and executables consume decoded data.
- The renderer must not know package paths, byte offsets, imports, exports, or filesystem resolution. Keep decoded CPU assets separate from GPU resources.
- Keep Unreal-to-render coordinate conversion in one module. Do not scatter axis swaps, handedness changes, winding fixes, or rotator sign changes across loaders and shaders.
- Keep crate roots as small module indexes or executable entry points. Preserve public re-exports when reorganizing internals.
- Package-specific mesh decoding and pose sampling belong in
openhp1-mesh. Keep playback orchestration with its current consumer until another consumer creates a genuinely reusable animation responsibility. - Package-specific audio decoding and platform playback belong in
openhp1-audio; keep game/runtime audio policy out of the renderer. openhp1-vieweruses eframe for its UI and displays the renderer's offscreen target.openhp1-rendermust remain independent of eframe.openhp1-window-editoruses eframe and the read-only package/texture decoders to author derived window masks without storing original texture pixels.openhp1-sceneembeds those masks and associates them with decoded texture indices soopenhp1-renderremains independent of package names and paths.openhp1-launcheruses eframe for game-data configuration and starts the siblingopenhp1-gameexecutable. Both executables resolve the original game root throughopenhp1-packageandOpenHP1.ini.- Classic and modern rendering share one scene, batching, material, sky, and
animation path. Keep HDR post-processing, GI, and reflection resources
internal to
openhp1-renderrather than duplicating the renderer or exposing package data. openhp1-gameowns its winit/wgpu event loop. Do not make eframe own the game loop, raw input, cursor grabbing, fullscreen behavior, or frame scheduling.- Use
kirafor PCM playback, mixing, spatial sound, and streaming. Keep package decoding and platform playback inopenhp1-audio, and keep the original game's playback policy in the runtime and game. - Preserve UE1 BSP collision and movement semantics rather than substituting a generic physics engine.
- Prefer the standard library and existing dependencies. Do not add an ECS, Tokio, a job system, a general asset database, dependency injection, or other infrastructure without a current demonstrated need.
Detailed package and map notes live in
docs/unreal-package-format.md and
docs/map-format.md.
.unr,.utx,.uax,.umx, and.ushare the Unreal package container with little-endian magic0x9E2A83C1. Support the local package-version range from 61 through 76.- Discover packages from configured
[Core.System] Paths, confirm package magic, and resolve package names and Windows-era filenames case-insensitively. Do not trust only the final extension; localized files use suffixes such as.int_uax,.spa_uax, and.hun_utx. - Discover installed languages from shipped
Default.inivariants and select their matching localization and package-overlay directories without writing to the original game installation. - Use a small bounds-checked archive over
Read + Seekor an in-memory buffer. Validate table counts and offsets, export ranges, integer conversions, and allocation sizes before use. - Malformed or unsupported input must return an error with package path and byte offset, not panic or allocate an attacker-controlled amount of memory.
- Implement signed compact indices once and test all byte counts, signs, and boundary cases.
- Model object references explicitly: zero is none, positive values select exports, and negative values select imports.
- Use stable
(package_id, object_index)identities for cyclic UObject graphs. Resolve outer chains and qualified names centrally. - Preserve unknown properties and unsupported exports for inspection. Never guess a serialized layout silently.
- Actor properties depend on class metadata and defaults; do not decode map actors as isolated tagged-property bags.
- Follow the
Levelexport's exact world-model reference. Do not replace it with a largest-export heuristic. - Keep the loader read-only. Do not build a package writer until explicitly required.
Keep the focused notes in
docs/renderer.md,
docs/texture-format.md,
docs/mesh-format.md, and
docs/runtime.md aligned with behavior.
- Serialized palette colors are RGBA; do not swap red and blue.
- Base textures and UE1 lightmaps modulate directly in display space using UE1's 2x modulation. Do not insert an sRGB-to-linear conversion.
- Zone zero is valid and inherits ambient settings from the active
LevelInfo. - Actor transforms use positive yaw, upward-positive pitch, and negative roll in yaw/pitch/roll composition order; this is not the inverse view rotation.
- UE1 skeletal meshes use a mirrored ActorX local Y axis. Mirror Y, reverse
triangle winding, negate
RotationOrigin.Yaw, and conjugate ActorX bone orientations before hierarchy composition. - State frames retain their instruction pointer and locals across latent actions. Runtime actions must update persistent actor state and the corresponding scene state without later animation ticks undoing them.
- Keep parsing deterministic, read-only, and independently testable without a GPU or window.
- Leave one focused runnable check for each non-trivial parser or conversion.
- Use synthetic unit fixtures for package headers, compact indices, object references, and malformed offsets.
- Local corpus scans may exercise
res/, but keep paths and expected output out of the repository. - Use UE Viewer and SurrealEngine for differential inspection where useful, while keeping OpenHP1 independent of them.
- For original-game behavior, inspect the shipped packages first: embedded UnrealScript, compiled bytecode and class defaults, configuration, and native binaries. Use reference engines only after that evidence is exhausted.
- Not a single line of engine code may be written without concrete evidence
from the original game engine, shipped UnrealScript, or
Engine.dll, whichever applies. Engine logic must behave identically to the original; workarounds without concrete original-game evidence are explicitly forbidden. - Treat embedded source comments and property dumps as leads, not active behavior; verify them against compiled defaults or bytecode before changing compatibility behavior. Do not tune guessed heuristics before tracing the authored and native path end to end.
- Never add map-, actor-, asset-, coordinate-, or property-pattern workarounds for gameplay behavior. Model the shared engine or UnrealScript semantics that make the original game behave correctly; if that behavior cannot yet be established from original-game evidence or a licensed reference engine, leave the issue unresolved rather than guessing.
- Prefer Semble's
searchtool for code searches. Query by symbol or behavior, follow the returned file and line, and fall back torgor another alternative only when Semble does not find the intended code. - Prefer explicit data structures and boring control flow over macros or generic parser frameworks.
- Fix defects at the shared archive/object/runtime seam rather than adding guards to individual callers.
- Preserve unsupported data and diagnostics instead of hiding incomplete support.
- Preserve the configured
sccachewrapper for fast iteration. In sandboxed agent environments, run Cargo compilation and test commands outside the sandbox with the narrow reusable Cargo approval; never clear or overrideRUSTC_WRAPPERto work around sandbox permissions. - Run test binaries with
cargo nextest run; usecargo test --doconly when doctests are relevant because nextest does not execute them. - Use
cargo checkfor compilation verification. Runcargo buildonly when the task actually needs a runnable binary or another build artifact. - Do not pass
--releaseto agent-run tests or verification builds. The user owns optimized release builds for live testing unless they explicitly ask the agent to run one. - Run focused formatting, compilation, and tests for touched crates. State exactly what ran and whether tests used the local copyrighted corpus.
- Run
cargo fmt --allbefore each commit; format rather than using--check. - Use Conventional Commits format:
type(scope): imperative summary. - Commit each logical change set separately instead of packing unrelated work into one large commit.